Task description
This is part of the new contract state feature, see also Concordium/concordium-node#237
Since the state will look very different in V1 contracts we will need to significantly revise the support for using it. The key features that need to be provided by concordium-std are
- efficient mappings from small objects (e.g., account addresses, token IDs) to (potentially) more complex objects
- efficient sets of small objects (e.g., account addresses, token IDs)
- easy to use state with multiple fields, e.g., a Rust struct as the state of a smart contract
- nested maps and sets should be possible and painless
Some of this will require writing new procedural macros to generate code for users as much as possible, based on their annotations.
As usual, this needs to be provided in two variants, one for use on the chain, and one for unit testing. The latter will involve essentially implementing the same kind of backing radix trie implementation as is used by the node, except the implementation does not have to deal with persistence.
Proposed API for the new contract state
/// Lookup an entry with the given key. The return value is either
/// u64::MAX if the entry at the given key does not exist, or else
/// the first bit of the result is 0, and the remaining bits
/// are an entry identifier that may be used in subsequent calls.
fn state_lookup_entry(key_start: *const u8, key_length: u32) -> u64;
/// Create an empty entry with the given key. The return value is either u64::MAX if
/// creating the entry failed because of an iterator lock on the part of the
/// tree, or else the first bit is 0, and the remaining bits are an entry identifier
/// that maybe used in subsequent calls.
fn state_create_entry(key_start: *const u8, key_length: u32) -> u64;
/// Delete the entry. Returns one of
/// - 0 if the part of the tree this entry was in is locked
/// - 1 if the entry did not exist
/// - 2 if the entry was deleted as a result of this call.
fn state_delete_entry(entry: u64) -> u32;
/// Delete a prefix in the tree, that is, delete all parts of the tree that have
/// the given key as prefix. Returns
/// - 0 if the tree was locked and thus deletion failed.
/// - 1 if the tree **was not locked**, but the key points to an empty part of the tree
/// - 2 if a part of the tree was successfully deleted
fn state_delete_prefix(key_start: *const u8, key_length: u32) -> u32;
/// Construct an iterator over a part of the tree. This **locks the part of the
/// tree that has the given prefix**. Locking means that no deletions or
/// insertions of entries may occur in that subtree.
/// Returns
/// - all 1 bits if too many iterators already exist with this key
/// - all but second bit set to 1 if there is no value in the state with the given key
/// - otherwise the first bit is 0, and the remaining bits are the iterator identifier
/// that may be used in subsequent calls to advance it, or to get its key.
fn state_iterate_prefix(prefix_start: *const u8, prefix_length: u32) -> u64;
/// Return the next entry along the iterator, and advance the iterator.
/// The return value is
/// - u64::MAX if the iterator does not exist (it was deleted, or the ID is invalid)
/// - all but the second bit set to 1 if no more entries are left, the iterator
/// is exhausted. All further calls will yield the same until the iterator is
/// deleted.
/// - otherwise the first bit is 0, and the remaining bits encode an entry
/// identifier that can be passed to any of the entry methods.
fn state_iterator_next(iterator: u64) -> u64;
/// Delete the iterator, unlocking the subtree. Returns
/// - u64::MAX if the iterator does not exist.
/// - 0 if the iterator was already deleted
/// - 1 if the iterator was successfully deleted as a result of this call.
fn state_iterator_delete(iterator: u64) -> u32;
/// Get the length of the key that the iterator is currently pointing at.
/// Returns
/// - u32::MAX if the iterator does not exist
/// - otherwise the length of the key in bytes.
fn state_iterator_key_size(iterator: u64) -> u32;
/// Read a section of the key the iterator is currently pointing at. Returns either
/// - u32::MAX if the iterator has already been deleted
/// - the amount of data that was copied. This will never be more than the supplied length.
fn state_iterator_key_read(iterator: u64, start: *mut u8, length: u32, offset: u32) -> u32;
// Operations on the entry.
/// Read a part of the entry. The arguments are
/// entry ... entry id returned by state_iterator_next or state_create_entry
/// start ... where to write in Wasm memory
/// length ... length of the data to read
/// offset ... where to start reading in the entry
/// The return value is
/// - u32::MAX if the entry does not exist (has been invalidated, or never
/// existed). In this case no data is written.
/// - amount of data that was read. This is never more than length.
fn state_entry_read(entry: u64, start: *mut u8, length: u32, offset: u32) -> u32;
/// Write a part of the entry. The arguments are
/// entry ... entry id returned by state_iterator_next or state_create_entry
/// start ... where to read from Wasm memory
/// length ... length of the data to read
/// offset ... where to start writing in the entry
/// The return value is
/// - u32::MAX if the entry does not exist (has been invalidated, or never
/// existed). In this case no data is written.
/// - amount of data that was written. This is never more than length.
fn state_entry_write(entry: u64, start: *const u8, length: u32, offset: u32) -> u32;
/// Return the current size of the entry in bytes.
/// The return value is either
/// - u32::MAX if the entry does not exist (has been invalidated, or never
/// existed). In this case no data is written.
/// - or the size of the entry.
fn state_entry_size(entry: u64) -> u32;
/// Resize the entry to the given size. Returns
/// - u32::MAX if the entry has already been invalidated
/// - 0 if the attempt was unsuccessful because new_size exceeds maximum entry size
/// - 1 if the entry was successfully resized.
fn state_entry_resize(entry: u64, new_size: u32) -> u32;
Sub-tasks