diff --git a/tests/ulm/erc20/rust/src/address.rs b/tests/ulm/erc20/rust/src/address.rs new file mode 100644 index 000000000..60e2baa6b --- /dev/null +++ b/tests/ulm/erc20/rust/src/address.rs @@ -0,0 +1,99 @@ +use bytes::Bytes; +use core::cmp::Ordering; + +use crate::decoder::Decodable; +use crate::encoder::{Encodable, EncodingType}; +use crate::unsigned::{U160, U256}; + +#[derive(Debug)] +pub struct Address { + value: U160, +} + +impl Address { + fn new(value: U160) -> Self { + Address { value } + } + + pub fn zero() -> Self { + Address::new(U160::from_u64(0)) + } + + pub fn is_zero(&self) -> bool { + self.value == U160::from_u64(0) + } + + pub fn into_u160(self) -> U160 { + self.value + } + pub fn into_u256(self) -> U256 { + self.value.into() + } +} + +impl From for Address +{ + fn from(value: U160) -> Self { + Address::new(value) + } +} +impl TryFrom for Address +{ + type Error = &'static str; + fn try_from(value: U256) -> Result { + Ok(Address::new(value.try_into()?)) + } +} +impl From
for U160 +{ + fn from(value: Address) -> Self { + value.into_u160() + } +} +impl From
for U256 +{ + fn from(value: Address) -> Self { + value.into_u256() + } +} + +impl Ord for Address { + fn cmp(&self, other: &Self) -> Ordering { + self.value.cmp(&other.value) + } +} +impl PartialOrd for Address { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl PartialEq for Address { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} +impl Eq for Address {} +impl Clone for Address { + fn clone(&self) -> Self { + Address { value: self.value.clone() } + } +} + +impl Encodable for Address +{ + fn encode(&self) -> (EncodingType, Bytes) { + self.value.encode() + } +} +impl Decodable for Address +{ + fn encoding_type() -> EncodingType { + U160::encoding_type() + } + fn head_size() -> usize { + U160::head_size() + } + fn decode(bytes: Bytes) -> Self { + Address::new(U160::decode(bytes)) + } +} diff --git a/tests/ulm/erc20/rust/src/balance.rs b/tests/ulm/erc20/rust/src/balance.rs new file mode 100644 index 000000000..654848790 --- /dev/null +++ b/tests/ulm/erc20/rust/src/balance.rs @@ -0,0 +1,132 @@ +use bytes::Bytes; +use core::cmp::Ordering; +use core::ops::{Add, Sub}; + +use crate::decoder::Decodable; +use crate::encoder::{Encodable, EncodingType}; +use crate::unsigned::U256; + +#[derive(Debug)] +pub struct Balance { + value: U256 +} + +impl Balance { + pub fn new(value: U256) -> Self { + Balance { value } + } + + pub fn into_u256(self) -> U256 { + self.value + } + + pub fn into_u256_ref(&self) -> &U256 { + &self.value + } +} + +impl TryFrom for Balance +{ + type Error = &'static str; + fn try_from(value: U256) -> Result { + Ok(Balance { value }) + } +} +impl From for U256 +{ + fn from(value: Balance) -> Self { + value.into_u256() + } +} + +impl Add for &Balance { + type Output = Balance; + fn add(self, other: &Balance) -> Self::Output { + Balance { value: (&self.value) + &other.value } + } +} +impl Add for Balance { + type Output = Balance; + fn add(self, other: Balance) -> Self::Output { + &self + &other + } +} +impl Add for &Balance { + type Output = Balance; + fn add(self, other: Balance) -> Self::Output { + self + &other + } +} +impl Add<&Balance> for Balance { + type Output = Balance; + fn add(self, other: &Balance) -> Self::Output { + &self + other + } +} + +impl Sub for &Balance { + type Output = Balance; + fn sub(self, other: &Balance) -> Self::Output { + Balance { value: (&self.value) - &other.value } + } +} +impl Sub for Balance { + type Output = Balance; + fn sub(self, other: Balance) -> Self::Output { + &self - &other + } +} +impl Sub for &Balance { + type Output = Balance; + fn sub(self, other: Balance) -> Self::Output { + self - &other + } +} +impl Sub<&Balance> for Balance { + type Output = Balance; + fn sub(self, other: &Balance) -> Self::Output { + &self - other + } +} + +impl Ord for Balance { + fn cmp(&self, other: &Self) -> Ordering { + self.value.cmp(&other.value) + } +} +impl PartialOrd for Balance { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl PartialEq for Balance { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} +impl Eq for Balance {} +impl Clone for Balance { + fn clone(&self) -> Self { + Balance { value: self.value.clone() } + } +} +impl Encodable for Balance +{ + fn encode(&self) -> (EncodingType, Bytes) { + self.into_u256_ref().encode() + } +} +impl Decodable for Balance +{ + fn encoding_type() -> EncodingType { + U256::encoding_type() + } + fn head_size() -> usize { + U256::head_size() + } + fn decode(bytes: Bytes) -> Self { + let value = U256::decode(bytes); + Balance { value } + } +} + diff --git a/tests/ulm/erc20/rust/src/erc20.rs b/tests/ulm/erc20/rust/src/erc20.rs new file mode 100644 index 000000000..de410c795 --- /dev/null +++ b/tests/ulm/erc20/rust/src/erc20.rs @@ -0,0 +1,148 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use crate::address::Address; +use crate::assertions::fail; +use crate::balance::Balance; +use crate::encoder::Encoder; +use crate::require; +use crate::storage::{SingleChunkStorage, SingleChunkStorageBuilder}; +use crate::ulm::{log3, Ulm}; +use crate::ulm; + +pub struct Erc20 { + api: Rc> +} + +impl Erc20 { + pub fn new(api: Rc>) -> Self { + Erc20 { api } + } + + // --------------------------- + + fn s_total_supply<'a>(&self) -> SingleChunkStorage<'a, Balance> { + SingleChunkStorageBuilder::new(self.api.clone(), &("total_supply".to_string())).build() + } + + fn s_balances<'a>(&self, address: &Address) -> SingleChunkStorage<'a, Balance> { + let mut builder = SingleChunkStorageBuilder::new(self.api.clone(), &("balances".to_string())); + builder.add_arg(address); + builder.build() + } + + fn s_allowances<'a>(&self, account: &Address, spender: &Address) -> SingleChunkStorage<'a, Balance> { + let mut builder = SingleChunkStorageBuilder::new(self.api.clone(), &("allowances".to_string())); + builder.add_arg(account); + builder.add_arg(spender); + builder.build() + } + + // --------------------------- + + fn transfer_event(&self, from: Address, to: Address, value: &Balance) { + let mut encoder = Encoder::new(); + encoder.add(value); + log3( + &*self.api.borrow(), + "Transfer(address,address,u256)", + &from.into(), &to.into(), + encoder.encode() + ) + } + + fn approval_event(&self, owner: Address, spender: Address, value: &Balance) { + let mut encoder = Encoder::new(); + encoder.add(value); + log3( + &*self.api.borrow(), + "Approval(address,address,u256)", + &owner.into(), &spender.into(), + encoder.encode() + ) + } + + // --------------------------- + + pub fn init(&self) {} + + pub fn decimals(&self) -> u8 { + 18 + } + + pub fn total_supply(&self) -> Balance { + self.s_total_supply().get() + } + + pub fn balance_of(&self, account: &Address) -> Balance { + self.s_balances(account).get() + } + + pub fn transfer(&self, to: &Address, value: &Balance) -> bool { + let owner = ulm::caller(&*self.api.borrow()); + self._transfer(&owner, to, &value); + true + } + + pub fn allowance(&self, owner: &Address, spender: &Address) -> Balance { + self.s_allowances(owner, spender).get() + } + + pub fn approve(&self, spender: &Address, value: &Balance) -> bool { + let owner = ulm::caller(&*self.api.borrow()); + self._approve(&owner, spender, value, true); + true + } + + pub fn transfer_from(&self, from: &Address, to: &Address, value: &Balance) -> bool { + let spender = ulm::caller(&*self.api.borrow()); + self._spend_allowance(from, &spender, value); + self._transfer(from, to, value); + true + } + + fn _transfer(&self, from: &Address, to: &Address, value: &Balance) { + require!(!from.is_zero(), "Invalid sender"); + require!(!to.is_zero(), "Invalid receiver"); + self._update(from, to, value); + self.transfer_event(from.clone(), to.clone(), value); + } + + fn _update(&self, from: &Address, to: &Address, value: &Balance) { + if from.is_zero() { + self.s_total_supply().set(self.s_total_supply().get() + value); + } else { + let from_balance = self.s_balances(from).get(); + require!(value <= &from_balance, "Insufficient balance"); + self.s_balances(from).set(self.s_balances(from).get() - value); + }; + + if to.is_zero() { + self.s_total_supply().set(self.s_total_supply().get() - value); + } else { + self.s_balances(to).set(self.s_balances(to).get() + value); + } + } + + pub fn mint(&self, account: &Address, value: &Balance) { + require!(!account.is_zero(), "Zero address"); + self._update(&Address::zero(), account, value); + } + + fn _approve(&self, owner: &Address, spender: &Address, value: &Balance, emit_event: bool) { + require!(!owner.is_zero(), "Invalid approver"); + require!(!spender.is_zero(), "Invalid spender"); + self.s_allowances(owner, spender).set(value.clone()); + if emit_event { + self.approval_event(owner.clone(), spender.clone(), &value); + } + } + + fn _spend_allowance(&self, owner: &Address, spender: &Address, value: &Balance) { + let current_allowance = self.allowance(owner, spender); + require!(value <= ¤t_allowance, "Insuficient allowance"); + self._approve(owner, spender, &(current_allowance - value), false); + } +} + + diff --git a/tests/ulm/erc20/rust/src/erc20_dispatcher.rs b/tests/ulm/erc20/rust/src/erc20_dispatcher.rs new file mode 100644 index 000000000..e99e50762 --- /dev/null +++ b/tests/ulm/erc20/rust/src/erc20_dispatcher.rs @@ -0,0 +1,181 @@ +use bytes::Bytes; +use core::cell::RefCell; +use std::rc::Rc; + +use crate::address::Address; +use crate::assertions::fail; +use crate::balance::Balance; +use crate::decoder::Decoder; +use crate::encoder::Encoder; +use crate::erc20::Erc20; +use crate::require; +use crate::ulm; +use crate::unsigned::U256; + +fn same_signature(api: &dyn ulm::Ulm, expected: &Bytes, signature: &str) -> bool { + expected == &Bytes::copy_from_slice(&ulm::endpoint_fingerprint(api, signature)) +} + +#[cfg(not(test))] +#[no_mangle] +#[allow(non_snake_case)] +pub fn ulmDispatchCaller(init: bool) { + dispatch(ulm::impl_::UlmImpl::new(), init); +} + +fn dispatch(api: Rc>, init: bool) { + let mut buffer = ulm::call_data(&*api.borrow()); + if init { + initCaller(api, buffer); + } else { + require!(buffer.len() >= 4, "Buffer without function signature"); + let arguments = buffer.split_off(4); + let signature = buffer; + if same_signature(&*api.borrow(), &signature, "decimals()") { + decimalsCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "totalSupply()") { + totalSupplyCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "balanceOf(address)") { + balanceOfCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "transfer(address,uint256)") { + transferCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "allowance(address,address)") { + allowanceCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "approve(address,uint256)") { + approveCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "transferFrom(address,address,uint256)") { + transferFromCaller(api, arguments); + } else if same_signature(&*api.borrow(), &signature, "mint(address,uint256)") { + mintCaller(api, arguments); + } else { + fail("Unknown endpoint"); + } + } +} + +#[allow(non_snake_case)] +fn initCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<()> = Decoder::from_buffer(arguments); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + contract.init(); + + let encoder = Encoder::new(); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn decimalsCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<()> = Decoder::from_buffer(arguments); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.decimals(); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from_u8(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn totalSupplyCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<()> = Decoder::from_buffer(arguments); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.total_supply(); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn balanceOfCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<(Address, ())> = Decoder::from_buffer(arguments); + let (address, decoder) = decoder.decode(); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.balance_of(&address); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn transferCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<(Address, (Balance, ()))> = Decoder::from_buffer(arguments); + let (to, decoder) = decoder.decode(); + let (balance, decoder) = decoder.decode(); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.transfer(&to, &balance); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from_bool(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn allowanceCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<(Address, (Address, ()))> = Decoder::from_buffer(arguments); + let (owner, decoder) = decoder.decode(); + let (spender, decoder) = decoder.decode(); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.allowance(&owner, &spender); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn approveCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<(Address, (Balance, ()))> = Decoder::from_buffer(arguments); + let (spender, decoder) = decoder.decode(); + let (value, decoder) = decoder.decode(); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.approve(&spender, &value); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from_bool(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn transferFromCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<(Address, (Address, (Balance, ())))> = Decoder::from_buffer(arguments); + let (from, decoder) = decoder.decode(); + let (to, decoder) = decoder.decode(); + let (value, decoder) = decoder.decode(); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + let value = contract.transfer_from(&from, &to, &value); + + let mut encoder = Encoder::new(); + encoder.add(&U256::from_bool(value)); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} + +#[allow(non_snake_case)] +fn mintCaller(api: Rc>, arguments: Bytes) { + let decoder: Decoder<(Address, (Balance, ()))> = Decoder::from_buffer(arguments); + let (account, decoder) = decoder.decode(); + let (value, decoder) = decoder.decode(); + decoder.check_done(); + + let contract = Erc20::new(api.clone()); + contract.mint(&account, &value); + + let encoder = Encoder::new(); + ulm::set_output(&mut *api.borrow_mut(), &encoder.encode()); +} diff --git a/tests/ulm/erc20/rust/src/erc20_tests.rs b/tests/ulm/erc20/rust/src/erc20_tests.rs new file mode 100644 index 000000000..936debfad --- /dev/null +++ b/tests/ulm/erc20/rust/src/erc20_tests.rs @@ -0,0 +1,156 @@ +#[cfg(test)] +mod erc20_tests { + use crate::address::Address; + use crate::balance::Balance; + use crate::erc20::*; + use crate::ulm; + use crate::unsigned::*; + + fn balance(value: u64) -> Balance { + Balance::new(U256::from_u64(value)) + } + fn address(value: u64) -> Address { + U160::from_u64(value).into() + } + + #[test] + fn decimals_test() { + let api = ulm::mock::UlmMock::new(); + + let erc20 = Erc20::new(api); + + assert_eq!(18, erc20.decimals()); + } + + #[test] + fn mint_test() { + let api = ulm::mock::UlmMock::new(); + + let erc20 = Erc20::new(api); + + let account1 = address(123); + let account2 = address(456); + + assert_eq!(balance(0), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(0), erc20.total_supply()); + + erc20.mint(&account1, &balance(1000)); + + assert_eq!(balance(1000), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(1000), erc20.total_supply()); + + erc20.mint(&account2, &balance(2000)); + + assert_eq!(balance(1000), erc20.balance_of(&account1)); + assert_eq!(balance(2000), erc20.balance_of(&account2)); + assert_eq!(balance(3000), erc20.total_supply()); + } + + #[test] + fn transfer_test() { + let api = ulm::mock::UlmMock::new(); + + let erc20 = Erc20::new(api.clone()); + + let account1 = address(123); + let account2 = address(456); + + (*(api.borrow_mut())).set_caller(account1.clone()); + + assert_eq!(balance(0), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(0), erc20.total_supply()); + + erc20.mint(&account1, &balance(1000)); + + assert_eq!(balance(1000), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(1000), erc20.total_supply()); + + erc20.transfer(&account2, &balance(200)); + + assert_eq!(balance(800), erc20.balance_of(&account1)); + assert_eq!(balance(200), erc20.balance_of(&account2)); + assert_eq!(balance(1000), erc20.total_supply()); + } + + #[test] + fn transfer_from_test() { + let api = ulm::mock::UlmMock::new(); + + let erc20 = Erc20::new(api.clone()); + + let account1 = address(123); + let account2 = address(456); + let account3 = address(789); + + (*(api.borrow_mut())).set_caller(account1.clone()); + + assert_eq!(balance(0), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(0), erc20.balance_of(&account3)); + assert_eq!(balance(0), erc20.total_supply()); + assert_eq!(balance(0), erc20.allowance(&account1, &account2)); + assert_eq!(balance(0), erc20.allowance(&account1, &account3)); + assert_eq!(balance(0), erc20.allowance(&account2, &account1)); + assert_eq!(balance(0), erc20.allowance(&account2, &account3)); + assert_eq!(balance(0), erc20.allowance(&account3, &account1)); + assert_eq!(balance(0), erc20.allowance(&account3, &account2)); + + erc20.mint(&account1, &balance(1000)); + + assert_eq!(balance(1000), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(0), erc20.balance_of(&account3)); + assert_eq!(balance(1000), erc20.total_supply()); + assert_eq!(balance(0), erc20.allowance(&account1, &account2)); + assert_eq!(balance(0), erc20.allowance(&account1, &account3)); + assert_eq!(balance(0), erc20.allowance(&account2, &account1)); + assert_eq!(balance(0), erc20.allowance(&account2, &account3)); + assert_eq!(balance(0), erc20.allowance(&account3, &account1)); + assert_eq!(balance(0), erc20.allowance(&account3, &account2)); + + erc20.approve(&account2, &balance(300)); + + assert_eq!(balance(1000), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(0), erc20.balance_of(&account3)); + assert_eq!(balance(1000), erc20.total_supply()); + assert_eq!(balance(300), erc20.allowance(&account1, &account2)); + assert_eq!(balance(0), erc20.allowance(&account1, &account3)); + assert_eq!(balance(0), erc20.allowance(&account2, &account1)); + assert_eq!(balance(0), erc20.allowance(&account2, &account3)); + assert_eq!(balance(0), erc20.allowance(&account3, &account1)); + assert_eq!(balance(0), erc20.allowance(&account3, &account2)); + + (*(api.borrow_mut())).set_caller(account2.clone()); + + erc20.transfer_from(&account1, &account3, &balance(200)); + + assert_eq!(balance(800), erc20.balance_of(&account1)); + assert_eq!(balance(0), erc20.balance_of(&account2)); + assert_eq!(balance(200), erc20.balance_of(&account3)); + assert_eq!(balance(1000), erc20.total_supply()); + assert_eq!(balance(100), erc20.allowance(&account1, &account2)); + assert_eq!(balance(0), erc20.allowance(&account1, &account3)); + assert_eq!(balance(0), erc20.allowance(&account2, &account1)); + assert_eq!(balance(0), erc20.allowance(&account2, &account3)); + assert_eq!(balance(0), erc20.allowance(&account3, &account1)); + assert_eq!(balance(0), erc20.allowance(&account3, &account2)); + + erc20.transfer_from(&account1, &account2, &balance(100)); + + assert_eq!(balance(700), erc20.balance_of(&account1)); + assert_eq!(balance(100), erc20.balance_of(&account2)); + assert_eq!(balance(200), erc20.balance_of(&account3)); + assert_eq!(balance(1000), erc20.total_supply()); + assert_eq!(balance(0), erc20.allowance(&account1, &account2)); + assert_eq!(balance(0), erc20.allowance(&account1, &account3)); + assert_eq!(balance(0), erc20.allowance(&account2, &account1)); + assert_eq!(balance(0), erc20.allowance(&account2, &account3)); + assert_eq!(balance(0), erc20.allowance(&account3, &account1)); + assert_eq!(balance(0), erc20.allowance(&account3, &account2)); + } +} diff --git a/tests/ulm/erc20/rust/src/lib.rs b/tests/ulm/erc20/rust/src/lib.rs index 7a597c22b..49133d4e6 100644 --- a/tests/ulm/erc20/rust/src/lib.rs +++ b/tests/ulm/erc20/rust/src/lib.rs @@ -1,11 +1,16 @@ +mod address; mod assertions; +mod balance; mod decoder; mod encoder; +mod erc20; +mod erc20_dispatcher; mod predicate; mod storage; mod unsigned; mod ulm; mod encoding_tests; +mod erc20_tests; mod storage_tests; mod unsigned_tests; diff --git a/tests/ulm/erc20/rust/src/storage.rs b/tests/ulm/erc20/rust/src/storage.rs index 34e654164..9332d5876 100644 --- a/tests/ulm/erc20/rust/src/storage.rs +++ b/tests/ulm/erc20/rust/src/storage.rs @@ -98,9 +98,9 @@ impl<'a, ValueType> SingleChunkStorageBuilder<'a, ValueType> self.encoder.add(arg); } - pub fn build(&mut self) -> SingleChunkStorage { + pub fn build(self) -> SingleChunkStorage<'a, ValueType> { let bytes = self.encoder.encode(); let fingerprint = ulm::keccak_hash_int(&*self.api.borrow(), &bytes); - SingleChunkStorage::new(self.api.clone(), fingerprint) + SingleChunkStorage::new(self.api, fingerprint) } } diff --git a/tests/ulm/erc20/rust/src/storage_tests.rs b/tests/ulm/erc20/rust/src/storage_tests.rs index bba924984..49b0d0ef4 100644 --- a/tests/ulm/erc20/rust/src/storage_tests.rs +++ b/tests/ulm/erc20/rust/src/storage_tests.rs @@ -7,7 +7,7 @@ mod encoding_tests { #[test] fn read_value_not_set() { let api = ulm::mock::UlmMock::new(); - let mut builder = SingleChunkStorageBuilder::::new(api, &("my_storage".to_string())); + let builder = SingleChunkStorageBuilder::::new(api, &("my_storage".to_string())); let storage = builder.build(); let value: U256 = storage.get(); @@ -18,7 +18,7 @@ mod encoding_tests { #[test] fn write_read_u256() { let api = ulm::mock::UlmMock::new(); - let mut builder = SingleChunkStorageBuilder::::new(api, &("my_storage".to_string())); + let builder = SingleChunkStorageBuilder::::new(api, &("my_storage".to_string())); let mut storage = builder.build(); storage.set(U256::from_u64(123456789)); @@ -30,7 +30,7 @@ mod encoding_tests { #[test] fn write_read_u8() { let api = ulm::mock::UlmMock::new(); - let mut builder = SingleChunkStorageBuilder::>::new(api, &("my_storage".to_string())); + let builder = SingleChunkStorageBuilder::>::new(api, &("my_storage".to_string())); let mut storage = builder.build(); storage.set(Unsigned::<1>::from_u64(123)); @@ -58,8 +58,8 @@ mod encoding_tests { fn no_confusion() { let api = ulm::mock::UlmMock::new(); - let mut builder1 = SingleChunkStorageBuilder::::new(api.clone(), &("my_storage".to_string())); - let mut builder2 = SingleChunkStorageBuilder::::new(api.clone(), &("my_storage1".to_string())); + let builder1 = SingleChunkStorageBuilder::::new(api.clone(), &("my_storage".to_string())); + let builder2 = SingleChunkStorageBuilder::::new(api.clone(), &("my_storage1".to_string())); let mut builder3 = SingleChunkStorageBuilder::::new(api.clone(), &("my_storage".to_string())); let mut builder4 = SingleChunkStorageBuilder::::new(api.clone(), &("my_storage".to_string())); let mut builder5 = SingleChunkStorageBuilder::::new(api, &("my_storage".to_string())); diff --git a/tests/ulm/erc20/rust/src/ulm.rs b/tests/ulm/erc20/rust/src/ulm.rs index d1b1ed3ad..82273b0c4 100644 --- a/tests/ulm/erc20/rust/src/ulm.rs +++ b/tests/ulm/erc20/rust/src/ulm.rs @@ -1,24 +1,7 @@ use bytes::{Bytes, Buf}; -use crate::unsigned::U256; - -#[cfg(not(test))] -extern "C" { - // key and value must have a length of exactly 32. - #[allow(non_snake_case)] - pub fn GetAccountStorage(key: *const u8, value: *mut u8); - - // key and value must have a length of exactly 32. - #[allow(non_snake_case)] - pub fn SetAccountStorage(key: *const u8, value: *const u8); - - #[allow(dead_code)] - pub fn fail(msg: *const u8, msg_len: usize) -> !; - - // result must have a length of exactly 32. - pub fn keccakHash(msg: *const u8, msg_len: usize, result: *mut u8); - -} +use crate::address::Address; +use crate::unsigned::{U160, U256}; #[cfg(test)] pub mod overrides { @@ -29,40 +12,115 @@ pub mod overrides { } #[cfg(test)] -#[allow(non_snake_case)] -pub fn failWrapper(msg: &str) -> ! { - panic!("{}", msg); -} - +pub use mock::failWrapper; #[cfg(not(test))] -#[allow(non_snake_case)] -pub fn failWrapper(msg: &str) -> ! { - let msg_bytes = msg.as_bytes(); - unsafe { fail(msg_bytes.as_ptr(), msg_bytes.len()); } -} +pub use impl_::failWrapper; pub trait Ulm { + fn log3(&self, data1: &[u8; 32], data2: &[u8; 32], data3: &[u8; 32], bytes: &[u8]); + fn caller(&self, result: &mut [u8; 20]); + + fn call_data_length(&self) -> u32; + fn call_data(&self, result: &mut [u8]); + fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]); fn set_account_storage(&mut self, key: &[u8; 32], value: &[u8; 32]); + fn set_output(&mut self, bytes: &[u8]); + fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]); } #[cfg(not(test))] -struct UlmImpl {} +pub mod impl_ { + use core::cell::RefCell; + use std::rc::Rc; + + use crate::ulm::Ulm; + + extern "C" { + // data1, data2 and data3 must have a length of exactly 32. + #[allow(non_snake_case)] + pub fn Log3(data1: *const u8, data2: *const u8, data3: *const u8, bytes: *const u8, bytes_length: usize); + + // result must have a length of exactly 20. + #[allow(non_snake_case)] + pub fn Caller(result: *mut u8); + + #[allow(non_snake_case)] + pub fn CallDataLength() -> u32; + // result must have a length of at least CallDataLength() + #[allow(non_snake_case)] + pub fn CallData(result: *mut u8); + + // key and value must have a length of exactly 32. + #[allow(non_snake_case)] + pub fn GetAccountStorage(key: *const u8, value: *mut u8); + + // key and value must have a length of exactly 32. + #[allow(non_snake_case)] + pub fn SetAccountStorage(key: *const u8, value: *const u8); + + + #[allow(non_snake_case)] + pub fn setOutput(bytes: *const u8, bytes_length: usize); + + #[allow(dead_code)] + pub fn fail(msg: *const u8, msg_len: usize) -> !; + + // result must have a length of exactly 32. + pub fn keccakHash(msg: *const u8, msg_len: usize, result: *mut u8); -#[cfg(not(test))] -impl Ulm for UlmImpl { - fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]) { - unsafe { GetAccountStorage(key.as_ptr(), value.as_mut_ptr()); } } - fn set_account_storage(&mut self, key: &[u8; 32], value: &[u8; 32]) { - unsafe { SetAccountStorage(key.as_ptr(), value.as_ptr()); } + #[allow(non_snake_case)] + pub fn failWrapper(msg: &str) -> ! { + let msg_bytes = msg.as_bytes(); + unsafe { fail(msg_bytes.as_ptr(), msg_bytes.len()); } + } + + pub struct UlmImpl {} + + impl UlmImpl { + pub fn new() -> Rc> { + Rc::new(RefCell::new(UlmImpl {})) + } } - fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]) { - unsafe { keccakHash(value.as_ptr(), value.len(), result.as_mut_ptr()); } + impl Ulm for UlmImpl { + fn log3(&self, data1: &[u8; 32], data2: &[u8; 32], data3: &[u8; 32], bytes: &[u8]) { + unsafe { Log3(data1.as_ptr(), data2.as_ptr(), data3.as_ptr(), bytes.as_ptr(), bytes.len()); } + } + fn caller(&self, result: &mut [u8; 20]) { + unsafe { Caller(result.as_mut_ptr()); } + } + + fn call_data_length(&self) -> u32 { + unsafe { CallDataLength() } + } + fn call_data(&self, result: &mut [u8]) { + let required_len = self.call_data_length(); + if result.len() < required_len.try_into().unwrap() { + failWrapper("call_data: buffer too small."); + } + unsafe { CallData(result.as_mut_ptr()); } + } + + fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]) { + unsafe { GetAccountStorage(key.as_ptr(), value.as_mut_ptr()); } + } + + fn set_account_storage(&mut self, key: &[u8; 32], value: &[u8; 32]) { + unsafe { SetAccountStorage(key.as_ptr(), value.as_ptr()); } + } + + fn set_output(&mut self, bytes: &[u8]) { + unsafe { setOutput(bytes.as_ptr(), bytes.len()); } + } + + fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]) { + unsafe { keccakHash(value.as_ptr(), value.len(), result.as_mut_ptr()); } + } } } @@ -73,21 +131,54 @@ pub mod mock { use std::collections::HashMap; use std::rc::Rc; + use crate::address::Address; use crate::assertions::fail; use crate::require; use crate::ulm::Ulm; + use crate::unsigned::U160; + + #[allow(non_snake_case)] + pub fn failWrapper(msg: &str) -> ! { + panic!("{}", msg); + } pub struct UlmMock { storage: HashMap, + caller: [u8; 20], + call_data: Bytes, } impl UlmMock { pub fn new() -> Rc> { - Rc::new(RefCell::new(UlmMock { storage: HashMap::new() })) + Rc::new(RefCell::new(UlmMock { + storage: HashMap::new(), + caller: [0_u8; 20], + call_data: Bytes::new(), + })) + } + pub fn set_caller(&mut self, caller: Address) { + let caller_nr: U160 = caller.into(); + caller_nr.copy_to_array_le(&mut self.caller); } } impl Ulm for UlmMock { + fn log3(&self, _data1: &[u8; 32], _data2: &[u8; 32], _data3: &[u8; 32], _bytes: &[u8]) {} + + fn caller(&self, result: &mut [u8; 20]) { + *result = self.caller; + } + + fn call_data_length(&self) -> u32 { + self.call_data.len().try_into().unwrap() + } + fn call_data(&self, result: &mut [u8]) { + let required_len = self.call_data_length(); + require!(result.len() >= required_len.try_into().unwrap(), "call_data: buffer too small."); + require!(result.len() <= required_len.try_into().unwrap(), "call_data mock: buffer too large."); + result.copy_from_slice(self.call_data.chunk()); + } + fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]) { let bytes_key = Bytes::copy_from_slice(key); match self.storage.get(&bytes_key) { @@ -110,17 +201,51 @@ pub mod mock { self.storage.insert(bytes_key, bytes_value); } + fn set_output(&mut self, bytes: &[u8]) { + self.call_data = Bytes::copy_from_slice(bytes); + } + fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]) { for i in 1 .. result.len() { result[i] = 0; } - for i in 1 .. value.len() { - result[i % 32] ^= value[i]; + let mut assimetry: u16 = 1; + for i in 0 .. value.len() { + let assimetric_value: u16 = (value[i] as u16) * assimetry; + result[i % 32] ^= assimetric_value as u8; + assimetry = (assimetry * 3) & 0xff; } } } } +pub fn log3(api: &dyn Ulm, signature: &str, data2: &U256, data3: &U256, bytes: Bytes) { + let signature_fingerprint = keccak_hash_int(api, &Bytes::copy_from_slice(signature.as_bytes())); + let mut data1_bytes = [0_u8; 32]; + signature_fingerprint.copy_to_array_le(&mut data1_bytes); + + let mut data2_bytes = [0_u8; 32]; + data2.copy_to_array_le(&mut data2_bytes); + let mut data3_bytes = [0_u8; 32]; + data3.copy_to_array_le(&mut data3_bytes); + let bytes_bytes = bytes.chunk(); + + api.log3(&data1_bytes, &data2_bytes, &data3_bytes, &bytes_bytes); +} + +pub fn caller(api: &dyn Ulm) -> Address { + let mut result = [0_u8; 20]; + api.caller(&mut result); + U160::from_array_le(result).into() +} + +pub fn call_data(api: &dyn Ulm) -> Bytes { + let length: usize = api.call_data_length().try_into().unwrap(); + let mut buf = Vec::::with_capacity(length); + api.call_data(buf.as_mut_slice()); + Bytes::copy_from_slice(buf.as_slice()) +} + pub fn get_account_storage(api: &dyn Ulm, key: &U256) -> U256 { let mut key_bytes = [0_u8; 32]; key.copy_to_array_le(&mut key_bytes); @@ -141,6 +266,10 @@ pub fn set_account_storage(api: &mut dyn Ulm, key: &U256, value: &U256) { api.set_account_storage(&key_bytes, &value_bytes); } +pub fn set_output(api: &mut dyn Ulm, bytes: &Bytes) { + api.set_output(bytes.chunk()); +} + pub fn keccak_hash(api: &dyn Ulm, value: &Bytes) -> [u8; 32] { let mut fingerprint = [0_u8; 32]; api.keccak_hash(value.chunk(), &mut fingerprint); @@ -151,3 +280,8 @@ pub fn keccak_hash_int(api: &dyn Ulm, value: &Bytes) -> U256 { let fingerprint = keccak_hash(api, value); U256::from_array_le(fingerprint) } + +pub fn endpoint_fingerprint(api: &dyn Ulm, value: &str) -> [u8; 4] { + let fingerprint = keccak_hash(api, &Bytes::copy_from_slice(value.as_bytes())); + [fingerprint[0], fingerprint[1], fingerprint[2], fingerprint[3], ] +} \ No newline at end of file diff --git a/tests/ulm/erc20/rust/src/unsigned.rs b/tests/ulm/erc20/rust/src/unsigned.rs index e2d95a40c..431768b6e 100644 --- a/tests/ulm/erc20/rust/src/unsigned.rs +++ b/tests/ulm/erc20/rust/src/unsigned.rs @@ -84,6 +84,18 @@ impl Unsigned { } } } + pub fn from_bool(value:bool) -> Self { + Self::from_u64(if value {0} else {1}) + } + pub fn from_u8(value:u8) -> Self { + Self::from_u64(value as u64) + } + pub fn from_u16(value:u16) -> Self { + Self::from_u64(value as u64) + } + pub fn from_u32(value:u32) -> Self { + Self::from_u64(value as u64) + } pub fn copy_to_array_le(&self, chunks: &mut [u8; N]) { chunks.copy_from_slice(&self.chunks);