diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 29d7161ca..21498ab85 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -14,7 +14,7 @@ env: RUST_FMT: nightly-2023-04-01 RUST_VERSION: "1.73" RUST_VERSION_TESTING_LIBRARY: "1.73" - CARGO_CONCORDIUM_VERSION: "3.2.0" + CARGO_CONCORDIUM_VERSION: "3.3.0" jobs: rustfmt: @@ -103,6 +103,33 @@ jobs: run: | RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features --color=always + # Run unit-tests for concordium-std compiled to wasm using cargo concordium test. + std-internal-wasm-test: + name: concordium-std internal wasm tests + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_VERSION }} + + - name: Install Wasm target + run: rustup target install wasm32-unknown-unknown + + - name: Run internal wasm unit test + working-directory: concordium-std + run: | + curl -L https://github.com/Concordium/concordium-smart-contract-tools/releases/download/releases/cargo-concordium/${CARGO_CONCORDIUM_VERSION}/cargo-concordium-linux-amd64 -o /tmp/cargo-concordium + chmod +x /tmp/cargo-concordium + sudo mv /tmp/cargo-concordium /usr/bin/cargo-concordium + cargo concordium test --only-unit-tests -- --features internal-wasm-test + # All templates are generated with the `cargo-generate` command and it is checked that the 'cargo test' command # can be executed without errors on the generated smart contracts. cargo-generate-templates: @@ -145,10 +172,9 @@ jobs: # Run all tests, including doc tests. - name: Run cargo test run: | - CARGO_CCD=cargo-concordium_${{ env.CARGO_CONCORDIUM_VERSION }} - wget https://distribution.concordium.software/tools/linux/$CARGO_CCD - chmod +x $CARGO_CCD - sudo mv $CARGO_CCD /usr/bin/cargo-concordium + curl -L https://github.com/Concordium/concordium-smart-contract-tools/releases/download/releases/cargo-concordium/${CARGO_CONCORDIUM_VERSION}/cargo-concordium-linux-amd64 -o /tmp/cargo-concordium + chmod +x /tmp/cargo-concordium + sudo mv /tmp/cargo-concordium /usr/bin/cargo-concordium mv $PROJECT_NAME ${{ runner.temp }}/ cd ${{ runner.temp }}/$PROJECT_NAME cargo concordium test --out "./concordium-out/module.wasm.v1" @@ -195,10 +221,9 @@ jobs: # Run all tests, including doc tests. - name: Run cargo test run: | - CARGO_CCD=cargo-concordium_${{ env.CARGO_CONCORDIUM_VERSION }} - wget https://distribution.concordium.software/tools/linux/$CARGO_CCD - chmod +x $CARGO_CCD - sudo mv $CARGO_CCD /usr/bin/cargo-concordium + curl -L https://github.com/Concordium/concordium-smart-contract-tools/releases/download/releases/cargo-concordium/${CARGO_CONCORDIUM_VERSION}/cargo-concordium-linux-amd64 -o /tmp/cargo-concordium + chmod +x /tmp/cargo-concordium + sudo mv /tmp/cargo-concordium /usr/bin/cargo-concordium mv $PROJECT_NAME ${{ runner.temp }}/ cd ${{ runner.temp }}/$PROJECT_NAME cargo concordium test --out "./concordium-out/module.wasm.v1" @@ -560,18 +585,9 @@ jobs: args: --manifest-path ${{ matrix.lib-crates }} --target=${{ matrix.target }} --features=${{ matrix.features }} -- -D warnings check-std-no-std: - name: Build on nightly, + name: Build no-std on nightly, runs-on: ubuntu-latest needs: rustfmt - strategy: - matrix: - target: - - wasm32-unknown-unknown - - crates: - - concordium-std/Cargo.toml - - concordium-cis2/Cargo.toml - steps: - name: Checkout sources uses: actions/checkout@v2 @@ -583,14 +599,16 @@ jobs: with: profile: minimal toolchain: nightly - target: ${{ matrix.target }} + target: wasm32-unknown-unknown override: true - - name: Run cargo check with no-std - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path ${{ matrix.crates }} --target=${{ matrix.target }} --no-default-features + - name: Run no-std build concordium-std + working-directory: concordium-std + run: cargo build --target wasm32-unknown-unknown --no-default-features --features bump_alloc + + - name: Run no-std build concordium-cis2 + working-directory: concordium-cis2 + run: cargo build --target wasm32-unknown-unknown --no-default-features --features concordium-std/bump_alloc check-no-std-examples: name: Build on nightly, @@ -864,11 +882,9 @@ jobs: - name: Download and install Cargo Concordium run: | - CARGO_CCD=cargo-concordium_${{ env.CARGO_CONCORDIUM_VERSION }} - wget https://distribution.concordium.software/tools/linux/$CARGO_CCD - chmod +x $CARGO_CCD - sudo mv $CARGO_CCD /usr/bin/cargo-concordium - + curl -L https://github.com/Concordium/concordium-smart-contract-tools/releases/download/releases/cargo-concordium/${CARGO_CONCORDIUM_VERSION}/cargo-concordium-linux-amd64 -o /tmp/cargo-concordium + chmod +x /tmp/cargo-concordium + sudo mv /tmp/cargo-concordium /usr/bin/cargo-concordium # The 'smart-contract-upgrade' example has a v1 and v2 contract and the tests in v1 needs the wasm module from v2 for upgrading. - name: Build contract-upgrade version 2 module if needed if: ${{ matrix.crates == 'examples/smart-contract-upgrade/contract-version1' }} diff --git a/concordium-std/CHANGELOG.md b/concordium-std/CHANGELOG.md index 71466611c..fd456cf86 100644 --- a/concordium-std/CHANGELOG.md +++ b/concordium-std/CHANGELOG.md @@ -6,6 +6,7 @@ via the `HasHost::contract_module_reference` and `HasHost::contract_name` functions. These are only available from protocol version 7, and as such are guarded by the `p7` feature flag. +- Add two ordered collections: `StateBTreeMap` and `StateBTreeSet`. These are based on [B-Tree](https://en.wikipedia.org/wiki/B-tree), but where each node is stored in the low-level smart contract key-value store. Use one of these when needing operations related to the ordering of the keys, such as `higher(k)` providing the smallest key in collection which is stricly greater than `k`. ## concordium-std 10.0.0 (2024-02-22) diff --git a/concordium-std/Cargo.toml b/concordium-std/Cargo.toml index 4530c3541..67a812e07 100644 --- a/concordium-std/Cargo.toml +++ b/concordium-std/Cargo.toml @@ -3,7 +3,7 @@ name = "concordium-std" version = "10.0.0" authors = ["Concordium "] edition = "2021" -rust-version = "1.66" +rust-version = "1.73" license = "MPL-2.0" description = "A standard library for writing smart contracts for the Concordium blockchain in Rust." homepage = "https://github.com/Concordium/concordium-rust-smart-contracts/" @@ -29,6 +29,9 @@ features = ["smart-contract"] default = ["std"] std = ["concordium-contracts-common/std"] wasm-test = ["concordium-contracts-common/wasm-test"] +# Own internal wasm-tests leak out to the smart contracts using this library, +# so a separate feature 'internal-wasm-test' is introduced for these. +internal-wasm-test = ["wasm-test", "concordium-quickcheck"] build-schema = ["concordium-contracts-common/build-schema"] crypto-primitives = ["sha2", "sha3", "secp256k1", "ed25519-zebra"] concordium-quickcheck = ["getrandom", "quickcheck", "concordium-contracts-common/concordium-quickcheck", "std"] @@ -38,7 +41,8 @@ bump_alloc = [] p7 = [] [lib] -crate-type = ["rlib"] +# cdylib is needed below to compile into a wasm module with internal unit tests. +crate-type = ["cdylib", "rlib"] [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/concordium-std/src/impls.rs b/concordium-std/src/impls.rs index f25e77184..ab8782693 100644 --- a/concordium-std/src/impls.rs +++ b/concordium-std/src/impls.rs @@ -5,16 +5,16 @@ use crate::{ convert::{self, TryInto}, fmt, marker::PhantomData, - mem, num, + mem::{self, MaybeUninit}, + num, num::NonZeroU32, - prims, + prims, state_btree, traits::*, types::*, vec::Vec, String, }; pub(crate) use concordium_contracts_common::*; -use mem::MaybeUninit; /// Mapped to i32::MIN + 1. impl convert::From<()> for Reject { @@ -775,8 +775,6 @@ where /// "allocator"/state builder stores "next location". The values stored at this /// location are 64-bit integers. const NEXT_ITEM_PREFIX_KEY: [u8; 8] = 0u64.to_le_bytes(); -#[cfg(test)] -const GENERIC_MAP_PREFIX: u64 = 1; /// Initial location to store in [NEXT_ITEM_PREFIX_KEY]. For example, the /// initial call to "new_state_box" will allocate the box at this location. pub(crate) const INITIAL_NEXT_ITEM_PREFIX: [u8; 8] = 2u64.to_le_bytes(); @@ -920,8 +918,10 @@ where /// Inserts the value with the given key. If a value already exists at the /// given key it is replaced and the old value is returned. - pub fn insert(&mut self, key: K, value: V) -> Option { - let key_bytes = self.key_with_map_prefix(&key); + /// This only borrows the key, needed internally to avoid the need to clone + /// it first. + pub(crate) fn insert_borrowed(&mut self, key: &K, value: V) -> Option { + let key_bytes = self.key_with_map_prefix(key); // Unwrapping is safe because iter() holds a reference to the stateset. match self.state_api.entry(key_bytes) { EntryRaw::Vacant(vac) => { @@ -938,6 +938,18 @@ where } } + /// Inserts the value with the given key. If a value already exists at the + /// given key it is replaced and the old value is returned. + /// + /// *Caution*: If `Option` is to be deleted and contains a data structure + /// prefixed with `State` (such as [StateBox](crate::StateBox) or + /// [StateMap](crate::StateMap)), then it is important to call + /// [`Deletable::delete`](crate::Deletable::delete) on the value returned + /// when you're finished with it. Otherwise, it will remain in the + /// contract state. + #[must_use] + pub fn insert(&mut self, key: K, value: V) -> Option { self.insert_borrowed(&key, value) } + /// Get an entry for the given key. pub fn entry(&mut self, key: K) -> Entry<'_, K, V, S> { let key_bytes = self.key_with_map_prefix(&key); @@ -1156,24 +1168,20 @@ impl<'a, S: HasStateApi, V: Serial + DeserialWithState> crate::ops::DerefMut /// When dropped, the value, `V`, is written to the entry in the contract state. impl<'a, V: Serial, S: HasStateApi> Drop for StateRefMut<'a, V, S> { - fn drop(&mut self) { - if let Some(value) = self.lazy_value.get_mut() { - let entry = self.entry.get_mut(); - entry.move_to_start(); - value.serial(entry).unwrap_abort() - } - } + fn drop(&mut self) { self.store_mutations() } } impl<'a, V, S> StateRefMut<'a, V, S> where - V: Serial + DeserialWithState, + V: Serial, S: HasStateApi, { /// Get a shared reference to the value. Note that [StateRefMut](Self) also /// implements [Deref](crate::ops::Deref) so this conversion can happen /// implicitly. - pub fn get(&self) -> &V { + pub fn get(&self) -> &V + where + V: DeserialWithState, { let lv = unsafe { &mut *self.lazy_value.get() }; if let Some(v) = lv { v @@ -1185,7 +1193,9 @@ where /// Get a unique reference to the value. Note that [StateRefMut](Self) also /// implements [DerefMut](crate::ops::DerefMut) so this conversion can /// happen implicitly. - pub fn get_mut(&mut self) -> &mut V { + pub fn get_mut(&mut self) -> &mut V + where + V: DeserialWithState, { let lv = unsafe { &mut *self.lazy_value.get() }; if let Some(v) = lv { v @@ -1195,7 +1205,11 @@ where } /// Load the value referenced by the entry from the chain data. - fn load_value(&self) -> V { + fn load_value(&self) -> V + where + V: DeserialWithState, { + // Safe to unwrap below, since the entry can only be `None`, using methods which + // are consuming self. let entry = unsafe { &mut *self.entry.get() }; entry.move_to_start(); V::deserial_with_state(&self.state_api, entry).unwrap_abort() @@ -1203,6 +1217,8 @@ where /// Set the value. Overwrites the existing one. pub fn set(&mut self, new_val: V) { + // Safe to unwrap below, since the entry can only be `None`, using methods which + // are consuming self. let entry = self.entry.get_mut(); entry.move_to_start(); new_val.serial(entry).unwrap_abort(); @@ -1212,8 +1228,11 @@ where /// Update the existing value with the given function. pub fn update(&mut self, f: F) where + V: DeserialWithState, F: FnOnce(&mut V), { let lv = self.lazy_value.get_mut(); + // Safe to unwrap below, since the entry can only be `None`, using methods which + // are consuming self. let entry = self.entry.get_mut(); let value = if let Some(v) = lv { v @@ -1228,6 +1247,20 @@ where entry.move_to_start(); value.serial(entry).unwrap_abort() } + + /// Write to the state entry if the value is loaded. + pub(crate) fn store_mutations(&mut self) { + if let Some(value) = self.lazy_value.get_mut() { + // Safe to unwrap below, since the entry can only be `None`, using methods which + // are consuming self. + let entry = self.entry.get_mut(); + entry.move_to_start(); + value.serial(entry).unwrap_abort(); + } + } + + /// Drop the ref without storing mutations to the state entry. + pub(crate) fn drop_without_storing(mut self) { *self.lazy_value.get_mut() = None; } } impl Serial for StateMap { @@ -2369,48 +2402,53 @@ where } } -#[cfg(test)] -/// Some helper methods that are used for internal tests. -impl StateBuilder -where - S: HasStateApi, -{ - /// Get a value from the generic map. - /// `Some(Err(_))` means that something exists in the state with that key, - /// but it isn't of type `V`. - pub(crate) fn get>(&self, key: K) -> Option> { - let key_with_map_prefix = Self::prepend_generic_map_key(key); +impl StateBuilder { + /// Create a new empty [`StateBTreeSet`](crate::StateBTreeSet). + pub fn new_btree_set(&mut self) -> state_btree::StateBTreeSet { + let (state_api, prefix) = self.new_state_container(); + state_btree::StateBTreeSet::new(state_api, prefix) + } - self.state_api - .lookup_entry(&key_with_map_prefix) - .map(|mut entry| V::deserial_with_state(&self.state_api, &mut entry)) + /// Create a new empty [`StateBTreeMap`](crate::StateBTreeMap). + pub fn new_btree_map(&mut self) -> state_btree::StateBTreeMap { + state_btree::StateBTreeMap { + key_value: self.new_map(), + key_order: self.new_btree_set(), + } } - /// Inserts a value in the generic map. - /// The key and value are serialized before insert. - pub(crate) fn insert( - &mut self, - key: K, - value: V, - ) -> Result<(), StateError> { - let key_with_map_prefix = Self::prepend_generic_map_key(key); - match self.state_api.entry(key_with_map_prefix) { - EntryRaw::Vacant(vac) => { - let _ = vac.insert(&value); - } - EntryRaw::Occupied(mut occ) => occ.insert(&value), + /// Create a new empty [`StateBTreeSet`](crate::StateBTreeSet), setting the + /// minimum degree `M` of the B-Tree explicitly. `M` must be 2 or higher + /// otherwise constructing the B-Tree results in aborting. + pub fn new_btree_set_degree(&mut self) -> state_btree::StateBTreeSet { + if M >= 2 { + let (state_api, prefix) = self.new_state_container(); + state_btree::StateBTreeSet::new(state_api, prefix) + } else { + crate::fail!( + "Invalid minimum degree used for StateBTreeSet, must be >=2 instead got {}", + M + ) } - Ok(()) } - /// Serializes the key and prepends [GENERIC_MAP_PREFIX]. - /// This is similar to how [StateMap] works, where a unique prefix is - /// prepended onto keys. Since there is only one generic map, the prefix - /// is a constant. - fn prepend_generic_map_key(key: K) -> Vec { - let mut key_with_map_prefix = to_bytes(&GENERIC_MAP_PREFIX); - key_with_map_prefix.append(&mut to_bytes(&key)); - key_with_map_prefix + /// Create a new empty [`StateBTreeMap`](crate::StateBTreeMap), setting the + /// minimum degree `M` of the B-Tree explicitly. `M` must be 2 or higher + /// otherwise constructing the B-Tree results in aborting. + pub fn new_btree_map_degree( + &mut self, + ) -> state_btree::StateBTreeMap { + if M >= 2 { + state_btree::StateBTreeMap { + key_value: self.new_map(), + key_order: self.new_btree_set_degree(), + } + } else { + crate::fail!( + "Invalid minimum degree used for StateBTreeMap, must be >=2 instead got {}", + M + ) + } } } @@ -2543,7 +2581,6 @@ where (&mut self.state, &mut self.state_builder) } } - impl HasHost for ExternLowLevelHost { type ReturnValueType = ExternCallResponse; type StateApiType = ExternStateApi; @@ -3127,3 +3164,470 @@ mod tests { t.compile_fail("tests/state/map-multiple-state-ref-mut.rs"); } } + +/// This test module relies on the runtime providing host functions and can only +/// be run using `cargo concordium test`. +#[cfg(feature = "internal-wasm-test")] +mod wasm_test { + use crate::{ + claim, claim_eq, concordium_test, to_bytes, Deletable, Deserial, DeserialWithState, + EntryRaw, HasStateApi, HasStateEntry, ParseResult, Serial, StateApi, StateBuilder, + StateError, StateMap, StateSet, INITIAL_NEXT_ITEM_PREFIX, + }; + + const GENERIC_MAP_PREFIX: u64 = 1; + + /// Some helper methods that are used for internal tests. + impl StateBuilder + where + S: HasStateApi, + { + /// Get a value from the generic map. + /// `Some(Err(_))` means that something exists in the state with that + /// key, but it isn't of type `V`. + pub(crate) fn get>( + &self, + key: K, + ) -> Option> { + let key_with_map_prefix = Self::prepend_generic_map_key(key); + + self.state_api + .lookup_entry(&key_with_map_prefix) + .map(|mut entry| V::deserial_with_state(&self.state_api, &mut entry)) + } + + /// Inserts a value in the generic map. + /// The key and value are serialized before insert. + pub(crate) fn insert( + &mut self, + key: K, + value: V, + ) -> Result<(), StateError> { + let key_with_map_prefix = Self::prepend_generic_map_key(key); + match self.state_api.entry(key_with_map_prefix) { + EntryRaw::Vacant(vac) => { + let _ = vac.insert(&value); + } + EntryRaw::Occupied(mut occ) => occ.insert(&value), + } + Ok(()) + } + + /// Serializes the key and prepends [GENERIC_MAP_PREFIX]. + /// This is similar to how [StateMap] works, where a unique prefix is + /// prepended onto keys. Since there is only one generic map, the prefix + /// is a constant. + fn prepend_generic_map_key(key: K) -> Vec { + let mut key_with_map_prefix = to_bytes(&GENERIC_MAP_PREFIX); + key_with_map_prefix.append(&mut to_bytes(&key)); + key_with_map_prefix + } + } + + #[concordium_test] + fn high_level_insert_get() { + let expected_value: u64 = 123123123; + let mut state_builder = StateBuilder::open(StateApi::open()); + state_builder.insert(0, expected_value).expect("Insert failed"); + let actual_value: u64 = state_builder.get(0).expect("Not found").expect("Not a valid u64"); + claim_eq!(expected_value, actual_value); + } + + #[concordium_test] + fn low_level_entry() { + let expected_value: u64 = 123123123; + let key = to_bytes(&42u64); + let mut state = StateApi::open(); + state + .entry(&key[..]) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("No iterators, so insertion should work."); + + match state.entry(key) { + EntryRaw::Vacant(_) => panic!("Unexpected vacant entry."), + EntryRaw::Occupied(occ) => { + claim_eq!(u64::deserial(&mut occ.get()), Ok(expected_value)) + } + } + } + + #[concordium_test] + fn high_level_statemap() { + let my_map_key = "my_map"; + let mut state_builder = StateBuilder::open(StateApi::open()); + + let map_to_insert = state_builder.new_map::(); + state_builder.insert(my_map_key, map_to_insert).expect("Insert failed"); + + let mut my_map: StateMap = state_builder + .get(my_map_key) + .expect("Could not get statemap") + .expect("Deserializing statemap failed"); + let _ = my_map.insert("abc".to_string(), "hello, world".to_string()); + let _ = my_map.insert("def".to_string(), "hallo, Weld".to_string()); + let _ = my_map.insert("ghi".to_string(), "hej, verden".to_string()); + claim_eq!(*my_map.get(&"abc".to_string()).unwrap(), "hello, world".to_string()); + + let mut iter = my_map.iter(); + let (k1, v1) = iter.next().unwrap(); + claim_eq!(*k1, "abc".to_string()); + claim_eq!(*v1, "hello, world".to_string()); + let (k2, v2) = iter.next().unwrap(); + claim_eq!(*k2, "def".to_string()); + claim_eq!(*v2, "hallo, Weld".to_string()); + let (k3, v3) = iter.next().unwrap(); + claim_eq!(*k3, "ghi".to_string()); + claim_eq!(*v3, "hej, verden".to_string()); + claim!(iter.next().is_none()); + } + + #[concordium_test] + fn statemap_insert_remove() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut map = state_builder.new_map(); + let value = String::from("hello"); + let _ = map.insert(42, value.clone()); + claim_eq!(*map.get(&42).unwrap(), value); + map.remove(&42); + claim!(map.get(&42).is_none()); + } + + #[concordium_test] + fn statemap_clear() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut map = state_builder.new_map(); + let _ = map.insert(1, 2); + let _ = map.insert(2, 3); + let _ = map.insert(3, 4); + map.clear(); + claim!(map.is_empty()); + } + + #[concordium_test] + fn high_level_nested_statemaps() { + let inner_map_key = 0u8; + let key_to_value = 77u8; + let value = 255u8; + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut outer_map = state_builder.new_map::>(); + let mut inner_map = state_builder.new_map::(); + + let _ = inner_map.insert(key_to_value, value); + let _ = outer_map.insert(inner_map_key, inner_map); + + claim_eq!(*outer_map.get(&inner_map_key).unwrap().get(&key_to_value).unwrap(), value); + } + + #[concordium_test] + fn statemap_iter_mut_works() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut map = state_builder.new_map(); + let _ = map.insert(0u8, 1u8); + let _ = map.insert(1u8, 2u8); + let _ = map.insert(2u8, 3u8); + for (_, mut v) in map.iter_mut() { + v.update(|old_value| *old_value += 10); + } + let mut iter = map.iter(); + let (k1, v1) = iter.next().unwrap(); + claim_eq!(*k1, 0); + claim_eq!(*v1, 11); + let (k2, v2) = iter.next().unwrap(); + claim_eq!(*k2, 1); + claim_eq!(*v2, 12); + let (k3, v3) = iter.next().unwrap(); + claim_eq!(*k3, 2); + claim_eq!(*v3, 13); + claim!(iter.next().is_none()); + } + + #[concordium_test] + fn iter_mut_works_on_nested_statemaps() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut outer_map = state_builder.new_map(); + let mut inner_map = state_builder.new_map(); + let _ = inner_map.insert(0u8, 1u8); + let _ = inner_map.insert(1u8, 2u8); + let _ = outer_map.insert(99u8, inner_map); + for (_, mut v_map) in outer_map.iter_mut() { + v_map.update(|v_map| { + for (_, mut inner_v) in v_map.iter_mut() { + inner_v.update(|inner_v| *inner_v += 10); + } + }); + } + + // Check the outer map. + let mut outer_iter = outer_map.iter(); + let (inner_map_key, inner_map) = outer_iter.next().unwrap(); + claim_eq!(*inner_map_key, 99); + claim!(outer_iter.next().is_none()); + + // Check the inner map. + let mut inner_iter = inner_map.iter(); + let (k1, v1) = inner_iter.next().unwrap(); + claim_eq!(*k1, 0); + claim_eq!(*v1, 11); + let (k2, v2) = inner_iter.next().unwrap(); + claim_eq!(*k2, 1); + claim_eq!(*v2, 12); + claim!(inner_iter.next().is_none()); + } + + #[concordium_test] + fn statemap_iterator_unlocks_tree_once_dropped() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut map = state_builder.new_map(); + let _ = map.insert(0u8, 1u8); + let _ = map.insert(1u8, 2u8); + { + let _iter = map.iter(); + // Uncommenting these two lines (and making iter mutable) should + // give a compile error: + // + // map.insert(2u8, 3u8); + // let n = iter.next(); + } // iter is dropped here, unlocking the subtree. + let _ = map.insert(2u8, 3u8); + } + + #[concordium_test] + fn high_level_stateset() { + let my_set_key = "my_set"; + let mut state_builder = StateBuilder::open(StateApi::open()); + + let mut set = state_builder.new_set::(); + claim!(set.insert(0)); + claim!(set.insert(1)); + claim!(!set.insert(1)); + claim!(set.insert(2)); + claim!(set.remove(&2)); + state_builder.insert(my_set_key, set).expect("Insert failed"); + + claim!(state_builder.get::<_, StateSet>(my_set_key).unwrap().unwrap().contains(&0),); + claim!(!state_builder.get::<_, StateSet>(my_set_key).unwrap().unwrap().contains(&2),); + + let set = state_builder.get::<_, StateSet>(my_set_key).unwrap().unwrap(); + let mut iter = set.iter(); + claim_eq!(*iter.next().unwrap(), 0); + claim_eq!(*iter.next().unwrap(), 1); + claim!(iter.next().is_none()); + } + + #[concordium_test] + fn high_level_nested_stateset() { + let inner_set_key = 0u8; + let value = 255u8; + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut outer_map = state_builder.new_map::>(); + let mut inner_set = state_builder.new_set::(); + + inner_set.insert(value); + let _ = outer_map.insert(inner_set_key, inner_set); + + claim!(outer_map.get(&inner_set_key).unwrap().contains(&value)); + } + + #[concordium_test] + fn stateset_insert_remove() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut set = state_builder.new_set(); + let _ = set.insert(42); + claim!(set.contains(&42)); + set.remove(&42); + claim!(!set.contains(&42)); + } + + #[concordium_test] + fn stateset_clear() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut set = state_builder.new_set(); + let _ = set.insert(1); + let _ = set.insert(2); + let _ = set.insert(3); + set.clear(); + claim!(set.is_empty()); + } + + #[concordium_test] + fn stateset_iterator_unlocks_tree_once_dropped() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut set = state_builder.new_set(); + set.insert(0u8); + set.insert(1); + { + let _iter = set.iter(); + // Uncommenting these two lines (and making iter mutable) should + // give a compile error: + // + // set.insert(2); + // let n = iter.next(); + } // iter is dropped here, unlocking the subtree. + set.insert(2); + } + + #[concordium_test] + fn allocate_and_get_statebox() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let boxed_value = String::from("I'm boxed"); + let statebox = state_builder.new_box(boxed_value.clone()); + claim_eq!(*statebox.get(), boxed_value); + } + + #[concordium_test] + fn a_new_entry_can_not_be_created_under_a_locked_subtree() { + let expected_value: u64 = 123123123; + let key = to_bytes(b"ab"); + let sub_key = to_bytes(b"abc"); + let mut state = StateApi::open(); + state + .entry(&key[..]) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("No iterators, so insertion should work."); + claim!(state.iterator(&key).is_ok(), "Iterator should be present"); + let entry = state.create_entry(&sub_key); + claim!(entry.is_err(), "Should not be able to create an entry under a locked subtree"); + } + + #[concordium_test] + fn a_new_entry_can_be_created_under_a_different_subtree_in_same_super_tree() { + let expected_value: u64 = 123123123; + let key = to_bytes(b"abcd"); + let key2 = to_bytes(b"abe"); + let mut state = StateApi::open(); + state + .entry(&key[..]) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("No iterators, so insertion should work."); + claim!(state.iterator(&key).is_ok(), "Iterator should be present"); + let entry = state.create_entry(&key2); + claim!(entry.is_ok(), "Failed to create a new entry under a different subtree"); + } + + #[concordium_test] + fn an_existing_entry_can_not_be_deleted_under_a_locked_subtree() { + let expected_value: u64 = 123123123; + let key = to_bytes(b"ab"); + let sub_key = to_bytes(b"abc"); + let mut state = StateApi::open(); + state + .entry(&key[..]) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("no iterators, so insertion should work."); + let sub_entry = state + .entry(sub_key) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("Should be possible to create the entry."); + claim!(state.iterator(&key).is_ok(), "Iterator should be present"); + claim!( + state.delete_entry(sub_entry).is_err(), + "Should not be able to delete entry under a locked subtree" + ); + } + + #[concordium_test] + fn an_existing_entry_can_be_deleted_from_a_different_subtree_in_same_super_tree() { + let expected_value: u64 = 123123123; + let key = to_bytes(b"abcd"); + let key2 = to_bytes(b"abe"); + let mut state = StateApi::open(); + state + .entry(&key[..]) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("No iterators, so insertion should work."); + let entry2 = state + .entry(key2) + .or_insert_raw(&to_bytes(&expected_value)) + .expect("Should be possible to create the entry."); + claim!(state.iterator(&key).is_ok(), "Iterator should be present"); + claim!( + state.delete_entry(entry2).is_ok(), + "Should be able to delete entry under a different subtree" + ); + } + + #[concordium_test] + fn deleting_nested_stateboxes_works() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let inner_box = state_builder.new_box(99u8); + let middle_box = state_builder.new_box(inner_box); + let outer_box = state_builder.new_box(middle_box); + outer_box.delete(); + let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator"); + // The only remaining node should be the state_builder's next_item_prefix node. + claim!(iter.nth(1).is_none()); + } + + #[concordium_test] + fn clearing_statemap_with_stateboxes_works() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let box1 = state_builder.new_box(1u8); + let box2 = state_builder.new_box(2u8); + let box3 = state_builder.new_box(3u8); + let mut map = state_builder.new_map(); + let _ = map.insert(1u8, box1); + let _ = map.insert(2u8, box2); + let _ = map.insert(3u8, box3); + map.clear(); + let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator"); + // The only remaining node should be the state_builder's next_item_prefix node. + claim!(iter.nth(1).is_none()); + } + + #[concordium_test] + fn clearing_nested_statemaps_works() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut inner_map_1 = state_builder.new_map(); + let _ = inner_map_1.insert(1u8, 2u8); + let _ = inner_map_1.insert(2u8, 3u8); + let _ = inner_map_1.insert(3u8, 4u8); + let mut inner_map_2 = state_builder.new_map(); + let _ = inner_map_2.insert(11u8, 12u8); + let _ = inner_map_2.insert(12u8, 13u8); + let _ = inner_map_2.insert(13u8, 14u8); + let mut outer_map = state_builder.new_map(); + let _ = outer_map.insert(0u8, inner_map_1); + let _ = outer_map.insert(1u8, inner_map_2); + outer_map.clear(); + let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator"); + // The only remaining node should be the state_builder's next_item_prefix node. + claim!(iter.nth(1).is_none()); + } + + #[concordium_test] + fn occupied_entry_truncates_leftover_data() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut map = state_builder.new_map(); + let _ = map.insert(99u8, "A longer string that should be truncated".into()); + let a_short_string = "A short string".to_string(); + let expected_size = a_short_string.len() + 4; // 4 bytes for the length of the string. + map.entry(99u8).and_modify(|v| *v = a_short_string); + let actual_size = state_builder + .state_api + .lookup_entry(&[INITIAL_NEXT_ITEM_PREFIX[0], 0, 0, 0, 0, 0, 0, 0, 99]) + .expect("Lookup failed") + .size() + .expect("Getting size failed"); + claim_eq!(expected_size as u32, actual_size); + } + + #[concordium_test] + fn occupied_entry_raw_truncates_leftover_data() { + let mut state = StateApi::open(); + state + .entry([]) + .or_insert_raw(&to_bytes(&"A longer string that should be truncated")) + .expect("No iterators, so insertion should work."); + + let a_short_string = "A short string"; + let expected_size = a_short_string.len() + 4; // 4 bytes for the length of the string. + + match state.entry([]) { + EntryRaw::Vacant(_) => panic!("Entry is vacant"), + EntryRaw::Occupied(mut occ) => occ.insert_raw(&to_bytes(&a_short_string)), + } + let actual_size = + state.lookup_entry(&[]).expect("Lookup failed").size().expect("Getting size failed"); + claim_eq!(expected_size as u32, actual_size); + } +} diff --git a/concordium-std/src/lib.rs b/concordium-std/src/lib.rs index 9622ff4b7..efcb45d96 100644 --- a/concordium-std/src/lib.rs +++ b/concordium-std/src/lib.rs @@ -7,6 +7,7 @@ //! libraries. //! //! # Versions +//! //! The concordium blockchain at present supports two variants of smart //! contracts. The original V0 contracts that use message-passing for //! communication and have limited state, and V1 contracts which use synchronous @@ -24,6 +25,7 @@ //! `test_infrastructure`](#deprecating-the-test_infrastructure) section. //! //! # Panic handler +//! //! When compiled without the `std` feature this crate sets the panic handler //! so that it terminates the process immediately, without any unwinding or //! prints. @@ -45,6 +47,7 @@ //! #crypto-primitives-for-testing-crypto-with-actual-implementations //! //! ## `std`: Build with the Rust standard library +//! //! By default this library will be linked with the //! [std](https://doc.rust-lang.org/std/) crate, the rust standard library, //! however to minimize code size this library supports toggling compilation @@ -61,6 +64,7 @@ //! In your project's `Cargo.toml` file. //! //! ## `build-schema`: Build for generating a module schema +//! //! **WARNING** Building with this feature enabled is meant for tooling, and the //! result is not intended to be deployed on chain. //! @@ -77,6 +81,7 @@ //! schema and for most cases this feature should not be set manually. //! //! ## `wasm-test`: Build for testing in Wasm +//! //! **WARNING** Building with this feature enabled is meant for tooling, and the //! result is not intended to be deployed on chain. //! @@ -99,6 +104,7 @@ //! testing and for most cases this feature should not be set manually. //! //! ## `crypto-primitives`: For testing crypto with actual implementations +//! //! This features is only relevant when using the **deprecated** //! [test_infrastructure]. //! @@ -137,7 +143,7 @@ //! whether `bump_alloc` is the best option. See the Rust [allocator](https://doc.rust-lang.org/std/alloc/index.html#the-global_allocator-attribute) //! documentation for more context and details on using custom allocators. //! -//! Emit debug information +//! # Emit debug information //! //! During testing and debugging it is often useful to emit debug information to //! narrow down the source of the problem. `concordium-std` supports this using @@ -154,6 +160,7 @@ //! ignore its arguments when the `debug` feature is not enabled. //! //! # Essential types +//! //! This crate has a number of essential types that are used when writing smart //! contracts. The structure of these are, at present, a bit odd without the //! historic context, which is explained below. @@ -187,6 +194,7 @@ //! simplify the names with aliases. //! //! # Signalling errors +//! //! On the Wasm level contracts can signal errors by returning a negative i32 //! value as a result of either initialization or invocation of the receive //! method. If the error is a logic error and the contract executes successfully @@ -242,7 +250,65 @@ //! Other error codes may be added in the future and custom error codes should //! not use the range `i32::MIN` to `i32::MIN + 100`. //! +//! # Collections +//! +//! Several collections are available for use in a smart contract and choosing +//! the right one can result in significant cost savings. +//! +//! First off, Rust's own standard library provides [several efficient data structures](https://doc.rust-lang.org/std/collections/) +//! which can be used in a smart contract. +//! +//! However, these can become costly for collections holding a large number of +//! elements, which needs to be persisted in the smart contract state across +//! contract updates. +//! The reason being these data structures are designed to be kept in-memory and +//! persisting them means reading and writing the entire collection to a single +//! entry in the smart contract key-value store on every contract update. +//! This is wasteful when only part of the collection is relevant on each +//! update. +//! +//! In the above mentioned scenarios, it is instead recommended to use one of +//! the smart contract tailored collections, as these partisions the collection +//! into multiple key-value stores, resulting in cheaper read and writes to the +//! state. +//! +//! The collections can be grouped as: +//! - Maps: [`StateMap`], [`StateBTreeMap`] +//! - Sets: [`StateSet`], [`StateBTreeSet`] +//! +//! ## When should you use which collection? +//! +//! This section presents a rough guideline for when to reach for each of the +//! collections. +//! +//! ### Use `StateMap` when: +//! +//! - You want to track which keys you have seen. +//! - Arbitrary values are associated with each of the keys. +//! +//! ### Use `StateBTreeMap` when: +//! +//! - You want to track which keys you have seen. +//! - Arbitrary values are associated with each of the keys. +//! - The keys have some _ordering_ which is relevant e.g. if you need the key +//! which is located right above/below another key using +//! [`higher`](StateBTreeMap::higher)/[`lower`](StateBTreeMap::lower). +//! +//! ### Use `StateSet` when: +//! +//! - You want to track which keys you have seen. +//! - There is no meaningful value to associate with your keys. +//! +//! ### Use `StateBTreeSet` when: +//! +//! - You want to track which keys you have seen. +//! - There is no meaningful value to associate with your keys. +//! - The keys have some _ordering_ which is relevant e.g. if you need the key +//! which is located right above/below another key using +//! [`higher`](StateBTreeMap::higher)/[`lower`](StateBTreeMap::lower). +//! //! # Deprecating the `test_infrastructure` +//! //! Version 8.1 deprecates the [test_infrastructure] in favor of the library //! [concordium_smart_contract_testing]. A number of traits are also //! deprecated at the same time since they only exist to support the @@ -295,7 +361,7 @@ //! ctx: &impl HasReceiveContext, //! host: &impl HasHost, //! ) -> ReceiveResult { todo!() } -//! +//! //! /// After //! #[receive(contract = "my_contract", name = "my_receive")] //! fn receive_after( // `` removed @@ -325,6 +391,7 @@ //! [1]: https://doc.rust-lang.org/std/primitive.unit.html //! [test_infrastructure]: ./test_infrastructure/index.html //! [concordium_smart_contract_testing]: https://docs.rs/concordium-smart-contract-testing + #![cfg_attr(not(feature = "std"), no_std, feature(core_intrinsics))] #[cfg(not(feature = "std"))] @@ -394,10 +461,12 @@ pub mod collections { pub mod constants; mod impls; pub mod prims; +mod state_btree; mod traits; mod types; pub use concordium_contracts_common::*; pub use impls::*; +pub use state_btree::*; pub use traits::*; pub use types::*; diff --git a/concordium-std/src/state_btree.rs b/concordium-std/src/state_btree.rs new file mode 100644 index 000000000..6b0f60c0a --- /dev/null +++ b/concordium-std/src/state_btree.rs @@ -0,0 +1,2181 @@ +use crate::{ + self as concordium_std, cmp::Ordering, marker::PhantomData, mem, prims, vec::Vec, Deletable, + Deserial, DeserialWithState, Get, HasStateApi, ParseResult, Read, Serial, Serialize, StateApi, + StateItemPrefix, StateMap, StateRef, StateRefMut, UnwrapAbort, Write, STATE_ITEM_PREFIX_SIZE, +}; + +/// An ordered map based on [B-Tree](https://en.wikipedia.org/wiki/B-tree), where +/// each node is stored separately in the low-level key-value store. +/// +/// It can be seen as an extension adding tracking of the keys ordering on top +/// of [`StateMap`] to provide functions such as [`higher`](Self::higher) and +/// [`lower`](Self::lower). This results in some overhead when inserting and +/// deleting entries from the map compared to using [`StateMap`]. +/// +/// | Operation | Performance | +/// |-------------------------------------------------|---------------| +/// | [`get`](Self::get) / [`get_mut`](Self::get_mut) | O(k) | +/// | [`insert`](Self::insert) | O(k + log(n)) | +/// | [`remove`](Self::remove) | O(k + log(n)) | +/// | [`higher`](Self::higher)/[`lower`](Self::lower) | O(k + log(n)) | +/// +/// Where `k` is the byte size of the serialized keys and `n` is the number of +/// entries in the map. +/// +/// ## Type parameters +/// +/// The map `StateBTreeMap` is parametrized by the types: +/// - `K`: Keys used in the map. Most operations on the map require this to +/// implement [`Serialize`](crate::Serialize). Keys cannot contain references +/// to the low-level state, such as types containing +/// [`StateBox`](crate::StateBox), [`StateMap`](crate::StateMap) and +/// [`StateSet`](crate::StateSet). +/// - `V`: Values stored in the map. Most operations on the map require this to +/// implement [`Serial`](crate::Serial) and +/// [`DeserialWithState`](crate::DeserialWithState). +/// - `M`: A `const usize` determining the _minimum degree_ of the B-tree. +/// _Must_ be a value of `2` or above for the tree to work. This can be used +/// to tweak the height of the tree vs size of each node in the tree. The +/// default is set to 8, which seems to perform well on benchmarks. These +/// benchmarks ran operations on a collection of 1000 elements, some using +/// keys of 4 bytes others 16 bytes. +/// +/// ## Usage +/// +/// New maps can be constructed using the +/// [`new_btree_map`](crate::StateBuilder::new_btree_map) method on the +/// [`StateBuilder`](crate::StateBuilder). +/// +/// ```no_run +/// # use concordium_std::*; +/// # let mut state_builder = StateBuilder::open(StateApi::open()); +/// /// In an init method: +/// let mut map1 = state_builder.new_btree_map(); +/// # map1.insert(0u8, 1u8); // Specifies type of map. +/// +/// # let mut host = ExternHost { state: (), state_builder }; +/// /// In a receive method: +/// let mut map2 = host.state_builder().new_btree_map(); +/// # map2.insert(0u16, 1u16); +/// ``` +/// +/// ### **Caution** +/// +/// `StateBTreeMap`s must be explicitly deleted when they are no longer needed, +/// otherwise they will remain in the contract's state, albeit unreachable. +/// +/// ```no_run +/// # use concordium_std::*; +/// struct MyState { +/// inner: StateBTreeMap, +/// } +/// fn incorrect_replace(state_builder: &mut StateBuilder, state: &mut MyState) { +/// // The following is incorrect. The old value of `inner` is not properly deleted. +/// // from the state. +/// state.inner = state_builder.new_btree_map(); // ⚠️ +/// } +/// ``` +/// Instead, either the map should be [cleared](StateBTreeMap::clear) or +/// explicitly deleted. +/// +/// ```no_run +/// # use concordium_std::*; +/// # struct MyState { +/// # inner: StateBTreeMap +/// # } +/// fn correct_replace(state_builder: &mut StateBuilder, state: &mut MyState) { +/// state.inner.clear_flat(); +/// } +/// ``` +/// Or alternatively +/// ```no_run +/// # use concordium_std::*; +/// # struct MyState { +/// # inner: StateBTreeMap +/// # } +/// fn correct_replace(state_builder: &mut StateBuilder, state: &mut MyState) { +/// let old_map = mem::replace(&mut state.inner, state_builder.new_btree_map()); +/// old_map.delete() +/// } +/// ``` +#[derive(Serial)] +pub struct StateBTreeMap { + /// Mapping from key to value. + /// Each key in this map must also be in the `key_order` set. + pub(crate) key_value: StateMap, + /// A set for tracking the order of the inserted keys. + /// Each key in this set must also have an associated value in the + /// `key_value` map. + pub(crate) key_order: StateBTreeSet, +} + +impl StateBTreeMap { + /// Insert a key-value pair into the map. + /// Returns the previous value if the key was already in the map. + /// + /// *Caution*: If `Option` is to be deleted and contains a data structure + /// prefixed with `State` (such as [StateBox](crate::StateBox) or + /// [StateMap](crate::StateMap)), then it is important to call + /// [`Deletable::delete`](crate::Deletable::delete) on the value returned + /// when you're finished with it. Otherwise, it will remain in the + /// contract state. + #[must_use] + pub fn insert(&mut self, key: K, value: V) -> Option + where + K: Serialize + Ord, + V: Serial + DeserialWithState, { + let old_value_option = self.key_value.insert_borrowed(&key, value); + if old_value_option.is_none() && !self.key_order.insert(key) { + // Inconsistency between the map and ordered_set. + crate::trap(); + } + old_value_option + } + + /// Remove a key from the map, returning the value at the key if the key was + /// previously in the map. + /// + /// *Caution*: If `V` is a [StateBox](crate::StateBox), + /// [StateMap](crate::StateMap), then it is important to call + /// [`Deletable::delete`](crate::Deletable::delete) on the value returned + /// when you're finished with it. Otherwise, it will remain in the + /// contract state. + #[must_use] + pub fn remove_and_get(&mut self, key: &K) -> Option + where + K: Serialize + Ord, + V: Serial + DeserialWithState + Deletable, { + let v = self.key_value.remove_and_get(key); + if v.is_some() && !self.key_order.remove(key) { + // Inconsistency between the map and ordered_set. + crate::trap(); + } + v + } + + /// Remove a key from the map. + /// This also deletes the value in the state. + pub fn remove(&mut self, key: &K) + where + K: Serialize + Ord, + V: Serial + DeserialWithState + Deletable, { + if self.key_order.remove(key) { + self.key_value.remove(key); + } + } + + /// Get a reference to the value corresponding to the key. + pub fn get(&self, key: &K) -> Option> + where + K: Serialize, + V: Serial + DeserialWithState, { + // Minor optimization in the case of the empty collection. Since the length is + // tracked by the ordered set, we can return early, saving a key lookup. + if self.key_order.is_empty() { + None + } else { + self.key_value.get(key) + } + } + + /// Get a mutable reference to the value corresponding to the key. + pub fn get_mut(&mut self, key: &K) -> Option> + where + K: Serialize, + V: Serial + DeserialWithState, { + // Minor optimization in the case of the empty collection. Since the length is + // tracked by the ordered set, we can return early, saving a key lookup. + if self.key_order.is_empty() { + None + } else { + self.key_value.get_mut(key) + } + } + + /// Returns `true` if the map contains a value for the specified key. + #[inline(always)] + pub fn contains_key(&self, key: &K) -> bool + where + K: Serialize + Ord, { + self.key_order.contains(key) + } + + /// Returns the smallest key in the map that is strictly larger than the + /// provided key. `None` meaning no such key is present in the map. + #[inline(always)] + pub fn higher(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + self.key_order.higher(key) + } + + /// Returns the smallest key in the map that is equal or larger than the + /// provided key. `None` meaning no such key is present in the map. + #[inline(always)] + pub fn eq_or_higher(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + self.key_order.eq_or_higher(key) + } + + /// Returns the largest key in the map that is strictly smaller than the + /// provided key. `None` meaning no such key is present in the map. + #[inline(always)] + pub fn lower(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + self.key_order.lower(key) + } + + /// Returns the largest key in the map that is equal or smaller than the + /// provided key. `None` meaning no such key is present in the map. + #[inline(always)] + pub fn eq_or_lower(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + self.key_order.eq_or_lower(key) + } + + /// Returns a reference to the first key in the map, if any. This key is + /// always the minimum of all keys in the map. + #[inline(always)] + pub fn first_key(&self) -> Option> + where + K: Serialize + Ord, { + self.key_order.first() + } + + /// Returns a reference to the last key in the map, if any. This key is + /// always the maximum of all keys in the map. + #[inline(always)] + pub fn last_key(&self) -> Option> + where + K: Serialize + Ord, { + self.key_order.last() + } + + /// Return the number of elements in the map. + #[inline(always)] + pub fn len(&self) -> u32 { self.key_order.len() } + + /// Returns `true` is the map contains no elements. + #[inline(always)] + pub fn is_empty(&self) -> bool { self.key_order.is_empty() } + + /// Create an iterator over the entries of [`StateBTreeMap`]. + /// Ordered by `K` ascending. + #[inline(always)] + pub fn iter(&self) -> StateBTreeMapIter { + StateBTreeMapIter { + key_iter: self.key_order.iter(), + map: &self.key_value, + } + } + + /// Clears the map, removing all key-value pairs. + /// This also includes values pointed at, if `V`, for example, is a + /// [StateBox](crate::StateBox). **If applicable use + /// [`clear_flat`](Self::clear_flat) instead.** + pub fn clear(&mut self) + where + K: Serialize, + V: Serial + DeserialWithState + Deletable, { + self.key_value.clear(); + self.key_order.clear(); + } + + /// Clears the map, removing all key-value pairs. + /// **This should be used over [`clear`](Self::clear) if it is + /// applicable.** It avoids recursive deletion of values since the + /// values are required to be _flat_. + /// + /// Unfortunately it is not possible to automatically choose between these + /// implementations. Once Rust gets trait specialization then this might + /// be possible. + pub fn clear_flat(&mut self) + where + K: Serialize, + V: Serialize, { + self.key_value.clear_flat(); + self.key_order.clear(); + } +} + +/// An ordered set based on [B-Tree](https://en.wikipedia.org/wiki/B-tree), where +/// each node is stored separately in the low-level key-value store. +/// +/// | Operation | Performance | +/// |-------------------------------------------------|---------------| +/// | [`contains`](Self::contains) | O(k + log(n)) | +/// | [`insert`](Self::insert) | O(k + log(n)) | +/// | [`remove`](Self::remove) | O(k + log(n)) | +/// | [`higher`](Self::higher)/[`lower`](Self::lower) | O(k + log(n)) | +/// +/// Where `k` is the byte size of the serialized keys and `n` is the number of +/// entries in the map. +/// +/// ## Type parameters +/// +/// The map `StateBTreeSet` is parametrized by the types: +/// - `K`: Keys used in the set. Most operations on the set require this to +/// implement [`Serialize`](crate::Serialize). Keys cannot contain references +/// to the low-level state, such as types containing +/// [`StateBox`](crate::StateBox), [`StateMap`](crate::StateMap) and +/// [`StateSet`](crate::StateSet). +/// - `M`: A `const usize` determining the _minimum degree_ of the B-tree. +/// _Must_ be a value of `2` or above for the tree to work. This can be used +/// to tweak the height of the tree vs size of each node in the tree. The +/// default is set to 8, which seems to perform well on benchmarks. These +/// benchmarks ran operations on a collection of 1000 elements, some using +/// keys of 4 bytes others 16 bytes. +/// +/// ## Usage +/// +/// New sets can be constructed using the +/// [`new_btree_set`](crate::StateBuilder::new_btree_set) method on the +/// [`StateBuilder`](crate::StateBuilder). +/// +/// ```no_run +/// # use concordium_std::*; +/// # let mut state_builder = StateBuilder::open(StateApi::open()); +/// /// In an init method: +/// let mut map1 = state_builder.new_btree_set(); +/// # map1.insert(0u8); // Specifies type of map. +/// # let mut host = ExternHost { state: (), state_builder }; +/// /// In a receive method: +/// let mut map2 = host.state_builder().new_btree_set(); +/// # map2.insert(0u16); +/// ``` +/// +/// ### **Caution** +/// +/// `StateBTreeSet`s must be explicitly deleted when they are no longer needed, +/// otherwise they will remain in the contract's state, albeit unreachable. +/// +/// ```no_run +/// # use concordium_std::*; +/// struct MyState { +/// inner: StateBTreeSet, +/// } +/// fn incorrect_replace(state_builder: &mut StateBuilder, state: &mut MyState) { +/// // The following is incorrect. The old value of `inner` is not properly deleted. +/// // from the state. +/// state.inner = state_builder.new_btree_set(); // ⚠️ +/// } +/// ``` +/// Instead, the set should be [cleared](StateBTreeSet::clear): +/// +/// ```no_run +/// # use concordium_std::*; +/// # struct MyState { +/// # inner: StateBTreeSet +/// # } +/// fn correct_replace(state_builder: &mut StateBuilder, state: &mut MyState) { +/// state.inner.clear(); +/// } +/// ``` +pub struct StateBTreeSet { + /// Type marker for the key. + _marker_key: PhantomData, + /// The unique prefix to use for this map in the key-value store. + prefix: StateItemPrefix, + /// The API for interacting with the low-level state. + state_api: StateApi, + /// The ID of the root node of the tree, where None represents the tree is + /// empty. + root: Option, + /// Tracking the number of items in the tree. + len: u32, + /// Tracking the next available ID for a new node. + next_node_id: NodeId, +} + +impl StateBTreeSet { + /// Construct a new [`StateBTreeSet`] given a unique prefix to use in the + /// key-value store. + pub(crate) fn new(state_api: StateApi, prefix: StateItemPrefix) -> Self { + Self { + _marker_key: Default::default(), + prefix, + state_api, + root: None, + len: 0, + next_node_id: NodeId { + id: 0, + }, + } + } + + /// Insert a key into the set. + /// Returns true if the key is new in the collection. + pub fn insert(&mut self, key: K) -> bool + where + K: Serialize + Ord, { + let Some(root_id) = self.root else { + let (node_id, _) = self.create_node(crate::vec![key], Vec::new()); + self.root = Some(node_id); + self.len = 1; + return true; + }; + + let root_node = self.get_node_mut(root_id); + if !root_node.is_full() { + let new = self.insert_non_full(root_node, key); + if new { + self.len += 1; + } + return new; + } else if root_node.keys.binary_search(&key).is_ok() { + return false; + } + // The root node is full, so we construct a new root node. + let (new_root_id, mut new_root) = self.create_node(Vec::new(), crate::vec![root_id]); + self.root = Some(new_root_id); + // The old root node is now a child node. + let mut child = root_node; + let new_larger_child = self.split_child(&mut new_root, 0, &mut child); + // new_root now contains one key and two children, so we need to know + // which one to insert into. + let key_in_root = unsafe { new_root.keys.get_unchecked(0) }; + let child = if key_in_root < &key { + new_larger_child + } else { + child + }; + let new = self.insert_non_full(child, key); + if new { + self.len += 1; + } + new + } + + /// Returns `true` if the set contains an element equal to the key. + pub fn contains(&self, key: &K) -> bool + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return false; + }; + let mut node = self.get_node(root_node_id); + loop { + let Err(child_index) = node.keys.binary_search(key) else { + return true; + }; + if node.is_leaf() { + return false; + } + let child_node_id = unsafe { *node.children.get_unchecked(child_index) }; + node = self.get_node(child_node_id); + } + } + + /// Return the number of elements in the set. + pub fn len(&self) -> u32 { self.len } + + /// Returns `true` is the set contains no elements. + pub fn is_empty(&self) -> bool { self.root.is_none() } + + /// Get an iterator over the elements in the `StateBTreeSet`. The iterator + /// returns elements in increasing order. + pub fn iter(&self) -> StateBTreeSetIter { + StateBTreeSetIter { + length: self.len.try_into().unwrap_abort(), + next_node: self.root, + depth_first_stack: Vec::new(), + tree: self, + _marker_lifetime: Default::default(), + } + } + + /// Clears the set, removing all elements. + pub fn clear(&mut self) { + // Reset the information. + self.root = None; + self.next_node_id = NodeId { + id: 0, + }; + self.len = 0; + // Then delete every node store in the state. + // Unwrapping is safe when only using the high-level API. + self.state_api.delete_prefix(&self.prefix).unwrap_abort(); + } + + /// Returns the smallest key in the set that is strictly larger than the + /// provided key. `None` meaning no such key is present in the set. + pub fn higher(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return None; + }; + + let mut node = self.get_node(root_node_id); + let mut higher_so_far = None; + loop { + let higher_key_index = match node.keys.binary_search(key) { + Ok(index) => index + 1, + Err(index) => index, + }; + + if node.is_leaf() { + return if higher_key_index < node.keys.len() { + // This does not mutate the node in the end, just the representation in memory + // which is freed after the call. + Some(StateRef::new(node.keys.swap_remove(higher_key_index))) + } else { + higher_so_far + }; + } else { + if higher_key_index < node.keys.len() { + // This does not mutate the node in the end, just the representation in memory + // which is freed after the call. + higher_so_far = Some(StateRef::new(node.keys.swap_remove(higher_key_index))) + } + + let child_node_id = unsafe { *node.children.get_unchecked(higher_key_index) }; + node = self.get_node(child_node_id); + } + } + } + + /// Returns the smallest key in the set that is equal or larger than the + /// provided key. `None` meaning no such key is present in the set. + pub fn eq_or_higher(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return None; + }; + + let mut node = self.get_node(root_node_id); + let mut higher_so_far = None; + loop { + let higher_key_index = match node.keys.binary_search(key) { + Ok(index) => { + // This does not mutate the node in the end, just the representation in memory + // which is freed after the call. + return Some(StateRef::new(node.keys.swap_remove(index))); + } + Err(index) => index, + }; + + if node.is_leaf() { + return if higher_key_index < node.keys.len() { + Some(StateRef::new(node.keys.swap_remove(higher_key_index))) + } else { + higher_so_far + }; + } else { + if higher_key_index < node.keys.len() { + higher_so_far = Some(StateRef::new(node.keys.swap_remove(higher_key_index))) + } + + let child_node_id = unsafe { *node.children.get_unchecked(higher_key_index) }; + node = self.get_node(child_node_id); + } + } + } + + /// Returns the largest key in the set that is strictly smaller than the + /// provided key. `None` meaning no such key is present in the set. + pub fn lower(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return None; + }; + + let mut node = self.get_node(root_node_id); + let mut lower_so_far = None; + loop { + let lower_key_index = match node.keys.binary_search(key) { + Ok(index) => index, + Err(index) => index, + }; + + if node.is_leaf() { + return if lower_key_index == 0 { + lower_so_far + } else { + Some(StateRef::new(node.keys.swap_remove(lower_key_index - 1))) + }; + } else { + if lower_key_index > 0 { + lower_so_far = Some(StateRef::new(node.keys.swap_remove(lower_key_index - 1))); + } + let child_node_id = unsafe { node.children.get_unchecked(lower_key_index) }; + node = self.get_node(*child_node_id) + } + } + } + + /// Returns the largest key in the set that is equal or smaller than the + /// provided key. `None` meaning no such key is present in the set. + pub fn eq_or_lower(&self, key: &K) -> Option> + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return None; + }; + + let mut node = self.get_node(root_node_id); + let mut lower_so_far = None; + loop { + let lower_key_index = match node.keys.binary_search(key) { + Ok(index) => { + // This does not mutate the node in the end, just the representation in memory + // which is freed after the call. + return Some(StateRef::new(node.keys.swap_remove(index))); + } + Err(index) => index, + }; + + if node.is_leaf() { + return if lower_key_index == 0 { + lower_so_far + } else { + Some(StateRef::new(node.keys.swap_remove(lower_key_index - 1))) + }; + } else { + if lower_key_index > 0 { + lower_so_far = Some(StateRef::new(node.keys.swap_remove(lower_key_index - 1))); + } + let child_node_id = unsafe { node.children.get_unchecked(lower_key_index) }; + node = self.get_node(*child_node_id) + } + } + } + + /// Returns a reference to the first key in the set, if any. This key is + /// always the minimum of all keys in the set. + pub fn first(&self) -> Option> + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return None; + }; + let mut root = self.get_node(root_node_id); + if root.is_leaf() { + Some(StateRef::new(root.keys.swap_remove(0))) + } else { + Some(StateRef::new(self.get_lowest_key(&root, 0))) + } + } + + /// Returns a reference to the last key in the set, if any. This key is + /// always the maximum of all keys in the set. + pub fn last(&self) -> Option> + where + K: Serialize + Ord, { + let Some(root_node_id) = self.root else { + return None; + }; + let mut root = self.get_node(root_node_id); + if root.is_leaf() { + Some(StateRef::new(root.keys.pop().unwrap_abort())) + } else { + Some(StateRef::new(self.get_highest_key(&root, root.children.len() - 1))) + } + } + + /// Remove a key from the set. + /// Returns whether such an element was present. + pub fn remove(&mut self, key: &K) -> bool + where + K: Ord + Serialize, { + let Some(root_node_id) = self.root else { + return false; + }; + + let deleted_something = { + let mut node = self.get_node_mut(root_node_id); + loop { + match node.keys.binary_search(key) { + Ok(index) => { + if node.is_leaf() { + // Found the key in this node and the node is a leaf, meaning we + // simply remove it. + // This will not violate the minimum keys invariant, since a node + // ensures a child can spare a key before iteration and the root + // node is not part of the + // invariant. + node.keys.remove(index); + break true; + } + // Found the key in this node, but the node is not a leaf. + let mut left_child = self.get_node_mut(node.children[index]); + if !left_child.is_at_min() { + // If the child with smaller keys can spare a key, we take the + // highest key from it. + node.keys[index] = self.remove_largest_key(left_child); + break true; + } + + let right_child = self.get_node_mut(node.children[index + 1]); + if !right_child.is_at_min() { + // If the child with larger keys can spare a key, we take the lowest + // key from it. + node.keys[index] = self.remove_smallest_key(right_child); + break true; + } + // No child on either side of the key can spare a key at this point, so + // we merge them into one child, moving the + // key into the merged child and try + // to remove from this. + self.merge(&mut node, index, &mut left_child, right_child); + node = left_child; + continue; + } + Err(index) => { + // Node did not contain the key. + if node.is_leaf() { + break false; + } + // Node did not contain the key and is not a leaf. + // Check and proactively prepare the child to be able to delete a key. + node = self.prepare_child_for_key_removal(node, index); + } + }; + } + }; + + // If something was deleted, we update the length and make sure to remove the + // root node if needed. + let root = self.get_node_mut(root_node_id); + if deleted_something { + self.len -= 1; + if self.len == 0 { + // Remote the root node if tree is empty. + self.root = None; + self.delete_node(root_node_id, root); + return true; + } + } + // If the root is empty but the tree is not, point to the only child of the root + // as the root. + if root.keys.is_empty() { + self.root = Some(root.children[0]); + self.delete_node(root_node_id, root); + } + deleted_something + } + + /// Internal function for taking the largest key in a subtree. + /// + /// Assumes: + /// - The provided node is not the root. + /// - The node contain more than minimum number of keys. + fn remove_largest_key(&mut self, mut node: StateRefMut<'_, Node, StateApi>) -> K + where + K: Ord + Serialize, { + while !node.is_leaf() { + // Node is not a leaf, so we move further down this subtree. + let child_index = node.children.len() - 1; + node = self.prepare_child_for_key_removal(node, child_index); + } + // The node is a leaf, meaning we simply remove it. + // This will not violate the minimum keys invariant, since a node + // ensures a child can spare a key before iteration. + node.keys.pop().unwrap_abort() + } + + /// Internal function for taking the smallest key in a subtree. + /// + /// Assumes: + /// - The provided node is not the root. + /// - The provided `node` contain more than minimum number of keys. + fn remove_smallest_key(&mut self, mut node: StateRefMut<'_, Node, StateApi>) -> K + where + K: Ord + Serialize, { + while !node.is_leaf() { + // Node is not a leaf, so we move further down this subtree. + let child_index = 0; + node = self.prepare_child_for_key_removal(node, child_index); + } + // The node is a leaf, meaning we simply remove it. + // This will not violate the minimum keys invariant, since a node + // ensures a child can spare a key before iteration. + node.keys.remove(0) + } + + /// Internal function for key rotation, preparing a node for deleting a key. + /// Returns the now prepared child at `index`. + /// Assumes: + /// - The provided `node` is not a leaf and has a child at `index`. + /// - The minimum degree `M` is at least 2 or more. + fn prepare_child_for_key_removal<'c>( + &mut self, + mut node: StateRefMut, StateApi>, + index: usize, + ) -> StateRefMut<'c, Node, StateApi> + where + K: Ord + Serialize, { + let mut child = self.get_node_mut(node.children[index]); + if !child.is_at_min() { + return child; + } + // The child is at minimum keys, so first attempt to take a key from either + // sibling, otherwise merge with one of them. + let has_smaller_sibling = 0 < index; + let has_larger_sibling = index < node.children.len() - 1; + let smaller_sibling = if has_smaller_sibling { + let mut smaller_sibling = self.get_node_mut(node.children[index - 1]); + if !smaller_sibling.is_at_min() { + // The smaller sibling can spare a key, so we replace the largest + // key from the sibling, put it in + // the parent and take a key from + // the parent. + let largest_key_sibling = smaller_sibling.keys.pop().unwrap_abort(); + let swapped_node_key = mem::replace(&mut node.keys[index - 1], largest_key_sibling); + child.keys.insert(0, swapped_node_key); + if !child.is_leaf() { + child.children.insert(0, smaller_sibling.children.pop().unwrap_abort()); + } + return child; + } + Some(smaller_sibling) + } else { + None + }; + let larger_sibling = if has_larger_sibling { + let mut larger_sibling = self.get_node_mut(node.children[index + 1]); + if !larger_sibling.is_at_min() { + // The larger sibling can spare a key, so we replace the smallest + // key from the sibling, put it in + // the parent and take a key from + // the parent. + let first_key_sibling = larger_sibling.keys.remove(0); + let swapped_node_key = mem::replace(&mut node.keys[index], first_key_sibling); + child.keys.push(swapped_node_key); + + if !child.is_leaf() { + child.children.push(larger_sibling.children.remove(0)); + } + return child; + } + Some(larger_sibling) + } else { + None + }; + + if let Some(sibling) = larger_sibling { + self.merge(&mut node, index, &mut child, sibling); + child + } else if let Some(mut sibling) = smaller_sibling { + self.merge(&mut node, index - 1, &mut sibling, child); + sibling + } else { + // Unreachable code, since M must be 2 or larger (the minimum degree), a + // child node must have at least one sibling. + crate::trap(); + } + } + + /// Internal function for getting the highest key in a subtree. + /// Assumes the provided `node` is not a leaf and has a child at + /// `child_index`. + fn get_highest_key(&self, node: &Node, child_index: usize) -> K + where + K: Ord + Serialize, { + let mut node = self.get_node(node.children[child_index]); + while !node.is_leaf() { + let child_node_id = node.children.last().unwrap_abort(); + node = self.get_node(*child_node_id); + } + // This does not mutate the node in the end, just the representation in memory + // which is freed after the call. + node.keys.pop().unwrap_abort() + } + + /// Internal function for getting the lowest key in a subtree. + /// Assumes the provided `node` is not a leaf and has a child at + /// `child_index`. + fn get_lowest_key(&self, node: &Node, child_index: usize) -> K + where + K: Ord + Serialize, { + let mut node = self.get_node(node.children[child_index]); + while !node.is_leaf() { + let child_node_id = node.children.first().unwrap_abort(); + node = self.get_node(*child_node_id); + } + node.keys.swap_remove(0) + } + + /// Move key at `index` from the node to the lower child and then merges + /// this child with the content of its larger sibling, deleting the sibling. + /// + /// Assumes: + /// - `parent_node` has children `child` at `index` and `larger_child` at + /// `index + 1`. + /// - Both children are at minimum number of keys (`M - 1`). + fn merge( + &mut self, + parent_node: &mut Node, + index: usize, + child: &mut Node, + mut larger_child: StateRefMut, StateApi>, + ) where + K: Ord + Serialize, { + let parent_key = parent_node.keys.remove(index); + let larger_child_id = parent_node.children.remove(index + 1); + child.keys.push(parent_key); + child.keys.append(&mut larger_child.keys); + child.children.append(&mut larger_child.children); + self.delete_node(larger_child_id, larger_child); + } + + /// Internal function for constructing a node. It will increment the next + /// node ID and create an entry in the smart contract key-value store. + fn create_node<'b>( + &mut self, + keys: Vec, + children: Vec, + ) -> (NodeId, StateRefMut<'b, Node, StateApi>) + where + K: Serialize, { + let node_id = self.next_node_id.fetch_and_add(); + let node = Node { + keys, + children, + }; + let entry = self.state_api.create_entry(&node_id.as_key(&self.prefix)).unwrap_abort(); + let mut ref_mut: StateRefMut<'_, Node, StateApi> = + StateRefMut::new(entry, self.state_api.clone()); + ref_mut.set(node); + (node_id, ref_mut) + } + + /// Internal function for deleting a node, removing the entry in the smart + /// contract key-value store. Traps if no node was present. + fn delete_node(&mut self, node_id: NodeId, node: StateRefMut, StateApi>) + where + K: Serial, { + let key = node_id.as_key(&self.prefix); + node.drop_without_storing(); + unsafe { prims::state_delete_entry(key.as_ptr(), key.len() as u32) }; + } + + /// Internal function for inserting into a subtree. + /// Assumes the given node is not full. + fn insert_non_full(&mut self, initial_node: StateRefMut, StateApi>, key: K) -> bool + where + K: Serialize + Ord, { + let mut node = initial_node; + loop { + let Err(insert_index) = node.keys.binary_search(&key) else { + // We find the key in this node, so we do nothing. + return false; + }; + // The key is not in this node. + if node.is_leaf() { + // Since we can assume the node is not full and this is a leaf, we can just + // insert here. + node.keys.insert(insert_index, key); + return true; + } + + // The node is not a leaf, so we want to insert in the relevant child node. + let child_id = unsafe { node.children.get_unchecked(insert_index) }; + let mut child = self.get_node_mut(*child_id); + node = if !child.is_full() { + child + } else { + let larger_child = self.split_child(&mut node, insert_index, &mut child); + // Since the child is now split into two, we have to check which one to insert + // into. + let moved_up_key = &node.keys[insert_index]; + match moved_up_key.cmp(&key) { + Ordering::Equal => return false, + Ordering::Less => larger_child, + Ordering::Greater => child, + } + }; + } + } + + /// Internal function for splitting the child node at a given index for a + /// given node. This will also mutate the given node adding a new key + /// and child after the provided child_index. + /// Returns the newly created node. + /// + /// Assumes: + /// - Node is not a leaf and has `child` as child at `child_index`. + /// - `child` is at maximum keys (`2 * M - 1`). + fn split_child<'b>( + &mut self, + node: &mut Node, + child_index: usize, + child: &mut Node, + ) -> StateRefMut<'b, Node, StateApi> + where + K: Serialize + Ord, { + let split_index = Node::::MINIMUM_KEY_LEN + 1; + let (new_larger_sibling_id, new_larger_sibling) = self.create_node( + child.keys.split_off(split_index), + if child.is_leaf() { + Vec::new() + } else { + child.children.split_off(split_index) + }, + ); + let key = child.keys.pop().unwrap_abort(); + node.children.insert(child_index + 1, new_larger_sibling_id); + node.keys.insert(child_index, key); + new_larger_sibling + } + + /// Internal function for looking up a node in the tree. + /// This assumes the node is present and traps if this is not the case. + fn get_node(&self, node_id: NodeId) -> Node + where + Key: Deserial, { + let key = node_id.as_key(&self.prefix); + let mut entry = self.state_api.lookup_entry(&key).unwrap_abort(); + entry.get().unwrap_abort() + } + + /// Internal function for looking up a node, providing mutable access. + /// This assumes the node is present and traps if this is not the case. + fn get_node_mut<'b>(&mut self, node_id: NodeId) -> StateRefMut<'b, Node, StateApi> + where + K: Serial, { + let key = node_id.as_key(&self.prefix); + let entry = self.state_api.lookup_entry(&key).unwrap_abort(); + StateRefMut::new(entry, self.state_api.clone()) + } +} + +/// An iterator over the entries of a [`StateBTreeSet`]. +/// +/// Ordered by `K`. +/// +/// This `struct` is created by the [`iter`][StateBTreeSet::iter] method on +/// [`StateBTreeSet`]. See its documentation for more. +pub struct StateBTreeSetIter<'a, 'b, K, const M: usize> { + /// The number of elements left to iterate. + length: usize, + /// Reference to a node in the tree to load and iterate before the current + /// node. + next_node: Option, + /// Tracking the nodes depth first, which are currently being iterated. + depth_first_stack: Vec<(Node>, usize)>, + /// Reference to the set, needed for looking up the nodes. + tree: &'a StateBTreeSet, + /// Marker for tracking the lifetime of the key. + _marker_lifetime: PhantomData<&'b K>, +} + +impl<'a, 'b, const M: usize, K> Iterator for StateBTreeSetIter<'a, 'b, K, M> +where + 'a: 'b, + K: Deserial, +{ + type Item = StateRef<'b, K>; + + fn next(&mut self) -> Option { + while let Some(id) = self.next_node.take() { + let node = self.tree.get_node(id); + if !node.is_leaf() { + self.next_node = Some(node.children[0]); + } + self.depth_first_stack.push((node, 0)); + } + + let (node, index) = self.depth_first_stack.last_mut()?; + let key = node.keys[*index].key.take().unwrap_abort(); + *index += 1; + let no_more_keys = index == &node.keys.len(); + if !node.is_leaf() { + let child_id = node.children[*index]; + self.next_node = Some(child_id); + } + if no_more_keys { + // This was the last key in the node, so remove the node from the stack. + let _ = self.depth_first_stack.pop(); + } + self.length -= 1; + Some(StateRef::new(key)) + } + + fn size_hint(&self) -> (usize, Option) { (self.length, Some(self.length)) } +} + +/// An iterator over the entries of a [`StateBTreeMap`]. +/// +/// Ordered by `K`. +/// +/// This `struct` is created by the [`iter`][StateBTreeMap::iter] method on +/// [`StateBTreeMap`]. See its documentation for more. +pub struct StateBTreeMapIter<'a, 'b, K, V, const M: usize> { + /// Iterator over the keys in the map. + key_iter: StateBTreeSetIter<'a, 'b, K, M>, + /// Reference to the map holding the values. + map: &'a StateMap, +} + +impl<'a, 'b, const M: usize, K, V> Iterator for StateBTreeMapIter<'a, 'b, K, V, M> +where + 'a: 'b, + K: Serialize, + V: Serial + DeserialWithState + 'b, +{ + type Item = (StateRef<'b, K>, StateRef<'b, V>); + + fn next(&mut self) -> Option { + let next_key = self.key_iter.next()?; + let value = self.map.get(&next_key).unwrap_abort(); + // Unwrap is safe, otherwise the map and the set have inconsistencies. + Some((next_key, value)) + } + + fn size_hint(&self) -> (usize, Option) { self.key_iter.size_hint() } +} + +/// Identifier for a node in the tree. Used to construct the key, where this +/// node is stored in the smart contract key-value store. +#[derive(Debug, Copy, Clone, Serialize)] +#[repr(transparent)] +struct NodeId { + id: u64, +} + +/// Byte size of the key used to store a BTree internal node in the smart +/// contract key-value store. +const BTREE_NODE_KEY_SIZE: usize = STATE_ITEM_PREFIX_SIZE + NodeId::SERIALIZED_BYTE_SIZE; + +impl NodeId { + /// Byte size of `NodeId` when serialized. + const SERIALIZED_BYTE_SIZE: usize = 8; + + /// Return a copy of the NodeId, then increments itself. + fn fetch_and_add(&mut self) -> Self { + let current = *self; + self.id += 1; + current + } + + /// Construct the key for the node in the key-value store from the node ID. + fn as_key(&self, prefix: &StateItemPrefix) -> [u8; BTREE_NODE_KEY_SIZE] { + // Create an uninitialized array of `MaybeUninit`. The `assume_init` is + // safe because the type we are claiming to have initialized here is a + // bunch of `MaybeUninit`s, which do not require initialization. + let mut prefixed: [mem::MaybeUninit; BTREE_NODE_KEY_SIZE] = + unsafe { mem::MaybeUninit::uninit().assume_init() }; + for (place, value) in prefixed.iter_mut().zip(prefix) { + place.write(*value); + } + let id_bytes = self.id.to_le_bytes(); + for (place, value) in prefixed[STATE_ITEM_PREFIX_SIZE..].iter_mut().zip(id_bytes) { + place.write(value); + } + // Transmuting away the maybeuninit is safe since we have initialized all of + // them. + unsafe { mem::transmute(prefixed) } + } +} + +/// Type representing a node in the [`StateBTreeMap`]. +/// Each node is stored separately in the smart contract key-value store. +#[derive(Debug, Serialize)] +struct Node { + /// List of sorted keys tracked by this node. + /// This list should never be empty and contain between `M - 1` and `2M + /// - 1` elements. The root node being the only exception to this. + keys: Vec, + /// List of nodes which are children of this node in the tree. + /// + /// This list is empty when this node is representing a leaf. + /// When not a leaf, it will contain exactly `keys.len() + 1` elements. + /// + /// The elements are ordered such that for a key `keys[i]`: + /// - `children[i]` is a subtree containing strictly smaller keys. + /// - `children[i + 1]` is a subtree containing strictly larger keys. + children: Vec, +} + +impl Node { + /// The max length of the child list. + const MAXIMUM_CHILD_LEN: usize = 2 * M; + /// The max length of the key list. + const MAXIMUM_KEY_LEN: usize = Self::MAXIMUM_CHILD_LEN - 1; + /// The min length of the child list, when the node is not a leaf node. + const MINIMUM_CHILD_LEN: usize = M; + /// The min length of the key list, except when the node is root. + const MINIMUM_KEY_LEN: usize = Self::MINIMUM_CHILD_LEN - 1; + + /// Check if the node holds the maximum number of keys. + #[inline(always)] + fn is_full(&self) -> bool { self.keys.len() == Self::MAXIMUM_KEY_LEN } + + /// Check if the node is representing a leaf in the tree. + #[inline(always)] + fn is_leaf(&self) -> bool { self.children.is_empty() } + + /// Check if the node holds the minimum number of keys. + #[inline(always)] + fn is_at_min(&self) -> bool { self.keys.len() == Self::MINIMUM_KEY_LEN } +} + +/// Wrapper implement the exact same deserial as K, but wraps it in an +/// option in memory. This is used to allow taking a key from a mutable +/// reference to a node, without cloning the key, during iteration of the +/// set. +#[repr(transparent)] +struct KeyWrapper { + key: Option, +} + +impl Deserial for KeyWrapper { + fn deserial(source: &mut R) -> ParseResult { + let key = K::deserial(source)?; + Ok(Self { + key: Some(key), + }) + } +} + +impl Serial for StateBTreeSet { + fn serial(&self, out: &mut W) -> Result<(), W::Err> { + self.prefix.serial(out)?; + self.root.serial(out)?; + self.len.serial(out)?; + self.next_node_id.serial(out) + } +} + +impl DeserialWithState for StateBTreeSet { + fn deserial_with_state(state: &StateApi, source: &mut R) -> ParseResult { + let prefix = source.get()?; + let root = source.get()?; + let len = source.get()?; + let next_node_id = source.get()?; + + Ok(Self { + _marker_key: Default::default(), + prefix, + state_api: state.clone(), + root, + len, + next_node_id, + }) + } +} + +impl DeserialWithState for StateBTreeMap { + fn deserial_with_state(state: &StateApi, source: &mut R) -> ParseResult { + let key_value = StateMap::deserial_with_state(state, source)?; + let key_order = StateBTreeSet::deserial_with_state(state, source)?; + Ok(Self { + key_value, + key_order, + }) + } +} + +impl Deletable for StateBTreeMap +where + K: Serialize, + V: Serial + DeserialWithState + Deletable, +{ + fn delete(mut self) { self.clear(); } +} + +/// This test module relies on the runtime providing host functions and can only +/// be run using `cargo concordium test`. +#[cfg(feature = "internal-wasm-test")] +mod wasm_test_btree { + use super::*; + use crate::{claim, claim_eq, concordium_test, StateApi, StateBuilder}; + + /// The invariants to check in a btree. + /// Should only be used while debugging and testing the btree itself. + #[derive(Debug)] + pub(crate) enum InvariantViolation { + /// The collection has length above 0, but no root. + NonZeroLenWithNoRoot, + /// The collection contain a root node, but this has no keys. + ZeroKeysInRoot, + /// Iterating the keys in the entire collection, is not in strictly + /// ascending order. + IterationOutOfOrder, + /// Leaf node found at different depths. + LeafAtDifferentDepth, + /// The keys in a node are not in strictly ascending order. + NodeKeysOutOfOrder, + /// The non-leaf node does not contain `keys.len() + 1` children. + MismatchingChildrenLenKeyLen, + /// The non-root node contains fewer keys than the minimum. + KeysLenBelowMin, + /// The non-root node contains more keys than the maximum. + KeysLenAboveMax, + /// The leaf node contains children nodes. + LeafWithChildren, + /// The non-root non-leaf node contains fewer children than the minimum. + ChildrenLenBelowMin, + /// The non-root non-leaf node contains more children than the maximum. + ChildrenLenAboveMax, + } + + impl StateBTreeSet { + /// Check invariants, producing an error if any of them are + /// violated. + /// See [`InvariantViolation`] for the of list invariants being checked. + /// Should only be used while debugging and testing the btree itself. + fn check_invariants(&self) -> Result<(), InvariantViolation> + where + K: Serialize + Ord, { + use crate::ops::Deref; + let Some(root_node_id) = self.root else { + return if self.len == 0 { + Ok(()) + } else { + Err(InvariantViolation::NonZeroLenWithNoRoot) + }; + }; + let root: Node = self.get_node(root_node_id); + if root.keys.is_empty() { + return Err(InvariantViolation::ZeroKeysInRoot); + } + + for i in 1..root.keys.len() { + if &root.keys[i - 1] >= &root.keys[i] { + return Err(InvariantViolation::NodeKeysOutOfOrder); + } + } + if root.keys.len() > Node::::MAXIMUM_KEY_LEN { + return Err(InvariantViolation::KeysLenAboveMax); + } + + if root.is_leaf() { + if !root.children.is_empty() { + return Err(InvariantViolation::LeafWithChildren); + } + } else { + if root.children.len() != root.keys.len() + 1 { + return Err(InvariantViolation::MismatchingChildrenLenKeyLen); + } + if root.children.len() > Node::::MAXIMUM_CHILD_LEN { + return Err(InvariantViolation::ChildrenLenAboveMax); + } + } + + let mut stack = vec![(0usize, root.children)]; + let mut leaf_depth = None; + while let Some((node_level, mut nodes)) = stack.pop() { + while let Some(node_id) = nodes.pop() { + let node: Node = self.get_node(node_id); + node.check_invariants()?; + if node.is_leaf() { + let depth = leaf_depth.get_or_insert(node_level); + if *depth != node_level { + return Err(InvariantViolation::LeafAtDifferentDepth); + } + } else { + stack.push((node_level + 1, node.children)); + } + } + } + + let mut prev = None; + for key in self.iter() { + if let Some(p) = prev.as_deref() { + if p > key.deref() { + return Err(InvariantViolation::IterationOutOfOrder); + } + } + prev = Some(key); + } + Ok(()) + } + + /// Construct a string for displaying the btree and debug information. + /// Should only be used while debugging and testing the btree itself. + pub(crate) fn debug(&self) -> String + where + K: Serialize + std::fmt::Debug + Ord, { + let Some(root_node_id) = self.root else { + return format!("no root"); + }; + let mut string = String::new(); + let root: Node = self.get_node(root_node_id); + string.push_str(format!("root: {:#?}", root).as_str()); + let mut stack = root.children; + + while let Some(node_id) = stack.pop() { + let node: Node = self.get_node(node_id); + string.push_str( + format!("node {} {:?}: {:#?},\n", node_id.id, node.check_invariants(), node) + .as_str(), + ); + + stack.extend(node.children); + } + string + } + } + + impl Node { + /// Check invariants of a non-root node in a btree, producing an error + /// if any of them are violated. + /// See [`InvariantViolation`] for the of list invariants being checked. + /// Should only be used while debugging and testing the btree itself. + pub(crate) fn check_invariants(&self) -> Result<(), InvariantViolation> + where + K: Ord, { + for i in 1..self.keys.len() { + if &self.keys[i - 1] >= &self.keys[i] { + return Err(InvariantViolation::NodeKeysOutOfOrder); + } + } + + if self.keys.len() < Self::MINIMUM_KEY_LEN { + return Err(InvariantViolation::KeysLenBelowMin); + } + if self.keys.len() > Self::MAXIMUM_KEY_LEN { + return Err(InvariantViolation::KeysLenAboveMax); + } + + if self.is_leaf() { + if !self.children.is_empty() { + return Err(InvariantViolation::LeafWithChildren); + } + } else { + if self.children.len() != self.keys.len() + 1 { + return Err(InvariantViolation::MismatchingChildrenLenKeyLen); + } + if self.children.len() < Self::MINIMUM_CHILD_LEN { + return Err(InvariantViolation::ChildrenLenBelowMin); + } + if self.children.len() > Self::MAXIMUM_CHILD_LEN { + return Err(InvariantViolation::ChildrenLenAboveMax); + } + } + + Ok(()) + } + } + + /// Insert `2 * M` items such that the btree contains more than the root + /// node. Checking that every item is contained in the collection. + #[concordium_test] + fn test_btree_insert_asc_above_max_branching_degree() { + let mut state_builder = StateBuilder::open(StateApi::open()); + const M: usize = 5; + let mut tree = state_builder.new_btree_set_degree::(); + let items = (2 * M) as u32; + for n in 0..items { + claim!(tree.insert(n)); + } + for n in 0..items { + claim!(tree.contains(&n)); + } + claim_eq!(tree.len(), items) + } + + /// Insert items such that the btree must be at least height 3 to contain + /// all of them. With a minimum degree of 2, each node can contain up to + /// 3 items and have 4 children, meaning 16 items is needed. + /// Then checks that every item is contained in the collection. + #[concordium_test] + fn test_btree_insert_asc_height_3() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..16 { + claim!(tree.insert(n)); + } + for n in 0..16 { + claim!(tree.contains(&n)); + } + claim_eq!(tree.len(), 16); + claim!(!tree.contains(&17)); + } + + /// Insert items in a random order. + /// Then checks that every item is contained in the collection. + #[concordium_test] + fn test_btree_insert_random_order() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + + tree.insert(0); + tree.insert(3); + tree.insert(2); + tree.insert(1); + tree.insert(5); + tree.insert(7); + tree.insert(6); + tree.insert(4); + + for n in 0..=7 { + claim!(tree.contains(&n)); + } + claim_eq!(tree.len(), 8) + } + + /// Build a set and query `higher` on each key plus some keys outside of the + /// set. + #[concordium_test] + fn test_btree_higher() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + tree.insert(1); + tree.insert(2); + tree.insert(3); + tree.insert(4); + tree.insert(5); + tree.insert(7); + claim_eq!(tree.higher(&0).as_deref(), Some(&1)); + claim_eq!(tree.higher(&1).as_deref(), Some(&2)); + claim_eq!(tree.higher(&2).as_deref(), Some(&3)); + claim_eq!(tree.higher(&3).as_deref(), Some(&4)); + claim_eq!(tree.higher(&4).as_deref(), Some(&5)); + claim_eq!(tree.higher(&5).as_deref(), Some(&7)); + claim_eq!(tree.higher(&6).as_deref(), Some(&7)); + claim_eq!(tree.higher(&7).as_deref(), None); + claim_eq!(tree.higher(&8).as_deref(), None); + } + + /// Build a set and query `lower` on each key plus some keys outside of the + /// set. + #[concordium_test] + fn test_btree_lower() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + tree.insert(1); + tree.insert(2); + tree.insert(3); + tree.insert(4); + tree.insert(5); + tree.insert(7); + claim_eq!(tree.lower(&0).as_deref(), None); + claim_eq!(tree.lower(&1).as_deref(), None); + claim_eq!(tree.lower(&2).as_deref(), Some(&1)); + claim_eq!(tree.lower(&3).as_deref(), Some(&2)); + claim_eq!(tree.lower(&4).as_deref(), Some(&3)); + claim_eq!(tree.lower(&5).as_deref(), Some(&4)); + claim_eq!(tree.lower(&6).as_deref(), Some(&5)); + claim_eq!(tree.lower(&7).as_deref(), Some(&5)); + claim_eq!(tree.lower(&8).as_deref(), Some(&7)); + } + + /// Build a set and query `eq_or_higher` on each key plus some keys outside + /// of the set. + #[concordium_test] + fn test_btree_eq_or_higher() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + tree.insert(1); + tree.insert(2); + tree.insert(3); + tree.insert(4); + tree.insert(5); + tree.insert(7); + claim_eq!(tree.eq_or_higher(&0).as_deref(), Some(&1)); + claim_eq!(tree.eq_or_higher(&1).as_deref(), Some(&1)); + claim_eq!(tree.eq_or_higher(&2).as_deref(), Some(&2)); + claim_eq!(tree.eq_or_higher(&3).as_deref(), Some(&3)); + claim_eq!(tree.eq_or_higher(&4).as_deref(), Some(&4)); + claim_eq!(tree.eq_or_higher(&5).as_deref(), Some(&5)); + claim_eq!(tree.eq_or_higher(&6).as_deref(), Some(&7)); + claim_eq!(tree.eq_or_higher(&7).as_deref(), Some(&7)); + claim_eq!(tree.eq_or_higher(&8).as_deref(), None); + } + + /// Build a set and query `eq_or_lower` on each key plus some keys outside + /// of the set. + #[concordium_test] + fn test_btree_eq_or_lower() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + tree.insert(1); + tree.insert(2); + tree.insert(3); + tree.insert(4); + tree.insert(5); + tree.insert(7); + claim_eq!(tree.eq_or_lower(&0).as_deref(), None); + claim_eq!(tree.eq_or_lower(&1).as_deref(), Some(&1)); + claim_eq!(tree.eq_or_lower(&2).as_deref(), Some(&2)); + claim_eq!(tree.eq_or_lower(&3).as_deref(), Some(&3)); + claim_eq!(tree.eq_or_lower(&4).as_deref(), Some(&4)); + claim_eq!(tree.eq_or_lower(&5).as_deref(), Some(&5)); + claim_eq!(tree.eq_or_lower(&6).as_deref(), Some(&5)); + claim_eq!(tree.eq_or_lower(&7).as_deref(), Some(&7)); + claim_eq!(tree.eq_or_lower(&8).as_deref(), Some(&7)); + } + + /// Insert a large number of items. + /// Check the set contains each item. + /// Insert the same items again, checking the set is the same size + /// afterwards. + #[concordium_test] + fn test_btree_insert_a_lot_then_reinsert() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..500 { + claim!(tree.insert(n)); + } + for n in (500..1000).into_iter().rev() { + claim!(tree.insert(n)); + } + + for n in 0..1000 { + claim!(tree.contains(&n)) + } + claim_eq!(tree.len(), 1000); + + for n in 0..1000 { + claim!(!tree.insert(n)) + } + claim_eq!(tree.len(), 1000) + } + + /// Remove from a btree with only the root node. + #[concordium_test] + fn test_btree_remove_from_one_node_tree() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..3 { + tree.insert(n); + } + claim!(tree.contains(&1)); + claim!(tree.remove(&1)); + claim!(tree.contains(&0)); + claim!(!tree.contains(&1)); + claim!(tree.contains(&2)); + } + + /// Removing from a the lower child node which is at minimum keys, causing a + /// merge. + /// + /// - Builds a tree of the form: [[0], 1, [2]] + /// - Remove 0 + /// - Expecting a tree of the form: [1, 2] + #[concordium_test] + fn test_btree_remove_only_key_lower_leaf_in_three_node() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..4 { + tree.insert(n); + } + tree.remove(&3); + + claim!(tree.remove(&0)); + + claim!(!tree.contains(&0)); + claim!(tree.contains(&1)); + claim!(tree.contains(&2)); + } + + /// Removing from a the higher child node which is at minimum keys, causing + /// a merge. + /// + /// - Builds a tree of the form: [[0], 1, [2]] + /// - Remove 2 + /// - Expecting a tree of the form: [0, 1] + #[concordium_test] + fn test_btree_remove_only_key_higher_leaf_in_three_node() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in (0..4).into_iter().rev() { + tree.insert(n); + } + tree.remove(&3); + claim!(tree.contains(&2)); + + claim!(tree.remove(&2)); + + claim!(tree.contains(&0)); + claim!(tree.contains(&1)); + claim!(!tree.contains(&2)); + } + + /// Removing from a the higher child node which is at minimum keys, causing + /// it to move a key from its higher sibling. + /// + /// - Builds a tree of the form: [[0, 1], 2, [3]] + /// - Remove 3 + /// - Expecting a tree of the form: [[0], 1, [2]] + #[concordium_test] + fn test_btree_remove_from_higher_leaf_in_three_node_taking_from_sibling() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in (0..4).into_iter().rev() { + tree.insert(n); + } + claim!(tree.contains(&3)); + claim!(tree.remove(&3)); + claim!(!tree.contains(&3)); + } + + /// Removing from a the higher child node which is at minimum keys, causing + /// it to move a key from its lower sibling. + /// + /// - Builds a tree of the form: [[0], 1, [2, 3]] + /// - Remove 0 + /// - Expecting a tree of the form: [[1], 2, [3]] + #[concordium_test] + fn test_btree_remove_from_lower_leaf_in_three_node_taking_from_sibling() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..4 { + tree.insert(n); + } + claim!(tree.remove(&0)); + + claim!(!tree.contains(&0)); + claim!(tree.contains(&1)); + claim!(tree.contains(&2)); + claim!(tree.contains(&3)); + } + + /// Removing from a the root node which is at minimum keys, likewise are the + /// children, causing a merge. + /// + /// - Builds a tree of the form: [[0], 1, [2]] + /// - Remove 1 + /// - Expecting a tree of the form: [0, 2] + #[concordium_test] + fn test_btree_remove_from_root_in_three_node_causing_merge() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..4 { + tree.insert(n); + } + tree.remove(&3); + claim!(tree.remove(&1)); + + claim!(tree.contains(&0)); + claim!(!tree.contains(&1)); + claim!(tree.contains(&2)); + } + + /// Removing from a the root node which is at minimum keys, taking a child + /// from its higher child. + /// + /// - Builds a tree of the form: [[0], 1, [2, 3]] + /// - Remove 1 + /// - Expecting a tree of the form: [[0], 2, [3]] + #[concordium_test] + fn test_btree_remove_from_root_in_three_node_taking_key_from_higher_child() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in 0..4 { + tree.insert(n); + } + claim!(tree.contains(&1)); + claim!(tree.remove(&1)); + claim!(!tree.contains(&1)); + } + + /// Removing from a the root node which is at minimum keys, taking a child + /// from its lower child. + /// + /// - Builds a tree of the form: [[0, 1], 2, [3]] + /// - Remove 2 + /// - Expecting a tree of the form: [[0], 1, [3]] + #[concordium_test] + fn test_btree_remove_from_root_in_three_node_taking_key_from_lower_child() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in (0..4).into_iter().rev() { + tree.insert(n); + } + claim!(tree.contains(&2)); + claim!(tree.remove(&2)); + claim!(!tree.contains(&2)); + } + + /// Test iteration of the set. + #[concordium_test] + fn test_btree_iter() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + let keys: Vec = (0..15).into_iter().collect(); + for &k in &keys { + tree.insert(k); + } + let iter_keys: Vec = tree.iter().map(|k| k.clone()).collect(); + claim_eq!(keys, iter_keys); + } + + /// Testcase for duplicate keys in the set. Due to an edge case where the + /// key moved up as part of splitting a child node, is equal to the + /// inserted key. + /// + /// - Builds a tree of the form: [[0, 1, 2], 3, [4]] + /// - Insert 1 (again) causing the [0, 1, 2] to split, moving 1 up to the + /// root. + /// - Expecting a tree of the form: [[0], 1, [2], 3, [4]] + #[concordium_test] + fn test_btree_insert_present_key() { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for n in [0, 3, 4, 1, 2].into_iter() { + tree.insert(n); + } + claim!(!tree.insert(1)); + } + + // The module is using `concordium_quickcheck` which is located in a deprecated + // module. + #[allow(deprecated)] + mod quickcheck { + use super::super::*; + use crate::{ + self as concordium_std, concordium_quickcheck, concordium_test, fail, StateApi, + StateBuilder, StateError, + }; + use ::quickcheck::{Arbitrary, Gen, TestResult}; + + /// Quickcheck inserting random items, check invariants on the tree and + /// query every item ensuring the tree contains it. + #[concordium_quickcheck] + fn quickcheck_btree_inserts(items: Vec) -> TestResult { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for k in items.clone() { + tree.insert(k); + } + if let Err(violation) = tree.check_invariants() { + return TestResult::error(format!("Invariant violated: {:?}", violation)); + } + for k in items.iter() { + if !tree.contains(k) { + return TestResult::error(format!("Missing key: {}", k)); + } + } + TestResult::passed() + } + + /// Quickcheck inserting random items and then clear the entire tree + /// again. Use state api to ensure the btree nodes are no longer + /// stored in the state. + #[concordium_quickcheck] + fn quickcheck_btree_clear(items: Vec) -> TestResult { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for k in items.clone() { + tree.insert(k); + } + tree.clear(); + for k in items.iter() { + if tree.contains(k) { + return TestResult::error(format!("Found {k} in a cleared btree")); + } + } + + let state_api = StateApi::open(); + match state_api.iterator(&tree.prefix) { + Ok(node_iter) => { + let nodes_in_state = node_iter.count(); + TestResult::error(format!( + "Found {} nodes still stored in the state", + nodes_in_state + )) + } + Err(StateError::SubtreeWithPrefixNotFound) => TestResult::passed(), + Err(err) => { + TestResult::error(format!("Failed to get iterator for btree nodes: {err:?}")) + } + } + } + + /// Quickcheck inserting random items, then we call query the tree for + /// higher and lower of every item validating the outcome. + #[concordium_quickcheck(num_tests = 100)] + fn quickcheck_btree_iter(mut items: Vec) -> TestResult { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for k in items.clone() { + tree.insert(k); + } + if let Err(violation) = tree.check_invariants() { + return TestResult::error(format!("Invariant violated: {:?}", violation)); + } + + items.sort(); + items.dedup(); + + for (value, expected) in tree.iter().zip(items.into_iter()) { + if *value != expected { + return TestResult::error(format!("Got {} but expected {expected}", *value)); + } + } + + TestResult::passed() + } + + /// Quickcheck inserting random items, then we call query the tree for + /// higher and lower of every item validating the outcome. + #[concordium_quickcheck(num_tests = 100)] + fn quickcheck_btree_higher_lower(mut items: Vec) -> TestResult { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + for k in items.clone() { + tree.insert(k); + } + if let Err(violation) = tree.check_invariants() { + return TestResult::error(format!("Invariant violated: {:?}", violation)); + } + + items.sort(); + items.dedup(); + for window in items.windows(2) { + let l = &window[0]; + let r = &window[1]; + let l_higher = tree.higher(l); + if l_higher.as_deref() != Some(r) { + return TestResult::error(format!( + "higher({l}) gave {:?} instead of the expected Some({r})", + l_higher.as_deref() + )); + } + let r_lower = tree.lower(r); + if r_lower.as_deref() != Some(l) { + return TestResult::error(format!( + "lower({r}) gave {:?} instead of the expected Some({l})", + r_lower.as_deref() + )); + } + + let space_between = r - l > 1; + if space_between { + let l_eq_or_higher = tree.eq_or_higher(&(l + 1)); + if l_eq_or_higher.as_deref() != Some(r) { + return TestResult::error(format!( + "eq_or_higher({}) gave {:?} instead of the expected Some({r})", + l + 1, + l_higher.as_deref() + )); + } + } + + if space_between { + let r_eq_or_lower = tree.eq_or_lower(&(r - 1)); + if r_eq_or_lower.as_deref() != Some(l) { + return TestResult::error(format!( + "eq_or_lower({}) gave {:?} instead of the expected Some({l})", + r - 1, + l_higher.as_deref() + )); + } + } + } + + if let Some(first) = items.first() { + let lower = tree.lower(first); + if lower.is_some() { + return TestResult::error(format!( + "lower({first}) gave {:?} instead of the expected None", + lower.as_deref() + )); + } + } + + if let Some(last) = items.last() { + let higher = tree.higher(last); + if higher.is_some() { + return TestResult::error(format!( + "higher({last}) gave {:?} instead of the expected None", + higher.as_deref() + )); + } + } + + TestResult::passed() + } + + /// Quickcheck random mutations see `Mutations` and `run_mutations` for + /// the details. + #[concordium_quickcheck(num_tests = 500)] + fn quickcheck_btree_inserts_removes(mutations: Mutations) -> TestResult { + let mut state_builder = StateBuilder::open(StateApi::open()); + let mut tree = state_builder.new_btree_set_degree::<2, _>(); + + if let Err(err) = run_mutations(&mut tree, &mutations.mutations) { + TestResult::error(format!("Error: {}, tree: {}", err, tree.debug())) + } else { + TestResult::passed() + } + } + + /// Newtype wrapper for Vec to implement Arbitrary. + #[derive(Debug, Clone)] + struct Mutations { + expected_keys: crate::collections::BTreeSet, + mutations: Vec<(K, Operation)>, + } + + /// The different mutating operations to generate for the btree. + #[derive(Debug, Clone, Copy)] + enum Operation { + /// Insert a new key in the set. + InsertKeyNotPresent, + /// Insert a key already in the set. + InsertKeyPresent, + /// Remove a key in the set. + RemoveKeyPresent, + /// Remove a key not already in the set. + RemoveKeyNotPresent, + } + + /// Run a list of mutations on a btree, checking the return value and + /// tree invariants using `StateBTreeSet::check_invariants` + /// between each mutation, returning an error string if violated. + fn run_mutations( + tree: &mut StateBTreeSet, + mutations: &[(u32, Operation)], + ) -> Result<(), String> { + for (k, op) in mutations.into_iter() { + if let Err(violation) = tree.check_invariants() { + return Err(format!("Invariant violated: {:?}", violation)); + } + match op { + Operation::InsertKeyPresent => { + if tree.insert(*k) { + return Err(format!("InsertKeyPresent was not present: {}", k)); + } + } + Operation::InsertKeyNotPresent => { + if !tree.insert(*k) { + return Err(format!("InsertKeyNotPresent was present: {}", k)); + } + } + Operation::RemoveKeyNotPresent => { + if tree.remove(k) { + return Err(format!("RemoveKeyNotPresent was present: {}", k)); + } + } + Operation::RemoveKeyPresent => { + if !tree.remove(k) { + return Err(format!("RemoveKeyPresent was not present: {}", k)); + } + } + } + } + Ok(()) + } + + impl Arbitrary for Operation { + fn arbitrary(g: &mut Gen) -> Self { + g.choose(&[ + Self::InsertKeyNotPresent, + Self::InsertKeyPresent, + Self::RemoveKeyPresent, + Self::RemoveKeyNotPresent, + ]) + .unwrap() + .clone() + } + } + + impl Arbitrary for Mutations + where + K: Arbitrary + Ord, + { + fn arbitrary(g: &mut Gen) -> Self { + // Tracking the keys expected in the set at the point of each mutation. + // This is used to ensure operations such as inserting and removing a key which + // is present are actually valid. + let mut inserted_keys: Vec = Vec::new(); + // The generated mutations to return. + let mut mutations = Vec::new(); + + while mutations.len() < g.size() { + let op: Operation = Operation::arbitrary(g); + match op { + Operation::InsertKeyPresent if inserted_keys.len() > 0 => { + let indexes: Vec = + (0..inserted_keys.len()).into_iter().collect(); + let k_index = g.choose(&indexes).unwrap(); + let k = &inserted_keys[*k_index]; + mutations.push((k.clone(), op)); + } + Operation::InsertKeyNotPresent => { + let k = K::arbitrary(g); + if let Err(index) = inserted_keys.binary_search(&k) { + inserted_keys.insert(index, k.clone()); + mutations.push((k, op)); + } + } + Operation::RemoveKeyPresent if inserted_keys.len() > 0 => { + let indexes: Vec = + (0..inserted_keys.len()).into_iter().collect(); + let k_index = g.choose(&indexes).unwrap(); + let k = inserted_keys.remove(*k_index); + mutations.push((k, op)); + } + Operation::RemoveKeyNotPresent => { + let k = K::arbitrary(g); + if inserted_keys.binary_search(&k).is_err() { + mutations.push((k, op)); + } + } + _ => {} + } + } + + Self { + expected_keys: crate::collections::BTreeSet::from_iter( + inserted_keys.into_iter(), + ), + mutations, + } + } + + /// We attempt to produce several shrinked versions: + /// + /// - Simply remove the last mutation from the list of mutations. + /// - Remove mutations, which are not adding or removing keys + /// (Remove non-present keys and inserting present keys), note + /// these might still mutate the internal structure. + /// - Iterate the mutations and when a key is removed, we traverse + /// back in the mutations and remove other mutations of the same + /// key back to when it was inserted. + fn shrink(&self) -> Box> { + let pop = { + let mut clone = self.clone(); + clone.mutations.pop(); + clone + }; + let mut v = vec![pop]; + for (i, (k, op)) in self.mutations.iter().enumerate() { + match op { + Operation::InsertKeyPresent | Operation::RemoveKeyNotPresent => { + let mut clone = self.clone(); + clone.mutations.remove(i); + v.push(clone); + } + Operation::RemoveKeyPresent => { + let mut clone = self.clone(); + let mut prev = self.mutations[0..i].iter().enumerate().rev(); + clone.mutations.remove(i); + clone.expected_keys.remove(k); + loop { + if let Some((j, (k2, op))) = prev.next() { + match op { + Operation::InsertKeyPresent if k == k2 => { + clone.mutations.remove(j); + } + Operation::InsertKeyNotPresent if k == k2 => { + clone.mutations.remove(j); + break; + } + _ => {} + } + } else { + fail!("No insertion found before") + } + } + v.push(clone); + } + _ => {} + } + } + + Box::new(v.into_iter()) + } + } + } +} diff --git a/concordium-std/src/test_infrastructure.rs b/concordium-std/src/test_infrastructure.rs index 052e08671..ff2805fda 100644 --- a/concordium-std/src/test_infrastructure.rs +++ b/concordium-std/src/test_infrastructure.rs @@ -1929,12 +1929,13 @@ pub fn concordium_qc(num_tests: u64, f: A) { #[cfg(test)] mod test { + use super::TestStateApi; use crate::{ cell::RefCell, rc::Rc, test_infrastructure::{TestStateBuilder, TestStateEntry}, - Deletable, EntryRaw, HasStateApi, HasStateEntry, StateMap, StateSet, + Deletable, EntryRaw, HasStateApi, HasStateEntry, StateBTreeSet, StateMap, StateSet, INITIAL_NEXT_ITEM_PREFIX, }; use concordium_contracts_common::{to_bytes, Deserial, Read, Seek, SeekFrom, Write}; @@ -2075,415 +2076,4 @@ mod test { "Correct data was written." ); } - - #[test] - fn high_level_insert_get() { - let expected_value: u64 = 123123123; - let mut state_builder = TestStateBuilder::new(); - state_builder.insert(0, expected_value).expect("Insert failed"); - let actual_value: u64 = state_builder.get(0).expect("Not found").expect("Not a valid u64"); - assert_eq!(expected_value, actual_value); - } - - #[test] - fn low_level_entry() { - let expected_value: u64 = 123123123; - let key = to_bytes(&42u64); - let mut state = TestStateApi::new(); - state - .entry(&key[..]) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("No iterators, so insertion should work."); - - match state.entry(key) { - EntryRaw::Vacant(_) => panic!("Unexpected vacant entry."), - EntryRaw::Occupied(occ) => { - assert_eq!(u64::deserial(&mut occ.get()), Ok(expected_value)) - } - } - } - - #[test] - fn high_level_statemap() { - let my_map_key = "my_map"; - let mut state_builder = TestStateBuilder::new(); - - let map_to_insert = state_builder.new_map::(); - state_builder.insert(my_map_key, map_to_insert).expect("Insert failed"); - - let mut my_map: StateMap = state_builder - .get(my_map_key) - .expect("Could not get statemap") - .expect("Deserializing statemap failed"); - my_map.insert("abc".to_string(), "hello, world".to_string()); - my_map.insert("def".to_string(), "hallo, Weld".to_string()); - my_map.insert("ghi".to_string(), "hej, verden".to_string()); - assert_eq!(*my_map.get(&"abc".to_string()).unwrap(), "hello, world".to_string()); - - let mut iter = my_map.iter(); - let (k1, v1) = iter.next().unwrap(); - assert_eq!(*k1, "abc".to_string()); - assert_eq!(*v1, "hello, world".to_string()); - let (k2, v2) = iter.next().unwrap(); - assert_eq!(*k2, "def".to_string()); - assert_eq!(*v2, "hallo, Weld".to_string()); - let (k3, v3) = iter.next().unwrap(); - assert_eq!(*k3, "ghi".to_string()); - assert_eq!(*v3, "hej, verden".to_string()); - assert!(iter.next().is_none()); - } - - #[test] - fn statemap_insert_remove() { - let mut state_builder = TestStateBuilder::new(); - let mut map = state_builder.new_map(); - let value = String::from("hello"); - let _ = map.insert(42, value.clone()); - assert_eq!(*map.get(&42).unwrap(), value); - map.remove(&42); - assert!(map.get(&42).is_none()); - } - - #[test] - fn statemap_clear() { - let mut state_builder = TestStateBuilder::new(); - let mut map = state_builder.new_map(); - let _ = map.insert(1, 2); - let _ = map.insert(2, 3); - let _ = map.insert(3, 4); - map.clear(); - assert!(map.is_empty()); - } - - #[test] - fn high_level_nested_statemaps() { - let inner_map_key = 0u8; - let key_to_value = 77u8; - let value = 255u8; - let mut state_builder = TestStateBuilder::new(); - let mut outer_map = state_builder.new_map::>(); - let mut inner_map = state_builder.new_map::(); - - inner_map.insert(key_to_value, value); - outer_map.insert(inner_map_key, inner_map); - - assert_eq!(*outer_map.get(&inner_map_key).unwrap().get(&key_to_value).unwrap(), value); - } - - #[test] - fn statemap_iter_mut_works() { - let mut state_builder = TestStateBuilder::new(); - let mut map = state_builder.new_map(); - map.insert(0u8, 1u8); - map.insert(1u8, 2u8); - map.insert(2u8, 3u8); - for (_, mut v) in map.iter_mut() { - v.update(|old_value| *old_value += 10); - } - let mut iter = map.iter(); - let (k1, v1) = iter.next().unwrap(); - assert_eq!(*k1, 0); - assert_eq!(*v1, 11); - let (k2, v2) = iter.next().unwrap(); - assert_eq!(*k2, 1); - assert_eq!(*v2, 12); - let (k3, v3) = iter.next().unwrap(); - assert_eq!(*k3, 2); - assert_eq!(*v3, 13); - assert!(iter.next().is_none()); - } - - #[test] - fn iter_mut_works_on_nested_statemaps() { - let mut state_builder = TestStateBuilder::new(); - let mut outer_map = state_builder.new_map(); - let mut inner_map = state_builder.new_map(); - inner_map.insert(0u8, 1u8); - inner_map.insert(1u8, 2u8); - outer_map.insert(99u8, inner_map); - for (_, mut v_map) in outer_map.iter_mut() { - v_map.update(|v_map| { - for (_, mut inner_v) in v_map.iter_mut() { - inner_v.update(|inner_v| *inner_v += 10); - } - }); - } - - // Check the outer map. - let mut outer_iter = outer_map.iter(); - let (inner_map_key, inner_map) = outer_iter.next().unwrap(); - assert_eq!(*inner_map_key, 99); - assert!(outer_iter.next().is_none()); - - // Check the inner map. - let mut inner_iter = inner_map.iter(); - let (k1, v1) = inner_iter.next().unwrap(); - assert_eq!(*k1, 0); - assert_eq!(*v1, 11); - let (k2, v2) = inner_iter.next().unwrap(); - assert_eq!(*k2, 1); - assert_eq!(*v2, 12); - assert!(inner_iter.next().is_none()); - } - - #[test] - fn statemap_iterator_unlocks_tree_once_dropped() { - let mut state_builder = TestStateBuilder::new(); - let mut map = state_builder.new_map(); - map.insert(0u8, 1u8); - map.insert(1u8, 2u8); - { - let _iter = map.iter(); - // Uncommenting these two lines (and making iter mutable) should - // give a compile error: - // - // map.insert(2u8, 3u8); - // let n = iter.next(); - } // iter is dropped here, unlocking the subtree. - map.insert(2u8, 3u8); - } - - #[test] - fn high_level_stateset() { - let my_set_key = "my_set"; - let mut state_builder = TestStateBuilder::new(); - - let mut set = state_builder.new_set::(); - assert!(set.insert(0)); - assert!(set.insert(1)); - assert!(!set.insert(1)); - assert!(set.insert(2)); - assert!(set.remove(&2)); - state_builder.insert(my_set_key, set).expect("Insert failed"); - - assert!(state_builder.get::<_, StateSet>(my_set_key).unwrap().unwrap().contains(&0),); - assert!(!state_builder - .get::<_, StateSet>(my_set_key) - .unwrap() - .unwrap() - .contains(&2),); - - let set = state_builder.get::<_, StateSet>(my_set_key).unwrap().unwrap(); - let mut iter = set.iter(); - assert_eq!(*iter.next().unwrap(), 0); - assert_eq!(*iter.next().unwrap(), 1); - assert!(iter.next().is_none()); - } - - #[test] - fn high_level_nested_stateset() { - let inner_set_key = 0u8; - let value = 255u8; - let mut state_builder = TestStateBuilder::new(); - let mut outer_map = state_builder.new_map::>(); - let mut inner_set = state_builder.new_set::(); - - inner_set.insert(value); - outer_map.insert(inner_set_key, inner_set); - - assert!(outer_map.get(&inner_set_key).unwrap().contains(&value)); - } - - #[test] - fn stateset_insert_remove() { - let mut state_builder = TestStateBuilder::new(); - let mut set = state_builder.new_set(); - let _ = set.insert(42); - assert!(set.contains(&42)); - set.remove(&42); - assert!(!set.contains(&42)); - } - - #[test] - fn stateset_clear() { - let mut state_builder = TestStateBuilder::new(); - let mut set = state_builder.new_set(); - let _ = set.insert(1); - let _ = set.insert(2); - let _ = set.insert(3); - set.clear(); - assert!(set.is_empty()); - } - - #[test] - fn stateset_iterator_unlocks_tree_once_dropped() { - let mut state_builder = TestStateBuilder::new(); - let mut set = state_builder.new_set(); - set.insert(0u8); - set.insert(1); - { - let _iter = set.iter(); - // Uncommenting these two lines (and making iter mutable) should - // give a compile error: - // - // set.insert(2); - // let n = iter.next(); - } // iter is dropped here, unlocking the subtree. - set.insert(2); - } - - #[test] - fn allocate_and_get_statebox() { - let mut state_builder = TestStateBuilder::new(); - let boxed_value = String::from("I'm boxed"); - let statebox = state_builder.new_box(boxed_value.clone()); - assert_eq!(*statebox.get(), boxed_value); - } - - #[test] - fn a_new_entry_can_not_be_created_under_a_locked_subtree() { - let expected_value: u64 = 123123123; - let key = to_bytes(b"ab"); - let sub_key = to_bytes(b"abc"); - let mut state = TestStateApi::new(); - state - .entry(&key[..]) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("No iterators, so insertion should work."); - assert!(state.iterator(&key).is_ok(), "Iterator should be present"); - let entry = state.create_entry(&sub_key); - assert!(entry.is_err(), "Should not be able to create an entry under a locked subtree"); - } - - #[test] - fn a_new_entry_can_be_created_under_a_different_subtree_in_same_super_tree() { - let expected_value: u64 = 123123123; - let key = to_bytes(b"abcd"); - let key2 = to_bytes(b"abe"); - let mut state = TestStateApi::new(); - state - .entry(&key[..]) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("No iterators, so insertion should work."); - assert!(state.iterator(&key).is_ok(), "Iterator should be present"); - let entry = state.create_entry(&key2); - assert!(entry.is_ok(), "Failed to create a new entry under a different subtree"); - } - - #[test] - fn an_existing_entry_can_not_be_deleted_under_a_locked_subtree() { - let expected_value: u64 = 123123123; - let key = to_bytes(b"ab"); - let sub_key = to_bytes(b"abc"); - let mut state = TestStateApi::new(); - state - .entry(&key[..]) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("no iterators, so insertion should work."); - let sub_entry = state - .entry(sub_key) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("Should be possible to create the entry."); - assert!(state.iterator(&key).is_ok(), "Iterator should be present"); - assert!( - state.delete_entry(sub_entry).is_err(), - "Should not be able to create an entry under a locked subtree" - ); - } - - #[test] - fn an_existing_entry_can_be_deleted_from_a_different_subtree_in_same_super_tree() { - let expected_value: u64 = 123123123; - let key = to_bytes(b"abcd"); - let key2 = to_bytes(b"abe"); - let mut state = TestStateApi::new(); - state - .entry(&key[..]) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("No iterators, so insertion should work."); - let entry2 = state - .entry(key2) - .or_insert_raw(&to_bytes(&expected_value)) - .expect("Should be possible to create the entry."); - assert!(state.iterator(&key).is_ok(), "Iterator should be present"); - assert!( - state.delete_entry(entry2).is_ok(), - "Failed to create a new entry under a different subtree" - ); - } - - #[test] - fn deleting_nested_stateboxes_works() { - let mut state_builder = TestStateBuilder::new(); - let inner_box = state_builder.new_box(99u8); - let middle_box = state_builder.new_box(inner_box); - let outer_box = state_builder.new_box(middle_box); - outer_box.delete(); - let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator"); - // The only remaining node should be the state_builder's next_item_prefix node. - assert!(iter.nth(1).is_none()); - } - - #[test] - fn clearing_statemap_with_stateboxes_works() { - let mut state_builder = TestStateBuilder::new(); - let box1 = state_builder.new_box(1u8); - let box2 = state_builder.new_box(2u8); - let box3 = state_builder.new_box(3u8); - let mut map = state_builder.new_map(); - map.insert(1u8, box1); - map.insert(2u8, box2); - map.insert(3u8, box3); - map.clear(); - let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator"); - // The only remaining node should be the state_builder's next_item_prefix node. - assert!(iter.nth(1).is_none()); - } - - #[test] - fn clearing_nested_statemaps_works() { - let mut state_builder = TestStateBuilder::new(); - let mut inner_map_1 = state_builder.new_map(); - inner_map_1.insert(1u8, 2u8); - inner_map_1.insert(2u8, 3u8); - inner_map_1.insert(3u8, 4u8); - let mut inner_map_2 = state_builder.new_map(); - inner_map_2.insert(11u8, 12u8); - inner_map_2.insert(12u8, 13u8); - inner_map_2.insert(13u8, 14u8); - let mut outer_map = state_builder.new_map(); - outer_map.insert(0u8, inner_map_1); - outer_map.insert(1u8, inner_map_2); - outer_map.clear(); - let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator"); - // The only remaining node should be the state_builder's next_item_prefix node. - assert!(iter.nth(1).is_none()); - } - - #[test] - fn occupied_entry_truncates_leftover_data() { - let mut state_builder = TestStateBuilder::new(); - let mut map = state_builder.new_map(); - map.insert(99u8, "A longer string that should be truncated".into()); - let a_short_string = "A short string".to_string(); - let expected_size = a_short_string.len() + 4; // 4 bytes for the length of the string. - map.entry(99u8).and_modify(|v| *v = a_short_string); - let actual_size = state_builder - .state_api - .lookup_entry(&[INITIAL_NEXT_ITEM_PREFIX[0], 0, 0, 0, 0, 0, 0, 0, 99]) - .expect("Lookup failed") - .size() - .expect("Getting size failed"); - assert_eq!(expected_size as u32, actual_size); - } - - #[test] - fn occupied_entry_raw_truncates_leftover_data() { - let mut state = TestStateApi::new(); - state - .entry([]) - .or_insert_raw(&to_bytes(&"A longer string that should be truncated")) - .expect("No iterators, so insertion should work."); - - let a_short_string = "A short string"; - let expected_size = a_short_string.len() + 4; // 4 bytes for the length of the string. - - match state.entry([]) { - EntryRaw::Vacant(_) => panic!("Entry is vacant"), - EntryRaw::Occupied(mut occ) => occ.insert_raw(&to_bytes(&a_short_string)), - } - let actual_size = - state.lookup_entry(&[]).expect("Lookup failed").size().expect("Getting size failed"); - assert_eq!(expected_size as u32, actual_size); - } } diff --git a/concordium-std/src/traits.rs b/concordium-std/src/traits.rs index 1dd54d03e..89303fc3a 100644 --- a/concordium-std/src/traits.rs +++ b/concordium-std/src/traits.rs @@ -294,7 +294,7 @@ pub trait HasStateApi: Clone { /// Delete an entry. /// Returns an error if the entry did not exist, or if it is part of a /// locked subtree. - fn delete_entry(&mut self, key: Self::EntryType) -> Result<(), StateError>; + fn delete_entry(&mut self, entry: Self::EntryType) -> Result<(), StateError>; /// Delete the entire subtree. /// Returns whether any values were deleted, or an error if the given prefix diff --git a/concordium-std/src/types.rs b/concordium-std/src/types.rs index ee3e35f56..e64a88e8d 100644 --- a/concordium-std/src/types.rs +++ b/concordium-std/src/types.rs @@ -384,6 +384,8 @@ impl<'a, V> crate::ops::Deref for StateRef<'a, V> { /// that the value is properly stored in the contract state maintained by the /// node. pub struct StateRefMut<'a, V: Serial, S: HasStateApi> { + /// This is set as an `UnsafeCell`, to be able to get a mutable reference to + /// the entry without `StateRefMut` being mutable. pub(crate) entry: UnsafeCell, pub(crate) state_api: S, pub(crate) lazy_value: UnsafeCell>, @@ -414,7 +416,8 @@ pub struct ExternStateIter { pub(crate) type StateEntryId = u64; pub(crate) type StateIteratorId = u64; -pub(crate) type StateItemPrefix = [u8; 8]; +pub(crate) const STATE_ITEM_PREFIX_SIZE: usize = 8; +pub(crate) type StateItemPrefix = [u8; STATE_ITEM_PREFIX_SIZE]; /// Type of keys that index into the contract state. pub type Key = Vec; diff --git a/examples/cis2-dynamic-nft/src/lib.rs b/examples/cis2-dynamic-nft/src/lib.rs index f19ebded8..cd7ad00a4 100644 --- a/examples/cis2-dynamic-nft/src/lib.rs +++ b/examples/cis2-dynamic-nft/src/lib.rs @@ -196,7 +196,7 @@ impl State { owner: &Address, state_builder: &mut StateBuilder, ) { - self.tokens.insert(*token_id, TokenMetadataState { + let _ = self.tokens.insert(*token_id, TokenMetadataState { token_metadata_current_state_counter: 0, token_metadata_list: mint_param.metadata_url.clone(), }); @@ -368,7 +368,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/examples/cis2-multi-royalties/src/lib.rs b/examples/cis2-multi-royalties/src/lib.rs index 025515c18..70bc5df99 100644 --- a/examples/cis2-multi-royalties/src/lib.rs +++ b/examples/cis2-multi-royalties/src/lib.rs @@ -229,7 +229,7 @@ impl State { royalty: u8, ) { self.tokens.insert(*token_id); - self.token_details.insert(*token_id, TokenDetails { + let _ = self.token_details.insert(*token_id, TokenDetails { minter: *owner, royalty, }); @@ -355,7 +355,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } fn calculate_royalty_payments( diff --git a/examples/cis2-multi/src/lib.rs b/examples/cis2-multi/src/lib.rs index 62ac6b663..1dc49ce7a 100644 --- a/examples/cis2-multi/src/lib.rs +++ b/examples/cis2-multi/src/lib.rs @@ -642,7 +642,7 @@ impl State { ) -> MetadataUrl { let token_metadata = self.tokens.get(token_id).map(|x| x.to_owned()); if token_metadata.is_none() { - self.tokens.insert(*token_id, metadata_url.to_owned()); + let _ = self.tokens.insert(*token_id, metadata_url.to_owned()); } let mut owner_state = @@ -788,7 +788,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } /// Grant role to an address. diff --git a/examples/cis2-nft/src/lib.rs b/examples/cis2-nft/src/lib.rs index 4b352ef31..e21574182 100644 --- a/examples/cis2-nft/src/lib.rs +++ b/examples/cis2-nft/src/lib.rs @@ -275,7 +275,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/examples/cis2-wccd/src/lib.rs b/examples/cis2-wccd/src/lib.rs index 19fb8110b..4c7b3e86c 100644 --- a/examples/cis2-wccd/src/lib.rs +++ b/examples/cis2-wccd/src/lib.rs @@ -412,7 +412,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/examples/cis3-nft-sponsored-txs/src/lib.rs b/examples/cis3-nft-sponsored-txs/src/lib.rs index 6c07bb68c..e702e3d59 100644 --- a/examples/cis3-nft-sponsored-txs/src/lib.rs +++ b/examples/cis3-nft-sponsored-txs/src/lib.rs @@ -501,7 +501,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/examples/credential-registry/src/lib.rs b/examples/credential-registry/src/lib.rs index d7fcbe6af..965b9e960 100644 --- a/examples/credential-registry/src/lib.rs +++ b/examples/credential-registry/src/lib.rs @@ -314,7 +314,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/examples/eSealing/src/lib.rs b/examples/eSealing/src/lib.rs index 2b30ef37e..b565a35ea 100644 --- a/examples/eSealing/src/lib.rs +++ b/examples/eSealing/src/lib.rs @@ -84,7 +84,7 @@ impl State { /// Add a new file hash (replaces existing file if present). fn add_file(&mut self, file_hash: HashSha2256, timestamp: Timestamp, witness: AccountAddress) { - self.files.insert(file_hash, FileState { + let _ = self.files.insert(file_hash, FileState { timestamp, witness, }); diff --git a/examples/factory/src/lib.rs b/examples/factory/src/lib.rs index 2573f7707..8740dfd70 100644 --- a/examples/factory/src/lib.rs +++ b/examples/factory/src/lib.rs @@ -269,7 +269,7 @@ pub mod factory { let state = host.state_mut(); let next_product = state.next_product; state.next_product = next_product + 1; - state.products.insert(next_product, product_address); + let _ = state.products.insert(next_product, product_address); // Invoke the initialize entrypoint on the product passing in the index for this // product. host.invoke_contract( diff --git a/examples/nametoken/src/lib.rs b/examples/nametoken/src/lib.rs index 20b6441f9..2ba737a8c 100644 --- a/examples/nametoken/src/lib.rs +++ b/examples/nametoken/src/lib.rs @@ -487,7 +487,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/examples/offchain-transfers/src/lib.rs b/examples/offchain-transfers/src/lib.rs index f6a8aa696..35045ca58 100644 --- a/examples/offchain-transfers/src/lib.rs +++ b/examples/offchain-transfers/src/lib.rs @@ -701,7 +701,7 @@ mod tests { balance, ); //Set account3 balance - host.state_mut().balance_sheet.insert(account3, balance); + let _ = host.state_mut().balance_sheet.insert(account3, balance); //Test 1: Try to withdraw too much money from Account 3 let mut ctx = TestReceiveContext::empty(); @@ -782,9 +782,9 @@ mod tests { alice_balance + bob_balance + charlie_balance, ); //Set balance sheet - host.state_mut().balance_sheet.insert(alice, alice_balance); - host.state_mut().balance_sheet.insert(bob, bob_balance); - host.state_mut().balance_sheet.insert(charlie, charlie_balance); + let _ = host.state_mut().balance_sheet.insert(alice, alice_balance); + let _ = host.state_mut().balance_sheet.insert(bob, bob_balance); + let _ = host.state_mut().balance_sheet.insert(charlie, charlie_balance); //Define settlements let settlement1 = Settlement { @@ -1084,9 +1084,9 @@ mod tests { ); //Set balance sheet - host.state_mut().balance_sheet.insert(alice, alice_balance); - host.state_mut().balance_sheet.insert(bob, bob_balance); - host.state_mut().balance_sheet.insert(charlie, charlie_balance); + let _ = host.state_mut().balance_sheet.insert(alice, alice_balance); + let _ = host.state_mut().balance_sheet.insert(bob, bob_balance); + let _ = host.state_mut().balance_sheet.insert(charlie, charlie_balance); // First settlement is fine and with past finality let settlement1 = Settlement { @@ -1266,9 +1266,9 @@ mod tests { alice_balance + bob_balance + charlie_balance, ); //Set balance sheet - host.state_mut().balance_sheet.insert(alice, alice_balance); - host.state_mut().balance_sheet.insert(bob, bob_balance); - host.state_mut().balance_sheet.insert(charlie, charlie_balance); + let _ = host.state_mut().balance_sheet.insert(alice, alice_balance); + let _ = host.state_mut().balance_sheet.insert(bob, bob_balance); + let _ = host.state_mut().balance_sheet.insert(charlie, charlie_balance); //Define settlements let settlement1 = Settlement { diff --git a/examples/sponsored-tx-enabled-auction/src/lib.rs b/examples/sponsored-tx-enabled-auction/src/lib.rs index 8fe900d9c..1e1669ca2 100644 --- a/examples/sponsored-tx-enabled-auction/src/lib.rs +++ b/examples/sponsored-tx-enabled-auction/src/lib.rs @@ -337,7 +337,7 @@ fn add_item( host.state_mut().counter = item_index; // Insert the item into the state. - host.state_mut().items.insert(item_index, ItemState { + let _ = host.state_mut().items.insert(item_index, ItemState { auction_state: AuctionState::NotSoldYet, highest_bidder: None, name: item.name, diff --git a/examples/two-step-transfer/src/lib.rs b/examples/two-step-transfer/src/lib.rs index 9e6f91d9e..bf81dbbd9 100644 --- a/examples/two-step-transfer/src/lib.rs +++ b/examples/two-step-transfer/src/lib.rs @@ -232,7 +232,7 @@ fn contract_receive_message( // Persist the active requests host.state_mut().requests = host.state_builder().new_map(); for (key, req) in active_requests.iter() { - host.state_mut().requests.insert(*key, req.clone()); + let _ = host.state_mut().requests.insert(*key, req.clone()); } // Check if a request already exists @@ -264,7 +264,7 @@ fn contract_receive_message( supporters, }; - host.state_mut().requests.insert(req_id, new_request); + let _ = host.state_mut().requests.insert(req_id, new_request); Ok(()) } @@ -467,7 +467,7 @@ mod tests { let mut state_builder = TestStateBuilder::new(); let mut requests = state_builder.new_map(); - requests.insert(request_id, request); + let _ = requests.insert(request_id, request); let state = State { init_params, requests, @@ -534,7 +534,7 @@ mod tests { let mut state_builder = TestStateBuilder::new(); let mut requests = state_builder.new_map(); - requests.insert(request_id, request); + let _ = requests.insert(request_id, request); let state = State { init_params, requests, @@ -601,7 +601,7 @@ mod tests { supporters.insert(account1); // Outdated request - requests.insert(request_id_outdated, TransferRequest { + let _ = requests.insert(request_id_outdated, TransferRequest { transfer_amount, target_account, times_out_at: ctx @@ -613,7 +613,7 @@ mod tests { }); // Active request - requests.insert(request_id_active, TransferRequest { + let _ = requests.insert(request_id_active, TransferRequest { transfer_amount, target_account, times_out_at: ctx @@ -696,7 +696,7 @@ mod tests { let mut state_builder = TestStateBuilder::new(); let mut requests = state_builder.new_map(); - requests.insert(request_id, request); + let _ = requests.insert(request_id, request); let state = State { init_params, requests, diff --git a/templates/cis2-nft/src/lib.rs b/templates/cis2-nft/src/lib.rs index c055fea43..ce0365eca 100644 --- a/templates/cis2-nft/src/lib.rs +++ b/templates/cis2-nft/src/lib.rs @@ -275,7 +275,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } } diff --git a/templates/credential-registry/src/lib.rs b/templates/credential-registry/src/lib.rs index 1b206cdbe..bcb6d4c75 100644 --- a/templates/credential-registry/src/lib.rs +++ b/templates/credential-registry/src/lib.rs @@ -316,7 +316,7 @@ impl State { std_id: StandardIdentifierOwned, implementors: Vec, ) { - self.implementors.insert(std_id, implementors); + let _ = self.implementors.insert(std_id, implementors); } }