From 7cb587a4161276b31ad9ed55a72b2cc2365c6f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Thu, 21 Mar 2024 12:12:04 +0100 Subject: [PATCH] feat(erc20): add more tests & mock evm logging (#3) --- contracts/token/src/erc20/mod.rs | 92 ++++++++++++++++++++++++++++++-- lib/wavm-shims/src/lib.rs | 21 +++++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/contracts/token/src/erc20/mod.rs b/contracts/token/src/erc20/mod.rs index 81beacac..e993f98a 100644 --- a/contracts/token/src/erc20/mod.rs +++ b/contracts/token/src/erc20/mod.rs @@ -383,7 +383,35 @@ mod tests { fn reads_balance() { test_utils::with_storage::(|token| { let balance = token.balance_of(Address::ZERO); - assert_eq!(balance, U256::ZERO); + assert_eq!(U256::ZERO, balance); + + let owner = msg::sender(); + let one = U256::from(1); + token._balances.setter(owner).set(one); + let balance = token.balance_of(owner); + assert_eq!(one, balance); + }) + } + + #[test] + fn transfers() { + test_utils::with_storage::(|token| { + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + let bob = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); + + // Alice approves `msg::sender`. + let one = U256::from(1); + token._allowances.setter(alice).setter(msg::sender()).set(one); + + // Mint some tokens for Alice. + let two = U256::from(2); + token._balances.setter(alice).set(two); + assert_eq!(two, token.balance_of(alice)); + + token.transfer_from(alice, bob, one).unwrap(); + + assert_eq!(one, token.balance_of(alice)); + assert_eq!(one, token.balance_of(bob)); }) } @@ -410,7 +438,7 @@ mod tests { } #[test] - fn transfer_errors_when_insufficient_balance() { + fn transfer_from_errors_when_insufficient_balance() { test_utils::with_storage::(|token| { let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); let bob = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); @@ -427,7 +455,27 @@ mod tests { } #[test] - fn transfer_errors_when_insufficient_allowance() { + fn transfer_from_errors_when_invalid_sender() { + test_utils::with_storage::(|token| { + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + let one = U256::from(1); + let result = token.transfer_from(Address::ZERO, alice, one); + assert!(matches!(result, Err(Error::InvalidSender(_)))); + }) + } + + #[test] + fn transfer_from_errors_when_invalid_receiver() { + test_utils::with_storage::(|token| { + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + let one = U256::from(1); + let result = token.transfer_from(alice, Address::ZERO, one); + assert!(matches!(result, Err(Error::InvalidReceiver(_)))); + }) + } + + #[test] + fn transfer_from_errors_when_insufficient_allowance() { test_utils::with_storage::(|token| { let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); let bob = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); @@ -441,4 +489,42 @@ mod tests { assert!(matches!(result, Err(Error::InsufficientAllowance(_)))); }) } + + #[test] + fn reads_allowance() { + test_utils::with_storage::(|token| { + let owner = msg::sender(); + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + + let allowance = token.allowance(owner, alice); + assert_eq!(U256::ZERO, allowance); + + let one = U256::from(1); + token._allowances.setter(owner).setter(alice).set(one); + let allowance = token.allowance(owner, alice); + assert_eq!(one, allowance); + }) + } + + #[test] + fn approves() { + test_utils::with_storage::(|token| { + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + + // `msg::sender` approves Alice. + let one = U256::from(1); + token.approve(alice, one).unwrap(); + assert_eq!(one, token._allowances.get(msg::sender()).get(alice)); + }) + } + + #[test] + fn approve_errors_when_invalid_spender() { + test_utils::with_storage::(|token| { + // `msg::sender` approves `Address::ZERO`. + let one = U256::from(1); + let result = token.approve(Address::ZERO, one); + assert!(matches!(result, Err(Error::InvalidSpender(_)))); + }) + } } diff --git a/lib/wavm-shims/src/lib.rs b/lib/wavm-shims/src/lib.rs index b32e935e..d76bbb38 100644 --- a/lib/wavm-shims/src/lib.rs +++ b/lib/wavm-shims/src/lib.rs @@ -3,6 +3,9 @@ //! Most of the documentation is taken from the [Stylus source]. //! //! [Stylus source]: https://github.com/OffchainLabs/stylus/blob/484efac4f56fb70f96d4890748b8ec2543d88acd/arbitrator/wasm-libraries/user-host-trait/src/lib.rs +//! +//! We allow unsafe here because safety is guaranteed by the Stylus team. +#![allow(clippy::missing_safety_doc)] use std::slice; use storage::{read_bytes32, write_bytes32, STORAGE}; @@ -76,7 +79,7 @@ pub unsafe extern "C" fn storage_store_bytes32( STORAGE.lock().unwrap().insert(key, value); } -/// +/// Dummy msg sender set for tests. pub const MSG_SENDER: &[u8; 42] = b"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"; /// Gets the address of the account that called the program. For normal @@ -95,3 +98,19 @@ pub unsafe extern "C" fn msg_sender(sender: *mut u8) { let addr = const_hex::const_decode_to_array::<20>(MSG_SENDER).unwrap(); std::ptr::copy(addr.as_ptr(), sender, 20); } + +/// Emits an EVM log with the given number of topics and data, the first bytes +/// of which should be the 32-byte-aligned topic data. The semantics are +/// equivalent to that of the EVM's [`LOG0`], [`LOG1`], [`LOG2`], [`LOG3`], and +/// [`LOG4`] opcodes based on the number of topics specified. Requesting more +/// than `4` topics will induce a revert. +/// +/// [`LOG0`]: https://www.evm.codes/#a0 +/// [`LOG1`]: https://www.evm.codes/#a1 +/// [`LOG2`]: https://www.evm.codes/#a2 +/// [`LOG3`]: https://www.evm.codes/#a3 +/// [`LOG4`]: https://www.evm.codes/#a4 +#[no_mangle] +pub unsafe extern "C" fn emit_log(_: *const u8, _: usize, _: usize) { + // No-op: we don't check for events in our unit-tests. +}