diff --git a/crates/gas/src/initia_stdlib.rs b/crates/gas/src/initia_stdlib.rs index 679d73d9..fb152a17 100644 --- a/crates/gas/src/initia_stdlib.rs +++ b/crates/gas/src/initia_stdlib.rs @@ -14,6 +14,10 @@ crate::macros::define_gas_parameters!( [account_create_address_base_cost: InternalGas, "account.create_address.base", 1102], [account_create_signer_base_cost: InternalGas, "account.create_signer.base", 1102], + [permission_address_base: InternalGas, "permissioned_signer.permission_address.base", 1102], + [is_permissioned_signer_base: InternalGas, "permissioned_signer.is_permissioned_signer.base", 1102], + [signer_from_permissioned_handle_base: InternalGas, "permissioned_signer.signer_from_permissioned_handle.base", 1102], + [address_to_string_base_cost: InternalGas, "address.to_string.base_cost", 1678], // 1102 + 18 * 32 [address_from_string_base_cost: InternalGas, "address.from_string.base_cost", 1102], [address_from_string_per_byte: InternalGasPerByte, "address.from_string.per_byte", 18], diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index ca37213f..87e6fa9e 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -37,7 +37,7 @@ pub use meter::{ pub use misc::{AbstractValueSizeGasParameters, MiscGasParameters}; pub use move_core_types::gas_algebra::{ Arg, Byte, GasQuantity, InternalGas, InternalGasPerArg, InternalGasPerByte, InternalGasUnit, - NumArgs, NumBytes, UnitDiv, + NumArgs, NumBytes, UnitDiv }; pub use traits::{FromOnChainGasSchedule, InitialGasSchedule, ToOnChainGasSchedule}; diff --git a/crates/gas/src/move_stdlib.rs b/crates/gas/src/move_stdlib.rs index 9cbdebe7..7a2233a6 100644 --- a/crates/gas/src/move_stdlib.rs +++ b/crates/gas/src/move_stdlib.rs @@ -1,4 +1,5 @@ -use move_core_types::gas_algebra::{InternalGas, InternalGasPerByte}; +use move_core_types::gas_algebra::{InternalGas, InternalGasPerArg, InternalGasPerByte, InternalGasPerTypeNode}; +use crate::InternalGasPerAbstractValueUnit; crate::macros::define_gas_parameters!( MoveStdlibGasParameters, @@ -11,6 +12,8 @@ crate::macros::define_gas_parameters!( [bcs_serialized_size_base: InternalGas, "bcs.serialized_size.base", 735], [bcs_serialized_size_per_byte_serialized: InternalGasPerByte, "bcs.serialized_size.per_byte_serialized", 36], [bcs_serialized_size_failure: InternalGas, "bcs.serialized_size.failure", 3676], + [bcs_constant_serialized_size_base: InternalGas, "bcs.constant_serialized_size.base", 735], + [bcs_constant_serialized_size_per_type_node: InternalGasPerTypeNode, "bcs.constant_serialized_size.per_type_node", 40], [hash_sha2_256_base: InternalGas, "hash.sha2_256.base", 11028], [hash_sha2_256_per_byte: InternalGasPerByte, "hash.sha2_256.per_byte", 183], @@ -31,5 +34,13 @@ crate::macros::define_gas_parameters!( [string_index_of_base: InternalGas, "string.index_of.base", 1470], [string_index_of_per_byte_pattern: InternalGasPerByte, "string.index_of.per_byte_pattern", 73], [string_index_of_per_byte_searched: InternalGasPerByte, "string.index_of.per_byte_searched", 36], + + [cmp_compare_base: InternalGas, "cmp.compare.base", 367], + [cmp_compare_per_abs_val_unit: InternalGasPerAbstractValueUnit, "cmp.compare.per_abs_val_unit", 14], + + [vector_move_range_base: InternalGas, "vector.move_range.base", 4000], + [vector_move_range_per_index_moved: InternalGasPerArg, "vector.move_range.per_index_moved", 10], + + [mem_swap_base: InternalGas, "mem.swap.base", 1500], ] ); diff --git a/crates/natives/src/interface/context.rs b/crates/natives/src/interface/context.rs index 80fc40b8..1b432675 100644 --- a/crates/natives/src/interface/context.rs +++ b/crates/natives/src/interface/context.rs @@ -61,6 +61,10 @@ impl SafeNativeContext<'_, '_, '_> { self.misc_gas_params.abs_val.abstract_value_size(val) } + pub fn abs_val_size_dereferenced(&self, val: &Value) -> AbstractValueSize { + self.misc_gas_params.abs_val.abstract_value_size_dereferenced(val) + } + /// Computes left gas balance for this native context. pub fn gas_balance(&self) -> InternalGas { self.gas_budget.checked_sub(self.gas_used).unwrap() diff --git a/crates/natives/src/lib.rs b/crates/natives/src/lib.rs index 94a127fd..5b3fe600 100644 --- a/crates/natives/src/lib.rs +++ b/crates/natives/src/lib.rs @@ -24,6 +24,7 @@ pub mod keccak; pub mod move_stdlib; pub mod object; pub mod oracle; +pub mod permissioned_signer; pub mod query; pub mod staking; pub mod string_utils; @@ -86,6 +87,8 @@ pub fn initia_move_natives( ); add_natives_from_module!("biguint", biguint::make_all(builder)); + add_natives_from_module!("permissioned_signer", permissioned_signer::make_all(builder)); + #[cfg(feature = "testing")] add_natives_from_module!("ibctesting", ibctesting::make_all(builder)); diff --git a/crates/natives/src/move_stdlib/bcs.rs b/crates/natives/src/move_stdlib/bcs.rs index a52ca600..e24e3995 100644 --- a/crates/natives/src/move_stdlib/bcs.rs +++ b/crates/natives/src/move_stdlib/bcs.rs @@ -4,13 +4,14 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use move_core_types::gas_algebra::NumBytes; +use move_binary_format::errors::PartialVMError; +use move_core_types::{account_address::AccountAddress, gas_algebra::{NumBytes, NumTypeNodes}, u256, value::{MoveStructLayout, MoveTypeLayout}, vm_status::{sub_status::NFE_BCS_SERIALIZATION_FAILURE, StatusCode}}; use move_vm_runtime::native_functions::NativeFunction; use move_vm_types::{ loaded_data::runtime_types::Type, natives::function::PartialVMResult, value_serde::ValueSerDeContext, - values::{values_impl::Reference, Value}, + values::{values_impl::Reference, Struct, Value}, }; use smallvec::{smallvec, SmallVec}; use std::collections::VecDeque; @@ -22,11 +23,9 @@ use crate::{ safely_pop_arg, }; -// See stdlib/error.move -const ECATEGORY_INVALID_ARGUMENT: u64 = 0x1; - -// native errors always start from 100 -const BCS_SERIALIZATION_FAILURE: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 100; +pub fn create_option_u64(value: Option) -> Value { + Value::struct_(Struct::pack(vec![Value::vector_u64(value)])) +} /*************************************************************************************************** * native fun to_bytes @@ -62,7 +61,7 @@ fn native_to_bytes( Err(_) => { context.charge(gas_params.bcs_to_bytes_failure)?; return Err(SafeNativeError::Abort { - abort_code: BCS_SERIALIZATION_FAILURE, + abort_code: NFE_BCS_SERIALIZATION_FAILURE, }); } }; @@ -80,7 +79,7 @@ fn native_to_bytes( None => { context.charge(gas_params.bcs_to_bytes_failure)?; return Err(SafeNativeError::Abort { - abort_code: BCS_SERIALIZATION_FAILURE, + abort_code: NFE_BCS_SERIALIZATION_FAILURE, }); } }; @@ -123,7 +122,7 @@ fn native_serialized_size( // Reuse the same abort code as bcs::to_bytes. return Err(SafeNativeError::Abort { - abort_code: BCS_SERIALIZATION_FAILURE, + abort_code: NFE_BCS_SERIALIZATION_FAILURE, }); } }; @@ -152,6 +151,110 @@ fn serialized_size_impl( .serialized_size(&value, &ty_layout) } +fn native_constant_serialized_size( + context: &mut SafeNativeContext, + mut ty_args: Vec, + _args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(ty_args.len() == 1); + + let gas_params = &context.native_gas_params.move_stdlib; + + context.charge(gas_params.bcs_constant_serialized_size_base)?; + + let ty = ty_args.pop().unwrap(); + let ty_layout = context.type_to_type_layout(&ty)?; + + let (visited_count, serialized_size_result) = constant_serialized_size(&ty_layout); + context + .charge(gas_params.bcs_constant_serialized_size_per_type_node * NumTypeNodes::new(visited_count))?; + + let result = match serialized_size_result { + Ok(value) => create_option_u64(value.map(|v| v as u64)), + Err(_) => { + context.charge(gas_params.bcs_serialized_size_failure)?; + + // Re-use the same abort code as bcs::to_bytes. + return Err(SafeNativeError::Abort { + abort_code: NFE_BCS_SERIALIZATION_FAILURE, + }); + }, + }; + + Ok(smallvec![result]) +} + +/// If given type has a constant serialized size (irrespective of the instance), it returns the serialized +/// size in bytes any value would have. +/// Otherwise it returns None. +/// First element of the returned tuple represents number of visited nodes, used to charge gas. +fn constant_serialized_size(ty_layout: &MoveTypeLayout) -> (u64, PartialVMResult>) { + let mut visited_count = 1; + let bcs_size_result = match ty_layout { + MoveTypeLayout::Bool => bcs::serialized_size(&false).map(Some), + MoveTypeLayout::U8 => bcs::serialized_size(&0u8).map(Some), + MoveTypeLayout::U16 => bcs::serialized_size(&0u16).map(Some), + MoveTypeLayout::U32 => bcs::serialized_size(&0u32).map(Some), + MoveTypeLayout::U64 => bcs::serialized_size(&0u64).map(Some), + MoveTypeLayout::U128 => bcs::serialized_size(&0u128).map(Some), + MoveTypeLayout::U256 => bcs::serialized_size(&u256::U256::zero()).map(Some), + MoveTypeLayout::Address => bcs::serialized_size(&AccountAddress::ZERO).map(Some), + // signer's size is VM implementation detail, and can change at will. + MoveTypeLayout::Signer => Ok(None), + // vectors have no constant size + MoveTypeLayout::Vector(_) => Ok(None), + // enums and functions have no constant size + MoveTypeLayout::Struct( + MoveStructLayout::RuntimeVariants(_) | MoveStructLayout::WithVariants(_), + ) + | MoveTypeLayout::Function(..) => Ok(None), + MoveTypeLayout::Struct(MoveStructLayout::Runtime(fields)) => { + let mut total = Some(0); + for field in fields { + let (cur_visited_count, cur) = constant_serialized_size(field); + visited_count += cur_visited_count; + match cur { + Err(e) => return (visited_count, Err(e)), + Ok(Some(cur_value)) => total = total.map(|v| v + cur_value), + Ok(None) => { + total = None; + break; + }, + } + } + Ok(total) + }, + MoveTypeLayout::Struct(MoveStructLayout::WithFields(_)) + | MoveTypeLayout::Struct(MoveStructLayout::WithTypes { .. }) => { + return ( + visited_count, + Err( + PartialVMError::new(StatusCode::VALUE_SERIALIZATION_ERROR).with_message( + "Only runtime types expected, but found WithFields/WithTypes".to_string(), + ), + ), + ) + }, + MoveTypeLayout::Native(_, inner) => { + let (cur_visited_count, cur) = constant_serialized_size(inner); + visited_count += cur_visited_count; + match cur { + Err(e) => return (visited_count, Err(e)), + Ok(v) => Ok(v), + } + }, + }; + ( + visited_count, + bcs_size_result.map_err(|e| { + PartialVMError::new(StatusCode::VALUE_SERIALIZATION_ERROR).with_message(format!( + "failed to compute serialized size of a value: {:?}", + e + )) + }), + ) +} + /*************************************************************************************************** * module **************************************************************************************************/ @@ -161,6 +264,7 @@ pub fn make_all( let funcs = [ ("to_bytes", native_to_bytes as RawSafeNative), ("serialized_size", native_serialized_size), + ("constant_serialized_size", native_constant_serialized_size), ]; builder.make_named_natives(funcs) diff --git a/crates/natives/src/move_stdlib/cmp.rs b/crates/natives/src/move_stdlib/cmp.rs new file mode 100644 index 00000000..d6793bf8 --- /dev/null +++ b/crates/natives/src/move_stdlib/cmp.rs @@ -0,0 +1,74 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +// Copyright (c) The Diem Core Contributors +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of native functions for value comparison. + +use crate::interface::{ + RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, SafeNativeResult, +}; +use move_core_types::vm_status::StatusCode; +use move_vm_runtime::native_functions::NativeFunction; +use move_vm_types::{ + loaded_data::runtime_types::Type, + natives::function::PartialVMError, + values::{Struct, Value}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + +const ORDERING_LESS_THAN_VARIANT: u16 = 0; +const ORDERING_EQUAL_VARIANT: u16 = 1; +const ORDERING_GREATER_THAN_VARIANT: u16 = 2; + +/*************************************************************************************************** + * native fun native_compare + * + * gas cost: CMP_COMPARE_BASE + CMP_COMPARE_PER_ABS_VAL_UNIT * dereferenced_size_of_both_values + * + **************************************************************************************************/ +fn native_compare( + context: &mut SafeNativeContext, + _ty_args: Vec, + args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(args.len() == 2); + let gas_params = &context.native_gas_params.move_stdlib; + + if args.len() != 2 { + return Err(SafeNativeError::InvariantViolation(PartialVMError::new( + StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, + ))); + } + + let cost = gas_params.cmp_compare_base + + gas_params.cmp_compare_per_abs_val_unit + * (context.abs_val_size_dereferenced(&args[0]) + + context.abs_val_size_dereferenced(&args[1])); + context.charge(cost)?; + + let ordering = args[0].compare(&args[1])?; + let ordering_move_variant = match ordering { + std::cmp::Ordering::Less => ORDERING_LESS_THAN_VARIANT, + std::cmp::Ordering::Equal => ORDERING_EQUAL_VARIANT, + std::cmp::Ordering::Greater => ORDERING_GREATER_THAN_VARIANT, + }; + + Ok(smallvec![Value::struct_(Struct::pack(vec![Value::u16( + ordering_move_variant + )]))]) +} + +/*************************************************************************************************** + * module + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [("compare", native_compare as RawSafeNative)]; + + builder.make_named_natives(natives) +} diff --git a/crates/natives/src/move_stdlib/mem.rs b/crates/natives/src/move_stdlib/mem.rs new file mode 100644 index 00000000..830ed157 --- /dev/null +++ b/crates/natives/src/move_stdlib/mem.rs @@ -0,0 +1,53 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of native functions for memory manipulation. + +use crate::{interface::{ + RawSafeNative, SafeNativeBuilder, SafeNativeContext, + SafeNativeResult, +}, safely_pop_arg}; +use move_vm_runtime::native_functions::NativeFunction; +use move_vm_types::{ + loaded_data::runtime_types::Type, + values::{Reference, Value}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + + +/*************************************************************************************************** + * native fun native_swap + * + * gas cost: MEM_SWAP_BASE + * + **************************************************************************************************/ +fn native_swap( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + let gas_params = &context.native_gas_params.move_stdlib; + + debug_assert!(args.len() == 2); + + context.charge(gas_params.mem_swap_base)?; + + let left = safely_pop_arg!(args, Reference); + let right = safely_pop_arg!(args, Reference); + + left.swap_values(right)?; + + Ok(smallvec![]) +} + +/*************************************************************************************************** + * module + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [("swap", native_swap as RawSafeNative)]; + + builder.make_named_natives(natives) +} diff --git a/crates/natives/src/move_stdlib/mod.rs b/crates/natives/src/move_stdlib/mod.rs index 6feb945c..76a27bcb 100644 --- a/crates/natives/src/move_stdlib/mod.rs +++ b/crates/natives/src/move_stdlib/mod.rs @@ -5,9 +5,12 @@ // SPDX-License-Identifier: Apache-2.0 pub mod bcs; +pub mod cmp; pub mod hash; +pub mod mem; pub mod signer; pub mod string; +pub mod vector; #[cfg(feature = "testing")] pub mod unit_test; @@ -34,6 +37,9 @@ pub fn all_natives( add_natives!("hash", hash::make_all(builder)); add_natives!("signer", signer::make_all(builder)); add_natives!("string", string::make_all(builder)); + add_natives!("cmp", cmp::make_all(builder)); + add_natives!("mem", mem::make_all(builder)); + add_natives!("vector", vector::make_all(builder)); #[cfg(feature = "testing")] add_natives!("unit_test", unit_test::make_all(builder)); diff --git a/crates/natives/src/move_stdlib/vector.rs b/crates/natives/src/move_stdlib/vector.rs new file mode 100644 index 00000000..fe822f59 --- /dev/null +++ b/crates/natives/src/move_stdlib/vector.rs @@ -0,0 +1,102 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +// Copyright (c) The Diem Core Contributors +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of native functions (non-bytecode instructions) for vector. + +use crate::{safely_pop_arg, interface::{ + RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, + SafeNativeResult, +}}; +use move_core_types::gas_algebra::NumArgs; +use move_vm_runtime::native_functions::NativeFunction; +use move_vm_types::{ + loaded_data::runtime_types::Type, + values::{Value, VectorRef}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + +/// Given input positions/lengths are outside of vector boundaries. +pub const EINDEX_OUT_OF_BOUNDS: u64 = 0x1 << 16 + 1; + +/*************************************************************************************************** + * native fun move_range(from: &mut vector, removal_position: u64, length: u64, to: &mut vector, insert_position: u64) + * + * gas cost: VECTOR_MOVE_RANGE_BASE + VECTOR_MOVE_RANGE_PER_INDEX_MOVED * num_elements_to_move + * + **************************************************************************************************/ +fn native_move_range( + context: &mut SafeNativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + let gas_params = &context.native_gas_params.move_stdlib; + + context.charge(gas_params.vector_move_range_base)?; + + let map_err = |_| SafeNativeError::Abort { + abort_code: EINDEX_OUT_OF_BOUNDS, + }; + let insert_position = usize::try_from(safely_pop_arg!(args, u64)).map_err(map_err)?; + let to = safely_pop_arg!(args, VectorRef); + let length = usize::try_from(safely_pop_arg!(args, u64)).map_err(map_err)?; + let removal_position = usize::try_from(safely_pop_arg!(args, u64)).map_err(map_err)?; + let from = safely_pop_arg!(args, VectorRef); + + // We need to charge before executing, so fetching and checking sizes here. + // We repeat fetching and checking of the sizes inside VectorRef::move_range call as well. + // Not sure if possible to combine (as we are never doing charging there). + let to_len = to.length_as_usize(&ty_args[0])?; + let from_len = from.length_as_usize(&ty_args[0])?; + + if removal_position + .checked_add(length) + .is_none_or(|end| end > from_len) + || insert_position > to_len + { + return Err(SafeNativeError::Abort { + abort_code: EINDEX_OUT_OF_BOUNDS, + }); + } + + // We are moving all elements in the range, all elements after range, and all elements after insertion point. + // We are counting "length" of moving block twice, as it both gets moved out and moved in. + // From calibration testing, this seems to be a reasonable approximation of the cost of the operation. + context.charge( + gas_params.vector_move_range_per_index_moved + * NumArgs::new( + (from_len - removal_position) + .checked_add(to_len - insert_position) + .and_then(|v| v.checked_add(length)) + .ok_or_else(|| SafeNativeError::Abort { + abort_code: EINDEX_OUT_OF_BOUNDS, + })? as u64, + ), + )?; + + VectorRef::move_range( + &from, + removal_position, + length, + &to, + insert_position, + &ty_args[0], + )?; + + Ok(smallvec![]) +} + +/*************************************************************************************************** + * module + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [("move_range", native_move_range as RawSafeNative)]; + + builder.make_named_natives(natives) +} diff --git a/crates/natives/src/permissioned_signer.rs b/crates/natives/src/permissioned_signer.rs new file mode 100644 index 00000000..2be0c3ab --- /dev/null +++ b/crates/natives/src/permissioned_signer.rs @@ -0,0 +1,111 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 +use crate::{interface::{ + RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, + SafeNativeResult, +}, safely_pop_arg}; +use move_core_types::account_address::AccountAddress; +use move_vm_runtime::native_functions::NativeFunction; +use move_vm_types::{ + loaded_data::runtime_types::Type, + values::{SignerRef, Value}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + +/*************************************************************************************************** + * native fun is_permissioned_signer_impl + * + * Returns true if the signer passed in is a permissioned signer + * gas cost: base_cost + * + **************************************************************************************************/ +fn native_is_permissioned_signer_impl( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut arguments: VecDeque, +) -> SafeNativeResult> { + debug_assert!(arguments.len() == 1); + + let gas_params = &context.native_gas_params.initia_stdlib; + let signer = safely_pop_arg!(arguments, SignerRef); + + context.charge(gas_params.is_permissioned_signer_base)?; + let result = signer.is_permissioned()?; + + Ok(smallvec![Value::bool(result)]) +} + +/*************************************************************************************************** + * native fun permission_address + * + * Returns the permission storage address if the signer passed in is a permissioned signer + * gas cost: base_cost + * + **************************************************************************************************/ +fn native_permission_address( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(args.len() == 1); + let gas_params = &context.native_gas_params.initia_stdlib; + let signer = safely_pop_arg!(args, SignerRef); + + context.charge(gas_params.permission_address_base)?; + if !signer.is_permissioned()? { + return Err(SafeNativeError::Abort { abort_code: 3 }); + } + + Ok(smallvec![signer.permission_address()?]) +} + +/*************************************************************************************************** + * native fun signer_from_permissioned_handle_impl + * + * Returns the permission signer from a master signer. + * gas cost: base_cost + * + **************************************************************************************************/ +fn native_signer_from_permissioned( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut arguments: VecDeque, +) -> SafeNativeResult> { + debug_assert!(arguments.len() == 2); + let gas_params = &context.native_gas_params.initia_stdlib; + let permission_addr = safely_pop_arg!(arguments, AccountAddress); + let master_addr = safely_pop_arg!(arguments, AccountAddress); + context.charge(gas_params.signer_from_permissioned_handle_base)?; + + Ok(smallvec![Value::permissioned_signer( + master_addr, + permission_addr + )]) +} + +/*************************************************************************************************** + * module + * + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [ + ( + "is_permissioned_signer_impl", + native_is_permissioned_signer_impl as RawSafeNative, + ), + ( + "is_permissioned_signer", + native_is_permissioned_signer_impl as RawSafeNative, + ), + ("permission_address", native_permission_address), + ( + "signer_from_permissioned_handle_impl", + native_signer_from_permissioned, + ), + ]; + + builder.make_named_natives(natives) +} diff --git a/precompile/binaries/minlib/acl.mv b/precompile/binaries/minlib/acl.mv index dfe5e9c0..f4d9379e 100644 Binary files a/precompile/binaries/minlib/acl.mv and b/precompile/binaries/minlib/acl.mv differ diff --git a/precompile/binaries/minlib/bcs.mv b/precompile/binaries/minlib/bcs.mv index 61cdac93..24718b86 100644 Binary files a/precompile/binaries/minlib/bcs.mv and b/precompile/binaries/minlib/bcs.mv differ diff --git a/precompile/binaries/minlib/bigdecimal.mv b/precompile/binaries/minlib/bigdecimal.mv index fc10282e..5cdca6b5 100644 Binary files a/precompile/binaries/minlib/bigdecimal.mv and b/precompile/binaries/minlib/bigdecimal.mv differ diff --git a/precompile/binaries/minlib/bit_vector.mv b/precompile/binaries/minlib/bit_vector.mv index a81d8d0a..cd67d5a6 100644 Binary files a/precompile/binaries/minlib/bit_vector.mv and b/precompile/binaries/minlib/bit_vector.mv differ diff --git a/precompile/binaries/minlib/capability.mv b/precompile/binaries/minlib/capability.mv index 293211a1..9114a23d 100644 Binary files a/precompile/binaries/minlib/capability.mv and b/precompile/binaries/minlib/capability.mv differ diff --git a/precompile/binaries/minlib/cmp.mv b/precompile/binaries/minlib/cmp.mv new file mode 100644 index 00000000..7b227002 Binary files /dev/null and b/precompile/binaries/minlib/cmp.mv differ diff --git a/precompile/binaries/minlib/ed25519.mv b/precompile/binaries/minlib/ed25519.mv index a67b2d3c..5968cbfa 100644 Binary files a/precompile/binaries/minlib/ed25519.mv and b/precompile/binaries/minlib/ed25519.mv differ diff --git a/precompile/binaries/minlib/mem.mv b/precompile/binaries/minlib/mem.mv new file mode 100644 index 00000000..4ddcfb68 Binary files /dev/null and b/precompile/binaries/minlib/mem.mv differ diff --git a/precompile/binaries/minlib/option.mv b/precompile/binaries/minlib/option.mv index e9e0bc15..65365b74 100644 Binary files a/precompile/binaries/minlib/option.mv and b/precompile/binaries/minlib/option.mv differ diff --git a/precompile/binaries/minlib/simple_map.mv b/precompile/binaries/minlib/simple_map.mv index 9922090f..dfd0c838 100644 Binary files a/precompile/binaries/minlib/simple_map.mv and b/precompile/binaries/minlib/simple_map.mv differ diff --git a/precompile/binaries/minlib/table.mv b/precompile/binaries/minlib/table.mv index f62b84c3..365a1ae8 100644 Binary files a/precompile/binaries/minlib/table.mv and b/precompile/binaries/minlib/table.mv differ diff --git a/precompile/binaries/minlib/vector.mv b/precompile/binaries/minlib/vector.mv index 9bf1e781..643760a9 100644 Binary files a/precompile/binaries/minlib/vector.mv and b/precompile/binaries/minlib/vector.mv differ diff --git a/precompile/binaries/stdlib/account.mv b/precompile/binaries/stdlib/account.mv index d9ca1512..1aacbda8 100644 Binary files a/precompile/binaries/stdlib/account.mv and b/precompile/binaries/stdlib/account.mv differ diff --git a/precompile/binaries/stdlib/acl.mv b/precompile/binaries/stdlib/acl.mv index dfe5e9c0..f4d9379e 100644 Binary files a/precompile/binaries/stdlib/acl.mv and b/precompile/binaries/stdlib/acl.mv differ diff --git a/precompile/binaries/stdlib/bcs.mv b/precompile/binaries/stdlib/bcs.mv index 61cdac93..24718b86 100644 Binary files a/precompile/binaries/stdlib/bcs.mv and b/precompile/binaries/stdlib/bcs.mv differ diff --git a/precompile/binaries/stdlib/big_ordered_map.mv b/precompile/binaries/stdlib/big_ordered_map.mv new file mode 100644 index 00000000..38fa9625 Binary files /dev/null and b/precompile/binaries/stdlib/big_ordered_map.mv differ diff --git a/precompile/binaries/stdlib/bigdecimal.mv b/precompile/binaries/stdlib/bigdecimal.mv index fc10282e..80f289ce 100644 Binary files a/precompile/binaries/stdlib/bigdecimal.mv and b/precompile/binaries/stdlib/bigdecimal.mv differ diff --git a/precompile/binaries/stdlib/biguint.mv b/precompile/binaries/stdlib/biguint.mv index 753fed82..dad9e2f6 100644 Binary files a/precompile/binaries/stdlib/biguint.mv and b/precompile/binaries/stdlib/biguint.mv differ diff --git a/precompile/binaries/stdlib/bit_vector.mv b/precompile/binaries/stdlib/bit_vector.mv index a81d8d0a..cd67d5a6 100644 Binary files a/precompile/binaries/stdlib/bit_vector.mv and b/precompile/binaries/stdlib/bit_vector.mv differ diff --git a/precompile/binaries/stdlib/capability.mv b/precompile/binaries/stdlib/capability.mv index 293211a1..9114a23d 100644 Binary files a/precompile/binaries/stdlib/capability.mv and b/precompile/binaries/stdlib/capability.mv differ diff --git a/precompile/binaries/stdlib/cmp.mv b/precompile/binaries/stdlib/cmp.mv new file mode 100644 index 00000000..7b227002 Binary files /dev/null and b/precompile/binaries/stdlib/cmp.mv differ diff --git a/precompile/binaries/stdlib/dex.mv b/precompile/binaries/stdlib/dex.mv index 97ceec7d..a99edd1f 100644 Binary files a/precompile/binaries/stdlib/dex.mv and b/precompile/binaries/stdlib/dex.mv differ diff --git a/precompile/binaries/stdlib/ed25519.mv b/precompile/binaries/stdlib/ed25519.mv index a67b2d3c..5968cbfa 100644 Binary files a/precompile/binaries/stdlib/ed25519.mv and b/precompile/binaries/stdlib/ed25519.mv differ diff --git a/precompile/binaries/stdlib/fixed_point64.mv b/precompile/binaries/stdlib/fixed_point64.mv index fb79ec86..71548f0e 100644 Binary files a/precompile/binaries/stdlib/fixed_point64.mv and b/precompile/binaries/stdlib/fixed_point64.mv differ diff --git a/precompile/binaries/stdlib/math128.mv b/precompile/binaries/stdlib/math128.mv index b4099a8c..32c437fd 100644 Binary files a/precompile/binaries/stdlib/math128.mv and b/precompile/binaries/stdlib/math128.mv differ diff --git a/precompile/binaries/stdlib/math64.mv b/precompile/binaries/stdlib/math64.mv index aa7dea16..15e2fe8c 100644 Binary files a/precompile/binaries/stdlib/math64.mv and b/precompile/binaries/stdlib/math64.mv differ diff --git a/precompile/binaries/stdlib/mem.mv b/precompile/binaries/stdlib/mem.mv new file mode 100644 index 00000000..4ddcfb68 Binary files /dev/null and b/precompile/binaries/stdlib/mem.mv differ diff --git a/precompile/binaries/stdlib/minitswap.mv b/precompile/binaries/stdlib/minitswap.mv index b1840c4c..6fe7a044 100644 Binary files a/precompile/binaries/stdlib/minitswap.mv and b/precompile/binaries/stdlib/minitswap.mv differ diff --git a/precompile/binaries/stdlib/option.mv b/precompile/binaries/stdlib/option.mv index e9e0bc15..65365b74 100644 Binary files a/precompile/binaries/stdlib/option.mv and b/precompile/binaries/stdlib/option.mv differ diff --git a/precompile/binaries/stdlib/ordered_map.mv b/precompile/binaries/stdlib/ordered_map.mv new file mode 100644 index 00000000..f594aaa8 Binary files /dev/null and b/precompile/binaries/stdlib/ordered_map.mv differ diff --git a/precompile/binaries/stdlib/permissioned_signer.mv b/precompile/binaries/stdlib/permissioned_signer.mv new file mode 100644 index 00000000..2bc7d98d Binary files /dev/null and b/precompile/binaries/stdlib/permissioned_signer.mv differ diff --git a/precompile/binaries/stdlib/royalty.mv b/precompile/binaries/stdlib/royalty.mv index 75527207..1bfefcf0 100644 Binary files a/precompile/binaries/stdlib/royalty.mv and b/precompile/binaries/stdlib/royalty.mv differ diff --git a/precompile/binaries/stdlib/simple_map.mv b/precompile/binaries/stdlib/simple_map.mv index 9922090f..412ee915 100644 Binary files a/precompile/binaries/stdlib/simple_map.mv and b/precompile/binaries/stdlib/simple_map.mv differ diff --git a/precompile/binaries/stdlib/storage_slots_allocator.mv b/precompile/binaries/stdlib/storage_slots_allocator.mv new file mode 100644 index 00000000..8d45ea50 Binary files /dev/null and b/precompile/binaries/stdlib/storage_slots_allocator.mv differ diff --git a/precompile/binaries/stdlib/table.mv b/precompile/binaries/stdlib/table.mv index f62b84c3..44379dfe 100644 Binary files a/precompile/binaries/stdlib/table.mv and b/precompile/binaries/stdlib/table.mv differ diff --git a/precompile/binaries/stdlib/table_with_length.mv b/precompile/binaries/stdlib/table_with_length.mv new file mode 100644 index 00000000..8eedccc8 Binary files /dev/null and b/precompile/binaries/stdlib/table_with_length.mv differ diff --git a/precompile/binaries/stdlib/vector.mv b/precompile/binaries/stdlib/vector.mv index 9bf1e781..643760a9 100644 Binary files a/precompile/binaries/stdlib/vector.mv and b/precompile/binaries/stdlib/vector.mv differ diff --git a/precompile/modules/initia_stdlib/sources/account.move b/precompile/modules/initia_stdlib/sources/account.move index f00be73f..8e96c299 100644 --- a/precompile/modules/initia_stdlib/sources/account.move +++ b/precompile/modules/initia_stdlib/sources/account.move @@ -9,6 +9,7 @@ module initia_std::account { friend initia_std::staking; friend initia_std::object; friend initia_std::table; + friend initia_std::permissioned_signer; /// Account Types const ACCOUNT_TYPE_BASE: u8 = 0; diff --git a/precompile/modules/initia_stdlib/sources/big_ordered_map.move b/precompile/modules/initia_stdlib/sources/big_ordered_map.move new file mode 100644 index 00000000..8b30a572 --- /dev/null +++ b/precompile/modules/initia_stdlib/sources/big_ordered_map.move @@ -0,0 +1,2672 @@ +/// This module provides an implementation for an big ordered map. +/// Big means that it is stored across multiple resources, and doesn't have an +/// upper limit on number of elements it can contain. +/// +/// Keys point to values, and each key in the map must be unique. +/// +/// Currently, one implementation is provided - BPlusTreeMap, backed by a B+Tree, +/// with each node being a separate resource, internally containing OrderedMap. +/// +/// BPlusTreeMap is chosen since the biggest (performance and gast) +/// costs are reading resources, and it: +/// * reduces number of resource accesses +/// * reduces number of rebalancing operations, and makes each rebalancing +/// operation touch only few resources +/// * it allows for parallelism for keys that are not close to each other, +/// once it contains enough keys +/// +/// TODO: all iterator functions are public(friend) for now, so that they can be modified in a +/// backward incompatible way. Type is also named IteratorPtr, so that Iterator is free to use later. +/// They are waiting for Move improvement that will allow references to be part of the struct, +/// allowing cleaner iterator APIs. +module initia_std::big_ordered_map { + use std::error; + use std::vector; + use std::option::{Self as option, Option}; + use std::bcs; + use std::ordered_map::{Self, OrderedMap}; + use std::cmp; + use std::storage_slots_allocator::{Self, StorageSlotsAllocator, StoredSlot}; + use std::math64::{max, min}; + + // Error constants shared with ordered_map (so try using same values) + + /// Map key already exists + const EKEY_ALREADY_EXISTS: u64 = 1; + /// Map key is not found + const EKEY_NOT_FOUND: u64 = 2; + /// Trying to do an operation on an IteratorPtr that would go out of bounds + const EITER_OUT_OF_BOUNDS: u64 = 3; + + // Error constants specific to big_ordered_map + + /// The provided configuration parameter is invalid. + const EINVALID_CONFIG_PARAMETER: u64 = 11; + /// Map isn't empty + const EMAP_NOT_EMPTY: u64 = 12; + /// Trying to insert too large of an object into the map. + const EARGUMENT_BYTES_TOO_LARGE: u64 = 13; + /// borrow_mut requires that key and value types have constant size + /// (otherwise it wouldn't be able to guarantee size requirements are not violated) + /// Use remove() + add() combo instead. + const EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE: u64 = 14; + + // Errors that should never be thrown + + /// Internal errors. + const EINTERNAL_INVARIANT_BROKEN: u64 = 20; + + // Internal constants. + + const DEFAULT_TARGET_NODE_SIZE: u64 = 4096; + const INNER_MIN_DEGREE: u16 = 4; + // We rely on 1 being valid size only for root node, + // so this cannot be below 3 (unless that is changed) + const LEAF_MIN_DEGREE: u16 = 3; + const MAX_DEGREE: u64 = 4096; + + const MAX_NODE_BYTES: u64 = 409600; // 400 KB, bellow the max resource limit. + + // Constants aligned with storage_slots_allocator + const NULL_INDEX: u64 = 0; + const ROOT_INDEX: u64 = 1; + + /// A node of the BigOrderedMap. + /// + /// Inner node will have all children be Child::Inner, pointing to the child nodes. + /// Leaf node will have all children be Child::Leaf. + /// Basically - Leaf node is a single-resource OrderedMap, containing as much key/value entries, as can fit. + /// So Leaf node contains multiple values, not just one. + enum Node has store { + V1 { + // Whether this node is a leaf node. + is_leaf: bool, + // The children of the nodes. + // When node is inner node, K represents max_key within the child subtree, and values are Child::Inner. + // When the node is leaf node, K represents key of the leaf, and values are Child::Leaf. + children: OrderedMap>, + // The node index of its previous node at the same level, or `NULL_INDEX` if it doesn't have a previous node. + prev: u64, + // The node index of its next node at the same level, or `NULL_INDEX` if it doesn't have a next node. + next: u64 + } + } + + /// Contents of a child node. + enum Child has store { + Inner { + // The node index of it's child + node_index: StoredSlot + }, + Leaf { + // Value associated with the leaf node. + value: V + } + } + + /// An iterator to iterate all keys in the BigOrderedMap. + /// + /// TODO: Once fields can be (mutable) references, this class will be deprecated. + enum IteratorPtr has copy, drop { + End, + Some { + /// The node index of the iterator pointing to. + node_index: u64, + + /// Child iter it is pointing to + child_iter: ordered_map::IteratorPtr, + + /// `key` to which `(node_index, child_iter)` are pointing to + /// cache to not require borrowing global resources to fetch again + key: K + } + } + + /// The BigOrderedMap data structure. + enum BigOrderedMap has store { + BPlusTreeMap { + /// Root node. It is stored directly in the resource itself, unlike all other nodes. + root: Node, + /// Storage of all non-root nodes. They are stored in separate storage slots. + nodes: StorageSlotsAllocator>, + /// The node index of the leftmost node. + min_leaf_index: u64, + /// The node index of the rightmost node. + max_leaf_index: u64, + + /// Whether Key and Value have constant serialized size, and if so, + /// optimize out size checks on every insert. + constant_kv_size: bool, + /// The max number of children an inner node can have. + inner_max_degree: u16, + /// The max number of children a leaf node can have. + leaf_max_degree: u16 + } + } + + // ======================= Constructors && Destructors ==================== + + /// Returns a new BigOrderedMap with the default configuration. + /// Only allowed to be called with constant size types. For variable sized types, + /// it is required to use new_with_config, to explicitly select automatic or specific degree selection. + public fun new(): BigOrderedMap { + // Use new_with_type_size_hints or new_with_config if your types have variable sizes. + assert!( + bcs::constant_serialized_size().is_some() + && bcs::constant_serialized_size().is_some(), + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + + new_with_config(0, 0, false) + } + + /// Returns a new BigOrderedMap with with reusable storage slots. + /// Only allowed to be called with constant size types. For variable sized types, + /// it is required to use new_with_config, to explicitly select automatic or specific degree selection. + public fun new_with_reusable(): BigOrderedMap { + // Use new_with_type_size_hints or new_with_config if your types have variable sizes. + assert!( + bcs::constant_serialized_size().is_some() + && bcs::constant_serialized_size().is_some(), + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + + new_with_config(0, 0, true) + } + + /// Returns a new BigOrderedMap, configured based on passed key and value serialized size hints. + public fun new_with_type_size_hints( + avg_key_bytes: u64, + max_key_bytes: u64, + avg_value_bytes: u64, + max_value_bytes: u64 + ): BigOrderedMap { + assert!( + avg_key_bytes <= max_key_bytes, + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + assert!( + avg_value_bytes <= max_value_bytes, + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + + let inner_max_degree_from_avg = + max( + min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / avg_key_bytes), + INNER_MIN_DEGREE as u64 + ); + let inner_max_degree_from_max = MAX_NODE_BYTES / max_key_bytes; + assert!( + inner_max_degree_from_max >= (INNER_MIN_DEGREE as u64), + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + + let avg_entry_size = avg_key_bytes + avg_value_bytes; + let max_entry_size = max_key_bytes + max_value_bytes; + + let leaf_max_degree_from_avg = + max( + min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / avg_entry_size), + LEAF_MIN_DEGREE as u64 + ); + let leaf_max_degree_from_max = MAX_NODE_BYTES / max_entry_size; + assert!( + leaf_max_degree_from_max >= (INNER_MIN_DEGREE as u64), + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + + new_with_config( + min(inner_max_degree_from_avg, inner_max_degree_from_max) as u16, + min(leaf_max_degree_from_avg, leaf_max_degree_from_max) as u16, + false + ) + } + + /// Returns a new BigOrderedMap with the provided max degree consts (the maximum # of children a node can have, both inner and leaf). + /// If 0 is passed, then it is dynamically computed based on size of first key and value. + /// + /// Sizes of all elements must respect (or their additions will be rejected): + /// `key_size * inner_max_degree <= MAX_NODE_BYTES` + /// `entry_size * leaf_max_degree <= MAX_NODE_BYTES` + /// If keys or values have variable size, and first element could be non-representative in size (i.e. smaller than future ones), + /// it is important to compute and pass inner_max_degree and leaf_max_degree based on the largest element you want to be able to insert. + /// + /// `reuse_slots` means that removing elements from the map doesn't free the storage slots and returns the refund. + /// Together with `allocate_spare_slots`, it allows to preallocate slots and have inserts have predictable gas costs. + /// (otherwise, inserts that require map to add new nodes, cost significantly more, compared to the rest) + public fun new_with_config( + inner_max_degree: u16, leaf_max_degree: u16, reuse_slots: bool + ): BigOrderedMap { + assert!( + inner_max_degree == 0 + || ( + inner_max_degree >= INNER_MIN_DEGREE + && (inner_max_degree as u64) <= MAX_DEGREE + ), + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + assert!( + leaf_max_degree == 0 + || ( + leaf_max_degree >= LEAF_MIN_DEGREE + && (leaf_max_degree as u64) <= MAX_DEGREE + ), + error::invalid_argument(EINVALID_CONFIG_PARAMETER) + ); + + // Assert that storage_slots_allocator special indices are aligned: + assert!( + storage_slots_allocator::is_null_index(NULL_INDEX), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + assert!( + storage_slots_allocator::is_special_unused_index(ROOT_INDEX), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + let nodes = storage_slots_allocator::new(reuse_slots); + + let self = BigOrderedMap::BPlusTreeMap { + root: new_node(/*is_leaf=*/ true), + nodes: nodes, + min_leaf_index: ROOT_INDEX, + max_leaf_index: ROOT_INDEX, + constant_kv_size: false, // Will be initialized in validate_static_size_and_init_max_degrees below. + inner_max_degree: inner_max_degree, + leaf_max_degree: leaf_max_degree + }; + self.validate_static_size_and_init_max_degrees(); + self + } + + /// Create a BigOrderedMap from a vector of keys and values, with default configuration. + /// Aborts with EKEY_ALREADY_EXISTS if duplicate keys are passed in. + public fun new_from( + keys: vector, values: vector + ): BigOrderedMap { + let map = new(); + map.add_all(keys, values); + map + } + + /// Destroys the map if it's empty, otherwise aborts. + public fun destroy_empty(self: BigOrderedMap) { + let BigOrderedMap::BPlusTreeMap { + root, + nodes, + min_leaf_index: _, + max_leaf_index: _, + constant_kv_size: _, + inner_max_degree: _, + leaf_max_degree: _ + } = self; + root.destroy_empty_node(); + // If root node is empty, then we know that no storage slots are used, + // and so we can safely destroy all nodes. + nodes.destroy_empty(); + } + + /// Map was created with reuse_slots=true, you can allocate spare slots, to pay storage fee now, to + /// allow future insertions to not require any storage slot creation - making their gas more predictable + /// and better bounded/fair. + /// (otherwsie, unlucky inserts create new storage slots and are charge more for it) + public fun allocate_spare_slots( + self: &mut BigOrderedMap, num_to_allocate: u64 + ) { + self.nodes.allocate_spare_slots(num_to_allocate) + } + + /// Returns true iff the BigOrderedMap is empty. + public fun is_empty(self: &BigOrderedMap): bool { + let node = self.borrow_node(self.min_leaf_index); + node.children.is_empty() + } + + /// Returns the number of elements in the BigOrderedMap. + /// This is an expensive function, as it goes through all the leaves to compute it. + public fun compute_length( + self: &BigOrderedMap + ): u64 { + let size = 0; + self.for_each_leaf_node_ref(|node| { + size += node.children.length(); + }); + size + } + + // ======================= Section with Modifiers ========================= + + /// Inserts the key/value into the BigOrderedMap. + /// Aborts if the key is already in the map. + public fun add( + self: &mut BigOrderedMap, key: K, value: V + ) { + self.add_or_upsert_impl(key, value, false).destroy_none() + } + + /// If the key doesn't exist in the map, inserts the key/value, and returns none. + /// Otherwise updates the value under the given key, and returns the old value. + public fun upsert( + self: &mut BigOrderedMap, key: K, value: V + ): Option { + let result = self.add_or_upsert_impl(key, value, true); + if (result.is_some()) { + let Child::Leaf { value: old_value } = result.destroy_some(); + option::some(old_value) + } else { + result.destroy_none(); + option::none() + } + } + + /// Removes the entry from BigOrderedMap and returns the value which `key` maps to. + /// Aborts if there is no entry for `key`. + public fun remove( + self: &mut BigOrderedMap, key: &K + ): V { + // Optimize case where only root node exists + // (optimizes out borrowing and path creation in `find_leaf_path`) + if (self.root.is_leaf) { + let Child::Leaf { value } = self.root.children.remove(key); + return value; + }; + + let path_to_leaf = self.find_leaf_path(key); + + assert!(!path_to_leaf.is_empty(), error::invalid_argument(EKEY_NOT_FOUND)); + + let Child::Leaf { value } = self.remove_at(path_to_leaf, key); + value + } + + /// Add multiple key/value pairs to the map. The keys must not already exist. + /// Aborts with EKEY_ALREADY_EXISTS if key already exist, or duplicate keys are passed in. + public fun add_all( + self: &mut BigOrderedMap, + keys: vector, + values: vector + ) { + // TODO: Can be optimized, both in insertion order (largest first, then from smallest), + // as well as on initializing inner_max_degree/leaf_max_degree better + keys.zip( + values, |key, value| { + self.add(key, value); + } + ); + } + + public fun pop_front( + self: &mut BigOrderedMap + ): (K, V) { + let it = self.new_begin_iter(); + let k = *it.iter_borrow_key(); + let v = self.remove(&k); + (k, v) + } + + public fun pop_back( + self: &mut BigOrderedMap + ): (K, V) { + let it = self.new_end_iter().iter_prev(self); + let k = *it.iter_borrow_key(); + let v = self.remove(&k); + (k, v) + } + + // ============================= Accessors ================================ + + /// Returns an iterator pointing to the first element that is greater or equal to the provided + /// key, or an end iterator if such element doesn't exist. + public(friend) fun lower_bound( + self: &BigOrderedMap, key: &K + ): IteratorPtr { + let leaf = self.find_leaf(key); + if (leaf == NULL_INDEX) { + return self.new_end_iter() + }; + + let node = self.borrow_node(leaf); + assert!(node.is_leaf, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + + let child_lower_bound = node.children.lower_bound(key); + if (child_lower_bound.iter_is_end(&node.children)) { + self.new_end_iter() + } else { + let iter_key = *child_lower_bound.iter_borrow_key(&node.children); + new_iter(leaf, child_lower_bound, iter_key) + } + } + + /// Returns an iterator pointing to the element that equals to the provided key, or an end + /// iterator if the key is not found. + public(friend) fun find( + self: &BigOrderedMap, key: &K + ): IteratorPtr { + let lower_bound = self.lower_bound(key); + if (lower_bound.iter_is_end(self)) { + lower_bound + } else if (&lower_bound.key == key) { + lower_bound + } else { + self.new_end_iter() + } + } + + /// Returns true iff the key exists in the map. + public fun contains( + self: &BigOrderedMap, key: &K + ): bool { + let lower_bound = self.lower_bound(key); + if (lower_bound.iter_is_end(self)) { false } + else if (&lower_bound.key == key) { true } + else { false } + } + + /// Returns a reference to the element with its key, aborts if the key is not found. + public fun borrow( + self: &BigOrderedMap, key: &K + ): &V { + let iter = self.find(key); + assert!(!iter.iter_is_end(self), error::invalid_argument(EKEY_NOT_FOUND)); + + iter.iter_borrow(self) + } + + public fun get( + self: &BigOrderedMap, key: &K + ): Option { + let iter = self.find(key); + if (iter.iter_is_end(self)) { + option::none() + } else { + option::some(*iter.iter_borrow(self)) + } + } + + /// Returns a mutable reference to the element with its key at the given index, aborts if the key is not found. + /// Aborts with EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE if KV size doesn't have constant size, + /// because if it doesn't we cannot assert invariants on the size. + /// In case of variable size, use either `borrow`, `copy` then `upsert`, or `remove` and `add` instead of mutable borrow. + public fun borrow_mut( + self: &mut BigOrderedMap, key: &K + ): &mut V { + let iter = self.find(key); + assert!(!iter.iter_is_end(self), error::invalid_argument(EKEY_NOT_FOUND)); + iter.iter_borrow_mut(self) + } + + public fun borrow_front( + self: &BigOrderedMap + ): (K, &V) { + let it = self.new_begin_iter(); + let key = *it.iter_borrow_key(); + (key, it.iter_borrow(self)) + } + + public fun borrow_back( + self: &BigOrderedMap + ): (K, &V) { + let it = self.new_end_iter().iter_prev(self); + let key = *it.iter_borrow_key(); + (key, it.iter_borrow(self)) + } + + public fun prev_key( + self: &BigOrderedMap, key: &K + ): Option { + let it = self.lower_bound(key); + if (it.iter_is_begin(self)) { + option::none() + } else { + option::some(*it.iter_prev(self).iter_borrow_key()) + } + } + + public fun next_key( + self: &BigOrderedMap, key: &K + ): Option { + let it = self.lower_bound(key); + if (it.iter_is_end(self)) { + option::none() + } else { + let cur_key = it.iter_borrow_key(); + if (key == cur_key) { + let it = it.iter_next(self); + if (it.iter_is_end(self)) { + option::none() + } else { + option::some(*it.iter_borrow_key()) + } + } else { + option::some(*cur_key) + } + } + } + + // =========================== Views and Traversals ============================== + + /// Convert a BigOrderedMap to an OrderedMap, which is supposed to be called mostly by view functions to get an atomic + /// view of the whole map. + /// Disclaimer: This function may be costly as the BigOrderedMap may be huge in size. Use it at your own discretion. + public fun to_ordered_map( + self: &BigOrderedMap + ): OrderedMap { + let result = ordered_map::new(); + self.for_each_ref_friend( + |k, v| { + result.new_end_iter().iter_add(&mut result, *k, *v); + } + ); + result + } + + /// Get all keys. + /// + /// For a large enough BigOrderedMap this function will fail due to execution gas limits, + /// use iterartor or next_key/prev_key to iterate over across portion of the map. + public fun keys( + self: &BigOrderedMap + ): vector { + let result = vector[]; + self.for_each_ref_friend(|k, _v| { + result.push_back(*k); + }); + result + } + + /// Apply the function to each element in the vector, consuming it, leaving the map empty. + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun for_each_and_clear( + self: &mut BigOrderedMap, f: |K, V| + ) { + // TODO - this can be done more efficiently, by destroying the leaves directly + // but that requires more complicated code and testing. + while (!self.is_empty()) { + let (k, v) = self.pop_front(); + f(k, v); + }; + } + + /// Apply the function to each element in the vector, consuming it, and consuming the map + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun for_each( + self: BigOrderedMap, f: |K, V| + ) { + // TODO - this can be done more efficiently, by destroying the leaves directly + // but that requires more complicated code and testing. + self.for_each_and_clear(|k, v| f(k, v)); + self.destroy_empty() + } + + /// Apply the function to a reference of each element in the vector. + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun for_each_ref( + self: &BigOrderedMap, f: |&K, &V| + ) { + // This implementation is innefficient: O(log(n)) for next_key / borrow lookups every time, + // but is the only one available through the public API. + if (!self.is_empty()) { + let (k, v) = self.borrow_front(); + f(&k, v); + + let cur_k = self.next_key(&k); + while (cur_k.is_some()) { + let k = cur_k.destroy_some(); + f(&k, self.borrow(&k)); + + cur_k = self.next_key(&k); + }; + }; + + // TODO use this more efficient implementation when function values are enabled. + // self.for_each_leaf_node_ref(|node| { + // node.children.for_each_ref(|k: &K, v: &Child| { + // f(k, &v.value); + // }); + // }) + } + + // TODO: Temporary friend implementaiton, until for_each_ref can be made efficient. + public(friend) inline fun for_each_ref_friend( + self: &BigOrderedMap, f: |&K, &V| + ) { + self.for_each_leaf_node_ref(|node| { + node.children.for_each_ref_friend(|k: &K, v: &Child| { + f(k, &v.value); + }); + }) + } + + /// Apply the function to a mutable reference of each key-value pair in the map. + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun for_each_mut( + self: &mut BigOrderedMap, f: |&K, &mut V| + ) { + // This implementation is innefficient: O(log(n)) for next_key / borrow lookups every time, + // but is the only one available through the public API. + if (!self.is_empty()) { + let (k, _v) = self.borrow_front(); + + let done = false; + while (!done) { + f(&k, self.borrow_mut(&k)); + + let cur_k = self.next_key(&k); + if (cur_k.is_some()) { + k = cur_k.destroy_some(); + } else { + done = true; + } + }; + }; + + // TODO: if we make iterator api public update to: + // let iter = self.new_begin_iter(); + // while (!iter.iter_is_end(self)) { + // let key = *iter.iter_borrow_key(self); + // f(key, iter.iter_borrow_mut(self)); + // iter = iter.iter_next(self); + // } + } + + /// Destroy a map, by destroying elements individually. + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun destroy( + self: BigOrderedMap, dv: |V| + ) { + self.for_each(|_k, v| { + dv(v); + }); + } + + // ========================= IteratorPtr functions =========================== + + /// Returns the begin iterator. + public(friend) fun new_begin_iter( + self: &BigOrderedMap + ): IteratorPtr { + if (self.is_empty()) { + return IteratorPtr::End; + }; + + let node = self.borrow_node(self.min_leaf_index); + assert!( + !node.children.is_empty(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + let begin_child_iter = node.children.new_begin_iter(); + let begin_child_key = *begin_child_iter.iter_borrow_key(&node.children); + new_iter(self.min_leaf_index, begin_child_iter, begin_child_key) + } + + /// Returns the end iterator. + public(friend) fun new_end_iter( + self: &BigOrderedMap + ): IteratorPtr { + IteratorPtr::End + } + + // Returns true iff the iterator is a begin iterator. + public(friend) fun iter_is_begin( + self: &IteratorPtr, map: &BigOrderedMap + ): bool { + if (self is IteratorPtr::End) { + map.is_empty() + } else { + ( + self.node_index == map.min_leaf_index + && self.child_iter.iter_is_begin_from_non_empty() + ) + } + } + + // Returns true iff the iterator is an end iterator. + public(friend) fun iter_is_end( + self: &IteratorPtr, _map: &BigOrderedMap + ): bool { + self is IteratorPtr::End + } + + /// Borrows the key given iterator points to. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_borrow_key(self: &IteratorPtr): &K { + assert!( + !(self is IteratorPtr::End), + error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + &self.key + } + + /// Borrows the value given iterator points to. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_borrow( + self: IteratorPtr, map: &BigOrderedMap + ): &V { + assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS)); + let IteratorPtr::Some { node_index, child_iter, key: _ } = self; + let children = &map.borrow_node(node_index).children; + &child_iter.iter_borrow(children).value + } + + /// Mutably borrows the value iterator points to. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Aborts with EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE if KV size doesn't have constant size, + /// because if it doesn't we cannot assert invariants on the size. + /// In case of variable size, use either `borrow`, `copy` then `upsert`, or `remove` and `add` instead of mutable borrow. + /// + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_borrow_mut( + self: IteratorPtr, map: &mut BigOrderedMap + ): &mut V { + assert!( + map.constant_kv_size, + error::invalid_argument(EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE) + ); + assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS)); + let IteratorPtr::Some { node_index, child_iter, key: _ } = self; + let children = &mut map.borrow_node_mut(node_index).children; + &mut child_iter.iter_borrow_mut(children).value + } + + /// Returns the next iterator. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Requires the map is not changed after the input iterator is generated. + public(friend) fun iter_next( + self: IteratorPtr, map: &BigOrderedMap + ): IteratorPtr { + assert!( + !(self is IteratorPtr::End), + error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + + let node_index = self.node_index; + let node = map.borrow_node(node_index); + + let child_iter = self.child_iter.iter_next(&node.children); + if (!child_iter.iter_is_end(&node.children)) { + // next is in the same leaf node + let iter_key = *child_iter.iter_borrow_key(&node.children); + return new_iter(node_index, child_iter, iter_key); + }; + + // next is in a different leaf node + let next_index = node.next; + if (next_index != NULL_INDEX) { + let next_node = map.borrow_node(next_index); + + let child_iter = next_node.children.new_begin_iter(); + assert!( + !child_iter.iter_is_end(&next_node.children), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + let iter_key = *child_iter.iter_borrow_key(&next_node.children); + return new_iter(next_index, child_iter, iter_key); + }; + + map.new_end_iter() + } + + /// Returns the previous iterator. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the beginning. + /// Requires the map is not changed after the input iterator is generated. + public(friend) fun iter_prev( + self: IteratorPtr, map: &BigOrderedMap + ): IteratorPtr { + let prev_index = + if (self is IteratorPtr::End) { + map.max_leaf_index + } else { + let node_index = self.node_index; + let node = map.borrow_node(node_index); + + if (!self.child_iter.iter_is_begin(&node.children)) { + // next is in the same leaf node + let child_iter = self.child_iter.iter_prev(&node.children); + let key = *child_iter.iter_borrow_key(&node.children); + return new_iter(node_index, child_iter, key); + }; + node.prev + }; + + assert!(prev_index != NULL_INDEX, error::invalid_argument(EITER_OUT_OF_BOUNDS)); + + // next is in a different leaf node + let prev_node = map.borrow_node(prev_index); + + let prev_children = &prev_node.children; + let child_iter = prev_children.new_end_iter().iter_prev(prev_children); + let iter_key = *child_iter.iter_borrow_key(prev_children); + new_iter(prev_index, child_iter, iter_key) + } + + // ====================== Internal Implementations ======================== + + inline fun for_each_leaf_node_ref( + self: &BigOrderedMap, f: |&Node| + ) { + let cur_node_index = self.min_leaf_index; + + while (cur_node_index != NULL_INDEX) { + let node = self.borrow_node(cur_node_index); + f(node); + cur_node_index = node.next; + } + } + + /// Borrow a node, given an index. Works for both root (i.e. inline) node and separately stored nodes + inline fun borrow_node( + self: &BigOrderedMap, node_index: u64 + ): &Node { + if (node_index == ROOT_INDEX) { + &self.root + } else { + self.nodes.borrow(node_index) + } + } + + /// Borrow a node mutably, given an index. Works for both root (i.e. inline) node and separately stored nodes + inline fun borrow_node_mut( + self: &mut BigOrderedMap, node_index: u64 + ): &mut Node { + if (node_index == ROOT_INDEX) { + &mut self.root + } else { + self.nodes.borrow_mut(node_index) + } + } + + fun add_or_upsert_impl( + self: &mut BigOrderedMap, + key: K, + value: V, + allow_overwrite: bool + ): Option> { + if (!self.constant_kv_size) { + self.validate_dynamic_size_and_init_max_degrees(&key, &value); + }; + + // Optimize case where only root node exists + // (optimizes out borrowing and path creation in `find_leaf_path`) + if (self.root.is_leaf) { + let children = &mut self.root.children; + let degree = children.length(); + + if (degree < (self.leaf_max_degree as u64)) { + let result = children.upsert(key, new_leaf_child(value)); + assert!( + allow_overwrite || result.is_none(), + error::invalid_argument(EKEY_ALREADY_EXISTS) + ); + return result; + }; + }; + + let path_to_leaf = self.find_leaf_path(&key); + + if (path_to_leaf.is_empty()) { + // In this case, the key is greater than all keys in the map. + // So we need to update `key` in the pointers to the last (rightmost) child + // on every level, to maintain the invariant of `add_at` + // we also create a path_to_leaf to the rightmost leaf. + let current = ROOT_INDEX; + + loop { + path_to_leaf.push_back(current); + + let current_node = self.borrow_node_mut(current); + if (current_node.is_leaf) { + break; + }; + let last_value = + current_node.children.new_end_iter().iter_prev(¤t_node.children) + .iter_remove(&mut current_node.children); + current = last_value.node_index.stored_to_index(); + current_node.children.add(key, last_value); + }; + }; + + self.add_at( + path_to_leaf, key, new_leaf_child(value), allow_overwrite + ) + } + + fun validate_dynamic_size_and_init_max_degrees( + self: &mut BigOrderedMap, key: &K, value: &V + ) { + let key_size = bcs::serialized_size(key); + let value_size = bcs::serialized_size(value); + self.validate_size_and_init_max_degrees(key_size, value_size) + } + + fun validate_static_size_and_init_max_degrees( + self: &mut BigOrderedMap + ) { + let key_size = bcs::constant_serialized_size(); + let value_size = bcs::constant_serialized_size(); + + if (key_size.is_some() && value_size.is_some()) { + self.validate_size_and_init_max_degrees( + key_size.destroy_some(), value_size.destroy_some() + ); + self.constant_kv_size = true; + }; + } + + fun validate_size_and_init_max_degrees( + self: &mut BigOrderedMap, + key_size: u64, + value_size: u64 + ) { + let entry_size = key_size + value_size; + + if (self.inner_max_degree == 0) { + self.inner_max_degree = + max( + min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / key_size), + INNER_MIN_DEGREE as u64 + ) as u16; + }; + + if (self.leaf_max_degree == 0) { + self.leaf_max_degree = + max( + min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / entry_size), + LEAF_MIN_DEGREE as u64 + ) as u16; + }; + + // Make sure that no nodes can exceed the upper size limit. + assert!( + key_size * (self.inner_max_degree as u64) <= MAX_NODE_BYTES, + error::invalid_argument(EARGUMENT_BYTES_TOO_LARGE) + ); + assert!( + entry_size * (self.leaf_max_degree as u64) <= MAX_NODE_BYTES, + error::invalid_argument(EARGUMENT_BYTES_TOO_LARGE) + ); + } + + fun destroy_inner_child(self: Child): StoredSlot { + let Child::Inner { node_index } = self; + + node_index + } + + fun destroy_empty_node(self: Node) { + let Node::V1 { children, is_leaf: _, prev: _, next: _ } = self; + assert!(children.is_empty(), error::invalid_argument(EMAP_NOT_EMPTY)); + children.destroy_empty(); + } + + fun new_node(is_leaf: bool): Node { + Node::V1 { + is_leaf: is_leaf, + children: ordered_map::new(), + prev: NULL_INDEX, + next: NULL_INDEX + } + } + + fun new_node_with_children( + is_leaf: bool, children: OrderedMap> + ): Node { + Node::V1 { + is_leaf: is_leaf, + children: children, + prev: NULL_INDEX, + next: NULL_INDEX + } + } + + fun new_inner_child(node_index: StoredSlot): Child { + Child::Inner { node_index: node_index } + } + + fun new_leaf_child(value: V): Child { + Child::Leaf { value: value } + } + + fun new_iter( + node_index: u64, child_iter: ordered_map::IteratorPtr, key: K + ): IteratorPtr { + IteratorPtr::Some { node_index: node_index, child_iter: child_iter, key: key } + } + + /// Find leaf where the given key would fall in. + /// So the largest leaf with its `max_key <= key`. + /// return NULL_INDEX if `key` is larger than any key currently stored in the map. + fun find_leaf( + self: &BigOrderedMap, key: &K + ): u64 { + let current = ROOT_INDEX; + loop { + let node = self.borrow_node(current); + if (node.is_leaf) { + return current; + }; + let children = &node.children; + let child_iter = children.lower_bound(key); + if (child_iter.iter_is_end(children)) { + return NULL_INDEX; + } else { + current = child_iter.iter_borrow(children).node_index.stored_to_index(); + }; + } + } + + /// Find leaf where the given key would fall in. + /// So the largest leaf with it's `max_key <= key`. + /// Returns the path from root to that leaf (including the leaf itself) + /// Returns empty path if `key` is larger than any key currently stored in the map. + fun find_leaf_path( + self: &BigOrderedMap, key: &K + ): vector { + let vec = vector::empty(); + + let current = ROOT_INDEX; + loop { + vec.push_back(current); + + let node = self.borrow_node(current); + if (node.is_leaf) { + return vec; + }; + let children = &node.children; + let child_iter = children.lower_bound(key); + if (child_iter.iter_is_end(children)) { + return vector::empty(); + } else { + current = child_iter.iter_borrow(children).node_index.stored_to_index(); + }; + } + } + + fun get_max_degree( + self: &BigOrderedMap, leaf: bool + ): u64 { + if (leaf) { + self.leaf_max_degree as u64 + } else { + self.inner_max_degree as u64 + } + } + + fun replace_root( + self: &mut BigOrderedMap, new_root: Node + ): Node { + // TODO: once mem::replace is made public/released, update to: + // mem::replace(&mut self.root, new_root_node) + + let root = &mut self.root; + let tmp_is_leaf = root.is_leaf; + root.is_leaf = new_root.is_leaf; + new_root.is_leaf = tmp_is_leaf; + + assert!( + root.prev == NULL_INDEX, error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + assert!( + root.next == NULL_INDEX, error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + assert!( + new_root.prev == NULL_INDEX, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + assert!( + new_root.next == NULL_INDEX, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + // let tmp_prev = root.prev; + // root.prev = new_root.prev; + // new_root.prev = tmp_prev; + + // let tmp_next = root.next; + // root.next = new_root.next; + // new_root.next = tmp_next; + + let tmp_children = root.children.trim(0); + root.children.append_disjoint(new_root.children.trim(0)); + new_root.children.append_disjoint(tmp_children); + + new_root + } + + /// Add a given child to a given node (last in the `path_to_node`), and update/rebalance the tree as necessary. + /// It is required that `key` pointers to the child node, on the `path_to_node` are greater or equal to the given key. + /// That means if we are adding a `key` larger than any currently existing in the map - we needed + /// to update `key` pointers on the `path_to_node` to include it, before calling this method. + /// + /// Returns Child previously associated with the given key. + /// If `allow_overwrite` is not set, function will abort if `key` is already present. + fun add_at( + self: &mut BigOrderedMap, + path_to_node: vector, + key: K, + child: Child, + allow_overwrite: bool + ): Option> { + // Last node in the path is one where we need to add the child to. + let node_index = path_to_node.pop_back(); + { + // First check if we can perform this operation, without changing structure of the tree (i.e. without adding any nodes). + + // For that we can just borrow the single node + let node = self.borrow_node_mut(node_index); + let children = &mut node.children; + let degree = children.length(); + + // Compute directly, as we cannot use get_max_degree(), as self is already mutably borrowed. + let max_degree = + if (node.is_leaf) { + self.leaf_max_degree as u64 + } else { + self.inner_max_degree as u64 + }; + + if (degree < max_degree) { + // Adding a child to a current node doesn't exceed the size, so we can just do that. + let old_child = children.upsert(key, child); + + if (node.is_leaf) { + assert!( + allow_overwrite || old_child.is_none(), + error::invalid_argument(EKEY_ALREADY_EXISTS) + ); + return old_child; + } else { + assert!( + !allow_overwrite && old_child.is_none(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + return old_child; + }; + }; + + // If we cannot add more nodes without exceeding the size, + // but node with `key` already exists, we either need to replace or abort. + let iter = children.find(&key); + if (!iter.iter_is_end(children)) { + assert!(node.is_leaf, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + assert!(allow_overwrite, error::invalid_argument(EKEY_ALREADY_EXISTS)); + + return option::some(iter.iter_replace(children, child)); + } + }; + + // # of children in the current node exceeds the threshold, need to split into two nodes. + + // If we are at the root, we need to move root node to become a child and have a new root node, + // in order to be able to split the node on the level it is. + let (reserved_slot, node) = + if (node_index == ROOT_INDEX) { + assert!( + path_to_node.is_empty(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + // Splitting root now, need to create a new root. + // Since root is stored direclty in the resource, we will swap-in the new node there. + let new_root_node = new_node(/*is_leaf=*/ false); + + // Reserve a slot where the current root will be moved to. + let (replacement_node_slot, replacement_node_reserved_slot) = + self.nodes.reserve_slot(); + + let max_key = { + let root_children = &self.root.children; + let max_key = + *root_children.new_end_iter().iter_prev(root_children).iter_borrow_key( + root_children + ); + // need to check if key is largest, as invariant is that "parent's pointers" have been updated, + // but key itself can be larger than all previous ones. + if (cmp::compare(&max_key, &key).is_lt()) { + max_key = key; + }; + max_key + }; + // New root will have start with a single child - the existing root (which will be at replacement location). + new_root_node.children.add( + max_key, new_inner_child(replacement_node_slot) + ); + let node = self.replace_root(new_root_node); + + // we moved the currently processing node one level down, so we need to update the path + path_to_node.push_back(ROOT_INDEX); + + let replacement_index = + replacement_node_reserved_slot.reserved_to_index(); + if (node.is_leaf) { + // replacement node is the only leaf, so we update the pointers: + self.min_leaf_index = replacement_index; + self.max_leaf_index = replacement_index; + }; + (replacement_node_reserved_slot, node) + } else { + // In order to work on multiple nodes at the same time, we cannot borrow_mut, and need to be + // remove_and_reserve existing node. + let (cur_node_reserved_slot, node) = + self.nodes.remove_and_reserve(node_index); + (cur_node_reserved_slot, node) + }; + + // move node_index out of scope, to make sure we don't accidentally access it, as we are done with it. + // (i.e. we should be using `reserved_slot` instead). + move node_index; + + // Now we can perform the split at the current level, as we know we are not at the root level. + assert!( + !path_to_node.is_empty(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + // Parent has a reference under max key to the current node, so existing index + // needs to be the right node. + // Since ordered_map::trim moves from the end (i.e. smaller keys stay), + // we are going to put the contents of the current node on the left side, + // and create a new right node. + // So if we had before (node_index, node), we will change that to end up having: + // (new_left_node_index, node trimmed off) and (node_index, new node with trimmed off children) + // + // So let's rename variables cleanly: + let right_node_reserved_slot = reserved_slot; + let left_node = node; + + let is_leaf = left_node.is_leaf; + let left_children = &mut left_node.children; + + let right_node_index = right_node_reserved_slot.reserved_to_index(); + let left_next = &mut left_node.next; + let left_prev = &mut left_node.prev; + + // Compute directly, as we cannot use get_max_degree(), as self is already mutably borrowed. + let max_degree = + if (is_leaf) { + self.leaf_max_degree as u64 + } else { + self.inner_max_degree as u64 + }; + // compute the target size for the left node: + let target_size = (max_degree + 1) / 2; + + // Add child (which will exceed the size), and then trim off to create two sets of children of correct sizes. + left_children.add(key, child); + let right_node_children = left_children.trim(target_size); + + assert!( + left_children.length() <= max_degree, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + assert!( + right_node_children.length() <= max_degree, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + let right_node = new_node_with_children(is_leaf, right_node_children); + + let (left_node_slot, left_node_reserved_slot) = self.nodes.reserve_slot(); + let left_node_index = left_node_slot.stored_to_index(); + + // right nodes next is the node that was next of the left (previous) node, and next of left node is the right node. + right_node.next = *left_next; + *left_next = right_node_index; + + // right node's prev becomes current left node + right_node.prev = left_node_index; + // Since the previously used index is going to the right node, `prev` pointer of the next node is correct, + // and we need to update next pointer of the previous node (if exists) + if (*left_prev != NULL_INDEX) { + self.nodes.borrow_mut(*left_prev).next = left_node_index; + assert!( + right_node_index != self.min_leaf_index, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + } else if (right_node_index == self.min_leaf_index) { + // Otherwise, if we were the smallest node on the level. if this is the leaf level, update the pointer. + assert!(is_leaf, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + self.min_leaf_index = left_node_index; + }; + + // Largest left key is the split key. + let max_left_key = + *left_children.new_end_iter().iter_prev(left_children).iter_borrow_key( + left_children + ); + + self.nodes.fill_reserved_slot(left_node_reserved_slot, left_node); + self.nodes.fill_reserved_slot(right_node_reserved_slot, right_node); + + // Add new Child (i.e. pointer to the left node) in the parent. + self.add_at( + path_to_node, max_left_key, new_inner_child(left_node_slot), false + ).destroy_none(); + option::none() + } + + /// Given a path to node (excluding the node itself), which is currently stored under "old_key", update "old_key" to "new_key". + fun update_key( + self: &mut BigOrderedMap, + path_to_node: vector, + old_key: &K, + new_key: K + ) { + while (!path_to_node.is_empty()) { + let node_index = path_to_node.pop_back(); + let node = self.borrow_node_mut(node_index); + let children = &mut node.children; + children.replace_key_inplace(old_key, new_key); + + // If we were not updating the largest child, we don't need to continue. + if (children.new_end_iter().iter_prev(children).iter_borrow_key(children) + != &new_key) { return }; + } + } + + fun remove_at( + self: &mut BigOrderedMap, + path_to_node: vector, + key: &K + ): Child { + // Last node in the path is one where we need to remove the child from. + let node_index = path_to_node.pop_back(); + let old_child = { + // First check if we can perform this operation, without changing structure of the tree (i.e. without rebalancing any nodes). + + // For that we can just borrow the single node + let node = self.borrow_node_mut(node_index); + + let children = &mut node.children; + let is_leaf = node.is_leaf; + + let old_child = children.remove(key); + if (node_index == ROOT_INDEX) { + // If current node is root, lower limit of max_degree/2 nodes doesn't apply. + // So we can adjust internally + assert!( + path_to_node.is_empty(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + if (!is_leaf && children.length() == 1) { + // If root is not leaf, but has a single child, promote only child to root, + // and drop current root. Since root is stored directly in the resource, we + // "move" the child into the root. + + let Child::Inner { node_index: inner_child_index } = + children.new_end_iter().iter_prev(children).iter_remove(children); + + let inner_child = self.nodes.remove(inner_child_index); + if (inner_child.is_leaf) { + self.min_leaf_index = ROOT_INDEX; + self.max_leaf_index = ROOT_INDEX; + }; + + self.replace_root(inner_child).destroy_empty_node(); + }; + return old_child; + }; + + // Compute directly, as we cannot use get_max_degree(), as self is already mutably borrowed. + let max_degree = + if (is_leaf) { + self.leaf_max_degree as u64 + } else { + self.inner_max_degree as u64 + }; + let degree = children.length(); + + // See if the node is big enough, or we need to merge it with another node on this level. + let big_enough = degree * 2 >= max_degree; + + let new_max_key = + *children.new_end_iter().iter_prev(children).iter_borrow_key(children); + + // See if max key was updated for the current node, and if so - update it on the path. + let max_key_updated = cmp::compare(&new_max_key, key).is_lt(); + if (max_key_updated) { + assert!(degree >= 1, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + + self.update_key(path_to_node, key, new_max_key); + }; + + // If node is big enough after removal, we are done. + if (big_enough) { + return old_child; + }; + + old_child + }; + + // Children size is below threshold, we need to rebalance with a neighbor on the same level. + + // In order to work on multiple nodes at the same time, we cannot borrow_mut, and need to be + // remove_and_reserve existing node. + let (node_slot, node) = self.nodes.remove_and_reserve(node_index); + + let is_leaf = node.is_leaf; + let max_degree = self.get_max_degree(is_leaf); + let prev = node.prev; + let next = node.next; + + // index of the node we will rebalance with. + let sibling_index = { + let parent_children = + &self.borrow_node(*path_to_node.borrow(path_to_node.length() - 1)).children; + assert!( + parent_children.length() >= 2, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + // If we are the largest node from the parent, we merge with the `prev` + // (which is then guaranteed to have the same parent, as any node has >1 children), + // otherwise we merge with `next`. + if (parent_children.new_end_iter().iter_prev(parent_children).iter_borrow(parent_children).node_index + .stored_to_index() == node_index) { prev } + else { next } + }; + + let children = &mut node.children; + + let (sibling_slot, sibling_node) = self.nodes.remove_and_reserve(sibling_index); + assert!( + is_leaf == sibling_node.is_leaf, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + let sibling_children = &mut sibling_node.children; + + if ((sibling_children.length() - 1) * 2 >= max_degree) { + // The sibling node has enough elements, we can just borrow an element from the sibling node. + if (sibling_index == next) { + // if sibling is the node with larger keys, we remove a child from the start + let old_max_key = + *children.new_end_iter().iter_prev(children).iter_borrow_key(children); + let sibling_begin_iter = sibling_children.new_begin_iter(); + let borrowed_max_key = + *sibling_begin_iter.iter_borrow_key(sibling_children); + let borrowed_element = sibling_begin_iter.iter_remove(sibling_children); + + children.new_end_iter().iter_add( + children, borrowed_max_key, borrowed_element + ); + + // max_key of the current node changed, so update + self.update_key(path_to_node, &old_max_key, borrowed_max_key); + } else { + // if sibling is the node with smaller keys, we remove a child from the end + let sibling_end_iter = + sibling_children.new_end_iter().iter_prev(sibling_children); + let borrowed_max_key = + *sibling_end_iter.iter_borrow_key(sibling_children); + let borrowed_element = sibling_end_iter.iter_remove(sibling_children); + + children.add(borrowed_max_key, borrowed_element); + + // max_key of the sibling node changed, so update + self.update_key( + path_to_node, + &borrowed_max_key, + *sibling_children.new_end_iter().iter_prev(sibling_children).iter_borrow_key( + sibling_children + ) + ); + }; + + self.nodes.fill_reserved_slot(node_slot, node); + self.nodes.fill_reserved_slot(sibling_slot, sibling_node); + return old_child; + }; + + // The sibling node doesn't have enough elements to borrow, merge with the sibling node. + // Keep the slot of the node with larger keys of the two, to not require updating key on the parent nodes. + // But append to the node with smaller keys, as ordered_map::append is more efficient when adding to the end. + let (key_to_remove, reserved_slot_to_remove) = + if (sibling_index == next) { + // destroying larger sibling node, keeping sibling_slot. + let Node::V1 { + children: sibling_children, + is_leaf: _, + prev: _, + next: sibling_next + } = sibling_node; + let key_to_remove = + *children.new_end_iter().iter_prev(children).iter_borrow_key(children); + children.append_disjoint(sibling_children); + node.next = sibling_next; + + if (node.next != NULL_INDEX) { + assert!( + self.nodes.borrow_mut(node.next).prev == sibling_index, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + }; + + // we are removing node_index, which previous's node's next was pointing to, + // so update the pointer + if (node.prev != NULL_INDEX) { + self.nodes.borrow_mut(node.prev).next = sibling_index; + }; + // Otherwise, we were the smallest node on the level. if this is the leaf level, update the pointer. + if (self.min_leaf_index == node_index) { + assert!(is_leaf, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + self.min_leaf_index = sibling_index; + }; + + self.nodes.fill_reserved_slot(sibling_slot, node); + + (key_to_remove, node_slot) + } else { + // destroying larger current node, keeping node_slot + let Node::V1 { + children: node_children, + is_leaf: _, + prev: _, + next: node_next + } = node; + let key_to_remove = + *sibling_children.new_end_iter().iter_prev(sibling_children).iter_borrow_key( + sibling_children + ); + sibling_children.append_disjoint(node_children); + sibling_node.next = node_next; + + if (sibling_node.next != NULL_INDEX) { + assert!( + self.nodes.borrow_mut(sibling_node.next).prev == node_index, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + }; + // we are removing sibling node_index, which previous's node's next was pointing to, + // so update the pointer + if (sibling_node.prev != NULL_INDEX) { + self.nodes.borrow_mut(sibling_node.prev).next = node_index; + }; + // Otherwise, sibling was the smallest node on the level. if this is the leaf level, update the pointer. + if (self.min_leaf_index == sibling_index) { + assert!(is_leaf, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + self.min_leaf_index = node_index; + }; + + self.nodes.fill_reserved_slot(node_slot, sibling_node); + + (key_to_remove, sibling_slot) + }; + + assert!( + !path_to_node.is_empty(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + let slot_to_remove = + self.remove_at(path_to_node, &key_to_remove).destroy_inner_child(); + self.nodes.free_reserved_slot(reserved_slot_to_remove, slot_to_remove); + + old_child + } + + // ===== spec =========== + + spec module { + pragma verify = false; + } + + // recursive functions need to be marked opaque + + spec add_at { + pragma opaque; + } + + spec remove_at { + pragma opaque; + } + + // ============================= Tests ==================================== + + #[test_only] + fun print_map(self: &BigOrderedMap) { + // uncomment to debug: + // aptos_std::debug::print(&std::string::utf8(b"print map")); + // aptos_std::debug::print(self); + // self.print_map_for_node(ROOT_INDEX, 0); + } + + #[test_only] + fun print_map_for_node( + self: &BigOrderedMap, + node_index: u64, + level: u64 + ) { + let node = self.borrow_node(node_index); + + initia_std::debug::print(&level); + initia_std::debug::print(&node_index); + initia_std::debug::print(node); + + if (!node.is_leaf) { + node.children.for_each_ref_friend(|_key, node| { + self.print_map_for_node(node.node_index.stored_to_index(), level + 1); + }); + }; + } + + #[test_only] + fun destroy_and_validate( + self: BigOrderedMap + ) { + let it = self.new_begin_iter(); + while (!it.iter_is_end(&self)) { + self.remove(it.iter_borrow_key()); + assert!( + self.find(it.iter_borrow_key()).iter_is_end(&self), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + it = self.new_begin_iter(); + self.validate_map(); + }; + + self.destroy_empty(); + } + + #[test_only] + fun validate_iteration( + self: &BigOrderedMap + ) { + let expected_num_elements = self.compute_length(); + let num_elements = 0; + let it = self.new_begin_iter(); + while (!it.iter_is_end(self)) { + num_elements += 1; + it = it.iter_next(self); + }; + + assert!( + num_elements == expected_num_elements, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + let num_elements = 0; + let it = self.new_end_iter(); + while (!it.iter_is_begin(self)) { + it = it.iter_prev(self); + num_elements += 1; + }; + assert!( + num_elements == expected_num_elements, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + let it = self.new_end_iter(); + if (!it.iter_is_begin(self)) { + it = it.iter_prev(self); + assert!( + it.node_index == self.max_leaf_index, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + } else { + assert!( + expected_num_elements == 0, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + }; + } + + #[test_only] + fun validate_subtree( + self: &BigOrderedMap, + node_index: u64, + expected_lower_bound_key: Option, + expected_max_key: Option + ) { + let node = self.borrow_node(node_index); + let len = node.children.length(); + assert!( + len <= self.get_max_degree(node.is_leaf), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + + if (node_index != ROOT_INDEX) { + assert!(len >= 1, error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + assert!( + len * 2 >= self.get_max_degree(node.is_leaf) + || node_index == ROOT_INDEX, + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + }; + + node.children.validate_ordered(); + + let previous_max_key = expected_lower_bound_key; + node.children.for_each_ref_friend( + |key: &K, child: &Child| { + if (!node.is_leaf) { + self.validate_subtree( + child.node_index.stored_to_index(), + previous_max_key, + option::some(*key) + ); + } else { + assert!((child is Child::Leaf), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN)); + }; + previous_max_key = option::some(*key); + } + ); + + if (expected_max_key.is_some()) { + let expected_max_key = expected_max_key.extract(); + assert!( + &expected_max_key + == node.children.new_end_iter().iter_prev(&node.children).iter_borrow_key( + &node.children + ), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + }; + + if (expected_lower_bound_key.is_some()) { + let expected_lower_bound_key = expected_lower_bound_key.extract(); + assert!( + cmp::compare( + &expected_lower_bound_key, + node.children.new_begin_iter().iter_borrow_key(&node.children) + ).is_lt(), + error::invalid_state(EINTERNAL_INVARIANT_BROKEN) + ); + }; + } + + #[test_only] + fun validate_map( + self: &BigOrderedMap + ) { + self.validate_subtree(ROOT_INDEX, option::none(), option::none()); + self.validate_iteration(); + } + + #[test] + fun test_small_example() { + let map = new_with_config(5, 3, true); + map.allocate_spare_slots(2); + map.print_map(); + map.validate_map(); + map.add(1, 1); + map.print_map(); + map.validate_map(); + map.add(2, 2); + map.print_map(); + map.validate_map(); + let r1 = map.upsert(3, 3); + map.print_map(); + map.validate_map(); + assert!(r1 == option::none(), 1); + map.add(4, 4); + map.print_map(); + map.validate_map(); + let r2 = map.upsert(4, 8); + map.print_map(); + map.validate_map(); + assert!(r2 == option::some(4), 2); + map.add(5, 5); + map.print_map(); + map.validate_map(); + map.add(6, 6); + map.print_map(); + map.validate_map(); + + let expected_keys = vector[1, 2, 3, 4, 5, 6]; + let expected_values = vector[1, 2, 3, 8, 5, 6]; + + let index = 0; + map.for_each_ref( + |k, v| { + assert!(k == expected_keys.borrow(index), *k + 100); + assert!(v == expected_values.borrow(index), *k + 200); + index += 1; + } + ); + + let index = 0; + map.for_each_ref_friend( + |k, v| { + assert!(k == expected_keys.borrow(index), *k + 100); + assert!(v == expected_values.borrow(index), *k + 200); + index += 1; + } + ); + + expected_keys.zip( + expected_values, + |key, value| { + assert!(map.borrow(&key) == &value, key + 300); + assert!(map.borrow_mut(&key) == &value, key + 400); + } + ); + + map.remove(&5); + map.print_map(); + map.validate_map(); + map.remove(&4); + map.print_map(); + map.validate_map(); + map.remove(&1); + map.print_map(); + map.validate_map(); + map.remove(&3); + map.print_map(); + map.validate_map(); + map.remove(&2); + map.print_map(); + map.validate_map(); + map.remove(&6); + map.print_map(); + map.validate_map(); + + map.destroy_empty(); + } + + #[test] + fun test_for_each() { + let map = new_with_config(4, 3, false); + map.add_all( + vector[1, 3, 6, 2, 9, 5, 7, 4, 8], vector[1, 3, 6, 2, 9, 5, 7, 4, 8] + ); + + let expected = vector[1, 2, 3, 4, 5, 6, 7, 8, 9]; + let index = 0; + map.for_each( + |k, v| { + assert!(k == expected[index], k + 100); + assert!(v == expected[index], k + 200); + index += 1; + } + ); + } + + #[test] + fun test_for_each_ref() { + let map = new_with_config(4, 3, false); + map.add_all( + vector[1, 3, 6, 2, 9, 5, 7, 4, 8], vector[1, 3, 6, 2, 9, 5, 7, 4, 8] + ); + + let expected = vector[1, 2, 3, 4, 5, 6, 7, 8, 9]; + let index = 0; + map.for_each_ref( + |k, v| { + assert!(*k == expected[index], *k + 100); + assert!(*v == expected[index], *k + 200); + index += 1; + } + ); + + map.destroy(|_v| {}); + } + + #[test] + fun test_for_each_variants() { + let keys = vector[1, 3, 5]; + let values = vector[10, 30, 50]; + let map = new_from(keys, values); + + let index = 0; + map.for_each_ref(|k, v| { + assert!(keys[index] == *k); + assert!(values[index] == *v); + index += 1; + }); + + let index = 0; + map.for_each_mut(|k, v| { + assert!(keys[index] == *k); + assert!(values[index] == *v); + *v += 1; + index += 1; + }); + + let index = 0; + map.for_each(|k, v| { + assert!(keys[index] == k); + assert!(values[index] + 1 == v); + index += 1; + }); + } + + #[test] + fun test_variable_size() { + let map = new_with_config, vector>(0, 0, false); + map.print_map(); + map.validate_map(); + map.add(vector[1], vector[1]); + map.print_map(); + map.validate_map(); + map.add(vector[2], vector[2]); + map.print_map(); + map.validate_map(); + let r1 = map.upsert(vector[3], vector[3]); + map.print_map(); + map.validate_map(); + assert!(r1 == option::none(), 1); + map.add(vector[4], vector[4]); + map.print_map(); + map.validate_map(); + let r2 = map.upsert( + vector[4], vector[8, 8, 8] + ); + map.print_map(); + map.validate_map(); + assert!(r2 == option::some(vector[4]), 2); + map.add(vector[5], vector[5]); + map.print_map(); + map.validate_map(); + map.add(vector[6], vector[6]); + map.print_map(); + map.validate_map(); + + vector[1, 2, 3, 4, 5, 6].zip( + vector[1, 2, 3, 8, 5, 6], + |key, value| { + assert!(map.borrow(&vector[key])[0] == value, key + 100); + } + ); + + map.remove(&vector[5]); + map.print_map(); + map.validate_map(); + map.remove(&vector[4]); + map.print_map(); + map.validate_map(); + map.remove(&vector[1]); + map.print_map(); + map.validate_map(); + map.remove(&vector[3]); + map.print_map(); + map.validate_map(); + map.remove(&vector[2]); + map.print_map(); + map.validate_map(); + map.remove(&vector[6]); + map.print_map(); + map.validate_map(); + + map.destroy_empty(); + } + + #[test] + fun test_deleting_and_creating_nodes() { + let map = new_with_config(4, 3, true); + map.allocate_spare_slots(2); + + for (i in 0..25) { + map.upsert(i, i); + map.validate_map(); + }; + + for (i in 0..20) { + map.remove(&i); + map.validate_map(); + }; + + for (i in 25..50) { + map.upsert(i, i); + map.validate_map(); + }; + + for (i in 25..45) { + map.remove(&i); + map.validate_map(); + }; + + for (i in 50..75) { + map.upsert(i, i); + map.validate_map(); + }; + + for (i in 50..75) { + map.remove(&i); + map.validate_map(); + }; + + for (i in 20..25) { + map.remove(&i); + map.validate_map(); + }; + + for (i in 45..50) { + map.remove(&i); + map.validate_map(); + }; + + map.destroy_empty(); + } + + #[test] + fun test_iterator() { + let map = new_with_config(5, 5, true); + map.allocate_spare_slots(2); + + let data = vector[1, 7, 5, 8, 4, 2, 6, 3, 9, 0]; + while (data.length() != 0) { + let element = data.pop_back(); + map.add(element, element); + }; + + let it = map.new_begin_iter(); + + let i = 0; + while (!it.iter_is_end(&map)) { + assert!(i == it.key, i); + assert!(it.iter_borrow(&map) == &i, i); + assert!(it.iter_borrow_mut(&mut map) == &i, i); + i += 1; + it = it.iter_next(&map); + }; + + map.destroy(|_v| {}); + } + + #[test] + fun test_find() { + let map = new_with_config(5, 5, true); + map.allocate_spare_slots(2); + + let data = vector[11, 1, 7, 5, 8, 2, 6, 3, 0, 10]; + map.add_all(data, data); + + let i = 0; + while (i < data.length()) { + let element = data.borrow(i); + let it = map.find(element); + assert!(!it.iter_is_end(&map), i); + assert!(it.iter_borrow_key() == element, i); + i += 1; + }; + + assert!(map.find(&4).iter_is_end(&map), 0); + assert!(map.find(&9).iter_is_end(&map), 1); + + map.destroy(|_v| {}); + } + + #[test] + fun test_lower_bound() { + let map = new_with_config(5, 5, true); + map.allocate_spare_slots(2); + + let data = vector[11, 1, 7, 5, 8, 2, 6, 3, 12, 10]; + map.add_all(data, data); + + let i = 0; + while (i < data.length()) { + let element = *data.borrow(i); + let it = map.lower_bound(&element); + assert!(!it.iter_is_end(&map), i); + assert!(it.key == element, i); + i += 1; + }; + + assert!(map.lower_bound(&0).key == 1, 0); + assert!(map.lower_bound(&4).key == 5, 1); + assert!(map.lower_bound(&9).key == 10, 2); + assert!(map.lower_bound(&13).iter_is_end(&map), 3); + + map.remove(&3); + assert!(map.lower_bound(&3).key == 5, 4); + map.remove(&5); + assert!(map.lower_bound(&3).key == 6, 5); + assert!(map.lower_bound(&4).key == 6, 6); + + map.destroy(|_v| {}); + } + + #[test] + fun test_contains() { + let map = new_with_config(4, 3, false); + let data = vector[3, 1, 9, 7, 5]; + map.add_all( + vector[3, 1, 9, 7, 5], vector[3, 1, 9, 7, 5] + ); + + data.for_each_ref(|i| assert!(map.contains(i), *i)); + + let missing = vector[0, 2, 4, 6, 8, 10]; + missing.for_each_ref(|i| assert!(!map.contains(i), *i)); + + map.destroy(|_v| {}); + } + + #[test] + fun test_non_iterator_ordering() { + let map = new_from(vector[1, 2, 3], vector[10, 20, 30]); + assert!(map.prev_key(&1).is_none(), 1); + assert!(map.next_key(&1) == option::some(2), 1); + + assert!(map.prev_key(&2) == option::some(1), 2); + assert!(map.next_key(&2) == option::some(3), 3); + + assert!(map.prev_key(&3) == option::some(2), 4); + assert!(map.next_key(&3).is_none(), 5); + + let (front_k, front_v) = map.borrow_front(); + assert!(front_k == 1, 6); + assert!(front_v == &10, 7); + + let (back_k, back_v) = map.borrow_back(); + assert!(back_k == 3, 8); + assert!(back_v == &30, 9); + + let (front_k, front_v) = map.pop_front(); + assert!(front_k == 1, 10); + assert!(front_v == 10, 11); + + let (back_k, back_v) = map.pop_back(); + assert!(back_k == 3, 12); + assert!(back_v == 30, 13); + + map.destroy(|_v| {}); + } + + #[test] + #[expected_failure(abort_code = 0x1000B, location = Self)] + /// EINVALID_CONFIG_PARAMETER + fun test_inner_max_degree_too_large() { + let map = new_with_config(4097, 0, false); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x1000B, location = Self)] + /// EINVALID_CONFIG_PARAMETER + fun test_inner_max_degree_too_small() { + let map = new_with_config(3, 0, false); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x1000B, location = Self)] + /// EINVALID_CONFIG_PARAMETER + fun test_leaf_max_degree_too_small() { + let map = new_with_config(0, 2, false); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_abort_add_existing_value() { + let map = new_from(vector[1], vector[1]); + map.add(1, 2); + map.destroy_and_validate(); + } + + #[test_only] + fun vector_range(from: u64, to: u64): vector { + let result = vector[]; + for (i in from..to) { + result.push_back(i); + }; + result + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_abort_add_existing_value_to_non_leaf() { + let map = new_with_config(4, 4, false); + map.add_all( + vector_range(1, 10), vector_range(1, 10) + ); + map.add(3, 3); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = initia_std::ordered_map)] + /// EKEY_NOT_FOUND + fun test_abort_remove_missing_value() { + let map = new_from(vector[1], vector[1]); + map.remove(&2); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = initia_std::ordered_map)] + /// EKEY_NOT_FOUND + fun test_abort_remove_missing_value_to_non_leaf() { + let map = new_with_config(4, 4, false); + map.add_all( + vector_range(1, 10), vector_range(1, 10) + ); + map.remove(&4); + map.remove(&4); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_abort_remove_largest_missing_value_to_non_leaf() { + let map = new_with_config(4, 4, false); + map.add_all( + vector_range(1, 10), vector_range(1, 10) + ); + map.remove(&11); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_abort_borrow_missing() { + let map = new_from(vector[1], vector[1]); + map.borrow(&2); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_abort_borrow_mut_missing() { + let map = new_from(vector[1], vector[1]); + map.borrow_mut(&2); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x1000E, location = Self)] + /// EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE + fun test_abort_borrow_mut_requires_constant_kv_size() { + let map = new_with_config(0, 0, false); + map.add(1, vector[1]); + map.borrow_mut(&1); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + fun test_abort_iter_borrow_key_missing() { + let map = new_from(vector[1], vector[1]); + map.new_end_iter().iter_borrow_key(); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + fun test_abort_iter_borrow_missing() { + let map = new_from(vector[1], vector[1]); + map.new_end_iter().iter_borrow(&map); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + fun test_abort_iter_borrow_mut_missing() { + let map = new_from(vector[1], vector[1]); + map.new_end_iter().iter_borrow_mut(&mut map); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x1000E, location = Self)] + /// EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE + fun test_abort_iter_borrow_mut_requires_constant_kv_size() { + let map = new_with_config(0, 0, false); + map.add(1, vector[1]); + map.new_begin_iter().iter_borrow_mut(&mut map); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + fun test_abort_end_iter_next() { + let map = new_from(vector[1, 2, 3], vector[1, 2, 3]); + map.new_end_iter().iter_next(&map); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + fun test_abort_begin_iter_prev() { + let map = new_from(vector[1, 2, 3], vector[1, 2, 3]); + map.new_begin_iter().iter_prev(&map); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x1000C, location = Self)] + /// EMAP_NOT_EMPTY + fun test_abort_fail_to_destroy_non_empty() { + let map = new_from(vector[1], vector[1]); + map.destroy_empty(); + } + + #[test] + #[expected_failure(abort_code = 0x1000D, location = Self)] + /// EARGUMENT_BYTES_TOO_LARGE + fun test_adding_key_too_large() { + let map = new_with_config(0, 0, false); + map.add(vector[1], 1); + map.add(vector_range(0, 143), 1); + map.destroy_and_validate(); + } + + #[test] + #[expected_failure(abort_code = 0x1000D, location = Self)] + /// EARGUMENT_BYTES_TOO_LARGE + fun test_adding_value_too_large() { + let map = new_with_config(0, 0, false); + map.add(1, vector[1]); + map.add(2, vector_range(0, 268)); + map.destroy_and_validate(); + } + + #[test_only] + inline fun comparison_test( + repeats: u64, + inner_max_degree: u16, + leaf_max_degree: u16, + reuse_slots: bool, + next_1: || u64, + next_2: || u64 + ) { + let big_map = new_with_config(inner_max_degree, leaf_max_degree, reuse_slots); + if (reuse_slots) { + big_map.allocate_spare_slots(4); + }; + let small_map = ordered_map::new(); + for (i in 0..repeats) { + let is_insert = if (2 * i < repeats) { + i % 3 != 2 + } else { + i % 3 == 0 + }; + if (is_insert) { + let v = next_1(); + assert!( + big_map.upsert(v, v) == small_map.upsert(v, v), + i + ); + } else { + let v = next_2(); + assert!(big_map.remove(&v) == small_map.remove(&v), i); + }; + if ((i + 1) % 50 == 0) { + big_map.validate_map(); + + let big_iter = big_map.new_begin_iter(); + let small_iter = small_map.new_begin_iter(); + while (!big_iter.iter_is_end(&big_map) + || !small_iter.iter_is_end(&small_map)) { + assert!( + big_iter.iter_borrow_key() + == small_iter.iter_borrow_key(&small_map), + i + ); + assert!( + big_iter.iter_borrow(&big_map) + == small_iter.iter_borrow(&small_map), + i + ); + big_iter = big_iter.iter_next(&big_map); + small_iter = small_iter.iter_next(&small_map); + }; + }; + }; + big_map.destroy_and_validate(); + } + + #[test_only] + const OFFSET: u64 = 270001; + #[test_only] + const MOD: u64 = 1000000; + + #[test] + fun test_comparison_random() { + let x = 1234; + let y = 1234; + comparison_test( + 500, + 5, + 5, + false, + || { + x += OFFSET; + if (x > MOD) { + x -= MOD + }; + x + }, + || { + y += OFFSET; + if (y > MOD) { + y -= MOD + }; + y + } + ); + } + + #[test] + fun test_comparison_increasing() { + let x = 0; + let y = 0; + comparison_test( + 500, + 5, + 5, + false, + || { + x += 1; + x + }, + || { + y += 1; + y + } + ); + } + + #[test] + fun test_comparison_decreasing() { + let x = 100000; + let y = 100000; + comparison_test( + 500, + 5, + 5, + false, + || { + x -= 1; + x + }, + || { + y -= 1; + y + } + ); + } + + #[test_only] + fun test_large_data_set_helper( + inner_max_degree: u16, leaf_max_degree: u16, reuse_slots: bool + ) { + use std::vector; + + let map = new_with_config(inner_max_degree, leaf_max_degree, reuse_slots); + if (reuse_slots) { + map.allocate_spare_slots(4); + }; + let data = ordered_map::large_dataset(); + let shuffled_data = ordered_map::large_dataset_shuffled(); + + let len = data.length(); + for (i in 0..len) { + let element = data[i]; + map.upsert(element, element); + if (i % 7 == 0) { + map.validate_map(); + } + }; + + for (i in 0..len) { + let element = shuffled_data.borrow(i); + let it = map.find(element); + assert!(!it.iter_is_end(&map), i); + assert!(it.iter_borrow_key() == element, i); + + // aptos_std::debug::print(&it); + + let it_next = it.iter_next(&map); + let it_after = map.lower_bound(&(*element + 1)); + + // aptos_std::debug::print(&it_next); + // aptos_std::debug::print(&it_after); + // aptos_std::debug::print(&std::string::utf8(b"bla")); + + assert!(it_next == it_after, i); + }; + + let removed = vector::empty(); + for (i in 0..len) { + let element = shuffled_data.borrow(i); + if (!removed.contains(element)) { + removed.push_back(*element); + map.remove(element); + if (i % 7 == 1) { + map.validate_map(); + + } + } else { + assert!(!map.contains(element)); + }; + }; + + map.destroy_empty(); + } + + // Currently ignored long / more extensive tests. + + // #[test] + // fun test_large_data_set_order_5_false() { + // test_large_data_set_helper(5, 5, false); + // } + + // #[test] + // fun test_large_data_set_order_5_true() { + // test_large_data_set_helper(5, 5, true); + // } + + // #[test] + // fun test_large_data_set_order_4_3_false() { + // test_large_data_set_helper(4, 3, false); + // } + + // #[test] + // fun test_large_data_set_order_4_3_true() { + // test_large_data_set_helper(4, 3, true); + // } + + // #[test] + // fun test_large_data_set_order_4_4_false() { + // test_large_data_set_helper(4, 4, false); + // } + + // #[test] + // fun test_large_data_set_order_4_4_true() { + // test_large_data_set_helper(4, 4, true); + // } + + // #[test] + // fun test_large_data_set_order_6_false() { + // test_large_data_set_helper(6, 6, false); + // } + + // #[test] + // fun test_large_data_set_order_6_true() { + // test_large_data_set_helper(6, 6, true); + // } + + // #[test] + // fun test_large_data_set_order_6_3_false() { + // test_large_data_set_helper(6, 3, false); + // } + + #[test] + fun test_large_data_set_order_6_3_true() { + test_large_data_set_helper(6, 3, true); + } + + #[test] + fun test_large_data_set_order_4_6_false() { + test_large_data_set_helper(4, 6, false); + } + + // #[test] + // fun test_large_data_set_order_4_6_true() { + // test_large_data_set_helper(4, 6, true); + // } + + // #[test] + // fun test_large_data_set_order_16_false() { + // test_large_data_set_helper(16, 16, false); + // } + + // #[test] + // fun test_large_data_set_order_16_true() { + // test_large_data_set_helper(16, 16, true); + // } + + // #[test] + // fun test_large_data_set_order_31_false() { + // test_large_data_set_helper(31, 31, false); + // } + + // #[test] + // fun test_large_data_set_order_31_true() { + // test_large_data_set_helper(31, 31, true); + // } + + // #[test] + // fun test_large_data_set_order_31_3_false() { + // test_large_data_set_helper(31, 3, false); + // } + + // #[test] + // fun test_large_data_set_order_31_3_true() { + // test_large_data_set_helper(31, 3, true); + // } + + // #[test] + // fun test_large_data_set_order_31_5_false() { + // test_large_data_set_helper(31, 5, false); + // } + + // #[test] + // fun test_large_data_set_order_31_5_true() { + // test_large_data_set_helper(31, 5, true); + // } + + // #[test] + // fun test_large_data_set_order_32_false() { + // test_large_data_set_helper(32, 32, false); + // } + + // #[test] + // fun test_large_data_set_order_32_true() { + // test_large_data_set_helper(32, 32, true); + // } +} diff --git a/precompile/modules/initia_stdlib/sources/bigdecimal.move b/precompile/modules/initia_stdlib/sources/bigdecimal.move index e52a5c13..321cca29 100644 --- a/precompile/modules/initia_stdlib/sources/bigdecimal.move +++ b/precompile/modules/initia_stdlib/sources/bigdecimal.move @@ -62,8 +62,8 @@ module initia_std::bigdecimal { } /// Get the scaled value of a BigDecimal. - public fun get_scaled(num: BigDecimal): BigUint { - num.scaled + public fun get_scaled(self: BigDecimal): BigUint { + self.scaled } /// Create a BigDecimal from a scaled BigUint le_bytes value. @@ -71,8 +71,8 @@ module initia_std::bigdecimal { BigDecimal { scaled: biguint::from_le_bytes(le_bytes) } } - public fun get_scaled_le_bytes(num: BigDecimal): vector { - biguint::to_le_bytes(num.scaled) + public fun get_scaled_le_bytes(self: BigDecimal): vector { + biguint::to_le_bytes(self.scaled) } public fun from_ratio(numerator: BigUint, denominator: BigUint): BigDecimal { @@ -116,15 +116,15 @@ module initia_std::bigdecimal { BigDecimal { scaled: biguint::div(numerator, denominator) } } - public fun rev(num: BigDecimal): BigDecimal { + public fun rev(self: BigDecimal): BigDecimal { assert!( - !biguint::is_zero(num.scaled), + !biguint::is_zero(self.scaled), error::invalid_argument(EDIVISION_BY_ZERO) ); let fractional = f(); BigDecimal { - scaled: biguint::div(biguint::mul(fractional, fractional), num.scaled) + scaled: biguint::div(biguint::mul(fractional, fractional), self.scaled) } } @@ -138,208 +138,208 @@ module initia_std::bigdecimal { // cmp - public fun eq(num1: BigDecimal, num2: BigDecimal): bool { - biguint::eq(num1.scaled, num2.scaled) + public fun eq(self: BigDecimal, other: BigDecimal): bool { + biguint::eq(self.scaled, other.scaled) } - public fun lt(num1: BigDecimal, num2: BigDecimal): bool { - biguint::lt(num1.scaled, num2.scaled) + public fun lt(self: BigDecimal, other: BigDecimal): bool { + biguint::lt(self.scaled, other.scaled) } - public fun le(num1: BigDecimal, num2: BigDecimal): bool { - biguint::le(num1.scaled, num2.scaled) + public fun le(self: BigDecimal, other: BigDecimal): bool { + biguint::le(self.scaled, other.scaled) } - public fun gt(num1: BigDecimal, num2: BigDecimal): bool { - biguint::gt(num1.scaled, num2.scaled) + public fun gt(self: BigDecimal, other: BigDecimal): bool { + biguint::gt(self.scaled, other.scaled) } - public fun ge(num1: BigDecimal, num2: BigDecimal): bool { - biguint::ge(num1.scaled, num2.scaled) + public fun ge(self: BigDecimal, other: BigDecimal): bool { + biguint::ge(self.scaled, other.scaled) } - public fun is_zero(num: BigDecimal): bool { - biguint::is_zero(num.scaled) + public fun is_zero(self: BigDecimal): bool { + biguint::is_zero(self.scaled) } - public fun is_one(num: BigDecimal): bool { - biguint::eq(num.scaled, f()) + public fun is_one(self: BigDecimal): bool { + biguint::eq(self.scaled, f()) } // arithmetic - public fun add(num1: BigDecimal, num2: BigDecimal): BigDecimal { - BigDecimal { scaled: biguint::add(num1.scaled, num2.scaled) } + public fun add(self: BigDecimal, other: BigDecimal): BigDecimal { + BigDecimal { scaled: biguint::add(self.scaled, other.scaled) } } - public fun add_by_u64(num1: BigDecimal, num2: u64): BigDecimal { + public fun add_by_u64(self: BigDecimal, other: u64): BigDecimal { BigDecimal { - scaled: biguint::add(num1.scaled, from_u64(num2).scaled) + scaled: biguint::add(self.scaled, from_u64(other).scaled) } } - public fun add_by_u128(num1: BigDecimal, num2: u128): BigDecimal { + public fun add_by_u128(self: BigDecimal, other: u128): BigDecimal { BigDecimal { - scaled: biguint::add(num1.scaled, from_u128(num2).scaled) + scaled: biguint::add(self.scaled, from_u128(other).scaled) } } - public fun add_by_u256(num1: BigDecimal, num2: u256): BigDecimal { + public fun add_by_u256(self: BigDecimal, other: u256): BigDecimal { BigDecimal { - scaled: biguint::add(num1.scaled, from_u256(num2).scaled) + scaled: biguint::add(self.scaled, from_u256(other).scaled) } } - public fun sub(num1: BigDecimal, num2: BigDecimal): BigDecimal { - assert!(ge(num1, num2), error::invalid_argument(NEGATIVE_RESULT)); - BigDecimal { scaled: biguint::sub(num1.scaled, num2.scaled) } + public fun sub(self: BigDecimal, other: BigDecimal): BigDecimal { + assert!(ge(self, other), error::invalid_argument(NEGATIVE_RESULT)); + BigDecimal { scaled: biguint::sub(self.scaled, other.scaled) } } - public fun sub_by_u64(num1: BigDecimal, num2: u64): BigDecimal { - let num2 = from_u64(num2); - assert!(ge(num1, num2), error::invalid_argument(NEGATIVE_RESULT)); - BigDecimal { scaled: biguint::sub(num1.scaled, num2.scaled) } + public fun sub_by_u64(self: BigDecimal, other: u64): BigDecimal { + let other = from_u64(other); + assert!(ge(self, other), error::invalid_argument(NEGATIVE_RESULT)); + BigDecimal { scaled: biguint::sub(self.scaled, other.scaled) } } - public fun sub_by_u128(num1: BigDecimal, num2: u128): BigDecimal { - let num2 = from_u128(num2); - assert!(ge(num1, num2), error::invalid_argument(NEGATIVE_RESULT)); - BigDecimal { scaled: biguint::sub(num1.scaled, num2.scaled) } + public fun sub_by_u128(self: BigDecimal, other: u128): BigDecimal { + let other = from_u128(other); + assert!(ge(self, other), error::invalid_argument(NEGATIVE_RESULT)); + BigDecimal { scaled: biguint::sub(self.scaled, other.scaled) } } - public fun sub_by_u256(num1: BigDecimal, num2: u256): BigDecimal { - let num2 = from_u256(num2); - assert!(ge(num1, num2), error::invalid_argument(NEGATIVE_RESULT)); - BigDecimal { scaled: biguint::sub(num1.scaled, num2.scaled) } + public fun sub_by_u256(self: BigDecimal, other: u256): BigDecimal { + let other = from_u256(other); + assert!(ge(self, other), error::invalid_argument(NEGATIVE_RESULT)); + BigDecimal { scaled: biguint::sub(self.scaled, other.scaled) } } - public fun mul(num1: BigDecimal, num2: BigDecimal): BigDecimal { + public fun mul(self: BigDecimal, other: BigDecimal): BigDecimal { BigDecimal { - scaled: biguint::div(biguint::mul(num1.scaled, num2.scaled), f()) + scaled: biguint::div(biguint::mul(self.scaled, other.scaled), f()) } } - public fun mul_truncate(num1: BigDecimal, num2: BigDecimal): BigUint { - truncate(mul(num1, num2)) + public fun mul_truncate(self: BigDecimal, other: BigDecimal): BigUint { + truncate(mul(self, other)) } - public fun mul_ceil(num1: BigDecimal, num2: BigDecimal): BigUint { - ceil(mul(num1, num2)) + public fun mul_ceil(self: BigDecimal, other: BigDecimal): BigUint { + ceil(mul(self, other)) } - public fun mul_by_u64(num1: BigDecimal, num2: u64): BigDecimal { - BigDecimal { scaled: biguint::mul_by_u64(num1.scaled, num2) } + public fun mul_by_u64(self: BigDecimal, other: u64): BigDecimal { + BigDecimal { scaled: biguint::mul_by_u64(self.scaled, other) } } - public fun mul_by_u64_truncate(num1: BigDecimal, num2: u64): u64 { - truncate_u64(mul_by_u64(num1, num2)) + public fun mul_by_u64_truncate(self: BigDecimal, other: u64): u64 { + truncate_u64(mul_by_u64(self, other)) } - public fun mul_by_u64_ceil(num1: BigDecimal, num2: u64): u64 { - ceil_u64(mul_by_u64(num1, num2)) + public fun mul_by_u64_ceil(self: BigDecimal, other: u64): u64 { + ceil_u64(mul_by_u64(self, other)) } - public fun mul_by_u128(num1: BigDecimal, num2: u128): BigDecimal { - BigDecimal { scaled: biguint::mul_by_u128(num1.scaled, num2) } + public fun mul_by_u128(self: BigDecimal, other: u128): BigDecimal { + BigDecimal { scaled: biguint::mul_by_u128(self.scaled, other) } } - public fun mul_by_u128_truncate(num1: BigDecimal, num2: u128): u128 { - truncate_u128(mul_by_u128(num1, num2)) + public fun mul_by_u128_truncate(self: BigDecimal, other: u128): u128 { + truncate_u128(mul_by_u128(self, other)) } - public fun mul_by_u128_ceil(num1: BigDecimal, num2: u128): u128 { - ceil_u128(mul_by_u128(num1, num2)) + public fun mul_by_u128_ceil(self: BigDecimal, other: u128): u128 { + ceil_u128(mul_by_u128(self, other)) } - public fun mul_by_u256(num1: BigDecimal, num2: u256): BigDecimal { - BigDecimal { scaled: biguint::mul_by_u256(num1.scaled, num2) } + public fun mul_by_u256(self: BigDecimal, other: u256): BigDecimal { + BigDecimal { scaled: biguint::mul_by_u256(self.scaled, other) } } - public fun mul_by_u256_truncate(num1: BigDecimal, num2: u256): u256 { - truncate_u256(mul_by_u256(num1, num2)) + public fun mul_by_u256_truncate(self: BigDecimal, other: u256): u256 { + truncate_u256(mul_by_u256(self, other)) } - public fun mul_by_u256_ceil(num1: BigDecimal, num2: u256): u256 { - ceil_u256(mul_by_u256(num1, num2)) + public fun mul_by_u256_ceil(self: BigDecimal, other: u256): u256 { + ceil_u256(mul_by_u256(self, other)) } - public fun div(num1: BigDecimal, num2: BigDecimal): BigDecimal { + public fun div(self: BigDecimal, other: BigDecimal): BigDecimal { assert!( - !biguint::is_zero(num2.scaled), + !biguint::is_zero(other.scaled), error::invalid_argument(EDIVISION_BY_ZERO) ); BigDecimal { - scaled: biguint::div(biguint::mul(num1.scaled, f()), num2.scaled) + scaled: biguint::div(biguint::mul(self.scaled, f()), other.scaled) } } - public fun div_by_u64(num1: BigDecimal, num2: u64): BigDecimal { - assert!(num2 != 0, error::invalid_argument(EDIVISION_BY_ZERO)); + public fun div_by_u64(self: BigDecimal, other: u64): BigDecimal { + assert!(other != 0, error::invalid_argument(EDIVISION_BY_ZERO)); - BigDecimal { scaled: biguint::div_by_u64(num1.scaled, num2) } + BigDecimal { scaled: biguint::div_by_u64(self.scaled, other) } } - public fun div_by_u128(num1: BigDecimal, num2: u128): BigDecimal { - assert!(num2 != 0, error::invalid_argument(EDIVISION_BY_ZERO)); + public fun div_by_u128(self: BigDecimal, other: u128): BigDecimal { + assert!(other != 0, error::invalid_argument(EDIVISION_BY_ZERO)); - BigDecimal { scaled: biguint::div_by_u128(num1.scaled, num2) } + BigDecimal { scaled: biguint::div_by_u128(self.scaled, other) } } - public fun div_by_u256(num1: BigDecimal, num2: u256): BigDecimal { - assert!(num2 != 0, error::invalid_argument(EDIVISION_BY_ZERO)); + public fun div_by_u256(self: BigDecimal, other: u256): BigDecimal { + assert!(other != 0, error::invalid_argument(EDIVISION_BY_ZERO)); - BigDecimal { scaled: biguint::div_by_u256(num1.scaled, num2) } + BigDecimal { scaled: biguint::div_by_u256(self.scaled, other) } } // cast - public fun truncate(num: BigDecimal): BigUint { - biguint::div(num.scaled, f()) + public fun truncate(self: BigDecimal): BigUint { + biguint::div(self.scaled, f()) } - public fun truncate_u64(num: BigDecimal): u64 { - biguint::to_u64(truncate(num)) + public fun truncate_u64(self: BigDecimal): u64 { + biguint::to_u64(truncate(self)) } - public fun truncate_u128(num: BigDecimal): u128 { - biguint::to_u128(truncate(num)) + public fun truncate_u128(self: BigDecimal): u128 { + biguint::to_u128(truncate(self)) } - public fun truncate_u256(num: BigDecimal): u256 { - biguint::to_u256(truncate(num)) + public fun truncate_u256(self: BigDecimal): u256 { + biguint::to_u256(truncate(self)) } - public fun round_up(num: BigDecimal): BigUint { - biguint::div(biguint::add(num.scaled, hf()), f()) + public fun round_up(self: BigDecimal): BigUint { + biguint::div(biguint::add(self.scaled, hf()), f()) } - public fun round_up_u64(num: BigDecimal): u64 { - biguint::to_u64(round_up(num)) + public fun round_up_u64(self: BigDecimal): u64 { + biguint::to_u64(round_up(self)) } - public fun round_up_u128(num: BigDecimal): u128 { - biguint::to_u128(round_up(num)) + public fun round_up_u128(self: BigDecimal): u128 { + biguint::to_u128(round_up(self)) } - public fun round_up_u256(num: BigDecimal): u256 { - biguint::to_u256(round_up(num)) + public fun round_up_u256(self: BigDecimal): u256 { + biguint::to_u256(round_up(self)) } - public fun ceil(num: BigDecimal): BigUint { - biguint::div(biguint::add(num.scaled, f_1()), f()) + public fun ceil(self: BigDecimal): BigUint { + biguint::div(biguint::add(self.scaled, f_1()), f()) } - public fun ceil_u64(num: BigDecimal): u64 { - biguint::to_u64(ceil(num)) + public fun ceil_u64(self: BigDecimal): u64 { + biguint::to_u64(ceil(self)) } - public fun ceil_u128(num: BigDecimal): u128 { - biguint::to_u128(ceil(num)) + public fun ceil_u128(self: BigDecimal): u128 { + biguint::to_u128(ceil(self)) } - public fun ceil_u256(num: BigDecimal): u256 { - biguint::to_u256(ceil(num)) + public fun ceil_u256(self: BigDecimal): u256 { + biguint::to_u256(ceil(self)) } // tests diff --git a/precompile/modules/initia_stdlib/sources/ordered_map.move b/precompile/modules/initia_stdlib/sources/ordered_map.move new file mode 100644 index 00000000..0e051fd5 --- /dev/null +++ b/precompile/modules/initia_stdlib/sources/ordered_map.move @@ -0,0 +1,1459 @@ +/// This module provides an implementation for an ordered map. +/// +/// Keys point to values, and each key in the map must be unique. +/// +/// Currently, one implementation is provided, backed by a single sorted vector. +/// +/// That means that keys can be found within O(log N) time. +/// Adds and removals take O(N) time, but the constant factor is small, +/// as it does only O(log N) comparisons, and does efficient mem-copy with vector operations. +/// +/// Additionally, it provides a way to lookup and iterate over sorted keys, making range query +/// take O(log N + R) time (where R is number of elements in the range). +/// +/// Most methods operate with OrderedMap being `self`. +/// All methods that start with iter_*, operate with IteratorPtr being `self`. +/// +/// Uses cmp::compare for ordering, which compares primitive types natively, and uses common +/// lexicographical sorting for complex types. +/// +/// TODO: all iterator functions are public(friend) for now, so that they can be modified in a +/// backward incompatible way. Type is also named IteratorPtr, so that Iterator is free to use later. +/// They are waiting for Move improvement that will allow references to be part of the struct, +/// allowing cleaner iterator APIs. +/// +module initia_std::ordered_map { + friend std::big_ordered_map; + + use std::vector; + + use std::option::{Self, Option}; + use std::cmp; + use std::error; + + /// Map key already exists + const EKEY_ALREADY_EXISTS: u64 = 1; + /// Map key is not found + const EKEY_NOT_FOUND: u64 = 2; + // Trying to do an operation on an IteratorPtr that would go out of bounds + const EITER_OUT_OF_BOUNDS: u64 = 3; + /// New key used in replace_key_inplace doesn't respect the order + const ENEW_KEY_NOT_IN_ORDER: u64 = 4; + + /// Individual entry holding (key, value) pair + struct Entry has drop, copy, store { + key: K, + value: V + } + + /// The OrderedMap datastructure. + enum OrderedMap has drop, copy, store { + /// sorted-vector based implementation of OrderedMap + SortedVectorMap { + /// List of entries, sorted by key. + entries: vector> + } + } + + /// An iterator pointing to a valid position in an ordered map, or to the end. + /// + /// TODO: Once fields can be (mutable) references, this class will be deprecated. + enum IteratorPtr has copy, drop { + End, + Position { + /// The index of the iterator pointing to. + index: u64 + } + } + + /// Create a new empty OrderedMap, using default (SortedVectorMap) implementation. + public fun new(): OrderedMap { + OrderedMap::SortedVectorMap { entries: vector::empty() } + } + + /// Create a OrderedMap from a vector of keys and values. + /// Aborts with EKEY_ALREADY_EXISTS if duplicate keys are passed in. + public fun new_from(keys: vector, values: vector): OrderedMap { + let map = new(); + map.add_all(keys, values); + map + } + + /// Number of elements in the map. + public fun length(self: &OrderedMap): u64 { + self.entries.length() + } + + /// Whether map is empty. + public fun is_empty(self: &OrderedMap): bool { + self.entries.is_empty() + } + + /// Add a key/value pair to the map. + /// Aborts with EKEY_ALREADY_EXISTS if key already exist. + public fun add(self: &mut OrderedMap, key: K, value: V) { + let len = self.entries.length(); + let index = binary_search(&key, &self.entries, 0, len); + + // key must not already be inside. + assert!( + index >= len || &self.entries[index].key != &key, + error::invalid_argument(EKEY_ALREADY_EXISTS) + ); + self.entries.insert(index, Entry { key, value }); + } + + /// If the key doesn't exist in the map, inserts the key/value, and returns none. + /// Otherwise, updates the value under the given key, and returns the old value. + public fun upsert( + self: &mut OrderedMap, key: K, value: V + ): Option { + let len = self.entries.length(); + let index = binary_search(&key, &self.entries, 0, len); + + if (index < len && &self.entries[index].key == &key) { + let Entry { key: _, value: old_value } = + self.entries.replace(index, Entry { key, value }); + option::some(old_value) + } else { + self.entries.insert(index, Entry { key, value }); + option::none() + } + } + + /// Remove a key/value pair from the map. + /// Aborts with EKEY_NOT_FOUND if `key` doesn't exist. + public fun remove(self: &mut OrderedMap, key: &K): V { + let len = self.entries.length(); + let index = binary_search(key, &self.entries, 0, len); + assert!(index < len, error::invalid_argument(EKEY_NOT_FOUND)); + let Entry { key: old_key, value } = self.entries.remove(index); + assert!(key == &old_key, error::invalid_argument(EKEY_NOT_FOUND)); + value + } + + /// Returns whether map contains a given key. + public fun contains(self: &OrderedMap, key: &K): bool { + !self.find(key).iter_is_end(self) + } + + public fun borrow(self: &OrderedMap, key: &K): &V { + self.find(key).iter_borrow(self) + } + + public fun borrow_mut(self: &mut OrderedMap, key: &K): &mut V { + self.find(key).iter_borrow_mut(self) + } + + /// Changes the key, while keeping the same value attached to it + /// Aborts with EKEY_NOT_FOUND if `old_key` doesn't exist. + /// Aborts with ENEW_KEY_NOT_IN_ORDER if `new_key` doesn't keep the order `old_key` was in. + public(friend) fun replace_key_inplace( + self: &mut OrderedMap, old_key: &K, new_key: K + ) { + let len = self.entries.length(); + let index = binary_search(old_key, &self.entries, 0, len); + assert!(index < len, error::invalid_argument(EKEY_NOT_FOUND)); + + assert!( + old_key == &self.entries[index].key, + error::invalid_argument(EKEY_NOT_FOUND) + ); + + // check that after we update the key, order is going to be respected + if (index > 0) { + assert!( + cmp::compare(&self.entries[index - 1].key, &new_key).is_lt(), + error::invalid_argument(ENEW_KEY_NOT_IN_ORDER) + ) + }; + + if (index + 1 < len) { + assert!( + cmp::compare(&new_key, &self.entries[index + 1].key).is_lt(), + error::invalid_argument(ENEW_KEY_NOT_IN_ORDER) + ) + }; + + self.entries[index].key = new_key; + } + + /// Add multiple key/value pairs to the map. The keys must not already exist. + /// Aborts with EKEY_ALREADY_EXISTS if key already exist, or duplicate keys are passed in. + public fun add_all( + self: &mut OrderedMap, + keys: vector, + values: vector + ) { + // TODO: Can be optimized, by sorting keys and values, and then creating map. + keys.zip( + values, |key, value| { + self.add(key, value); + } + ); + } + + /// Add multiple key/value pairs to the map, overwrites values if they exist already, + /// or if duplicate keys are passed in. + public fun upsert_all( + self: &mut OrderedMap, + keys: vector, + values: vector + ) { + // TODO: Can be optimized, by sorting keys and values, and then creating map. + keys.zip( + values, |key, value| { + self.upsert(key, value); + } + ); + } + + /// Takes all elements from `other` and adds them to `self`, + /// overwritting if any key is already present in self. + public fun append( + self: &mut OrderedMap, other: OrderedMap + ) { + self.append_impl(other); + } + + /// Takes all elements from `other` and adds them to `self`. + /// Aborts with EKEY_ALREADY_EXISTS if `other` has a key already present in `self`. + public fun append_disjoint( + self: &mut OrderedMap, other: OrderedMap + ) { + let overwritten = self.append_impl(other); + assert!(overwritten.length() == 0, error::invalid_argument(EKEY_ALREADY_EXISTS)); + overwritten.destroy_empty(); + } + + /// Takes all elements from `other` and adds them to `self`, returning list of entries in self that were overwritten. + fun append_impl( + self: &mut OrderedMap, other: OrderedMap + ): vector> { + let OrderedMap::SortedVectorMap { entries: other_entries } = other; + let overwritten = vector::empty(); + + if (other_entries.is_empty()) { + other_entries.destroy_empty(); + return overwritten; + }; + + if (self.entries.is_empty()) { + self.entries.append(other_entries); + return overwritten; + }; + + // Optimization: if all elements in `other` are larger than all elements in `self`, we can just move them over. + if (cmp::compare( + &self.entries.borrow(self.entries.length() - 1).key, + &other_entries.borrow(0).key + ).is_lt()) { + self.entries.append(other_entries); + return overwritten; + }; + + // In O(n), traversing from the back, build reverse sorted result, and then reverse it back + let reverse_result = vector::empty(); + let cur_i = self.entries.length() - 1; + let other_i = other_entries.length() - 1; + + // after the end of the loop, other_entries is empty, and any leftover is in entries + loop { + let ord = cmp::compare( + &self.entries[cur_i].key, &other_entries[other_i].key + ); + if (ord.is_gt()) { + reverse_result.push_back(self.entries.pop_back()); + if (cur_i == 0) { + // make other_entries empty, and rest in entries. + // TODO cannot use mem::swap until it is public/released + // mem::swap(&mut self.entries, &mut other_entries); + self.entries.append(other_entries); + break; + } else { + cur_i -= 1; + }; + } else { + // is_lt or is_eq + if (ord.is_eq()) { + // we skip the entries one, and below put in the result one from other. + overwritten.push_back(self.entries.pop_back()); + }; + + reverse_result.push_back(other_entries.pop_back()); + if (other_i == 0) { + other_entries.destroy_empty(); + break; + } else { + other_i -= 1; + }; + }; + }; + + self.entries.reverse_append(reverse_result); + + overwritten + } + + /// Splits the collection into two, such to leave `self` with `at` number of elements. + /// Returns a newly allocated map containing the elements in the range [at, len). + /// After the call, the original map will be left containing the elements [0, at). + public fun trim(self: &mut OrderedMap, at: u64): OrderedMap { + let rest = self.entries.trim(at); + + OrderedMap::SortedVectorMap { entries: rest } + } + + public fun borrow_front(self: &OrderedMap): (&K, &V) { + let entry = self.entries.borrow(0); + (&entry.key, &entry.value) + } + + public fun borrow_back(self: &OrderedMap): (&K, &V) { + let entry = self.entries.borrow(self.entries.length() - 1); + (&entry.key, &entry.value) + } + + public fun pop_front(self: &mut OrderedMap): (K, V) { + let Entry { key, value } = self.entries.remove(0); + (key, value) + } + + public fun pop_back(self: &mut OrderedMap): (K, V) { + let Entry { key, value } = self.entries.pop_back(); + (key, value) + } + + public fun prev_key(self: &OrderedMap, key: &K): Option { + let it = self.lower_bound(key); + if (it.iter_is_begin(self)) { + option::none() + } else { + option::some(*it.iter_prev(self).iter_borrow_key(self)) + } + } + + public fun next_key(self: &OrderedMap, key: &K): Option { + let it = self.lower_bound(key); + if (it.iter_is_end(self)) { + option::none() + } else { + let cur_key = it.iter_borrow_key(self); + if (key == cur_key) { + let it = it.iter_next(self); + if (it.iter_is_end(self)) { + option::none() + } else { + option::some(*it.iter_borrow_key(self)) + } + } else { + option::some(*cur_key) + } + } + } + + // TODO: see if it is more understandable if iterator points between elements, + // and there is iter_borrow_next and iter_borrow_prev, and provide iter_insert. + + /// Returns an iterator pointing to the first element that is greater or equal to the provided + /// key, or an end iterator if such element doesn't exist. + public(friend) fun lower_bound(self: &OrderedMap, key: &K): IteratorPtr { + let entries = &self.entries; + let len = entries.length(); + + let index = binary_search(key, entries, 0, len); + if (index == len) { + self.new_end_iter() + } else { + new_iter(index) + } + } + + /// Returns an iterator pointing to the element that equals to the provided key, or an end + /// iterator if the key is not found. + public(friend) fun find(self: &OrderedMap, key: &K): IteratorPtr { + let lower_bound = self.lower_bound(key); + if (lower_bound.iter_is_end(self)) { + lower_bound + } else if (lower_bound.iter_borrow_key(self) == key) { + lower_bound + } else { + self.new_end_iter() + } + } + + /// Returns the begin iterator. + public(friend) fun new_begin_iter(self: &OrderedMap): IteratorPtr { + if (self.is_empty()) { + return IteratorPtr::End; + }; + + new_iter(0) + } + + /// Returns the end iterator. + public(friend) fun new_end_iter(self: &OrderedMap): IteratorPtr { + IteratorPtr::End + } + + // ========== Section for methods opearting on iterators ======== + // Note: After any modifications to the map, do not use any of the iterators obtained beforehand. + // Operations on iterators after map is modified are unexpected/incorrect. + + /// Returns the next iterator, or none if already at the end iterator. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_next( + self: IteratorPtr, map: &OrderedMap + ): IteratorPtr { + assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS)); + + let index = self.index + 1; + if (index < map.entries.length()) { + new_iter(index) + } else { + map.new_end_iter() + } + } + + /// Returns the previous iterator, or none if already at the begin iterator. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_prev( + self: IteratorPtr, map: &OrderedMap + ): IteratorPtr { + assert!(!self.iter_is_begin(map), error::invalid_argument(EITER_OUT_OF_BOUNDS)); + + let index = + if (self is IteratorPtr::End) { + map.entries.length() - 1 + } else { + self.index - 1 + }; + + new_iter(index) + } + + /// Returns whether the iterator is a begin iterator. + public(friend) fun iter_is_begin( + self: &IteratorPtr, map: &OrderedMap + ): bool { + if (self is IteratorPtr::End) { + map.is_empty() + } else { + self.index == 0 + } + } + + /// Returns true iff the iterator is a begin iterator from a non-empty collection. + /// (I.e. if iterator points to a valid element) + /// This method doesn't require having access to map, unlike iter_is_begin. + public(friend) fun iter_is_begin_from_non_empty(self: &IteratorPtr): bool { + if (self is IteratorPtr::End) { false } + else { + self.index == 0 + } + } + + /// Returns whether the iterator is an end iterator. + public(friend) fun iter_is_end( + self: &IteratorPtr, _map: &OrderedMap + ): bool { + self is IteratorPtr::End + } + + /// Borrows the key given iterator points to. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_borrow_key( + self: &IteratorPtr, map: &OrderedMap + ): &K { + assert!( + !(self is IteratorPtr::End), error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + + &map.entries.borrow(self.index).key + } + + /// Borrows the value given iterator points to. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_borrow( + self: IteratorPtr, map: &OrderedMap + ): &V { + assert!( + !(self is IteratorPtr::End), error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + &map.entries.borrow(self.index).value + } + + /// Mutably borrows the value iterator points to. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_borrow_mut( + self: IteratorPtr, map: &mut OrderedMap + ): &mut V { + assert!( + !(self is IteratorPtr::End), error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + &mut map.entries.borrow_mut(self.index).value + } + + /// Removes (key, value) pair iterator points to, returning the previous value. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_remove( + self: IteratorPtr, map: &mut OrderedMap + ): V { + assert!( + !(self is IteratorPtr::End), error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + + let Entry { key: _, value } = map.entries.remove(self.index); + value + } + + /// Replaces the value iterator is pointing to, returning the previous value. + /// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end. + /// Note: Requires that the map is not changed after the input iterator is generated. + public(friend) fun iter_replace( + self: IteratorPtr, + map: &mut OrderedMap, + value: V + ): V { + assert!( + !(self is IteratorPtr::End), error::invalid_argument(EITER_OUT_OF_BOUNDS) + ); + + // TODO once mem::replace is public/released, update to: + // let entry = map.entries.borrow_mut(self.index); + // mem::replace(&mut entry.value, value) + let key = map.entries[self.index].key; + let Entry { key: _, value: prev_value } = + map.entries.replace(self.index, Entry { key, value }); + prev_value + } + + /// Add key/value pair to the map, at the iterator position (before the element at the iterator position). + /// Aborts with ENEW_KEY_NOT_IN_ORDER is key is not larger than the key before the iterator, + /// or smaller than the key at the iterator position. + public(friend) fun iter_add( + self: IteratorPtr, + map: &mut OrderedMap, + key: K, + value: V + ) { + let len = map.entries.length(); + let insert_index = + if (self is IteratorPtr::End) { len } + else { + self.index + }; + + if (insert_index > 0) { + assert!( + cmp::compare(&map.entries[insert_index - 1].key, &key).is_lt(), + error::invalid_argument(ENEW_KEY_NOT_IN_ORDER) + ) + }; + + if (insert_index < len) { + assert!( + cmp::compare(&key, &map.entries[insert_index].key).is_lt(), + error::invalid_argument(ENEW_KEY_NOT_IN_ORDER) + ) + }; + + map.entries.insert(insert_index, Entry { key, value }); + } + + /// Destroys empty map. + /// Aborts if `self` is not empty. + public fun destroy_empty(self: OrderedMap) { + let OrderedMap::SortedVectorMap { entries } = self; + // assert!(entries.is_empty(), E_NOT_EMPTY); + entries.destroy_empty(); + } + + // ========= Section with views and inline for-loop methods ======= + + /// Return all keys in the map. This requires keys to be copyable. + public fun keys(self: &OrderedMap): vector { + self.entries.map_ref(|e| { + let e: &Entry = e; + e.key + }) + } + + /// Return all values in the map. This requires values to be copyable. + public fun values(self: &OrderedMap): vector { + self.entries.map_ref(|e| { + let e: &Entry = e; + e.value + }) + } + + /// Transform the map into two vectors with the keys and values respectively + /// Primarily used to destroy a map + public fun to_vec_pair(self: OrderedMap): (vector, vector) { + let keys: vector = vector::empty(); + let values: vector = vector::empty(); + let OrderedMap::SortedVectorMap { entries } = self; + entries.for_each(|e| { + let Entry { key, value } = e; + keys.push_back(key); + values.push_back(value); + }); + (keys, values) + } + + /// For maps that cannot be dropped this is a utility to destroy them + /// using lambdas to destroy the individual keys and values. + public inline fun destroy( + self: OrderedMap, + dk: |K|, + dv: |V| + ) { + let (keys, values) = self.to_vec_pair(); + keys.destroy(|_k| dk(_k)); + values.destroy(|_v| dv(_v)); + } + + /// Apply the function to each key-value pair in the map, consuming it. + public inline fun for_each(self: OrderedMap, f: |K, V|) { + let (keys, values) = self.to_vec_pair(); + keys.zip( + values, |k, v| f(k, v) + ); + } + + /// Apply the function to a reference of each key-value pair in the map. + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun for_each_ref( + self: &OrderedMap, f: |&K, &V| + ) { + // This implementation is innefficient: O(log(n)) for next_key / borrow lookups every time, + // but is the only one available through the public API. + if (!self.is_empty()) { + let (k, v) = self.borrow_front(); + f(k, v); + + let cur_k = self.next_key(k); + while (cur_k.is_some()) { + let k = cur_k.destroy_some(); + f(&k, self.borrow(&k)); + + cur_k = self.next_key(&k); + }; + }; + + // TODO: if we make iterator api public update to: + // let iter = self.new_begin_iter(); + // while (!iter.iter_is_end(self)) { + // f(iter.iter_borrow_key(self), iter.iter_borrow(self)); + // iter = iter.iter_next(self); + // } + + // TODO: once move supports private functions udpate to: + // vector::for_each_ref( + // &self.entries, + // |entry| { + // f(&entry.key, &entry.value) + // } + // ); + } + + // TODO: Temporary friend implementaiton, until for_each_ref can be made efficient. + public(friend) inline fun for_each_ref_friend( + self: &OrderedMap, f: |&K, &V| + ) { + let iter = self.new_begin_iter(); + while (!iter.iter_is_end(self)) { + f(iter.iter_borrow_key(self), iter.iter_borrow(self)); + iter = iter.iter_next(self); + } + } + + /// Apply the function to a mutable reference of each key-value pair in the map. + /// + /// Current implementation is O(n * log(n)). After function values will be optimized + /// to O(n). + public inline fun for_each_mut( + self: &mut OrderedMap, f: |&K, &mut V| + ) { + // This implementation is innefficient: O(log(n)) for next_key / borrow lookups every time, + // but is the only one available through the public API. + if (!self.is_empty()) { + let (k, _v) = self.borrow_front(); + + let k = *k; + let done = false; + while (!done) { + f(&k, self.borrow_mut(&k)); + + let cur_k = self.next_key(&k); + if (cur_k.is_some()) { + k = cur_k.destroy_some(); + } else { + done = true; + } + }; + }; + + // TODO: if we make iterator api public update to: + // let iter = self.new_begin_iter(); + // while (!iter.iter_is_end(self)) { + // let key = *iter.iter_borrow_key(self); + // f(key, iter.iter_borrow_mut(self)); + // iter = iter.iter_next(self); + // } + + // TODO: once move supports private functions udpate to: + // vector::for_each_mut( + // &mut self.entries, + // |entry| { + // f(&mut entry.key, &mut entry.value) + // } + // ); + } + + // ========= Section with private methods =============== + + inline fun new_iter(index: u64): IteratorPtr { + IteratorPtr::Position { index: index } + } + + // return index containing the key, or insert position. + // I.e. index of first element that has key larger or equal to the passed `key` argument. + fun binary_search( + key: &K, + entries: &vector>, + start: u64, + end: u64 + ): u64 { + let l = start; + let r = end; + while (l != r) { + let mid = l + ((r - l) >> 1); + let comparison = cmp::compare(&entries.borrow(mid).key, key); + if (comparison.is_lt()) { + l = mid + 1; + } else { + r = mid; + }; + }; + l + } + + // see if useful, and add + // + // public fun iter_num_below(self: IteratorPtr, map: &OrderedMap): u64 { + // if (self.iter_is_end()) { + // map.entries.length() + // } else { + // self.index + // } + // } + + spec module { + pragma verify = false; + } + + // ================= Section for tests ===================== + + #[test_only] + fun print_map(self: &OrderedMap) { + initia_std::debug::print(&self.entries); + } + + #[test_only] + public fun validate_ordered(self: &OrderedMap) { + let len = self.entries.length(); + let i = 1; + while (i < len) { + assert!( + cmp::compare(&self.entries.borrow(i).key, &self.entries.borrow(i - 1).key) + .is_gt(), + 1 + ); + i += 1; + }; + } + + #[test_only] + fun validate_iteration( + self: &OrderedMap + ) { + let expected_num_elements = self.length(); + let num_elements = 0; + let it = self.new_begin_iter(); + while (!it.iter_is_end(self)) { + num_elements += 1; + it = it.iter_next(self); + }; + assert!(num_elements == expected_num_elements, 2); + + let num_elements = 0; + let it = self.new_end_iter(); + while (!it.iter_is_begin(self)) { + it = it.iter_prev(self); + num_elements += 1; + }; + assert!(num_elements == expected_num_elements, 3); + } + + #[test_only] + fun validate_map(self: &OrderedMap) { + self.validate_ordered(); + self.validate_iteration(); + } + + #[test] + fun test_map_small() { + let map = new(); + map.validate_map(); + map.add(1, 1); + map.validate_map(); + map.add(2, 2); + map.validate_map(); + let r1 = map.upsert(3, 3); + map.validate_map(); + assert!(r1 == option::none(), 4); + map.add(4, 4); + map.validate_map(); + let r2 = map.upsert(4, 8); + map.validate_map(); + assert!(r2 == option::some(4), 5); + map.add(5, 5); + map.validate_map(); + map.add(6, 6); + map.validate_map(); + + map.remove(&5); + map.validate_map(); + map.remove(&4); + map.validate_map(); + map.remove(&1); + map.validate_map(); + map.remove(&3); + map.validate_map(); + map.remove(&2); + map.validate_map(); + map.remove(&6); + map.validate_map(); + + map.destroy_empty(); + } + + #[test] + fun test_add_remove_many() { + let map = new(); + + assert!(map.length() == 0, 0); + assert!(!map.contains(&3), 1); + map.add(3, 1); + assert!(map.length() == 1, 2); + assert!(map.contains(&3), 3); + assert!(map.borrow(&3) == &1, 4); + *map.borrow_mut(&3) = 2; + assert!(map.borrow(&3) == &2, 5); + + assert!(!map.contains(&2), 6); + map.add(2, 5); + assert!(map.length() == 2, 7); + assert!(map.contains(&2), 8); + assert!(map.borrow(&2) == &5, 9); + *map.borrow_mut(&2) = 9; + assert!(map.borrow(&2) == &9, 10); + + map.remove(&2); + assert!(map.length() == 1, 11); + assert!(!map.contains(&2), 12); + assert!(map.borrow(&3) == &2, 13); + + map.remove(&3); + assert!(map.length() == 0, 14); + assert!(!map.contains(&3), 15); + + map.destroy_empty(); + } + + #[test] + fun test_add_all() { + let map = new(); + + assert!(map.length() == 0, 0); + map.add_all( + vector[2, 1, 3], vector[20, 10, 30] + ); + + assert!( + map == new_from(vector[1, 2, 3], vector[10, 20, 30]), + 1 + ); + + assert!(map.length() == 3, 1); + assert!(map.borrow(&1) == &10, 2); + assert!(map.borrow(&2) == &20, 3); + assert!(map.borrow(&3) == &30, 4); + } + + #[test] + #[expected_failure(abort_code = 0x20002, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_add_all_mismatch() { + new_from(vector[1, 3], vector[10]); + } + + #[test] + fun test_upsert_all() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.upsert_all( + vector[7, 2, 3], vector[70, 20, 35] + ); + assert!( + map == new_from(vector[1, 2, 3, 5, 7], vector[10, 20, 35, 50, 70]), + 1 + ); + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_new_from_duplicate() { + new_from(vector[1, 3, 1, 5], vector[10, 30, 11, 50]); + } + + #[test] + #[expected_failure(abort_code = 0x20002, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_upsert_all_mismatch() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.upsert_all(vector[2], vector[20, 35]); + } + + #[test] + fun test_to_vec_pair() { + let (keys, values) = new_from( + vector[3, 1, 5], vector[30, 10, 50] + ).to_vec_pair(); + assert!(keys == vector[1, 3, 5], 1); + assert!(values == vector[10, 30, 50], 2); + } + + #[test] + fun test_keys() { + let map = new(); + assert!(map.keys() == vector[], 0); + map.add(2, 1); + map.add(3, 1); + + assert!(map.keys() == vector[2, 3], 0); + } + + #[test] + fun test_values() { + let map = new(); + assert!(map.values() == vector[], 0); + map.add(2, 1); + map.add(3, 2); + + assert!(map.values() == vector[1, 2], 0); + } + + #[test] + fun test_for_each_variants() { + let keys = vector[1, 3, 5]; + let values = vector[10, 30, 50]; + let map = new_from(keys, values); + + let index = 0; + map.for_each_ref(|k, v| { + assert!(keys[index] == *k); + assert!(values[index] == *v); + index += 1; + }); + + let index = 0; + map.for_each_mut(|k, v| { + assert!(keys[index] == *k); + assert!(values[index] == *v); + *v += 1; + index += 1; + }); + + let index = 0; + map.for_each(|k, v| { + assert!(keys[index] == k); + assert!(values[index] + 1 == v); + index += 1; + }); + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_add_twice() { + let map = new(); + map.add(3, 1); + map.add(3, 1); + + map.remove(&3); + map.destroy_empty(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_remove_twice_1() { + let map = new(); + map.add(3, 1); + map.remove(&3); + map.remove(&3); + + map.destroy_empty(); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_remove_twice_2() { + let map = new(); + map.add(3, 1); + map.add(4, 1); + map.remove(&3); + map.remove(&3); + + map.destroy_empty(); + } + + #[test] + fun test_upsert_test() { + let map = new(); + // test adding 3 elements using upsert + map.upsert:: (1, 1); + map.upsert(2, 2); + map.upsert(3, 3); + + assert!(map.length() == 3, 0); + assert!(map.contains(&1), 1); + assert!(map.contains(&2), 2); + assert!(map.contains(&3), 3); + assert!(map.borrow(&1) == &1, 4); + assert!(map.borrow(&2) == &2, 5); + assert!(map.borrow(&3) == &3, 6); + + // change mapping 1->1 to 1->4 + map.upsert(1, 4); + + assert!(map.length() == 3, 7); + assert!(map.contains(&1), 8); + assert!(map.borrow(&1) == &4, 9); + } + + #[test] + fun test_append() { + { + let map = new(); + let other = new(); + map.append(other); + assert!(map.is_empty(), 0); + }; + { + let map = new_from(vector[1, 2], vector[10, 20]); + let other = new(); + map.append(other); + assert!(map == new_from(vector[1, 2], vector[10, 20]), 1); + }; + { + let map = new(); + let other = new_from(vector[1, 2], vector[10, 20]); + map.append(other); + assert!(map == new_from(vector[1, 2], vector[10, 20]), 2); + }; + { + let map = new_from(vector[1, 2, 3], vector[10, 20, 30]); + let other = new_from(vector[4, 5], vector[40, 50]); + map.append(other); + assert!( + map == new_from(vector[1, 2, 3, 4, 5], vector[10, 20, 30, 40, 50]), + 3 + ); + }; + { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + let other = new_from(vector[2, 4], vector[20, 40]); + map.append(other); + assert!( + map == new_from(vector[1, 2, 3, 4, 5], vector[10, 20, 30, 40, 50]), + 4 + ); + }; + { + let map = new_from(vector[2, 4], vector[20, 40]); + let other = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.append(other); + assert!( + map == new_from(vector[1, 2, 3, 4, 5], vector[10, 20, 30, 40, 50]), + 6 + ); + }; + { + let map = new_from(vector[1], vector[10]); + let other = new_from(vector[1], vector[11]); + map.append(other); + assert!(map == new_from(vector[1], vector[11]), 7); + } + } + + #[test] + fun test_append_disjoint() { + let map = new_from(vector[1, 2, 3], vector[10, 20, 30]); + let other = new_from(vector[4, 5], vector[40, 50]); + map.append_disjoint(other); + assert!( + map == new_from(vector[1, 2, 3, 4, 5], vector[10, 20, 30, 40, 50]), + 1 + ); + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + /// EKEY_ALREADY_EXISTS + fun test_append_disjoint_abort() { + let map = new_from(vector[1], vector[10]); + let other = new_from(vector[1], vector[11]); + map.append_disjoint(other); + } + + #[test] + fun test_trim() { + let map = new_from(vector[1, 2, 3], vector[10, 20, 30]); + let rest = map.trim(2); + assert!(map == new_from(vector[1, 2], vector[10, 20]), 1); + assert!(rest == new_from(vector[3], vector[30]), 2); + } + + #[test] + fun test_non_iterator_ordering() { + let map = new_from(vector[1, 2, 3], vector[10, 20, 30]); + assert!(map.prev_key(&1).is_none(), 1); + assert!(map.next_key(&1) == option::some(2), 1); + + assert!(map.prev_key(&2) == option::some(1), 2); + assert!(map.next_key(&2) == option::some(3), 3); + + assert!(map.prev_key(&3) == option::some(2), 4); + assert!(map.next_key(&3).is_none(), 5); + + let (front_k, front_v) = map.borrow_front(); + assert!(front_k == &1, 6); + assert!(front_v == &10, 7); + + let (back_k, back_v) = map.borrow_back(); + assert!(back_k == &3, 8); + assert!(back_v == &30, 9); + + let (front_k, front_v) = map.pop_front(); + assert!(front_k == 1, 10); + assert!(front_v == 10, 11); + + let (back_k, back_v) = map.pop_back(); + assert!(back_k == 3, 12); + assert!(back_v == 30, 13); + } + + #[test] + fun test_replace_key_inplace() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.replace_key_inplace(&5, 6); + assert!( + map == new_from(vector[1, 3, 6], vector[10, 30, 50]), + 1 + ); + map.replace_key_inplace(&3, 4); + assert!( + map == new_from(vector[1, 4, 6], vector[10, 30, 50]), + 2 + ); + map.replace_key_inplace(&1, 0); + assert!( + map == new_from(vector[0, 4, 6], vector[10, 30, 50]), + 3 + ); + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_replace_key_inplace_not_found_1() { + let map = new_from(vector[1, 3, 6], vector[10, 30, 50]); + map.replace_key_inplace(&4, 5); + + } + + #[test] + #[expected_failure(abort_code = 0x10002, location = Self)] + /// EKEY_NOT_FOUND + fun test_replace_key_inplace_not_found_2() { + let map = new_from(vector[1, 3, 6], vector[10, 30, 50]); + map.replace_key_inplace(&7, 8); + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + fun test_replace_key_inplace_not_in_order_1() { + let map = new_from(vector[1, 3, 6], vector[10, 30, 50]); + map.replace_key_inplace(&3, 7); + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + fun test_replace_key_inplace_not_in_order_2() { + let map = new_from(vector[1, 3, 6], vector[10, 30, 50]); + map.replace_key_inplace(&1, 3); + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + fun test_replace_key_inplace_not_in_order_3() { + let map = new_from(vector[1, 3, 6], vector[10, 30, 50]); + map.replace_key_inplace(&6, 3); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_end_next_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_next(&map); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_end_borrow_key_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_borrow_key(&map); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_end_borrow_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_borrow(&map); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_end_borrow_mut_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_borrow_mut(&mut map); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_begin_prev_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_prev(&map); + } + + #[test] + public fun test_iter_is_begin_from_non_empty() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + let iter = map.new_begin_iter(); + assert!(iter.iter_is_begin(&map), 1); + assert!(iter.iter_is_begin_from_non_empty(), 1); + + iter = iter.iter_next(&map); + assert!(!iter.iter_is_begin(&map), 1); + assert!(!iter.iter_is_begin_from_non_empty(), 1); + + let map = new(); + let iter = map.new_begin_iter(); + assert!(iter.iter_is_begin(&map), 1); + assert!(!iter.iter_is_begin_from_non_empty(), 1); + } + + #[test] + public fun test_iter_remove() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_next(&map).iter_remove(&mut map); + assert!(map == new_from(vector[1, 5], vector[10, 50]), 1); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_remove_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_remove(&mut map); + } + + #[test] + public fun test_iter_replace() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_next(&map).iter_replace(&mut map, 35); + assert!( + map == new_from(vector[1, 3, 5], vector[10, 35, 50]), + 1 + ); + } + + #[test] + #[expected_failure(abort_code = 0x10003, location = Self)] + /// EITER_OUT_OF_BOUNDS + public fun test_iter_replace_abort() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_replace(&mut map, 35); + } + + #[test] + public fun test_iter_add() { + { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_add(&mut map, 0, 5); + assert!( + map == new_from(vector[0, 1, 3, 5], vector[5, 10, 30, 50]), + 1 + ); + }; + { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_next(&map).iter_add(&mut map, 2, 20); + assert!( + map == new_from(vector[1, 2, 3, 5], vector[10, 20, 30, 50]), + 2 + ); + }; + { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_add(&mut map, 6, 60); + assert!( + map == new_from(vector[1, 3, 5, 6], vector[10, 30, 50, 60]), + 3 + ); + }; + { + let map = new(); + map.new_end_iter().iter_add(&mut map, 1, 10); + assert!(map == new_from(vector[1], vector[10]), 4); + }; + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + public fun test_iter_add_abort_1() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_add(&mut map, 1, 5); + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + public fun test_iter_add_abort_2() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_end_iter().iter_add(&mut map, 5, 55); + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + public fun test_iter_add_abort_3() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_next(&map).iter_add(&mut map, 1, 15); + } + + #[test] + #[expected_failure(abort_code = 0x10004, location = Self)] + /// ENEW_KEY_NOT_IN_ORDER + public fun test_iter_add_abort_4() { + let map = new_from(vector[1, 3, 5], vector[10, 30, 50]); + map.new_begin_iter().iter_next(&map).iter_add(&mut map, 3, 25); + } + + #[test_only] + public fun large_dataset(): vector { + vector[ + 383, 886, 777, 915, 793, 335, 386, 492, 649, 421, 362, 27, 690, 59, 763, 926, + 540, 426, 172, 736, 211, 368, 567, 429, 782, 530, 862, 123, 67, 135, 929, 802, + 22, 58, 69, 167, 393, 456, 11, 42, 229, 373, 421, 919, 784, 537, 198, 324, 315, + 370, 413, 526, 91, 980, 956, 873, 862, 170, 996, 281, 305, 925, 84, 327, 336, + 505, 846, 729, 313, 857, 124, 895, 582, 545, 814, 367, 434, 364, 43, 750, 87, + 808, 276, 178, 788, 584, 403, 651, 754, 399, 932, 60, 676, 368, 739, 12, 226, + 586, 94, 539, 795, 570, 434, 378, 467, 601, 97, 902, 317, 492, 652, 756, 301, + 280, 286, 441, 865, 689, 444, 619, 440, 729, 31, 117, 97, 771, 481, 675, 709, + 927, 567, 856, 497, 353, 586, 965, 306, 683, 219, 624, 528, 871, 732, 829, 503, + 19, 270, 368, 708, 715, 340, 149, 796, 723, 618, 245, 846, 451, 921, 555, 379, + 488, 764, 228, 841, 350, 193, 500, 34, 764, 124, 914, 987, 856, 743, 491, 227, + 365, 859, 936, 432, 551, 437, 228, 275, 407, 474, 121, 858, 395, 29, 237, 235, + 793, 818, 428, 143, 11, 928, 529 + ] + } + + #[test_only] + public fun large_dataset_shuffled(): vector { + vector[ + 895, 228, 530, 784, 624, 335, 729, 818, 373, 456, 914, 226, 368, 750, 428, 956, + 437, 586, 763, 235, 567, 91, 829, 690, 434, 178, 584, 426, 228, 407, 237, 497, + 764, 135, 124, 421, 537, 270, 11, 367, 378, 856, 529, 276, 729, 618, 929, 227, + 149, 788, 925, 675, 121, 795, 306, 198, 421, 350, 555, 441, 403, 932, 368, 383, + 928, 841, 440, 771, 364, 902, 301, 987, 467, 873, 921, 11, 365, 340, 739, 492, + 540, 386, 919, 723, 539, 87, 12, 782, 324, 862, 689, 395, 488, 793, 709, 505, + 582, 814, 245, 980, 936, 736, 619, 69, 370, 545, 764, 886, 305, 551, 19, 865, + 229, 432, 29, 754, 34, 676, 43, 846, 451, 491, 871, 500, 915, 708, 586, 60, + 280, 652, 327, 172, 856, 481, 796, 474, 219, 651, 170, 281, 84, 97, 715, 857, + 353, 862, 393, 567, 368, 777, 97, 315, 526, 94, 31, 167, 123, 413, 503, 193, + 808, 649, 143, 42, 444, 317, 67, 926, 434, 211, 379, 570, 683, 965, 732, 927, + 429, 859, 313, 528, 996, 117, 492, 336, 22, 399, 275, 802, 743, 124, 846, 58, + 858, 286, 756, 601, 27, 59, 362, 793 + ] + } + + #[test] + fun test_map_large() { + let map = new(); + let data = large_dataset(); + let shuffled_data = large_dataset_shuffled(); + + let len = data.length(); + for (i in 0..len) { + let element = *data.borrow(i); + map.upsert(element, element); + map.validate_map(); + }; + + for (i in 0..len) { + let element = shuffled_data.borrow(i); + let it = map.find(element); + assert!(!it.iter_is_end(&map), 6); + assert!(it.iter_borrow_key(&map) == element, 7); + + let it_next = it.iter_next(&map); + let it_after = map.lower_bound(&(*element + 1)); + + assert!(it_next == it_after, 8); + }; + + let removed = vector::empty(); + for (i in 0..len) { + let element = shuffled_data.borrow(i); + if (!removed.contains(element)) { + removed.push_back(*element); + map.remove(element); + map.validate_map(); + } else { + assert!(!map.contains(element)); + }; + }; + + map.destroy_empty(); + } +} diff --git a/precompile/modules/initia_stdlib/sources/permissioned_signer.move b/precompile/modules/initia_stdlib/sources/permissioned_signer.move new file mode 100644 index 00000000..ab35d84b --- /dev/null +++ b/precompile/modules/initia_stdlib/sources/permissioned_signer.move @@ -0,0 +1,667 @@ +/// A _permissioned signer_ consists of a pair of the original signer and a generated +/// address which is used to store information about associated permissions. +/// +/// A permissioned signer is a restricted version of a signer. Functions `move_to` and +/// `address_of` behave the same, and can be passed wherever signer is needed. However, +/// code can internally query for the permissions to assert additional restrictions on +/// the use of the signer. +/// +/// A client which is interested in restricting access granted via a signer can create a permissioned signer +/// and pass on to other existing code without changes to existing APIs. Core functions in the framework, for +/// example account functions, can then assert availability of permissions, effectively restricting +/// existing code in a compatible way. +/// +/// After introducing the core functionality, examples are provided for withdraw limit on accounts, and +/// for blind signing. +module initia_std::permissioned_signer { + use std::signer; + use std::error; + use std::vector; + use std::option::{Option, Self}; + use std::copyable_any::{Self, Any}; + use initia_std::big_ordered_map::{Self, BigOrderedMap}; + use initia_std::account::create_signer; + use initia_std::transaction_context::generate_unique_address; + use initia_std::timestamp; + + /// Trying to grant permission using non-master signer. + const ENOT_MASTER_SIGNER: u64 = 1; + + /// Cannot authorize a permission. + const ECANNOT_AUTHORIZE: u64 = 2; + + /// Access permission information from a master signer. + const ENOT_PERMISSIONED_SIGNER: u64 = 3; + + /// signer doesn't have enough capacity to extract permission. + const ECANNOT_EXTRACT_PERMISSION: u64 = 4; + + /// permission handle has expired. + const E_PERMISSION_EXPIRED: u64 = 5; + + /// storing extracted permission into a different signer. + const E_PERMISSION_MISMATCH: u64 = 6; + + /// permission handle has been revoked by the original signer. + const E_PERMISSION_REVOKED: u64 = 7; + + /// destroying permission handle that has already been revoked or not owned by the + /// given master signer. + const E_NOT_ACTIVE: u64 = 8; + + /// Permissioned signer feature is not activated. + const EPERMISSION_SIGNER_DISABLED: u64 = 9; + + const U256_MAX: u256 = + 115792089237316195423570985008687907853269984665640564039457584007913129639935; + + /// If a permissioned signer has this permission, it would be able to revoke other granted + /// permission handles in the same signer. + struct RevokePermissionHandlePermission has copy, store, drop {} + + /// Stores the list of granted permission handles for a given account. + struct GrantedPermissionHandles has key { + /// Each address refers to a `permissions_storage_addr` that stores the `PermissionStorage`. + active_handles: vector
+ } + + /// A ephermeral permission handle that can be used to generate a permissioned signer with permission + /// configuration stored within. + enum PermissionedHandle { + V1 { + /// Address of the signer that creates this handle. + master_account_addr: address, + /// Address that stores `PermissionStorage`. + permissions_storage_addr: address + } + } + + /// A permission handle that can be used to generate a permissioned signer. + /// + /// This handle is storable and thus should be treated very carefully as it serves similar functionality + /// as signer delegation. + enum StorablePermissionedHandle has store { + V1 { + /// Address of the signer that creates this handle. + master_account_addr: address, + /// Address that stores `PermissionStorage`. + permissions_storage_addr: address, + /// Permissioned signer can no longer be generated from this handle after `expiration_time`. + expiration_time: u64 + } + } + + /// The actual permission configuration stored on-chain. + /// + /// The address that holds `PermissionStorage` will be generated freshly every time a permission + /// handle gets created. + enum PermissionStorage has key { + V1 { + /// A hetherogenous map from `Permission` structs defined by each different modules to + /// its permission capacity. + perms: BigOrderedMap + } + } + + /// Types of permission capacity stored on chain. + enum StoredPermission has store, copy, drop { + /// Unlimited capacity. + Unlimited, + /// Fixed capacity, will be deducted when permission is used. + Capacity(u256) + } + + /// Create an ephermeral permission handle based on the master signer. + /// + /// This handle can be used to derive a signer that can be used in the context of + /// the current transaction. + public fun create_permissioned_handle(master: &signer): PermissionedHandle { + assert_master_signer(master); + let permissions_storage_addr = generate_unique_address(); + let master_account_addr = signer::address_of(master); + + initialize_permission_address(permissions_storage_addr); + + PermissionedHandle::V1 { master_account_addr, permissions_storage_addr } + } + + /// Destroys an ephermeral permission handle. Clean up the permission stored in that handle + public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermissionStorage { + let PermissionedHandle::V1 { master_account_addr: _, permissions_storage_addr } = + p; + destroy_permissions_storage_address(permissions_storage_addr); + } + + /// Generate the permissioned signer based on the ephermeral permission handle. + /// + /// This signer can be used as a regular signer for other smart contracts. However when such + /// signer interacts with various framework functions, it would subject to permission checks + /// and would abort if check fails. + public fun signer_from_permissioned_handle(p: &PermissionedHandle): signer { + signer_from_permissioned_handle_impl( + p.master_account_addr, p.permissions_storage_addr + ) + } + + /// Returns true if `s` is a permissioned signer. + public fun is_permissioned_signer(s: &signer): bool { + // When the permissioned signer is disabled, no one is able to construct a permissioned + // signer. Thus we should return false here, as other on chain permission checks will + // depend on this checks. + is_permissioned_signer_impl(s) + } + + /// Grant the permissioned signer the permission to revoke granted permission handles under + /// its address. + public fun grant_revoke_permission( + master: &signer, permissioned: &signer + ) acquires PermissionStorage { + authorize_unlimited(master, permissioned, RevokePermissionHandlePermission {}); + } + + /// Revoke a specific storable permission handle immediately. This will disallow owner of + /// the storable permission handle to derive signer from it anymore. + public entry fun revoke_permission_storage_address( + s: &signer, permissions_storage_addr: address + ) acquires GrantedPermissionHandles, PermissionStorage { + assert!( + check_permission_exists(s, RevokePermissionHandlePermission {}), + error::permission_denied(ENOT_MASTER_SIGNER) + ); + let master_account_addr = signer::address_of(s); + + assert!( + exists(master_account_addr), + error::permission_denied(E_PERMISSION_REVOKED) + ); + let active_handles = + &mut GrantedPermissionHandles[master_account_addr].active_handles; + let (found, idx) = active_handles.index_of(&permissions_storage_addr); + + // The address has to be in the activated list in the master account address. + assert!(found, error::permission_denied(E_NOT_ACTIVE)); + active_handles.swap_remove(idx); + destroy_permissions_storage_address(permissions_storage_addr); + } + + /// Revoke all storable permission handle of the signer immediately. + public entry fun revoke_all_handles( + s: &signer + ) acquires GrantedPermissionHandles, PermissionStorage { + assert!( + check_permission_exists(s, RevokePermissionHandlePermission {}), + error::permission_denied(ENOT_MASTER_SIGNER) + ); + let master_account_addr = signer::address_of(s); + if (!exists(master_account_addr)) { return }; + + let granted_permissions = + borrow_global_mut(master_account_addr); + let delete_list = vector::trim_reverse( + &mut granted_permissions.active_handles, 0 + ); + vector::destroy( + delete_list, + |address| { + destroy_permissions_storage_address(address); + } + ) + } + + /// initialize permission storage by putting an empty storage under the address. + inline fun initialize_permission_address( + permissions_storage_addr: address + ) { + move_to( + &create_signer(permissions_storage_addr), + // Each key is ~100bytes, the value is 12 bytes. + PermissionStorage::V1 { + perms: big_ordered_map::new_with_config(40, 35, false) + } + ); + } + + /// Create an storable permission handle based on the master signer. + /// + /// This handle can be used to derive a signer that can be stored by a smart contract. + /// This is as dangerous as key delegation, thus it remains public(package) for now. + /// + /// The caller should check if `expiration_time` is not too far in the future. + public(package) fun create_storable_permissioned_handle( + master: &signer, expiration_time: u64 + ): StorablePermissionedHandle acquires GrantedPermissionHandles { + assert_master_signer(master); + let permissions_storage_addr = generate_unique_address(); + let master_account_addr = signer::address_of(master); + + assert!( + timestamp::now_seconds() < expiration_time, + error::permission_denied(E_PERMISSION_EXPIRED) + ); + + if (!exists(master_account_addr)) { + move_to( + master, GrantedPermissionHandles { active_handles: vector::empty() } + ); + }; + + GrantedPermissionHandles[master_account_addr].active_handles.push_back( + permissions_storage_addr + ); + + initialize_permission_address(permissions_storage_addr); + + StorablePermissionedHandle::V1 { + master_account_addr, + permissions_storage_addr, + expiration_time + } + } + + /// Destroys a storable permission handle. Clean up the permission stored in that handle + public(package) fun destroy_storable_permissioned_handle( + p: StorablePermissionedHandle + ) acquires PermissionStorage, GrantedPermissionHandles { + let StorablePermissionedHandle::V1 { + master_account_addr, + permissions_storage_addr, + expiration_time: _ + } = p; + + assert!( + exists(master_account_addr), + error::permission_denied(E_PERMISSION_REVOKED) + ); + let active_handles = + &mut GrantedPermissionHandles[master_account_addr].active_handles; + + let (found, idx) = active_handles.index_of(&permissions_storage_addr); + + // Removing the address from the active handle list if it's still active. + if (found) { + active_handles.swap_remove(idx); + }; + + destroy_permissions_storage_address(permissions_storage_addr); + } + + inline fun destroy_permissions_storage_address( + permissions_storage_addr: address + ) acquires PermissionStorage { + if (exists(permissions_storage_addr)) { + let PermissionStorage::V1 { perms } = + move_from(permissions_storage_addr); + big_ordered_map::destroy(perms, |_dv| {}); + } + } + + /// Generate the permissioned signer based on the storable permission handle. + public(package) fun signer_from_storable_permissioned_handle( + p: &StorablePermissionedHandle + ): signer { + assert!( + timestamp::now_seconds() < p.expiration_time, + error::permission_denied(E_PERMISSION_EXPIRED) + ); + assert!( + exists(p.permissions_storage_addr), + error::permission_denied(E_PERMISSION_REVOKED) + ); + signer_from_permissioned_handle_impl( + p.master_account_addr, p.permissions_storage_addr + ) + } + + /// Return the permission handle address so that it could be used for revocation purpose. + public(package) fun permissions_storage_address( + p: &StorablePermissionedHandle + ): address { + p.permissions_storage_addr + } + + /// Helper function that would abort if the signer passed in is a permissioned signer. + public(package) fun assert_master_signer(s: &signer) { + assert!( + !is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER) + ); + } + + /// ===================================================================================================== + /// StoredPermission operations + /// + /// check if StoredPermission has at least `threshold` capacity. + fun is_above(perm: &StoredPermission, threshold: u256): bool { + match(perm) { + StoredPermission::Capacity(capacity) => *capacity >= threshold, + StoredPermission::Unlimited => true + } + } + + /// consume `threshold` capacity from StoredPermission + fun consume_capacity(perm: &mut StoredPermission, threshold: u256): bool { + match(perm) { + StoredPermission::Capacity(current_capacity) => { + if (*current_capacity >= threshold) { + *current_capacity = *current_capacity - threshold; + true + } else { false } + } + StoredPermission::Unlimited => true + } + } + + /// increase `threshold` capacity from StoredPermission + fun increase_capacity(perm: &mut StoredPermission, threshold: u256) { + match(perm) { + StoredPermission::Capacity(current_capacity) => { + *current_capacity = *current_capacity + threshold; + } + StoredPermission::Unlimited => () + } + } + + /// merge the two stored permission + fun merge(lhs: &mut StoredPermission, rhs: StoredPermission) { + match(rhs) { + StoredPermission::Capacity(new_capacity) => { + match(lhs) { + StoredPermission::Capacity(current_capacity) => { + *current_capacity = *current_capacity + new_capacity; + } + StoredPermission::Unlimited => () + } + } + StoredPermission::Unlimited => *lhs = StoredPermission::Unlimited + } + } + + /// ===================================================================================================== + /// Permission Management + /// + /// Authorizes `permissioned` with the given permission. This requires to have access to the `master` + /// signer. + + inline fun map_or( + permissioned: &signer, + perm: PermKey, + mutate: |&mut StoredPermission| T, + default: T + ): T { + let permission_signer_addr = permission_address(permissioned); + assert!( + exists(permission_signer_addr), + error::permission_denied(E_NOT_ACTIVE) + ); + let perms = + &mut borrow_global_mut(permission_signer_addr).perms; + let key = copyable_any::pack(perm); + if (big_ordered_map::contains(perms, &key)) { + let value = perms.remove(&key); + let return_ = mutate(&mut value); + perms.add(key, value); + return_ + } else { + default + } + } + + inline fun insert_or( + permissioned: &signer, + perm: PermKey, + mutate: |&mut StoredPermission|, + default: StoredPermission + ) { + let permission_signer_addr = permission_address(permissioned); + assert!( + exists(permission_signer_addr), + error::permission_denied(E_NOT_ACTIVE) + ); + let perms = + &mut borrow_global_mut(permission_signer_addr).perms; + let key = copyable_any::pack(perm); + if (perms.contains(&key)) { + let value = perms.remove(&key); + mutate(&mut value); + perms.add(key, value); + } else { + perms.add(key, default); + } + } + + /// Authorizes `permissioned` with a given capacity and increment the existing capacity if present. + /// + /// Consumption using `check_permission_consume` will deduct the capacity. + public(package) fun authorize_increase( + master: &signer, + permissioned: &signer, + capacity: u256, + perm: PermKey + ) acquires PermissionStorage { + assert!( + is_permissioned_signer(permissioned) + && !is_permissioned_signer(master) + && signer::address_of(master) == signer::address_of(permissioned), + error::permission_denied(ECANNOT_AUTHORIZE) + ); + insert_or( + permissioned, + perm, + |stored_permission| { + increase_capacity(stored_permission, capacity); + }, + StoredPermission::Capacity(capacity) + ) + } + + /// Authorizes `permissioned` with the given unlimited permission. + /// Unlimited permission can be consumed however many times. + public(package) fun authorize_unlimited( + master: &signer, permissioned: &signer, perm: PermKey + ) acquires PermissionStorage { + assert!( + is_permissioned_signer(permissioned) + && !is_permissioned_signer(master) + && signer::address_of(master) == signer::address_of(permissioned), + error::permission_denied(ECANNOT_AUTHORIZE) + ); + insert_or( + permissioned, + perm, + |stored_permission| { + *stored_permission = StoredPermission::Unlimited; + }, + StoredPermission::Unlimited + ) + } + + /// Grant an unlimited permission to a permissioned signer **without** master signer's approvoal. + public(package) fun grant_unlimited_with_permissioned_signer( + permissioned: &signer, perm: PermKey + ) acquires PermissionStorage { + if (!is_permissioned_signer(permissioned)) { + return; + }; + insert_or( + permissioned, + perm, + |stored_permission| { + *stored_permission = StoredPermission::Unlimited; + }, + StoredPermission::Unlimited + ) + } + + /// Increase the `capacity` of a permissioned signer **without** master signer's approvoal. + /// + /// The caller of the module will need to make sure the witness type `PermKey` can only be + /// constructed within its own module, otherwise attackers can refill the permission for itself + /// to bypass the checks. + public(package) fun increase_limit( + permissioned: &signer, capacity: u256, perm: PermKey + ) acquires PermissionStorage { + if (!is_permissioned_signer(permissioned)) { + return; + }; + insert_or( + permissioned, + perm, + |stored_permission| { + increase_capacity(stored_permission, capacity); + }, + StoredPermission::Capacity(capacity) + ) + } + + public(package) fun check_permission_exists( + s: &signer, perm: PermKey + ): bool acquires PermissionStorage { + // 0 capacity permissions will be treated as non-existant. + check_permission_capacity_above(s, 1, perm) + } + + public(package) fun check_permission_capacity_above( + s: &signer, threshold: u256, perm: PermKey + ): bool acquires PermissionStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + map_or( + s, + perm, + |stored_permission| { is_above(stored_permission, threshold) }, + false + ) + } + + public(package) fun check_permission_consume( + s: &signer, threshold: u256, perm: PermKey + ): bool acquires PermissionStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + map_or( + s, + perm, + |stored_permission| { consume_capacity(stored_permission, threshold) }, + false + ) + } + + public(package) fun capacity( + s: &signer, perm: PermKey + ): Option acquires PermissionStorage { + if (!is_permissioned_signer(s)) { + return option::some(U256_MAX) + }; + map_or( + s, + perm, + |stored_permission: &mut StoredPermission| { + option::some( + match(stored_permission) { + StoredPermission::Capacity(capacity) => *capacity, + StoredPermission::Unlimited => U256_MAX + } + ) + }, + option::none() + ) + } + + public(package) fun revoke_permission( + permissioned: &signer, perm: PermKey + ) acquires PermissionStorage { + if (!is_permissioned_signer(permissioned)) { + // Master signer has no permissions associated with it. + return + }; + let addr = permission_address(permissioned); + if (!exists(addr)) { return }; + let perm_storage = &mut PermissionStorage[addr].perms; + let key = copyable_any::pack(perm); + if (perm_storage.contains(&key)) { + perm_storage.remove(&key); + } + } + + /// Unused function. Keeping it for compatibility purpose. + public fun address_of(_s: &signer): address { + abort error::permission_denied(EPERMISSION_SIGNER_DISABLED) + } + + /// Unused function. Keeping it for compatibility purpose. + public fun borrow_address(_s: &signer): &address { + abort error::permission_denied(EPERMISSION_SIGNER_DISABLED) + } + + // ===================================================================================================== + // Native Functions + /// + /// Check whether this is a permissioned signer. + native fun is_permissioned_signer_impl(s: &signer): bool; + /// Return the address used for storing permissions. Aborts if not a permissioned signer. + native fun permission_address(permissioned: &signer): address; + /// Creates a permissioned signer from an existing universal signer. The function aborts if the + /// given signer is already a permissioned signer. + /// + /// The implementation of this function requires to extend the value representation for signers in the VM. + /// invariants: + /// signer::address_of(master) == signer::address_of(signer_from_permissioned_handle(create_permissioned_handle(master))), + /// + native fun signer_from_permissioned_handle_impl( + master_account_addr: address, permissions_storage_addr: address + ): signer; + + #[test(creator = @0xcafe)] + fun signer_address_roundtrip( + creator: &signer + ) acquires PermissionStorage, GrantedPermissionHandles { + let aptos_framework = create_signer(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let handle = create_permissioned_handle(creator); + let perm_signer = signer_from_permissioned_handle(&handle); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + assert!( + permission_address(&perm_signer) == handle.permissions_storage_addr, + 1 + ); + assert!(exists(handle.permissions_storage_addr), 1); + + destroy_permissioned_handle(handle); + + let handle = create_storable_permissioned_handle(creator, 60); + let perm_signer = signer_from_storable_permissioned_handle(&handle); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + assert!( + permission_address(&perm_signer) == handle.permissions_storage_addr, + 1 + ); + assert!(exists(handle.permissions_storage_addr), 1); + + destroy_storable_permissioned_handle(handle); + } + + #[test_only] + use initia_std::bcs; + + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x1C5, location = initia_std::bcs)] + fun signer_serialization(creator: &signer) acquires PermissionStorage { + let aptos_framework = create_signer(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let handle = create_permissioned_handle(creator); + let perm_signer = signer_from_permissioned_handle(&handle); + + assert!( + bcs::to_bytes(creator) == bcs::to_bytes(&signer::address_of(creator)), + 1 + ); + bcs::to_bytes(&perm_signer); + + destroy_permissioned_handle(handle); + } +} diff --git a/precompile/modules/initia_stdlib/sources/simple_map.move b/precompile/modules/initia_stdlib/sources/simple_map.move index 1c84d2a4..9f054f51 100644 --- a/precompile/modules/initia_stdlib/sources/simple_map.move +++ b/precompile/modules/initia_stdlib/sources/simple_map.move @@ -4,6 +4,9 @@ /// 3) A Key can be found within O(N) time /// 4) The keys are unsorted. /// 5) Adds and removals take O(N) time +/// +/// DEPRECATED: since it's implementation is inneficient, it +/// has been deprecated in favor of `ordered_map.move`. module initia_std::simple_map { use std::error; use std::option; @@ -14,6 +17,8 @@ module initia_std::simple_map { /// Map key is not found const EKEY_NOT_FOUND: u64 = 2; + /// DEPRECATED: since it's implementation is inneficient, it + /// has been deprecated in favor of `ordered_map.move`. struct SimpleMap has copy, drop, store { data: vector> } @@ -26,7 +31,7 @@ module initia_std::simple_map { public fun length( self: &SimpleMap ): u64 { - vector::length(&self.data) + self.data.length() } /// Create an empty SimpleMap. @@ -39,7 +44,7 @@ module initia_std::simple_map { keys: vector, values: vector ): SimpleMap { let map = new(); - add_all(&mut map, keys, values); + map.add_all(keys, values); map } @@ -53,33 +58,33 @@ module initia_std::simple_map { public fun borrow( self: &SimpleMap, key: &Key ): &Value { - let maybe_idx = find(self, key); - assert!(option::is_some(&maybe_idx), error::invalid_argument(EKEY_NOT_FOUND)); - let idx = option::extract(&mut maybe_idx); - &vector::borrow(&self.data, idx).value + let maybe_idx = self.find(key); + assert!(maybe_idx.is_some(), error::invalid_argument(EKEY_NOT_FOUND)); + let idx = maybe_idx.extract(); + &self.data.borrow(idx).value } public fun borrow_mut( self: &mut SimpleMap, key: &Key ): &mut Value { - let maybe_idx = find(self, key); - assert!(option::is_some(&maybe_idx), error::invalid_argument(EKEY_NOT_FOUND)); - let idx = option::extract(&mut maybe_idx); - &mut vector::borrow_mut(&mut self.data, idx).value + let maybe_idx = self.find(key); + assert!(maybe_idx.is_some(), error::invalid_argument(EKEY_NOT_FOUND)); + let idx = maybe_idx.extract(); + &mut self.data.borrow_mut(idx).value } public fun contains_key( self: &SimpleMap, key: &Key ): bool { - let maybe_idx = find(self, key); - option::is_some(&maybe_idx) + let maybe_idx = self.find(key); + maybe_idx.is_some() } public fun destroy_empty( self: SimpleMap ) { let SimpleMap { data } = self; - vector::destroy_empty(data); + data.destroy_empty(); } /// Add a key/value pair to the map. The key must not already exist. @@ -88,12 +93,10 @@ module initia_std::simple_map { key: Key, value: Value ) { - let maybe_idx = find(self, &key); - assert!( - option::is_none(&maybe_idx), error::invalid_argument(EKEY_ALREADY_EXISTS) - ); + let maybe_idx = self.find(&key); + assert!(maybe_idx.is_none(), error::invalid_argument(EKEY_ALREADY_EXISTS)); - vector::push_back(&mut self.data, Element { key, value }); + self.data.push_back(Element { key, value }); } /// Add multiple key/value pairs to the map. The keys must not already exist. @@ -102,11 +105,9 @@ module initia_std::simple_map { keys: vector, values: vector ) { - vector::zip( - keys, - values, - |key, value| { - add(self, key, value); + keys.zip( + values, |key, value| { + self.add(key, value); } ); } @@ -118,30 +119,28 @@ module initia_std::simple_map { value: Value ): (std::option::Option, std::option::Option) { let data = &mut self.data; - let len = vector::length(data); - let i = 0; - while (i < len) { - let element = vector::borrow(data, i); + let len = data.length(); + for (i in 0..len) { + let element = data.borrow(i); if (&element.key == &key) { - vector::push_back(data, Element { key, value }); - vector::swap(data, i, len); - let Element { key, value } = vector::pop_back(data); + data.push_back(Element { key, value }); + data.swap(i, len); + let Element { key, value } = data.pop_back(); return (std::option::some(key), std::option::some(value)) }; - i = i + 1; }; - vector::push_back(&mut self.data, Element { key, value }); + self.data.push_back(Element { key, value }); (std::option::none(), std::option::none()) } /// Return all keys in the map. This requires keys to be copyable. public fun keys(self: &SimpleMap): vector { - vector::map_ref(&self.data, |e| { e.key }) + self.data.map_ref(|e| { e.key }) } /// Return all values in the map. This requires values to be copyable. public fun values(self: &SimpleMap): vector { - vector::map_ref(&self.data, |e| { e.value }) + self.data.map_ref(|e| { e.value }) } /// Transform the map into two vectors with the keys and values respectively @@ -152,14 +151,11 @@ module initia_std::simple_map { let keys: vector = vector::empty(); let values: vector = vector::empty(); let SimpleMap { data } = self; - vector::for_each( - data, - |e| { - let Element { key, value } = e; - vector::push_back(&mut keys, key); - vector::push_back(&mut values, value); - } - ); + data.for_each(|e| { + let Element { key, value } = e; + keys.push_back(key); + values.push_back(value); + }); (keys, values) } @@ -170,33 +166,31 @@ module initia_std::simple_map { dk: |Key|, dv: |Value| ) { - let (keys, values) = to_vec_pair(self); - vector::destroy(keys, |_k| dk(_k)); - vector::destroy(values, |_v| dv(_v)); + let (keys, values) = self.to_vec_pair(); + keys.destroy(|_k| dk(_k)); + values.destroy(|_v| dv(_v)); } /// Remove a key/value pair from the map. The key must exist. public fun remove( self: &mut SimpleMap, key: &Key ): (Key, Value) { - let maybe_idx = find(self, key); - assert!(option::is_some(&maybe_idx), error::invalid_argument(EKEY_NOT_FOUND)); - let placement = option::extract(&mut maybe_idx); - let Element { key, value } = vector::swap_remove(&mut self.data, placement); + let maybe_idx = self.find(key); + assert!(maybe_idx.is_some(), error::invalid_argument(EKEY_NOT_FOUND)); + let placement = maybe_idx.extract(); + let Element { key, value } = self.data.swap_remove(placement); (key, value) } fun find( self: &SimpleMap, key: &Key ): option::Option { - let leng = vector::length(&self.data); - let i = 0; - while (i < leng) { - let element = vector::borrow(&self.data, i); + let len = self.data.length(); + for (i in 0..len) { + let element = self.data.borrow(i); if (&element.key == key) { return option::some(i) }; - i = i + 1; }; option::none() } @@ -205,115 +199,117 @@ module initia_std::simple_map { public fun test_add_remove_many() { let map = create(); - assert!(length(&map) == 0, 0); - assert!(!contains_key(&map, &3), 1); - add(&mut map, 3, 1); - assert!(length(&map) == 1, 2); - assert!(contains_key(&map, &3), 3); - assert!(borrow(&map, &3) == &1, 4); - *borrow_mut(&mut map, &3) = 2; - assert!(borrow(&map, &3) == &2, 5); - - assert!(!contains_key(&map, &2), 6); - add(&mut map, 2, 5); - assert!(length(&map) == 2, 7); - assert!(contains_key(&map, &2), 8); - assert!(borrow(&map, &2) == &5, 9); - *borrow_mut(&mut map, &2) = 9; - assert!(borrow(&map, &2) == &9, 10); - - remove(&mut map, &2); - assert!(length(&map) == 1, 11); - assert!(!contains_key(&map, &2), 12); - assert!(borrow(&map, &3) == &2, 13); - - remove(&mut map, &3); - assert!(length(&map) == 0, 14); - assert!(!contains_key(&map, &3), 15); - - destroy_empty(map); + assert!(map.length() == 0, 0); + assert!(!map.contains_key(&3), 1); + map.add(3, 1); + assert!(map.length() == 1, 2); + assert!(map.contains_key(&3), 3); + assert!(map.borrow(&3) == &1, 4); + *map.borrow_mut(&3) = 2; + assert!(map.borrow(&3) == &2, 5); + + assert!(!map.contains_key(&2), 6); + map.add(2, 5); + assert!(map.length() == 2, 7); + assert!(map.contains_key(&2), 8); + assert!(map.borrow(&2) == &5, 9); + *map.borrow_mut(&2) = 9; + assert!(map.borrow(&2) == &9, 10); + + map.remove(&2); + assert!(map.length() == 1, 11); + assert!(!map.contains_key(&2), 12); + assert!(map.borrow(&3) == &2, 13); + + map.remove(&3); + assert!(map.length() == 0, 14); + assert!(!map.contains_key(&3), 15); + + map.destroy_empty(); } #[test] public fun test_add_all() { let map = create(); - assert!(length(&map) == 0, 0); - add_all(&mut map, vector[1, 2, 3], vector[10, 20, 30]); - assert!(length(&map) == 3, 1); - assert!(borrow(&map, &1) == &10, 2); - assert!(borrow(&map, &2) == &20, 3); - assert!(borrow(&map, &3) == &30, 4); - - remove(&mut map, &1); - remove(&mut map, &2); - remove(&mut map, &3); - destroy_empty(map); + assert!(map.length() == 0, 0); + map.add_all( + vector[1, 2, 3], vector[10, 20, 30] + ); + assert!(map.length() == 3, 1); + assert!(map.borrow(&1) == &10, 2); + assert!(map.borrow(&2) == &20, 3); + assert!(map.borrow(&3) == &30, 4); + + map.remove(&1); + map.remove(&2); + map.remove(&3); + map.destroy_empty(); } #[test] public fun test_keys() { let map = create(); - assert!(keys(&map) == vector[], 0); - add(&mut map, 2, 1); - add(&mut map, 3, 1); + assert!(map.keys() == vector[], 0); + map.add(2, 1); + map.add(3, 1); - assert!(keys(&map) == vector[2, 3], 0); + assert!(map.keys() == vector[2, 3], 0); } #[test] public fun test_values() { let map = create(); - assert!(values(&map) == vector[], 0); - add(&mut map, 2, 1); - add(&mut map, 3, 2); + assert!(map.values() == vector[], 0); + map.add(2, 1); + map.add(3, 2); - assert!(values(&map) == vector[1, 2], 0); + assert!(map.values() == vector[1, 2], 0); } #[test] #[expected_failure] public fun test_add_twice() { let map = create(); - add(&mut map, 3, 1); - add(&mut map, 3, 1); + map.add(3, 1); + map.add(3, 1); - remove(&mut map, &3); - destroy_empty(map); + map.remove(&3); + map.destroy_empty(); } #[test] #[expected_failure] public fun test_remove_twice() { let map = create(); - add(&mut map, 3, 1); - remove(&mut map, &3); - remove(&mut map, &3); + map.add(3, 1); + map.remove(&3); + map.remove(&3); - destroy_empty(map); + map.destroy_empty(); } #[test] public fun test_upsert_test() { let map = create(); // test adding 3 elements using upsert - upsert(&mut map, 1, 1); - upsert(&mut map, 2, 2); - upsert(&mut map, 3, 3); - - assert!(length(&map) == 3, 0); - assert!(contains_key(&map, &1), 1); - assert!(contains_key(&map, &2), 2); - assert!(contains_key(&map, &3), 3); - assert!(borrow(&map, &1) == &1, 4); - assert!(borrow(&map, &2) == &2, 5); - assert!(borrow(&map, &3) == &3, 6); + map.upsert:: (1, 1); + map.upsert(2, 2); + map.upsert(3, 3); + + assert!(map.length() == 3, 0); + assert!(map.contains_key(&1), 1); + assert!(map.contains_key(&2), 2); + assert!(map.contains_key(&3), 3); + assert!(map.borrow(&1) == &1, 4); + assert!(map.borrow(&2) == &2, 5); + assert!(map.borrow(&3) == &3, 6); // change mapping 1->1 to 1->4 - upsert(&mut map, 1, 4); + map.upsert(1, 4); - assert!(length(&map) == 3, 7); - assert!(contains_key(&map, &1), 8); - assert!(borrow(&map, &1) == &4, 9); + assert!(map.length() == 3, 7); + assert!(map.contains_key(&1), 8); + assert!(map.borrow(&1) == &4, 9); } } diff --git a/precompile/modules/initia_stdlib/sources/storage_slots_allocator.move b/precompile/modules/initia_stdlib/sources/storage_slots_allocator.move new file mode 100644 index 00000000..d9b0600b --- /dev/null +++ b/precompile/modules/initia_stdlib/sources/storage_slots_allocator.move @@ -0,0 +1,263 @@ +/// Abstraction to having "addressable" storage slots (i.e. items) in global storage. +/// Addresses are local u64 values (unique within a single StorageSlotsAllocator instance, +/// but can and do overlap across instances). +/// +/// Allows optionally to initialize slots (and pay for them upfront), and then reuse them, +/// providing predictable storage costs. +/// +/// If we need to mutate multiple slots at the same time, we can workaround borrow_mut preventing us from that, +/// via provided pair of `remove_and_reserve` and `fill_reserved_slot` methods, to do so in non-conflicting manner. +/// +/// Similarly allows getting an address upfront via `reserve_slot`, for a slot created +/// later (i.e. if we need address to initialize the value itself). +/// +/// In the future, more sophisticated strategies can be added, without breaking/modifying callers, +/// for example: +/// * inlining some nodes +/// * having a fee-payer for any storage creation operations +module std::storage_slots_allocator { + use std::error; + use std::table_with_length::{Self, TableWithLength}; + use std::option::{Self, Option}; + + const EINVALID_ARGUMENT: u64 = 1; + const ECANNOT_HAVE_SPARES_WITHOUT_REUSE: u64 = 2; + const EINTERNAL_INVARIANT_BROKEN: u64 = 7; + + const NULL_INDEX: u64 = 0; + const FIRST_INDEX: u64 = 10; // keeping space for usecase-specific values + + /// Data stored in an individual slot + enum Link has store { + /// Variant that stores actual data + Occupied { + value: T + }, + /// Empty variant (that keeps storage item from being deleted) + /// and represents a node in a linked list of empty slots. + Vacant { + next: u64 + } + } + + enum StorageSlotsAllocator has store { + // V1 is sequential - any two operations on the StorageSlotsAllocator will conflict. + // In general, StorageSlotsAllocator is invoked on less frequent operations, so + // that shouldn't be a big issue. + V1 { + slots: Option>>, // Lazily create slots table only when needed + new_slot_index: u64, + should_reuse: bool, + reuse_head_index: u64, + reuse_spare_count: u32 + } + } + + /// Handle to a reserved slot within a transaction. + /// Not copy/drop/store-able, to guarantee reservation + /// is used or released within the transaction. + struct ReservedSlot { + slot_index: u64 + } + + /// Ownership handle to a slot. + /// Not copy/drop-able to make sure slots are released when not needed, + /// and there is unique owner for each slot. + struct StoredSlot has store { + slot_index: u64 + } + + public fun new(should_reuse: bool): StorageSlotsAllocator { + StorageSlotsAllocator::V1 { + slots: option::none(), + new_slot_index: FIRST_INDEX, + should_reuse, + reuse_head_index: NULL_INDEX, + reuse_spare_count: 0 + } + } + + public fun allocate_spare_slots( + self: &mut StorageSlotsAllocator, num_to_allocate: u64 + ) { + assert!( + self.should_reuse, + error::invalid_argument(ECANNOT_HAVE_SPARES_WITHOUT_REUSE) + ); + for (i in 0..num_to_allocate) { + let slot_index = self.next_slot_index(); + self.maybe_push_to_reuse_queue(slot_index); + }; + } + + public fun get_num_spare_slot_count( + self: &StorageSlotsAllocator + ): u32 { + assert!( + self.should_reuse, + error::invalid_argument(ECANNOT_HAVE_SPARES_WITHOUT_REUSE) + ); + self.reuse_spare_count + } + + public fun add(self: &mut StorageSlotsAllocator, val: T): StoredSlot { + let (stored_slot, reserved_slot) = self.reserve_slot(); + self.fill_reserved_slot(reserved_slot, val); + stored_slot + } + + public fun remove( + self: &mut StorageSlotsAllocator, slot: StoredSlot + ): T { + let (reserved_slot, value) = self.remove_and_reserve(slot.stored_to_index()); + self.free_reserved_slot(reserved_slot, slot); + value + } + + public fun destroy_empty(self: StorageSlotsAllocator) { + loop { + let reuse_index = self.maybe_pop_from_reuse_queue(); + if (reuse_index == NULL_INDEX) { + break; + }; + }; + match(self) { + V1 { + slots, + new_slot_index: _, + should_reuse: _, + reuse_head_index, + reuse_spare_count: _ + } => { + assert!(reuse_head_index == NULL_INDEX, EINTERNAL_INVARIANT_BROKEN); + if (slots.is_some()) { + slots.destroy_some().destroy_empty(); + } else { + slots.destroy_none(); + } + } + }; + } + + public fun borrow( + self: &StorageSlotsAllocator, slot_index: u64 + ): &T { + &self.slots.borrow().borrow(slot_index).value + } + + public fun borrow_mut( + self: &mut StorageSlotsAllocator, slot_index: u64 + ): &mut T { + &mut self.slots.borrow_mut().borrow_mut(slot_index).value + } + + // We also provide here operations where `add()` is split into `reserve_slot`, + // and then doing fill_reserved_slot later. + + // Similarly we have `remove_and_reserve`, and then `fill_reserved_slot` later. + + public fun reserve_slot( + self: &mut StorageSlotsAllocator + ): (StoredSlot, ReservedSlot) { + let slot_index = self.maybe_pop_from_reuse_queue(); + if (slot_index == NULL_INDEX) { + slot_index = self.next_slot_index(); + }; + + (StoredSlot { slot_index }, ReservedSlot { slot_index }) + } + + public fun fill_reserved_slot( + self: &mut StorageSlotsAllocator, slot: ReservedSlot, val: T + ) { + let ReservedSlot { slot_index } = slot; + self.add_link(slot_index, Link::Occupied { value: val }); + } + + /// Remove storage slot, but reserve it for later. + public fun remove_and_reserve( + self: &mut StorageSlotsAllocator, slot_index: u64 + ): (ReservedSlot, T) { + let Link::Occupied { value } = self.remove_link(slot_index); + (ReservedSlot { slot_index }, value) + } + + public fun free_reserved_slot( + self: &mut StorageSlotsAllocator, + reserved_slot: ReservedSlot, + stored_slot: StoredSlot + ) { + let ReservedSlot { slot_index } = reserved_slot; + assert!(slot_index == stored_slot.slot_index, EINVALID_ARGUMENT); + let StoredSlot { slot_index: _ } = stored_slot; + self.maybe_push_to_reuse_queue(slot_index); + } + + // ========== Section for methods handling references ======== + + public fun reserved_to_index(self: &ReservedSlot): u64 { + self.slot_index + } + + public fun stored_to_index(self: &StoredSlot): u64 { + self.slot_index + } + + public fun is_null_index(slot_index: u64): bool { + slot_index == NULL_INDEX + } + + public fun is_special_unused_index(slot_index: u64): bool { + slot_index != NULL_INDEX && slot_index < FIRST_INDEX + } + + // ========== Section for private internal utility methods ======== + + fun maybe_pop_from_reuse_queue( + self: &mut StorageSlotsAllocator + ): u64 { + let slot_index = self.reuse_head_index; + if (slot_index != NULL_INDEX) { + let Link::Vacant { next } = self.remove_link(slot_index); + self.reuse_head_index = next; + self.reuse_spare_count -= 1; + }; + slot_index + } + + fun maybe_push_to_reuse_queue( + self: &mut StorageSlotsAllocator, slot_index: u64 + ) { + if (self.should_reuse) { + let link = Link::Vacant { next: self.reuse_head_index }; + self.add_link(slot_index, link); + self.reuse_head_index = slot_index; + self.reuse_spare_count += 1; + }; + } + + fun next_slot_index(self: &mut StorageSlotsAllocator): u64 { + let slot_index = self.new_slot_index; + self.new_slot_index += 1; + if (self.slots.is_none()) { + self.slots.fill(table_with_length::new>()); + }; + slot_index + } + + fun add_link( + self: &mut StorageSlotsAllocator, slot_index: u64, link: Link + ) { + self.slots.borrow_mut().add(slot_index, link); + } + + fun remove_link( + self: &mut StorageSlotsAllocator, slot_index: u64 + ): Link { + self.slots.borrow_mut().remove(slot_index) + } + + spec module { + pragma verify = false; + } +} diff --git a/precompile/modules/initia_stdlib/sources/table.move b/precompile/modules/initia_stdlib/sources/table.move index 236a9c35..c16fadee 100644 --- a/precompile/modules/initia_stdlib/sources/table.move +++ b/precompile/modules/initia_stdlib/sources/table.move @@ -1,5 +1,8 @@ /// Type of large-scale storage tables. module initia_std::table { + friend initia_std::table_with_length; + friend initia_std::storage_slots_allocator; + use std::error; use std::account; use std::vector; @@ -31,34 +34,32 @@ module initia_std::table { } /// Destroy a table. The table must be empty to succeed. - public fun destroy_empty(table: Table) { + public fun destroy_empty(self: Table) { assert!( - table.length == 0, + self.length == 0, error::invalid_state(ENOT_EMPTY) ); - destroy_empty_box>(&table); - drop_unchecked_box>(table) + destroy_empty_box>(&self); + drop_unchecked_box>(self) } /// Return a table handle address. - public fun handle(table: &Table): address { - table.handle + public fun handle(self: &Table): address { + self.handle } /// Add a new entry to the table. Aborts if an entry for this /// key already exists. The entry itself is not stored in the /// table, and cannot be discovered from it. - public fun add( - table: &mut Table, key: K, val: V - ) { - add_box>(table, key, Box { val }); - table.length = table.length + 1 + public fun add(self: &mut Table, key: K, val: V) { + add_box>(self, key, Box { val }); + self.length = self.length + 1 } /// Acquire an immutable reference to the value which `key` maps to. /// Aborts if there is no entry for `key`. - public fun borrow(table: &Table, key: K): &V { - &borrow_box>(table, key).val + public fun borrow(self: &Table, key: K): &V { + &borrow_box>(self, key).val } /// Acquire an immutable reference to the value which `key` maps to. @@ -75,65 +76,65 @@ module initia_std::table { /// Acquire a mutable reference to the value which `key` maps to. /// Aborts if there is no entry for `key`. - public fun borrow_mut(table: &mut Table, key: K): &mut V { - &mut borrow_box_mut>(table, key).val + public fun borrow_mut(self: &mut Table, key: K): &mut V { + &mut borrow_box_mut>(self, key).val } /// Returns the length of the table, i.e. the number of entries. - public fun length(table: &Table): u64 { - table.length + public fun length(self: &Table): u64 { + self.length } /// Returns true if this table is empty. - public fun empty(table: &Table): bool { - table.length == 0 + public fun empty(self: &Table): bool { + self.length == 0 } /// Acquire a mutable reference to the value which `key` maps to. /// Insert the pair (`key`, `default`) first if there is no entry for `key`. public fun borrow_mut_with_default( - table: &mut Table, + self: &mut Table, key: K, default: V ): &mut V { - if (!contains(table, copy key)) { - add(table, copy key, default) + if (!contains(self, copy key)) { + add(self, copy key, default) }; - borrow_mut(table, key) + borrow_mut(self, key) } /// Insert the pair (`key`, `value`) if there is no entry for `key`. /// update the value of the entry for `key` to `value` otherwise public fun upsert( - table: &mut Table, + self: &mut Table, key: K, value: V ) { - if (!contains(table, copy key)) { - add(table, copy key, value) + if (!contains(self, copy key)) { + add(self, copy key, value) } else { - let ref = borrow_mut(table, key); + let ref = borrow_mut(self, key); *ref = value; }; } /// Remove from `table` and return the value which `key` maps to. /// Aborts if there is no entry for `key`. - public fun remove(table: &mut Table, key: K): V { - let Box { val } = remove_box>(table, key); - table.length = table.length - 1; + public fun remove(self: &mut Table, key: K): V { + let Box { val } = remove_box>(self, key); + self.length = self.length - 1; val } /// Returns true iff `table` contains an entry for `key`. - public fun contains(table: &Table, key: K): bool { - contains_box>(table, key) + public fun contains(self: &Table, key: K): bool { + contains_box>(self, key) } #[test_only] /// Testing only: allows to drop a table even if it is not empty. - public fun drop_unchecked(table: Table) { - drop_unchecked_box>(table) + public fun drop_unchecked(self: Table) { + drop_unchecked_box>(self) } /// Create iterator for `table`. @@ -154,7 +155,7 @@ module initia_std::table { /// functions to obtain the Big Endian key bytes of a number. /// public fun iter( - table: &Table, + self: &Table, start: Option, /* inclusive */ end: Option, /* exclusive */ order: u8 /* 1: Ascending, 2: Descending */ @@ -173,7 +174,7 @@ module initia_std::table { vector::empty() }; - new_table_iter>(table, start_bytes, end_bytes, order) + new_table_iter>(self, start_bytes, end_bytes, order) } public fun prepare(table_iter: &TableIter): bool { @@ -203,7 +204,7 @@ module initia_std::table { /// functions to obtain the Big Endian key bytes of a number. /// public fun iter_mut( - table: &mut Table, + self: &mut Table, start: Option, /* inclusive */ end: Option, /* exclusive */ order: u8 /* 1: Ascending, 2: Descending */ @@ -222,7 +223,7 @@ module initia_std::table { vector::empty() }; - new_table_iter_mut>(table, start_bytes, end_bytes, order) + new_table_iter_mut>(self, start_bytes, end_bytes, order) } public fun prepare_mut( @@ -237,10 +238,10 @@ module initia_std::table { } public fun to_simple_map( - table: &Table + self: &Table ): std::simple_map::SimpleMap { let map = std::simple_map::new(); - let iter = iter(table, option::none(), option::none(), 1); + let iter = iter(self, option::none(), option::none(), 1); while (prepare(iter)) { let (key, value) = next(iter); std::simple_map::add(&mut map, key, *value); @@ -249,6 +250,15 @@ module initia_std::table { map } + /// Table cannot know if it is empty or not, so this method is not public, + /// and can be used only in modules that know by themselves that table is empty. + friend fun destroy_known_empty_unsafe( + self: Table + ) { + destroy_empty_box>(&self); + drop_unchecked_box>(self) + } + // ====================================================================================================== // Internal API @@ -262,34 +272,34 @@ module initia_std::table { native fun new_table_handle(): address; native fun add_box( - table: &mut Table, key: K, val: Box + self: &mut Table, key: K, val: Box ); - native fun borrow_box(table: &Table, key: K): &Box; + native fun borrow_box(self: &Table, key: K): &Box; native fun borrow_box_mut( - table: &mut Table, key: K + self: &mut Table, key: K ): &mut Box; - native fun contains_box(table: &Table, key: K): bool; + native fun contains_box(self: &Table, key: K): bool; native fun remove_box( - table: &mut Table, key: K + self: &mut Table, key: K ): Box; - native fun destroy_empty_box(table: &Table); + native fun destroy_empty_box(self: &Table); - native fun drop_unchecked_box(table: Table); + native fun drop_unchecked_box(self: Table); native fun new_table_iter( - table: &Table, + self: &Table, start: vector, end: vector, order: u8 ): &TableIter; native fun new_table_iter_mut( - table: &mut Table, + self: &mut Table, start: vector, end: vector, order: u8 diff --git a/precompile/modules/initia_stdlib/sources/table_with_length.move b/precompile/modules/initia_stdlib/sources/table_with_length.move new file mode 100644 index 00000000..70cba910 --- /dev/null +++ b/precompile/modules/initia_stdlib/sources/table_with_length.move @@ -0,0 +1,155 @@ +/// Extends Table and provides functions such as length and the ability to be destroyed + +module initia_std::table_with_length { + use std::error; + use std::table::{Self, Table}; + + // native code raises this with error::invalid_arguments() + const EALREADY_EXISTS: u64 = 100; + // native code raises this with error::invalid_arguments() + const ENOT_FOUND: u64 = 101; + const ENOT_EMPTY: u64 = 102; + + /// Type of tables + struct TableWithLength has store { + inner: Table, + length: u64 + } + + /// Create a new Table. + public fun new(): TableWithLength { + TableWithLength { + inner: table::new(), + length: 0 + } + } + + /// Destroy a table. The table must be empty to succeed. + public fun destroy_empty(self: TableWithLength) { + assert!(self.length == 0, error::invalid_state(ENOT_EMPTY)); + let TableWithLength { inner, length: _ } = self; + inner.destroy_known_empty_unsafe() + } + + /// Add a new entry to the table. Aborts if an entry for this + /// key already exists. The entry itself is not stored in the + /// table, and cannot be discovered from it. + public fun add( + self: &mut TableWithLength, key: K, val: V + ) { + self.inner.add(key, val); + self.length += 1; + } + + /// Acquire an immutable reference to the value which `key` maps to. + /// Aborts if there is no entry for `key`. + public fun borrow( + self: &TableWithLength, key: K + ): &V { + self.inner.borrow(key) + } + + /// Acquire a mutable reference to the value which `key` maps to. + /// Aborts if there is no entry for `key`. + public fun borrow_mut( + self: &mut TableWithLength, key: K + ): &mut V { + self.inner.borrow_mut(key) + } + + /// Returns the length of the table, i.e. the number of entries. + public fun length(self: &TableWithLength): u64 { + self.length + } + + /// Returns true if this table is empty. + public fun empty(self: &TableWithLength): bool { + self.length == 0 + } + + /// Acquire a mutable reference to the value which `key` maps to. + /// Insert the pair (`key`, `default`) first if there is no entry for `key`. + public fun borrow_mut_with_default( + self: &mut TableWithLength, key: K, default: V + ): &mut V { + if (self.inner.contains(key)) { + self.inner.borrow_mut(key) + } else { + self.inner.add(key, default); + self.length += 1; + self.inner.borrow_mut(key) + } + } + + /// Insert the pair (`key`, `value`) if there is no entry for `key`. + /// update the value of the entry for `key` to `value` otherwise + public fun upsert( + self: &mut TableWithLength, key: K, value: V + ) { + if (!self.inner.contains(key)) { + self.add(copy key, value) + } else { + let ref = self.inner.borrow_mut(key); + *ref = value; + }; + } + + /// Remove from `table` and return the value which `key` maps to. + /// Aborts if there is no entry for `key`. + public fun remove( + self: &mut TableWithLength, key: K + ): V { + let val = self.inner.remove(key); + self.length -= 1; + val + } + + /// Returns true iff `table` contains an entry for `key`. + public fun contains( + self: &TableWithLength, key: K + ): bool { + self.inner.contains(key) + } + + #[test_only] + /// Drop table even if not empty, only when testing. + public fun drop_unchecked(self: TableWithLength) { + // Unpack table with length, dropping length count but not + // inner table. + let TableWithLength { inner, length: _ } = self; + inner.drop_unchecked(); // Drop inner table. + } + + #[test] + /// Verify test-only drop functionality. + fun test_drop_unchecked() { + let table = new(); // Declare new table. + table.add(true, false); // Add table entry. + table.drop_unchecked(); // Drop table. + } + + #[test] + fun test_upsert() { + let t = new(); + // Table should not have key 0 yet + assert!(!t.contains(0), 1); + // This should insert key 0, with value 10, and length should be 1 + t.upsert(0, 10); + // Ensure the value is correctly set to 10 + assert!(*t.borrow(0) == 10, 1); + // Ensure the length is correctly set + assert!(t.length() == 1, 1); + // Lets upsert the value to something else, and verify it's correct + t.upsert(0, 23); + assert!(*t.borrow(0) == 23, 1); + // Since key 0 already existed, the length should not have changed + assert!(t.length() == 1, 1); + // If we upsert a non-existing key, the length should increase + t.upsert(1, 7); + assert!(t.length() == 2, 1); + + t.remove(0); + t.remove(1); + t.destroy_empty(); + } +} diff --git a/precompile/modules/move_nursery/sources/acl.move b/precompile/modules/move_nursery/sources/acl.move index 239e9efd..02e6d95a 100644 --- a/precompile/modules/move_nursery/sources/acl.move +++ b/precompile/modules/move_nursery/sources/acl.move @@ -1,4 +1,4 @@ -/// Access control list (ACL) module. An ACL is a list of account addresses who +/// Access control list (acl) module. An acl is a list of account addresses who /// have the access permission to a certain object. /// This module uses a `vector` to represent the list, but can be refactored to /// use a "set" instead when it's available in the language in the future. @@ -22,31 +22,25 @@ module std::acl { } /// Add the address to the ACL. - public fun add(acl: &mut ACL, addr: address) { - assert!( - !vector::contains(&mut acl.list, &addr), - error::invalid_argument(ECONTAIN) - ); - vector::push_back(&mut acl.list, addr); + public fun add(self: &mut ACL, addr: address) { + assert!(!self.list.contains(&addr), error::invalid_argument(ECONTAIN)); + self.list.push_back(addr); } /// Remove the address from the ACL. - public fun remove(acl: &mut ACL, addr: address) { - let (found, index) = vector::index_of(&mut acl.list, &addr); + public fun remove(self: &mut ACL, addr: address) { + let (found, index) = self.list.index_of(&addr); assert!(found, error::invalid_argument(ENOT_CONTAIN)); - vector::remove(&mut acl.list, index); + self.list.remove(index); } /// Return true iff the ACL contains the address. - public fun contains(acl: &ACL, addr: address): bool { - vector::contains(&acl.list, &addr) + public fun contains(self: &ACL, addr: address): bool { + self.list.contains(&addr) } /// assert! that the ACL has the address. - public fun assert_contains(acl: &ACL, addr: address) { - assert!( - contains(acl, addr), - error::invalid_argument(ENOT_CONTAIN) - ); + public fun assert_contains(self: &ACL, addr: address) { + assert!(self.contains(addr), error::invalid_argument(ENOT_CONTAIN)); } } diff --git a/precompile/modules/move_nursery/sources/capability.move b/precompile/modules/move_nursery/sources/capability.move index 868f8922..f8b1753e 100644 --- a/precompile/modules/move_nursery/sources/capability.move +++ b/precompile/modules/move_nursery/sources/capability.move @@ -76,8 +76,12 @@ module std::capability { use std::signer; use std::vector; - const ECAP: u64 = 0; - const EDELEGATE: u64 = 1; + /// Capability resource already exists on the specified account + const ECAPABILITY_ALREADY_EXISTS: u64 = 1; + /// Capability resource not found + const ECAPABILITY_NOT_FOUND: u64 = 2; + /// Account does not have delegated permissions + const EDELEGATE: u64 = 3; /// The token representing an acquired capability. Cannot be stored in memory, but copied and dropped freely. struct Cap has copy, drop { @@ -108,7 +112,7 @@ module std::capability { let addr = signer::address_of(owner); assert!( !exists>(addr), - error::already_exists(ECAP) + error::already_exists(ECAPABILITY_ALREADY_EXISTS) ); move_to>(owner, CapState { delegates: vector::empty() }); } @@ -145,17 +149,14 @@ module std::capability { error::invalid_state(EDELEGATE) ); assert!( - vector::contains( - &borrow_global>(root_addr).delegates, - &addr - ), + borrow_global>(root_addr).delegates.contains(&addr), error::invalid_state(EDELEGATE) ); root_addr } else { assert!( exists>(addr), - error::not_found(ECAP) + error::not_found(ECAPABILITY_NOT_FOUND) ); addr } @@ -164,29 +165,29 @@ module std::capability { /// Returns the root address associated with the given capability token. Only the owner /// of the feature can do this. public fun root_addr( - cap: Cap, _feature_witness: &Feature + self: Cap, _feature_witness: &Feature ): address { - cap.root + self.root } /// Returns the root address associated with the given linear capability token. public fun linear_root_addr( - cap: LinearCap, _feature_witness: &Feature + self: LinearCap, _feature_witness: &Feature ): address { - cap.root + self.root } /// Registers a delegation relation. If the relation already exists, this function does /// nothing. // TODO: explore whether this should be idempotent like now or abort public fun delegate( - cap: Cap, _feature_witness: &Feature, to: &signer + self: Cap, _feature_witness: &Feature, to: &signer ) acquires CapState { let addr = signer::address_of(to); if (exists>(addr)) return; - move_to(to, CapDelegateState { root: cap.root }); + move_to(to, CapDelegateState { root: self.root }); add_element( - &mut borrow_global_mut>(cap.root).delegates, + &mut borrow_global_mut>(self.root).delegates, addr ); } @@ -194,38 +195,28 @@ module std::capability { /// Revokes a delegation relation. If no relation exists, this function does nothing. // TODO: explore whether this should be idempotent like now or abort public fun revoke( - cap: Cap, _feature_witness: &Feature, from: address + self: Cap, _feature_witness: &Feature, from: address ) acquires CapState, CapDelegateState { if (!exists>(from)) return; let CapDelegateState { root: _root } = move_from>(from); remove_element( - &mut borrow_global_mut>(cap.root).delegates, + &mut borrow_global_mut>(self.root).delegates, &from ); } /// Helper to remove an element from a vector. fun remove_element(v: &mut vector, x: &E) { - let (found, index) = vector::index_of(v, x); + let (found, index) = v.index_of(x); if (found) { - vector::remove(v, index); + v.remove(index); } } /// Helper to add an element to a vector. fun add_element(v: &mut vector, x: E) { - if (!vector::contains(v, &x)) { - vector::push_back(v, x) + if (!v.contains(&x)) { + v.push_back(x) } } - - /// Helper specification function to check whether a capability exists at address. - spec fun spec_has_cap(addr: address): bool { - exists>(addr) - } - - /// Helper specification function to obtain the delegates of a capability. - spec fun spec_delegates(addr: address): vector
{ - global>(addr).delegates - } } diff --git a/precompile/modules/move_stdlib/sources/bcs.move b/precompile/modules/move_stdlib/sources/bcs.move index 7e47428c..d6362440 100644 --- a/precompile/modules/move_stdlib/sources/bcs.move +++ b/precompile/modules/move_stdlib/sources/bcs.move @@ -3,9 +3,30 @@ /// published on-chain. See https://github.com/diem/bcs#binary-canonical-serialization-bcs for more /// details on BCS. module std::bcs { - /// Return the binary representation of `v` in BCS (Binary Canonical Serialization) format + use std::option::Option; + + /// Note: all natives would fail if the MoveValue contains a permissioned signer in it. + + /// Returns the binary representation of `v` in BCS (Binary Canonical Serialization) format. + /// Aborts with `0x1c5` error code if serialization fails. native public fun to_bytes(v: &MoveValue): vector; + /// Returns the size of the binary representation of `v` in BCS (Binary Canonical Serialization) format. + /// Aborts with `0x1c5` error code if there is a failure when calculating serialized size. + native public fun serialized_size(v: &MoveValue): u64; + + /// If the type has known constant (always the same, independent of instance) serialized size + /// in BCS (Binary Canonical Serialization) format, returns it, otherwise returns None. + /// Aborts with `0x1c5` error code if there is a failure when calculating serialized size. + /// + /// Note: + /// For some types it might not be known they have constant size, and function might return None. + /// For example, signer appears to have constant size, but it's size might change. + /// If this function returned Some() for some type before - it is guaranteed to continue returning Some(). + /// On the other hand, if function has returned None for some type, + /// it might change in the future to return Some() instead, if size becomes "known". + native public fun constant_serialized_size(): Option; + // ============================== // Module Specification spec module {} // switch to module documentation context @@ -14,4 +35,14 @@ module std::bcs { /// Native function which is defined in the prover's prelude. native fun serialize(v: &MoveValue): vector; } + + spec serialized_size(v: &MoveValue): u64 { + pragma opaque; + ensures result == len(serialize(v)); + } + + spec constant_serialized_size { + // TODO: temporary mockup. + pragma opaque; + } } diff --git a/precompile/modules/move_stdlib/sources/bit_vector.move b/precompile/modules/move_stdlib/sources/bit_vector.move index 19d58c38..be968fae 100644 --- a/precompile/modules/move_stdlib/sources/bit_vector.move +++ b/precompile/modules/move_stdlib/sources/bit_vector.move @@ -10,6 +10,10 @@ module std::bit_vector { /// The maximum allowed bitvector size const MAX_SIZE: u64 = 1024; + spec BitVector { + invariant length == len(bit_field); + } + struct BitVector has copy, drop, store { length: u64, bit_field: vector @@ -27,8 +31,8 @@ module std::bit_vector { }; (counter < length) }) { - vector::push_back(&mut bit_field, false); - counter = counter + 1; + bit_field.push_back(false); + counter += 1; }; spec { assert counter == length; @@ -50,130 +54,205 @@ module std::bit_vector { aborts_if length >= MAX_SIZE with ELENGTH; } - /// Set the bit at `bit_index` in the `bitvector` regardless of its previous state. - public fun set(bitvector: &mut BitVector, bit_index: u64) { - assert!( - bit_index < vector::length(&bitvector.bit_field), - EINDEX - ); - let x = vector::borrow_mut(&mut bitvector.bit_field, bit_index); - *x = true; + /// Set the bit at `bit_index` in the `self` regardless of its previous state. + public fun set(self: &mut BitVector, bit_index: u64) { + assert!(bit_index < self.bit_field.length(), EINDEX); + self.bit_field[bit_index] = true; } spec set { include SetAbortsIf; - ensures bitvector.bit_field[bit_index]; + ensures self.bit_field[bit_index]; } spec schema SetAbortsIf { - bitvector: BitVector; + self: BitVector; bit_index: u64; - aborts_if bit_index >= length(bitvector) with EINDEX; + aborts_if bit_index >= self.length() with EINDEX; } - /// Unset the bit at `bit_index` in the `bitvector` regardless of its previous state. - public fun unset(bitvector: &mut BitVector, bit_index: u64) { - assert!( - bit_index < vector::length(&bitvector.bit_field), - EINDEX - ); - let x = vector::borrow_mut(&mut bitvector.bit_field, bit_index); - *x = false; + /// Unset the bit at `bit_index` in the `self` regardless of its previous state. + public fun unset(self: &mut BitVector, bit_index: u64) { + assert!(bit_index < self.bit_field.length(), EINDEX); + self.bit_field[bit_index] = false; } spec unset { include UnsetAbortsIf; - ensures !bitvector.bit_field[bit_index]; + ensures !self.bit_field[bit_index]; } spec schema UnsetAbortsIf { - bitvector: BitVector; + self: BitVector; bit_index: u64; - aborts_if bit_index >= length(bitvector) with EINDEX; + aborts_if bit_index >= self.length() with EINDEX; } - /// Shift the `bitvector` left by `amount`. If `amount` is greater than the + /// Shift the `self` left by `amount`. If `amount` is greater than the /// bitvector's length the bitvector will be zeroed out. - public fun shift_left(bitvector: &mut BitVector, amount: u64) { - if (amount >= bitvector.length) { - let len = vector::length(&bitvector.bit_field); - let i = 0; - while (i < len) { - let elem = vector::borrow_mut(&mut bitvector.bit_field, i); + public fun shift_left(self: &mut BitVector, amount: u64) { + if (amount >= self.length) { + self.bit_field.for_each_mut(|elem| { *elem = false; - i = i + 1; - }; + }); } else { let i = amount; - while (i < bitvector.length) { - if (is_index_set(bitvector, i)) set(bitvector, i - amount) - else unset(bitvector, i - amount); - i = i + 1; + while (i < self.length) { + if (self.is_index_set(i)) self.set(i - amount) + else self.unset(i - amount); + i += 1; }; - i = bitvector.length - amount; + i = self.length - amount; - while (i < bitvector.length) { - unset(bitvector, i); - i = i + 1; + while (i < self.length) { + self.unset(i); + i += 1; }; } } - /// Return the value of the bit at `bit_index` in the `bitvector`. `true` + spec shift_left { + // TODO: set to false because data invariant cannot be proved with inline function. Will remove it once inline is supported + pragma verify = false; + } + + /// Return the value of the bit at `bit_index` in the `self`. `true` /// represents "1" and `false` represents a 0 - public fun is_index_set(bitvector: &BitVector, bit_index: u64): bool { - assert!( - bit_index < vector::length(&bitvector.bit_field), - EINDEX - ); - *vector::borrow(&bitvector.bit_field, bit_index) + public fun is_index_set(self: &BitVector, bit_index: u64): bool { + assert!(bit_index < self.bit_field.length(), EINDEX); + self.bit_field[bit_index] } spec is_index_set { include IsIndexSetAbortsIf; - ensures result == bitvector.bit_field[bit_index]; + ensures result == self.bit_field[bit_index]; } spec schema IsIndexSetAbortsIf { - bitvector: BitVector; + self: BitVector; bit_index: u64; - aborts_if bit_index >= length(bitvector) with EINDEX; + aborts_if bit_index >= self.length() with EINDEX; } - spec fun spec_is_index_set(bitvector: BitVector, bit_index: u64): bool { - if (bit_index >= length(bitvector)) { false } + spec fun spec_is_index_set(self: BitVector, bit_index: u64): bool { + if (bit_index >= self.length()) { false } else { - bitvector.bit_field[bit_index] + self.bit_field[bit_index] } } /// Return the length (number of usable bits) of this bitvector - public fun length(bitvector: &BitVector): u64 { - vector::length(&bitvector.bit_field) + public fun length(self: &BitVector): u64 { + self.bit_field.length() } /// Returns the length of the longest sequence of set bits starting at (and /// including) `start_index` in the `bitvector`. If there is no such /// sequence, then `0` is returned. public fun longest_set_sequence_starting_at( - bitvector: &BitVector, start_index: u64 + self: &BitVector, start_index: u64 ): u64 { - assert!(start_index < bitvector.length, EINDEX); + assert!(start_index < self.length, EINDEX); let index = start_index; // Find the greatest index in the vector such that all indices less than it are set. - while (index < bitvector.length) { - if (!is_index_set(bitvector, index)) break; - index = index + 1; + while ({ + spec { + invariant index >= start_index; + invariant index == start_index || self.is_index_set(index - 1); + invariant index == start_index || index - 1 < len(self.bit_field); + invariant forall j in start_index..index: self.is_index_set(j); + invariant forall j in start_index..index: j < len(self.bit_field); + }; + index < self.length + }) { + if (!self.is_index_set(index)) break; + index += 1; }; index - start_index } + spec longest_set_sequence_starting_at(self: &BitVector, start_index: u64): u64 { + aborts_if start_index >= self.length; + ensures forall i in start_index..result: self.is_index_set(i); + } + #[test_only] public fun word_size(): u64 { WORD_SIZE } + + #[verify_only] + public fun shift_left_for_verification_only( + self: &mut BitVector, amount: u64 + ) { + if (amount >= self.length) { + let len = self.bit_field.length(); + let i = 0; + while ({ + spec { + invariant len == self.length; + invariant forall k in 0..i: !self.bit_field[k]; + invariant forall k in i..self.length: + self.bit_field[k] == old(self).bit_field[k]; + }; + i < len + }) { + let elem = self.bit_field.borrow_mut(i); + *elem = false; + i += 1; + }; + } else { + let i = amount; + + while ({ + spec { + invariant i >= amount; + invariant self.length == old(self).length; + invariant forall j in amount..i: + old(self).bit_field[j] == self.bit_field[j - amount]; + invariant forall j in (i - amount)..self.length: + old(self).bit_field[j] == self.bit_field[j]; + invariant forall k in 0..i - amount: + self.bit_field[k] == old(self).bit_field[k + amount]; + }; + i < self.length + }) { + if (self.is_index_set(i)) self.set(i - amount) + else self.unset(i - amount); + i += 1; + }; + + i = self.length - amount; + + while ({ + spec { + invariant forall j in self.length - amount..i: !self.bit_field[j]; + invariant forall k in 0..self.length - amount: + self.bit_field[k] == old(self).bit_field[k + amount]; + invariant i >= self.length - amount; + }; + i < self.length + }) { + self.unset(i); + i += 1; + } + } + } + + spec shift_left_for_verification_only { + aborts_if false; + ensures amount >= self.length ==> + (forall k in 0..self.length: !self.bit_field[k]); + ensures amount < self.length ==> + (forall i in self.length - amount..self.length: !self.bit_field[i]); + ensures amount < self.length ==> + ( + forall i in 0..self.length - amount: + self.bit_field[i] == old(self).bit_field[i + amount] + ); + } } diff --git a/precompile/modules/move_stdlib/sources/cmp.move b/precompile/modules/move_stdlib/sources/cmp.move new file mode 100644 index 00000000..ac4481ff --- /dev/null +++ b/precompile/modules/move_stdlib/sources/cmp.move @@ -0,0 +1,308 @@ +module std::cmp { + enum Ordering has copy, drop { + /// First value is less than the second value. + Less, + /// First value is equal to the second value. + Equal, + /// First value is greater than the second value. + Greater + } + + /// Compares two values with the natural ordering: + /// - native types are compared identically to `<` and other operators + /// - complex types + /// - Structs and vectors - are compared lexicographically - first field/element is compared first, + /// and if equal we proceed to the next. + /// - enum's are compared first by their variant, and if equal - they are compared as structs are. + native public fun compare(first: &T, second: &T): Ordering; + + public fun is_eq(self: &Ordering): bool { + self is Ordering::Equal + } + + public fun is_ne(self: &Ordering): bool { + !(self is Ordering::Equal) + } + + public fun is_lt(self: &Ordering): bool { + self is Ordering::Less + } + + public fun is_le(self: &Ordering): bool { + !(self is Ordering::Greater) + } + + public fun is_gt(self: &Ordering): bool { + self is Ordering::Greater + } + + public fun is_ge(self: &Ordering): bool { + !(self is Ordering::Less) + } + + spec compare { + // TODO: temporary mockup. + pragma opaque; + } + + #[test_only] + struct SomeStruct has drop { + field_1: u64, + field_2: u64 + } + + #[test_only] + enum SimpleEnum has drop { + V { + field: u64 + } + } + + #[test_only] + enum SomeEnum has drop { + V1 { + field_1: u64 + }, + V2 { + field_2: u64 + }, + V3 { + field_3: SomeStruct + }, + V4 { + field_4: vector + }, + V5 { + field_5: SimpleEnum + } + } + + #[test] + fun test_compare_numbers() { + assert!(compare(&1, &5).is_ne(), 0); + assert!(!compare(&1, &5).is_eq(), 0); + assert!(compare(&1, &5).is_lt(), 1); + assert!(compare(&1, &5).is_le(), 2); + assert!(compare(&5, &5).is_eq(), 3); + assert!(!compare(&5, &5).is_ne(), 3); + assert!(!compare(&5, &5).is_lt(), 4); + assert!(compare(&5, &5).is_le(), 5); + assert!(!compare(&7, &5).is_eq(), 6); + assert!(compare(&7, &5).is_ne(), 6); + assert!(!compare(&7, &5).is_lt(), 7); + assert!(!compare(&7, &5).is_le(), 8); + + assert!(!compare(&1, &5).is_eq(), 0); + assert!(compare(&1, &5).is_ne(), 0); + assert!(compare(&1, &5).is_lt(), 1); + assert!(compare(&1, &5).is_le(), 2); + assert!(!compare(&1, &5).is_gt(), 1); + assert!(!compare(&1, &5).is_ge(), 1); + assert!(compare(&5, &5).is_eq(), 3); + assert!(!compare(&5, &5).is_ne(), 3); + assert!(!compare(&5, &5).is_lt(), 4); + assert!(compare(&5, &5).is_le(), 5); + assert!(!compare(&5, &5).is_gt(), 5); + assert!(compare(&5, &5).is_ge(), 5); + assert!(!compare(&7, &5).is_eq(), 6); + assert!(compare(&7, &5).is_ne(), 6); + assert!(!compare(&7, &5).is_lt(), 7); + assert!(!compare(&7, &5).is_le(), 8); + assert!(compare(&7, &5).is_gt(), 7); + assert!(compare(&7, &5).is_ge(), 8); + } + + #[test] + fun test_compare_vectors() { + let empty = vector[]; // here for typing, for the second line + assert!(compare(&empty, &vector[1]) is Ordering::Less, 0); + assert!(compare(&empty, &vector[]) is Ordering::Equal, 1); + assert!( + compare(&vector[1], &vector[]) is Ordering::Greater, + 2 + ); + assert!( + compare( + &vector[1, 2], &vector[1, 2] + ) is Ordering::Equal, + 3 + ); + assert!( + compare( + &vector[1, 2, 3], &vector[5] + ) is Ordering::Less, + 4 + ); + assert!( + compare( + &vector[1, 2, 3], &vector[5, 6, 7] + ) is Ordering::Less, + 5 + ); + assert!( + compare( + &vector[1, 2, 3], &vector[1, 2, 7] + ) is Ordering::Less, + 6 + ); + } + + #[test] + fun test_compare_structs() { + assert!( + compare( + &SomeStruct { field_1: 1, field_2: 2 }, + &SomeStruct { field_1: 1, field_2: 2 } + ) is Ordering::Equal, + 0 + ); + assert!( + compare( + &SomeStruct { field_1: 1, field_2: 2 }, + &SomeStruct { field_1: 1, field_2: 3 } + ) is Ordering::Less, + 1 + ); + assert!( + compare( + &SomeStruct { field_1: 1, field_2: 2 }, + &SomeStruct { field_1: 1, field_2: 1 } + ) is Ordering::Greater, + 2 + ); + assert!( + compare( + &SomeStruct { field_1: 2, field_2: 1 }, + &SomeStruct { field_1: 1, field_2: 2 } + ) is Ordering::Greater, + 3 + ); + } + + #[test] + fun test_compare_vector_of_structs() { + assert!( + compare( + &vector[ + SomeStruct { field_1: 1, field_2: 2 }, + SomeStruct { field_1: 3, field_2: 4 } + ], + &vector[SomeStruct { field_1: 1, field_2: 3 }] + ) is Ordering::Less, + 0 + ); + assert!( + compare( + &vector[ + SomeStruct { field_1: 1, field_2: 2 }, + SomeStruct { field_1: 3, field_2: 4 } + ], + &vector[ + SomeStruct { field_1: 1, field_2: 2 }, + SomeStruct { field_1: 1, field_2: 3 } + ] + ) is Ordering::Greater, + 1 + ); + } + + #[test] + fun test_compare_enums() { + assert!( + compare(&SomeEnum::V1 { field_1: 6 }, &SomeEnum::V1 { field_1: 6 }) is Ordering::Equal, + 0 + ); + assert!( + compare(&SomeEnum::V1 { field_1: 6 }, &SomeEnum::V2 { field_2: 1 }) is Ordering::Less, + 1 + ); + assert!( + compare(&SomeEnum::V1 { field_1: 6 }, &SomeEnum::V2 { field_2: 8 }) is Ordering::Less, + 2 + ); + assert!( + compare(&SomeEnum::V1 { field_1: 6 }, &SomeEnum::V1 { field_1: 5 }) is Ordering::Greater, + 3 + ); + + assert!( + compare( + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2 } }, + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2 } } + ) is Ordering::Equal, + 4 + ); + assert!( + compare( + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2 } }, + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 3 } } + ) is Ordering::Less, + 5 + ); + assert!( + compare( + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2 } }, + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 1 } } + ) is Ordering::Greater, + 6 + ); + assert!( + compare( + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 2 } }, + &SomeEnum::V3 { field_3: SomeStruct { field_1: 1, field_2: 1 } } + ) is Ordering::Greater, + 7 + ); + + assert!( + compare( + &SomeEnum::V4 { field_4: vector[1, 2] }, + &SomeEnum::V4 { field_4: vector[1, 2] } + ) is Ordering::Equal, + 8 + ); + assert!( + compare( + &SomeEnum::V4 { field_4: vector[1, 2, 3] }, + &SomeEnum::V4 { field_4: vector[5] } + ) is Ordering::Less, + 9 + ); + assert!( + compare( + &SomeEnum::V4 { field_4: vector[1, 2, 3] }, + &SomeEnum::V4 { field_4: vector[5, 6, 7] } + ) is Ordering::Less, + 10 + ); + assert!( + compare( + &SomeEnum::V4 { field_4: vector[1, 2, 3] }, + &SomeEnum::V4 { field_4: vector[1, 2, 7] } + ) is Ordering::Less, + 11 + ); + + assert!( + compare( + &SomeEnum::V5 { field_5: SimpleEnum::V { field: 3 } }, + &SomeEnum::V5 { field_5: SimpleEnum::V { field: 3 } } + ) is Ordering::Equal, + 12 + ); + assert!( + compare( + &SomeEnum::V5 { field_5: SimpleEnum::V { field: 5 } }, + &SomeEnum::V5 { field_5: SimpleEnum::V { field: 3 } } + ) is Ordering::Greater, + 13 + ); + assert!( + compare( + &SomeEnum::V5 { field_5: SimpleEnum::V { field: 3 } }, + &SomeEnum::V5 { field_5: SimpleEnum::V { field: 5 } } + ) is Ordering::Less, + 14 + ); + } +} diff --git a/precompile/modules/move_stdlib/sources/error.move b/precompile/modules/move_stdlib/sources/error.move index 25bc0a7a..ce01a340 100644 --- a/precompile/modules/move_stdlib/sources/error.move +++ b/precompile/modules/move_stdlib/sources/error.move @@ -65,6 +65,14 @@ module std::error { (category << 16) + reason } + spec canonical { + pragma opaque = true; + let shl_res = category << 16; + ensures [concrete] result == shl_res + reason; + aborts_if [abstract] false; + ensures [abstract] result == category; + } + /// Functions to construct a canonical error code of the given category. public fun invalid_argument(r: u64): u64 { canonical(INVALID_ARGUMENT, r) diff --git a/precompile/modules/move_stdlib/sources/fixed_point32.move b/precompile/modules/move_stdlib/sources/fixed_point32.move index 9c71ca30..3556afb9 100644 --- a/precompile/modules/move_stdlib/sources/fixed_point32.move +++ b/precompile/modules/move_stdlib/sources/fixed_point32.move @@ -16,7 +16,6 @@ module std::fixed_point32 { value: u64 } - ///> TODO: This is a basic constant and should be provided somewhere centrally in the framework. const MAX_U64: u128 = 18446744073709551615; /// The denominator provided was zero @@ -43,7 +42,7 @@ module std::fixed_point32 { let product = unscaled_product >> 32; // Check whether the value is too large. assert!(product <= MAX_U64, EMULTIPLICATION); - (product as u64) + product as u64 } spec multiply_u64 { @@ -126,7 +125,6 @@ module std::fixed_point32 { } spec create_from_rational { - pragma verify = false; // TIMEOUT pragma opaque; include CreateFromRationalAbortsIf; ensures result == spec_create_from_rational(numerator, denominator); @@ -163,13 +161,13 @@ module std::fixed_point32 { /// Accessor for the raw u64 value. Other less common operations, such as /// adding or subtracting FixedPoint32 values, can be done using the raw /// values directly. - public fun get_raw_value(num: FixedPoint32): u64 { - num.value + public fun get_raw_value(self: FixedPoint32): u64 { + self.value } /// Returns true if the ratio is zero. - public fun is_zero(num: FixedPoint32): bool { - num.value == 0 + public fun is_zero(self: FixedPoint32): bool { + self.value == 0 } /// Returns the smaller of the two FixedPoint32 numbers. @@ -230,29 +228,29 @@ module std::fixed_point32 { } /// Returns the largest integer less than or equal to a given number. - public fun floor(num: FixedPoint32): u64 { - num.value >> 32 + public fun floor(self: FixedPoint32): u64 { + self.value >> 32 } spec floor { pragma opaque; aborts_if false; - ensures result == spec_floor(num); + ensures result == spec_floor(self); } - spec fun spec_floor(val: FixedPoint32): u64 { - let fractional = val.value % (1 << 32); + spec fun spec_floor(self: FixedPoint32): u64 { + let fractional = self.value % (1 << 32); if (fractional == 0) { - val.value >> 32 + self.value >> 32 } else { - (val.value - fractional) >> 32 + (self.value - fractional) >> 32 } } /// Rounds up the given FixedPoint32 to the next largest integer. - public fun ceil(num: FixedPoint32): u64 { - let floored_num = floor(num) << 32; - if (num.value == floored_num) { + public fun ceil(self: FixedPoint32): u64 { + let floored_num = self.floor() << 32; + if (self.value == floored_num) { return floored_num >> 32 }; let val = ((floored_num as u128) + (1 << 32)); @@ -260,48 +258,48 @@ module std::fixed_point32 { } spec ceil { - pragma verify = false; // timeout + pragma verify_duration_estimate = 120; pragma opaque; aborts_if false; - ensures result == spec_ceil(num); + ensures result == spec_ceil(self); } - spec fun spec_ceil(val: FixedPoint32): u64 { - let fractional = val.value % (1 << 32); + spec fun spec_ceil(self: FixedPoint32): u64 { + let fractional = self.value % (1 << 32); let one = 1 << 32; if (fractional == 0) { - val.value >> 32 + self.value >> 32 } else { - (val.value - fractional + one) >> 32 + (self.value - fractional + one) >> 32 } } /// Returns the value of a FixedPoint32 to the nearest integer. - public fun round(num: FixedPoint32): u64 { - let floored_num = floor(num) << 32; + public fun round(self: FixedPoint32): u64 { + let floored_num = self.floor() << 32; let boundary = floored_num + ((1 << 32) / 2); - if (num.value < boundary) { + if (self.value < boundary) { floored_num >> 32 } else { - ceil(num) + self.ceil() } } spec round { + pragma verify_duration_estimate = 120; pragma opaque; - pragma timeout = 120; aborts_if false; - ensures result == spec_round(num); + ensures result == spec_round(self); } - spec fun spec_round(val: FixedPoint32): u64 { - let fractional = val.value % (1 << 32); + spec fun spec_round(self: FixedPoint32): u64 { + let fractional = self.value % (1 << 32); let boundary = (1 << 32) / 2; let one = 1 << 32; if (fractional < boundary) { - (val.value - fractional) >> 32 + (self.value - fractional) >> 32 } else { - (val.value - fractional + one) >> 32 + (self.value - fractional + one) >> 32 } } diff --git a/precompile/modules/move_stdlib/sources/mem.move b/precompile/modules/move_stdlib/sources/mem.move new file mode 100644 index 00000000..c0276ea5 --- /dev/null +++ b/precompile/modules/move_stdlib/sources/mem.move @@ -0,0 +1,33 @@ +/// Module with methods for safe memory manipulation. +module std::mem { + // TODO - functions here are `friend` here for one release, + // and to be changed to `public` one release later. + friend std::vector; + + /// Swap contents of two passed mutable references. + /// + /// Move prevents from having two mutable references to the same value, + /// so `left` and `right` references are always distinct. + native friend fun swap(left: &mut T, right: &mut T); + + /// Replace the value reference points to with the given new value, + /// and return the value it had before. + friend fun replace(ref: &mut T, new: T): T { + swap(ref, &mut new); + new + } + + spec swap(left: &mut T, right: &mut T) { + pragma opaque; + aborts_if false; + ensures right == old(left); + ensures left == old(right); + } + + spec replace(ref: &mut T, new: T): T { + pragma opaque; + aborts_if false; + ensures result == old(ref); + ensures ref == new; + } +} diff --git a/precompile/modules/move_stdlib/sources/option.move b/precompile/modules/move_stdlib/sources/option.move index 89bbcb82..e19e8e58 100644 --- a/precompile/modules/move_stdlib/sources/option.move +++ b/precompile/modules/move_stdlib/sources/option.move @@ -35,7 +35,7 @@ module std::option { } spec fun spec_none(): Option { - Option { vec: vec() } + Option { vec: vector[] } } /// Return an `Option` containing `e` @@ -54,327 +54,330 @@ module std::option { } public fun from_vec(vec: vector): Option { - assert!(vector::length(&vec) <= 1, EOPTION_VEC_TOO_LONG); + assert!(vec.length() <= 1, EOPTION_VEC_TOO_LONG); Option { vec } } spec from_vec { - aborts_if vector::length(vec) > 1; + aborts_if vec.length() > 1; } - /// Return true if `t` does not hold a value - public fun is_none(t: &Option): bool { - vector::is_empty(&t.vec) + /// Return true if `self` does not hold a value + public fun is_none(self: &Option): bool { + self.vec.is_empty() } spec is_none { pragma opaque; aborts_if false; - ensures result == spec_is_none(t); + ensures result == spec_is_none(self); } - spec fun spec_is_none(t: Option): bool { - vector::is_empty(t.vec) + spec fun spec_is_none(self: Option): bool { + self.vec.is_empty() } - /// Return true if `t` holds a value - public fun is_some(t: &Option): bool { - !vector::is_empty(&t.vec) + /// Return true if `self` holds a value + public fun is_some(self: &Option): bool { + !self.vec.is_empty() } spec is_some { pragma opaque; aborts_if false; - ensures result == spec_is_some(t); + ensures result == spec_is_some(self); } - spec fun spec_is_some(t: Option): bool { - !vector::is_empty(t.vec) + spec fun spec_is_some(self: Option): bool { + !self.vec.is_empty() } - /// Return true if the value in `t` is equal to `e_ref` - /// Always returns `false` if `t` does not hold a value - public fun contains(t: &Option, e_ref: &Element): bool { - vector::contains(&t.vec, e_ref) + /// Return true if the value in `self` is equal to `e_ref` + /// Always returns `false` if `self` does not hold a value + public fun contains(self: &Option, e_ref: &Element): bool { + self.vec.contains(e_ref) } spec contains { pragma opaque; aborts_if false; - ensures result == spec_contains(t, e_ref); + ensures result == spec_contains(self, e_ref); } - spec fun spec_contains(t: Option, e: Element): bool { - is_some(t) && borrow(t) == e + spec fun spec_contains(self: Option, e: Element): bool { + self.is_some() && self.borrow() == e } - /// Return an immutable reference to the value inside `t` - /// Aborts if `t` does not hold a value - public fun borrow(t: &Option): &Element { - assert!(is_some(t), EOPTION_NOT_SET); - vector::borrow(&t.vec, 0) + /// Return an immutable reference to the value inside `self` + /// Aborts if `self` does not hold a value + public fun borrow(self: &Option): &Element { + assert!(self.is_some(), EOPTION_NOT_SET); + &self.vec[0] } spec borrow { pragma opaque; include AbortsIfNone; - ensures result == spec_borrow(t); + ensures result == spec_borrow(self); } - spec fun spec_borrow(t: Option): Element { - t.vec[0] + spec fun spec_borrow(self: Option): Element { + self.vec[0] } - /// Return a reference to the value inside `t` if it holds one - /// Return `default_ref` if `t` does not hold a value + /// Return a reference to the value inside `self` if it holds one + /// Return `default_ref` if `self` does not hold a value public fun borrow_with_default( - t: &Option, default_ref: &Element + self: &Option, default_ref: &Element ): &Element { - let vec_ref = &t.vec; - if (vector::is_empty(vec_ref)) default_ref - else vector::borrow(vec_ref, 0) + let vec_ref = &self.vec; + if (vec_ref.is_empty()) default_ref else &vec_ref[0] } spec borrow_with_default { pragma opaque; aborts_if false; - ensures result == (if (spec_is_some(t)) spec_borrow(t) + ensures result == (if (spec_is_some(self)) spec_borrow(self) else default_ref); } - /// Return the value inside `t` if it holds one - /// Return `default` if `t` does not hold a value + /// Return the value inside `self` if it holds one + /// Return `default` if `self` does not hold a value public fun get_with_default( - t: &Option, default: Element + self: &Option, default: Element ): Element { - let vec_ref = &t.vec; - if (vector::is_empty(vec_ref)) default else *vector::borrow(vec_ref, 0) + let vec_ref = &self.vec; + if (vec_ref.is_empty()) default else vec_ref[0] } spec get_with_default { pragma opaque; aborts_if false; - ensures result == (if (spec_is_some(t)) spec_borrow(t) + ensures result == (if (spec_is_some(self)) spec_borrow(self) else default); } - /// Convert the none option `t` to a some option by adding `e`. - /// Aborts if `t` already holds a value - public fun fill(t: &mut Option, e: Element) { - let vec_ref = &mut t.vec; - if (vector::is_empty(vec_ref)) vector::push_back(vec_ref, e) - else abort EOPTION_IS_SET + /// Convert the none option `self` to a some option by adding `e`. + /// Aborts if `self` already holds a value + public fun fill(self: &mut Option, e: Element) { + let vec_ref = &mut self.vec; + if (vec_ref.is_empty()) vec_ref.push_back(e) else abort EOPTION_IS_SET } spec fill { pragma opaque; - aborts_if spec_is_some(t) with EOPTION_IS_SET; - ensures spec_is_some(t); - ensures spec_borrow(t) == e; + aborts_if spec_is_some(self) with EOPTION_IS_SET; + ensures spec_is_some(self); + ensures spec_borrow(self) == e; } - /// Convert a `some` option to a `none` by removing and returning the value stored inside `t` - /// Aborts if `t` does not hold a value - public fun extract(t: &mut Option): Element { - assert!(is_some(t), EOPTION_NOT_SET); - vector::pop_back(&mut t.vec) + /// Convert a `some` option to a `none` by removing and returning the value stored inside `self` + /// Aborts if `self` does not hold a value + public fun extract(self: &mut Option): Element { + assert!(self.is_some(), EOPTION_NOT_SET); + self.vec.pop_back() } spec extract { pragma opaque; include AbortsIfNone; - ensures result == spec_borrow(old(t)); - ensures spec_is_none(t); + ensures result == spec_borrow(old(self)); + ensures spec_is_none(self); } - /// Return a mutable reference to the value inside `t` - /// Aborts if `t` does not hold a value - public fun borrow_mut(t: &mut Option): &mut Element { - assert!(is_some(t), EOPTION_NOT_SET); - vector::borrow_mut(&mut t.vec, 0) + /// Return a mutable reference to the value inside `self` + /// Aborts if `self` does not hold a value + public fun borrow_mut(self: &mut Option): &mut Element { + assert!(self.is_some(), EOPTION_NOT_SET); + self.vec.borrow_mut(0) } spec borrow_mut { include AbortsIfNone; - ensures result == spec_borrow(t); - ensures t == old(t); + ensures result == spec_borrow(self); + ensures self == old(self); } - /// Swap the old value inside `t` with `e` and return the old value - /// Aborts if `t` does not hold a value - public fun swap(t: &mut Option, e: Element): Element { - assert!(is_some(t), EOPTION_NOT_SET); - let vec_ref = &mut t.vec; - let old_value = vector::pop_back(vec_ref); - vector::push_back(vec_ref, e); + /// Swap the old value inside `self` with `e` and return the old value + /// Aborts if `self` does not hold a value + public fun swap(self: &mut Option, e: Element): Element { + assert!(self.is_some(), EOPTION_NOT_SET); + let vec_ref = &mut self.vec; + let old_value = vec_ref.pop_back(); + vec_ref.push_back(e); old_value } spec swap { pragma opaque; include AbortsIfNone; - ensures result == spec_borrow(old(t)); - ensures spec_is_some(t); - ensures spec_borrow(t) == e; + ensures result == spec_borrow(old(self)); + ensures spec_is_some(self); + ensures spec_borrow(self) == e; } - /// Swap the old value inside `t` with `e` and return the old value; + /// Swap the old value inside `self` with `e` and return the old value; /// or if there is no old value, fill it with `e`. - /// Different from swap(), swap_or_fill() allows for `t` not holding a value. - public fun swap_or_fill(t: &mut Option, e: Element): Option { - let vec_ref = &mut t.vec; + /// Different from swap(), swap_or_fill() allows for `self` not holding a value. + public fun swap_or_fill( + self: &mut Option, e: Element + ): Option { + let vec_ref = &mut self.vec; let old_value = - if (vector::is_empty(vec_ref)) none() - else some(vector::pop_back(vec_ref)); - vector::push_back(vec_ref, e); + if (vec_ref.is_empty()) none() else some(vec_ref.pop_back()); + vec_ref.push_back(e); old_value } spec swap_or_fill { pragma opaque; aborts_if false; - ensures result == old(t); - ensures spec_borrow(t) == e; + ensures result == old(self); + ensures spec_borrow(self) == e; } - /// Destroys `t.` If `t` holds a value, return it. Returns `default` otherwise + /// Destroys `self.` If `self` holds a value, return it. Returns `default` otherwise public fun destroy_with_default( - t: Option, default: Element + self: Option, default: Element ): Element { - let Option { vec } = t; - if (vector::is_empty(&mut vec)) default else vector::pop_back(&mut vec) + let Option { vec } = self; + if (vec.is_empty()) default else vec.pop_back() } spec destroy_with_default { pragma opaque; aborts_if false; - ensures result == (if (spec_is_some(t)) spec_borrow(t) + ensures result == (if (spec_is_some(self)) spec_borrow(self) else default); } - /// Unpack `t` and return its contents - /// Aborts if `t` does not hold a value - public fun destroy_some(t: Option): Element { - assert!(is_some(&t), EOPTION_NOT_SET); - let Option { vec } = t; - let elem = vector::pop_back(&mut vec); - vector::destroy_empty(vec); + /// Unpack `self` and return its contents + /// Aborts if `self` does not hold a value + public fun destroy_some(self: Option): Element { + assert!(self.is_some(), EOPTION_NOT_SET); + let Option { vec } = self; + let elem = vec.pop_back(); + vec.destroy_empty(); elem } spec destroy_some { pragma opaque; include AbortsIfNone; - ensures result == spec_borrow(t); + ensures result == spec_borrow(self); } - /// Unpack `t` - /// Aborts if `t` holds a value - public fun destroy_none(t: Option) { - assert!(is_none(&t), EOPTION_IS_SET); - let Option { vec } = t; - vector::destroy_empty(vec) + /// Unpack `self` + /// Aborts if `self` holds a value + public fun destroy_none(self: Option) { + assert!(self.is_none(), EOPTION_IS_SET); + let Option { vec } = self; + vec.destroy_empty() } spec destroy_none { pragma opaque; - aborts_if spec_is_some(t) with EOPTION_IS_SET; + aborts_if spec_is_some(self) with EOPTION_IS_SET; } - /// Convert `t` into a vector of length 1 if it is `Some`, + /// Convert `self` into a vector of length 1 if it is `Some`, /// and an empty vector otherwise - public fun to_vec(t: Option): vector { - let Option { vec } = t; + public fun to_vec(self: Option): vector { + let Option { vec } = self; vec } spec to_vec { pragma opaque; aborts_if false; - ensures result == t.vec; + ensures result == self.vec; } /// Apply the function to the optional element, consuming it. Does nothing if no value present. - public inline fun for_each(o: Option, f: |Element|) { - if (is_some(&o)) { - f(destroy_some(o)) + public inline fun for_each( + self: Option, f: |Element| + ) { + if (self.is_some()) { + f(self.destroy_some()) } else { - destroy_none(o) + self.destroy_none() } } /// Apply the function to the optional element reference. Does nothing if no value present. public inline fun for_each_ref( - o: &Option, f: |&Element| + self: &Option, f: |&Element| ) { - if (is_some(o)) { - f(borrow(o)) + if (self.is_some()) { + f(self.borrow()) } } /// Apply the function to the optional element reference. Does nothing if no value present. public inline fun for_each_mut( - o: &mut Option, f: |&mut Element| + self: &mut Option, f: |&mut Element| ) { - if (is_some(o)) { - f(borrow_mut(o)) + if (self.is_some()) { + f(self.borrow_mut()) } } /// Folds the function over the optional element. public inline fun fold( - o: Option, + self: Option, init: Accumulator, f: |Accumulator, Element| Accumulator ): Accumulator { - if (is_some(&o)) { - f(init, destroy_some(o)) + if (self.is_some()) { + f(init, self.destroy_some()) } else { - destroy_none(o); + self.destroy_none(); init } } /// Maps the content of an option. public inline fun map( - o: Option, f: |Element| OtherElement + self: Option, f: |Element| OtherElement ): Option { - if (is_some(&o)) { - some(f(destroy_some(o))) + if (self.is_some()) { + some(f(self.destroy_some())) } else { - destroy_none(o); + self.destroy_none(); none() } } /// Maps the content of an option without destroying the original option. public inline fun map_ref( - o: &Option, f: |&Element| OtherElement + self: &Option, f: |&Element| OtherElement ): Option { - if (is_some(o)) { - some(f(borrow(o))) + if (self.is_some()) { + some(f(self.borrow())) } else { none() } } /// Filters the content of an option public inline fun filter( - o: Option, f: |&Element| bool + self: Option, f: |&Element| bool ): Option { - if (is_some(&o) && f(borrow(&o))) { o } + if (self.is_some() && f(self.borrow())) { self } else { none() } } /// Returns true if the option contains an element which satisfies predicate. public inline fun any( - o: &Option, p: |&Element| bool + self: &Option, p: |&Element| bool ): bool { - is_some(o) && p(borrow(o)) + self.is_some() && p(self.borrow()) } /// Utility function to destroy an option that is not droppable. - public inline fun destroy(o: Option, d: |Element|) { - let vec = to_vec(o); - vector::destroy(vec, |e| d(e)); + public inline fun destroy( + self: Option, d: |Element| + ) { + let vec = self.to_vec(); + vec.destroy(|e| d(e)); } spec module {} // switch documentation context back to module level @@ -386,7 +389,7 @@ module std::option { /// # Helper Schema spec schema AbortsIfNone { - t: Option; - aborts_if spec_is_none(t) with EOPTION_NOT_SET; + self: Option; + aborts_if spec_is_none(self) with EOPTION_NOT_SET; } } diff --git a/precompile/modules/move_stdlib/sources/signer.move b/precompile/modules/move_stdlib/sources/signer.move index 08395646..16d5c2cc 100644 --- a/precompile/modules/move_stdlib/sources/signer.move +++ b/precompile/modules/move_stdlib/sources/signer.move @@ -1,11 +1,25 @@ module std::signer { - // Borrows the address of the signer - // Conceptually, you can think of the `signer` as being a struct wrapper around an - // address - // ``` - // struct signer has drop { addr: address } - // ``` - // `borrow_address` borrows this inner field + /// signer is a builtin move type that represents an address that has been verfied by the VM. + /// + /// VM Runtime representation is equivalent to following: + /// ``` + /// enum signer has drop { + /// Master { account: address }, + /// Permissioned { account: address, permissions_address: address }, + /// } + /// ``` + /// + /// for bcs serialization: + /// + /// ``` + /// struct signer has drop { + /// account: address, + /// } + /// ``` + /// ^ The discrepency is needed to maintain backwards compatibility of signer serialization + /// semantics. + /// + /// `borrow_address` borrows this inner field native public fun borrow_address(s: &signer): &address; // Copies the address of the signer diff --git a/precompile/modules/move_stdlib/sources/string.move b/precompile/modules/move_stdlib/sources/string.move index bc030840..f9bcc844 100644 --- a/precompile/modules/move_stdlib/sources/string.move +++ b/precompile/modules/move_stdlib/sources/string.move @@ -1,6 +1,5 @@ /// The `string` module defines the `String` type which represents UTF8 encoded strings. module std::string { - use std::vector; use std::option::{Self, Option}; /// An invalid UTF8 encoding. @@ -30,52 +29,52 @@ module std::string { } /// Returns a reference to the underlying byte vector. - public fun bytes(s: &String): &vector { - &s.bytes + public fun bytes(self: &String): &vector { + &self.bytes } /// Checks whether this string is empty. - public fun is_empty(s: &String): bool { - vector::is_empty(&s.bytes) + public fun is_empty(self: &String): bool { + self.bytes.is_empty() } /// Returns the length of this string, in bytes. - public fun length(s: &String): u64 { - vector::length(&s.bytes) + public fun length(self: &String): u64 { + self.bytes.length() } /// Appends a string. - public fun append(s: &mut String, r: String) { - vector::append(&mut s.bytes, r.bytes) + public fun append(self: &mut String, r: String) { + self.bytes.append(r.bytes) } /// Appends bytes which must be in valid utf8 format. - public fun append_utf8(s: &mut String, bytes: vector) { - append(s, utf8(bytes)) + public fun append_utf8(self: &mut String, bytes: vector) { + self.append(utf8(bytes)) } /// Insert the other string at the byte index in given string. The index must be at a valid utf8 char /// boundary. - public fun insert(s: &mut String, at: u64, o: String) { - let bytes = &s.bytes; + public fun insert(self: &mut String, at: u64, o: String) { + let bytes = &self.bytes; assert!( - at <= vector::length(bytes) && internal_is_char_boundary(bytes, at), + at <= bytes.length() && internal_is_char_boundary(bytes, at), EINVALID_INDEX ); - let l = length(s); - let front = sub_string(s, 0, at); - let end = sub_string(s, at, l); - append(&mut front, o); - append(&mut front, end); - *s = front; + let l = self.length(); + let front = self.sub_string(0, at); + let end = self.sub_string(at, l); + front.append(o); + front.append(end); + *self = front; } /// Returns a sub-string using the given byte indices, where `i` is the first byte position and `j` is the start /// of the first byte not included (or the length of the string). The indices must be at valid utf8 char boundaries, /// guaranteeing that the result is valid utf8. - public fun sub_string(s: &String, i: u64, j: u64): String { - let bytes = &s.bytes; - let l = vector::length(bytes); + public fun sub_string(self: &String, i: u64, j: u64): String { + let bytes = &self.bytes; + let l = bytes.length(); assert!( j <= l && i <= j @@ -87,12 +86,12 @@ module std::string { } /// Computes the index of the first occurrence of a string. Returns `length(s)` if no occurrence found. - public fun index_of(s: &String, r: &String): u64 { - internal_index_of(&s.bytes, &r.bytes) + public fun index_of(self: &String, r: &String): u64 { + internal_index_of(&self.bytes, &r.bytes) } // Native API - native public fun internal_check_utf8(v: &vector): bool; + public native fun internal_check_utf8(v: &vector): bool; native fun internal_is_char_boundary(v: &vector, i: u64): bool; native fun internal_sub_string(v: &vector, i: u64, j: u64): vector; native fun internal_index_of(v: &vector, r: &vector): u64; diff --git a/precompile/modules/move_stdlib/sources/vector.move b/precompile/modules/move_stdlib/sources/vector.move index ff5706a9..ca1f0191 100644 --- a/precompile/modules/move_stdlib/sources/vector.move +++ b/precompile/modules/move_stdlib/sources/vector.move @@ -9,6 +9,8 @@ /// Move functions here because many have loops, requiring loop invariants to prove, and /// the return on investment didn't seem worth it for these simple functions. module std::vector { + use std::mem; + /// The index into the vector is out of bounds const EINDEX_OUT_OF_BOUNDS: u64 = 0x20000; @@ -24,51 +26,75 @@ module std::vector { /// The range in `slice` is invalid. const EINVALID_SLICE_RANGE: u64 = 0x20004; + /// Whether to utilize native vector::move_range + /// Vector module cannot call features module, due to cyclic dependency, + /// so this is a constant. + const USE_MOVE_RANGE: bool = true; + #[bytecode_instruction] /// Create an empty vector. native public fun empty(): vector; #[bytecode_instruction] /// Return the length of the vector. - native public fun length(v: &vector): u64; + native public fun length(self: &vector): u64; #[bytecode_instruction] - /// Acquire an immutable reference to the `i`th element of the vector `v`. + /// Acquire an immutable reference to the `i`th element of the vector `self`. /// Aborts if `i` is out of bounds. - native public fun borrow(v: &vector, i: u64): ∈ + native public fun borrow(self: &vector, i: u64): ∈ #[bytecode_instruction] - /// Add element `e` to the end of the vector `v`. + /// Add element `e` to the end of the vector `self`. native public fun push_back( - v: &mut vector, e: Element + self: &mut vector, e: Element ); #[bytecode_instruction] - /// Return a mutable reference to the `i`th element in the vector `v`. + /// Return a mutable reference to the `i`th element in the vector `self`. /// Aborts if `i` is out of bounds. - native public fun borrow_mut(v: &mut vector, i: u64): &mut Element; + native public fun borrow_mut( + self: &mut vector, i: u64 + ): &mut Element; #[bytecode_instruction] - /// Pop an element from the end of vector `v`. - /// Aborts if `v` is empty. - native public fun pop_back(v: &mut vector): Element; + /// Pop an element from the end of vector `self`. + /// Aborts if `self` is empty. + native public fun pop_back(self: &mut vector): Element; #[bytecode_instruction] - /// Destroy the vector `v`. - /// Aborts if `v` is not empty. - native public fun destroy_empty(v: vector); + /// Destroy the vector `self`. + /// Aborts if `self` is not empty. + native public fun destroy_empty(self: vector); #[bytecode_instruction] - /// Swaps the elements at the `i`th and `j`th indices in the vector `v`. + /// Swaps the elements at the `i`th and `j`th indices in the vector `self`. /// Aborts if `i` or `j` is out of bounds. native public fun swap( - v: &mut vector, i: u64, j: u64 + self: &mut vector, i: u64, j: u64 + ); + + /// Moves range of elements `[removal_position, removal_position + length)` from vector `from`, + /// to vector `to`, inserting them starting at the `insert_position`. + /// In the `from` vector, elements after the selected range are moved left to fill the hole + /// (i.e. range is removed, while the order of the rest of the elements is kept) + /// In the `to` vector, elements after the `insert_position` are moved to the right to make + /// space for new elements (i.e. range is inserted, while the order of the rest of the + /// elements is kept). + /// Move prevents from having two mutable references to the same value, so `from` and `to` + /// vectors are always distinct. + native public fun move_range( + from: &mut vector, + removal_position: u64, + length: u64, + to: &mut vector, + insert_position: u64 ); /// Return an vector of size one containing element `e`. public fun singleton(e: Element): vector { let v = empty(); - push_back(&mut v, e); + v.push_back(e); v } @@ -77,27 +103,27 @@ module std::vector { ensures result == vec(e); } - /// Reverses the order of the elements in the vector `v` in place. - public fun reverse(v: &mut vector) { - let len = length(v); - reverse_slice(v, 0, len); + /// Reverses the order of the elements in the vector `self` in place. + public fun reverse(self: &mut vector) { + let len = self.length(); + self.reverse_slice(0, len); } spec reverse { pragma intrinsic = true; } - /// Reverses the order of the elements [left, right) in the vector `v` in place. + /// Reverses the order of the elements [left, right) in the vector `self` in place. public fun reverse_slice( - v: &mut vector, left: u64, right: u64 + self: &mut vector, left: u64, right: u64 ) { assert!(left <= right, EINVALID_RANGE); if (left == right) return; - right = right - 1; + right -= 1; while (left < right) { - swap(v, left, right); - left = left + 1; - right = right - 1; + self.swap(left, right); + left += 1; + right -= 1; } } @@ -105,12 +131,19 @@ module std::vector { pragma intrinsic = true; } - /// Pushes all of the elements of the `other` vector into the `lhs` vector. + /// Pushes all of the elements of the `other` vector into the `self` vector. public fun append( - lhs: &mut vector, other: vector + self: &mut vector, other: vector ) { - reverse(&mut other); - reverse_append(lhs, other); + if (USE_MOVE_RANGE) { + let self_length = self.length(); + let other_length = other.length(); + move_range(&mut other, 0, other_length, self, self_length); + other.destroy_empty(); + } else { + other.reverse(); + self.reverse_append(other); + } } spec append { @@ -121,27 +154,43 @@ module std::vector { pragma intrinsic = true; } - /// Pushes all of the elements of the `other` vector into the `lhs` vector. + /// Pushes all of the elements of the `other` vector into the `self` vector. public fun reverse_append( - lhs: &mut vector, other: vector + self: &mut vector, other: vector ) { - let len = length(&other); + let len = other.length(); while (len > 0) { - push_back(lhs, pop_back(&mut other)); - len = len - 1; + self.push_back(other.pop_back()); + len -= 1; }; - destroy_empty(other); + other.destroy_empty(); } spec reverse_append { pragma intrinsic = true; } - /// Trim a vector to a smaller size, returning the evicted elements in order - public fun trim(v: &mut vector, new_len: u64): vector { - let res = trim_reverse(v, new_len); - reverse(&mut res); - res + /// Splits (trims) the collection into two at the given index. + /// Returns a newly allocated vector containing the elements in the range [new_len, len). + /// After the call, the original vector will be left containing the elements [0, new_len) + /// with its previous capacity unchanged. + /// In many languages this is also called `split_off`. + public fun trim(self: &mut vector, new_len: u64): vector { + let len = self.length(); + assert!(new_len <= len, EINDEX_OUT_OF_BOUNDS); + + let other = empty(); + if (USE_MOVE_RANGE) { + move_range(self, new_len, len - new_len, &mut other, 0); + } else { + while (len > new_len) { + other.push_back(self.pop_back()); + len -= 1; + }; + other.reverse(); + }; + + other } spec trim { @@ -150,14 +199,14 @@ module std::vector { /// Trim a vector to a smaller size, returning the evicted elements in reverse order public fun trim_reverse( - v: &mut vector, new_len: u64 + self: &mut vector, new_len: u64 ): vector { - let len = length(v); + let len = self.length(); assert!(new_len <= len, EINDEX_OUT_OF_BOUNDS); let result = empty(); while (new_len < len) { - push_back(&mut result, pop_back(v)); - len = len - 1; + result.push_back(self.pop_back()); + len -= 1; }; result } @@ -166,18 +215,18 @@ module std::vector { pragma intrinsic = true; } - /// Return `true` if the vector `v` has no elements and `false` otherwise. - public fun is_empty(v: &vector): bool { - length(v) == 0 + /// Return `true` if the vector `self` has no elements and `false` otherwise. + public fun is_empty(self: &vector): bool { + self.length() == 0 } - /// Return true if `e` is in the vector `v`. - public fun contains(v: &vector, e: &Element): bool { + /// Return true if `e` is in the vector `self`. + public fun contains(self: &vector, e: &Element): bool { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - if (borrow(v, i) == e) return true; - i = i + 1; + if (self.borrow(i) == e) return true; + i += 1; }; false } @@ -186,14 +235,14 @@ module std::vector { pragma intrinsic = true; } - /// Return `(true, i)` if `e` is in the vector `v` at index `i`. + /// Return `(true, i)` if `e` is in the vector `self` at index `i`. /// Otherwise, returns `(false, 0)`. - public fun index_of(v: &vector, e: &Element): (bool, u64) { + public fun index_of(self: &vector, e: &Element): (bool, u64) { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - if (borrow(v, i) == e) return (true, i); - i = i + 1; + if (self.borrow(i) == e) return (true, i); + i += 1; }; (false, 0) } @@ -206,20 +255,20 @@ module std::vector { /// the predicate, only the index of the first one is returned. /// Otherwise, returns `(false, 0)`. public inline fun find( - v: &vector, f: |&Element| bool + self: &vector, f: |&Element| bool ): (bool, u64) { let find = false; let found_index = 0; let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { // Cannot call return in an inline function so we need to resort to break here. - if (f(borrow(v, i))) { + if (f(self.borrow(i))) { find = true; found_index = i; break }; - i = i + 1; + i += 1; }; (find, found_index) } @@ -227,14 +276,31 @@ module std::vector { /// Insert a new element at position 0 <= i <= length, using O(length - i) time. /// Aborts if out of bounds. public fun insert( - v: &mut vector, i: u64, e: Element + self: &mut vector, i: u64, e: Element ) { - let len = length(v); + let len = self.length(); assert!(i <= len, EINDEX_OUT_OF_BOUNDS); - push_back(v, e); - while (i < len) { - swap(v, i, len); - i = i + 1; + + if (USE_MOVE_RANGE) { + if (i + 2 >= len) { + // When we are close to the end, it is cheaper to not create + // a temporary vector, and swap directly + self.push_back(e); + while (i < len) { + self.swap(i, len); + i += 1; + }; + } else { + let other = singleton(e); + move_range(&mut other, 0, 1, self, i); + other.destroy_empty(); + } + } else { + self.push_back(e); + while (i < len) { + self.swap(i, len); + i += 1; + }; }; } @@ -242,40 +308,59 @@ module std::vector { pragma intrinsic = true; } - /// Remove the `i`th element of the vector `v`, shifting all subsequent elements. + /// Remove the `i`th element of the vector `self`, shifting all subsequent elements. /// This is O(n) and preserves ordering of elements in the vector. /// Aborts if `i` is out of bounds. - public fun remove(v: &mut vector, i: u64): Element { - let len = length(v); + public fun remove(self: &mut vector, i: u64): Element { + let len = self.length(); // i out of bounds; abort if (i >= len) abort EINDEX_OUT_OF_BOUNDS; - len = len - 1; - while (i < len) swap(v, i, { - i = i + 1; - i - }); - pop_back(v) + if (USE_MOVE_RANGE) { + // When we are close to the end, it is cheaper to not create + // a temporary vector, and swap directly + if (i + 3 >= len) { + len -= 1; + while (i < len) self.swap(i, { + i += 1; + i + }); + self.pop_back() + } else { + let other = empty(); + move_range(self, i, 1, &mut other, 0); + let result = other.pop_back(); + other.destroy_empty(); + result + } + } else { + len -= 1; + while (i < len) self.swap(i, { + i += 1; + i + }); + self.pop_back() + } } spec remove { pragma intrinsic = true; } - /// Remove the first occurrence of a given value in the vector `v` and return it in a vector, shifting all + /// Remove the first occurrence of a given value in the vector `self` and return it in a vector, shifting all /// subsequent elements. /// This is O(n) and preserves ordering of elements in the vector. /// This returns an empty vector if the value isn't present in the vector. /// Note that this cannot return an option as option uses vector and there'd be a circular dependency between option /// and vector. public fun remove_value( - v: &mut vector, val: &Element + self: &mut vector, val: &Element ): vector { // This doesn't cost a O(2N) run time as index_of scans from left to right and stops when the element is found, // while remove would continue from the identified index to the end of the vector. - let (found, index) = index_of(v, val); + let (found, index) = self.index_of(val); if (found) { - vector[remove(v, index)] + vector[self.remove(index)] } else { vector[] } @@ -285,238 +370,260 @@ module std::vector { pragma intrinsic = true; } - /// Swap the `i`th element of the vector `v` with the last element and then pop the vector. + /// Swap the `i`th element of the vector `self` with the last element and then pop the vector. /// This is O(1), but does not preserve ordering of elements in the vector. /// Aborts if `i` is out of bounds. - public fun swap_remove(v: &mut vector, i: u64): Element { - assert!(!is_empty(v), EINDEX_OUT_OF_BOUNDS); - let last_idx = length(v) - 1; - swap(v, i, last_idx); - pop_back(v) + public fun swap_remove(self: &mut vector, i: u64): Element { + assert!(!self.is_empty(), EINDEX_OUT_OF_BOUNDS); + let last_idx = self.length() - 1; + self.swap(i, last_idx); + self.pop_back() } spec swap_remove { pragma intrinsic = true; } + /// Replace the `i`th element of the vector `self` with the given value, and return + /// to the caller the value that was there before. + /// Aborts if `i` is out of bounds. + public fun replace( + self: &mut vector, i: u64, val: Element + ): Element { + let last_idx = self.length(); + assert!(i < last_idx, EINDEX_OUT_OF_BOUNDS); + if (USE_MOVE_RANGE) { + mem::replace(self.borrow_mut(i), val) + } else { + self.push_back(val); + self.swap(i, last_idx); + self.pop_back() + } + } + /// Apply the function to each element in the vector, consuming it. - public inline fun for_each(v: vector, f: |Element|) { - reverse(&mut v); // We need to reverse the vector to consume it efficiently - for_each_reverse(v, |e| f(e)); + public inline fun for_each( + self: vector, f: |Element| + ) { + self.reverse(); // We need to reverse the vector to consume it efficiently + self.for_each_reverse(|e| f(e)); } /// Apply the function to each element in the vector, consuming it. public inline fun for_each_reverse( - v: vector, f: |Element| + self: vector, f: |Element| ) { - let len = length(&v); + let len = self.length(); while (len > 0) { - f(pop_back(&mut v)); - len = len - 1; + f(self.pop_back()); + len -= 1; }; - destroy_empty(v) + self.destroy_empty() } /// Apply the function to a reference of each element in the vector. public inline fun for_each_ref( - v: &vector, f: |&Element| + self: &vector, f: |&Element| ) { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - f(borrow(v, i)); - i = i + 1 + f(self.borrow(i)); + i += 1 } } /// Apply the function to each pair of elements in the two given vectors, consuming them. public inline fun zip( - v1: vector, + self: vector, v2: vector, f: |Element1, Element2| ) { // We need to reverse the vectors to consume it efficiently - reverse(&mut v1); - reverse(&mut v2); - zip_reverse(v1, v2, |e1, e2| f(e1, e2)); + self.reverse(); + v2.reverse(); + self.zip_reverse( + v2, |e1, e2| f(e1, e2) + ); } /// Apply the function to each pair of elements in the two given vectors in the reverse order, consuming them. /// This errors out if the vectors are not of the same length. public inline fun zip_reverse( - v1: vector, + self: vector, v2: vector, f: |Element1, Element2| ) { - let len = length(&v1); + let len = self.length(); // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it // due to how inline functions work. - assert!(len == length(&v2), 0x20002); + assert!(len == v2.length(), 0x20002); while (len > 0) { - f(pop_back(&mut v1), pop_back(&mut v2)); - len = len - 1; + f(self.pop_back(), v2.pop_back()); + len -= 1; }; - destroy_empty(v1); - destroy_empty(v2); + self.destroy_empty(); + v2.destroy_empty(); } /// Apply the function to the references of each pair of elements in the two given vectors. /// This errors out if the vectors are not of the same length. public inline fun zip_ref( - v1: &vector, + self: &vector, v2: &vector, f: |&Element1, &Element2| ) { - let len = length(v1); + let len = self.length(); // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it // due to how inline functions work. - assert!(len == length(v2), 0x20002); + assert!(len == v2.length(), 0x20002); let i = 0; while (i < len) { - f(borrow(v1, i), borrow(v2, i)); - i = i + 1 + f(self.borrow(i), v2.borrow(i)); + i += 1 } } /// Apply the function to a reference of each element in the vector with its index. public inline fun enumerate_ref( - v: &vector, f: |u64, &Element| + self: &vector, f: |u64, &Element| ) { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - f(i, borrow(v, i)); - i = i + 1; + f(i, self.borrow(i)); + i += 1; }; } /// Apply the function to a mutable reference to each element in the vector. public inline fun for_each_mut( - v: &mut vector, f: |&mut Element| + self: &mut vector, f: |&mut Element| ) { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - f(borrow_mut(v, i)); - i = i + 1 + f(self.borrow_mut(i)); + i += 1 } } /// Apply the function to mutable references to each pair of elements in the two given vectors. /// This errors out if the vectors are not of the same length. public inline fun zip_mut( - v1: &mut vector, + self: &mut vector, v2: &mut vector, f: |&mut Element1, &mut Element2| ) { let i = 0; - let len = length(v1); + let len = self.length(); // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it // due to how inline functions work. - assert!(len == length(v2), 0x20002); + assert!(len == v2.length(), 0x20002); while (i < len) { - f(borrow_mut(v1, i), borrow_mut(v2, i)); - i = i + 1 + f(self.borrow_mut(i), v2.borrow_mut(i)); + i += 1 } } /// Apply the function to a mutable reference of each element in the vector with its index. public inline fun enumerate_mut( - v: &mut vector, f: |u64, &mut Element| + self: &mut vector, f: |u64, &mut Element| ) { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - f(i, borrow_mut(v, i)); - i = i + 1; + f(i, self.borrow_mut(i)); + i += 1; }; } /// Fold the function over the elements. For example, `fold(vector[1,2,3], 0, f)` will execute /// `f(f(f(0, 1), 2), 3)` public inline fun fold( - v: vector, + self: vector, init: Accumulator, f: |Accumulator, Element| Accumulator ): Accumulator { let accu = init; - for_each(v, |elem| accu = f(accu, elem)); + self.for_each(|elem| accu = f(accu, elem)); accu } /// Fold right like fold above but working right to left. For example, `fold(vector[1,2,3], 0, f)` will execute /// `f(1, f(2, f(3, 0)))` public inline fun foldr( - v: vector, + self: vector, init: Accumulator, f: |Element, Accumulator| Accumulator ): Accumulator { let accu = init; - for_each_reverse(v, |elem| accu = f(elem, accu)); + self.for_each_reverse(|elem| accu = f(elem, accu)); accu } /// Map the function over the references of the elements of the vector, producing a new vector without modifying the /// original vector. public inline fun map_ref( - v: &vector, f: |&Element| NewElement + self: &vector, f: |&Element| NewElement ): vector { let result = vector[]; - for_each_ref(v, |elem| push_back(&mut result, f(elem))); + self.for_each_ref(|elem| result.push_back(f(elem))); result } /// Map the function over the references of the element pairs of two vectors, producing a new vector from the return /// values without modifying the original vectors. public inline fun zip_map_ref( - v1: &vector, + self: &vector, v2: &vector, f: |&Element1, &Element2| NewElement ): vector { // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it // due to how inline functions work. - assert!(length(v1) == length(v2), 0x20002); + assert!(self.length() == v2.length(), 0x20002); let result = vector[]; - zip_ref(v1, v2, |e1, e2| push_back(&mut result, f(e1, e2))); + self.zip_ref( + v2, |e1, e2| result.push_back(f(e1, e2)) + ); result } /// Map the function over the elements of the vector, producing a new vector. public inline fun map( - v: vector, f: |Element| NewElement + self: vector, f: |Element| NewElement ): vector { let result = vector[]; - for_each(v, |elem| push_back(&mut result, f(elem))); + self.for_each(|elem| result.push_back(f(elem))); result } /// Map the function over the element pairs of the two vectors, producing a new vector. public inline fun zip_map( - v1: vector, + self: vector, v2: vector, f: |Element1, Element2| NewElement ): vector { // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it // due to how inline functions work. - assert!(length(&v1) == length(&v2), 0x20002); + assert!(self.length() == v2.length(), 0x20002); let result = vector[]; - zip(v1, v2, |e1, e2| push_back(&mut result, f(e1, e2))); + self.zip( + v2, |e1, e2| result.push_back(f(e1, e2)) + ); result } /// Filter the vector using the boolean function, removing all elements for which `p(e)` is not true. public inline fun filter( - v: vector, p: |&Element| bool + self: vector, p: |&Element| bool ): vector { let result = vector[]; - for_each( - v, - |elem| { - if (p(&elem)) push_back(&mut result, elem); - } - ); + self.for_each(|elem| { + if (p(&elem)) result.push_back(elem); + }); result } @@ -524,31 +631,31 @@ module std::vector { /// Preserves the relative order of the elements for which pred is true, /// BUT NOT for the elements for which pred is false. public inline fun partition( - v: &mut vector, pred: |&Element| bool + self: &mut vector, pred: |&Element| bool ): u64 { let i = 0; - let len = length(v); + let len = self.length(); while (i < len) { - if (!pred(borrow(v, i))) break; - i = i + 1; + if (!pred(self.borrow(i))) break; + i += 1; }; let p = i; - i = i + 1; + i += 1; while (i < len) { - if (pred(borrow(v, i))) { - swap(v, p, i); - p = p + 1; + if (pred(self.borrow(i))) { + self.swap(p, i); + p += 1; }; - i = i + 1; + i += 1; }; p } /// rotate(&mut [1, 2, 3, 4, 5], 2) -> [3, 4, 5, 1, 2] in place, returns the split point /// ie. 3 in the example above - public fun rotate(v: &mut vector, rot: u64): u64 { - let len = length(v); - rotate_slice(v, 0, rot, len) + public fun rotate(self: &mut vector, rot: u64): u64 { + let len = self.length(); + self.rotate_slice(0, rot, len) } spec rotate { @@ -558,14 +665,14 @@ module std::vector { /// Same as above but on a sub-slice of an array [left, right) with left <= rot <= right /// returns the public fun rotate_slice( - v: &mut vector, + self: &mut vector, left: u64, rot: u64, right: u64 ): u64 { - reverse_slice(v, left, rot); - reverse_slice(v, rot, right); - reverse_slice(v, left, right); + self.reverse_slice(left, rot); + self.reverse_slice(rot, right); + self.reverse_slice(left, right); left + (right - rot) } @@ -576,58 +683,60 @@ module std::vector { /// Partition the array based on a predicate p, this routine is stable and thus /// preserves the relative order of the elements in the two partitions. public inline fun stable_partition( - v: &mut vector, p: |&Element| bool + self: &mut vector, p: |&Element| bool ): u64 { - let len = length(v); + let len = self.length(); let t = empty(); let f = empty(); while (len > 0) { - let e = pop_back(v); + let e = self.pop_back(); if (p(&e)) { - push_back(&mut t, e); + t.push_back(e); } else { - push_back(&mut f, e); + f.push_back(e); }; - len = len - 1; + len -= 1; }; - let pos = length(&t); - reverse_append(v, t); - reverse_append(v, f); + let pos = t.length(); + self.reverse_append(t); + self.reverse_append(f); pos } /// Return true if any element in the vector satisfies the predicate. public inline fun any( - v: &vector, p: |&Element| bool + self: &vector, p: |&Element| bool ): bool { let result = false; let i = 0; - while (i < length(v)) { - result = p(borrow(v, i)); + while (i < self.length()) { + result = p(self.borrow(i)); if (result) { break }; - i = i + 1 + i += 1 }; result } /// Return true if all elements in the vector satisfy the predicate. public inline fun all( - v: &vector, p: |&Element| bool + self: &vector, p: |&Element| bool ): bool { let result = true; let i = 0; - while (i < length(v)) { - result = p(borrow(v, i)); + while (i < self.length()) { + result = p(self.borrow(i)); if (!result) { break }; - i = i + 1 + i += 1 }; result } /// Destroy a vector, just a wrapper around for_each_reverse with a descriptive name /// when used in the context of destroying a vector. - public inline fun destroy(v: vector, d: |Element|) { - for_each_reverse(v, |e| d(e)) + public inline fun destroy( + self: vector, d: |Element| + ) { + self.for_each_reverse(|e| d(e)) } public fun range(start: u64, end: u64): vector { @@ -639,24 +748,24 @@ module std::vector { let vec = vector[]; while (start < end) { - push_back(&mut vec, start); - start = start + step; + vec.push_back(start); + start += step; }; vec } public fun slice( - v: &vector, start: u64, end: u64 + self: &vector, start: u64, end: u64 ): vector { assert!( - start <= end && end <= length(v), + start <= end && end <= self.length(), EINVALID_SLICE_RANGE ); let vec = vector[]; while (start < end) { - push_back(&mut vec, *borrow(v, start)); - start = start + 1; + vec.push_back(self[start]); + start += 1; }; vec } @@ -669,23 +778,23 @@ module std::vector { /// # Helper Functions spec module { - /// Check if `v1` is equal to the result of adding `e` at the end of `v2` - fun eq_push_back(v1: vector, v2: vector, e: Element): bool { - len(v1) == len(v2) + 1 - && v1[len(v1) - 1] == e - && v1[0..len(v1) - 1] == v2[0..len(v2)] + /// Check if `self` is equal to the result of adding `e` at the end of `v2` + fun eq_push_back(self: vector, v2: vector, e: Element): bool { + len(self) == len(v2) + 1 + && self[len(self) - 1] == e + && self[0..len(self) - 1] == v2[0..len(v2)] } - /// Check if `v` is equal to the result of concatenating `v1` and `v2` - fun eq_append(v: vector, v1: vector, v2: vector): bool { - len(v) == len(v1) + len(v2) - && v[0..len(v1)] == v1 - && v[len(v1)..len(v)] == v2 + /// Check if `self` is equal to the result of concatenating `v1` and `v2` + fun eq_append(self: vector, v1: vector, v2: vector): bool { + len(self) == len(v1) + len(v2) + && self[0..len(v1)] == v1 + && self[len(v1)..len(self)] == v2 } - /// Check `v1` is equal to the result of removing the first element of `v2` - fun eq_pop_front(v1: vector, v2: vector): bool { - len(v1) + 1 == len(v2) && v1 == v2[1..len(v2)] + /// Check `self` is equal to the result of removing the first element of `v2` + fun eq_pop_front(self: vector, v2: vector): bool { + len(self) + 1 == len(v2) && self == v2[1..len(v2)] } /// Check that `v1` is equal to the result of removing the element at index `i` from `v2`. @@ -695,9 +804,9 @@ module std::vector { && v1[i..len(v1)] == v2[i + 1..len(v2)] } - /// Check if `v` contains `e`. - fun spec_contains(v: vector, e: Element): bool { - exists x in v: x == e + /// Check if `self` contains `e`. + fun spec_contains(self: vector, e: Element): bool { + exists x in self: x == e } } }