use crate::*;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use convert::TryInto;
#[cfg(not(feature = "std"))]
use core::{cmp, num};
#[cfg(feature = "std")]
use std::{boxed::Box, cmp, num};
#[derive(Default, Clone)]
pub struct ChainMetaTest {
pub(crate) slot_time: Option<SlotTime>,
}
#[derive(Debug, Clone)]
pub struct TestPolicy {
position: usize,
policy: OwnedPolicy,
}
impl TestPolicy {
fn new(policy: OwnedPolicy) -> Self {
Self {
position: 0,
policy,
}
}
}
#[derive(Default, Clone)]
#[doc(hidden)]
pub struct CommonDataTest<'a> {
pub(crate) metadata: ChainMetaTest,
pub(crate) parameter: Option<&'a [u8]>,
pub(crate) policies: Option<Vec<TestPolicy>>,
}
#[derive(Default, Clone)]
pub struct ContextTest<'a, C> {
pub(crate) common: CommonDataTest<'a>,
pub(crate) custom: C,
}
pub type InitContextTest<'a> = ContextTest<'a, InitOnlyDataTest>;
#[derive(Default)]
#[doc(hidden)]
pub struct InitOnlyDataTest {
init_origin: Option<AccountAddress>,
}
pub type ReceiveContextTest<'a> = ContextTest<'a, ReceiveOnlyDataTest>;
#[derive(Default)]
#[doc(hidden)]
pub struct ReceiveOnlyDataTest {
pub(crate) invoker: Option<AccountAddress>,
pub(crate) self_address: Option<ContractAddress>,
pub(crate) self_balance: Option<Amount>,
pub(crate) sender: Option<Address>,
pub(crate) owner: Option<AccountAddress>,
}
impl ChainMetaTest {
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> ContextTest<'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 ChainMetaTest { &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> InitContextTest<'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> ReceiveContextTest<'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_self_balance(&mut self, value: Amount) -> &mut Self {
self.custom.self_balance = 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
}
}
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 field necessary for the \
contract",
name
),
}
}
impl HasChainMetadata for ChainMetaTest {
fn slot_time(&self) -> SlotTime { unwrap_ctx_field(self.slot_time, "metadata.slot_time") }
}
impl HasPolicy for TestPolicy {
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.len();
buf[0..len].copy_from_slice(&item.1);
self.position += 1;
Some((item.0, len as u8))
} else {
None
}
}
}
impl<'a, C> HasCommonData for ContextTest<'a, C> {
type MetadataType = ChainMetaTest;
type ParamType = Cursor<&'a [u8]>;
type PolicyIteratorType = crate::vec::IntoIter<TestPolicy>;
type PolicyType = TestPolicy;
fn parameter_cursor(&self) -> Self::ParamType {
Cursor::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 InitContextTest<'a> {
type InitData = ();
fn open(_data: Self::InitData) -> Self { InitContextTest::default() }
fn init_origin(&self) -> AccountAddress {
unwrap_ctx_field(self.custom.init_origin, "init_origin")
}
}
impl<'a> HasReceiveContext for ReceiveContextTest<'a> {
type ReceiveData = ();
fn open(_data: Self::ReceiveData) -> Self { ReceiveContextTest::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 self_balance(&self) -> Amount { unwrap_ctx_field(self.custom.self_balance, "self_balance") }
fn sender(&self) -> Address { unwrap_ctx_field(self.custom.sender, "sender") }
fn owner(&self) -> AccountAddress { unwrap_ctx_field(self.custom.owner, "owner") }
}
impl<'a> HasParameter for Cursor<&'a [u8]> {
fn size(&self) -> u32 { self.data.len() as u32 }
}
pub struct LogRecorder {
pub logs: Vec<Vec<u8>>,
}
impl HasLogger for LogRecorder {
fn init() -> Self {
Self {
logs: Vec::new(),
}
}
fn log_bytes(&mut self, event: &[u8]) { self.logs.push(event.to_vec()) }
}
#[derive(Eq, PartialEq, Debug)]
pub enum ActionsTree {
Accept,
SimpleTransfer {
to: AccountAddress,
amount: Amount,
},
Send {
to: ContractAddress,
receive_name: String,
amount: Amount,
parameter: Vec<u8>,
},
AndThen {
left: Box<ActionsTree>,
right: Box<ActionsTree>,
},
OrElse {
left: Box<ActionsTree>,
right: Box<ActionsTree>,
},
}
impl HasActions for ActionsTree {
fn accept() -> Self { ActionsTree::Accept }
fn simple_transfer(acc: &AccountAddress, amount: Amount) -> Self {
ActionsTree::SimpleTransfer {
to: *acc,
amount,
}
}
fn send(ca: &ContractAddress, receive_name: &str, amount: Amount, parameter: &[u8]) -> Self {
ActionsTree::Send {
to: *ca,
receive_name: receive_name.to_string(),
amount,
parameter: parameter.to_vec(),
}
}
fn and_then(self, then: Self) -> Self {
ActionsTree::AndThen {
left: Box::new(self),
right: Box::new(then),
}
}
fn or_else(self, el: Self) -> Self {
ActionsTree::OrElse {
left: Box::new(self),
right: Box::new(el),
}
}
}
#[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) {}
pub struct ContractStateTest<T> {
pub cursor: Cursor<T>,
}
pub type ContractStateTestBorrowed<'a> = ContractStateTest<&'a mut Vec<u8>>;
pub type ContractStateTestOwned = ContractStateTest<Vec<u8>>;
#[derive(Debug, PartialEq, Eq)]
pub enum ContractStateError {
Overflow,
Write,
Offset,
Default,
}
impl<T: convert::AsRef<[u8]>> Read for ContractStateTest<T> {
fn read(&mut self, buf: &mut [u8]) -> ParseResult<usize> { self.cursor.read(buf) }
}
impl<T: convert::AsMut<[u8]>> Write for ContractStateTest<T> {
type Err = ContractStateError;
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Err> {
let data = &mut self.cursor.data.as_mut()[self.cursor.offset..];
let to_write = cmp::min(data.len(), buf.len());
data.copy_from_slice(&buf[..to_write]);
self.cursor.offset += to_write;
Ok(to_write)
}
}
impl<T: AsMut<Vec<u8>> + AsMut<[u8]> + AsRef<[u8]>> HasContractState<ContractStateError>
for ContractStateTest<T>
{
type ContractStateData = T;
fn open(data: Self::ContractStateData) -> Self {
Self {
cursor: Cursor::new(data),
}
}
fn size(&self) -> u32 { self.cursor.data.as_ref().len() as u32 }
fn truncate(&mut self, new_size: u32) {
if self.size() > new_size {
let new_size = new_size as usize;
let data: &mut Vec<u8> = self.cursor.data.as_mut();
data.truncate(new_size);
if self.cursor.offset > new_size {
self.cursor.offset = new_size
}
}
}
fn reserve(&mut self, len: u32) -> bool {
if len <= constants::MAX_CONTRACT_STATE_SIZE {
if self.size() < len {
let data: &mut Vec<u8> = self.cursor.data.as_mut();
data.resize(len as usize, 0u8);
}
true
} else {
false
}
}
}
impl Default for ContractStateError {
fn default() -> Self { Self::Default }
}
impl From<num::TryFromIntError> for ContractStateError {
fn from(_: num::TryFromIntError) -> Self { ContractStateError::Overflow }
}
impl<T: AsRef<[u8]>> Seek for ContractStateTest<T> {
type Err = ContractStateError;
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Err> {
use ContractStateError::*;
match pos {
SeekFrom::Start(x) => {
let new_offset = x.try_into()?;
if new_offset <= self.cursor.data.as_ref().len() {
self.cursor.offset = new_offset;
Ok(x)
} else {
Err(Offset)
}
}
SeekFrom::End(x) => {
if x <= 0 {
let end: u32 = self.cursor.data.as_ref().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(u64::from(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 <= self.cursor.data.as_ref().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)
}
},
}
}
}
#[cfg(test)]
mod test {
use concordium_contracts_common::{Read, Seek, SeekFrom, Write};
use super::ContractStateTest;
use crate::{constants, traits::HasContractState};
#[test]
fn test_contract_state() {
let data = vec![1; 100];
let mut state = ContractStateTest::open(data);
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(20), "Writing writes an incorrect amount of data.");
assert_eq!(state.cursor.offset, 100, "After writing the cursor is at the end.");
assert_eq!(state.write(&[0; 21]), Ok(0), "Writing again writes some 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(80));
assert_eq!(state.read(&mut buf), Ok(20), "Reading from offset 80 should read 20 bytes.");
assert_eq!(&buf[0..20], &state.cursor.data[80..100], "Incorrect data was read.");
assert_eq!(
state.cursor.offset, 100,
"After reading the offset is in the correct position."
);
assert!(state.reserve(200), "Could not increase state to 200.");
assert!(
!state.reserve(constants::MAX_CONTRACT_STATE_SIZE + 1),
"State should not be resizable beyond max limit."
);
assert_eq!(state.write(&[2; 100]), Ok(100), "Should have written 100 bytes.");
assert_eq!(state.cursor.offset, 200, "After writing the offset should be 200.");
state.truncate(50);
assert_eq!(state.cursor.offset, 50, "After truncation the state should be 50.");
assert_eq!(
state.write(&[1; 1000]),
Ok(0),
"Writing at the end after truncation should do nothing."
);
}
}