use self::trie::StateTrie;
use crate::{
boxed::Box,
cell::RefCell,
cmp,
collections::{BTreeMap, BTreeSet},
num,
rc::Rc,
*,
};
use convert::TryInto;
#[cfg(feature = "concordium-quickcheck")]
use quickcheck::*;
mod trie;
#[derive(Default, Clone)]
pub struct TestChainMeta {
pub(crate) slot_time: Option<SlotTime>,
}
#[derive(Debug, Clone)]
pub struct TestPolicy {
position: usize,
policy: Policy<Rc<[(AttributeTag, AttributeValue)]>>,
}
impl TestPolicy {
fn new(policy: OwnedPolicy) -> Self {
let policy = Policy {
identity_provider: policy.identity_provider,
created_at: policy.created_at,
valid_to: policy.valid_to,
items: policy.items.into(),
};
Self {
position: 0,
policy,
}
}
}
#[derive(Default, Clone)]
#[doc(hidden)]
pub struct TestCommonData<'a> {
pub(crate) metadata: TestChainMeta,
pub(crate) parameter: Option<&'a [u8]>,
pub(crate) policies: Option<Vec<TestPolicy>>,
}
#[derive(Default, Clone)]
pub struct TestContext<'a, C> {
pub(crate) common: TestCommonData<'a>,
pub(crate) custom: C,
}
pub type TestInitContext<'a> = TestContext<'a, TestInitOnlyData>;
#[derive(Default)]
#[doc(hidden)]
pub struct TestInitOnlyData {
init_origin: Option<AccountAddress>,
}
pub type TestReceiveContext<'a> = TestContext<'a, TestReceiveOnlyData>;
#[derive(Default)]
#[doc(hidden)]
pub struct TestReceiveOnlyData {
pub(crate) invoker: Option<AccountAddress>,
pub(crate) self_address: Option<ContractAddress>,
pub(crate) sender: Option<Address>,
pub(crate) owner: Option<AccountAddress>,
pub(crate) named_entrypoint: Option<OwnedEntrypointName>,
}
pub struct TestParameterCursor<'a> {
cursor: Cursor<&'a [u8]>,
}
impl<'a> TestParameterCursor<'a> {
fn new(data: &'a [u8]) -> Self {
TestParameterCursor {
cursor: Cursor::new(data),
}
}
}
impl<'a> AsRef<[u8]> for TestParameterCursor<'a> {
#[inline(always)]
fn as_ref(&self) -> &[u8] { self.cursor.as_ref() }
}
impl<'a> Seek for TestParameterCursor<'a> {
type Err = ();
#[inline(always)]
fn seek(&mut self, seek: SeekFrom) -> Result<u32, Self::Err> { self.cursor.seek(seek) }
#[inline(always)]
fn cursor_position(&self) -> u32 { self.cursor.cursor_position() }
}
impl<'a> Read for TestParameterCursor<'a> {
#[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> ParseResult<usize> { self.cursor.read(buf) }
}
impl<'a> HasParameter for TestParameterCursor<'a> {}
impl TestChainMeta {
pub fn empty() -> Self { Default::default() }
pub fn set_slot_time(&mut self, value: SlotTime) -> &mut Self {
self.slot_time = Some(value);
self
}
}
impl<'a, C> TestContext<'a, C> {
pub fn push_policy(&mut self, value: OwnedPolicy) -> &mut Self {
if let Some(policies) = self.common.policies.as_mut() {
policies.push(TestPolicy::new(value));
} else {
self.common.policies = Some(vec![TestPolicy::new(value)])
}
self
}
pub fn empty_policies(&mut self) -> &mut Self {
self.common.policies = Some(Vec::new());
self
}
pub fn set_parameter(&mut self, value: &'a [u8]) -> &mut Self {
self.common.parameter = Some(value);
self
}
pub fn metadata_mut(&mut self) -> &mut TestChainMeta { &mut self.common.metadata }
pub fn set_metadata_slot_time(&mut self, value: SlotTime) -> &mut Self {
self.metadata_mut().set_slot_time(value);
self
}
}
impl<'a> TestInitContext<'a> {
pub fn empty() -> Self { Default::default() }
pub fn set_init_origin(&mut self, value: AccountAddress) -> &mut Self {
self.custom.init_origin = Some(value);
self
}
}
impl<'a> TestReceiveContext<'a> {
pub fn empty() -> Self { Default::default() }
pub fn set_invoker(&mut self, value: AccountAddress) -> &mut Self {
self.custom.invoker = Some(value);
self
}
pub fn set_self_address(&mut self, value: ContractAddress) -> &mut Self {
self.custom.self_address = Some(value);
self
}
pub fn set_sender(&mut self, value: Address) -> &mut Self {
self.custom.sender = Some(value);
self
}
pub fn set_owner(&mut self, value: AccountAddress) -> &mut Self {
self.custom.owner = Some(value);
self
}
pub fn set_named_entrypoint(&mut self, value: OwnedEntrypointName) -> &mut Self {
self.custom.named_entrypoint = Some(value);
self
}
}
fn unwrap_ctx_field<A>(opt: Option<A>, name: &str) -> A {
match opt {
Some(v) => v,
None => fail!(
"Unset field on test context '{}', make sure to set all the fields necessary for the \
contract",
name
),
}
}
impl HasChainMetadata for TestChainMeta {
fn slot_time(&self) -> SlotTime { unwrap_ctx_field(self.slot_time, "metadata.slot_time") }
}
pub struct TestIterator {
items: Rc<[(AttributeTag, AttributeValue)]>,
position: usize,
}
impl HasPolicy for TestPolicy {
type Iterator = TestIterator;
fn identity_provider(&self) -> IdentityProvider { self.policy.identity_provider }
fn created_at(&self) -> Timestamp { self.policy.created_at }
fn valid_to(&self) -> Timestamp { self.policy.valid_to }
fn next_item(&mut self, buf: &mut [u8; 31]) -> Option<(AttributeTag, u8)> {
if let Some(item) = self.policy.items.get(self.position) {
let len = item.1.as_ref().len();
buf[0..len].copy_from_slice(item.1.as_ref());
self.position += 1;
Some((item.0, len as u8))
} else {
None
}
}
fn attributes(&self) -> Self::Iterator {
TestIterator {
items: self.policy.items.clone(),
position: 0,
}
}
}
impl Iterator for TestIterator {
type Item = (AttributeTag, AttributeValue);
fn next(&mut self) -> Option<Self::Item> {
let elem = self.items.get(self.position)?;
self.position += 1;
Some(*elem)
}
}
impl<'a, C> HasCommonData for TestContext<'a, C> {
type MetadataType = TestChainMeta;
type ParamType = TestParameterCursor<'a>;
type PolicyIteratorType = crate::vec::IntoIter<TestPolicy>;
type PolicyType = TestPolicy;
fn parameter_cursor(&self) -> Self::ParamType {
TestParameterCursor::new(unwrap_ctx_field(self.common.parameter, "parameter"))
}
fn metadata(&self) -> &Self::MetadataType { &self.common.metadata }
fn policies(&self) -> Self::PolicyIteratorType {
unwrap_ctx_field(self.common.policies.clone(), "policies").into_iter()
}
}
impl<'a> HasInitContext for TestInitContext<'a> {
type InitData = ();
fn open(_data: Self::InitData) -> Self { TestInitContext::default() }
fn init_origin(&self) -> AccountAddress {
unwrap_ctx_field(self.custom.init_origin, "init_origin")
}
}
impl<'a> HasReceiveContext for TestReceiveContext<'a> {
type ReceiveData = ();
fn open(_data: Self::ReceiveData) -> Self { TestReceiveContext::default() }
fn invoker(&self) -> AccountAddress { unwrap_ctx_field(self.custom.invoker, "invoker") }
fn self_address(&self) -> ContractAddress {
unwrap_ctx_field(self.custom.self_address, "self_address")
}
fn sender(&self) -> Address { unwrap_ctx_field(self.custom.sender, "sender") }
fn owner(&self) -> AccountAddress { unwrap_ctx_field(self.custom.owner, "owner") }
fn named_entrypoint(&self) -> OwnedEntrypointName {
unwrap_ctx_field(self.custom.named_entrypoint.clone(), "named_entrypoint")
}
}
pub struct TestLogger {
pub logs: Vec<Vec<u8>>,
}
impl HasLogger for TestLogger {
fn init() -> Self {
Self {
logs: Vec::new(),
}
}
fn log_raw(&mut self, event: &[u8]) -> Result<(), LogError> {
if event.len() > constants::MAX_LOG_SIZE {
return Err(LogError::Malformed);
}
if self.logs.len() >= constants::MAX_NUM_LOGS {
return Err(LogError::Full);
}
self.logs.push(event.to_vec());
Ok(())
}
}
#[doc(hidden)]
#[cfg(all(feature = "wasm-test", target_arch = "wasm32"))]
pub fn report_error(message: &str, filename: &str, line: u32, column: u32) {
let msg_bytes = message.as_bytes();
let filename_bytes = filename.as_bytes();
unsafe {
crate::prims::report_error(
msg_bytes.as_ptr(),
msg_bytes.len() as u32,
filename_bytes.as_ptr(),
filename_bytes.len() as u32,
line,
column,
)
};
}
#[doc(hidden)]
#[cfg(not(all(feature = "wasm-test", target_arch = "wasm32")))]
pub fn report_error(_message: &str, _filename: &str, _line: u32, _column: u32) {}
#[derive(Debug, PartialEq, Eq)]
pub enum TestStateError {
Overflow,
Write,
Offset,
Default,
EntryDeleted,
}
impl Default for TestStateError {
fn default() -> Self { Self::Default }
}
impl From<num::TryFromIntError> for TestStateError {
fn from(_: num::TryFromIntError) -> Self { TestStateError::Overflow }
}
impl From<TestStateError> for ParseError {
fn from(_: TestStateError) -> Self { ParseError::default() }
}
#[derive(Debug, Clone)]
pub enum TestStateEntryData {
EntryDeleted,
EntryExists(Vec<u8>),
}
impl TestStateEntryData {
pub(crate) fn new_from(data: Vec<u8>) -> Self { Self::EntryExists(data) }
pub(crate) fn new() -> Self { Self::EntryExists(Vec::new()) }
pub(crate) fn data(&self) -> Result<&[u8], TestStateError> {
match self {
TestStateEntryData::EntryDeleted => Err(TestStateError::EntryDeleted),
TestStateEntryData::EntryExists(v) => Ok(v),
}
}
pub(crate) fn data_mut(&mut self) -> Result<&mut Vec<u8>, TestStateError> {
match self {
TestStateEntryData::EntryDeleted => Err(TestStateError::EntryDeleted),
TestStateEntryData::EntryExists(v) => Ok(v),
}
}
}
impl From<Vec<u8>> for TestStateEntryData {
fn from(data: Vec<u8>) -> Self { Self::new_from(data) }
}
#[derive(Debug)]
pub struct TestStateEntry {
pub(crate) cursor: Cursor<Rc<RefCell<TestStateEntryData>>>,
pub(crate) key: Vec<u8>,
pub(crate) state_entry_id: StateEntryId,
}
impl TestStateEntry {
pub(crate) fn open(
data: Rc<RefCell<TestStateEntryData>>,
key: Vec<u8>,
state_entry_id: StateEntryId,
) -> Self {
Self {
cursor: Cursor::new(data),
key,
state_entry_id,
}
}
}
#[derive(Debug, Clone)]
pub struct TestStateApi {
trie: Rc<RefCell<StateTrie>>,
}
impl HasStateApi for TestStateApi {
type EntryType = TestStateEntry;
type IterType = trie::TestStateIter;
fn create_entry(&mut self, key: &[u8]) -> Result<Self::EntryType, StateError> {
self.trie.borrow_mut().create_entry(key)
}
fn lookup_entry(&self, key: &[u8]) -> Option<Self::EntryType> { self.trie.borrow().lookup(key) }
fn delete_entry(&mut self, entry: Self::EntryType) -> Result<(), StateError> {
self.trie.borrow_mut().delete_entry(entry)
}
fn delete_prefix(&mut self, prefix: &[u8]) -> Result<bool, StateError> {
self.trie.borrow_mut().delete_prefix(prefix)
}
fn iterator(&self, prefix: &[u8]) -> Result<Self::IterType, StateError> {
self.trie.borrow().iterator(prefix)
}
fn delete_iterator(&mut self, iter: Self::IterType) {
self.trie.borrow_mut().delete_iterator(iter);
}
}
pub type TestStateMapIter<'a, K, V> = StateMapIter<'a, K, V, TestStateApi>;
pub type TestStateMapIterMut<'a, K, V> = StateMapIterMut<'a, K, V, TestStateApi>;
pub type TestStateSetIter<'a, T> = StateSetIter<'a, T, TestStateApi>;
impl TestStateApi {
pub fn new() -> Self {
Self {
trie: Rc::new(RefCell::new(StateTrie::new())),
}
}
pub(crate) fn clone_deep(&self) -> Self {
Self {
trie: Rc::new(RefCell::new(self.trie.borrow().clone_deep())),
}
}
}
impl Default for TestStateApi {
fn default() -> Self { Self::new() }
}
pub type TestStateBuilder = StateBuilder<TestStateApi>;
impl TestStateBuilder {
pub fn new() -> Self { Self::open(TestStateApi::new()) }
}
#[cfg(not(feature = "crypto-primitives"))]
type MockFnVerifyEd25519 = Box<dyn FnMut(PublicKeyEd25519, SignatureEd25519, &[u8]) -> bool>;
#[cfg(not(feature = "crypto-primitives"))]
type MockFnEcdsaSecp256k1 =
Box<dyn FnMut(PublicKeyEcdsaSecp256k1, SignatureEcdsaSecp256k1, [u8; 32]) -> bool>;
#[cfg(not(feature = "crypto-primitives"))]
type MockFnHash<T> = Box<dyn FnMut(&[u8]) -> T>;
pub struct TestCryptoPrimitives {
#[cfg(not(feature = "crypto-primitives"))]
verify_ed25519_signature_mock: RefCell<Option<MockFnVerifyEd25519>>,
#[cfg(not(feature = "crypto-primitives"))]
verify_ecdsa_secp256k1_signature_mock: RefCell<Option<MockFnEcdsaSecp256k1>>,
#[cfg(not(feature = "crypto-primitives"))]
hash_sha2_256_mock: RefCell<Option<MockFnHash<HashSha2256>>>,
#[cfg(not(feature = "crypto-primitives"))]
hash_sha3_256_mock: RefCell<Option<MockFnHash<HashSha3256>>>,
#[cfg(not(feature = "crypto-primitives"))]
hash_keccak_256_mock: RefCell<Option<MockFnHash<HashKeccak256>>>,
}
impl Default for TestCryptoPrimitives {
fn default() -> Self { Self::new() }
}
impl TestCryptoPrimitives {
pub fn new() -> Self {
#[cfg(not(feature = "crypto-primitives"))]
return Self {
verify_ed25519_signature_mock: RefCell::new(None),
verify_ecdsa_secp256k1_signature_mock: RefCell::new(None),
hash_sha2_256_mock: RefCell::new(None),
hash_sha3_256_mock: RefCell::new(None),
hash_keccak_256_mock: RefCell::new(None),
};
#[cfg(feature = "crypto-primitives")]
Self {}
}
#[cfg(not(feature = "crypto-primitives"))]
pub fn setup_verify_ed25519_signature_mock<F>(&self, mock: F)
where
F: FnMut(PublicKeyEd25519, SignatureEd25519, &[u8]) -> bool + 'static, {
*self.verify_ed25519_signature_mock.borrow_mut() = Some(Box::new(mock));
}
#[cfg(not(feature = "crypto-primitives"))]
pub fn setup_verify_ecdsa_secp256k1_signature_mock<F>(&self, mock: F)
where
F: FnMut(PublicKeyEcdsaSecp256k1, SignatureEcdsaSecp256k1, [u8; 32]) -> bool + 'static,
{
*self.verify_ecdsa_secp256k1_signature_mock.borrow_mut() = Some(Box::new(mock));
}
#[cfg(not(feature = "crypto-primitives"))]
pub fn setup_hash_sha2_256_mock<F>(&self, mock: F)
where
F: FnMut(&[u8]) -> HashSha2256 + 'static, {
*self.hash_sha2_256_mock.borrow_mut() = Some(Box::new(mock));
}
#[cfg(not(feature = "crypto-primitives"))]
pub fn setup_hash_sha3_256_mock<F>(&self, mock: F)
where
F: FnMut(&[u8]) -> HashSha3256 + 'static, {
*self.hash_sha3_256_mock.borrow_mut() = Some(Box::new(mock));
}
#[cfg(not(feature = "crypto-primitives"))]
pub fn setup_hash_keccak_256_mock<F>(&self, mock: F)
where
F: FnMut(&[u8]) -> HashKeccak256 + 'static, {
*self.hash_keccak_256_mock.borrow_mut() = Some(Box::new(mock));
}
#[cfg(not(feature = "crypto-primitives"))]
fn fail_with_crypto_primitives_error(method_name: &str) -> ! {
fail!(
"To use {}, you need to either enable the \"concordium-std/crypto-primitives\" \
feature, which makes it use an actual implemenation, or set up a mock with the \
setup_{}_mock method on TestCryptoPrimitives.",
method_name,
method_name
)
}
}
impl HasCryptoPrimitives for TestCryptoPrimitives {
fn verify_ed25519_signature(
&self,
public_key: PublicKeyEd25519,
signature: SignatureEd25519,
message: &[u8],
) -> bool {
#[cfg(feature = "crypto-primitives")]
{
let signature = ed25519_zebra::Signature::try_from(&signature.0[..]);
let public_key = ed25519_zebra::VerificationKey::try_from(&public_key.0[..]);
match (signature, public_key) {
(Ok(ref signature), Ok(public_key)) => {
public_key.verify(signature, message).is_ok()
}
_ => false,
}
}
#[cfg(not(feature = "crypto-primitives"))]
{
if let Some(ref mut mock) = *self.verify_ed25519_signature_mock.borrow_mut() {
mock(public_key, signature, message)
} else {
Self::fail_with_crypto_primitives_error("verify_ed25519_signature")
}
}
}
fn verify_ecdsa_secp256k1_signature(
&self,
public_key: PublicKeyEcdsaSecp256k1,
signature: SignatureEcdsaSecp256k1,
message_hash: [u8; 32],
) -> bool {
#[cfg(feature = "crypto-primitives")]
{
let signature = secp256k1::ecdsa::Signature::from_compact(&signature.0[..]);
let public_key = secp256k1::PublicKey::from_slice(&public_key.0[..]);
let message_hash = secp256k1::Message::from_slice(&message_hash[..]);
match (signature, public_key, message_hash) {
(Ok(ref signature), Ok(public_key), Ok(message_hash)) => {
let verifier = secp256k1::Secp256k1::verification_only();
verifier.verify_ecdsa(&message_hash, signature, &public_key).is_ok()
}
_ => false,
}
}
#[cfg(not(feature = "crypto-primitives"))]
{
if let Some(ref mut mock) = *self.verify_ecdsa_secp256k1_signature_mock.borrow_mut() {
mock(public_key, signature, message_hash)
} else {
Self::fail_with_crypto_primitives_error("verify_ecdsa_secp256k1")
}
}
}
fn hash_sha2_256(&self, data: &[u8]) -> HashSha2256 {
#[cfg(feature = "crypto-primitives")]
{
use sha2::Digest;
HashSha2256(sha2::Sha256::digest(data).into())
}
#[cfg(not(feature = "crypto-primitives"))]
{
if let Some(ref mut mock) = *self.hash_sha2_256_mock.borrow_mut() {
mock(data)
} else {
Self::fail_with_crypto_primitives_error("hash_sha2_256")
}
}
}
fn hash_sha3_256(&self, data: &[u8]) -> HashSha3256 {
#[cfg(feature = "crypto-primitives")]
{
use sha3::Digest;
HashSha3256(sha3::Sha3_256::digest(data).into())
}
#[cfg(not(feature = "crypto-primitives"))]
{
if let Some(ref mut mock) = *self.hash_sha3_256_mock.borrow_mut() {
mock(data)
} else {
Self::fail_with_crypto_primitives_error("hash_sha3_256")
}
}
}
fn hash_keccak_256(&self, data: &[u8]) -> HashKeccak256 {
#[cfg(feature = "crypto-primitives")]
{
use sha3::Digest;
HashKeccak256(sha3::Keccak256::digest(data).into())
}
#[cfg(not(feature = "crypto-primitives"))]
{
if let Some(ref mut mock) = *self.hash_keccak_256_mock.borrow_mut() {
mock(data)
} else {
Self::fail_with_crypto_primitives_error("hash_keccak_256")
}
}
}
}
impl HasStateEntry for TestStateEntry {
type Error = TestStateError;
type StateEntryData = Rc<RefCell<TestStateEntryData>>;
type StateEntryKey = Vec<u8>;
#[inline(always)]
fn move_to_start(&mut self) { self.cursor.offset = 0; }
fn size(&self) -> Result<u32, Self::Error> {
Ok(self.cursor.data.borrow().data()?.len() as u32)
}
fn truncate(&mut self, new_size: u32) -> Result<(), Self::Error> {
let cur_size = self.size()?;
if cur_size > new_size {
self.resize(new_size)?;
}
Ok(())
}
fn get_key(&self) -> &[u8] { &self.key }
fn resize(&mut self, new_size: u32) -> Result<(), Self::Error> {
let new_size = new_size as usize;
self.cursor.data.borrow_mut().data_mut()?.resize(new_size, 0);
if self.cursor.offset > new_size {
self.cursor.offset = new_size;
}
Ok(())
}
}
impl Read for TestStateEntry {
fn read(&mut self, buf: &mut [u8]) -> ParseResult<usize> {
let mut len = self.cursor.data.borrow().data()?.len() - self.cursor.offset;
if len > buf.len() {
len = buf.len();
}
if len > 0 {
buf[0..len].copy_from_slice(
&self.cursor.data.borrow().data()?[self.cursor.offset..self.cursor.offset + len],
);
self.cursor.offset += len;
Ok(len)
} else {
Ok(0)
}
}
}
impl Write for TestStateEntry {
type Err = TestStateError;
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Err> {
let end = self.cursor.offset + buf.len();
if self.cursor.data.borrow().data()?.len() < end {
self.resize(end.try_into()?)?;
}
let mut cursor_data = self.cursor.data.as_ref().borrow_mut();
let data = &mut cursor_data.data_mut()?[self.cursor.offset..];
let to_write = cmp::min(data.len(), buf.len());
data[..to_write].copy_from_slice(&buf[..to_write]);
self.cursor.offset += to_write;
Ok(to_write)
}
}
impl Seek for TestStateEntry {
type Err = TestStateError;
fn seek(&mut self, pos: SeekFrom) -> Result<u32, Self::Err> {
use self::TestStateError::*;
let len = self.cursor.data.borrow().data()?.len();
match pos {
SeekFrom::Start(x) => {
let new_offset = x.try_into()?;
if new_offset <= len {
self.cursor.offset = new_offset;
Ok(x)
} else {
Err(Offset)
}
}
SeekFrom::End(x) => {
if x <= 0 {
let end: u32 = len.try_into()?;
let minus_x = x.checked_abs().ok_or(Overflow)?;
if let Some(new_pos) = end.checked_sub(minus_x.try_into()?) {
self.cursor.offset = new_pos.try_into()?;
Ok(new_pos)
} else {
Err(Offset)
}
} else {
Err(Offset)
}
}
SeekFrom::Current(x) => match x {
0 => Ok(self.cursor.offset.try_into()?),
x if x > 0 => {
let x = x.try_into()?;
let new_pos = self.cursor.offset.checked_add(x).ok_or(Overflow)?;
if new_pos <= len {
self.cursor.offset = new_pos;
new_pos.try_into().map_err(Self::Err::from)
} else {
Err(Offset)
}
}
x => {
let x = (-x).try_into()?;
let new_pos = self.cursor.offset.checked_sub(x).ok_or(Overflow)?;
self.cursor.offset = new_pos;
new_pos.try_into().map_err(Self::Err::from)
}
},
}
}
#[inline(always)]
fn cursor_position(&self) -> u32 { self.cursor.offset as u32 }
}
impl HasCallResponse for Cursor<Vec<u8>> {
fn size(&self) -> u32 { self.data.len() as u32 }
}
pub struct MockFn<State> {
f: TestMockFn<State>,
}
type TestMockFn<State> =
Box<dyn Fn(Parameter, Amount, &mut Amount, &mut State) -> CallContractResult<Cursor<Vec<u8>>>>;
impl<State> MockFn<State> {
pub fn new<R, F>(mock_fn_return: F) -> Self
where
R: Serial,
F: Fn(Parameter, Amount, &mut Amount, &mut State) -> CallContractResult<R> + 'static, {
let mock_fn = Box::new(
move |parameter: Parameter, amount: Amount, balance: &mut Amount, state: &mut State| {
match mock_fn_return(parameter, amount, balance, state) {
Ok((modified, return_value)) => {
if let Some(return_value) = return_value {
Ok((modified, Some(Cursor::new(to_bytes(&return_value)))))
} else {
Ok((modified, None))
}
}
Err(e) => match e {
CallContractError::AmountTooLarge => Err(CallContractError::AmountTooLarge),
CallContractError::MissingAccount => Err(CallContractError::MissingAccount),
CallContractError::MissingContract => {
Err(CallContractError::MissingContract)
}
CallContractError::MissingEntrypoint => {
Err(CallContractError::MissingEntrypoint)
}
CallContractError::MessageFailed => Err(CallContractError::MessageFailed),
CallContractError::LogicReject {
reason,
return_value,
} => Err(CallContractError::LogicReject {
reason,
return_value: Cursor::new(to_bytes(&return_value)),
}),
CallContractError::Trap => Err(CallContractError::Trap),
},
}
},
);
Self {
f: mock_fn,
}
}
pub fn new_v1<R, F>(mock_fn_return: F) -> Self
where
R: Serial,
F: Fn(
Parameter,
Amount,
&mut Amount,
&mut State,
) -> Result<(bool, R), CallContractError<R>>
+ 'static, {
Self::new(move |p, a, b, s| {
mock_fn_return(p, a, b, s).map(|(modified, rv)| (modified, Some(rv)))
})
}
pub fn new_v0<R, F>(mock_fn_return: F) -> Self
where
R: Serial,
F: Fn(Parameter, Amount, &mut Amount, &mut State) -> Result<bool, CallContractError<R>>
+ 'static, {
Self::new(move |p, a, b, s| mock_fn_return(p, a, b, s).map(|modified| (modified, None)))
}
pub fn returning_ok<R: Clone + Serial + 'static>(return_value: R) -> Self {
Self::new(move |_parameter, _amount, _balance, _state| -> CallContractResult<R> {
Ok((false, Some(return_value.clone())))
})
}
pub fn returning_err<R: Clone + Serial + 'static>(error: CallContractError<R>) -> Self {
Self::new(
move |_parameter: Parameter,
_amount: Amount,
_balance: &mut Amount,
_state: &mut State|
-> CallContractResult<R> { Err(error.clone()) },
)
}
}
type MockFnMap<State> = BTreeMap<(ContractAddress, OwnedEntrypointName), MockFn<State>>;
type MockUpgradeMap = BTreeMap<ModuleReference, UpgradeResult>;
pub struct TestHost<State> {
mocking_fns: Rc<RefCell<MockFnMap<State>>>,
transfers: RefCell<Vec<(AccountAddress, Amount)>>,
contract_balance: RefCell<Amount>,
contract_address: Option<ContractAddress>,
mocking_upgrades: RefCell<MockUpgradeMap>,
state_builder: StateBuilder<TestStateApi>,
state: State,
query_account_balances: RefCell<BTreeMap<AccountAddress, AccountBalance>>,
query_contract_balances: RefCell<BTreeMap<ContractAddress, Amount>>,
query_exchange_rates: Option<ExchangeRates>,
missing_accounts: BTreeSet<AccountAddress>,
missing_contracts: BTreeSet<ContractAddress>,
}
impl<State: Serial + DeserialWithState<TestStateApi>> HasHost<State> for TestHost<State> {
type ReturnValueType = Cursor<Vec<u8>>;
type StateApiType = TestStateApi;
fn invoke_transfer(&self, receiver: &AccountAddress, amount: Amount) -> TransferResult {
if self.missing_accounts.contains(receiver) {
return Err(TransferError::MissingAccount);
}
if *self.contract_balance.borrow() >= amount {
*self.contract_balance.borrow_mut() -= amount;
self.transfers.borrow_mut().push((*receiver, amount));
if let Some(balance) = self.query_account_balances.borrow_mut().get_mut(receiver) {
balance.total += amount;
}
Ok(())
} else {
Err(TransferError::AmountTooLarge)
}
}
fn invoke_contract_raw(
&mut self,
to: &ContractAddress,
parameter: Parameter,
method: EntrypointName,
amount: Amount,
) -> CallContractResult<Self::ReturnValueType> {
self.commit_state();
let mocking_fns = self.mocking_fns.clone();
let mut mocking_fns_mut = mocking_fns.borrow_mut();
let handler = match mocking_fns_mut.get_mut(&(*to, OwnedEntrypointName::from(method))) {
Some(handler) => handler,
None => fail!(
"Mocking has not been set up for invoking contract {:?} with method '{}'.",
to,
method
),
};
if *self.contract_balance.borrow() < amount {
return Err(CallContractError::AmountTooLarge);
}
let host_checkpoint = self.checkpoint();
let invocation_res = (handler.f)(
parameter,
amount,
&mut self.contract_balance.borrow_mut(),
&mut self.state,
);
match invocation_res {
Err(error) => {
*self = host_checkpoint;
Err(error)
}
Ok((state_modified, res)) => {
*self.contract_balance.borrow_mut() -= amount;
if let Some(balance) = self.query_contract_balances.borrow_mut().get_mut(to) {
*balance += amount;
}
if state_modified {
self.commit_state();
}
Ok((state_modified, res))
}
}
}
fn invoke_contract_raw_read_only(
&self,
to: &ContractAddress,
parameter: Parameter,
method: EntrypointName,
amount: Amount,
) -> ReadOnlyCallContractResult<Self::ReturnValueType> {
let mocking_fns = self.mocking_fns.borrow();
let handler = match mocking_fns.get(&(*to, OwnedEntrypointName::from(method))) {
Some(handler) => handler,
None => fail!(
"Mocking has not been set up for invoking contract {:?} with method '{}'.",
to,
method
),
};
if amount.micro_ccd > 0 && *self.contract_balance.borrow() < amount {
return Err(CallContractError::AmountTooLarge);
}
let mut state = match State::deserial_with_state(
&self.state_builder.state_api,
&mut self
.state_builder
.state_api
.lookup_entry(&[])
.expect_report("Could not lookup the state root."),
) {
Ok(state) => state,
Err(e) => fail!("Failed to deserialize state: {:?}", e),
};
let (state_modified, res) =
(handler.f)(parameter, amount, &mut self.contract_balance.borrow_mut(), &mut state)?;
if state_modified {
fail!("State modified in a read-only contract call.");
}
*self.contract_balance.borrow_mut() -= amount;
if let Some(balance) = self.query_contract_balances.borrow_mut().get_mut(to) {
*balance += amount;
}
Ok(res)
}
fn account_balance(&self, address: AccountAddress) -> QueryAccountBalanceResult {
if self.missing_accounts.contains(&address) {
Err(QueryAccountBalanceError)
} else if let Some(balances) = self.query_account_balances.borrow().get(&address) {
Ok(*balances)
} else {
fail!("No account balance for {:?} has been set up.", address)
}
}
fn contract_balance(&self, address: ContractAddress) -> QueryContractBalanceResult {
if Some(address) == self.contract_address {
Ok(self.contract_balance.borrow().to_owned())
} else if self.missing_contracts.contains(&address) {
Err(QueryContractBalanceError)
} else if let Some(balances) = self.query_contract_balances.borrow().get(&address) {
Ok(*balances)
} else {
fail!("No contract balance for {:?} has been set up.", address)
}
}
fn exchange_rates(&self) -> ExchangeRates {
if let Some(exchange_rates) = self.query_exchange_rates {
exchange_rates
} else {
fail!("No exchange rates has been set up")
}
}
fn upgrade(&mut self, module: ModuleReference) -> UpgradeResult {
if let Some(result) = self.mocking_upgrades.borrow().get(&module) {
result.to_owned()
} else {
fail!(
"Mocking has not been set up for upgrading to this particular module reference: \
{:?}",
module
)
}
}
fn commit_state(&mut self) {
let mut root_entry = self
.state_builder
.state_api
.lookup_entry(&[])
.expect_report("commit_state: Cannot lookup state root.");
self.state.serial(&mut root_entry).expect_report("commit_state: Cannot serialize state.");
let new_state_size = root_entry
.size()
.expect_report("commit_state: Cannot get state size. Entry was deleted.");
root_entry
.truncate(new_state_size)
.expect_report("commit_state: Cannot truncate state. Entry was deleted.");
}
fn state(&self) -> &State { &self.state }
fn state_mut(&mut self) -> &mut State { &mut self.state }
fn self_balance(&self) -> Amount { *self.contract_balance.borrow() }
fn state_builder(&mut self) -> &mut StateBuilder<Self::StateApiType> { &mut self.state_builder }
fn state_and_builder(&mut self) -> (&mut State, &mut StateBuilder<Self::StateApiType>) {
(&mut self.state, &mut self.state_builder)
}
fn account_public_keys(&self, _address: AccountAddress) -> QueryAccountPublicKeysResult {
unimplemented!(
"The test infrastructure will be deprecated and so does not implement new \
functionality."
)
}
fn check_account_signature(
&self,
_address: AccountAddress,
_signatures: &AccountSignatures,
_data: &[u8],
) -> CheckAccountSignatureResult {
unimplemented!(
"The test infrastructure will be deprecated and so does not implement new \
functionality."
)
}
}
impl<State: Serial + DeserialWithState<TestStateApi>> TestHost<State> {
pub fn new(state: State, mut state_builder: StateBuilder<TestStateApi>) -> Self {
let mut root_entry = state_builder
.state_api
.create_entry(&[])
.expect_report("TestHost::new: Could not store state root.");
state.serial(&mut root_entry).expect_report("TestHost::new: cannot serialize state.");
Self {
mocking_fns: Rc::new(RefCell::new(BTreeMap::new())),
transfers: RefCell::new(Vec::new()),
contract_balance: RefCell::new(Amount::zero()),
contract_address: None,
mocking_upgrades: RefCell::new(BTreeMap::new()),
state_builder,
state,
missing_accounts: BTreeSet::new(),
missing_contracts: BTreeSet::new(),
query_account_balances: RefCell::new(BTreeMap::new()),
query_contract_balances: RefCell::new(BTreeMap::new()),
query_exchange_rates: None,
}
}
pub fn state_builder(&mut self) -> &mut StateBuilder<TestStateApi> { &mut self.state_builder }
pub fn setup_mock_entrypoint(
&mut self,
to: ContractAddress,
method: OwnedEntrypointName,
handler: MockFn<State>,
) {
self.mocking_fns.borrow_mut().insert((to, method), handler);
}
pub fn setup_mock_upgrade(&mut self, module: ModuleReference, result: UpgradeResult) {
self.mocking_upgrades.borrow_mut().insert(module, result);
}
pub fn set_self_balance(&mut self, amount: Amount) {
*self.contract_balance.borrow_mut() = amount;
}
pub fn set_self_address(&mut self, address: ContractAddress) {
if self.missing_contracts.contains(&address) {
fail!("The self_address is marked as a missing contract address.")
}
if self.query_contract_balances.borrow().get(&address).is_some() {
fail!(
"The self_address cannot be setup as a query contract balance. Either use another \
address as self_address or another address in 'setup_query_contract_balance'."
)
}
self.contract_address = Some(address);
}
pub fn setup_query_account_balance(
&mut self,
address: AccountAddress,
account_balance: AccountBalance,
) {
if self.missing_accounts.contains(&address) {
fail!(
"Setting up a query account balance for the provided address is not possible, \
because the address is marked as a missing account."
)
}
self.query_account_balances.borrow_mut().insert(address, account_balance);
}
pub fn setup_query_contract_balance(&mut self, address: ContractAddress, balance: Amount) {
if self.missing_contracts.contains(&address) {
fail!(
"Setting up a query contract balance for the provided address is not possible, \
because the address is marked as a missing contract address."
)
}
if Some(address) == self.contract_address {
fail!(
"Setting up a query contract balance for the self_address is not possible, use \
'set_self_balance' instead."
)
}
self.query_contract_balances.borrow_mut().insert(address, balance);
}
pub fn set_exchange_rates(&mut self, exchange_rates: ExchangeRates) {
self.query_exchange_rates = Some(exchange_rates);
}
pub fn transfer_occurred(&self, receiver: &AccountAddress, amount: Amount) -> bool {
self.transfers.borrow().contains(&(*receiver, amount))
}
pub fn get_transfers(&self) -> Vec<(AccountAddress, Amount)> {
self.transfers.borrow().to_vec()
}
pub fn get_transfers_to(&self, account: AccountAddress) -> Vec<Amount> {
self.transfers
.borrow()
.iter()
.filter_map(|(acc, amount)| {
if *acc == account {
Some(*amount)
} else {
None
}
})
.collect()
}
pub fn make_account_missing(&mut self, account: AccountAddress) {
if self.query_account_balances.borrow().get(&account).is_some() {
fail!(
"The account cannot be setup as a missing account. It is already setup for a \
query account balance."
)
}
self.missing_accounts.insert(account);
}
pub fn make_contract_missing(&mut self, contract: ContractAddress) {
if self.contract_address == Some(contract) {
fail!("The address of the instance cannot be one of the missing contracts.")
}
if self.query_contract_balances.borrow().get(&contract).is_some() {
fail!(
"The contract address cannot be setup as a missing contract. It is already setup \
for a query contract balance."
)
}
self.missing_contracts.insert(contract);
}
}
impl<State: DeserialWithState<TestStateApi>> TestHost<State> {
fn checkpoint(&self) -> Self {
let cloned_state_api = self.state_builder.state_api.clone_deep();
let state: State = cloned_state_api
.read_root()
.expect_report("Could not deserialize root entry from state clone");
Self {
mocking_fns: self.mocking_fns.clone(),
transfers: self.transfers.clone(),
contract_balance: self.contract_balance.clone(),
contract_address: self.contract_address,
mocking_upgrades: self.mocking_upgrades.clone(),
state_builder: StateBuilder {
state_api: cloned_state_api,
},
state,
missing_accounts: self.missing_accounts.clone(),
missing_contracts: self.missing_contracts.clone(),
query_account_balances: self.query_account_balances.clone(),
query_contract_balances: self.query_contract_balances.clone(),
query_exchange_rates: self.query_exchange_rates,
}
}
pub fn with_rollback<R, E>(
&mut self,
call: impl FnOnce(&mut TestHost<State>) -> Result<R, E>,
) -> Result<R, E> {
let checkpoint = self.checkpoint();
let res = call(self);
if res.is_err() {
*self = checkpoint;
}
res
}
}
#[cfg(all(feature = "wasm-test", feature = "concordium-quickcheck", target_arch = "wasm32"))]
use getrandom::register_custom_getrandom;
#[cfg(all(feature = "wasm-test", feature = "concordium-quickcheck", target_arch = "wasm32"))]
fn get_random(dest: &mut [u8]) -> Result<(), getrandom::Error> {
unsafe {
crate::prims::get_random(dest.as_mut_ptr(), dest.len() as u32);
}
Ok(())
}
#[cfg(all(feature = "wasm-test", feature = "concordium-quickcheck", target_arch = "wasm32"))]
register_custom_getrandom!(get_random);
#[cfg(feature = "concordium-quickcheck")]
const QUICKCHECK_MAX_WITH_DISCARDED_TESTS: u64 = 100_000_000;
#[cfg(all(feature = "concordium-quickcheck", target_arch = "wasm32"))]
pub fn concordium_qc<A: Testable>(num_tests: u64, f: A) {
let mut qc = QuickCheck::new().tests(num_tests).max_tests(QUICKCHECK_MAX_WITH_DISCARDED_TESTS);
let res = qc.quicktest(f);
match res {
Ok(n_tests_passed) => {
if n_tests_passed == 0 {
let msg = "(No QuickCheck tests were generated)";
report_error(msg, file!(), line!(), column!())
}
}
Err(result) => {
let msg = format!("Failed with the counterexample: {:#?}", result);
report_error(&msg, file!(), line!(), column!());
}
}
}
#[cfg(all(feature = "concordium-quickcheck", not(target_arch = "wasm32")))]
pub fn concordium_qc<A: Testable>(num_tests: u64, f: A) {
QuickCheck::new().tests(num_tests).max_tests(QUICKCHECK_MAX_WITH_DISCARDED_TESTS).quickcheck(f)
}
#[cfg(test)]
mod test {
use super::TestStateApi;
use crate::{
cell::RefCell,
rc::Rc,
test_infrastructure::{TestStateBuilder, TestStateEntry},
Deletable, EntryRaw, HasStateApi, HasStateEntry, StateMap, StateSet,
INITIAL_NEXT_ITEM_PREFIX,
};
use concordium_contracts_common::{to_bytes, Deserial, Read, Seek, SeekFrom, Write};
#[test]
fn test_testhost_balance_queries_reflect_transfers() {
use super::*;
let mut host = TestHost::new((), TestStateBuilder::new());
let self_address = ContractAddress::new(0, 0);
host.set_self_address(self_address);
host.set_self_balance(Amount::from_micro_ccd(3000));
let account = AccountAddress([0; 32]);
let account_balance =
AccountBalance::new(Amount::zero(), Amount::zero(), Amount::zero()).unwrap();
host.setup_query_account_balance(account, account_balance);
host.invoke_transfer(&account, Amount::from_micro_ccd(2000))
.expect("Transferring should succeed");
let account_new_balance = host.account_balance(account).expect("Should succeed");
let self_new_balance = host.contract_balance(self_address).expect("Should succeed");
assert_eq!(account_new_balance.total, Amount::from_micro_ccd(2000));
assert_eq!(self_new_balance, Amount::from_micro_ccd(1000));
}
#[test]
fn test_testhost_balance_queries_reflect_invoke() {
use super::*;
let mut host = TestHost::new((), TestStateBuilder::new());
let self_address = ContractAddress::new(0, 0);
host.set_self_address(self_address);
host.set_self_balance(Amount::from_micro_ccd(3000));
let other_address = ContractAddress::new(1, 0);
host.setup_query_contract_balance(other_address, Amount::from_micro_ccd(4000));
let entrypoint = OwnedEntrypointName::new_unchecked("test.method".to_string());
host.setup_mock_entrypoint(other_address, entrypoint.clone(), MockFn::returning_ok(()));
host.invoke_contract_raw(
&other_address,
Parameter::empty(),
entrypoint.as_entrypoint_name(),
Amount::from_micro_ccd(1000),
)
.expect("Invoke should succeed");
let other_new_balance = host.contract_balance(other_address).expect("Should succeed");
let self_new_balance = host.contract_balance(self_address).expect("Should succeed");
assert_eq!(other_new_balance, Amount::from_micro_ccd(5000));
assert_eq!(self_new_balance, Amount::from_micro_ccd(2000));
}
#[test]
fn test_contract_state() {
let data = Rc::new(RefCell::new(vec![1; 100].into()));
let mut state = TestStateEntry::open(data, Vec::new(), 0);
assert_eq!(state.seek(SeekFrom::Start(100)), Ok(100), "Seeking to the end failed.");
assert_eq!(
state.seek(SeekFrom::Current(0)),
Ok(100),
"Seeking from current position with offset 0 failed."
);
assert!(
state.seek(SeekFrom::Current(1)).is_err(),
"Seeking from current position with offset 1 succeeded."
);
assert_eq!(state.cursor.offset, 100, "Cursor position changed on failed seek.");
assert_eq!(
state.seek(SeekFrom::Current(-1)),
Ok(99),
"Seeking from current position backwards with offset 1 failed."
);
assert!(state.seek(SeekFrom::Current(-100)).is_err(), "Seeking beyond beginning succeeds");
assert_eq!(state.seek(SeekFrom::Current(-99)), Ok(0), "Seeking to the beginning fails.");
assert_eq!(state.seek(SeekFrom::End(0)), Ok(100), "Seeking from end fails.");
assert!(
state.seek(SeekFrom::End(1)).is_err(),
"Seeking beyond the end succeeds but should fail."
);
assert_eq!(state.cursor.offset, 100, "Cursor position changed on failed seek.");
assert_eq!(
state.seek(SeekFrom::End(-20)),
Ok(80),
"Seeking from end leads to incorrect position."
);
assert_eq!(state.write(&[0; 21]), Ok(21), "Writing writes an incorrect amount of data.");
assert_eq!(state.cursor.offset, 101, "After writing the cursor is at the end.");
assert_eq!(state.write(&[0; 21]), Ok(21), "Writing again writes incorrect amount of data.");
let mut buf = [0; 30];
assert_eq!(state.read(&mut buf), Ok(0), "Reading from the end should read 0 bytes.");
assert_eq!(state.seek(SeekFrom::End(-20)), Ok(102));
assert_eq!(state.read(&mut buf), Ok(20), "Reading from offset 80 should read 20 bytes.");
assert_eq!(
&buf[0..20],
&state.cursor.data.borrow().data().expect("Entry was deleted")[80..100],
"Incorrect data was read."
);
assert_eq!(
state.cursor.offset, 122,
"After reading the offset is in the correct position."
);
assert!(state.reserve(222).is_ok(), "Could not increase state to 222.");
assert_eq!(state.write(&[2; 100]), Ok(100), "Should have written 100 bytes.");
assert_eq!(state.cursor.offset, 222, "After writing the offset should be 200.");
state.truncate(50).expect("Could not truncate state to 50.");
assert_eq!(state.cursor.offset, 50, "After truncation the state should be 50.");
assert_eq!(
state.write(&[1; 1000]),
Ok(1000),
"Writing at the end after truncation should succeed."
);
}
#[test]
fn test_contract_state_write() {
let data = Rc::new(RefCell::new(vec![0u8; 10].into()));
let mut state = TestStateEntry::open(data, Vec::new(), 0);
assert_eq!(state.write(&1u64.to_le_bytes()), Ok(8), "Incorrect number of bytes written.");
assert_eq!(
state.write(&2u64.to_le_bytes()),
Ok(8),
"State should be resized automatically."
);
assert_eq!(state.cursor.offset, 16, "Pos should be at the end.");
assert_eq!(
*state.cursor.data.as_ref().borrow().data().expect("Entry was deleted"),
vec![1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0],
"Correct data was written."
);
}
#[test]
fn high_level_insert_get() {
let expected_value: u64 = 123123123;
let mut state_builder = TestStateBuilder::new();
state_builder.insert(0, expected_value).expect("Insert failed");
let actual_value: u64 = state_builder.get(0).expect("Not found").expect("Not a valid u64");
assert_eq!(expected_value, actual_value);
}
#[test]
fn low_level_entry() {
let expected_value: u64 = 123123123;
let key = to_bytes(&42u64);
let mut state = TestStateApi::new();
state
.entry(&key[..])
.or_insert_raw(&to_bytes(&expected_value))
.expect("No iterators, so insertion should work.");
match state.entry(key) {
EntryRaw::Vacant(_) => panic!("Unexpected vacant entry."),
EntryRaw::Occupied(occ) => {
assert_eq!(u64::deserial(&mut occ.get()), Ok(expected_value))
}
}
}
#[test]
fn high_level_statemap() {
let my_map_key = "my_map";
let mut state_builder = TestStateBuilder::new();
let map_to_insert = state_builder.new_map::<String, String>();
state_builder.insert(my_map_key, map_to_insert).expect("Insert failed");
let mut my_map: StateMap<String, String, _> = state_builder
.get(my_map_key)
.expect("Could not get statemap")
.expect("Deserializing statemap failed");
my_map.insert("abc".to_string(), "hello, world".to_string());
my_map.insert("def".to_string(), "hallo, Weld".to_string());
my_map.insert("ghi".to_string(), "hej, verden".to_string());
assert_eq!(*my_map.get(&"abc".to_string()).unwrap(), "hello, world".to_string());
let mut iter = my_map.iter();
let (k1, v1) = iter.next().unwrap();
assert_eq!(*k1, "abc".to_string());
assert_eq!(*v1, "hello, world".to_string());
let (k2, v2) = iter.next().unwrap();
assert_eq!(*k2, "def".to_string());
assert_eq!(*v2, "hallo, Weld".to_string());
let (k3, v3) = iter.next().unwrap();
assert_eq!(*k3, "ghi".to_string());
assert_eq!(*v3, "hej, verden".to_string());
assert!(iter.next().is_none());
}
#[test]
fn statemap_insert_remove() {
let mut state_builder = TestStateBuilder::new();
let mut map = state_builder.new_map();
let value = String::from("hello");
let _ = map.insert(42, value.clone());
assert_eq!(*map.get(&42).unwrap(), value);
map.remove(&42);
assert!(map.get(&42).is_none());
}
#[test]
fn statemap_clear() {
let mut state_builder = TestStateBuilder::new();
let mut map = state_builder.new_map();
let _ = map.insert(1, 2);
let _ = map.insert(2, 3);
let _ = map.insert(3, 4);
map.clear();
assert!(map.is_empty());
}
#[test]
fn high_level_nested_statemaps() {
let inner_map_key = 0u8;
let key_to_value = 77u8;
let value = 255u8;
let mut state_builder = TestStateBuilder::new();
let mut outer_map = state_builder.new_map::<u8, StateMap<u8, u8, _>>();
let mut inner_map = state_builder.new_map::<u8, u8>();
inner_map.insert(key_to_value, value);
outer_map.insert(inner_map_key, inner_map);
assert_eq!(*outer_map.get(&inner_map_key).unwrap().get(&key_to_value).unwrap(), value);
}
#[test]
fn statemap_iter_mut_works() {
let mut state_builder = TestStateBuilder::new();
let mut map = state_builder.new_map();
map.insert(0u8, 1u8);
map.insert(1u8, 2u8);
map.insert(2u8, 3u8);
for (_, mut v) in map.iter_mut() {
v.update(|old_value| *old_value += 10);
}
let mut iter = map.iter();
let (k1, v1) = iter.next().unwrap();
assert_eq!(*k1, 0);
assert_eq!(*v1, 11);
let (k2, v2) = iter.next().unwrap();
assert_eq!(*k2, 1);
assert_eq!(*v2, 12);
let (k3, v3) = iter.next().unwrap();
assert_eq!(*k3, 2);
assert_eq!(*v3, 13);
assert!(iter.next().is_none());
}
#[test]
fn iter_mut_works_on_nested_statemaps() {
let mut state_builder = TestStateBuilder::new();
let mut outer_map = state_builder.new_map();
let mut inner_map = state_builder.new_map();
inner_map.insert(0u8, 1u8);
inner_map.insert(1u8, 2u8);
outer_map.insert(99u8, inner_map);
for (_, mut v_map) in outer_map.iter_mut() {
v_map.update(|v_map| {
for (_, mut inner_v) in v_map.iter_mut() {
inner_v.update(|inner_v| *inner_v += 10);
}
});
}
let mut outer_iter = outer_map.iter();
let (inner_map_key, inner_map) = outer_iter.next().unwrap();
assert_eq!(*inner_map_key, 99);
assert!(outer_iter.next().is_none());
let mut inner_iter = inner_map.iter();
let (k1, v1) = inner_iter.next().unwrap();
assert_eq!(*k1, 0);
assert_eq!(*v1, 11);
let (k2, v2) = inner_iter.next().unwrap();
assert_eq!(*k2, 1);
assert_eq!(*v2, 12);
assert!(inner_iter.next().is_none());
}
#[test]
fn statemap_iterator_unlocks_tree_once_dropped() {
let mut state_builder = TestStateBuilder::new();
let mut map = state_builder.new_map();
map.insert(0u8, 1u8);
map.insert(1u8, 2u8);
{
let _iter = map.iter();
} map.insert(2u8, 3u8);
}
#[test]
fn high_level_stateset() {
let my_set_key = "my_set";
let mut state_builder = TestStateBuilder::new();
let mut set = state_builder.new_set::<u8>();
assert!(set.insert(0));
assert!(set.insert(1));
assert!(!set.insert(1));
assert!(set.insert(2));
assert!(set.remove(&2));
state_builder.insert(my_set_key, set).expect("Insert failed");
assert!(state_builder.get::<_, StateSet<u8, _>>(my_set_key).unwrap().unwrap().contains(&0),);
assert!(!state_builder
.get::<_, StateSet<u8, _>>(my_set_key)
.unwrap()
.unwrap()
.contains(&2),);
let set = state_builder.get::<_, StateSet<u8, _>>(my_set_key).unwrap().unwrap();
let mut iter = set.iter();
assert_eq!(*iter.next().unwrap(), 0);
assert_eq!(*iter.next().unwrap(), 1);
assert!(iter.next().is_none());
}
#[test]
fn high_level_nested_stateset() {
let inner_set_key = 0u8;
let value = 255u8;
let mut state_builder = TestStateBuilder::new();
let mut outer_map = state_builder.new_map::<u8, StateSet<u8, _>>();
let mut inner_set = state_builder.new_set::<u8>();
inner_set.insert(value);
outer_map.insert(inner_set_key, inner_set);
assert!(outer_map.get(&inner_set_key).unwrap().contains(&value));
}
#[test]
fn stateset_insert_remove() {
let mut state_builder = TestStateBuilder::new();
let mut set = state_builder.new_set();
let _ = set.insert(42);
assert!(set.contains(&42));
set.remove(&42);
assert!(!set.contains(&42));
}
#[test]
fn stateset_clear() {
let mut state_builder = TestStateBuilder::new();
let mut set = state_builder.new_set();
let _ = set.insert(1);
let _ = set.insert(2);
let _ = set.insert(3);
set.clear();
assert!(set.is_empty());
}
#[test]
fn stateset_iterator_unlocks_tree_once_dropped() {
let mut state_builder = TestStateBuilder::new();
let mut set = state_builder.new_set();
set.insert(0u8);
set.insert(1);
{
let _iter = set.iter();
} set.insert(2);
}
#[test]
fn allocate_and_get_statebox() {
let mut state_builder = TestStateBuilder::new();
let boxed_value = String::from("I'm boxed");
let statebox = state_builder.new_box(boxed_value.clone());
assert_eq!(*statebox.get(), boxed_value);
}
#[test]
fn a_new_entry_can_not_be_created_under_a_locked_subtree() {
let expected_value: u64 = 123123123;
let key = to_bytes(b"ab");
let sub_key = to_bytes(b"abc");
let mut state = TestStateApi::new();
state
.entry(&key[..])
.or_insert_raw(&to_bytes(&expected_value))
.expect("No iterators, so insertion should work.");
assert!(state.iterator(&key).is_ok(), "Iterator should be present");
let entry = state.create_entry(&sub_key);
assert!(entry.is_err(), "Should not be able to create an entry under a locked subtree");
}
#[test]
fn a_new_entry_can_be_created_under_a_different_subtree_in_same_super_tree() {
let expected_value: u64 = 123123123;
let key = to_bytes(b"abcd");
let key2 = to_bytes(b"abe");
let mut state = TestStateApi::new();
state
.entry(&key[..])
.or_insert_raw(&to_bytes(&expected_value))
.expect("No iterators, so insertion should work.");
assert!(state.iterator(&key).is_ok(), "Iterator should be present");
let entry = state.create_entry(&key2);
assert!(entry.is_ok(), "Failed to create a new entry under a different subtree");
}
#[test]
fn an_existing_entry_can_not_be_deleted_under_a_locked_subtree() {
let expected_value: u64 = 123123123;
let key = to_bytes(b"ab");
let sub_key = to_bytes(b"abc");
let mut state = TestStateApi::new();
state
.entry(&key[..])
.or_insert_raw(&to_bytes(&expected_value))
.expect("no iterators, so insertion should work.");
let sub_entry = state
.entry(sub_key)
.or_insert_raw(&to_bytes(&expected_value))
.expect("Should be possible to create the entry.");
assert!(state.iterator(&key).is_ok(), "Iterator should be present");
assert!(
state.delete_entry(sub_entry).is_err(),
"Should not be able to create an entry under a locked subtree"
);
}
#[test]
fn an_existing_entry_can_be_deleted_from_a_different_subtree_in_same_super_tree() {
let expected_value: u64 = 123123123;
let key = to_bytes(b"abcd");
let key2 = to_bytes(b"abe");
let mut state = TestStateApi::new();
state
.entry(&key[..])
.or_insert_raw(&to_bytes(&expected_value))
.expect("No iterators, so insertion should work.");
let entry2 = state
.entry(key2)
.or_insert_raw(&to_bytes(&expected_value))
.expect("Should be possible to create the entry.");
assert!(state.iterator(&key).is_ok(), "Iterator should be present");
assert!(
state.delete_entry(entry2).is_ok(),
"Failed to create a new entry under a different subtree"
);
}
#[test]
fn deleting_nested_stateboxes_works() {
let mut state_builder = TestStateBuilder::new();
let inner_box = state_builder.new_box(99u8);
let middle_box = state_builder.new_box(inner_box);
let outer_box = state_builder.new_box(middle_box);
outer_box.delete();
let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator");
assert!(iter.nth(1).is_none());
}
#[test]
fn clearing_statemap_with_stateboxes_works() {
let mut state_builder = TestStateBuilder::new();
let box1 = state_builder.new_box(1u8);
let box2 = state_builder.new_box(2u8);
let box3 = state_builder.new_box(3u8);
let mut map = state_builder.new_map();
map.insert(1u8, box1);
map.insert(2u8, box2);
map.insert(3u8, box3);
map.clear();
let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator");
assert!(iter.nth(1).is_none());
}
#[test]
fn clearing_nested_statemaps_works() {
let mut state_builder = TestStateBuilder::new();
let mut inner_map_1 = state_builder.new_map();
inner_map_1.insert(1u8, 2u8);
inner_map_1.insert(2u8, 3u8);
inner_map_1.insert(3u8, 4u8);
let mut inner_map_2 = state_builder.new_map();
inner_map_2.insert(11u8, 12u8);
inner_map_2.insert(12u8, 13u8);
inner_map_2.insert(13u8, 14u8);
let mut outer_map = state_builder.new_map();
outer_map.insert(0u8, inner_map_1);
outer_map.insert(1u8, inner_map_2);
outer_map.clear();
let mut iter = state_builder.state_api.iterator(&[]).expect("Could not get iterator");
assert!(iter.nth(1).is_none());
}
#[test]
fn occupied_entry_truncates_leftover_data() {
let mut state_builder = TestStateBuilder::new();
let mut map = state_builder.new_map();
map.insert(99u8, "A longer string that should be truncated".into());
let a_short_string = "A short string".to_string();
let expected_size = a_short_string.len() + 4; map.entry(99u8).and_modify(|v| *v = a_short_string);
let actual_size = state_builder
.state_api
.lookup_entry(&[INITIAL_NEXT_ITEM_PREFIX[0], 0, 0, 0, 0, 0, 0, 0, 99])
.expect("Lookup failed")
.size()
.expect("Getting size failed");
assert_eq!(expected_size as u32, actual_size);
}
#[test]
fn occupied_entry_raw_truncates_leftover_data() {
let mut state = TestStateApi::new();
state
.entry([])
.or_insert_raw(&to_bytes(&"A longer string that should be truncated"))
.expect("No iterators, so insertion should work.");
let a_short_string = "A short string";
let expected_size = a_short_string.len() + 4;
match state.entry([]) {
EntryRaw::Vacant(_) => panic!("Entry is vacant"),
EntryRaw::Occupied(mut occ) => occ.insert_raw(&to_bytes(&a_short_string)),
}
let actual_size =
state.lookup_entry(&[]).expect("Lookup failed").size().expect("Getting size failed");
assert_eq!(expected_size as u32, actual_size);
}
}