From 98e0660891c7f774efed540e845c199b560e1028 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 17 Sep 2024 16:18:06 +0200 Subject: [PATCH 01/77] Add SafeErc20 + safe_transfer --- contracts/src/token/erc20/mod.rs | 1 + contracts/src/token/erc20/utils/mod.rs | 4 + contracts/src/token/erc20/utils/safe_erc20.rs | 111 ++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 contracts/src/token/erc20/utils/mod.rs create mode 100644 contracts/src/token/erc20/utils/safe_erc20.rs diff --git a/contracts/src/token/erc20/mod.rs b/contracts/src/token/erc20/mod.rs index 5f9629af..b3554b64 100644 --- a/contracts/src/token/erc20/mod.rs +++ b/contracts/src/token/erc20/mod.rs @@ -14,6 +14,7 @@ use stylus_sdk::{ }; pub mod extensions; +pub mod utils; sol! { /// Emitted when `value` tokens are moved from one account (`from`) to diff --git a/contracts/src/token/erc20/utils/mod.rs b/contracts/src/token/erc20/utils/mod.rs new file mode 100644 index 00000000..955a3f63 --- /dev/null +++ b/contracts/src/token/erc20/utils/mod.rs @@ -0,0 +1,4 @@ +//! Utilities for the ERC-20 standard. +pub mod safe_erc20; + +pub use safe_erc20::SafeErc20; diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs new file mode 100644 index 00000000..e96d9658 --- /dev/null +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -0,0 +1,111 @@ +//! Wrappers around ERC-20 operations that throw on failure. + +use crate::token::{erc20, erc20::Erc20}; +use alloc::vec::Vec; +use alloy_primitives::{Address, U256}; +use alloy_sol_types::{ + sol, + sol_data::{Address as SOLAddress, Uint}, + SolType, +}; +use stylus_proc::SolidityError; +use stylus_sdk::{ + call::{call, Call}, + contract::address, + function_selector, msg, + storage::TopLevelStorage, + types::AddressVM, +}; + +sol! { + /// An operation with an ERC-20 token failed. + #[derive(Debug)] + #[allow(missing_docs)] + error SafeErc20FailedOperation(address token); + + /// Indicates a failed `decreaseAllowance` request. + #[derive(Debug)] + #[allow(missing_docs)] + error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); +} + +/// A SafeErc20 error +#[derive(SolidityError, Debug)] +pub enum Error { + /// Error type from [`Erc20`] contract [`erc20::Error`]. + Erc20(erc20::Error), + /// An operation with an ERC-20 token failed. + SafeErc20FailedOperation(SafeErc20FailedOperation), + /// Indicates a failed `decreaseAllowance` request. + SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), +} + +/// Wrappers around ERC-20 operations that throw on failure (when the token +/// contract returns false). Tokens that return no value (and instead revert or +/// throw on failure) are also supported, non-reverting calls are assumed to be +/// successful. +/// To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, +/// which allows you to call the safe operations as `token.safeTransfer(...)`, etc. +pub trait SafeErc20 { + /// The error type associated to this Safe ERC-20 trait implementation. + type Error: Into>; + + /// Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + /// non-reverting calls are assumed to be successful. + fn safe_transfer( + &mut self, + to: Address, + value: U256, + ) -> Result<(), Self::Error>; +} + +impl SafeErc20 for Erc20 { + type Error = Error; + + fn safe_transfer(&mut self, to: Address, value: U256) -> Result<(), Error> { + type TransferType = (SOLAddress, Uint<256>); + let tx_data = (to, value); + let data = TransferType::abi_encode_params(&tx_data); + // Get function selector + let hashed_function_selector = function_selector!("transfer"); + // Combine function selector and input data (use abi_packed way) + let calldata = [&hashed_function_selector[..4], &data].concat(); + self.call_optional_return(calldata) + } +} + +/// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when +/// calling other contracts and not `&mut (impl TopLevelStorage + +/// BorrowMut)`. Should be fixed in the future by the Stylus team. +unsafe impl TopLevelStorage for Erc20 {} + +impl Erc20 { + /// Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + /// on the return value: the return value is optional (but if data is returned, it must not be false). + /// @param token The token targeted by the call. + /// @param data The call data (encoded using abi.encode or one of its variants). + /// + /// This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. + fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { + let result = call( + Call::new_in(self).value(msg::value()), + address(), + data.as_slice(), + ); + match result { + Ok(data) => { + if data.is_empty() && Address::has_code(&address()) { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token: address() }, + )); + } + } + Err(_) => { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token: address() }, + )) + } + } + Ok(()) + } +} From 025dc51a95f89b57638ef1ba3bd8a3837acba50d Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 17 Sep 2024 16:21:47 +0200 Subject: [PATCH 02/77] Add transfer param types when calc. selector --- contracts/src/token/erc20/utils/safe_erc20.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index e96d9658..ef1f4ca2 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -67,7 +67,8 @@ impl SafeErc20 for Erc20 { let tx_data = (to, value); let data = TransferType::abi_encode_params(&tx_data); // Get function selector - let hashed_function_selector = function_selector!("transfer"); + let hashed_function_selector = + function_selector!("transfer", Address, U256); // Combine function selector and input data (use abi_packed way) let calldata = [&hashed_function_selector[..4], &data].concat(); self.call_optional_return(calldata) From 228725116180d01ef99726508f23408b7367b719 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 18 Sep 2024 10:53:34 +0200 Subject: [PATCH 03/77] Add gas_left + fix has code check --- contracts/src/token/erc20/utils/safe_erc20.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index ef1f4ca2..8abfda72 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -12,6 +12,7 @@ use stylus_proc::SolidityError; use stylus_sdk::{ call::{call, Call}, contract::address, + evm::gas_left, function_selector, msg, storage::TopLevelStorage, types::AddressVM, @@ -89,13 +90,13 @@ impl Erc20 { /// This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { let result = call( - Call::new_in(self).value(msg::value()), + Call::new_in(self).value(msg::value()).gas(gas_left()), address(), data.as_slice(), ); match result { Ok(data) => { - if data.is_empty() && Address::has_code(&address()) { + if data.is_empty() && !Address::has_code(&address()) { return Err(Error::SafeErc20FailedOperation( SafeErc20FailedOperation { token: address() }, )); From eb779f1f346641e92a5f44d5ea710d8b87ce81b0 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 18 Sep 2024 11:18:30 +0200 Subject: [PATCH 04/77] Use RawCall with no value instead of Call --- contracts/src/token/erc20/utils/safe_erc20.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 8abfda72..96dd0283 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -10,7 +10,7 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::{call, Call}, + call::{call, Call, RawCall}, contract::address, evm::gas_left, function_selector, msg, @@ -89,12 +89,11 @@ impl Erc20 { /// /// This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { - let result = call( - Call::new_in(self).value(msg::value()).gas(gas_left()), - address(), - data.as_slice(), - ); - match result { + match RawCall::new() + .gas(gas_left()) + .limit_return_data(0, 32) + .call(address(), data.as_slice()) + { Ok(data) => { if data.is_empty() && !Address::has_code(&address()) { return Err(Error::SafeErc20FailedOperation( From 3182cc6d71422696b03c8b0859681885229ee10d Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 18 Sep 2024 11:19:08 +0200 Subject: [PATCH 05/77] Remove unused imports --- contracts/src/token/erc20/utils/safe_erc20.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 96dd0283..b9df4cc4 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -10,12 +10,8 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::{call, Call, RawCall}, - contract::address, - evm::gas_left, - function_selector, msg, - storage::TopLevelStorage, - types::AddressVM, + call::RawCall, contract::address, evm::gas_left, function_selector, + storage::TopLevelStorage, types::AddressVM, }; sol! { From a3a46737ad91377c9ce28c5a45c8fc7fb51592df Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 07:28:52 +0200 Subject: [PATCH 06/77] Add safe_transfer happy path test --- contracts/src/token/erc20/utils/safe_erc20.rs | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index b9df4cc4..12320815 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -1,7 +1,7 @@ //! Wrappers around ERC-20 operations that throw on failure. -use crate::token::{erc20, erc20::Erc20}; use alloc::vec::Vec; + use alloy_primitives::{Address, U256}; use alloy_sol_types::{ sol, @@ -10,10 +10,12 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::RawCall, contract::address, evm::gas_left, function_selector, + call::RawCall, contract::address, function_selector, storage::TopLevelStorage, types::AddressVM, }; +use crate::token::{erc20, erc20::Erc20}; + sol! { /// An operation with an ERC-20 token failed. #[derive(Debug)] @@ -41,14 +43,16 @@ pub enum Error { /// contract returns false). Tokens that return no value (and instead revert or /// throw on failure) are also supported, non-reverting calls are assumed to be /// successful. -/// To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, -/// which allows you to call the safe operations as `token.safeTransfer(...)`, etc. +/// To use this library you can add a `using SafeERC20 for IERC20;` statement to +/// your contract, which allows you to call the safe operations as +/// `token.safeTransfer(...)`, etc. pub trait SafeErc20 { /// The error type associated to this Safe ERC-20 trait implementation. type Error: Into>; - /// Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, - /// non-reverting calls are assumed to be successful. + /// Transfer `value` amount of `token` from the calling contract to `to`. If + /// `token` returns no value, non-reverting calls are assumed to be + /// successful. fn safe_transfer( &mut self, to: Address, @@ -78,15 +82,17 @@ impl SafeErc20 for Erc20 { unsafe impl TopLevelStorage for Erc20 {} impl Erc20 { - /// Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - /// on the return value: the return value is optional (but if data is returned, it must not be false). + /// Imitates a Solidity high-level call (i.e. a regular function call to a + /// contract), relaxing the requirement on the return value: the return + /// value is optional (but if data is returned, it must not be false). /// @param token The token targeted by the call. - /// @param data The call data (encoded using abi.encode or one of its variants). + /// @param data The call data (encoded using abi.encode or one of its + /// variants). /// - /// This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. + /// This is a variant of {_callOptionalReturnBool} that reverts if call + /// fails to meet the requirements. fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { match RawCall::new() - .gas(gas_left()) .limit_return_data(0, 32) .call(address(), data.as_slice()) { @@ -106,3 +112,42 @@ impl Erc20 { Ok(()) } } + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::{address, uint, Address}; + use stylus_sdk::msg; + + use super::SafeErc20; + use crate::token::erc20::{Erc20, IErc20}; + + #[motsu::test] + fn safe_transfer(contract: Erc20) { + let sender = msg::sender(); + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + let one = uint!(1_U256); + + // Initialize state for the test case: + // Msg sender's & Alice's balance as `one`. + contract + ._update(Address::ZERO, sender, one) + .expect("should mint tokens"); + contract + ._update(Address::ZERO, alice, one) + .expect("should mint tokens"); + + // Store initial balance & supply. + let initial_sender_balance = contract.balance_of(sender); + let initial_alice_balance = contract.balance_of(alice); + let initial_supply = contract.total_supply(); + + // Transfer action should work. + let result = contract.safe_transfer(alice, one); + assert!(result.is_ok()); + + // Check updated balance & supply. + assert_eq!(initial_sender_balance - one, contract.balance_of(alice)); + assert_eq!(initial_alice_balance + one, contract.balance_of(sender)); + assert_eq!(initial_supply, contract.total_supply()); + } +} From 05b235f8a575ff3371bb2e5ed47320968a439fc2 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 07:41:44 +0200 Subject: [PATCH 07/77] Fix test balance assertions --- contracts/src/token/erc20/utils/safe_erc20.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 12320815..8dd09b9d 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -146,8 +146,8 @@ mod tests { assert!(result.is_ok()); // Check updated balance & supply. - assert_eq!(initial_sender_balance - one, contract.balance_of(alice)); - assert_eq!(initial_alice_balance + one, contract.balance_of(sender)); + assert_eq!(initial_sender_balance - one, contract.balance_of(sender)); + assert_eq!(initial_alice_balance + one, contract.balance_of(alice)); assert_eq!(initial_supply, contract.total_supply()); } } From 9e449f4da888807c99e53082127cdec9980d2c75 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 09:01:00 +0200 Subject: [PATCH 08/77] Mock erc20 contract address in raw call --- contracts/src/token/erc20/utils/safe_erc20.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 8dd09b9d..20264c6f 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; -use alloy_primitives::{Address, U256}; +use alloy_primitives::{keccak256, Address, U256}; use alloy_sol_types::{ sol, sol_data::{Address as SOLAddress, Uint}, @@ -10,8 +10,8 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::RawCall, contract::address, function_selector, - storage::TopLevelStorage, types::AddressVM, + call::RawCall, contract::address, storage::TopLevelStorage, + types::AddressVM, }; use crate::token::{erc20, erc20::Erc20}; @@ -67,11 +67,10 @@ impl SafeErc20 for Erc20 { type TransferType = (SOLAddress, Uint<256>); let tx_data = (to, value); let data = TransferType::abi_encode_params(&tx_data); - // Get function selector - let hashed_function_selector = - function_selector!("transfer", Address, U256); + let hashed_function_selector = keccak256("transfer".as_bytes()); // Combine function selector and input data (use abi_packed way) let calldata = [&hashed_function_selector[..4], &data].concat(); + self.call_optional_return(calldata) } } @@ -94,7 +93,7 @@ impl Erc20 { fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { match RawCall::new() .limit_return_data(0, 32) - .call(address(), data.as_slice()) + .call(todo!("get address of token"), data.as_slice()) { Ok(data) => { if data.is_empty() && !Address::has_code(&address()) { From 025564a19bc273b40e7c108a96c2595aa79b0def Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 09:16:37 +0200 Subject: [PATCH 09/77] Use function_selector to get the appropriate value --- contracts/src/token/erc20/utils/safe_erc20.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 20264c6f..cfd089d5 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; -use alloy_primitives::{keccak256, Address, U256}; +use alloy_primitives::{Address, U256}; use alloy_sol_types::{ sol, sol_data::{Address as SOLAddress, Uint}, @@ -10,8 +10,8 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::RawCall, contract::address, storage::TopLevelStorage, - types::AddressVM, + call::RawCall, contract::address, function_selector, + storage::TopLevelStorage, types::AddressVM, }; use crate::token::{erc20, erc20::Erc20}; @@ -67,7 +67,8 @@ impl SafeErc20 for Erc20 { type TransferType = (SOLAddress, Uint<256>); let tx_data = (to, value); let data = TransferType::abi_encode_params(&tx_data); - let hashed_function_selector = keccak256("transfer".as_bytes()); + let hashed_function_selector = + function_selector!("transfer", Address, U256); // Combine function selector and input data (use abi_packed way) let calldata = [&hashed_function_selector[..4], &data].concat(); From 019ef51e889449a49312475ce8ce1e5784ee1ae9 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 10:18:47 +0200 Subject: [PATCH 10/77] Use Call instead of RawCall --- contracts/src/token/erc20/utils/safe_erc20.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index cfd089d5..bd4a73eb 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -10,8 +10,11 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::RawCall, contract::address, function_selector, - storage::TopLevelStorage, types::AddressVM, + call::{call, Call}, + contract::address, + function_selector, + storage::TopLevelStorage, + types::AddressVM, }; use crate::token::{erc20, erc20::Erc20}; @@ -92,10 +95,11 @@ impl Erc20 { /// This is a variant of {_callOptionalReturnBool} that reverts if call /// fails to meet the requirements. fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { - match RawCall::new() - .limit_return_data(0, 32) - .call(todo!("get address of token"), data.as_slice()) - { + match call( + Call::new_in(self), + todo!("get address of token"), + data.as_slice(), + ) { Ok(data) => { if data.is_empty() && !Address::has_code(&address()) { return Err(Error::SafeErc20FailedOperation( From 33ef80535f301b2cfa3dde74c97e547166afd504 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 10:20:15 +0200 Subject: [PATCH 11/77] Use Call:new instead of new_in --- contracts/src/token/erc20/utils/safe_erc20.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index bd4a73eb..3022e662 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -57,7 +57,7 @@ pub trait SafeErc20 { /// `token` returns no value, non-reverting calls are assumed to be /// successful. fn safe_transfer( - &mut self, + &self, to: Address, value: U256, ) -> Result<(), Self::Error>; @@ -66,7 +66,7 @@ pub trait SafeErc20 { impl SafeErc20 for Erc20 { type Error = Error; - fn safe_transfer(&mut self, to: Address, value: U256) -> Result<(), Error> { + fn safe_transfer(&self, to: Address, value: U256) -> Result<(), Error> { type TransferType = (SOLAddress, Uint<256>); let tx_data = (to, value); let data = TransferType::abi_encode_params(&tx_data); @@ -94,12 +94,9 @@ impl Erc20 { /// /// This is a variant of {_callOptionalReturnBool} that reverts if call /// fails to meet the requirements. - fn call_optional_return(&mut self, data: Vec) -> Result<(), Error> { - match call( - Call::new_in(self), - todo!("get address of token"), - data.as_slice(), - ) { + fn call_optional_return(&self, data: Vec) -> Result<(), Error> { + match call(Call::new(), todo!("get address of token"), data.as_slice()) + { Ok(data) => { if data.is_empty() && !Address::has_code(&address()) { return Err(Error::SafeErc20FailedOperation( From ef2b0c83042758e18ed00c15e57d161c1e2d2e80 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 10:25:06 +0200 Subject: [PATCH 12/77] Revert to RawCall --- contracts/src/token/erc20/utils/safe_erc20.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 3022e662..dc22b1c5 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -10,11 +10,8 @@ use alloy_sol_types::{ }; use stylus_proc::SolidityError; use stylus_sdk::{ - call::{call, Call}, - contract::address, - function_selector, - storage::TopLevelStorage, - types::AddressVM, + call::RawCall, contract::address, function_selector, + storage::TopLevelStorage, types::AddressVM, }; use crate::token::{erc20, erc20::Erc20}; @@ -95,7 +92,9 @@ impl Erc20 { /// This is a variant of {_callOptionalReturnBool} that reverts if call /// fails to meet the requirements. fn call_optional_return(&self, data: Vec) -> Result<(), Error> { - match call(Call::new(), todo!("get address of token"), data.as_slice()) + match RawCall::new() + .limit_return_data(0, 32) + .call(todo!("get address of token"), data.as_slice()) { Ok(data) => { if data.is_empty() && !Address::has_code(&address()) { From 8041c47c08eeb0057bc87a734a892c8d5426d41f Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 11:31:21 +0200 Subject: [PATCH 13/77] Add safe_transfer_from --- contracts/src/token/erc20/utils/safe_erc20.rs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index dc22b1c5..d4181d4d 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -58,6 +58,15 @@ pub trait SafeErc20 { to: Address, value: U256, ) -> Result<(), Self::Error>; + + /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + /// calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + fn safe_transfer_from( + &self, + from: Address, + to: Address, + value: U256, + ) -> Result<(), Self::Error>; } impl SafeErc20 for Erc20 { @@ -74,6 +83,23 @@ impl SafeErc20 for Erc20 { self.call_optional_return(calldata) } + + fn safe_transfer_from( + &self, + from: Address, + to: Address, + value: U256, + ) -> Result<(), Self::Error> { + type TransferType = (SOLAddress, SOLAddress, Uint<256>); + let tx_data = (from, to, value); + let data = TransferType::abi_encode_params(&tx_data); + let hashed_function_selector = + function_selector!("transferFrom", Address, Address, U256); + // Combine function selector and input data (use abi_packed way) + let calldata = [&hashed_function_selector[..4], &data].concat(); + + self.call_optional_return(calldata) + } } /// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when @@ -115,7 +141,7 @@ impl Erc20 { #[cfg(all(test, feature = "std"))] mod tests { - use alloy_primitives::{address, uint, Address}; + use alloy_primitives::{address, uint, Address, U256}; use stylus_sdk::msg; use super::SafeErc20; @@ -150,4 +176,27 @@ mod tests { assert_eq!(initial_alice_balance + one, contract.balance_of(alice)); assert_eq!(initial_supply, contract.total_supply()); } + + #[motsu::test] + fn transfers_from(contract: Erc20) { + let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + let bob = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); + let sender = msg::sender(); + + // Alice approves `msg::sender`. + let one = uint!(1_U256); + contract._allowances.setter(alice).setter(sender).set(one); + + // Mint some tokens for Alice. + let two = uint!(2_U256); + contract._update(Address::ZERO, alice, two).unwrap(); + assert_eq!(two, contract.balance_of(alice)); + + let result = contract.safe_transfer_from(alice, bob, one); + assert!(result.is_ok()); + + assert_eq!(one, contract.balance_of(alice)); + assert_eq!(one, contract.balance_of(bob)); + assert_eq!(U256::ZERO, contract.allowance(alice, sender)); + } } From e4ba4e583824c0d0b4e78791e8349e689cb14436 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 11:49:42 +0200 Subject: [PATCH 14/77] Format --- contracts/src/token/erc20/utils/safe_erc20.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index d4181d4d..aa997f6b 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -59,8 +59,9 @@ pub trait SafeErc20 { value: U256, ) -> Result<(), Self::Error>; - /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the - /// calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + /// Transfer `value` amount of `token` from `from` to `to`, spending the + /// approval given by `from` to the calling contract. If `token` returns + /// no value, non-reverting calls are assumed to be successful. fn safe_transfer_from( &self, from: Address, From 1a477096457cbefcd03723ac33d3ac4ec5e50315 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 16:39:25 +0200 Subject: [PATCH 15/77] Update logic (removes safe_transfer_from) --- contracts/src/token/erc20/utils/safe_erc20.rs | 186 ++++-------------- 1 file changed, 37 insertions(+), 149 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index aa997f6b..b486dbed 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -1,20 +1,13 @@ //! Wrappers around ERC-20 operations that throw on failure. -use alloc::vec::Vec; - use alloy_primitives::{Address, U256}; -use alloy_sol_types::{ - sol, - sol_data::{Address as SOLAddress, Uint}, - SolType, -}; -use stylus_proc::SolidityError; +use alloy_sol_types::sol; +use stylus_proc::{sol_interface, sol_storage, SolidityError}; use stylus_sdk::{ - call::RawCall, contract::address, function_selector, - storage::TopLevelStorage, types::AddressVM, + call::Call, contract::address, storage::TopLevelStorage, types::AddressVM, }; -use crate::token::{erc20, erc20::Erc20}; +use crate::token::erc20; sol! { /// An operation with an ERC-20 token failed. @@ -39,90 +32,46 @@ pub enum Error { SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } -/// Wrappers around ERC-20 operations that throw on failure (when the token -/// contract returns false). Tokens that return no value (and instead revert or -/// throw on failure) are also supported, non-reverting calls are assumed to be -/// successful. -/// To use this library you can add a `using SafeERC20 for IERC20;` statement to -/// your contract, which allows you to call the safe operations as -/// `token.safeTransfer(...)`, etc. -pub trait SafeErc20 { - /// The error type associated to this Safe ERC-20 trait implementation. - type Error: Into>; - - /// Transfer `value` amount of `token` from the calling contract to `to`. If - /// `token` returns no value, non-reverting calls are assumed to be - /// successful. - fn safe_transfer( - &self, - to: Address, - value: U256, - ) -> Result<(), Self::Error>; - - /// Transfer `value` amount of `token` from `from` to `to`, spending the - /// approval given by `from` to the calling contract. If `token` returns - /// no value, non-reverting calls are assumed to be successful. - fn safe_transfer_from( - &self, - from: Address, - to: Address, - value: U256, - ) -> Result<(), Self::Error>; -} - -impl SafeErc20 for Erc20 { - type Error = Error; - - fn safe_transfer(&self, to: Address, value: U256) -> Result<(), Error> { - type TransferType = (SOLAddress, Uint<256>); - let tx_data = (to, value); - let data = TransferType::abi_encode_params(&tx_data); - let hashed_function_selector = - function_selector!("transfer", Address, U256); - // Combine function selector and input data (use abi_packed way) - let calldata = [&hashed_function_selector[..4], &data].concat(); - - self.call_optional_return(calldata) +sol_interface! { + /// Interface of the ERC-20 standard as defined in the ERC. + interface IERC20 { + /// Moves a `value` amount of tokens from the caller's account to `to`. + /// Returns a boolean value indicating whether the operation succeeded. + /// Emits a {Transfer} event. + function transfer(address to, uint256 value) external returns (bytes4); } +} - fn safe_transfer_from( - &self, - from: Address, - to: Address, - value: U256, - ) -> Result<(), Self::Error> { - type TransferType = (SOLAddress, SOLAddress, Uint<256>); - let tx_data = (from, to, value); - let data = TransferType::abi_encode_params(&tx_data); - let hashed_function_selector = - function_selector!("transferFrom", Address, Address, U256); - // Combine function selector and input data (use abi_packed way) - let calldata = [&hashed_function_selector[..4], &data].concat(); - - self.call_optional_return(calldata) - } +sol_storage! { + /// Wrappers around ERC-20 operations that throw on failure (when the token + /// contract returns false). Tokens that return no value (and instead revert or + /// throw on failure) are also supported, non-reverting calls are assumed to be + /// successful. + /// To use this library you can add a `using SafeERC20 for IERC20;` statement to + /// your contract, which allows you to call the safe operations as + /// `token.safeTransfer(...)`, etc. + pub struct SafeErc20 {} } /// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when /// calling other contracts and not `&mut (impl TopLevelStorage + /// BorrowMut)`. Should be fixed in the future by the Stylus team. -unsafe impl TopLevelStorage for Erc20 {} +unsafe impl TopLevelStorage for SafeErc20 {} -impl Erc20 { - /// Imitates a Solidity high-level call (i.e. a regular function call to a - /// contract), relaxing the requirement on the return value: the return - /// value is optional (but if data is returned, it must not be false). - /// @param token The token targeted by the call. - /// @param data The call data (encoded using abi.encode or one of its - /// variants). - /// - /// This is a variant of {_callOptionalReturnBool} that reverts if call - /// fails to meet the requirements. - fn call_optional_return(&self, data: Vec) -> Result<(), Error> { - match RawCall::new() - .limit_return_data(0, 32) - .call(todo!("get address of token"), data.as_slice()) - { +impl SafeErc20 { + /// Transfer `value` amount of `token` from the calling contract to `to`. If + /// `token` returns no value, non-reverting calls are assumed to be + /// successful. + pub fn safe_transfer( + &mut self, + token: Address, + to: Address, + value: U256, + ) -> Result<(), Error> { + let erc20 = IERC20::new(token); + let call = Call::new_in(self); + + match erc20.transfer(call, to, value) { Ok(data) => { if data.is_empty() && !Address::has_code(&address()) { return Err(Error::SafeErc20FailedOperation( @@ -136,68 +85,7 @@ impl Erc20 { )) } } - Ok(()) - } -} - -#[cfg(all(test, feature = "std"))] -mod tests { - use alloy_primitives::{address, uint, Address, U256}; - use stylus_sdk::msg; - - use super::SafeErc20; - use crate::token::erc20::{Erc20, IErc20}; - - #[motsu::test] - fn safe_transfer(contract: Erc20) { - let sender = msg::sender(); - let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); - let one = uint!(1_U256); - // Initialize state for the test case: - // Msg sender's & Alice's balance as `one`. - contract - ._update(Address::ZERO, sender, one) - .expect("should mint tokens"); - contract - ._update(Address::ZERO, alice, one) - .expect("should mint tokens"); - - // Store initial balance & supply. - let initial_sender_balance = contract.balance_of(sender); - let initial_alice_balance = contract.balance_of(alice); - let initial_supply = contract.total_supply(); - - // Transfer action should work. - let result = contract.safe_transfer(alice, one); - assert!(result.is_ok()); - - // Check updated balance & supply. - assert_eq!(initial_sender_balance - one, contract.balance_of(sender)); - assert_eq!(initial_alice_balance + one, contract.balance_of(alice)); - assert_eq!(initial_supply, contract.total_supply()); - } - - #[motsu::test] - fn transfers_from(contract: Erc20) { - let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); - let bob = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); - let sender = msg::sender(); - - // Alice approves `msg::sender`. - let one = uint!(1_U256); - contract._allowances.setter(alice).setter(sender).set(one); - - // Mint some tokens for Alice. - let two = uint!(2_U256); - contract._update(Address::ZERO, alice, two).unwrap(); - assert_eq!(two, contract.balance_of(alice)); - - let result = contract.safe_transfer_from(alice, bob, one); - assert!(result.is_ok()); - - assert_eq!(one, contract.balance_of(alice)); - assert_eq!(one, contract.balance_of(bob)); - assert_eq!(U256::ZERO, contract.allowance(alice, sender)); + Ok(()) } } From e978a6b376b6d2ed7e545f1720d7b92f0bdb43f1 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 20:31:25 +0200 Subject: [PATCH 16/77] Initialize tests --- Cargo.lock | 15 ++++ Cargo.toml | 2 + contracts/src/token/erc20/utils/safe_erc20.rs | 3 +- examples/safe-erc20/Cargo.toml | 26 +++++++ examples/safe-erc20/src/ERC20Mock.sol | 21 +++++ examples/safe-erc20/src/constructor.sol | 5 ++ examples/safe-erc20/src/lib.rs | 29 +++++++ examples/safe-erc20/tests/abi/mod.rs | 12 +++ examples/safe-erc20/tests/mock/erc20.rs | 31 ++++++++ examples/safe-erc20/tests/mock/mod.rs | 1 + examples/safe-erc20/tests/safe-erc20.rs | 77 +++++++++++++++++++ scripts/e2e-tests.sh | 2 +- 12 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 examples/safe-erc20/Cargo.toml create mode 100644 examples/safe-erc20/src/ERC20Mock.sol create mode 100644 examples/safe-erc20/src/constructor.sol create mode 100644 examples/safe-erc20/src/lib.rs create mode 100644 examples/safe-erc20/tests/abi/mod.rs create mode 100644 examples/safe-erc20/tests/mock/erc20.rs create mode 100644 examples/safe-erc20/tests/mock/mod.rs create mode 100644 examples/safe-erc20/tests/safe-erc20.rs diff --git a/Cargo.lock b/Cargo.lock index ebb25bb5..a4002680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3212,6 +3212,21 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safe-erc20-example" +version = "0.0.0" +dependencies = [ + "alloy", + "alloy-primitives", + "e2e", + "eyre", + "mini-alloc", + "openzeppelin-stylus", + "stylus-proc", + "stylus-sdk", + "tokio", +] + [[package]] name = "salsa20" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index f196fd18..31ed30de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "examples/basic/token", "examples/basic/script", "examples/ecdsa", + "examples/safe-erc20", "benches", ] default-members = [ @@ -30,6 +31,7 @@ default-members = [ "examples/erc721", "examples/erc721-consecutive", "examples/erc721-metadata", + "examples/safe-erc20", "examples/merkle-proofs", "examples/ownable", "examples/access-control", diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index b486dbed..4bd06ee0 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -2,7 +2,7 @@ use alloy_primitives::{Address, U256}; use alloy_sol_types::sol; -use stylus_proc::{sol_interface, sol_storage, SolidityError}; +use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; use stylus_sdk::{ call::Call, contract::address, storage::TopLevelStorage, types::AddressVM, }; @@ -58,6 +58,7 @@ sol_storage! { /// BorrowMut)`. Should be fixed in the future by the Stylus team. unsafe impl TopLevelStorage for SafeErc20 {} +#[public] impl SafeErc20 { /// Transfer `value` amount of `token` from the calling contract to `to`. If /// `token` returns no value, non-reverting calls are assumed to be diff --git a/examples/safe-erc20/Cargo.toml b/examples/safe-erc20/Cargo.toml new file mode 100644 index 00000000..8a971ebd --- /dev/null +++ b/examples/safe-erc20/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "safe-erc20-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version = "0.0.0" + +[dependencies] +openzeppelin-stylus = { path = "../../contracts" } +alloy-primitives = { workspace = true, features = ["tiny-keccak"] } +stylus-sdk.workspace = true +stylus-proc.workspace = true +mini-alloc.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e = { path = "../../lib/e2e" } + +[features] +e2e = [] + +[lib] +crate-type = ["lib", "cdylib"] diff --git a/examples/safe-erc20/src/ERC20Mock.sol b/examples/safe-erc20/src/ERC20Mock.sol new file mode 100644 index 00000000..5f7499a3 --- /dev/null +++ b/examples/safe-erc20/src/ERC20Mock.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("MyToken", "MTK") {} + + function balanceOf(address account) public override view returns (uint256) { + return super.balanceOf(account); + } + + function mint(address account, uint256 amount) public { + super._mint(account, amount); + } + + function transfer(address to, uint256 amount) public override returns (bool) { + return super.transfer(to, amount); + } +} \ No newline at end of file diff --git a/examples/safe-erc20/src/constructor.sol b/examples/safe-erc20/src/constructor.sol new file mode 100644 index 00000000..7de339b9 --- /dev/null +++ b/examples/safe-erc20/src/constructor.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract SafeErc20Example { +} diff --git a/examples/safe-erc20/src/lib.rs b/examples/safe-erc20/src/lib.rs new file mode 100644 index 00000000..658cba63 --- /dev/null +++ b/examples/safe-erc20/src/lib.rs @@ -0,0 +1,29 @@ +#![cfg_attr(not(test), no_main, no_std)] +extern crate alloc; + +use alloy_primitives::{Address, U256}; +use openzeppelin_stylus::token::erc20::utils::safe_erc20::{Error, SafeErc20}; +use stylus_sdk::prelude::{entrypoint, public, sol_storage}; + +sol_storage! { + #[entrypoint] + struct SafeErc20Example { + #[borrow] + SafeErc20 safe_erc20; + } +} + +#[public] +#[inherit(SafeErc20)] +impl SafeErc20Example { + // Add token minting feature. + pub fn safe_transfer_token( + &mut self, + token: Address, + to: Address, + value: U256, + ) -> Result<(), Error> { + self.safe_erc20.safe_transfer(token, to, value)?; + Ok(()) + } +} diff --git a/examples/safe-erc20/tests/abi/mod.rs b/examples/safe-erc20/tests/abi/mod.rs new file mode 100644 index 00000000..1b7482ec --- /dev/null +++ b/examples/safe-erc20/tests/abi/mod.rs @@ -0,0 +1,12 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract SafeErc20Example { + function safeTransfer(address token, address to, uint256 value) external; + + error SafeErc20FailedOperation(address token); + error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + } +); diff --git a/examples/safe-erc20/tests/mock/erc20.rs b/examples/safe-erc20/tests/mock/erc20.rs new file mode 100644 index 00000000..f51f3880 --- /dev/null +++ b/examples/safe-erc20/tests/mock/erc20.rs @@ -0,0 +1,31 @@ +#![allow(dead_code)] +#![cfg(feature = "e2e")] +use alloy::{primitives::Address, sol}; +use e2e::Wallet; + +sol! { + #[allow(missing_docs)] + // Built with Remix IDE; solc v0.8.21+commit.d9974bed + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600781526020017f4d79546f6b656e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610ed380620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b4c565b60405180910390f35b6100d860048036038101906100d39190610bfd565b6102b4565b6040516100e59190610c55565b60405180910390f35b6100f66102d6565b6040516101039190610c7d565b60405180910390f35b61012660048036038101906101219190610c96565b6102df565b6040516101339190610c55565b60405180910390f35b61014461030d565b6040516101519190610d01565b60405180910390f35b610174600480360381019061016f9190610bfd565b610315565b005b610190600480360381019061018b9190610d1a565b610323565b60405161019d9190610c7d565b60405180910390f35b6101ae610334565b6040516101bb9190610b4c565b60405180910390f35b6101de60048036038101906101d99190610bfd565b6103c4565b6040516101eb9190610c55565b60405180910390f35b61020e60048036038101906102099190610d45565b6103d7565b60405161021b9190610c7d565b60405180910390f35b60606003805461023390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610db0565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f806102be610459565b90506102cb818585610460565b600191505092915050565b5f600254905090565b5f806102e9610459565b90506102f6858285610472565b610301858585610504565b60019150509392505050565b5f6012905090565b61031f82826105f4565b5050565b5f61032d82610673565b9050919050565b60606004805461034390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461036f90610db0565b80156103ba5780601f10610391576101008083540402835291602001916103ba565b820191905f5260205f20905b81548152906001019060200180831161039d57829003601f168201915b5050505050905090565b5f6103cf83836106b8565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b61046d83838360016106da565b505050565b5f61047d84846103d7565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146104fe57818110156104ef578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016104e693929190610def565b60405180910390fd5b6104fd84848484035f6106da565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610574575f6040517f96c6fd1e00000000000000000000000000000000000000000000000000000000815260040161056b9190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036105e4575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016105db9190610e24565b60405180910390fd5b6105ef8383836108a9565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610664575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161065b9190610e24565b60405180910390fd5b61066f5f83836108a9565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f806106c2610459565b90506106cf818585610504565b600191505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361074a575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016107419190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036107ba575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016107b19190610e24565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080156108a3578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161089a9190610c7d565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108f9578060025f8282546108ed9190610e6a565b925050819055506109c7565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610982578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161097993929190610def565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0e578060025f8282540392505081905550610a58565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610ab59190610c7d565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610af9578082015181840152602081019050610ade565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b1e82610ac2565b610b288185610acc565b9350610b38818560208601610adc565b610b4181610b04565b840191505092915050565b5f6020820190508181035f830152610b648184610b14565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b9982610b70565b9050919050565b610ba981610b8f565b8114610bb3575f80fd5b50565b5f81359050610bc481610ba0565b92915050565b5f819050919050565b610bdc81610bca565b8114610be6575f80fd5b50565b5f81359050610bf781610bd3565b92915050565b5f8060408385031215610c1357610c12610b6c565b5b5f610c2085828601610bb6565b9250506020610c3185828601610be9565b9150509250929050565b5f8115159050919050565b610c4f81610c3b565b82525050565b5f602082019050610c685f830184610c46565b92915050565b610c7781610bca565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b5f805f60608486031215610cad57610cac610b6c565b5b5f610cba86828701610bb6565b9350506020610ccb86828701610bb6565b9250506040610cdc86828701610be9565b9150509250925092565b5f60ff82169050919050565b610cfb81610ce6565b82525050565b5f602082019050610d145f830184610cf2565b92915050565b5f60208284031215610d2f57610d2e610b6c565b5b5f610d3c84828501610bb6565b91505092915050565b5f8060408385031215610d5b57610d5a610b6c565b5b5f610d6885828601610bb6565b9250506020610d7985828601610bb6565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610dc757607f821691505b602082108103610dda57610dd9610d83565b5b50919050565b610de981610b8f565b82525050565b5f606082019050610e025f830186610de0565b610e0f6020830185610c6e565b610e1c6040830184610c6e565b949350505050565b5f602082019050610e375f830184610de0565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e7482610bca565b9150610e7f83610bca565b9250828201905080821115610e9757610e96610e3d565b5b9291505056fea2646970667358221220e360083fadcd14e331a763a568beb9c124a547faa854fc1d40cc4e530621cbb964736f6c63430008150033")] + contract ERC20Mock is ERC20 { + constructor() ERC20("MyToken", "MTK") {} + + function balanceOf(address account) public override view returns (uint256) { + return super.balanceOf(account); + } + + function mint(address account, uint256 amount) public { + super._mint(account, amount); + } + + function transfer(address to, uint256 amount) public override returns (bool) { + return super.transfer(to, amount); + } + } +} + +pub async fn deploy(wallet: &Wallet) -> eyre::Result
{ + // Deploy the contract. + let contract = ERC20Mock::deploy(wallet).await?; + Ok(*contract.address()) +} diff --git a/examples/safe-erc20/tests/mock/mod.rs b/examples/safe-erc20/tests/mock/mod.rs new file mode 100644 index 00000000..8f3777f6 --- /dev/null +++ b/examples/safe-erc20/tests/mock/mod.rs @@ -0,0 +1 @@ +pub mod erc20; diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs new file mode 100644 index 00000000..b58fcefb --- /dev/null +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -0,0 +1,77 @@ +#![cfg(feature = "e2e")] + +use alloy::primitives::uint; +use e2e::{watch, Account, ReceiptExt}; + +use abi::SafeErc20Example; +use mock::{erc20, erc20::ERC20Mock}; + +mod abi; +mod mock; + +#[e2e::test] +async fn constructs(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = SafeErc20Example::new(contract_addr, &alice.wallet); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20 = ERC20Mock::new(erc20_address, &alice.wallet); + let one = uint!(1_U256); + let _ = watch!(erc20.mint(alice.address(), one)); + let balance = erc20.balanceOf(alice.address()).call().await?._0; + assert_eq!(balance, one); + + Ok(()) +} + +// #[e2e::test] +// async fn transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { +// let receivers = vec![alice.address(), bob.address()]; +// let amounts = vec![1000_u128, 1000_u128]; +// // Deploy and mint batches of 1000 tokens to Alice and Bob. +// let receipt = alice +// .as_deployer() +// .with_constructor(ctr(receivers, amounts)) +// .deploy() +// .await?; +// let contract = Erc721::new(receipt.address()?, &alice.wallet); + +// let first_consecutive_token_id = U256::from(FIRST_CONSECUTIVE_ID); + +// // Transfer first consecutive token from Alice to Bob. +// let _ = watch!(contract.transferFrom( +// alice.address(), +// bob.address(), +// first_consecutive_token_id +// ))?; + +// let Erc721::ownerOfReturn { ownerOf } = +// contract.ownerOf(first_consecutive_token_id).call().await?; +// assert_eq!(ownerOf, bob.address()); + +// // Check that balances changed. +// let Erc721::balanceOfReturn { balance: alice_balance } = +// contract.balanceOf(alice.address()).call().await?; +// assert_eq!(alice_balance, uint!(1000_U256) - uint!(1_U256)); +// let Erc721::balanceOfReturn { balance: bob_balance } = +// contract.balanceOf(bob.address()).call().await?; +// assert_eq!(bob_balance, uint!(1000_U256) + uint!(1_U256)); + +// // Test non-consecutive mint. +// let token_id = random_token_id(); +// let _ = watch!(contract.mint(alice.address(), token_id))?; +// let Erc721::balanceOfReturn { balance: alice_balance } = +// contract.balanceOf(alice.address()).call().await?; +// assert_eq!(alice_balance, uint!(1000_U256)); + +// // Test transfer of the token that wasn't minted consecutive. +// let _ = watch!(contract.transferFrom( +// alice.address(), +// bob.address(), +// token_id +// ))?; +// let Erc721::balanceOfReturn { balance: alice_balance } = +// contract.balanceOf(alice.address()).call().await?; +// assert_eq!(alice_balance, uint!(1000_U256) - uint!(1_U256)); +// Ok(()) +// } diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index f7b9cdeb..56d3644d 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" +cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example From 6e10c60a5c4dc49e531662e293b7866a6908b68e Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 21:14:56 +0200 Subject: [PATCH 17/77] Fix token address in SafeErc20FailedOperation error --- contracts/src/token/erc20/utils/safe_erc20.rs | 6 +- examples/safe-erc20/tests/abi/mod.rs | 2 +- examples/safe-erc20/tests/safe-erc20.rs | 90 +++++++------------ scripts/e2e-tests.sh | 2 +- 4 files changed, 37 insertions(+), 63 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 4bd06ee0..34cc0030 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -74,15 +74,15 @@ impl SafeErc20 { match erc20.transfer(call, to, value) { Ok(data) => { - if data.is_empty() && !Address::has_code(&address()) { + if data.is_empty() && !Address::has_code(&token) { return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token: address() }, + SafeErc20FailedOperation { token }, )); } } Err(_) => { return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token: address() }, + SafeErc20FailedOperation { token }, )) } } diff --git a/examples/safe-erc20/tests/abi/mod.rs b/examples/safe-erc20/tests/abi/mod.rs index 1b7482ec..2a73483c 100644 --- a/examples/safe-erc20/tests/abi/mod.rs +++ b/examples/safe-erc20/tests/abi/mod.rs @@ -3,7 +3,7 @@ use alloy::sol; sol!( #[sol(rpc)] - contract SafeErc20Example { + contract SafeErc20 { function safeTransfer(address token, address to, uint256 value) external; error SafeErc20FailedOperation(address token); diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index b58fcefb..5b1a1e6b 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -1,77 +1,51 @@ #![cfg(feature = "e2e")] use alloy::primitives::uint; -use e2e::{watch, Account, ReceiptExt}; +use alloy_primitives::U256; +use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; -use abi::SafeErc20Example; +use abi::SafeErc20; use mock::{erc20, erc20::ERC20Mock}; mod abi; mod mock; #[e2e::test] -async fn constructs(alice: Account) -> eyre::Result<()> { +async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = SafeErc20Example::new(contract_addr, &alice.wallet); + let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); - let erc20_address = erc20::deploy(&alice.wallet).await?; - let erc20 = ERC20Mock::new(erc20_address, &alice.wallet); - let one = uint!(1_U256); - let _ = watch!(erc20.mint(alice.address(), one)); - let balance = erc20.balanceOf(alice.address()).call().await?._0; - assert_eq!(balance, one); - - Ok(()) -} + let balance = uint!(10_U256); + let value = uint!(1_U256); -// #[e2e::test] -// async fn transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { -// let receivers = vec![alice.address(), bob.address()]; -// let amounts = vec![1000_u128, 1000_u128]; -// // Deploy and mint batches of 1000 tokens to Alice and Bob. -// let receipt = alice -// .as_deployer() -// .with_constructor(ctr(receivers, amounts)) -// .deploy() -// .await?; -// let contract = Erc721::new(receipt.address()?, &alice.wallet); + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); -// let first_consecutive_token_id = U256::from(FIRST_CONSECUTIVE_ID); + let _ = watch!(erc20_alice.mint(alice_addr, balance)); -// // Transfer first consecutive token from Alice to Bob. -// let _ = watch!(contract.transferFrom( -// alice.address(), -// bob.address(), -// first_consecutive_token_id -// ))?; + let ERC20Mock::balanceOfReturn { _0: initial_alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + assert_eq!(initial_alice_balance, balance); + assert_eq!(initial_bob_balance, U256::ZERO); -// let Erc721::ownerOfReturn { ownerOf } = -// contract.ownerOf(first_consecutive_token_id).call().await?; -// assert_eq!(ownerOf, bob.address()); + let err = + send!(contract_alice.safeTransfer(erc20_address, bob_addr, value)) + .expect_err("should err I guess"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address, + })); -// // Check that balances changed. -// let Erc721::balanceOfReturn { balance: alice_balance } = -// contract.balanceOf(alice.address()).call().await?; -// assert_eq!(alice_balance, uint!(1000_U256) - uint!(1_U256)); -// let Erc721::balanceOfReturn { balance: bob_balance } = -// contract.balanceOf(bob.address()).call().await?; -// assert_eq!(bob_balance, uint!(1000_U256) + uint!(1_U256)); + let ERC20Mock::balanceOfReturn { _0: alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; -// // Test non-consecutive mint. -// let token_id = random_token_id(); -// let _ = watch!(contract.mint(alice.address(), token_id))?; -// let Erc721::balanceOfReturn { balance: alice_balance } = -// contract.balanceOf(alice.address()).call().await?; -// assert_eq!(alice_balance, uint!(1000_U256)); + assert_eq!(initial_alice_balance - value, alice_balance); + assert_eq!(initial_bob_balance + value, bob_balance); -// // Test transfer of the token that wasn't minted consecutive. -// let _ = watch!(contract.transferFrom( -// alice.address(), -// bob.address(), -// token_id -// ))?; -// let Erc721::balanceOfReturn { balance: alice_balance } = -// contract.balanceOf(alice.address()).call().await?; -// assert_eq!(alice_balance, uint!(1000_U256) - uint!(1_U256)); -// Ok(()) -// } + Ok(()) +} diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 56d3644d..dd5044aa 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example +cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example --test safe-erc20 From 970e3cff6d81f827855462e25f137f5200b80f3a Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 21:31:39 +0200 Subject: [PATCH 18/77] Use receipt instead of send --- examples/safe-erc20/tests/safe-erc20.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index 5b1a1e6b..77bb60a7 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -2,7 +2,7 @@ use alloy::primitives::uint; use alloy_primitives::U256; -use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; +use e2e::{receipt, watch, Account, ReceiptExt}; use abi::SafeErc20; use mock::{erc20, erc20::ERC20Mock}; @@ -32,12 +32,8 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { assert_eq!(initial_alice_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let err = - send!(contract_alice.safeTransfer(erc20_address, bob_addr, value)) - .expect_err("should err I guess"); - assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: erc20_address, - })); + let _ = + receipt!(contract_alice.safeTransfer(erc20_address, bob_addr, value))?; let ERC20Mock::balanceOfReturn { _0: alice_balance } = erc20_alice.balanceOf(alice_addr).call().await?; From 22092daed7398cd044b5bdc7c083e5d79f330454 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 21:34:21 +0200 Subject: [PATCH 19/77] SafeErc20Example.safe_transfer_token->safe_transfer --- contracts/src/token/erc20/utils/safe_erc20.rs | 4 +--- examples/safe-erc20/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 34cc0030..e5b48616 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -3,9 +3,7 @@ use alloy_primitives::{Address, U256}; use alloy_sol_types::sol; use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; -use stylus_sdk::{ - call::Call, contract::address, storage::TopLevelStorage, types::AddressVM, -}; +use stylus_sdk::{call::Call, storage::TopLevelStorage, types::AddressVM}; use crate::token::erc20; diff --git a/examples/safe-erc20/src/lib.rs b/examples/safe-erc20/src/lib.rs index 658cba63..d2a66e77 100644 --- a/examples/safe-erc20/src/lib.rs +++ b/examples/safe-erc20/src/lib.rs @@ -17,7 +17,7 @@ sol_storage! { #[inherit(SafeErc20)] impl SafeErc20Example { // Add token minting feature. - pub fn safe_transfer_token( + pub fn safe_transfer( &mut self, token: Address, to: Address, From 1ed10b1b2db850798ba52d26468968fbc92af8b3 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 19 Sep 2024 22:16:03 +0200 Subject: [PATCH 20/77] Simplify ERC20Mock --- contracts/src/token/erc20/utils/safe_erc20.rs | 4 ++-- examples/safe-erc20/src/ERC20Mock.sol | 18 +++++++++--------- examples/safe-erc20/tests/mock/erc20.rs | 18 ++++++++++-------- examples/safe-erc20/tests/safe-erc20.rs | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index e5b48616..b242c0d0 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -36,7 +36,7 @@ sol_interface! { /// Moves a `value` amount of tokens from the caller's account to `to`. /// Returns a boolean value indicating whether the operation succeeded. /// Emits a {Transfer} event. - function transfer(address to, uint256 value) external returns (bytes4); + function transfer(address to, uint256 amount) external returns (bool); } } @@ -72,7 +72,7 @@ impl SafeErc20 { match erc20.transfer(call, to, value) { Ok(data) => { - if data.is_empty() && !Address::has_code(&token) { + if data && !Address::has_code(&token) { return Err(Error::SafeErc20FailedOperation( SafeErc20FailedOperation { token }, )); diff --git a/examples/safe-erc20/src/ERC20Mock.sol b/examples/safe-erc20/src/ERC20Mock.sol index 5f7499a3..96370d15 100644 --- a/examples/safe-erc20/src/ERC20Mock.sol +++ b/examples/safe-erc20/src/ERC20Mock.sol @@ -2,20 +2,20 @@ pragma solidity ^0.8.21; -import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC20/ERC20.sol"; +contract ERC20Mock { + mapping(address => uint256) private _balances; -contract ERC20Mock is ERC20 { - constructor() ERC20("MyToken", "MTK") {} - - function balanceOf(address account) public override view returns (uint256) { - return super.balanceOf(account); + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; } function mint(address account, uint256 amount) public { - super._mint(account, amount); + _balances[account] += amount; } - function transfer(address to, uint256 amount) public override returns (bool) { - return super.transfer(to, amount); + function transfer(address to, uint256 amount) public returns (bool) { + _balances[msg.sender] -= amount; + _balances[to] += amount; + return true; } } \ No newline at end of file diff --git a/examples/safe-erc20/tests/mock/erc20.rs b/examples/safe-erc20/tests/mock/erc20.rs index f51f3880..3a13124f 100644 --- a/examples/safe-erc20/tests/mock/erc20.rs +++ b/examples/safe-erc20/tests/mock/erc20.rs @@ -6,20 +6,22 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600781526020017f4d79546f6b656e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610ed380620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b4c565b60405180910390f35b6100d860048036038101906100d39190610bfd565b6102b4565b6040516100e59190610c55565b60405180910390f35b6100f66102d6565b6040516101039190610c7d565b60405180910390f35b61012660048036038101906101219190610c96565b6102df565b6040516101339190610c55565b60405180910390f35b61014461030d565b6040516101519190610d01565b60405180910390f35b610174600480360381019061016f9190610bfd565b610315565b005b610190600480360381019061018b9190610d1a565b610323565b60405161019d9190610c7d565b60405180910390f35b6101ae610334565b6040516101bb9190610b4c565b60405180910390f35b6101de60048036038101906101d99190610bfd565b6103c4565b6040516101eb9190610c55565b60405180910390f35b61020e60048036038101906102099190610d45565b6103d7565b60405161021b9190610c7d565b60405180910390f35b60606003805461023390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610db0565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f806102be610459565b90506102cb818585610460565b600191505092915050565b5f600254905090565b5f806102e9610459565b90506102f6858285610472565b610301858585610504565b60019150509392505050565b5f6012905090565b61031f82826105f4565b5050565b5f61032d82610673565b9050919050565b60606004805461034390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461036f90610db0565b80156103ba5780601f10610391576101008083540402835291602001916103ba565b820191905f5260205f20905b81548152906001019060200180831161039d57829003601f168201915b5050505050905090565b5f6103cf83836106b8565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b61046d83838360016106da565b505050565b5f61047d84846103d7565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146104fe57818110156104ef578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016104e693929190610def565b60405180910390fd5b6104fd84848484035f6106da565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610574575f6040517f96c6fd1e00000000000000000000000000000000000000000000000000000000815260040161056b9190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036105e4575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016105db9190610e24565b60405180910390fd5b6105ef8383836108a9565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610664575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161065b9190610e24565b60405180910390fd5b61066f5f83836108a9565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f806106c2610459565b90506106cf818585610504565b600191505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361074a575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016107419190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036107ba575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016107b19190610e24565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080156108a3578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161089a9190610c7d565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108f9578060025f8282546108ed9190610e6a565b925050819055506109c7565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610982578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161097993929190610def565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0e578060025f8282540392505081905550610a58565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610ab59190610c7d565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610af9578082015181840152602081019050610ade565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b1e82610ac2565b610b288185610acc565b9350610b38818560208601610adc565b610b4181610b04565b840191505092915050565b5f6020820190508181035f830152610b648184610b14565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b9982610b70565b9050919050565b610ba981610b8f565b8114610bb3575f80fd5b50565b5f81359050610bc481610ba0565b92915050565b5f819050919050565b610bdc81610bca565b8114610be6575f80fd5b50565b5f81359050610bf781610bd3565b92915050565b5f8060408385031215610c1357610c12610b6c565b5b5f610c2085828601610bb6565b9250506020610c3185828601610be9565b9150509250929050565b5f8115159050919050565b610c4f81610c3b565b82525050565b5f602082019050610c685f830184610c46565b92915050565b610c7781610bca565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b5f805f60608486031215610cad57610cac610b6c565b5b5f610cba86828701610bb6565b9350506020610ccb86828701610bb6565b9250506040610cdc86828701610be9565b9150509250925092565b5f60ff82169050919050565b610cfb81610ce6565b82525050565b5f602082019050610d145f830184610cf2565b92915050565b5f60208284031215610d2f57610d2e610b6c565b5b5f610d3c84828501610bb6565b91505092915050565b5f8060408385031215610d5b57610d5a610b6c565b5b5f610d6885828601610bb6565b9250506020610d7985828601610bb6565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610dc757607f821691505b602082108103610dda57610dd9610d83565b5b50919050565b610de981610b8f565b82525050565b5f606082019050610e025f830186610de0565b610e0f6020830185610c6e565b610e1c6040830184610c6e565b949350505050565b5f602082019050610e375f830184610de0565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e7482610bca565b9150610e7f83610bca565b9250828201905080821115610e9757610e96610e3d565b5b9291505056fea2646970667358221220e360083fadcd14e331a763a568beb9c124a547faa854fc1d40cc4e530621cbb964736f6c63430008150033")] - contract ERC20Mock is ERC20 { - constructor() ERC20("MyToken", "MTK") {} + #[sol(rpc, bytecode="608060405234801561000f575f80fd5b506104278061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c806340c10f191461004357806370a082311461005f578063a9059cbb1461008f575b5f80fd5b61005d6004803603810190610058919061029a565b6100bf565b005b610079600480360381019061007491906102d8565b610115565b6040516100869190610312565b60405180910390f35b6100a960048036038101906100a4919061029a565b61015a565b6040516100b69190610345565b60405180910390f35b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461010a919061038b565b925050819055505050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f815f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546101a691906103be565b92505081905550815f808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546101f8919061038b565b925050819055506001905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102368261020d565b9050919050565b6102468161022c565b8114610250575f80fd5b50565b5f813590506102618161023d565b92915050565b5f819050919050565b61027981610267565b8114610283575f80fd5b50565b5f8135905061029481610270565b92915050565b5f80604083850312156102b0576102af610209565b5b5f6102bd85828601610253565b92505060206102ce85828601610286565b9150509250929050565b5f602082840312156102ed576102ec610209565b5b5f6102fa84828501610253565b91505092915050565b61030c81610267565b82525050565b5f6020820190506103255f830184610303565b92915050565b5f8115159050919050565b61033f8161032b565b82525050565b5f6020820190506103585f830184610336565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61039582610267565b91506103a083610267565b92508282019050808211156103b8576103b761035e565b5b92915050565b5f6103c882610267565b91506103d383610267565b92508282039050818111156103eb576103ea61035e565b5b9291505056fea264697066735822122057c469102c1fccf2c882c0a431f0687b30ef2c58ab13e2ecfc052290af551aa264736f6c63430008150033")] + contract ERC20Mock { + mapping(address => uint256) private _balances; - function balanceOf(address account) public override view returns (uint256) { - return super.balanceOf(account); + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; } function mint(address account, uint256 amount) public { - super._mint(account, amount); + _balances[account] += amount; } - function transfer(address to, uint256 amount) public override returns (bool) { - return super.transfer(to, amount); + function transfer(address to, uint256 amount) public returns (bool) { + _balances[msg.sender] -= amount; + _balances[to] += amount; + return true; } } } diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index 77bb60a7..38dfd01b 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -40,8 +40,8 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { let ERC20Mock::balanceOfReturn { _0: bob_balance } = erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_alice_balance - value, alice_balance); assert_eq!(initial_bob_balance + value, bob_balance); + assert_eq!(initial_alice_balance - value, alice_balance); Ok(()) } From 429b42ded5f5a8a4b260385631d4656ca876b165 Mon Sep 17 00:00:00 2001 From: Nenad Date: Fri, 20 Sep 2024 10:38:20 +0200 Subject: [PATCH 21/77] Revert e2e-tests.sh --- scripts/e2e-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index dd5044aa..f7b9cdeb 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example --test safe-erc20 +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" From 1348579963105fa879371b7af552f3e1e04ee060 Mon Sep 17 00:00:00 2001 From: Nenad Date: Fri, 20 Sep 2024 10:43:29 +0200 Subject: [PATCH 22/77] Add additional failure tests --- examples/safe-erc20/tests/safe-erc20.rs | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index 38dfd01b..997d747d 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -2,7 +2,7 @@ use alloy::primitives::uint; use alloy_primitives::U256; -use e2e::{receipt, watch, Account, ReceiptExt}; +use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; use abi::SafeErc20; use mock::{erc20, erc20::ERC20Mock}; @@ -45,3 +45,47 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { Ok(()) } + +#[e2e::test] +async fn safe_transfer_rejects_with_eoa_as_token( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let alice_addr = bob.address(); + let bob_addr = bob.address(); + + let value = uint!(1_U256); + + let err = send!(contract_alice.safeTransfer(alice_addr, bob_addr, value)) + .expect_err("should not be able to invoke 'transfer' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: bob_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn safe_transfer_rejects_insufficient_balance( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let bob_addr = bob.address(); + + let value = uint!(1_U256); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + + let err = + send!(contract_alice.safeTransfer(erc20_address, bob_addr, value)) + .expect_err("should not transfer when insufficient balance"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} From 3542d32967bd4fb28adaf32c226640321a918049 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 23 Sep 2024 10:45:12 +0200 Subject: [PATCH 23/77] Use inherited ERC20 functions + remove redundant src/ERC20Mock.sol --- examples/safe-erc20/src/ERC20Mock.sol | 21 --------------------- examples/safe-erc20/tests/mock/erc20.rs | 21 ++++++++++----------- scripts/e2e-tests.sh | 2 +- 3 files changed, 11 insertions(+), 33 deletions(-) delete mode 100644 examples/safe-erc20/src/ERC20Mock.sol diff --git a/examples/safe-erc20/src/ERC20Mock.sol b/examples/safe-erc20/src/ERC20Mock.sol deleted file mode 100644 index 96370d15..00000000 --- a/examples/safe-erc20/src/ERC20Mock.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.21; - -contract ERC20Mock { - mapping(address => uint256) private _balances; - - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; - } - - function mint(address account, uint256 amount) public { - _balances[account] += amount; - } - - function transfer(address to, uint256 amount) public returns (bool) { - _balances[msg.sender] -= amount; - _balances[to] += amount; - return true; - } -} \ No newline at end of file diff --git a/examples/safe-erc20/tests/mock/erc20.rs b/examples/safe-erc20/tests/mock/erc20.rs index 3a13124f..da71cb3d 100644 --- a/examples/safe-erc20/tests/mock/erc20.rs +++ b/examples/safe-erc20/tests/mock/erc20.rs @@ -6,22 +6,21 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801561000f575f80fd5b506104278061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c806340c10f191461004357806370a082311461005f578063a9059cbb1461008f575b5f80fd5b61005d6004803603810190610058919061029a565b6100bf565b005b610079600480360381019061007491906102d8565b610115565b6040516100869190610312565b60405180910390f35b6100a960048036038101906100a4919061029a565b61015a565b6040516100b69190610345565b60405180910390f35b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461010a919061038b565b925050819055505050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f815f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546101a691906103be565b92505081905550815f808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546101f8919061038b565b925050819055506001905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102368261020d565b9050919050565b6102468161022c565b8114610250575f80fd5b50565b5f813590506102618161023d565b92915050565b5f819050919050565b61027981610267565b8114610283575f80fd5b50565b5f8135905061029481610270565b92915050565b5f80604083850312156102b0576102af610209565b5b5f6102bd85828601610253565b92505060206102ce85828601610286565b9150509250929050565b5f602082840312156102ed576102ec610209565b5b5f6102fa84828501610253565b91505092915050565b61030c81610267565b82525050565b5f6020820190506103255f830184610303565b92915050565b5f8115159050919050565b61033f8161032b565b82525050565b5f6020820190506103585f830184610336565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61039582610267565b91506103a083610267565b92508282019050808211156103b8576103b761035e565b5b92915050565b5f6103c882610267565b91506103d383610267565b92508282039050818111156103eb576103ea61035e565b5b9291505056fea264697066735822122057c469102c1fccf2c882c0a431f0687b30ef2c58ab13e2ecfc052290af551aa264736f6c63430008150033")] - contract ERC20Mock { - mapping(address => uint256) private _balances; + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600781526020017f4d79546f6b656e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610ed380620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b4c565b60405180910390f35b6100d860048036038101906100d39190610bfd565b6102b4565b6040516100e59190610c55565b60405180910390f35b6100f66102d6565b6040516101039190610c7d565b60405180910390f35b61012660048036038101906101219190610c96565b6102df565b6040516101339190610c55565b60405180910390f35b61014461030d565b6040516101519190610d01565b60405180910390f35b610174600480360381019061016f9190610bfd565b610315565b005b610190600480360381019061018b9190610d1a565b610323565b60405161019d9190610c7d565b60405180910390f35b6101ae610334565b6040516101bb9190610b4c565b60405180910390f35b6101de60048036038101906101d99190610bfd565b6103c4565b6040516101eb9190610c55565b60405180910390f35b61020e60048036038101906102099190610d45565b6103d7565b60405161021b9190610c7d565b60405180910390f35b60606003805461023390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610db0565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f806102be610459565b90506102cb818585610460565b600191505092915050565b5f600254905090565b5f806102e9610459565b90506102f6858285610472565b610301858585610504565b60019150509392505050565b5f6012905090565b61031f82826105f4565b5050565b5f61032d82610673565b9050919050565b60606004805461034390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461036f90610db0565b80156103ba5780601f10610391576101008083540402835291602001916103ba565b820191905f5260205f20905b81548152906001019060200180831161039d57829003601f168201915b5050505050905090565b5f6103cf83836106b8565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b61046d83838360016106da565b505050565b5f61047d84846103d7565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146104fe57818110156104ef578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016104e693929190610def565b60405180910390fd5b6104fd84848484035f6106da565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610574575f6040517f96c6fd1e00000000000000000000000000000000000000000000000000000000815260040161056b9190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036105e4575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016105db9190610e24565b60405180910390fd5b6105ef8383836108a9565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610664575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161065b9190610e24565b60405180910390fd5b61066f5f83836108a9565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f806106c2610459565b90506106cf818585610504565b600191505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361074a575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016107419190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036107ba575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016107b19190610e24565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080156108a3578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161089a9190610c7d565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108f9578060025f8282546108ed9190610e6a565b925050819055506109c7565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610982578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161097993929190610def565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0e578060025f8282540392505081905550610a58565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610ab59190610c7d565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610af9578082015181840152602081019050610ade565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b1e82610ac2565b610b288185610acc565b9350610b38818560208601610adc565b610b4181610b04565b840191505092915050565b5f6020820190508181035f830152610b648184610b14565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b9982610b70565b9050919050565b610ba981610b8f565b8114610bb3575f80fd5b50565b5f81359050610bc481610ba0565b92915050565b5f819050919050565b610bdc81610bca565b8114610be6575f80fd5b50565b5f81359050610bf781610bd3565b92915050565b5f8060408385031215610c1357610c12610b6c565b5b5f610c2085828601610bb6565b9250506020610c3185828601610be9565b9150509250929050565b5f8115159050919050565b610c4f81610c3b565b82525050565b5f602082019050610c685f830184610c46565b92915050565b610c7781610bca565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b5f805f60608486031215610cad57610cac610b6c565b5b5f610cba86828701610bb6565b9350506020610ccb86828701610bb6565b9250506040610cdc86828701610be9565b9150509250925092565b5f60ff82169050919050565b610cfb81610ce6565b82525050565b5f602082019050610d145f830184610cf2565b92915050565b5f60208284031215610d2f57610d2e610b6c565b5b5f610d3c84828501610bb6565b91505092915050565b5f8060408385031215610d5b57610d5a610b6c565b5b5f610d6885828601610bb6565b9250506020610d7985828601610bb6565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610dc757607f821691505b602082108103610dda57610dd9610d83565b5b50919050565b610de981610b8f565b82525050565b5f606082019050610e025f830186610de0565b610e0f6020830185610c6e565b610e1c6040830184610c6e565b949350505050565b5f602082019050610e375f830184610de0565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e7482610bca565b9150610e7f83610bca565b9250828201905080821115610e9757610e96610e3d565b5b9291505056fea2646970667358221220ce6ad8a217435fe22e4de3a933dfc9f416d3df7a68c81a7e6083d9cfbcae807c64736f6c63430008150033")] + // SPDX-License-Identifier: MIT + contract ERC20Mock is ERC20 { + constructor() ERC20("MyToken", "MTK") {} - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; + function balanceOf(address account) public override view returns (uint256) { + return super.balanceOf(account); } - function mint(address account, uint256 amount) public { - _balances[account] += amount; + function mint(address account, uint256 value) public { + super._mint(account, value); } - function transfer(address to, uint256 amount) public returns (bool) { - _balances[msg.sender] -= amount; - _balances[to] += amount; - return true; + function transfer(address to, uint256 amount) public override returns (bool) { + return super.transfer(to, amount); } } } diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index f7b9cdeb..dd5044aa 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" +cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example --test safe-erc20 From b6ccccfde266b66579cc466b27daf75d870a5757 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 23 Sep 2024 10:56:41 +0200 Subject: [PATCH 24/77] Rename reject-on-error test --- examples/safe-erc20/tests/safe-erc20.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index 997d747d..de363d10 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -68,7 +68,7 @@ async fn safe_transfer_rejects_with_eoa_as_token( } #[e2e::test] -async fn safe_transfer_rejects_insufficient_balance( +async fn safe_transfer_rejects_on_error( alice: Account, bob: Account, ) -> eyre::Result<()> { From c42aa9d25347beeb2251e20254c8a6521c1b9149 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 23 Sep 2024 11:16:42 +0200 Subject: [PATCH 25/77] Add safe_transfer_from_+ tests + fix eoa rejection tests (fix alice address) --- contracts/src/token/erc20/utils/safe_erc20.rs | 41 ++++++ examples/safe-erc20/tests/abi/mod.rs | 1 + examples/safe-erc20/tests/mock/erc20.rs | 10 +- examples/safe-erc20/tests/safe-erc20.rs | 120 +++++++++++++++++- 4 files changed, 167 insertions(+), 5 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index b242c0d0..33e9364b 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -34,9 +34,20 @@ sol_interface! { /// Interface of the ERC-20 standard as defined in the ERC. interface IERC20 { /// Moves a `value` amount of tokens from the caller's account to `to`. + /// /// Returns a boolean value indicating whether the operation succeeded. + /// /// Emits a {Transfer} event. function transfer(address to, uint256 amount) external returns (bool); + + /// Moves a `value` amount of tokens from `from` to `to` using the + /// allowance mechanism. `value` is then deducted from the caller's + /// allowance. + /// + /// Returns a boolean value indicating whether the operation succeeded. + /// + /// Emits a {Transfer} event. + function transferFrom(address from, address to, uint256 amount) external returns (bool); } } @@ -87,4 +98,34 @@ impl SafeErc20 { Ok(()) } + + /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + /// calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + pub fn safe_transfer_from( + &mut self, + token: Address, + from: Address, + to: Address, + value: U256, + ) -> Result<(), Error> { + let erc20 = IERC20::new(token); + let call = Call::new_in(self); + + match erc20.transfer_from(call, from, to, value) { + Ok(data) => { + if data && !Address::has_code(&token) { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token }, + )); + } + } + Err(_) => { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token }, + )) + } + } + + Ok(()) + } } diff --git a/examples/safe-erc20/tests/abi/mod.rs b/examples/safe-erc20/tests/abi/mod.rs index 2a73483c..6a440d38 100644 --- a/examples/safe-erc20/tests/abi/mod.rs +++ b/examples/safe-erc20/tests/abi/mod.rs @@ -5,6 +5,7 @@ sol!( #[sol(rpc)] contract SafeErc20 { function safeTransfer(address token, address to, uint256 value) external; + function safeTransferFrom(address token, address from, address to, uint256 value) external; error SafeErc20FailedOperation(address token); error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); diff --git a/examples/safe-erc20/tests/mock/erc20.rs b/examples/safe-erc20/tests/mock/erc20.rs index da71cb3d..a39edc16 100644 --- a/examples/safe-erc20/tests/mock/erc20.rs +++ b/examples/safe-erc20/tests/mock/erc20.rs @@ -6,11 +6,15 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600781526020017f4d79546f6b656e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610ed380620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b4c565b60405180910390f35b6100d860048036038101906100d39190610bfd565b6102b4565b6040516100e59190610c55565b60405180910390f35b6100f66102d6565b6040516101039190610c7d565b60405180910390f35b61012660048036038101906101219190610c96565b6102df565b6040516101339190610c55565b60405180910390f35b61014461030d565b6040516101519190610d01565b60405180910390f35b610174600480360381019061016f9190610bfd565b610315565b005b610190600480360381019061018b9190610d1a565b610323565b60405161019d9190610c7d565b60405180910390f35b6101ae610334565b6040516101bb9190610b4c565b60405180910390f35b6101de60048036038101906101d99190610bfd565b6103c4565b6040516101eb9190610c55565b60405180910390f35b61020e60048036038101906102099190610d45565b6103d7565b60405161021b9190610c7d565b60405180910390f35b60606003805461023390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610db0565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f806102be610459565b90506102cb818585610460565b600191505092915050565b5f600254905090565b5f806102e9610459565b90506102f6858285610472565b610301858585610504565b60019150509392505050565b5f6012905090565b61031f82826105f4565b5050565b5f61032d82610673565b9050919050565b60606004805461034390610db0565b80601f016020809104026020016040519081016040528092919081815260200182805461036f90610db0565b80156103ba5780601f10610391576101008083540402835291602001916103ba565b820191905f5260205f20905b81548152906001019060200180831161039d57829003601f168201915b5050505050905090565b5f6103cf83836106b8565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b61046d83838360016106da565b505050565b5f61047d84846103d7565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146104fe57818110156104ef578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016104e693929190610def565b60405180910390fd5b6104fd84848484035f6106da565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610574575f6040517f96c6fd1e00000000000000000000000000000000000000000000000000000000815260040161056b9190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036105e4575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016105db9190610e24565b60405180910390fd5b6105ef8383836108a9565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610664575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161065b9190610e24565b60405180910390fd5b61066f5f83836108a9565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f806106c2610459565b90506106cf818585610504565b600191505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361074a575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016107419190610e24565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036107ba575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016107b19190610e24565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555080156108a3578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161089a9190610c7d565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108f9578060025f8282546108ed9190610e6a565b925050819055506109c7565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610982578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161097993929190610def565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0e578060025f8282540392505081905550610a58565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610ab59190610c7d565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610af9578082015181840152602081019050610ade565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b1e82610ac2565b610b288185610acc565b9350610b38818560208601610adc565b610b4181610b04565b840191505092915050565b5f6020820190508181035f830152610b648184610b14565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b9982610b70565b9050919050565b610ba981610b8f565b8114610bb3575f80fd5b50565b5f81359050610bc481610ba0565b92915050565b5f819050919050565b610bdc81610bca565b8114610be6575f80fd5b50565b5f81359050610bf781610bd3565b92915050565b5f8060408385031215610c1357610c12610b6c565b5b5f610c2085828601610bb6565b9250506020610c3185828601610be9565b9150509250929050565b5f8115159050919050565b610c4f81610c3b565b82525050565b5f602082019050610c685f830184610c46565b92915050565b610c7781610bca565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b5f805f60608486031215610cad57610cac610b6c565b5b5f610cba86828701610bb6565b9350506020610ccb86828701610bb6565b9250506040610cdc86828701610be9565b9150509250925092565b5f60ff82169050919050565b610cfb81610ce6565b82525050565b5f602082019050610d145f830184610cf2565b92915050565b5f60208284031215610d2f57610d2e610b6c565b5b5f610d3c84828501610bb6565b91505092915050565b5f8060408385031215610d5b57610d5a610b6c565b5b5f610d6885828601610bb6565b9250506020610d7985828601610bb6565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610dc757607f821691505b602082108103610dda57610dd9610d83565b5b50919050565b610de981610b8f565b82525050565b5f606082019050610e025f830186610de0565b610e0f6020830185610c6e565b610e1c6040830184610c6e565b949350505050565b5f602082019050610e375f830184610de0565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e7482610bca565b9150610e7f83610bca565b9250828201905080821115610e9757610e96610e3d565b5b9291505056fea2646970667358221220ce6ad8a217435fe22e4de3a933dfc9f416d3df7a68c81a7e6083d9cfbcae807c64736f6c63430008150033")] + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600781526020017f4d79546f6b656e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610efb80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b74565b60405180910390f35b6100d860048036038101906100d39190610c25565b6102b4565b6040516100e59190610c7d565b60405180910390f35b6100f66102c7565b6040516101039190610ca5565b60405180910390f35b61012660048036038101906101219190610cbe565b6102d0565b6040516101339190610c7d565b60405180910390f35b6101446102e5565b6040516101519190610d29565b60405180910390f35b610174600480360381019061016f9190610c25565b6102ed565b005b610190600480360381019061018b9190610d42565b6102fb565b60405161019d9190610ca5565b60405180910390f35b6101ae61030c565b6040516101bb9190610b74565b60405180910390f35b6101de60048036038101906101d99190610c25565b61039c565b6040516101eb9190610c7d565b60405180910390f35b61020e60048036038101906102099190610d6d565b6103af565b60405161021b9190610ca5565b60405180910390f35b60606003805461023390610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610dd8565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f6102bf8383610431565b905092915050565b5f600254905090565b5f6102dc848484610453565b90509392505050565b5f6012905090565b6102f78282610481565b5050565b5f61030582610500565b9050919050565b60606004805461031b90610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461034790610dd8565b80156103925780601f1061036957610100808354040283529160200191610392565b820191905f5260205f20905b81548152906001019060200180831161037557829003601f168201915b5050505050905090565b5f6103a78383610545565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f8061043b610567565b905061044881858561056e565b600191505092915050565b5f8061045d610567565b905061046a858285610580565b610475858585610612565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104f1575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104e89190610e17565b60405180910390fd5b6104fc5f8383610702565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f8061054f610567565b905061055c818585610612565b600191505092915050565b5f33905090565b61057b838383600161091b565b505050565b5f61058b84846103af565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461060c57818110156105fd578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016105f493929190610e30565b60405180910390fd5b61060b84848484035f61091b565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610682575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106799190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106f2575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106e99190610e17565b60405180910390fd5b6106fd838383610702565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610752578060025f8282546107469190610e92565b92505081905550610820565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156107db578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016107d293929190610e30565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610867578060025f82825403925050819055506108b1565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161090e9190610ca5565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361098b575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109829190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036109fb575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016109f29190610e17565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610ae4578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610adb9190610ca5565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b21578082015181840152602081019050610b06565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b4682610aea565b610b508185610af4565b9350610b60818560208601610b04565b610b6981610b2c565b840191505092915050565b5f6020820190508181035f830152610b8c8184610b3c565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bc182610b98565b9050919050565b610bd181610bb7565b8114610bdb575f80fd5b50565b5f81359050610bec81610bc8565b92915050565b5f819050919050565b610c0481610bf2565b8114610c0e575f80fd5b50565b5f81359050610c1f81610bfb565b92915050565b5f8060408385031215610c3b57610c3a610b94565b5b5f610c4885828601610bde565b9250506020610c5985828601610c11565b9150509250929050565b5f8115159050919050565b610c7781610c63565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b610c9f81610bf2565b82525050565b5f602082019050610cb85f830184610c96565b92915050565b5f805f60608486031215610cd557610cd4610b94565b5b5f610ce286828701610bde565b9350506020610cf386828701610bde565b9250506040610d0486828701610c11565b9150509250925092565b5f60ff82169050919050565b610d2381610d0e565b82525050565b5f602082019050610d3c5f830184610d1a565b92915050565b5f60208284031215610d5757610d56610b94565b5b5f610d6484828501610bde565b91505092915050565b5f8060408385031215610d8357610d82610b94565b5b5f610d9085828601610bde565b9250506020610da185828601610bde565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610def57607f821691505b602082108103610e0257610e01610dab565b5b50919050565b610e1181610bb7565b82525050565b5f602082019050610e2a5f830184610e08565b92915050565b5f606082019050610e435f830186610e08565b610e506020830185610c96565b610e5d6040830184610c96565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e9c82610bf2565b9150610ea783610bf2565b9250828201905080821115610ebf57610ebe610e65565b5b9291505056fea26469706673582212204b0d5b8c644aa4b312a9bee1615964e800240731dcc271c19c1615f2940a1ed564736f6c63430008150033")] // SPDX-License-Identifier: MIT contract ERC20Mock is ERC20 { constructor() ERC20("MyToken", "MTK") {} + function approve(address spender, uint256 value) public override returns (bool) { + return super.approve(spender, value); + } + function balanceOf(address account) public override view returns (uint256) { return super.balanceOf(account); } @@ -22,6 +26,10 @@ sol! { function transfer(address to, uint256 amount) public override returns (bool) { return super.transfer(to, amount); } + + function transferFrom(address from, address to, uint256 value) public override returns (bool) { + return super.transferFrom(from, to, value); + } } } diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index de363d10..1ccb9588 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -1,5 +1,7 @@ #![cfg(feature = "e2e")] +use std::assert_ne; + use alloy::primitives::uint; use alloy_primitives::U256; use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; @@ -40,8 +42,8 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { let ERC20Mock::balanceOfReturn { _0: bob_balance } = erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_bob_balance + value, bob_balance); assert_eq!(initial_alice_balance - value, alice_balance); + assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) } @@ -53,7 +55,7 @@ async fn safe_transfer_rejects_with_eoa_as_token( ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); - let alice_addr = bob.address(); + let alice_addr = alice.address(); let bob_addr = bob.address(); let value = uint!(1_U256); @@ -61,8 +63,9 @@ async fn safe_transfer_rejects_with_eoa_as_token( let err = send!(contract_alice.safeTransfer(alice_addr, bob_addr, value)) .expect_err("should not be able to invoke 'transfer' on EOA"); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: bob_addr + token: alice_addr })); + assert_ne!(alice_addr, bob_addr); Ok(()) } @@ -82,7 +85,116 @@ async fn safe_transfer_rejects_on_error( let err = send!(contract_alice.safeTransfer(erc20_address, bob_addr, value)) - .expect_err("should not transfer when insufficient balance"); + .expect_err( + "transfer should not succeed when insufficient balance", + ); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} + +#[e2e::test] +async fn safe_transfers_from( + alice: Account, + bob: Account, + charlie: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let charlie_addr = charlie.address(); + + let balance = uint!(10_U256); + let value = uint!(1_U256); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.mint(charlie_addr, balance)); + + let ERC20Mock::balanceOfReturn { _0: initial_charlie_balance } = + erc20_alice.balanceOf(charlie_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + assert_eq!(initial_charlie_balance, balance); + assert_eq!(initial_bob_balance, U256::ZERO); + + let erc20_charlie = ERC20Mock::new(erc20_address, &charlie.wallet); + let _ = watch!(erc20_charlie.approve(alice_addr, value)); + + let _ = receipt!(contract_alice.safeTransferFrom( + erc20_address, + charlie_addr, + bob_addr, + value + ))?; + + let ERC20Mock::balanceOfReturn { _0: charlie_balance } = + erc20_alice.balanceOf(charlie_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + + assert_eq!(initial_charlie_balance - value, charlie_balance); + assert_eq!(initial_bob_balance + value, bob_balance); + + Ok(()) +} + +#[e2e::test] +async fn safe_transfer_from_rejects_with_eoa_as_token( + alice: Account, + bob: Account, + charlie: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let charlie_addr = charlie.address(); + + let value = uint!(1_U256); + + let err = send!(contract_alice.safeTransferFrom( + alice_addr, + bob_addr, + charlie_addr, + value + )) + .expect_err("should not be able to invoke 'transferFrom' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: alice_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn safe_transfer_from_rejects_on_error( + alice: Account, + bob: Account, + charlie: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let bob_addr = bob.address(); + let charlie_addr = charlie.address(); + + let value = uint!(1_U256); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + + let err = send!(contract_alice.safeTransferFrom( + erc20_address, + charlie_addr, + bob_addr, + value + )) + .expect_err( + "transferFrom should not succeed when not enough approved balance", + ); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { token: erc20_address })); From 282e9f96c301b34f5227762920562924e2c5238b Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 24 Sep 2024 11:31:33 +0200 Subject: [PATCH 26/77] Fixed tests --- examples/safe-erc20/tests/safe-erc20.rs | 135 +++++++++++++----------- 1 file changed, 72 insertions(+), 63 deletions(-) diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/safe-erc20.rs index 1ccb9588..8fdd2070 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/safe-erc20.rs @@ -1,7 +1,5 @@ #![cfg(feature = "e2e")] -use std::assert_ne; - use alloy::primitives::uint; use alloy_primitives::U256; use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; @@ -14,35 +12,41 @@ mod mock; #[e2e::test] async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); - let alice_addr = alice.address(); + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); let bob_addr = bob.address(); let balance = uint!(10_U256); let value = uint!(1_U256); - let erc20_address = erc20::deploy(&alice.wallet).await?; - let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + let erc20mock_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20mock_address, &alice.wallet); - let _ = watch!(erc20_alice.mint(alice_addr, balance)); + let _ = watch!(erc20_alice.mint(safe_erc20_mock_addr, balance)); - let ERC20Mock::balanceOfReturn { _0: initial_alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: initial_safe_erc20_mock_balance } = + erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_alice_balance, balance); + assert_eq!(initial_safe_erc20_mock_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = - receipt!(contract_alice.safeTransfer(erc20_address, bob_addr, value))?; + let _ = receipt!(safe_erc20_mock_alice.safeTransfer( + erc20mock_address, + bob_addr, + value + ))?; - let ERC20Mock::balanceOfReturn { _0: alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: safe_erc20_mock_balance } = + erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; let ERC20Mock::balanceOfReturn { _0: bob_balance } = erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_alice_balance - value, alice_balance); + assert_eq!( + initial_safe_erc20_mock_balance - value, + safe_erc20_mock_balance + ); assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) @@ -52,20 +56,25 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { async fn safe_transfer_rejects_with_eoa_as_token( alice: Account, bob: Account, + has_no_code: Account, ) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); - let alice_addr = alice.address(); + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); let bob_addr = bob.address(); + let has_no_code_addr = has_no_code.address(); let value = uint!(1_U256); - let err = send!(contract_alice.safeTransfer(alice_addr, bob_addr, value)) - .expect_err("should not be able to invoke 'transfer' on EOA"); + let err = send!(safe_erc20_mock_alice.safeTransfer( + has_no_code_addr, + bob_addr, + value + )) + .expect_err("should not be able to invoke 'transfer' on EOA"); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: alice_addr + token: has_no_code_addr })); - assert_ne!(alice_addr, bob_addr); Ok(()) } @@ -75,19 +84,21 @@ async fn safe_transfer_rejects_on_error( alice: Account, bob: Account, ) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); let bob_addr = bob.address(); let value = uint!(1_U256); let erc20_address = erc20::deploy(&alice.wallet).await?; - let err = - send!(contract_alice.safeTransfer(erc20_address, bob_addr, value)) - .expect_err( - "transfer should not succeed when insufficient balance", - ); + let err = send!(safe_erc20_mock_alice.safeTransfer( + erc20_address, + bob_addr, + value + )) + .expect_err("transfer should not succeed when insufficient balance"); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { token: erc20_address })); @@ -96,16 +107,12 @@ async fn safe_transfer_rejects_on_error( } #[e2e::test] -async fn safe_transfers_from( - alice: Account, - bob: Account, - charlie: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); +async fn safe_transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); - let charlie_addr = charlie.address(); let balance = uint!(10_U256); let value = uint!(1_U256); @@ -113,31 +120,29 @@ async fn safe_transfers_from( let erc20_address = erc20::deploy(&alice.wallet).await?; let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); - let _ = watch!(erc20_alice.mint(charlie_addr, balance)); + let _ = watch!(erc20_alice.mint(alice_addr, balance)); + let _ = watch!(erc20_alice.approve(safe_erc20_mock_addr, value)); - let ERC20Mock::balanceOfReturn { _0: initial_charlie_balance } = - erc20_alice.balanceOf(charlie_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: initial_alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_charlie_balance, balance); + assert_eq!(initial_alice_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let erc20_charlie = ERC20Mock::new(erc20_address, &charlie.wallet); - let _ = watch!(erc20_charlie.approve(alice_addr, value)); - - let _ = receipt!(contract_alice.safeTransferFrom( + let _ = receipt!(safe_erc20_mock_alice.safeTransferFrom( erc20_address, - charlie_addr, + alice_addr, bob_addr, value ))?; - let ERC20Mock::balanceOfReturn { _0: charlie_balance } = - erc20_alice.balanceOf(charlie_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; let ERC20Mock::balanceOfReturn { _0: bob_balance } = erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_charlie_balance - value, charlie_balance); + assert_eq!(initial_alice_balance - value, alice_balance); assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) @@ -147,25 +152,26 @@ async fn safe_transfers_from( async fn safe_transfer_from_rejects_with_eoa_as_token( alice: Account, bob: Account, - charlie: Account, + has_no_code: Account, ) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); - let charlie_addr = charlie.address(); + let has_no_code_addr = has_no_code.address(); let value = uint!(1_U256); - let err = send!(contract_alice.safeTransferFrom( + let err = send!(safe_erc20_mock_alice.safeTransferFrom( + has_no_code_addr, alice_addr, bob_addr, - charlie_addr, value )) .expect_err("should not be able to invoke 'transferFrom' on EOA"); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: alice_addr + token: has_no_code_addr })); Ok(()) @@ -175,20 +181,23 @@ async fn safe_transfer_from_rejects_with_eoa_as_token( async fn safe_transfer_from_rejects_on_error( alice: Account, bob: Account, - charlie: Account, ) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract_alice = SafeErc20::new(contract_addr, &alice.wallet); + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let alice_addr = alice.address(); let bob_addr = bob.address(); - let charlie_addr = charlie.address(); + let balance = uint!(10_U256); let value = uint!(1_U256); let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + let _ = watch!(erc20_alice.mint(alice_addr, balance)); - let err = send!(contract_alice.safeTransferFrom( + let err = send!(safe_erc20_mock_alice.safeTransferFrom( erc20_address, - charlie_addr, + alice_addr, bob_addr, value )) From 23ca2af3d22a852da97fd39c1e28453e66d03632 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 24 Sep 2024 12:05:03 +0200 Subject: [PATCH 27/77] Add stubs for the rest of ERC20-related safe-functions --- contracts/src/token/erc20/utils/safe_erc20.rs | 34 +++++++++++++++++++ examples/safe-erc20/tests/abi/mod.rs | 3 ++ 2 files changed, 37 insertions(+) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 33e9364b..13e135ef 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -128,4 +128,38 @@ impl SafeErc20 { Ok(()) } + + /// Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + /// non-reverting calls are assumed to be successful. + pub fn safe_increase_allowance( + &mut self, + token: Address, + spender: Address, + value: U256, + ) -> Result<(), Error> { + todo!() + } + + /// Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no + /// value, non-reverting calls are assumed to be successful. + pub fn safe_decrease_allowance( + &mut self, + token: Address, + spender: Address, + requested_decrease: U256, + ) -> Result<(), Error> { + todo!() + } + + /// Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + /// non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + /// to be set to zero before setting it to a non-zero value, such as USDT. + pub fn force_approve( + &mut self, + token: Address, + spender: Address, + value: U256, + ) -> Result<(), Error> { + todo!() + } } diff --git a/examples/safe-erc20/tests/abi/mod.rs b/examples/safe-erc20/tests/abi/mod.rs index 6a440d38..ac07892c 100644 --- a/examples/safe-erc20/tests/abi/mod.rs +++ b/examples/safe-erc20/tests/abi/mod.rs @@ -6,6 +6,9 @@ sol!( contract SafeErc20 { function safeTransfer(address token, address to, uint256 value) external; function safeTransferFrom(address token, address from, address to, uint256 value) external; + function safeIncreaseAllowance(address token, address spender, uint256 value) external; + function safeDecreaseAllowance(address token, address spender, uint256 requestedDecrease) external; + function forceApprove(address token, address spender, uint256 value) external; error SafeErc20FailedOperation(address token); error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); From 53ab7f06e7b0721f3ac9605d0e9959656cbb740f Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 24 Sep 2024 12:22:03 +0200 Subject: [PATCH 28/77] Add all required erc20 mocks --- examples/safe-erc20/tests/mock/erc20.rs | 4 +- .../tests/mock/erc20_force_approve.rs | 25 +++++++++ .../safe-erc20/tests/mock/erc20_no_return.rs | 53 +++++++++++++++++++ .../tests/mock/erc20_return_false.rs | 44 +++++++++++++++ examples/safe-erc20/tests/mock/mod.rs | 3 ++ 5 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 examples/safe-erc20/tests/mock/erc20_force_approve.rs create mode 100644 examples/safe-erc20/tests/mock/erc20_no_return.rs create mode 100644 examples/safe-erc20/tests/mock/erc20_return_false.rs diff --git a/examples/safe-erc20/tests/mock/erc20.rs b/examples/safe-erc20/tests/mock/erc20.rs index a39edc16..6d9a7b98 100644 --- a/examples/safe-erc20/tests/mock/erc20.rs +++ b/examples/safe-erc20/tests/mock/erc20.rs @@ -6,10 +6,10 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600781526020017f4d79546f6b656e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610efb80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b74565b60405180910390f35b6100d860048036038101906100d39190610c25565b6102b4565b6040516100e59190610c7d565b60405180910390f35b6100f66102c7565b6040516101039190610ca5565b60405180910390f35b61012660048036038101906101219190610cbe565b6102d0565b6040516101339190610c7d565b60405180910390f35b6101446102e5565b6040516101519190610d29565b60405180910390f35b610174600480360381019061016f9190610c25565b6102ed565b005b610190600480360381019061018b9190610d42565b6102fb565b60405161019d9190610ca5565b60405180910390f35b6101ae61030c565b6040516101bb9190610b74565b60405180910390f35b6101de60048036038101906101d99190610c25565b61039c565b6040516101eb9190610c7d565b60405180910390f35b61020e60048036038101906102099190610d6d565b6103af565b60405161021b9190610ca5565b60405180910390f35b60606003805461023390610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610dd8565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f6102bf8383610431565b905092915050565b5f600254905090565b5f6102dc848484610453565b90509392505050565b5f6012905090565b6102f78282610481565b5050565b5f61030582610500565b9050919050565b60606004805461031b90610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461034790610dd8565b80156103925780601f1061036957610100808354040283529160200191610392565b820191905f5260205f20905b81548152906001019060200180831161037557829003601f168201915b5050505050905090565b5f6103a78383610545565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f8061043b610567565b905061044881858561056e565b600191505092915050565b5f8061045d610567565b905061046a858285610580565b610475858585610612565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104f1575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104e89190610e17565b60405180910390fd5b6104fc5f8383610702565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f8061054f610567565b905061055c818585610612565b600191505092915050565b5f33905090565b61057b838383600161091b565b505050565b5f61058b84846103af565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461060c57818110156105fd578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016105f493929190610e30565b60405180910390fd5b61060b84848484035f61091b565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610682575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106799190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106f2575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106e99190610e17565b60405180910390fd5b6106fd838383610702565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610752578060025f8282546107469190610e92565b92505081905550610820565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156107db578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016107d293929190610e30565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610867578060025f82825403925050819055506108b1565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161090e9190610ca5565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361098b575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109829190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036109fb575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016109f29190610e17565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610ae4578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610adb9190610ca5565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b21578082015181840152602081019050610b06565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b4682610aea565b610b508185610af4565b9350610b60818560208601610b04565b610b6981610b2c565b840191505092915050565b5f6020820190508181035f830152610b8c8184610b3c565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bc182610b98565b9050919050565b610bd181610bb7565b8114610bdb575f80fd5b50565b5f81359050610bec81610bc8565b92915050565b5f819050919050565b610c0481610bf2565b8114610c0e575f80fd5b50565b5f81359050610c1f81610bfb565b92915050565b5f8060408385031215610c3b57610c3a610b94565b5b5f610c4885828601610bde565b9250506020610c5985828601610c11565b9150509250929050565b5f8115159050919050565b610c7781610c63565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b610c9f81610bf2565b82525050565b5f602082019050610cb85f830184610c96565b92915050565b5f805f60608486031215610cd557610cd4610b94565b5b5f610ce286828701610bde565b9350506020610cf386828701610bde565b9250506040610d0486828701610c11565b9150509250925092565b5f60ff82169050919050565b610d2381610d0e565b82525050565b5f602082019050610d3c5f830184610d1a565b92915050565b5f60208284031215610d5757610d56610b94565b5b5f610d6484828501610bde565b91505092915050565b5f8060408385031215610d8357610d82610b94565b5b5f610d9085828601610bde565b9250506020610da185828601610bde565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610def57607f821691505b602082108103610e0257610e01610dab565b5b50919050565b610e1181610bb7565b82525050565b5f602082019050610e2a5f830184610e08565b92915050565b5f606082019050610e435f830186610e08565b610e506020830185610c96565b610e5d6040830184610c96565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e9c82610bf2565b9150610ea783610bf2565b9250828201905080821115610ebf57610ebe610e65565b5b9291505056fea26469706673582212204b0d5b8c644aa4b312a9bee1615964e800240731dcc271c19c1615f2940a1ed564736f6c63430008150033")] + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600981526020017f45524332304d6f636b00000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610efb80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b74565b60405180910390f35b6100d860048036038101906100d39190610c25565b6102b4565b6040516100e59190610c7d565b60405180910390f35b6100f66102c7565b6040516101039190610ca5565b60405180910390f35b61012660048036038101906101219190610cbe565b6102d0565b6040516101339190610c7d565b60405180910390f35b6101446102e5565b6040516101519190610d29565b60405180910390f35b610174600480360381019061016f9190610c25565b6102ed565b005b610190600480360381019061018b9190610d42565b6102fb565b60405161019d9190610ca5565b60405180910390f35b6101ae61030c565b6040516101bb9190610b74565b60405180910390f35b6101de60048036038101906101d99190610c25565b61039c565b6040516101eb9190610c7d565b60405180910390f35b61020e60048036038101906102099190610d6d565b6103af565b60405161021b9190610ca5565b60405180910390f35b60606003805461023390610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610dd8565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f6102bf8383610431565b905092915050565b5f600254905090565b5f6102dc848484610453565b90509392505050565b5f6012905090565b6102f78282610481565b5050565b5f61030582610500565b9050919050565b60606004805461031b90610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461034790610dd8565b80156103925780601f1061036957610100808354040283529160200191610392565b820191905f5260205f20905b81548152906001019060200180831161037557829003601f168201915b5050505050905090565b5f6103a78383610545565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f8061043b610567565b905061044881858561056e565b600191505092915050565b5f8061045d610567565b905061046a858285610580565b610475858585610612565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104f1575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104e89190610e17565b60405180910390fd5b6104fc5f8383610702565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f8061054f610567565b905061055c818585610612565b600191505092915050565b5f33905090565b61057b838383600161091b565b505050565b5f61058b84846103af565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461060c57818110156105fd578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016105f493929190610e30565b60405180910390fd5b61060b84848484035f61091b565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610682575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106799190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106f2575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106e99190610e17565b60405180910390fd5b6106fd838383610702565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610752578060025f8282546107469190610e92565b92505081905550610820565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156107db578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016107d293929190610e30565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610867578060025f82825403925050819055506108b1565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161090e9190610ca5565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361098b575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109829190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036109fb575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016109f29190610e17565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610ae4578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610adb9190610ca5565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b21578082015181840152602081019050610b06565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b4682610aea565b610b508185610af4565b9350610b60818560208601610b04565b610b6981610b2c565b840191505092915050565b5f6020820190508181035f830152610b8c8184610b3c565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bc182610b98565b9050919050565b610bd181610bb7565b8114610bdb575f80fd5b50565b5f81359050610bec81610bc8565b92915050565b5f819050919050565b610c0481610bf2565b8114610c0e575f80fd5b50565b5f81359050610c1f81610bfb565b92915050565b5f8060408385031215610c3b57610c3a610b94565b5b5f610c4885828601610bde565b9250506020610c5985828601610c11565b9150509250929050565b5f8115159050919050565b610c7781610c63565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b610c9f81610bf2565b82525050565b5f602082019050610cb85f830184610c96565b92915050565b5f805f60608486031215610cd557610cd4610b94565b5b5f610ce286828701610bde565b9350506020610cf386828701610bde565b9250506040610d0486828701610c11565b9150509250925092565b5f60ff82169050919050565b610d2381610d0e565b82525050565b5f602082019050610d3c5f830184610d1a565b92915050565b5f60208284031215610d5757610d56610b94565b5b5f610d6484828501610bde565b91505092915050565b5f8060408385031215610d8357610d82610b94565b5b5f610d9085828601610bde565b9250506020610da185828601610bde565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610def57607f821691505b602082108103610e0257610e01610dab565b5b50919050565b610e1181610bb7565b82525050565b5f602082019050610e2a5f830184610e08565b92915050565b5f606082019050610e435f830186610e08565b610e506020830185610c96565b610e5d6040830184610c96565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e9c82610bf2565b9150610ea783610bf2565b9250828201905080821115610ebf57610ebe610e65565b5b9291505056fea264697066735822122098e48e82a2d2ff17cb731942aac20d7002c5b8470944afacca92dce20fd8178a64736f6c63430008150033")] // SPDX-License-Identifier: MIT contract ERC20Mock is ERC20 { - constructor() ERC20("MyToken", "MTK") {} + constructor() ERC20("ERC20Mock", "MTK") {} function approve(address spender, uint256 value) public override returns (bool) { return super.approve(spender, value); diff --git a/examples/safe-erc20/tests/mock/erc20_force_approve.rs b/examples/safe-erc20/tests/mock/erc20_force_approve.rs new file mode 100644 index 00000000..39a1393b --- /dev/null +++ b/examples/safe-erc20/tests/mock/erc20_force_approve.rs @@ -0,0 +1,25 @@ +#![allow(dead_code)] +#![cfg(feature = "e2e")] +use alloy::{primitives::Address, sol}; +use e2e::Wallet; + +sol! { + #[allow(missing_docs)] + // Built with Remix IDE; solc v0.8.21+commit.d9974bed + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601581526020017f4552433230466f726365417070726f76654d6f636b00000000000000000000008152506040518060400160405280600381526020017f46414d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610ecb80620003ff5f395ff3fe608060405234801561000f575f80fd5b5060043610610091575f3560e01c8063313ce56711610064578063313ce5671461013157806370a082311461014f57806395d89b411461017f578063a9059cbb1461019d578063dd62ed3e146101cd57610091565b806306fdde0314610095578063095ea7b3146100b357806318160ddd146100e357806323b872dd14610101575b5f80fd5b61009d6101fd565b6040516100aa9190610adc565b60405180910390f35b6100cd60048036038101906100c89190610b8d565b61028d565b6040516100da9190610be5565b60405180910390f35b6100eb6102f5565b6040516100f89190610c0d565b60405180910390f35b61011b60048036038101906101169190610c26565b6102fe565b6040516101289190610be5565b60405180910390f35b61013961032c565b6040516101469190610c91565b60405180910390f35b61016960048036038101906101649190610caa565b610334565b6040516101769190610c0d565b60405180910390f35b610187610379565b6040516101949190610adc565b60405180910390f35b6101b760048036038101906101b29190610b8d565b610409565b6040516101c49190610be5565b60405180910390f35b6101e760048036038101906101e29190610cd5565b61042b565b6040516101f49190610c0d565b60405180910390f35b60606003805461020c90610d40565b80601f016020809104026020016040519081016040528092919081815260200182805461023890610d40565b80156102835780601f1061025a57610100808354040283529160200191610283565b820191905f5260205f20905b81548152906001019060200180831161026657829003601f168201915b5050505050905090565b5f808214806102a457505f6102a2338561042b565b145b6102e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102da90610dba565b60405180910390fd5b6102ed83836104ad565b905092915050565b5f600254905090565b5f806103086104cf565b90506103158582856104d6565b610320858585610568565b60019150509392505050565b5f6012905090565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606004805461038890610d40565b80601f01602080910402602001604051908101604052809291908181526020018280546103b490610d40565b80156103ff5780601f106103d6576101008083540402835291602001916103ff565b820191905f5260205f20905b8154815290600101906020018083116103e257829003601f168201915b5050505050905090565b5f806104136104cf565b9050610420818585610568565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f806104b76104cf565b90506104c4818585610658565b600191505092915050565b5f33905090565b5f6104e1848461042b565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146105625781811015610553578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161054a93929190610de7565b60405180910390fd5b61056184848484035f61066a565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105d8575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016105cf9190610e1c565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610648575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161063f9190610e1c565b60405180910390fd5b610653838383610839565b505050565b610665838383600161066a565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036106da575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016106d19190610e1c565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361074a575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016107419190610e1c565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610833578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161082a9190610c0d565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610889578060025f82825461087d9190610e62565b92505081905550610957565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610912578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161090993929190610de7565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361099e578060025f82825403925050819055506109e8565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610a459190610c0d565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610a89578082015181840152602081019050610a6e565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610aae82610a52565b610ab88185610a5c565b9350610ac8818560208601610a6c565b610ad181610a94565b840191505092915050565b5f6020820190508181035f830152610af48184610aa4565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b2982610b00565b9050919050565b610b3981610b1f565b8114610b43575f80fd5b50565b5f81359050610b5481610b30565b92915050565b5f819050919050565b610b6c81610b5a565b8114610b76575f80fd5b50565b5f81359050610b8781610b63565b92915050565b5f8060408385031215610ba357610ba2610afc565b5b5f610bb085828601610b46565b9250506020610bc185828601610b79565b9150509250929050565b5f8115159050919050565b610bdf81610bcb565b82525050565b5f602082019050610bf85f830184610bd6565b92915050565b610c0781610b5a565b82525050565b5f602082019050610c205f830184610bfe565b92915050565b5f805f60608486031215610c3d57610c3c610afc565b5b5f610c4a86828701610b46565b9350506020610c5b86828701610b46565b9250506040610c6c86828701610b79565b9150509250925092565b5f60ff82169050919050565b610c8b81610c76565b82525050565b5f602082019050610ca45f830184610c82565b92915050565b5f60208284031215610cbf57610cbe610afc565b5b5f610ccc84828501610b46565b91505092915050565b5f8060408385031215610ceb57610cea610afc565b5b5f610cf885828601610b46565b9250506020610d0985828601610b46565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610d5757607f821691505b602082108103610d6a57610d69610d13565b5b50919050565b7f5553445420617070726f76616c206661696c75726500000000000000000000005f82015250565b5f610da4601583610a5c565b9150610daf82610d70565b602082019050919050565b5f6020820190508181035f830152610dd181610d98565b9050919050565b610de181610b1f565b82525050565b5f606082019050610dfa5f830186610dd8565b610e076020830185610bfe565b610e146040830184610bfe565b949350505050565b5f602082019050610e2f5f830184610dd8565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e6c82610b5a565b9150610e7783610b5a565b9250828201905080821115610e8f57610e8e610e35565b5b9291505056fea264697066735822122085f85d85cd9786ae551a5465b306925cc60ff074c4b00744641f465d7439905064736f6c63430008150033")] + // SPDX-License-Identifier: MIT + contract ERC20ForceApproveMock is ERC20 { + constructor() ERC20("ERC20ForceApproveMock", "FAM") {} + + function approve(address spender, uint256 amount) public virtual override returns (bool) { + require(amount == 0 || allowance(msg.sender, spender) == 0, "USDT approval failure"); + return super.approve(spender, amount); + } + } +} + +pub async fn deploy(wallet: &Wallet) -> eyre::Result
{ + // Deploy the contract. + let contract = ERC20ForceApproveMock::deploy(wallet).await?; + Ok(*contract.address()) +} diff --git a/examples/safe-erc20/tests/mock/erc20_no_return.rs b/examples/safe-erc20/tests/mock/erc20_no_return.rs new file mode 100644 index 00000000..8e548e78 --- /dev/null +++ b/examples/safe-erc20/tests/mock/erc20_no_return.rs @@ -0,0 +1,53 @@ +#![allow(dead_code)] +#![cfg(feature = "e2e")] +use alloy::{primitives::Address, sol}; +use e2e::Wallet; + +sol! { + #[allow(missing_docs)] + // Built with Remix IDE; solc v0.8.21+commit.d9974bed + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601181526020017f45524332304e6f52657475726e4d6f636b0000000000000000000000000000008152506040518060400160405280600381526020017f4e524d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610f0180620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b7a565b60405180910390f35b6100d860048036038101906100d39190610c2b565b6102b4565b6040516100e59190610c83565b60405180910390f35b6100f66102c3565b6040516101039190610cab565b60405180910390f35b61012660048036038101906101219190610cc4565b6102cc565b6040516101339190610c83565b60405180910390f35b6101446102dc565b6040516101519190610d2f565b60405180910390f35b610174600480360381019061016f9190610c2b565b6102e4565b005b610190600480360381019061018b9190610d48565b6102f2565b60405161019d9190610cab565b60405180910390f35b6101ae610303565b6040516101bb9190610b7a565b60405180910390f35b6101de60048036038101906101d99190610c2b565b610393565b6040516101eb9190610c83565b60405180910390f35b61020e60048036038101906102099190610d73565b6103a2565b60405161021b9190610cab565b60405180910390f35b60606003805461023390610dde565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610dde565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f6102bf83836103b5565b5f80f35b5f600254905090565b5f6102d88484846103d7565b5f80f35b5f6012905090565b6102ee8282610405565b5050565b5f6102fc82610484565b9050919050565b60606004805461031290610dde565b80601f016020809104026020016040519081016040528092919081815260200182805461033e90610dde565b80156103895780601f1061036057610100808354040283529160200191610389565b820191905f5260205f20905b81548152906001019060200180831161036c57829003601f168201915b5050505050905090565b5f61039e83836104c9565b5f80f35b5f6103ad83836104eb565b905092915050565b5f806103bf61056d565b90506103cc818585610574565b600191505092915050565b5f806103e161056d565b90506103ee858285610586565b6103f9858585610618565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610475575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161046c9190610e1d565b60405180910390fd5b6104805f8383610708565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f806104d361056d565b90506104e0818585610618565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b6105818383836001610921565b505050565b5f61059184846103a2565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106125781811015610603578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016105fa93929190610e36565b60405180910390fd5b61061184848484035f610921565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610688575f6040517f96c6fd1e00000000000000000000000000000000000000000000000000000000815260040161067f9190610e1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106f8575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106ef9190610e1d565b60405180910390fd5b610703838383610708565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610758578060025f82825461074c9190610e98565b92505081905550610826565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156107e1578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016107d893929190610e36565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361086d578060025f82825403925050819055506108b7565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109149190610cab565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610991575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109889190610e1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a01575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016109f89190610e1d565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610aea578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610ae19190610cab565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b27578082015181840152602081019050610b0c565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b4c82610af0565b610b568185610afa565b9350610b66818560208601610b0a565b610b6f81610b32565b840191505092915050565b5f6020820190508181035f830152610b928184610b42565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bc782610b9e565b9050919050565b610bd781610bbd565b8114610be1575f80fd5b50565b5f81359050610bf281610bce565b92915050565b5f819050919050565b610c0a81610bf8565b8114610c14575f80fd5b50565b5f81359050610c2581610c01565b92915050565b5f8060408385031215610c4157610c40610b9a565b5b5f610c4e85828601610be4565b9250506020610c5f85828601610c17565b9150509250929050565b5f8115159050919050565b610c7d81610c69565b82525050565b5f602082019050610c965f830184610c74565b92915050565b610ca581610bf8565b82525050565b5f602082019050610cbe5f830184610c9c565b92915050565b5f805f60608486031215610cdb57610cda610b9a565b5b5f610ce886828701610be4565b9350506020610cf986828701610be4565b9250506040610d0a86828701610c17565b9150509250925092565b5f60ff82169050919050565b610d2981610d14565b82525050565b5f602082019050610d425f830184610d20565b92915050565b5f60208284031215610d5d57610d5c610b9a565b5b5f610d6a84828501610be4565b91505092915050565b5f8060408385031215610d8957610d88610b9a565b5b5f610d9685828601610be4565b9250506020610da785828601610be4565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610df557607f821691505b602082108103610e0857610e07610db1565b5b50919050565b610e1781610bbd565b82525050565b5f602082019050610e305f830184610e0e565b92915050565b5f606082019050610e495f830186610e0e565b610e566020830185610c9c565b610e636040830184610c9c565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610ea282610bf8565b9150610ead83610bf8565b9250828201905080821115610ec557610ec4610e6b565b5b9291505056fea2646970667358221220b88d95169fcafa24f302904c1d9f60a9d0d8b37907b43a0ef69b85ef238c60fd64736f6c63430008150033")] + // SPDX-License-Identifier: MIT + contract ERC20NoReturnMock is ERC20 { + constructor() ERC20("ERC20NoReturnMock", "NRM") {} + + function transfer(address to, uint256 amount) public override returns (bool) { + super.transfer(to, amount); + assembly { + return(0, 0) + } + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + super.transferFrom(from, to, amount); + assembly { + return(0, 0) + } + } + + function approve(address spender, uint256 amount) public override returns (bool) { + super.approve(spender, amount); + assembly { + return(0, 0) + } + } + + function balanceOf(address account) public override view returns (uint256) { + return super.balanceOf(account); + } + + function mint(address account, uint256 value) public { + super._mint(account, value); + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return super.allowance(owner, spender); + } + } +} + +pub async fn deploy(wallet: &Wallet) -> eyre::Result
{ + // Deploy the contract. + let contract = ERC20NoReturnMock::deploy(wallet).await?; + Ok(*contract.address()) +} diff --git a/examples/safe-erc20/tests/mock/erc20_return_false.rs b/examples/safe-erc20/tests/mock/erc20_return_false.rs new file mode 100644 index 00000000..70059496 --- /dev/null +++ b/examples/safe-erc20/tests/mock/erc20_return_false.rs @@ -0,0 +1,44 @@ +#![allow(dead_code)] +#![cfg(feature = "e2e")] +use alloy::{primitives::Address, sol}; +use e2e::Wallet; + +sol! { + #[allow(missing_docs)] + // Built with Remix IDE; solc v0.8.21+commit.d9974bed + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601481526020017f455243323052657475726e46616c73654d6f636b0000000000000000000000008152506040518060400160405280600381526020017f52464d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610b0d80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610786565b60405180910390f35b6100d860048036038101906100d39190610837565b6102b4565b6040516100e5919061088f565b60405180910390f35b6100f66102bb565b60405161010391906108b7565b60405180910390f35b610126600480360381019061012191906108d0565b6102c4565b604051610133919061088f565b60405180910390f35b6101446102cc565b604051610151919061093b565b60405180910390f35b610174600480360381019061016f9190610837565b6102d4565b005b610190600480360381019061018b9190610954565b6102e2565b60405161019d91906108b7565b60405180910390f35b6101ae6102f3565b6040516101bb9190610786565b60405180910390f35b6101de60048036038101906101d99190610837565b610383565b6040516101eb919061088f565b60405180910390f35b61020e6004803603810190610209919061097f565b61038a565b60405161021b91906108b7565b60405180910390f35b606060038054610233906109ea565b80601f016020809104026020016040519081016040528092919081815260200182805461025f906109ea565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f92915050565b5f600254905090565b5f9392505050565b5f6012905090565b6102de828261039d565b5050565b5f6102ec8261041c565b9050919050565b606060048054610302906109ea565b80601f016020809104026020016040519081016040528092919081815260200182805461032e906109ea565b80156103795780601f1061035057610100808354040283529160200191610379565b820191905f5260205f20905b81548152906001019060200180831161035c57829003601f168201915b5050505050905090565b5f92915050565b5f6103958383610461565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361040d575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104049190610a29565b60405180910390fd5b6104185f83836104e3565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610533578060025f8282546105279190610a6f565b92505081905550610601565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156105bc578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016105b393929190610aa2565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610648578060025f8282540392505081905550610692565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106ef91906108b7565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610733578082015181840152602081019050610718565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610758826106fc565b6107628185610706565b9350610772818560208601610716565b61077b8161073e565b840191505092915050565b5f6020820190508181035f83015261079e818461074e565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6107d3826107aa565b9050919050565b6107e3816107c9565b81146107ed575f80fd5b50565b5f813590506107fe816107da565b92915050565b5f819050919050565b61081681610804565b8114610820575f80fd5b50565b5f813590506108318161080d565b92915050565b5f806040838503121561084d5761084c6107a6565b5b5f61085a858286016107f0565b925050602061086b85828601610823565b9150509250929050565b5f8115159050919050565b61088981610875565b82525050565b5f6020820190506108a25f830184610880565b92915050565b6108b181610804565b82525050565b5f6020820190506108ca5f8301846108a8565b92915050565b5f805f606084860312156108e7576108e66107a6565b5b5f6108f4868287016107f0565b9350506020610905868287016107f0565b925050604061091686828701610823565b9150509250925092565b5f60ff82169050919050565b61093581610920565b82525050565b5f60208201905061094e5f83018461092c565b92915050565b5f60208284031215610969576109686107a6565b5b5f610976848285016107f0565b91505092915050565b5f8060408385031215610995576109946107a6565b5b5f6109a2858286016107f0565b92505060206109b3858286016107f0565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610a0157607f821691505b602082108103610a1457610a136109bd565b5b50919050565b610a23816107c9565b82525050565b5f602082019050610a3c5f830184610a1a565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610a7982610804565b9150610a8483610804565b9250828201905080821115610a9c57610a9b610a42565b5b92915050565b5f606082019050610ab55f830186610a1a565b610ac260208301856108a8565b610acf60408301846108a8565b94935050505056fea2646970667358221220f1cadaf5ef8bc913a1b43b93786c010029633a1d4f32f66e979fa3fb47f1e9cc64736f6c63430008150033")] + // SPDX-License-Identifier: MIT + contract ERC20ReturnFalseMock is ERC20 { + constructor() ERC20("ERC20ReturnFalseMock", "RFM") {} + + function approve(address, uint256) public pure override returns (bool) { + return false; + } + + function transfer(address, uint256) public pure override returns (bool) { + return false; + } + + function transferFrom(address, address, uint256) public pure override returns (bool) { + return false; + } + + function balanceOf(address account) public override view returns (uint256) { + return super.balanceOf(account); + } + + function mint(address account, uint256 value) public { + super._mint(account, value); + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return super.allowance(owner, spender); + } + } +} + +pub async fn deploy(wallet: &Wallet) -> eyre::Result
{ + // Deploy the contract. + let contract = ERC20ReturnFalseMock::deploy(wallet).await?; + Ok(*contract.address()) +} diff --git a/examples/safe-erc20/tests/mock/mod.rs b/examples/safe-erc20/tests/mock/mod.rs index 8f3777f6..c8c60bd7 100644 --- a/examples/safe-erc20/tests/mock/mod.rs +++ b/examples/safe-erc20/tests/mock/mod.rs @@ -1 +1,4 @@ pub mod erc20; +pub mod erc20_force_approve; +pub mod erc20_no_return; +pub mod erc20_return_false; From bcf137422c67c9148f74af8920344451a66cf9e0 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 24 Sep 2024 13:11:16 +0200 Subject: [PATCH 29/77] Simplify SafeErc20Example --- examples/safe-erc20/src/lib.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/examples/safe-erc20/src/lib.rs b/examples/safe-erc20/src/lib.rs index d2a66e77..54049912 100644 --- a/examples/safe-erc20/src/lib.rs +++ b/examples/safe-erc20/src/lib.rs @@ -1,8 +1,7 @@ #![cfg_attr(not(test), no_main, no_std)] extern crate alloc; -use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::token::erc20::utils::safe_erc20::{Error, SafeErc20}; +use openzeppelin_stylus::token::erc20::utils::safe_erc20::SafeErc20; use stylus_sdk::prelude::{entrypoint, public, sol_storage}; sol_storage! { @@ -15,15 +14,4 @@ sol_storage! { #[public] #[inherit(SafeErc20)] -impl SafeErc20Example { - // Add token minting feature. - pub fn safe_transfer( - &mut self, - token: Address, - to: Address, - value: U256, - ) -> Result<(), Error> { - self.safe_erc20.safe_transfer(token, to, value)?; - Ok(()) - } -} +impl SafeErc20Example {} From 29ea3b4e7cc4899bd8523ff4e3015f14bd20fb5e Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 25 Sep 2024 09:22:24 +0200 Subject: [PATCH 30/77] Extract has_no_code tests + Create the rest of has_no_code tests --- .../tests/{safe-erc20.rs => erc20.rs} | 56 ------- examples/safe-erc20/tests/has_no_code.rs | 144 ++++++++++++++++++ scripts/e2e-tests.sh | 2 +- 3 files changed, 145 insertions(+), 57 deletions(-) rename examples/safe-erc20/tests/{safe-erc20.rs => erc20.rs} (75%) create mode 100644 examples/safe-erc20/tests/has_no_code.rs diff --git a/examples/safe-erc20/tests/safe-erc20.rs b/examples/safe-erc20/tests/erc20.rs similarity index 75% rename from examples/safe-erc20/tests/safe-erc20.rs rename to examples/safe-erc20/tests/erc20.rs index 8fdd2070..db5e5019 100644 --- a/examples/safe-erc20/tests/safe-erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -52,33 +52,6 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { Ok(()) } -#[e2e::test] -async fn safe_transfer_rejects_with_eoa_as_token( - alice: Account, - bob: Account, - has_no_code: Account, -) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); - let bob_addr = bob.address(); - let has_no_code_addr = has_no_code.address(); - - let value = uint!(1_U256); - - let err = send!(safe_erc20_mock_alice.safeTransfer( - has_no_code_addr, - bob_addr, - value - )) - .expect_err("should not be able to invoke 'transfer' on EOA"); - assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: has_no_code_addr - })); - - Ok(()) -} - #[e2e::test] async fn safe_transfer_rejects_on_error( alice: Account, @@ -148,35 +121,6 @@ async fn safe_transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { Ok(()) } -#[e2e::test] -async fn safe_transfer_from_rejects_with_eoa_as_token( - alice: Account, - bob: Account, - has_no_code: Account, -) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); - let alice_addr = alice.address(); - let bob_addr = bob.address(); - let has_no_code_addr = has_no_code.address(); - - let value = uint!(1_U256); - - let err = send!(safe_erc20_mock_alice.safeTransferFrom( - has_no_code_addr, - alice_addr, - bob_addr, - value - )) - .expect_err("should not be able to invoke 'transferFrom' on EOA"); - assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: has_no_code_addr - })); - - Ok(()) -} - #[e2e::test] async fn safe_transfer_from_rejects_on_error( alice: Account, diff --git a/examples/safe-erc20/tests/has_no_code.rs b/examples/safe-erc20/tests/has_no_code.rs new file mode 100644 index 00000000..625346b2 --- /dev/null +++ b/examples/safe-erc20/tests/has_no_code.rs @@ -0,0 +1,144 @@ +#![cfg(feature = "e2e")] + +use alloy::primitives::{uint, U256}; +use e2e::{send, Account, ReceiptExt, Revert}; + +use abi::SafeErc20; + +mod abi; +mod mock; + +#[e2e::test] +async fn reverts_on_transfer( + alice: Account, + bob: Account, + has_no_code: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + let has_no_code_addr = has_no_code.address(); + + let value = uint!(1_U256); + + let err = send!(safe_erc20_mock_alice.safeTransfer( + has_no_code_addr, + bob_addr, + value + )) + .expect_err("should not be able to invoke 'transfer' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: has_no_code_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_transfer_from( + alice: Account, + bob: Account, + has_no_code: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let has_no_code_addr = has_no_code.address(); + + let value = uint!(1_U256); + + let err = send!(safe_erc20_mock_alice.safeTransferFrom( + has_no_code_addr, + alice_addr, + bob_addr, + value + )) + .expect_err("should not be able to invoke 'transferFrom' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: has_no_code_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_increase_allowance( + alice: Account, + bob: Account, + has_no_code: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + let has_no_code_addr = has_no_code.address(); + + let value = uint!(1_U256); + + let err = send!(safe_erc20_mock_alice.safeIncreaseAllowance( + has_no_code_addr, + bob_addr, + value + )) + .expect_err("should not be able to invoke 'increaseAllowance' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: has_no_code_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_decrease_allowance( + alice: Account, + bob: Account, + has_no_code: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + let has_no_code_addr = has_no_code.address(); + + let requested_descrease = uint!(1_U256); + + let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + has_no_code_addr, + bob_addr, + requested_descrease + )) + .expect_err("should not be able to invoke 'decreaseAllowance' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: has_no_code_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_force_approve( + alice: Account, + bob: Account, + has_no_code: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + let has_no_code_addr = has_no_code.address(); + + let err = send!(safe_erc20_mock_alice.forceApprove( + has_no_code_addr, + bob_addr, + U256::ZERO + )) + .expect_err("should not be able to invoke 'forceApprove' on EOA"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: has_no_code_addr + })); + + Ok(()) +} diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index dd5044aa..56d3644d 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example --test safe-erc20 +cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example From 423743b3e4c407f6bde1a698945da35c34b5f3de Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 25 Sep 2024 09:43:54 +0200 Subject: [PATCH 31/77] Revert to using low level call --- contracts/src/token/erc20/utils/safe_erc20.rs | 124 +++++++++--------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 13e135ef..8f182fca 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -1,9 +1,19 @@ //! Wrappers around ERC-20 operations that throw on failure. +use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use alloy_sol_types::sol; +use alloy_sol_types::{ + sol, + sol_data::{Address as SOLAddress, Uint}, + SolType, +}; use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; -use stylus_sdk::{call::Call, storage::TopLevelStorage, types::AddressVM}; +use stylus_sdk::{ + call::{call, Call}, + function_selector, + storage::TopLevelStorage, + types::AddressVM, +}; use crate::token::erc20; @@ -30,27 +40,6 @@ pub enum Error { SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } -sol_interface! { - /// Interface of the ERC-20 standard as defined in the ERC. - interface IERC20 { - /// Moves a `value` amount of tokens from the caller's account to `to`. - /// - /// Returns a boolean value indicating whether the operation succeeded. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) external returns (bool); - - /// Moves a `value` amount of tokens from `from` to `to` using the - /// allowance mechanism. `value` is then deducted from the caller's - /// allowance. - /// - /// Returns a boolean value indicating whether the operation succeeded. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) external returns (bool); - } -} - sol_storage! { /// Wrappers around ERC-20 operations that throw on failure (when the token /// contract returns false). Tokens that return no value (and instead revert or @@ -78,25 +67,15 @@ impl SafeErc20 { to: Address, value: U256, ) -> Result<(), Error> { - let erc20 = IERC20::new(token); - let call = Call::new_in(self); - - match erc20.transfer(call, to, value) { - Ok(data) => { - if data && !Address::has_code(&token) { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )); - } - } - Err(_) => { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )) - } - } - - Ok(()) + type TransferType = (SOLAddress, Uint<256>); + let tx_data = (to, value); + let data = TransferType::abi_encode_params(&tx_data); + let hashed_function_selector = + function_selector!("transfer", Address, U256); + // Combine function selector and input data (use abi_packed way) + let calldata = [&hashed_function_selector[..4], &data].concat(); + + self.call_optional_return(token, calldata) } /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the @@ -108,25 +87,15 @@ impl SafeErc20 { to: Address, value: U256, ) -> Result<(), Error> { - let erc20 = IERC20::new(token); - let call = Call::new_in(self); - - match erc20.transfer_from(call, from, to, value) { - Ok(data) => { - if data && !Address::has_code(&token) { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )); - } - } - Err(_) => { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )) - } - } - - Ok(()) + type TransferType = (SOLAddress, SOLAddress, Uint<256>); + let tx_data = (from, to, value); + let data = TransferType::abi_encode_params(&tx_data); + let hashed_function_selector = + function_selector!("transferFrom", Address, Address, U256); + // Combine function selector and input data (use abi_packed way) + let calldata = [&hashed_function_selector[..4], &data].concat(); + + self.call_optional_return(token, calldata) } /// Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, @@ -163,3 +132,36 @@ impl SafeErc20 { todo!() } } + +impl SafeErc20 { + /// Imitates a Solidity high-level call (i.e. a regular function call to a + /// contract), relaxing the requirement on the return value: the return + /// value is optional (but if data is returned, it must not be false). + /// @param token The token targeted by the call. + /// @param data The call data (encoded using abi.encode or one of its + /// variants). + /// + /// This is a variant of {_callOptionalReturnBool} that reverts if call + /// fails to meet the requirements. + fn call_optional_return( + &self, + token: Address, + data: Vec, + ) -> Result<(), Error> { + match call(Call::new(), token, data.as_slice()) { + Ok(data) => { + if data.is_empty() && !Address::has_code(&token) { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token }, + )); + } + } + Err(_) => { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token }, + )) + } + } + Ok(()) + } +} From d47db5ab2b988cbb15070c3dd6d437aebbd06ab9 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 25 Sep 2024 20:50:25 +0200 Subject: [PATCH 32/77] Implement forceApprove --- contracts/src/token/erc20/utils/safe_erc20.rs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 8f182fca..3e4023e1 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -129,7 +129,25 @@ impl SafeErc20 { spender: Address, value: U256, ) -> Result<(), Error> { - todo!() + type TransferType = (SOLAddress, Uint<256>); + let tx_data = (spender, value); + let data = TransferType::abi_encode_params(&tx_data); + let hashed_function_selector = + function_selector!("approve", Address, U256); + // Combine function selector and input data (use abi_packed way) + let approve_calldata = [&hashed_function_selector[..4], &data].concat(); + + if self.call_optional_return(token, approve_calldata.clone()).is_err() { + let tx_data = (spender, U256::ZERO); + let data = TransferType::abi_encode_params(&tx_data); + self.call_optional_return( + token, + [&hashed_function_selector[..4], &data].concat(), + )?; + self.call_optional_return(token, approve_calldata)?; + } + + Ok(()) } } From 2b79af1fd73370bf93a3d6a6d62dbe958f8eef92 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 11:10:13 +0200 Subject: [PATCH 33/77] Implement safe_increase_allowance --- contracts/src/token/erc20/utils/safe_erc20.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 3e4023e1..fb033552 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -10,6 +10,7 @@ use alloy_sol_types::{ use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; use stylus_sdk::{ call::{call, Call}, + contract::address, function_selector, storage::TopLevelStorage, types::AddressVM, @@ -40,6 +41,18 @@ pub enum Error { SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } +sol_interface! { + /// Interface of the ERC-20 standard as defined in the ERC. + interface IERC20 { + /// Returns the remaining number of tokens that `spender` will be + /// allowed to spend on behalf of `owner` through {transferFrom}. This is + /// zero by default. + /// + /// This value changes when {approve} or {transferFrom} are called. + function allowance(address owner, address spender) external view returns (uint256); + } +} + sol_storage! { /// Wrappers around ERC-20 operations that throw on failure (when the token /// contract returns false). Tokens that return no value (and instead revert or @@ -106,7 +119,22 @@ impl SafeErc20 { spender: Address, value: U256, ) -> Result<(), Error> { - todo!() + let erc20 = IERC20::new(token); + let call = Call::new_in(self); + match erc20.allowance(call, address(), spender) { + Ok(old_allowance) => { + return self.force_approve( + token, + spender, + old_allowance + value, + ) + } + Err(_) => { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token }, + )) + } + } } /// Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no From 306ec74a3794f3f29fcbdbefd4ad152bb16844c1 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 11:29:55 +0200 Subject: [PATCH 34/77] Refactor allowance-related fns --- contracts/src/token/erc20/utils/safe_erc20.rs | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index fb033552..86898ea0 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -121,20 +121,12 @@ impl SafeErc20 { ) -> Result<(), Error> { let erc20 = IERC20::new(token); let call = Call::new_in(self); - match erc20.allowance(call, address(), spender) { - Ok(old_allowance) => { - return self.force_approve( - token, - spender, - old_allowance + value, - ) - } - Err(_) => { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )) - } - } + let old_allowance = erc20.allowance(call, address(), spender).or( + Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { + token, + })), + )?; + self.force_approve(token, spender, old_allowance + value) } /// Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no @@ -165,17 +157,16 @@ impl SafeErc20 { // Combine function selector and input data (use abi_packed way) let approve_calldata = [&hashed_function_selector[..4], &data].concat(); - if self.call_optional_return(token, approve_calldata.clone()).is_err() { - let tx_data = (spender, U256::ZERO); - let data = TransferType::abi_encode_params(&tx_data); - self.call_optional_return( - token, - [&hashed_function_selector[..4], &data].concat(), - )?; - self.call_optional_return(token, approve_calldata)?; - } - - Ok(()) + self.call_optional_return(token, approve_calldata.clone()) + .or({ + let tx_data = (spender, U256::ZERO); + let data = TransferType::abi_encode_params(&tx_data); + self.call_optional_return( + token, + [&hashed_function_selector[..4], &data].concat(), + ) + }) + .and(self.call_optional_return(token, approve_calldata)) } } From b88ee725f71f146ca40002177f0cc8b1df3ee11c Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 11:51:51 +0200 Subject: [PATCH 35/77] refactor --- contracts/src/token/erc20/utils/safe_erc20.rs | 78 +++++++++---------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 86898ea0..e19dec8c 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -88,7 +88,7 @@ impl SafeErc20 { // Combine function selector and input data (use abi_packed way) let calldata = [&hashed_function_selector[..4], &data].concat(); - self.call_optional_return(token, calldata) + self.call_optional_return(token, &calldata) } /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the @@ -108,7 +108,7 @@ impl SafeErc20 { // Combine function selector and input data (use abi_packed way) let calldata = [&hashed_function_selector[..4], &data].concat(); - self.call_optional_return(token, calldata) + self.call_optional_return(token, &calldata) } /// Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, @@ -149,56 +149,50 @@ impl SafeErc20 { spender: Address, value: U256, ) -> Result<(), Error> { - type TransferType = (SOLAddress, Uint<256>); - let tx_data = (spender, value); - let data = TransferType::abi_encode_params(&tx_data); - let hashed_function_selector = - function_selector!("approve", Address, U256); - // Combine function selector and input data (use abi_packed way) - let approve_calldata = [&hashed_function_selector[..4], &data].concat(); + let selector = function_selector!("approve", Address, U256); + + // Helper function to construct calldata + fn build_approve_calldata( + spender: Address, + value: U256, + selector: &[u8], + ) -> Vec { + type ApproveArgs = (SOLAddress, Uint<256>); + let args = (spender, value); + let encoded_args = ApproveArgs::abi_encode_params(&args); + [&selector[..4], &encoded_args].concat() + } - self.call_optional_return(token, approve_calldata.clone()) - .or({ - let tx_data = (spender, U256::ZERO); - let data = TransferType::abi_encode_params(&tx_data); - self.call_optional_return( - token, - [&hashed_function_selector[..4], &data].concat(), - ) - }) - .and(self.call_optional_return(token, approve_calldata)) + // Try performing the approval with the desired value + let approve_data = build_approve_calldata(spender, value, &selector); + if self.call_optional_return(token, &approve_data).is_ok() { + return Ok(()); + } + + // If that fails, reset allowance to zero, then retry the desired approval + let reset_data = build_approve_calldata(spender, U256::ZERO, &selector); + self.call_optional_return(token, &reset_data)?; + self.call_optional_return(token, &approve_data)?; + + Ok(()) } } impl SafeErc20 { - /// Imitates a Solidity high-level call (i.e. a regular function call to a - /// contract), relaxing the requirement on the return value: the return - /// value is optional (but if data is returned, it must not be false). - /// @param token The token targeted by the call. - /// @param data The call data (encoded using abi.encode or one of its - /// variants). - /// - /// This is a variant of {_callOptionalReturnBool} that reverts if call - /// fails to meet the requirements. + /// Imitates a Solidity high-level call, relaxing the requirement on the return value: + /// if data is returned, it must not be `false`, otherwise calls are assumed to be successful. fn call_optional_return( &self, token: Address, - data: Vec, + data: &[u8], ) -> Result<(), Error> { - match call(Call::new(), token, data.as_slice()) { - Ok(data) => { - if data.is_empty() && !Address::has_code(&token) { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )); - } - } - Err(_) => { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, - )) + match call(Call::new(), token, data) { + Ok(data) if !data.is_empty() || Address::has_code(&token) => Ok(()), + _ => { + Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { + token, + })) } } - Ok(()) } } From 19f3448b2a1d8538cf0f9e22f3f7d13ce8872837 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 11:52:08 +0200 Subject: [PATCH 36/77] IMplement safe_decrease_allowance --- contracts/src/token/erc20/utils/safe_erc20.rs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index e19dec8c..d22d0cce 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -137,7 +137,26 @@ impl SafeErc20 { spender: Address, requested_decrease: U256, ) -> Result<(), Error> { - todo!() + let erc20 = IERC20::new(token); + let call = Call::new_in(self); + let current_allowance = + erc20.allowance(call, address(), spender).or_else(|_| { + Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { + token, + })) + })?; + + if current_allowance < requested_decrease { + return Err(Error::SafeErc20FailedOperation( + SafeErc20FailedOperation { token }, + )); + } + + self.force_approve( + token, + spender, + current_allowance - requested_decrease, + ) } /// Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, From a7dd96de46a25f288d6294c79be4f3c76b12f448 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 12:07:39 +0200 Subject: [PATCH 37/77] Refactor arg encoding --- contracts/src/token/erc20/utils/safe_erc20.rs | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index d22d0cce..90feeb67 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -2,11 +2,7 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use alloy_sol_types::{ - sol, - sol_data::{Address as SOLAddress, Uint}, - SolType, -}; +use alloy_sol_types::{sol, SolValue}; use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; use stylus_sdk::{ call::{call, Call}, @@ -80,15 +76,12 @@ impl SafeErc20 { to: Address, value: U256, ) -> Result<(), Error> { - type TransferType = (SOLAddress, Uint<256>); - let tx_data = (to, value); - let data = TransferType::abi_encode_params(&tx_data); - let hashed_function_selector = - function_selector!("transfer", Address, U256); + let encoded_args = (to, value).abi_encode_params(); + let selector = function_selector!("transfer", Address, U256); // Combine function selector and input data (use abi_packed way) - let calldata = [&hashed_function_selector[..4], &data].concat(); + let data = [&selector[..4], &encoded_args].concat(); - self.call_optional_return(token, &calldata) + self.call_optional_return(token, &data) } /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the @@ -100,15 +93,13 @@ impl SafeErc20 { to: Address, value: U256, ) -> Result<(), Error> { - type TransferType = (SOLAddress, SOLAddress, Uint<256>); - let tx_data = (from, to, value); - let data = TransferType::abi_encode_params(&tx_data); - let hashed_function_selector = + let encoded_args = (from, to, value).abi_encode_params(); + let selector = function_selector!("transferFrom", Address, Address, U256); // Combine function selector and input data (use abi_packed way) - let calldata = [&hashed_function_selector[..4], &data].concat(); + let data = [&selector[..4], &encoded_args].concat(); - self.call_optional_return(token, &calldata) + self.call_optional_return(token, &data) } /// Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, @@ -140,7 +131,7 @@ impl SafeErc20 { let erc20 = IERC20::new(token); let call = Call::new_in(self); let current_allowance = - erc20.allowance(call, address(), spender).or_else(|_| { + erc20.allowance(call, address(), spender).or({ Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { token, })) @@ -176,9 +167,7 @@ impl SafeErc20 { value: U256, selector: &[u8], ) -> Vec { - type ApproveArgs = (SOLAddress, Uint<256>); - let args = (spender, value); - let encoded_args = ApproveArgs::abi_encode_params(&args); + let encoded_args = (spender, value).abi_encode_params(); [&selector[..4], &encoded_args].concat() } From bc7dc15dc5d7b8565c7957bc3f309b4d8709c00a Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 12:11:25 +0200 Subject: [PATCH 38/77] Fail with SafeErc20FailedDecreaseAllowance on failing to decrease allowance --- contracts/src/token/erc20/utils/safe_erc20.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 90feeb67..c16afda6 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -138,8 +138,12 @@ impl SafeErc20 { })?; if current_allowance < requested_decrease { - return Err(Error::SafeErc20FailedOperation( - SafeErc20FailedOperation { token }, + return Err(Error::SafeErc20FailedDecreaseAllowance( + SafeErc20FailedDecreaseAllowance { + spender, + currentAllowance: current_allowance, + requestedDecrease: requested_decrease, + }, )); } From d3bb6ecf688cbc774bdd049c135e58a161cebd45 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 12:41:37 +0200 Subject: [PATCH 39/77] Implement return_false tests --- .../safe-erc20/tests/always_returns_false.rs | 134 ++++++++++++++++++ .../tests/mock/erc20_return_false.rs | 8 +- 2 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 examples/safe-erc20/tests/always_returns_false.rs diff --git a/examples/safe-erc20/tests/always_returns_false.rs b/examples/safe-erc20/tests/always_returns_false.rs new file mode 100644 index 00000000..e14fc784 --- /dev/null +++ b/examples/safe-erc20/tests/always_returns_false.rs @@ -0,0 +1,134 @@ +#![cfg(feature = "e2e")] + +use alloy::primitives::U256; +use e2e::{send, Account, ReceiptExt, Revert}; + +use abi::SafeErc20; +use mock::erc20_return_false; + +mod abi; +mod mock; + +#[e2e::test] +async fn reverts_on_transfer(alice: Account, bob: Account) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; + + let err = send!(safe_erc20_mock_alice.safeTransfer( + erc20_address, + bob_addr, + U256::ZERO + )) + .expect_err("should not be able to succeed on 'transfer'"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; + + let err = send!(safe_erc20_mock_alice.safeTransferFrom( + erc20_address, + alice_addr, + bob_addr, + U256::ZERO + )) + .expect_err("should not be able to succeed on 'transferFrom'"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_increase_allowance( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; + + let err = send!(safe_erc20_mock_alice.safeIncreaseAllowance( + erc20_address, + bob_addr, + U256::ZERO + )) + .expect_err("should not be able to succeed on 'increaseAllowance'"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_decrease_allowance( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; + + let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + bob_addr, + U256::ZERO + )) + .expect_err("should not be able to succeed on 'decreaseAllowance'"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} + +#[e2e::test] +async fn reverts_on_force_approve( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; + + let err = send!(safe_erc20_mock_alice.forceApprove( + erc20_address, + bob_addr, + U256::ZERO + )) + .expect_err("should not be able to succeed on 'forceApprove'"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + Ok(()) +} diff --git a/examples/safe-erc20/tests/mock/erc20_return_false.rs b/examples/safe-erc20/tests/mock/erc20_return_false.rs index 70059496..809c8062 100644 --- a/examples/safe-erc20/tests/mock/erc20_return_false.rs +++ b/examples/safe-erc20/tests/mock/erc20_return_false.rs @@ -6,20 +6,20 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601481526020017f455243323052657475726e46616c73654d6f636b0000000000000000000000008152506040518060400160405280600381526020017f52464d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610b0d80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610786565b60405180910390f35b6100d860048036038101906100d39190610837565b6102b4565b6040516100e5919061088f565b60405180910390f35b6100f66102bb565b60405161010391906108b7565b60405180910390f35b610126600480360381019061012191906108d0565b6102c4565b604051610133919061088f565b60405180910390f35b6101446102cc565b604051610151919061093b565b60405180910390f35b610174600480360381019061016f9190610837565b6102d4565b005b610190600480360381019061018b9190610954565b6102e2565b60405161019d91906108b7565b60405180910390f35b6101ae6102f3565b6040516101bb9190610786565b60405180910390f35b6101de60048036038101906101d99190610837565b610383565b6040516101eb919061088f565b60405180910390f35b61020e6004803603810190610209919061097f565b61038a565b60405161021b91906108b7565b60405180910390f35b606060038054610233906109ea565b80601f016020809104026020016040519081016040528092919081815260200182805461025f906109ea565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f92915050565b5f600254905090565b5f9392505050565b5f6012905090565b6102de828261039d565b5050565b5f6102ec8261041c565b9050919050565b606060048054610302906109ea565b80601f016020809104026020016040519081016040528092919081815260200182805461032e906109ea565b80156103795780601f1061035057610100808354040283529160200191610379565b820191905f5260205f20905b81548152906001019060200180831161035c57829003601f168201915b5050505050905090565b5f92915050565b5f6103958383610461565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361040d575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104049190610a29565b60405180910390fd5b6104185f83836104e3565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610533578060025f8282546105279190610a6f565b92505081905550610601565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156105bc578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016105b393929190610aa2565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610648578060025f8282540392505081905550610692565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106ef91906108b7565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610733578082015181840152602081019050610718565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610758826106fc565b6107628185610706565b9350610772818560208601610716565b61077b8161073e565b840191505092915050565b5f6020820190508181035f83015261079e818461074e565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6107d3826107aa565b9050919050565b6107e3816107c9565b81146107ed575f80fd5b50565b5f813590506107fe816107da565b92915050565b5f819050919050565b61081681610804565b8114610820575f80fd5b50565b5f813590506108318161080d565b92915050565b5f806040838503121561084d5761084c6107a6565b5b5f61085a858286016107f0565b925050602061086b85828601610823565b9150509250929050565b5f8115159050919050565b61088981610875565b82525050565b5f6020820190506108a25f830184610880565b92915050565b6108b181610804565b82525050565b5f6020820190506108ca5f8301846108a8565b92915050565b5f805f606084860312156108e7576108e66107a6565b5b5f6108f4868287016107f0565b9350506020610905868287016107f0565b925050604061091686828701610823565b9150509250925092565b5f60ff82169050919050565b61093581610920565b82525050565b5f60208201905061094e5f83018461092c565b92915050565b5f60208284031215610969576109686107a6565b5b5f610976848285016107f0565b91505092915050565b5f8060408385031215610995576109946107a6565b5b5f6109a2858286016107f0565b92505060206109b3858286016107f0565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610a0157607f821691505b602082108103610a1457610a136109bd565b5b50919050565b610a23816107c9565b82525050565b5f602082019050610a3c5f830184610a1a565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610a7982610804565b9150610a8483610804565b9250828201905080821115610a9c57610a9b610a42565b5b92915050565b5f606082019050610ab55f830186610a1a565b610ac260208301856108a8565b610acf60408301846108a8565b94935050505056fea2646970667358221220f1cadaf5ef8bc913a1b43b93786c010029633a1d4f32f66e979fa3fb47f1e9cc64736f6c63430008150033")] + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601481526020017f455243323052657475726e46616c73654d6f636b0000000000000000000000008152506040518060400160405280600381526020017f52464d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610b0d80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610786565b60405180910390f35b6100d860048036038101906100d39190610837565b6102b4565b6040516100e5919061088f565b60405180910390f35b6100f66102bb565b60405161010391906108b7565b60405180910390f35b610126600480360381019061012191906108d0565b6102c4565b604051610133919061088f565b60405180910390f35b6101446102cc565b604051610151919061093b565b60405180910390f35b610174600480360381019061016f9190610837565b6102d4565b005b610190600480360381019061018b9190610954565b6102e2565b60405161019d91906108b7565b60405180910390f35b6101ae6102f3565b6040516101bb9190610786565b60405180910390f35b6101de60048036038101906101d99190610837565b610383565b6040516101eb919061088f565b60405180910390f35b61020e6004803603810190610209919061097f565b61038a565b60405161021b91906108b7565b60405180910390f35b606060038054610233906109ea565b80601f016020809104026020016040519081016040528092919081815260200182805461025f906109ea565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f92915050565b5f600254905090565b5f9392505050565b5f6012905090565b6102de828261039d565b5050565b5f6102ec8261041c565b9050919050565b606060048054610302906109ea565b80601f016020809104026020016040519081016040528092919081815260200182805461032e906109ea565b80156103795780601f1061035057610100808354040283529160200191610379565b820191905f5260205f20905b81548152906001019060200180831161035c57829003601f168201915b5050505050905090565b5f92915050565b5f6103958383610461565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361040d575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104049190610a29565b60405180910390fd5b6104185f83836104e3565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610533578060025f8282546105279190610a6f565b92505081905550610601565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156105bc578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016105b393929190610aa2565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610648578060025f8282540392505081905550610692565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106ef91906108b7565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610733578082015181840152602081019050610718565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610758826106fc565b6107628185610706565b9350610772818560208601610716565b61077b8161073e565b840191505092915050565b5f6020820190508181035f83015261079e818461074e565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6107d3826107aa565b9050919050565b6107e3816107c9565b81146107ed575f80fd5b50565b5f813590506107fe816107da565b92915050565b5f819050919050565b61081681610804565b8114610820575f80fd5b50565b5f813590506108318161080d565b92915050565b5f806040838503121561084d5761084c6107a6565b5b5f61085a858286016107f0565b925050602061086b85828601610823565b9150509250929050565b5f8115159050919050565b61088981610875565b82525050565b5f6020820190506108a25f830184610880565b92915050565b6108b181610804565b82525050565b5f6020820190506108ca5f8301846108a8565b92915050565b5f805f606084860312156108e7576108e66107a6565b5b5f6108f4868287016107f0565b9350506020610905868287016107f0565b925050604061091686828701610823565b9150509250925092565b5f60ff82169050919050565b61093581610920565b82525050565b5f60208201905061094e5f83018461092c565b92915050565b5f60208284031215610969576109686107a6565b5b5f610976848285016107f0565b91505092915050565b5f8060408385031215610995576109946107a6565b5b5f6109a2858286016107f0565b92505060206109b3858286016107f0565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610a0157607f821691505b602082108103610a1457610a136109bd565b5b50919050565b610a23816107c9565b82525050565b5f602082019050610a3c5f830184610a1a565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610a7982610804565b9150610a8483610804565b9250828201905080821115610a9c57610a9b610a42565b5b92915050565b5f606082019050610ab55f830186610a1a565b610ac260208301856108a8565b610acf60408301846108a8565b94935050505056fea26469706673582212204aac6dd6254b82f37f30add0ed2937474eced0bafc505b611f66b99ebe39999e64736f6c63430008150033")] // SPDX-License-Identifier: MIT contract ERC20ReturnFalseMock is ERC20 { constructor() ERC20("ERC20ReturnFalseMock", "RFM") {} - function approve(address, uint256) public pure override returns (bool) { + function approve(address, uint256) public override returns (bool) { return false; } - function transfer(address, uint256) public pure override returns (bool) { + function transfer(address, uint256) public override returns (bool) { return false; } - function transferFrom(address, address, uint256) public pure override returns (bool) { + function transferFrom(address, address, uint256) public override returns (bool) { return false; } From 8dd894e4f25b4c0e29339d86efb90d60daf88770 Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 12:53:37 +0200 Subject: [PATCH 40/77] Check whether return is true in call_optional_return --- contracts/src/token/erc20/utils/safe_erc20.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index c16afda6..9a06a1f4 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -199,7 +199,15 @@ impl SafeErc20 { data: &[u8], ) -> Result<(), Error> { match call(Call::new(), token, data) { - Ok(data) if !data.is_empty() || Address::has_code(&token) => Ok(()), + Ok(data) + if !(data.is_empty() || !Address::has_code(&token)) + && data[data.len() - 1] == 0x01 + && data[..data.len() - 1] + .iter() + .all(|&byte| byte == 0x00) => + { + Ok(()) + } _ => { Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { token, From 8739ec6260167de8bef5e0a8db18e0a3d7a0daeb Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 16:15:02 +0200 Subject: [PATCH 41/77] Use RawCall to limit the amount of returned data --- contracts/src/token/erc20/utils/safe_erc20.rs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 9a06a1f4..7940d8aa 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -5,8 +5,9 @@ use alloy_primitives::{Address, U256}; use alloy_sol_types::{sol, SolValue}; use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; use stylus_sdk::{ - call::{call, Call}, + call::{Call, RawCall}, contract::address, + evm::gas_left, function_selector, storage::TopLevelStorage, types::AddressVM, @@ -198,13 +199,14 @@ impl SafeErc20 { token: Address, data: &[u8], ) -> Result<(), Error> { - match call(Call::new(), token, data) { + match RawCall::new() + .gas(gas_left()) + .limit_return_data(0, 32) + .call(token, data) + { Ok(data) - if !(data.is_empty() || !Address::has_code(&token)) - && data[data.len() - 1] == 0x01 - && data[..data.len() - 1] - .iter() - .all(|&byte| byte == 0x00) => + if !(data.is_empty() && !Address::has_code(&token)) + && encodes_true(&data) => { Ok(()) } @@ -216,3 +218,8 @@ impl SafeErc20 { } } } + +fn encodes_true(data: &[u8]) -> bool { + data[..data.len() - 1].iter().all(|&byte| byte == 0) + && data[data.len() - 1] == 1 +} From 5db49ad50ffa3bf4f712fdd456807c1f839bcc1c Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 30 Sep 2024 16:35:44 +0200 Subject: [PATCH 42/77] Implement tests for USDT approval behavior --- .../tests/mock/erc20_force_approve.rs | 10 +- .../tests/usdt_approval_behavior.rs | 124 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 examples/safe-erc20/tests/usdt_approval_behavior.rs diff --git a/examples/safe-erc20/tests/mock/erc20_force_approve.rs b/examples/safe-erc20/tests/mock/erc20_force_approve.rs index 39a1393b..ccf5c414 100644 --- a/examples/safe-erc20/tests/mock/erc20_force_approve.rs +++ b/examples/safe-erc20/tests/mock/erc20_force_approve.rs @@ -6,7 +6,7 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601581526020017f4552433230466f726365417070726f76654d6f636b00000000000000000000008152506040518060400160405280600381526020017f46414d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610ecb80620003ff5f395ff3fe608060405234801561000f575f80fd5b5060043610610091575f3560e01c8063313ce56711610064578063313ce5671461013157806370a082311461014f57806395d89b411461017f578063a9059cbb1461019d578063dd62ed3e146101cd57610091565b806306fdde0314610095578063095ea7b3146100b357806318160ddd146100e357806323b872dd14610101575b5f80fd5b61009d6101fd565b6040516100aa9190610adc565b60405180910390f35b6100cd60048036038101906100c89190610b8d565b61028d565b6040516100da9190610be5565b60405180910390f35b6100eb6102f5565b6040516100f89190610c0d565b60405180910390f35b61011b60048036038101906101169190610c26565b6102fe565b6040516101289190610be5565b60405180910390f35b61013961032c565b6040516101469190610c91565b60405180910390f35b61016960048036038101906101649190610caa565b610334565b6040516101769190610c0d565b60405180910390f35b610187610379565b6040516101949190610adc565b60405180910390f35b6101b760048036038101906101b29190610b8d565b610409565b6040516101c49190610be5565b60405180910390f35b6101e760048036038101906101e29190610cd5565b61042b565b6040516101f49190610c0d565b60405180910390f35b60606003805461020c90610d40565b80601f016020809104026020016040519081016040528092919081815260200182805461023890610d40565b80156102835780601f1061025a57610100808354040283529160200191610283565b820191905f5260205f20905b81548152906001019060200180831161026657829003601f168201915b5050505050905090565b5f808214806102a457505f6102a2338561042b565b145b6102e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102da90610dba565b60405180910390fd5b6102ed83836104ad565b905092915050565b5f600254905090565b5f806103086104cf565b90506103158582856104d6565b610320858585610568565b60019150509392505050565b5f6012905090565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606004805461038890610d40565b80601f01602080910402602001604051908101604052809291908181526020018280546103b490610d40565b80156103ff5780601f106103d6576101008083540402835291602001916103ff565b820191905f5260205f20905b8154815290600101906020018083116103e257829003601f168201915b5050505050905090565b5f806104136104cf565b9050610420818585610568565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f806104b76104cf565b90506104c4818585610658565b600191505092915050565b5f33905090565b5f6104e1848461042b565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146105625781811015610553578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161054a93929190610de7565b60405180910390fd5b61056184848484035f61066a565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105d8575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016105cf9190610e1c565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610648575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161063f9190610e1c565b60405180910390fd5b610653838383610839565b505050565b610665838383600161066a565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036106da575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016106d19190610e1c565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361074a575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016107419190610e1c565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610833578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161082a9190610c0d565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610889578060025f82825461087d9190610e62565b92505081905550610957565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610912578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161090993929190610de7565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361099e578060025f82825403925050819055506109e8565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610a459190610c0d565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610a89578082015181840152602081019050610a6e565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610aae82610a52565b610ab88185610a5c565b9350610ac8818560208601610a6c565b610ad181610a94565b840191505092915050565b5f6020820190508181035f830152610af48184610aa4565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b2982610b00565b9050919050565b610b3981610b1f565b8114610b43575f80fd5b50565b5f81359050610b5481610b30565b92915050565b5f819050919050565b610b6c81610b5a565b8114610b76575f80fd5b50565b5f81359050610b8781610b63565b92915050565b5f8060408385031215610ba357610ba2610afc565b5b5f610bb085828601610b46565b9250506020610bc185828601610b79565b9150509250929050565b5f8115159050919050565b610bdf81610bcb565b82525050565b5f602082019050610bf85f830184610bd6565b92915050565b610c0781610b5a565b82525050565b5f602082019050610c205f830184610bfe565b92915050565b5f805f60608486031215610c3d57610c3c610afc565b5b5f610c4a86828701610b46565b9350506020610c5b86828701610b46565b9250506040610c6c86828701610b79565b9150509250925092565b5f60ff82169050919050565b610c8b81610c76565b82525050565b5f602082019050610ca45f830184610c82565b92915050565b5f60208284031215610cbf57610cbe610afc565b5b5f610ccc84828501610b46565b91505092915050565b5f8060408385031215610ceb57610cea610afc565b5b5f610cf885828601610b46565b9250506020610d0985828601610b46565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610d5757607f821691505b602082108103610d6a57610d69610d13565b5b50919050565b7f5553445420617070726f76616c206661696c75726500000000000000000000005f82015250565b5f610da4601583610a5c565b9150610daf82610d70565b602082019050919050565b5f6020820190508181035f830152610dd181610d98565b9050919050565b610de181610b1f565b82525050565b5f606082019050610dfa5f830186610dd8565b610e076020830185610bfe565b610e146040830184610bfe565b949350505050565b5f602082019050610e2f5f830184610dd8565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e6c82610b5a565b9150610e7783610b5a565b9250828201905080821115610e8f57610e8e610e35565b5b9291505056fea264697066735822122085f85d85cd9786ae551a5465b306925cc60ff074c4b00744641f465d7439905064736f6c63430008150033")] + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601581526020017f4552433230466f726365417070726f76654d6f636b00000000000000000000008152506040518060400160405280600381526020017f46414d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610f1580620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806370a082311161006457806370a082311461015a5780638483acfe1461018a57806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b26565b60405180910390f35b6100d860048036038101906100d39190610bd7565b6102b4565b6040516100e59190610c2f565b60405180910390f35b6100f661031c565b6040516101039190610c57565b60405180910390f35b61012660048036038101906101219190610c70565b610325565b6040516101339190610c2f565b60405180910390f35b610144610353565b6040516101519190610cdb565b60405180910390f35b610174600480360381019061016f9190610cf4565b61035b565b6040516101819190610c57565b60405180910390f35b6101a4600480360381019061019f9190610c70565b6103a0565b005b6101ae6103b0565b6040516101bb9190610b26565b60405180910390f35b6101de60048036038101906101d99190610bd7565b610440565b6040516101eb9190610c2f565b60405180910390f35b61020e60048036038101906102099190610d1f565b610462565b60405161021b9190610c57565b60405180910390f35b60606003805461023390610d8a565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610d8a565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f808214806102cb57505f6102c93385610462565b145b61030a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161030190610e04565b60405180910390fd5b6103148383610475565b905092915050565b5f600254905090565b5f8061032f610497565b905061033c85828561049e565b610347858585610530565b60019150509392505050565b5f6012905090565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b6103ab838383610620565b505050565b6060600480546103bf90610d8a565b80601f01602080910402602001604051908101604052809291908181526020018280546103eb90610d8a565b80156104365780601f1061040d57610100808354040283529160200191610436565b820191905f5260205f20905b81548152906001019060200180831161041957829003601f168201915b5050505050905090565b5f8061044a610497565b9050610457818585610530565b600191505092915050565b5f61046d8383610632565b905092915050565b5f8061047f610497565b905061048c818585610620565b600191505092915050565b5f33905090565b5f6104a98484610462565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461052a578181101561051b578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161051293929190610e31565b60405180910390fd5b61052984848484035f6106b4565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105a0575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016105979190610e66565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610610575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106079190610e66565b60405180910390fd5b61061b838383610883565b505050565b61062d83838360016106b4565b505050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610724575f6040517fe602df0500000000000000000000000000000000000000000000000000000000815260040161071b9190610e66565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610794575f6040517f94280d6200000000000000000000000000000000000000000000000000000000815260040161078b9190610e66565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550801561087d578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516108749190610c57565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108d3578060025f8282546108c79190610eac565b925050819055506109a1565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205490508181101561095c578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161095393929190610e31565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036109e8578060025f8282540392505081905550610a32565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610a8f9190610c57565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610ad3578082015181840152602081019050610ab8565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610af882610a9c565b610b028185610aa6565b9350610b12818560208601610ab6565b610b1b81610ade565b840191505092915050565b5f6020820190508181035f830152610b3e8184610aee565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b7382610b4a565b9050919050565b610b8381610b69565b8114610b8d575f80fd5b50565b5f81359050610b9e81610b7a565b92915050565b5f819050919050565b610bb681610ba4565b8114610bc0575f80fd5b50565b5f81359050610bd181610bad565b92915050565b5f8060408385031215610bed57610bec610b46565b5b5f610bfa85828601610b90565b9250506020610c0b85828601610bc3565b9150509250929050565b5f8115159050919050565b610c2981610c15565b82525050565b5f602082019050610c425f830184610c20565b92915050565b610c5181610ba4565b82525050565b5f602082019050610c6a5f830184610c48565b92915050565b5f805f60608486031215610c8757610c86610b46565b5b5f610c9486828701610b90565b9350506020610ca586828701610b90565b9250506040610cb686828701610bc3565b9150509250925092565b5f60ff82169050919050565b610cd581610cc0565b82525050565b5f602082019050610cee5f830184610ccc565b92915050565b5f60208284031215610d0957610d08610b46565b5b5f610d1684828501610b90565b91505092915050565b5f8060408385031215610d3557610d34610b46565b5b5f610d4285828601610b90565b9250506020610d5385828601610b90565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610da157607f821691505b602082108103610db457610db3610d5d565b5b50919050565b7f5553445420617070726f76616c206661696c75726500000000000000000000005f82015250565b5f610dee601583610aa6565b9150610df982610dba565b602082019050919050565b5f6020820190508181035f830152610e1b81610de2565b9050919050565b610e2b81610b69565b82525050565b5f606082019050610e445f830186610e22565b610e516020830185610c48565b610e5e6040830184610c48565b949350505050565b5f602082019050610e795f830184610e22565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610eb682610ba4565b9150610ec183610ba4565b9250828201905080821115610ed957610ed8610e7f565b5b9291505056fea26469706673582212204360bd31161a5a21172e203aecf5b49d953f1b1c8a64d9d0e539c9c81467f7b064736f6c63430008150033")] // SPDX-License-Identifier: MIT contract ERC20ForceApproveMock is ERC20 { constructor() ERC20("ERC20ForceApproveMock", "FAM") {} @@ -15,6 +15,14 @@ sol! { require(amount == 0 || allowance(msg.sender, spender) == 0, "USDT approval failure"); return super.approve(spender, amount); } + + function regular_approve(address owner, address spender, uint256 amount) public { + super._approve(owner, spender, amount); + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return super.allowance(owner, spender); + } } } diff --git a/examples/safe-erc20/tests/usdt_approval_behavior.rs b/examples/safe-erc20/tests/usdt_approval_behavior.rs new file mode 100644 index 00000000..efe6306b --- /dev/null +++ b/examples/safe-erc20/tests/usdt_approval_behavior.rs @@ -0,0 +1,124 @@ +#![cfg(feature = "e2e")] + +use alloy::primitives::uint; +use e2e::{receipt, watch, Account, ReceiptExt}; + +use abi::SafeErc20; +use mock::{erc20_force_approve, erc20_force_approve::ERC20ForceApproveMock}; + +mod abi; +mod mock; + +#[e2e::test] +async fn safe_increase_allowance_works( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_force_approve::deploy(&alice.wallet).await?; + let erc20_alice = ERC20ForceApproveMock::new(erc20_address, &alice.wallet); + + let init_approval = uint!(100_U256); + let value = uint!(10_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + bob_addr, + init_approval + )); + + let ERC20ForceApproveMock::allowanceReturn { _0: initial_bob_allowance } = + erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + assert_eq!(initial_bob_allowance, init_approval); + + let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + erc20_address, + bob_addr, + value + )); + + let ERC20ForceApproveMock::allowanceReturn { _0: bob_allowance } = + erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + assert_eq!(bob_allowance, init_approval + value); + + Ok(()) +} + +#[e2e::test] +async fn safe_decrease_allowance_works( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_force_approve::deploy(&alice.wallet).await?; + let erc20_alice = ERC20ForceApproveMock::new(erc20_address, &alice.wallet); + + let init_approval = uint!(100_U256); + let value = uint!(10_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + bob_addr, + init_approval + )); + + let ERC20ForceApproveMock::allowanceReturn { _0: initial_bob_allowance } = + erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + assert_eq!(initial_bob_allowance, init_approval); + + let _ = receipt!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + bob_addr, + value + )); + + let ERC20ForceApproveMock::allowanceReturn { _0: bob_allowance } = + erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + assert_eq!(bob_allowance, init_approval - value); + + Ok(()) +} + +#[e2e::test] +async fn force_approve_works(alice: Account, bob: Account) -> eyre::Result<()> { + let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let erc20_address = erc20_force_approve::deploy(&alice.wallet).await?; + let erc20_alice = ERC20ForceApproveMock::new(erc20_address, &alice.wallet); + + let init_approval = uint!(100_U256); + let updated_approval = uint!(10_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + bob_addr, + init_approval + )); + + let ERC20ForceApproveMock::allowanceReturn { _0: initial_bob_allowance } = + erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + assert_eq!(initial_bob_allowance, init_approval); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + bob_addr, + updated_approval + )); + + let ERC20ForceApproveMock::allowanceReturn { _0: bob_allowance } = + erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + assert_eq!(bob_allowance, updated_approval); + + Ok(()) +} From 8ed52492a5a137ab6cace129e39bb32d2e35aafc Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 12:21:34 +0200 Subject: [PATCH 43/77] Add approvals test with zero init allowance --- examples/safe-erc20/tests/erc20.rs | 166 +++++++++++++++++++++++- examples/safe-erc20/tests/mock/erc20.rs | 10 +- 2 files changed, 171 insertions(+), 5 deletions(-) diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index db5e5019..f4d7f6f8 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -11,7 +11,7 @@ mod abi; mod mock; #[e2e::test] -async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { +async fn transfers(alice: Account, bob: Account) -> eyre::Result<()> { let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; let safe_erc20_mock_alice = SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); @@ -53,7 +53,7 @@ async fn safe_transfers(alice: Account, bob: Account) -> eyre::Result<()> { } #[e2e::test] -async fn safe_transfer_rejects_on_error( +async fn transfer_rejects_on_error( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -80,7 +80,7 @@ async fn safe_transfer_rejects_on_error( } #[e2e::test] -async fn safe_transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { +async fn transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; let safe_erc20_mock_alice = SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); @@ -122,7 +122,7 @@ async fn safe_transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { } #[e2e::test] -async fn safe_transfer_from_rejects_on_error( +async fn transfer_from_rejects_on_error( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -154,3 +154,161 @@ async fn safe_transfer_from_rejects_on_error( Ok(()) } + +mod approvals { + mod with_zero_allowance { + use alloy::primitives::uint; + use alloy_primitives::U256; + use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; + + use crate::abi::SafeErc20; + use crate::mock::{erc20, erc20::ERC20Mock}; + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let value = uint!(100_U256); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + value + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, value); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + U256::ZERO + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, U256::ZERO); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_increasing_the_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let value = uint!(10_U256); + + let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + erc20_address, + spender_addr, + value + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, value); + + Ok(()) + } + + #[e2e::test] + async fn reverts_when_decreasing_the_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let value = uint!(10_U256); + + let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + spender_addr, + value + )) + .expect_err("should not be able to succeed on 'decreaseAllowance'"); + assert!(err.reverted_with( + SafeErc20::SafeErc20FailedDecreaseAllowance { + spender: spender_addr, + currentAllowance: U256::ZERO, + requestedDecrease: value + } + )); + + Ok(()) + } + } +} diff --git a/examples/safe-erc20/tests/mock/erc20.rs b/examples/safe-erc20/tests/mock/erc20.rs index 6d9a7b98..68514dae 100644 --- a/examples/safe-erc20/tests/mock/erc20.rs +++ b/examples/safe-erc20/tests/mock/erc20.rs @@ -6,7 +6,7 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600981526020017f45524332304d6f636b00000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610efb80620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b74565b60405180910390f35b6100d860048036038101906100d39190610c25565b6102b4565b6040516100e59190610c7d565b60405180910390f35b6100f66102c7565b6040516101039190610ca5565b60405180910390f35b61012660048036038101906101219190610cbe565b6102d0565b6040516101339190610c7d565b60405180910390f35b6101446102e5565b6040516101519190610d29565b60405180910390f35b610174600480360381019061016f9190610c25565b6102ed565b005b610190600480360381019061018b9190610d42565b6102fb565b60405161019d9190610ca5565b60405180910390f35b6101ae61030c565b6040516101bb9190610b74565b60405180910390f35b6101de60048036038101906101d99190610c25565b61039c565b6040516101eb9190610c7d565b60405180910390f35b61020e60048036038101906102099190610d6d565b6103af565b60405161021b9190610ca5565b60405180910390f35b60606003805461023390610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610dd8565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f6102bf8383610431565b905092915050565b5f600254905090565b5f6102dc848484610453565b90509392505050565b5f6012905090565b6102f78282610481565b5050565b5f61030582610500565b9050919050565b60606004805461031b90610dd8565b80601f016020809104026020016040519081016040528092919081815260200182805461034790610dd8565b80156103925780601f1061036957610100808354040283529160200191610392565b820191905f5260205f20905b81548152906001019060200180831161037557829003601f168201915b5050505050905090565b5f6103a78383610545565b905092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f8061043b610567565b905061044881858561056e565b600191505092915050565b5f8061045d610567565b905061046a858285610580565b610475858585610612565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104f1575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104e89190610e17565b60405180910390fd5b6104fc5f8383610702565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f8061054f610567565b905061055c818585610612565b600191505092915050565b5f33905090565b61057b838383600161091b565b505050565b5f61058b84846103af565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461060c57818110156105fd578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016105f493929190610e30565b60405180910390fd5b61060b84848484035f61091b565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610682575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106799190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106f2575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106e99190610e17565b60405180910390fd5b6106fd838383610702565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610752578060025f8282546107469190610e92565b92505081905550610820565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156107db578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016107d293929190610e30565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610867578060025f82825403925050819055506108b1565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161090e9190610ca5565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361098b575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109829190610e17565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036109fb575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016109f29190610e17565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610ae4578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610adb9190610ca5565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b21578082015181840152602081019050610b06565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b4682610aea565b610b508185610af4565b9350610b60818560208601610b04565b610b6981610b2c565b840191505092915050565b5f6020820190508181035f830152610b8c8184610b3c565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bc182610b98565b9050919050565b610bd181610bb7565b8114610bdb575f80fd5b50565b5f81359050610bec81610bc8565b92915050565b5f819050919050565b610c0481610bf2565b8114610c0e575f80fd5b50565b5f81359050610c1f81610bfb565b92915050565b5f8060408385031215610c3b57610c3a610b94565b5b5f610c4885828601610bde565b9250506020610c5985828601610c11565b9150509250929050565b5f8115159050919050565b610c7781610c63565b82525050565b5f602082019050610c905f830184610c6e565b92915050565b610c9f81610bf2565b82525050565b5f602082019050610cb85f830184610c96565b92915050565b5f805f60608486031215610cd557610cd4610b94565b5b5f610ce286828701610bde565b9350506020610cf386828701610bde565b9250506040610d0486828701610c11565b9150509250925092565b5f60ff82169050919050565b610d2381610d0e565b82525050565b5f602082019050610d3c5f830184610d1a565b92915050565b5f60208284031215610d5757610d56610b94565b5b5f610d6484828501610bde565b91505092915050565b5f8060408385031215610d8357610d82610b94565b5b5f610d9085828601610bde565b9250506020610da185828601610bde565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610def57607f821691505b602082108103610e0257610e01610dab565b5b50919050565b610e1181610bb7565b82525050565b5f602082019050610e2a5f830184610e08565b92915050565b5f606082019050610e435f830186610e08565b610e506020830185610c96565b610e5d6040830184610c96565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e9c82610bf2565b9150610ea783610bf2565b9250828201905080821115610ebf57610ebe610e65565b5b9291505056fea264697066735822122098e48e82a2d2ff17cb731942aac20d7002c5b8470944afacca92dce20fd8178a64736f6c63430008150033")] + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600981526020017f45524332304d6f636b00000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610f4580620003ff5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c806340c10f191161006f57806340c10f191461016557806370a08231146101815780638483acfe146101b157806395d89b41146101cd578063a9059cbb146101eb578063dd62ed3e1461021b576100a7565b806306fdde03146100ab578063095ea7b3146100c957806318160ddd146100f957806323b872dd14610117578063313ce56714610147575b5f80fd5b6100b361024b565b6040516100c09190610bbe565b60405180910390f35b6100e360048036038101906100de9190610c6f565b6102db565b6040516100f09190610cc7565b60405180910390f35b6101016102ee565b60405161010e9190610cef565b60405180910390f35b610131600480360381019061012c9190610d08565b6102f7565b60405161013e9190610cc7565b60405180910390f35b61014f61030c565b60405161015c9190610d73565b60405180910390f35b61017f600480360381019061017a9190610c6f565b610314565b005b61019b60048036038101906101969190610d8c565b610322565b6040516101a89190610cef565b60405180910390f35b6101cb60048036038101906101c69190610d08565b610333565b005b6101d5610343565b6040516101e29190610bbe565b60405180910390f35b61020560048036038101906102009190610c6f565b6103d3565b6040516102129190610cc7565b60405180910390f35b61023560048036038101906102309190610db7565b6103e6565b6040516102429190610cef565b60405180910390f35b60606003805461025a90610e22565b80601f016020809104026020016040519081016040528092919081815260200182805461028690610e22565b80156102d15780601f106102a8576101008083540402835291602001916102d1565b820191905f5260205f20905b8154815290600101906020018083116102b457829003601f168201915b5050505050905090565b5f6102e683836103f9565b905092915050565b5f600254905090565b5f61030384848461041b565b90509392505050565b5f6012905090565b61031e8282610449565b5050565b5f61032c826104c8565b9050919050565b61033e83838361050d565b505050565b60606004805461035290610e22565b80601f016020809104026020016040519081016040528092919081815260200182805461037e90610e22565b80156103c95780601f106103a0576101008083540402835291602001916103c9565b820191905f5260205f20905b8154815290600101906020018083116103ac57829003601f168201915b5050505050905090565b5f6103de838361051f565b905092915050565b5f6103f18383610541565b905092915050565b5f806104036105c3565b905061041081858561050d565b600191505092915050565b5f806104256105c3565b90506104328582856105ca565b61043d85858561065c565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104b9575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104b09190610e61565b60405180910390fd5b6104c45f838361074c565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b61051a8383836001610965565b505050565b5f806105296105c3565b905061053681858561065c565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b5f6105d584846103e6565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106565781811015610647578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161063e93929190610e7a565b60405180910390fd5b61065584848484035f610965565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106cc575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106c39190610e61565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361073c575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016107339190610e61565b60405180910390fd5b61074783838361074c565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361079c578060025f8282546107909190610edc565b9250508190555061086a565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610825578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161081c93929190610e7a565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108b1578060025f82825403925050819055506108fb565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109589190610cef565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036109d5575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109cc9190610e61565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a45575f6040517f94280d62000000000000000000000000000000000000000000000000000000008152600401610a3c9190610e61565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610b2e578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610b259190610cef565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b6b578082015181840152602081019050610b50565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b9082610b34565b610b9a8185610b3e565b9350610baa818560208601610b4e565b610bb381610b76565b840191505092915050565b5f6020820190508181035f830152610bd68184610b86565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610c0b82610be2565b9050919050565b610c1b81610c01565b8114610c25575f80fd5b50565b5f81359050610c3681610c12565b92915050565b5f819050919050565b610c4e81610c3c565b8114610c58575f80fd5b50565b5f81359050610c6981610c45565b92915050565b5f8060408385031215610c8557610c84610bde565b5b5f610c9285828601610c28565b9250506020610ca385828601610c5b565b9150509250929050565b5f8115159050919050565b610cc181610cad565b82525050565b5f602082019050610cda5f830184610cb8565b92915050565b610ce981610c3c565b82525050565b5f602082019050610d025f830184610ce0565b92915050565b5f805f60608486031215610d1f57610d1e610bde565b5b5f610d2c86828701610c28565b9350506020610d3d86828701610c28565b9250506040610d4e86828701610c5b565b9150509250925092565b5f60ff82169050919050565b610d6d81610d58565b82525050565b5f602082019050610d865f830184610d64565b92915050565b5f60208284031215610da157610da0610bde565b5b5f610dae84828501610c28565b91505092915050565b5f8060408385031215610dcd57610dcc610bde565b5b5f610dda85828601610c28565b9250506020610deb85828601610c28565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610e3957607f821691505b602082108103610e4c57610e4b610df5565b5b50919050565b610e5b81610c01565b82525050565b5f602082019050610e745f830184610e52565b92915050565b5f606082019050610e8d5f830186610e52565b610e9a6020830185610ce0565b610ea76040830184610ce0565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610ee682610c3c565b9150610ef183610c3c565b9250828201905080821115610f0957610f08610eaf565b5b9291505056fea2646970667358221220383e898342e74543d1bfb6186eff00b4ae7a39d4ecde6190742c5e9f2a7a2e9364736f6c63430008150033")] // SPDX-License-Identifier: MIT contract ERC20Mock is ERC20 { constructor() ERC20("ERC20Mock", "MTK") {} @@ -15,6 +15,10 @@ sol! { return super.approve(spender, value); } + function regular_approve(address owner, address spender, uint256 amount) public { + super._approve(owner, spender, amount); + } + function balanceOf(address account) public override view returns (uint256) { return super.balanceOf(account); } @@ -30,6 +34,10 @@ sol! { function transferFrom(address from, address to, uint256 value) public override returns (bool) { return super.transferFrom(from, to, value); } + + function allowance(address owner, address spender) public view override returns (uint256) { + return super.allowance(owner, spender); + } } } From c90bee07b4f633a0218c673d2c9175941237e225 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 12:29:46 +0200 Subject: [PATCH 44/77] Add approvals test with non-zero init allowance --- examples/safe-erc20/tests/erc20.rs | 203 +++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index f4d7f6f8..6db0c781 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -311,4 +311,207 @@ mod approvals { Ok(()) } } + + mod with_non_zero_allowance { + use alloy::primitives::uint; + use alloy_primitives::U256; + use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; + + use crate::abi::SafeErc20; + use crate::mock::{erc20, erc20::ERC20Mock}; + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(20_U256); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + value + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, value); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + U256::ZERO + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, U256::ZERO); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_increasing_the_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(10_U256); + + let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + erc20_address, + spender_addr, + value + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, allowance + value); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_decreasing_the_allowance_to_a_positive_value( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(50_U256); + + let _ = receipt!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + spender_addr, + value + ))?; + + let ERC20Mock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, allowance - value); + + Ok(()) + } + + #[e2e::test] + async fn reverts_when_decreasing_the_allowance_to_a_negative_value( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(200_U256); + + let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + spender_addr, + value + )) + .expect_err("should not be able to succeed on 'decreaseAllowance'"); + assert!(err.reverted_with( + SafeErc20::SafeErc20FailedDecreaseAllowance { + spender: spender_addr, + currentAllowance: allowance, + requestedDecrease: value + } + )); + + Ok(()) + } + } } From 03f1777407c01f3f4ce6b3874d876f26fcb4bcd0 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 12:36:42 +0200 Subject: [PATCH 45/77] Nest transfer-related tests into separate module --- examples/safe-erc20/tests/erc20.rs | 249 +++++++++++------------------ 1 file changed, 95 insertions(+), 154 deletions(-) diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index 6db0c781..6ee6d864 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -10,159 +10,105 @@ use mock::{erc20, erc20::ERC20Mock}; mod abi; mod mock; -#[e2e::test] -async fn transfers(alice: Account, bob: Account) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); - let bob_addr = bob.address(); - - let balance = uint!(10_U256); - let value = uint!(1_U256); - - let erc20mock_address = erc20::deploy(&alice.wallet).await?; - let erc20_alice = ERC20Mock::new(erc20mock_address, &alice.wallet); - - let _ = watch!(erc20_alice.mint(safe_erc20_mock_addr, balance)); - - let ERC20Mock::balanceOfReturn { _0: initial_safe_erc20_mock_balance } = - erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_safe_erc20_mock_balance, balance); - assert_eq!(initial_bob_balance, U256::ZERO); - - let _ = receipt!(safe_erc20_mock_alice.safeTransfer( - erc20mock_address, - bob_addr, - value - ))?; - - let ERC20Mock::balanceOfReturn { _0: safe_erc20_mock_balance } = - erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; - - assert_eq!( - initial_safe_erc20_mock_balance - value, - safe_erc20_mock_balance - ); - assert_eq!(initial_bob_balance + value, bob_balance); - - Ok(()) -} - -#[e2e::test] -async fn transfer_rejects_on_error( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); - let bob_addr = bob.address(); - - let value = uint!(1_U256); - - let erc20_address = erc20::deploy(&alice.wallet).await?; - - let err = send!(safe_erc20_mock_alice.safeTransfer( - erc20_address, - bob_addr, - value - )) - .expect_err("transfer should not succeed when insufficient balance"); - assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: erc20_address - })); - - Ok(()) -} - -#[e2e::test] -async fn transfers_from(alice: Account, bob: Account) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); - let alice_addr = alice.address(); - let bob_addr = bob.address(); - - let balance = uint!(10_U256); - let value = uint!(1_U256); - - let erc20_address = erc20::deploy(&alice.wallet).await?; - let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); - - let _ = watch!(erc20_alice.mint(alice_addr, balance)); - let _ = watch!(erc20_alice.approve(safe_erc20_mock_addr, value)); - - let ERC20Mock::balanceOfReturn { _0: initial_alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_alice_balance, balance); - assert_eq!(initial_bob_balance, U256::ZERO); - - let _ = receipt!(safe_erc20_mock_alice.safeTransferFrom( - erc20_address, - alice_addr, - bob_addr, - value - ))?; - - let ERC20Mock::balanceOfReturn { _0: alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; - - assert_eq!(initial_alice_balance - value, alice_balance); - assert_eq!(initial_bob_balance + value, bob_balance); - - Ok(()) -} +mod transfers { + use super::*; + + #[e2e::test] + async fn doesnt_revert_on_transfer( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let balance = uint!(10_U256); + let value = uint!(1_U256); + + let erc20mock_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20mock_address, &alice.wallet); + + let _ = watch!(erc20_alice.mint(safe_erc20_mock_addr, balance)); + + let ERC20Mock::balanceOfReturn { _0: initial_safe_erc20_mock_balance } = + erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + assert_eq!(initial_safe_erc20_mock_balance, balance); + assert_eq!(initial_bob_balance, U256::ZERO); + + let _ = receipt!(safe_erc20_mock_alice.safeTransfer( + erc20mock_address, + bob_addr, + value + ))?; + + let ERC20Mock::balanceOfReturn { _0: safe_erc20_mock_balance } = + erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + + assert_eq!( + initial_safe_erc20_mock_balance - value, + safe_erc20_mock_balance + ); + assert_eq!(initial_bob_balance + value, bob_balance); + + Ok(()) + } -#[e2e::test] -async fn transfer_from_rejects_on_error( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); - let alice_addr = alice.address(); - let bob_addr = bob.address(); - - let balance = uint!(10_U256); - let value = uint!(1_U256); - - let erc20_address = erc20::deploy(&alice.wallet).await?; - let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); - let _ = watch!(erc20_alice.mint(alice_addr, balance)); - - let err = send!(safe_erc20_mock_alice.safeTransferFrom( - erc20_address, - alice_addr, - bob_addr, - value - )) - .expect_err( - "transferFrom should not succeed when not enough approved balance", - ); - assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { - token: erc20_address - })); - - Ok(()) + #[e2e::test] + async fn doesnt_revert_on_transfer_from( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let balance = uint!(10_U256); + let value = uint!(1_U256); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.mint(alice_addr, balance)); + let _ = watch!(erc20_alice.approve(safe_erc20_mock_addr, value)); + + let ERC20Mock::balanceOfReturn { _0: initial_alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + assert_eq!(initial_alice_balance, balance); + assert_eq!(initial_bob_balance, U256::ZERO); + + let _ = receipt!(safe_erc20_mock_alice.safeTransferFrom( + erc20_address, + alice_addr, + bob_addr, + value + ))?; + + let ERC20Mock::balanceOfReturn { _0: alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20Mock::balanceOfReturn { _0: bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + + assert_eq!(initial_alice_balance - value, alice_balance); + assert_eq!(initial_bob_balance + value, bob_balance); + + Ok(()) + } } mod approvals { mod with_zero_allowance { - use alloy::primitives::uint; - use alloy_primitives::U256; - use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; - - use crate::abi::SafeErc20; - use crate::mock::{erc20, erc20::ERC20Mock}; + use super::super::*; #[e2e::test] async fn doesnt_revert_when_force_approving_a_non_zero_allowance( @@ -313,12 +259,7 @@ mod approvals { } mod with_non_zero_allowance { - use alloy::primitives::uint; - use alloy_primitives::U256; - use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; - - use crate::abi::SafeErc20; - use crate::mock::{erc20, erc20::ERC20Mock}; + use super::super::*; #[e2e::test] async fn doesnt_revert_when_force_approving_a_non_zero_allowance( From 3168ec22a2e4b0be2e6e7094043bbdbe5e07ee4c Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 13:38:46 +0200 Subject: [PATCH 46/77] Fix call_optional_return Ok logic --- contracts/src/token/erc20/utils/safe_erc20.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 7940d8aa..e3a849d0 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -205,8 +205,8 @@ impl SafeErc20 { .call(token, data) { Ok(data) - if !(data.is_empty() && !Address::has_code(&token)) - && encodes_true(&data) => + if (data.is_empty() && Address::has_code(&token)) + || (!data.is_empty() && encodes_true(&data)) => { Ok(()) } From e5736ffa6ee7edb827d25d62730eeb1e908fdf70 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 13:39:06 +0200 Subject: [PATCH 47/77] Add erc20_that_doesnt_return tests --- .../safe-erc20/tests/mock/erc20_no_return.rs | 6 +- examples/safe-erc20/tests/no_return.rs | 469 ++++++++++++++++++ 2 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 examples/safe-erc20/tests/no_return.rs diff --git a/examples/safe-erc20/tests/mock/erc20_no_return.rs b/examples/safe-erc20/tests/mock/erc20_no_return.rs index 8e548e78..2b1189d9 100644 --- a/examples/safe-erc20/tests/mock/erc20_no_return.rs +++ b/examples/safe-erc20/tests/mock/erc20_no_return.rs @@ -6,7 +6,7 @@ use e2e::Wallet; sol! { #[allow(missing_docs)] // Built with Remix IDE; solc v0.8.21+commit.d9974bed - #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601181526020017f45524332304e6f52657475726e4d6f636b0000000000000000000000000000008152506040518060400160405280600381526020017f4e524d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610f0180620003ff5f395ff3fe608060405234801561000f575f80fd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f80fd5b6100a8610224565b6040516100b59190610b7a565b60405180910390f35b6100d860048036038101906100d39190610c2b565b6102b4565b6040516100e59190610c83565b60405180910390f35b6100f66102c3565b6040516101039190610cab565b60405180910390f35b61012660048036038101906101219190610cc4565b6102cc565b6040516101339190610c83565b60405180910390f35b6101446102dc565b6040516101519190610d2f565b60405180910390f35b610174600480360381019061016f9190610c2b565b6102e4565b005b610190600480360381019061018b9190610d48565b6102f2565b60405161019d9190610cab565b60405180910390f35b6101ae610303565b6040516101bb9190610b7a565b60405180910390f35b6101de60048036038101906101d99190610c2b565b610393565b6040516101eb9190610c83565b60405180910390f35b61020e60048036038101906102099190610d73565b6103a2565b60405161021b9190610cab565b60405180910390f35b60606003805461023390610dde565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610dde565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f6102bf83836103b5565b5f80f35b5f600254905090565b5f6102d88484846103d7565b5f80f35b5f6012905090565b6102ee8282610405565b5050565b5f6102fc82610484565b9050919050565b60606004805461031290610dde565b80601f016020809104026020016040519081016040528092919081815260200182805461033e90610dde565b80156103895780601f1061036057610100808354040283529160200191610389565b820191905f5260205f20905b81548152906001019060200180831161036c57829003601f168201915b5050505050905090565b5f61039e83836104c9565b5f80f35b5f6103ad83836104eb565b905092915050565b5f806103bf61056d565b90506103cc818585610574565b600191505092915050565b5f806103e161056d565b90506103ee858285610586565b6103f9858585610618565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610475575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161046c9190610e1d565b60405180910390fd5b6104805f8383610708565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b5f806104d361056d565b90506104e0818585610618565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b6105818383836001610921565b505050565b5f61059184846103a2565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106125781811015610603578281836040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526004016105fa93929190610e36565b60405180910390fd5b61061184848484035f610921565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610688575f6040517f96c6fd1e00000000000000000000000000000000000000000000000000000000815260040161067f9190610e1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106f8575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106ef9190610e1d565b60405180910390fd5b610703838383610708565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610758578060025f82825461074c9190610e98565b92505081905550610826565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050818110156107e1578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016107d893929190610e36565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361086d578060025f82825403925050819055506108b7565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109149190610cab565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610991575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109889190610e1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a01575f6040517f94280d620000000000000000000000000000000000000000000000000000000081526004016109f89190610e1d565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610aea578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610ae19190610cab565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b27578082015181840152602081019050610b0c565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b4c82610af0565b610b568185610afa565b9350610b66818560208601610b0a565b610b6f81610b32565b840191505092915050565b5f6020820190508181035f830152610b928184610b42565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bc782610b9e565b9050919050565b610bd781610bbd565b8114610be1575f80fd5b50565b5f81359050610bf281610bce565b92915050565b5f819050919050565b610c0a81610bf8565b8114610c14575f80fd5b50565b5f81359050610c2581610c01565b92915050565b5f8060408385031215610c4157610c40610b9a565b5b5f610c4e85828601610be4565b9250506020610c5f85828601610c17565b9150509250929050565b5f8115159050919050565b610c7d81610c69565b82525050565b5f602082019050610c965f830184610c74565b92915050565b610ca581610bf8565b82525050565b5f602082019050610cbe5f830184610c9c565b92915050565b5f805f60608486031215610cdb57610cda610b9a565b5b5f610ce886828701610be4565b9350506020610cf986828701610be4565b9250506040610d0a86828701610c17565b9150509250925092565b5f60ff82169050919050565b610d2981610d14565b82525050565b5f602082019050610d425f830184610d20565b92915050565b5f60208284031215610d5d57610d5c610b9a565b5b5f610d6a84828501610be4565b91505092915050565b5f8060408385031215610d8957610d88610b9a565b5b5f610d9685828601610be4565b9250506020610da785828601610be4565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610df557607f821691505b602082108103610e0857610e07610db1565b5b50919050565b610e1781610bbd565b82525050565b5f602082019050610e305f830184610e0e565b92915050565b5f606082019050610e495f830186610e0e565b610e566020830185610c9c565b610e636040830184610c9c565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610ea282610bf8565b9150610ead83610bf8565b9250828201905080821115610ec557610ec4610e6b565b5b9291505056fea2646970667358221220b88d95169fcafa24f302904c1d9f60a9d0d8b37907b43a0ef69b85ef238c60fd64736f6c63430008150033")] + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280601181526020017f45524332304e6f52657475726e4d6f636b0000000000000000000000000000008152506040518060400160405280600381526020017f4e524d000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610f3880620003ff5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c806340c10f191161006f57806340c10f191461016557806370a08231146101815780638483acfe146101b157806395d89b41146101cd578063a9059cbb146101eb578063dd62ed3e1461021b576100a7565b806306fdde03146100ab578063095ea7b3146100c957806318160ddd146100f957806323b872dd14610117578063313ce56714610147575b5f80fd5b6100b361024b565b6040516100c09190610bb1565b60405180910390f35b6100e360048036038101906100de9190610c62565b6102db565b6040516100f09190610cba565b60405180910390f35b6101016102ea565b60405161010e9190610ce2565b60405180910390f35b610131600480360381019061012c9190610cfb565b6102f3565b60405161013e9190610cba565b60405180910390f35b61014f610303565b60405161015c9190610d66565b60405180910390f35b61017f600480360381019061017a9190610c62565b61030b565b005b61019b60048036038101906101969190610d7f565b610319565b6040516101a89190610ce2565b60405180910390f35b6101cb60048036038101906101c69190610cfb565b61032a565b005b6101d561033a565b6040516101e29190610bb1565b60405180910390f35b61020560048036038101906102009190610c62565b6103ca565b6040516102129190610cba565b60405180910390f35b61023560048036038101906102309190610daa565b6103d9565b6040516102429190610ce2565b60405180910390f35b60606003805461025a90610e15565b80601f016020809104026020016040519081016040528092919081815260200182805461028690610e15565b80156102d15780601f106102a8576101008083540402835291602001916102d1565b820191905f5260205f20905b8154815290600101906020018083116102b457829003601f168201915b5050505050905090565b5f6102e683836103ec565b5f80f35b5f600254905090565b5f6102ff84848461040e565b5f80f35b5f6012905090565b610315828261043c565b5050565b5f610323826104bb565b9050919050565b610335838383610500565b505050565b60606004805461034990610e15565b80601f016020809104026020016040519081016040528092919081815260200182805461037590610e15565b80156103c05780601f10610397576101008083540402835291602001916103c0565b820191905f5260205f20905b8154815290600101906020018083116103a357829003601f168201915b5050505050905090565b5f6103d58383610512565b5f80f35b5f6103e48383610534565b905092915050565b5f806103f66105b6565b9050610403818585610500565b600191505092915050565b5f806104186105b6565b90506104258582856105bd565b61043085858561064f565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104ac575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104a39190610e54565b60405180910390fd5b6104b75f838361073f565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b61050d8383836001610958565b505050565b5f8061051c6105b6565b905061052981858561064f565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b5f6105c884846103d9565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610649578181101561063a578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161063193929190610e6d565b60405180910390fd5b61064884848484035f610958565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106bf575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106b69190610e54565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361072f575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016107269190610e54565b60405180910390fd5b61073a83838361073f565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361078f578060025f8282546107839190610ecf565b9250508190555061085d565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610818578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161080f93929190610e6d565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108a4578060025f82825403925050819055506108ee565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161094b9190610ce2565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036109c8575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109bf9190610e54565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a38575f6040517f94280d62000000000000000000000000000000000000000000000000000000008152600401610a2f9190610e54565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610b21578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610b189190610ce2565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b5e578082015181840152602081019050610b43565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b8382610b27565b610b8d8185610b31565b9350610b9d818560208601610b41565b610ba681610b69565b840191505092915050565b5f6020820190508181035f830152610bc98184610b79565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610bfe82610bd5565b9050919050565b610c0e81610bf4565b8114610c18575f80fd5b50565b5f81359050610c2981610c05565b92915050565b5f819050919050565b610c4181610c2f565b8114610c4b575f80fd5b50565b5f81359050610c5c81610c38565b92915050565b5f8060408385031215610c7857610c77610bd1565b5b5f610c8585828601610c1b565b9250506020610c9685828601610c4e565b9150509250929050565b5f8115159050919050565b610cb481610ca0565b82525050565b5f602082019050610ccd5f830184610cab565b92915050565b610cdc81610c2f565b82525050565b5f602082019050610cf55f830184610cd3565b92915050565b5f805f60608486031215610d1257610d11610bd1565b5b5f610d1f86828701610c1b565b9350506020610d3086828701610c1b565b9250506040610d4186828701610c4e565b9150509250925092565b5f60ff82169050919050565b610d6081610d4b565b82525050565b5f602082019050610d795f830184610d57565b92915050565b5f60208284031215610d9457610d93610bd1565b5b5f610da184828501610c1b565b91505092915050565b5f8060408385031215610dc057610dbf610bd1565b5b5f610dcd85828601610c1b565b9250506020610dde85828601610c1b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610e2c57607f821691505b602082108103610e3f57610e3e610de8565b5b50919050565b610e4e81610bf4565b82525050565b5f602082019050610e675f830184610e45565b92915050565b5f606082019050610e805f830186610e45565b610e8d6020830185610cd3565b610e9a6040830184610cd3565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610ed982610c2f565b9150610ee483610c2f565b9250828201905080821115610efc57610efb610ea2565b5b9291505056fea2646970667358221220e66fa5f170a573315c29c69b9be493f577016746885cba3ad5a88b793a2bebac64736f6c63430008150033")] // SPDX-License-Identifier: MIT contract ERC20NoReturnMock is ERC20 { constructor() ERC20("ERC20NoReturnMock", "NRM") {} @@ -32,6 +32,10 @@ sol! { } } + function regular_approve(address owner, address spender, uint256 amount) public { + super._approve(owner, spender, amount); + } + function balanceOf(address account) public override view returns (uint256) { return super.balanceOf(account); } diff --git a/examples/safe-erc20/tests/no_return.rs b/examples/safe-erc20/tests/no_return.rs new file mode 100644 index 00000000..53b96edd --- /dev/null +++ b/examples/safe-erc20/tests/no_return.rs @@ -0,0 +1,469 @@ +#![cfg(feature = "e2e")] + +use alloy::primitives::uint; +use alloy_primitives::U256; +use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; + +use abi::SafeErc20; +use mock::{erc20_no_return, erc20_no_return::ERC20NoReturnMock}; + +mod abi; +mod mock; + +mod transfers { + use super::*; + + #[e2e::test] + async fn doesnt_revert_on_transfer( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let bob_addr = bob.address(); + + let balance = uint!(10_U256); + let value = uint!(1_U256); + + let erc20mock_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20mock_address, &alice.wallet); + + let _ = watch!(erc20_alice.mint(safe_erc20_mock_addr, balance)); + + let ERC20NoReturnMock::balanceOfReturn { + _0: initial_safe_erc20_mock_balance, + } = erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; + let ERC20NoReturnMock::balanceOfReturn { _0: initial_bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + assert_eq!(initial_safe_erc20_mock_balance, balance); + assert_eq!(initial_bob_balance, U256::ZERO); + + let _ = receipt!(safe_erc20_mock_alice.safeTransfer( + erc20mock_address, + bob_addr, + value + ))?; + + let ERC20NoReturnMock::balanceOfReturn { _0: safe_erc20_mock_balance } = + erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; + let ERC20NoReturnMock::balanceOfReturn { _0: bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + + assert_eq!( + initial_safe_erc20_mock_balance - value, + safe_erc20_mock_balance + ); + assert_eq!(initial_bob_balance + value, bob_balance); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_on_transfer_from( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let balance = uint!(10_U256); + let value = uint!(1_U256); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.mint(alice_addr, balance)); + let _ = watch!(erc20_alice.approve(safe_erc20_mock_addr, value)); + + let ERC20NoReturnMock::balanceOfReturn { _0: initial_alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20NoReturnMock::balanceOfReturn { _0: initial_bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + assert_eq!(initial_alice_balance, balance); + assert_eq!(initial_bob_balance, U256::ZERO); + + let _ = receipt!(safe_erc20_mock_alice.safeTransferFrom( + erc20_address, + alice_addr, + bob_addr, + value + ))?; + + let ERC20NoReturnMock::balanceOfReturn { _0: alice_balance } = + erc20_alice.balanceOf(alice_addr).call().await?; + let ERC20NoReturnMock::balanceOfReturn { _0: bob_balance } = + erc20_alice.balanceOf(bob_addr).call().await?; + + assert_eq!(initial_alice_balance - value, alice_balance); + assert_eq!(initial_bob_balance + value, bob_balance); + + Ok(()) + } +} + +mod approvals { + mod with_zero_allowance { + use super::super::*; + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let value = uint!(100_U256); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + value + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, value); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + U256::ZERO + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, U256::ZERO); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_increasing_the_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let value = uint!(10_U256); + + let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + erc20_address, + spender_addr, + value + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, value); + + Ok(()) + } + + #[e2e::test] + async fn reverts_when_decreasing_the_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + U256::ZERO + )); + + let value = uint!(10_U256); + + let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + spender_addr, + value + )) + .expect_err("should not be able to succeed on 'decreaseAllowance'"); + assert!(err.reverted_with( + SafeErc20::SafeErc20FailedDecreaseAllowance { + spender: spender_addr, + currentAllowance: U256::ZERO, + requestedDecrease: value + } + )); + + Ok(()) + } + } + + mod with_non_zero_allowance { + use super::super::*; + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(20_U256); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + value + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, value); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_force_approving_a_zero_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let _ = receipt!(safe_erc20_mock_alice.forceApprove( + erc20_address, + spender_addr, + U256::ZERO + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, U256::ZERO); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_increasing_the_allowance( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(10_U256); + + let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + erc20_address, + spender_addr, + value + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, allowance + value); + + Ok(()) + } + + #[e2e::test] + async fn doesnt_revert_when_decreasing_the_allowance_to_a_positive_value( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(50_U256); + + let _ = receipt!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + spender_addr, + value + ))?; + + let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = + erc20_alice + .allowance(safe_erc20_mock_addr, spender_addr) + .call() + .await?; + assert_eq!(spender_allowance, allowance - value); + + Ok(()) + } + + #[e2e::test] + async fn reverts_when_decreasing_the_allowance_to_a_negative_value( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_mock_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_mock_alice = + SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let allowance = uint!(100_U256); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_mock_addr, + spender_addr, + allowance + )); + + let value = uint!(200_U256); + + let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + erc20_address, + spender_addr, + value + )) + .expect_err("should not be able to succeed on 'decreaseAllowance'"); + assert!(err.reverted_with( + SafeErc20::SafeErc20FailedDecreaseAllowance { + spender: spender_addr, + currentAllowance: allowance, + requestedDecrease: value + } + )); + + Ok(()) + } + } +} From 05f3e1a21a0b7a344f20241cbe8cc53a26239dcf Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 13:39:53 +0200 Subject: [PATCH 48/77] Give more descriptive test file names --- .../safe-erc20/tests/{has_no_code.rs => address_with_no_code.rs} | 0 .../safe-erc20/tests/{no_return.rs => er20_that_doesnt_return.rs} | 0 ...always_returns_false.rs => erc20_that_always_returns_false.rs} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/safe-erc20/tests/{has_no_code.rs => address_with_no_code.rs} (100%) rename examples/safe-erc20/tests/{no_return.rs => er20_that_doesnt_return.rs} (100%) rename examples/safe-erc20/tests/{always_returns_false.rs => erc20_that_always_returns_false.rs} (100%) diff --git a/examples/safe-erc20/tests/has_no_code.rs b/examples/safe-erc20/tests/address_with_no_code.rs similarity index 100% rename from examples/safe-erc20/tests/has_no_code.rs rename to examples/safe-erc20/tests/address_with_no_code.rs diff --git a/examples/safe-erc20/tests/no_return.rs b/examples/safe-erc20/tests/er20_that_doesnt_return.rs similarity index 100% rename from examples/safe-erc20/tests/no_return.rs rename to examples/safe-erc20/tests/er20_that_doesnt_return.rs diff --git a/examples/safe-erc20/tests/always_returns_false.rs b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs similarity index 100% rename from examples/safe-erc20/tests/always_returns_false.rs rename to examples/safe-erc20/tests/erc20_that_always_returns_false.rs From e3b2782fd8b7980b4a62ce7eb4db129037a6c666 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 13:41:30 +0200 Subject: [PATCH 49/77] Revert changes to e2e-tests.sh --- scripts/e2e-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 56d3644d..f7b9cdeb 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features e2e -p safe-erc20-example +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" From ef3dba3a510e6d376451bd7362efe2dac6a58547 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 13:49:03 +0200 Subject: [PATCH 50/77] Fix comments --- contracts/src/token/erc20/utils/safe_erc20.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index e3a849d0..9c47493e 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -55,9 +55,9 @@ sol_storage! { /// contract returns false). Tokens that return no value (and instead revert or /// throw on failure) are also supported, non-reverting calls are assumed to be /// successful. - /// To use this library you can add a `using SafeERC20 for IERC20;` statement to + /// To use this library you can add a `#[inherit(SafeErc20)]` attribute to /// your contract, which allows you to call the safe operations as - /// `token.safeTransfer(...)`, etc. + /// `contract.safe_transfer(token_addr, ...)`, etc. pub struct SafeErc20 {} } @@ -79,7 +79,6 @@ impl SafeErc20 { ) -> Result<(), Error> { let encoded_args = (to, value).abi_encode_params(); let selector = function_selector!("transfer", Address, U256); - // Combine function selector and input data (use abi_packed way) let data = [&selector[..4], &encoded_args].concat(); self.call_optional_return(token, &data) @@ -97,7 +96,6 @@ impl SafeErc20 { let encoded_args = (from, to, value).abi_encode_params(); let selector = function_selector!("transferFrom", Address, Address, U256); - // Combine function selector and input data (use abi_packed way) let data = [&selector[..4], &encoded_args].concat(); self.call_optional_return(token, &data) @@ -121,7 +119,7 @@ impl SafeErc20 { self.force_approve(token, spender, old_allowance + value) } - /// Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no + /// Decrease the calling contract's allowance toward `spender` by `requested_decrease`. If `token` returns no /// value, non-reverting calls are assumed to be successful. pub fn safe_decrease_allowance( &mut self, @@ -192,7 +190,7 @@ impl SafeErc20 { } impl SafeErc20 { - /// Imitates a Solidity high-level call, relaxing the requirement on the return value: + /// Imitates a Stylus high-level call, relaxing the requirement on the return value: /// if data is returned, it must not be `false`, otherwise calls are assumed to be successful. fn call_optional_return( &self, From 86f352cf574f3a7311ee4f1d2f1ce7b70f8995ab Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 13:49:59 +0200 Subject: [PATCH 51/77] Format constructor.sol --- examples/safe-erc20/src/constructor.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/safe-erc20/src/constructor.sol b/examples/safe-erc20/src/constructor.sol index 7de339b9..3ca3ff09 100644 --- a/examples/safe-erc20/src/constructor.sol +++ b/examples/safe-erc20/src/constructor.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -contract SafeErc20Example { -} +contract SafeErc20Example {} From a4c21fb8bfe152f2cb62bc80a65df991a46ec8fe Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 3 Oct 2024 07:29:16 +0200 Subject: [PATCH 52/77] Remove empty constructor.sol --- examples/safe-erc20/src/constructor.sol | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 examples/safe-erc20/src/constructor.sol diff --git a/examples/safe-erc20/src/constructor.sol b/examples/safe-erc20/src/constructor.sol deleted file mode 100644 index 3ca3ff09..00000000 --- a/examples/safe-erc20/src/constructor.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -contract SafeErc20Example {} From 34eda9f2947687dd4b82583eb36ebe968e9d6ae1 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 3 Oct 2024 07:51:31 +0200 Subject: [PATCH 53/77] Align Cargo.toml deps --- Cargo.lock | 3 +-- contracts/src/token/erc20/utils/safe_erc20.rs | 2 +- examples/safe-erc20/Cargo.toml | 9 ++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2431ce69..5775dc1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3214,7 +3214,7 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-erc20-example" -version = "0.0.0" +version = "0.1.0-rc" dependencies = [ "alloy", "alloy-primitives", @@ -3222,7 +3222,6 @@ dependencies = [ "eyre", "mini-alloc", "openzeppelin-stylus", - "stylus-proc", "stylus-sdk", "tokio", ] diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 9c47493e..bbf7fb03 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -3,13 +3,13 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; use alloy_sol_types::{sol, SolValue}; -use stylus_proc::{public, sol_interface, sol_storage, SolidityError}; use stylus_sdk::{ call::{Call, RawCall}, contract::address, evm::gas_left, function_selector, storage::TopLevelStorage, + stylus_proc::{public, sol_interface, sol_storage, SolidityError}, types::AddressVM, }; diff --git a/examples/safe-erc20/Cargo.toml b/examples/safe-erc20/Cargo.toml index 8a971ebd..51494b4a 100644 --- a/examples/safe-erc20/Cargo.toml +++ b/examples/safe-erc20/Cargo.toml @@ -4,20 +4,19 @@ edition.workspace = true license.workspace = true repository.workspace = true publish = false -version = "0.0.0" +version.workspace = true [dependencies] -openzeppelin-stylus = { path = "../../contracts" } -alloy-primitives = { workspace = true, features = ["tiny-keccak"] } +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true stylus-sdk.workspace = true -stylus-proc.workspace = true mini-alloc.workspace = true [dev-dependencies] alloy.workspace = true eyre.workspace = true tokio.workspace = true -e2e = { path = "../../lib/e2e" } +e2e.workspace = true [features] e2e = [] From fde3b188b0d92b37b211e2525aa95f6965cb31d8 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 3 Oct 2024 08:45:28 +0200 Subject: [PATCH 54/77] Fix typos --- examples/safe-erc20/tests/address_with_no_code.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/safe-erc20/tests/address_with_no_code.rs b/examples/safe-erc20/tests/address_with_no_code.rs index 625346b2..241500cc 100644 --- a/examples/safe-erc20/tests/address_with_no_code.rs +++ b/examples/safe-erc20/tests/address_with_no_code.rs @@ -103,12 +103,12 @@ async fn reverts_on_decrease_allowance( let bob_addr = bob.address(); let has_no_code_addr = has_no_code.address(); - let requested_descrease = uint!(1_U256); + let requested_decrease = uint!(1_U256); let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( has_no_code_addr, bob_addr, - requested_descrease + requested_decrease )) .expect_err("should not be able to invoke 'decreaseAllowance' on EOA"); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { From 06d81126028ffb15b9e848d38ca33202322bed51 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 3 Oct 2024 08:52:11 +0200 Subject: [PATCH 55/77] Format with nightly --- contracts/src/token/erc20/utils/safe_erc20.rs | 29 ++++++++++++------- .../safe-erc20/tests/address_with_no_code.rs | 3 +- .../tests/er20_that_doesnt_return.rs | 3 +- examples/safe-erc20/tests/erc20.rs | 3 +- .../tests/erc20_that_always_returns_false.rs | 3 +- .../tests/usdt_approval_behavior.rs | 3 +- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index bbf7fb03..f7aebe78 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -1,6 +1,7 @@ //! Wrappers around ERC-20 operations that throw on failure. use alloc::vec::Vec; + use alloy_primitives::{Address, U256}; use alloy_sol_types::{sol, SolValue}; use stylus_sdk::{ @@ -84,8 +85,9 @@ impl SafeErc20 { self.call_optional_return(token, &data) } - /// Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the - /// calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + /// Transfer `value` amount of `token` from `from` to `to`, spending the + /// approval given by `from` to the calling contract. If `token` returns + /// no value, non-reverting calls are assumed to be successful. pub fn safe_transfer_from( &mut self, token: Address, @@ -101,8 +103,9 @@ impl SafeErc20 { self.call_optional_return(token, &data) } - /// Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, - /// non-reverting calls are assumed to be successful. + /// Increase the calling contract's allowance toward `spender` by `value`. + /// If `token` returns no value, non-reverting calls are assumed to be + /// successful. pub fn safe_increase_allowance( &mut self, token: Address, @@ -119,8 +122,9 @@ impl SafeErc20 { self.force_approve(token, spender, old_allowance + value) } - /// Decrease the calling contract's allowance toward `spender` by `requested_decrease`. If `token` returns no - /// value, non-reverting calls are assumed to be successful. + /// Decrease the calling contract's allowance toward `spender` by + /// `requested_decrease`. If `token` returns no value, non-reverting + /// calls are assumed to be successful. pub fn safe_decrease_allowance( &mut self, token: Address, @@ -153,8 +157,9 @@ impl SafeErc20 { ) } - /// Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, - /// non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + /// Set the calling contract's allowance toward `spender` to `value`. If + /// `token` returns no value, non-reverting calls are assumed to be + /// successful. Meant to be used with tokens that require the approval /// to be set to zero before setting it to a non-zero value, such as USDT. pub fn force_approve( &mut self, @@ -180,7 +185,8 @@ impl SafeErc20 { return Ok(()); } - // If that fails, reset allowance to zero, then retry the desired approval + // If that fails, reset allowance to zero, then retry the desired + // approval let reset_data = build_approve_calldata(spender, U256::ZERO, &selector); self.call_optional_return(token, &reset_data)?; self.call_optional_return(token, &approve_data)?; @@ -190,8 +196,9 @@ impl SafeErc20 { } impl SafeErc20 { - /// Imitates a Stylus high-level call, relaxing the requirement on the return value: - /// if data is returned, it must not be `false`, otherwise calls are assumed to be successful. + /// Imitates a Stylus high-level call, relaxing the requirement on the + /// return value: if data is returned, it must not be `false`, otherwise + /// calls are assumed to be successful. fn call_optional_return( &self, token: Address, diff --git a/examples/safe-erc20/tests/address_with_no_code.rs b/examples/safe-erc20/tests/address_with_no_code.rs index 241500cc..72d09889 100644 --- a/examples/safe-erc20/tests/address_with_no_code.rs +++ b/examples/safe-erc20/tests/address_with_no_code.rs @@ -1,10 +1,9 @@ #![cfg(feature = "e2e")] +use abi::SafeErc20; use alloy::primitives::{uint, U256}; use e2e::{send, Account, ReceiptExt, Revert}; -use abi::SafeErc20; - mod abi; mod mock; diff --git a/examples/safe-erc20/tests/er20_that_doesnt_return.rs b/examples/safe-erc20/tests/er20_that_doesnt_return.rs index 53b96edd..ca4d4663 100644 --- a/examples/safe-erc20/tests/er20_that_doesnt_return.rs +++ b/examples/safe-erc20/tests/er20_that_doesnt_return.rs @@ -1,10 +1,9 @@ #![cfg(feature = "e2e")] +use abi::SafeErc20; use alloy::primitives::uint; use alloy_primitives::U256; use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; - -use abi::SafeErc20; use mock::{erc20_no_return, erc20_no_return::ERC20NoReturnMock}; mod abi; diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index 6ee6d864..2107082f 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -1,10 +1,9 @@ #![cfg(feature = "e2e")] +use abi::SafeErc20; use alloy::primitives::uint; use alloy_primitives::U256; use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; - -use abi::SafeErc20; use mock::{erc20, erc20::ERC20Mock}; mod abi; diff --git a/examples/safe-erc20/tests/erc20_that_always_returns_false.rs b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs index e14fc784..3307432d 100644 --- a/examples/safe-erc20/tests/erc20_that_always_returns_false.rs +++ b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs @@ -1,9 +1,8 @@ #![cfg(feature = "e2e")] +use abi::SafeErc20; use alloy::primitives::U256; use e2e::{send, Account, ReceiptExt, Revert}; - -use abi::SafeErc20; use mock::erc20_return_false; mod abi; diff --git a/examples/safe-erc20/tests/usdt_approval_behavior.rs b/examples/safe-erc20/tests/usdt_approval_behavior.rs index efe6306b..6e4bd932 100644 --- a/examples/safe-erc20/tests/usdt_approval_behavior.rs +++ b/examples/safe-erc20/tests/usdt_approval_behavior.rs @@ -1,9 +1,8 @@ #![cfg(feature = "e2e")] +use abi::SafeErc20; use alloy::primitives::uint; use e2e::{receipt, watch, Account, ReceiptExt}; - -use abi::SafeErc20; use mock::{erc20_force_approve, erc20_force_approve::ERC20ForceApproveMock}; mod abi; From 08d5ac63f19f4696e62a1247ec8601db79f24274 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 3 Oct 2024 08:53:43 +0200 Subject: [PATCH 56/77] Fix docs link to Erc20 --- contracts/src/token/erc20/utils/safe_erc20.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index f7aebe78..bb75162c 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -31,7 +31,7 @@ sol! { /// A SafeErc20 error #[derive(SolidityError, Debug)] pub enum Error { - /// Error type from [`Erc20`] contract [`erc20::Error`]. + /// Error type from [`erc20::Erc20`] contract [`erc20::Error`]. Erc20(erc20::Error), /// An operation with an ERC-20 token failed. SafeErc20FailedOperation(SafeErc20FailedOperation), From ee6beb424226ba561a74740fff193d6126491e49 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 8 Oct 2024 11:24:29 +0200 Subject: [PATCH 57/77] Add underscore prefix to call_optional_return->_call_optional_return --- contracts/src/token/erc20/utils/safe_erc20.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index bb75162c..885ac5a8 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -82,7 +82,7 @@ impl SafeErc20 { let selector = function_selector!("transfer", Address, U256); let data = [&selector[..4], &encoded_args].concat(); - self.call_optional_return(token, &data) + self._call_optional_return(token, &data) } /// Transfer `value` amount of `token` from `from` to `to`, spending the @@ -100,7 +100,7 @@ impl SafeErc20 { function_selector!("transferFrom", Address, Address, U256); let data = [&selector[..4], &encoded_args].concat(); - self.call_optional_return(token, &data) + self._call_optional_return(token, &data) } /// Increase the calling contract's allowance toward `spender` by `value`. @@ -181,15 +181,15 @@ impl SafeErc20 { // Try performing the approval with the desired value let approve_data = build_approve_calldata(spender, value, &selector); - if self.call_optional_return(token, &approve_data).is_ok() { + if self._call_optional_return(token, &approve_data).is_ok() { return Ok(()); } // If that fails, reset allowance to zero, then retry the desired // approval let reset_data = build_approve_calldata(spender, U256::ZERO, &selector); - self.call_optional_return(token, &reset_data)?; - self.call_optional_return(token, &approve_data)?; + self._call_optional_return(token, &reset_data)?; + self._call_optional_return(token, &approve_data)?; Ok(()) } @@ -199,7 +199,7 @@ impl SafeErc20 { /// Imitates a Stylus high-level call, relaxing the requirement on the /// return value: if data is returned, it must not be `false`, otherwise /// calls are assumed to be successful. - fn call_optional_return( + fn _call_optional_return( &self, token: Address, data: &[u8], From e32574566e860c60ae6c437e5ffee27ead1783a0 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 9 Oct 2024 12:20:55 +0200 Subject: [PATCH 58/77] Move contract docs to the top of file --- contracts/src/token/erc20/utils/safe_erc20.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 885ac5a8..56ff1ebc 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -1,5 +1,10 @@ -//! Wrappers around ERC-20 operations that throw on failure. - +//! Wrappers around ERC-20 operations that throw on failure (when the token +//! contract returns false). Tokens that return no value (and instead revert or +//! throw on failure) are also supported, non-reverting calls are assumed to be +//! successful. +//! To use this library you can add a `#[inherit(SafeErc20)]` attribute to +//! your contract, which allows you to call the safe operations as +//! `contract.safe_transfer(token_addr, ...)`, etc. use alloc::vec::Vec; use alloy_primitives::{Address, U256}; @@ -52,13 +57,7 @@ sol_interface! { } sol_storage! { - /// Wrappers around ERC-20 operations that throw on failure (when the token - /// contract returns false). Tokens that return no value (and instead revert or - /// throw on failure) are also supported, non-reverting calls are assumed to be - /// successful. - /// To use this library you can add a `#[inherit(SafeErc20)]` attribute to - /// your contract, which allows you to call the safe operations as - /// `contract.safe_transfer(token_addr, ...)`, etc. + /// State of the SafeErc20 Contract. pub struct SafeErc20 {} } From 839517b46d5165892ab6329c4966381f92fc1c45 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 15 Oct 2024 17:24:26 +0200 Subject: [PATCH 59/77] Refactor er20_that_doesnt_return tests --- .../tests/er20_that_doesnt_return.rs | 224 +++++++++--------- 1 file changed, 106 insertions(+), 118 deletions(-) diff --git a/examples/safe-erc20/tests/er20_that_doesnt_return.rs b/examples/safe-erc20/tests/er20_that_doesnt_return.rs index ca4d4663..32684efc 100644 --- a/examples/safe-erc20/tests/er20_that_doesnt_return.rs +++ b/examples/safe-erc20/tests/er20_that_doesnt_return.rs @@ -17,44 +17,36 @@ mod transfers { alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = - alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let balance = uint!(10_U256); let value = uint!(1_U256); - let erc20mock_address = erc20_no_return::deploy(&alice.wallet).await?; - let erc20_alice = - ERC20NoReturnMock::new(erc20mock_address, &alice.wallet); + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = ERC20NoReturnMock::new(erc20_address, &alice.wallet); - let _ = watch!(erc20_alice.mint(safe_erc20_mock_addr, balance)); + let _ = watch!(erc20_alice.mint(safe_erc20_addr, balance)); - let ERC20NoReturnMock::balanceOfReturn { - _0: initial_safe_erc20_mock_balance, - } = erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; - let ERC20NoReturnMock::balanceOfReturn { _0: initial_bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_safe_erc20_mock_balance, balance); + let initial_safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; + assert_eq!(initial_safe_erc20_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_mock_alice.safeTransfer( - erc20mock_address, + let _ = receipt!(safe_erc20_alice.safeTransfer( + erc20_address, bob_addr, value ))?; - let ERC20NoReturnMock::balanceOfReturn { _0: safe_erc20_mock_balance } = - erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; - let ERC20NoReturnMock::balanceOfReturn { _0: bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; + let safe_erc20_mock_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; - assert_eq!( - initial_safe_erc20_mock_balance - value, - safe_erc20_mock_balance - ); + assert_eq!(initial_safe_erc20_balance - value, safe_erc20_mock_balance); assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) @@ -65,10 +57,8 @@ mod transfers { alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = - alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -79,26 +69,24 @@ mod transfers { let erc20_alice = ERC20NoReturnMock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.mint(alice_addr, balance)); - let _ = watch!(erc20_alice.approve(safe_erc20_mock_addr, value)); + let _ = watch!(erc20_alice.approve(safe_erc20_addr, value)); - let ERC20NoReturnMock::balanceOfReturn { _0: initial_alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; - let ERC20NoReturnMock::balanceOfReturn { _0: initial_bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; + let initial_alice_balance = + erc20_alice.balanceOf(alice_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; assert_eq!(initial_alice_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_mock_alice.safeTransferFrom( + let _ = receipt!(safe_erc20_alice.safeTransferFrom( erc20_address, alice_addr, bob_addr, value ))?; - let ERC20NoReturnMock::balanceOfReturn { _0: alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; - let ERC20NoReturnMock::balanceOfReturn { _0: bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; + let alice_balance = erc20_alice.balanceOf(alice_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; assert_eq!(initial_alice_balance - value, alice_balance); assert_eq!(initial_bob_balance + value, bob_balance); @@ -115,10 +103,10 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -126,24 +114,24 @@ mod approvals { ERC20NoReturnMock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); let value = uint!(100_U256); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, value); Ok(()) @@ -153,10 +141,10 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -164,22 +152,22 @@ mod approvals { ERC20NoReturnMock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, U256::ZERO); Ok(()) @@ -189,10 +177,10 @@ mod approvals { async fn doesnt_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -200,24 +188,24 @@ mod approvals { ERC20NoReturnMock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); let value = uint!(10_U256); - let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, value); Ok(()) @@ -227,10 +215,10 @@ mod approvals { async fn reverts_when_decreasing_the_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -238,14 +226,14 @@ mod approvals { ERC20NoReturnMock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); let value = uint!(10_U256); - let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + let err = send!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value @@ -270,10 +258,10 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -283,24 +271,24 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(20_U256); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, value); Ok(()) @@ -310,10 +298,10 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -323,22 +311,22 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, U256::ZERO); Ok(()) @@ -348,10 +336,10 @@ mod approvals { async fn doesnt_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -361,24 +349,24 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(10_U256); - let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, allowance + value); Ok(()) @@ -388,10 +376,10 @@ mod approvals { async fn doesnt_revert_when_decreasing_the_allowance_to_a_positive_value( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -401,24 +389,24 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(50_U256); - let _ = receipt!(safe_erc20_mock_alice.safeDecreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value ))?; - let ERC20NoReturnMock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, allowance - value); Ok(()) @@ -428,10 +416,10 @@ mod approvals { async fn reverts_when_decreasing_the_allowance_to_a_negative_value( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; @@ -441,14 +429,14 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(200_U256); - let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + let err = send!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value From f006d42cfa8b3cfe5d79747fd4b2ec95b391fa06 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 15 Oct 2024 17:28:13 +0200 Subject: [PATCH 60/77] Refactor all tests --- .../safe-erc20/tests/address_with_no_code.rs | 42 ++-- .../tests/er20_that_doesnt_return.rs | 4 +- examples/safe-erc20/tests/erc20.rs | 222 +++++++++--------- .../tests/erc20_that_always_returns_false.rs | 40 ++-- .../tests/usdt_approval_behavior.rs | 51 ++-- 5 files changed, 169 insertions(+), 190 deletions(-) diff --git a/examples/safe-erc20/tests/address_with_no_code.rs b/examples/safe-erc20/tests/address_with_no_code.rs index 72d09889..221be971 100644 --- a/examples/safe-erc20/tests/address_with_no_code.rs +++ b/examples/safe-erc20/tests/address_with_no_code.rs @@ -13,20 +13,16 @@ async fn reverts_on_transfer( bob: Account, has_no_code: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let has_no_code_addr = has_no_code.address(); let value = uint!(1_U256); - let err = send!(safe_erc20_mock_alice.safeTransfer( - has_no_code_addr, - bob_addr, - value - )) - .expect_err("should not be able to invoke 'transfer' on EOA"); + let err = + send!(safe_erc20_alice.safeTransfer(has_no_code_addr, bob_addr, value)) + .expect_err("should not be able to invoke 'transfer' on EOA"); assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { token: has_no_code_addr })); @@ -40,16 +36,15 @@ async fn reverts_on_transfer_from( bob: Account, has_no_code: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); let has_no_code_addr = has_no_code.address(); let value = uint!(1_U256); - let err = send!(safe_erc20_mock_alice.safeTransferFrom( + let err = send!(safe_erc20_alice.safeTransferFrom( has_no_code_addr, alice_addr, bob_addr, @@ -69,15 +64,14 @@ async fn reverts_on_increase_allowance( bob: Account, has_no_code: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let has_no_code_addr = has_no_code.address(); let value = uint!(1_U256); - let err = send!(safe_erc20_mock_alice.safeIncreaseAllowance( + let err = send!(safe_erc20_alice.safeIncreaseAllowance( has_no_code_addr, bob_addr, value @@ -96,15 +90,14 @@ async fn reverts_on_decrease_allowance( bob: Account, has_no_code: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let has_no_code_addr = has_no_code.address(); let requested_decrease = uint!(1_U256); - let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + let err = send!(safe_erc20_alice.safeDecreaseAllowance( has_no_code_addr, bob_addr, requested_decrease @@ -123,13 +116,12 @@ async fn reverts_on_force_approve( bob: Account, has_no_code: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let has_no_code_addr = has_no_code.address(); - let err = send!(safe_erc20_mock_alice.forceApprove( + let err = send!(safe_erc20_alice.forceApprove( has_no_code_addr, bob_addr, U256::ZERO diff --git a/examples/safe-erc20/tests/er20_that_doesnt_return.rs b/examples/safe-erc20/tests/er20_that_doesnt_return.rs index 32684efc..31a13fae 100644 --- a/examples/safe-erc20/tests/er20_that_doesnt_return.rs +++ b/examples/safe-erc20/tests/er20_that_doesnt_return.rs @@ -42,11 +42,11 @@ mod transfers { value ))?; - let safe_erc20_mock_balance = + let safe_erc20_balance = erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; - assert_eq!(initial_safe_erc20_balance - value, safe_erc20_mock_balance); + assert_eq!(initial_safe_erc20_balance - value, safe_erc20_balance); assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index 2107082f..0d1617c7 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -17,42 +17,36 @@ mod transfers { alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = - alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let balance = uint!(10_U256); let value = uint!(1_U256); - let erc20mock_address = erc20::deploy(&alice.wallet).await?; - let erc20_alice = ERC20Mock::new(erc20mock_address, &alice.wallet); + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); - let _ = watch!(erc20_alice.mint(safe_erc20_mock_addr, balance)); + let _ = watch!(erc20_alice.mint(safe_erc20_addr, balance)); - let ERC20Mock::balanceOfReturn { _0: initial_safe_erc20_mock_balance } = - erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; - assert_eq!(initial_safe_erc20_mock_balance, balance); + let initial_safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; + assert_eq!(initial_safe_erc20_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_mock_alice.safeTransfer( - erc20mock_address, + let _ = receipt!(safe_erc20_alice.safeTransfer( + erc20_address, bob_addr, value ))?; - let ERC20Mock::balanceOfReturn { _0: safe_erc20_mock_balance } = - erc20_alice.balanceOf(safe_erc20_mock_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; + let safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; - assert_eq!( - initial_safe_erc20_mock_balance - value, - safe_erc20_mock_balance - ); + assert_eq!(initial_safe_erc20_balance - value, safe_erc20_balance); assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) @@ -63,10 +57,8 @@ mod transfers { alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = - alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -77,26 +69,24 @@ mod transfers { let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.mint(alice_addr, balance)); - let _ = watch!(erc20_alice.approve(safe_erc20_mock_addr, value)); + let _ = watch!(erc20_alice.approve(safe_erc20_addr, value)); - let ERC20Mock::balanceOfReturn { _0: initial_alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: initial_bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; + let initial_alice_balance = + erc20_alice.balanceOf(alice_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; assert_eq!(initial_alice_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_mock_alice.safeTransferFrom( + let _ = receipt!(safe_erc20_alice.safeTransferFrom( erc20_address, alice_addr, bob_addr, value ))?; - let ERC20Mock::balanceOfReturn { _0: alice_balance } = - erc20_alice.balanceOf(alice_addr).call().await?; - let ERC20Mock::balanceOfReturn { _0: bob_balance } = - erc20_alice.balanceOf(bob_addr).call().await?; + let alice_balance = erc20_alice.balanceOf(alice_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; assert_eq!(initial_alice_balance - value, alice_balance); assert_eq!(initial_bob_balance + value, bob_balance); @@ -113,34 +103,34 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); let value = uint!(100_U256); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, value); Ok(()) @@ -150,32 +140,32 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, U256::ZERO); Ok(()) @@ -185,34 +175,34 @@ mod approvals { async fn doesnt_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); let value = uint!(10_U256); - let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, value); Ok(()) @@ -222,24 +212,24 @@ mod approvals { async fn reverts_when_decreasing_the_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, U256::ZERO )); let value = uint!(10_U256); - let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + let err = send!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value @@ -264,10 +254,10 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; @@ -276,24 +266,24 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(20_U256); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, value); Ok(()) @@ -303,10 +293,10 @@ mod approvals { async fn doesnt_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; @@ -315,22 +305,22 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, U256::ZERO); Ok(()) @@ -340,10 +330,10 @@ mod approvals { async fn doesnt_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; @@ -352,24 +342,24 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(10_U256); - let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, allowance + value); Ok(()) @@ -379,10 +369,10 @@ mod approvals { async fn doesnt_revert_when_decreasing_the_allowance_to_a_positive_value( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; @@ -391,24 +381,24 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(50_U256); - let _ = receipt!(safe_erc20_mock_alice.safeDecreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value ))?; - let ERC20Mock::allowanceReturn { _0: spender_allowance } = - erc20_alice - .allowance(safe_erc20_mock_addr, spender_addr) - .call() - .await?; + let spender_allowance = erc20_alice + .allowance(safe_erc20_addr, spender_addr) + .call() + .await? + ._0; assert_eq!(spender_allowance, allowance - value); Ok(()) @@ -418,10 +408,10 @@ mod approvals { async fn reverts_when_decreasing_the_allowance_to_a_negative_value( alice: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let spender_addr = alice.address(); let erc20_address = erc20::deploy(&alice.wallet).await?; @@ -430,14 +420,14 @@ mod approvals { let allowance = uint!(100_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, spender_addr, allowance )); let value = uint!(200_U256); - let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + let err = send!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value diff --git a/examples/safe-erc20/tests/erc20_that_always_returns_false.rs b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs index 3307432d..17c45908 100644 --- a/examples/safe-erc20/tests/erc20_that_always_returns_false.rs +++ b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs @@ -10,14 +10,14 @@ mod mock; #[e2e::test] async fn reverts_on_transfer(alice: Account, bob: Account) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; - let err = send!(safe_erc20_mock_alice.safeTransfer( + let err = send!(safe_erc20_alice.safeTransfer( erc20_address, bob_addr, U256::ZERO @@ -35,15 +35,15 @@ async fn reverts_on_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; - let err = send!(safe_erc20_mock_alice.safeTransferFrom( + let err = send!(safe_erc20_alice.safeTransferFrom( erc20_address, alice_addr, bob_addr, @@ -62,14 +62,14 @@ async fn reverts_on_increase_allowance( alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; - let err = send!(safe_erc20_mock_alice.safeIncreaseAllowance( + let err = send!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, bob_addr, U256::ZERO @@ -87,14 +87,14 @@ async fn reverts_on_decrease_allowance( alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; - let err = send!(safe_erc20_mock_alice.safeDecreaseAllowance( + let err = send!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, bob_addr, U256::ZERO @@ -112,14 +112,14 @@ async fn reverts_on_force_approve( alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; - let err = send!(safe_erc20_mock_alice.forceApprove( + let err = send!(safe_erc20_alice.forceApprove( erc20_address, bob_addr, U256::ZERO diff --git a/examples/safe-erc20/tests/usdt_approval_behavior.rs b/examples/safe-erc20/tests/usdt_approval_behavior.rs index 6e4bd932..064fc92f 100644 --- a/examples/safe-erc20/tests/usdt_approval_behavior.rs +++ b/examples/safe-erc20/tests/usdt_approval_behavior.rs @@ -13,9 +13,8 @@ async fn safe_increase_allowance_works( alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_force_approve::deploy(&alice.wallet).await?; @@ -25,23 +24,23 @@ async fn safe_increase_allowance_works( let value = uint!(10_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, bob_addr, init_approval )); - let ERC20ForceApproveMock::allowanceReturn { _0: initial_bob_allowance } = - erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + let initial_bob_allowance = + erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(initial_bob_allowance, init_approval); - let _ = receipt!(safe_erc20_mock_alice.safeIncreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, bob_addr, value )); - let ERC20ForceApproveMock::allowanceReturn { _0: bob_allowance } = - erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + let bob_allowance = + erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(bob_allowance, init_approval + value); Ok(()) @@ -52,9 +51,8 @@ async fn safe_decrease_allowance_works( alice: Account, bob: Account, ) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_force_approve::deploy(&alice.wallet).await?; @@ -64,23 +62,23 @@ async fn safe_decrease_allowance_works( let value = uint!(10_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, bob_addr, init_approval )); - let ERC20ForceApproveMock::allowanceReturn { _0: initial_bob_allowance } = - erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + let initial_bob_allowance = + erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(initial_bob_allowance, init_approval); - let _ = receipt!(safe_erc20_mock_alice.safeDecreaseAllowance( + let _ = receipt!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, bob_addr, value )); - let ERC20ForceApproveMock::allowanceReturn { _0: bob_allowance } = - erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + let bob_allowance = + erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(bob_allowance, init_approval - value); Ok(()) @@ -88,9 +86,8 @@ async fn safe_decrease_allowance_works( #[e2e::test] async fn force_approve_works(alice: Account, bob: Account) -> eyre::Result<()> { - let safe_erc20_mock_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_mock_alice = - SafeErc20::new(safe_erc20_mock_addr, &alice.wallet); + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_force_approve::deploy(&alice.wallet).await?; @@ -100,23 +97,23 @@ async fn force_approve_works(alice: Account, bob: Account) -> eyre::Result<()> { let updated_approval = uint!(10_U256); let _ = watch!(erc20_alice.regular_approve( - safe_erc20_mock_addr, + safe_erc20_addr, bob_addr, init_approval )); - let ERC20ForceApproveMock::allowanceReturn { _0: initial_bob_allowance } = - erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + let initial_bob_allowance = + erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(initial_bob_allowance, init_approval); - let _ = receipt!(safe_erc20_mock_alice.forceApprove( + let _ = receipt!(safe_erc20_alice.forceApprove( erc20_address, bob_addr, updated_approval )); - let ERC20ForceApproveMock::allowanceReturn { _0: bob_allowance } = - erc20_alice.allowance(safe_erc20_mock_addr, bob_addr).call().await?; + let bob_allowance = + erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(bob_allowance, updated_approval); Ok(()) From b4f78b17538243a7f2ec6e70f79aeb8082f6cfae Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 29 Oct 2024 19:50:56 +0100 Subject: [PATCH 61/77] Run format --- .../tests/erc20_that_always_returns_false.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/examples/safe-erc20/tests/erc20_that_always_returns_false.rs b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs index 17c45908..a3fa7851 100644 --- a/examples/safe-erc20/tests/erc20_that_always_returns_false.rs +++ b/examples/safe-erc20/tests/erc20_that_always_returns_false.rs @@ -11,8 +11,7 @@ mod mock; #[e2e::test] async fn reverts_on_transfer(alice: Account, bob: Account) -> eyre::Result<()> { let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_alice = - SafeErc20::new(safe_erc20_addr, &alice.wallet); + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; @@ -36,8 +35,7 @@ async fn reverts_on_transfer_from( bob: Account, ) -> eyre::Result<()> { let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_alice = - SafeErc20::new(safe_erc20_addr, &alice.wallet); + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -63,8 +61,7 @@ async fn reverts_on_increase_allowance( bob: Account, ) -> eyre::Result<()> { let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_alice = - SafeErc20::new(safe_erc20_addr, &alice.wallet); + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; @@ -88,8 +85,7 @@ async fn reverts_on_decrease_allowance( bob: Account, ) -> eyre::Result<()> { let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_alice = - SafeErc20::new(safe_erc20_addr, &alice.wallet); + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; @@ -113,8 +109,7 @@ async fn reverts_on_force_approve( bob: Account, ) -> eyre::Result<()> { let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; - let safe_erc20_alice = - SafeErc20::new(safe_erc20_addr, &alice.wallet); + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); let bob_addr = bob.address(); let erc20_address = erc20_return_false::deploy(&alice.wallet).await?; From b40b0971a90f5a40888b61602487aad114996d1a Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 13:05:21 +0100 Subject: [PATCH 62/77] fix: compilation error --- Cargo.lock | 3 +-- examples/safe-erc20/Cargo.toml | 1 - examples/safe-erc20/src/lib.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ed1977d..1e79c788 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3207,13 +3207,12 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-erc20-example" -version = "0.1.0-rc" +version = "0.1.1" dependencies = [ "alloy", "alloy-primitives", "e2e", "eyre", - "mini-alloc", "openzeppelin-stylus", "stylus-sdk", "tokio", diff --git a/examples/safe-erc20/Cargo.toml b/examples/safe-erc20/Cargo.toml index 51494b4a..15408bd5 100644 --- a/examples/safe-erc20/Cargo.toml +++ b/examples/safe-erc20/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true openzeppelin-stylus.workspace = true alloy-primitives.workspace = true stylus-sdk.workspace = true -mini-alloc.workspace = true [dev-dependencies] alloy.workspace = true diff --git a/examples/safe-erc20/src/lib.rs b/examples/safe-erc20/src/lib.rs index 54049912..fcc64a9c 100644 --- a/examples/safe-erc20/src/lib.rs +++ b/examples/safe-erc20/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(test), no_main, no_std)] +#![cfg_attr(not(test), no_main)] extern crate alloc; use openzeppelin_stylus::token::erc20::utils::safe_erc20::SafeErc20; From 2e9169de7151bd39873e93b1d9dbe0fc92696eab Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 13:20:42 +0100 Subject: [PATCH 63/77] ref: add ISafeErc20 trait --- contracts/src/token/erc20/utils/safe_erc20.rs | 123 ++++++++++++------ 1 file changed, 85 insertions(+), 38 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 56ff1ebc..0260eed0 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -15,7 +15,7 @@ use stylus_sdk::{ evm::gas_left, function_selector, storage::TopLevelStorage, - stylus_proc::{public, sol_interface, sol_storage, SolidityError}, + stylus_proc::{public, sol_storage, SolidityError}, types::AddressVM, }; @@ -33,29 +33,32 @@ sol! { error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); } -/// A SafeErc20 error +/// A [`SafeErc20`] error. #[derive(SolidityError, Debug)] pub enum Error { /// Error type from [`erc20::Erc20`] contract [`erc20::Error`]. Erc20(erc20::Error), /// An operation with an ERC-20 token failed. SafeErc20FailedOperation(SafeErc20FailedOperation), - /// Indicates a failed `decreaseAllowance` request. + /// Indicates a failed [`ISafeErc20::decrease_allowance`] request. SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } -sol_interface! { - /// Interface of the ERC-20 standard as defined in the ERC. - interface IERC20 { - /// Returns the remaining number of tokens that `spender` will be - /// allowed to spend on behalf of `owner` through {transferFrom}. This is - /// zero by default. - /// - /// This value changes when {approve} or {transferFrom} are called. - function allowance(address owner, address spender) external view returns (uint256); +pub use token::IERC20; +#[allow(missing_docs)] +mod token { + stylus_sdk::stylus_proc::sol_interface! { + /// Interface of the ERC-20 standard as defined in the ERC. + interface IERC20 { + /// Returns the remaining number of tokens that `spender` will be + /// allowed to spend on behalf of `owner` through {transferFrom}. This is + /// zero by default. + /// + /// This value changes when {approve} or {transferFrom} are called. + function allowance(address owner, address spender) external view returns (uint256); + } } } - sol_storage! { /// State of the SafeErc20 Contract. pub struct SafeErc20 {} @@ -66,17 +69,74 @@ sol_storage! { /// BorrowMut)`. Should be fixed in the future by the Stylus team. unsafe impl TopLevelStorage for SafeErc20 {} -#[public] -impl SafeErc20 { +/// Required interface of an [`SafeErc20`] utility contract. +pub trait ISafeErc20 { + /// The error type associated to this ERC-20 trait implementation. + type Error: Into>; + /// Transfer `value` amount of `token` from the calling contract to `to`. If /// `token` returns no value, non-reverting calls are assumed to be /// successful. - pub fn safe_transfer( + fn safe_transfer( &mut self, token: Address, to: Address, value: U256, - ) -> Result<(), Error> { + ) -> Result<(), Self::Error>; + + /// Transfer `value` amount of `token` from `from` to `to`, spending the + /// approval given by `from` to the calling contract. If `token` returns + /// no value, non-reverting calls are assumed to be successful. + fn safe_transfer_from( + &mut self, + token: Address, + from: Address, + to: Address, + value: U256, + ) -> Result<(), Self::Error>; + + /// Increase the calling contract's allowance toward `spender` by `value`. + /// If `token` returns no value, non-reverting calls are assumed to be + /// successful. + fn safe_increase_allowance( + &mut self, + token: Address, + spender: Address, + value: U256, + ) -> Result<(), Self::Error>; + + /// Decrease the calling contract's allowance toward `spender` by + /// `requested_decrease`. If `token` returns no value, non-reverting + /// calls are assumed to be successful. + fn safe_decrease_allowance( + &mut self, + token: Address, + spender: Address, + requested_decrease: U256, + ) -> Result<(), Self::Error>; + + /// Set the calling contract's allowance toward `spender` to `value`. If + /// `token` returns no value, non-reverting calls are assumed to be + /// successful. Meant to be used with tokens that require the approval + /// to be set to zero before setting it to a non-zero value, such as USDT. + fn force_approve( + &mut self, + token: Address, + spender: Address, + value: U256, + ) -> Result<(), Self::Error>; +} + +#[public] +impl ISafeErc20 for SafeErc20 { + type Error = Error; + + fn safe_transfer( + &mut self, + token: Address, + to: Address, + value: U256, + ) -> Result<(), Self::Error> { let encoded_args = (to, value).abi_encode_params(); let selector = function_selector!("transfer", Address, U256); let data = [&selector[..4], &encoded_args].concat(); @@ -84,16 +144,13 @@ impl SafeErc20 { self._call_optional_return(token, &data) } - /// Transfer `value` amount of `token` from `from` to `to`, spending the - /// approval given by `from` to the calling contract. If `token` returns - /// no value, non-reverting calls are assumed to be successful. - pub fn safe_transfer_from( + fn safe_transfer_from( &mut self, token: Address, from: Address, to: Address, value: U256, - ) -> Result<(), Error> { + ) -> Result<(), Self::Error> { let encoded_args = (from, to, value).abi_encode_params(); let selector = function_selector!("transferFrom", Address, Address, U256); @@ -102,15 +159,12 @@ impl SafeErc20 { self._call_optional_return(token, &data) } - /// Increase the calling contract's allowance toward `spender` by `value`. - /// If `token` returns no value, non-reverting calls are assumed to be - /// successful. - pub fn safe_increase_allowance( + fn safe_increase_allowance( &mut self, token: Address, spender: Address, value: U256, - ) -> Result<(), Error> { + ) -> Result<(), Self::Error> { let erc20 = IERC20::new(token); let call = Call::new_in(self); let old_allowance = erc20.allowance(call, address(), spender).or( @@ -121,15 +175,12 @@ impl SafeErc20 { self.force_approve(token, spender, old_allowance + value) } - /// Decrease the calling contract's allowance toward `spender` by - /// `requested_decrease`. If `token` returns no value, non-reverting - /// calls are assumed to be successful. - pub fn safe_decrease_allowance( + fn safe_decrease_allowance( &mut self, token: Address, spender: Address, requested_decrease: U256, - ) -> Result<(), Error> { + ) -> Result<(), Self::Error> { let erc20 = IERC20::new(token); let call = Call::new_in(self); let current_allowance = @@ -156,16 +207,12 @@ impl SafeErc20 { ) } - /// Set the calling contract's allowance toward `spender` to `value`. If - /// `token` returns no value, non-reverting calls are assumed to be - /// successful. Meant to be used with tokens that require the approval - /// to be set to zero before setting it to a non-zero value, such as USDT. - pub fn force_approve( + fn force_approve( &mut self, token: Address, spender: Address, value: U256, - ) -> Result<(), Error> { + ) -> Result<(), Self::Error> { let selector = function_selector!("approve", Address, U256); // Helper function to construct calldata From b8f04ecc011d3b6505390fb90bfbf40756096594 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 13:53:10 +0100 Subject: [PATCH 64/77] ref: improve code style --- contracts/src/token/erc20/utils/safe_erc20.rs | 83 ++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 0260eed0..c0a2f0eb 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -23,14 +23,24 @@ use crate::token::erc20; sol! { /// An operation with an ERC-20 token failed. + /// + /// * `token` - Address of the ERC-20 token. #[derive(Debug)] #[allow(missing_docs)] error SafeErc20FailedOperation(address token); - /// Indicates a failed `decreaseAllowance` request. + /// Indicates a failed [`ISafeErc20::decrease_allowance`] request. + /// + /// * `spender` - Address of future tokens' spender. + /// * `current_allowance` - Current allowance of the `spender`. + /// * `requested_decrease` - Requested decrease in allowance for `spender`. #[derive(Debug)] #[allow(missing_docs)] - error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + error SafeErc20FailedDecreaseAllowance( + address spender, + uint256 current_allowance, + uint256 requested_decrease + ); } /// A [`SafeErc20`] error. @@ -44,18 +54,16 @@ pub enum Error { SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } -pub use token::IERC20; +pub use token::IErc20; #[allow(missing_docs)] mod token { stylus_sdk::stylus_proc::sol_interface! { /// Interface of the ERC-20 standard as defined in the ERC. - interface IERC20 { - /// Returns the remaining number of tokens that `spender` will be - /// allowed to spend on behalf of `owner` through {transferFrom}. This is - /// zero by default. - /// - /// This value changes when {approve} or {transferFrom} are called. + interface IErc20 { function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transfer(address recipient, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); } } } @@ -98,6 +106,10 @@ pub trait ISafeErc20 { /// Increase the calling contract's allowance toward `spender` by `value`. /// If `token` returns no value, non-reverting calls are assumed to be /// successful. + /// + /// # Panics + /// + /// If increased allowance exceeds `U256::MAX`. fn safe_increase_allowance( &mut self, token: Address, @@ -165,14 +177,11 @@ impl ISafeErc20 for SafeErc20 { spender: Address, value: U256, ) -> Result<(), Self::Error> { - let erc20 = IERC20::new(token); - let call = Call::new_in(self); - let old_allowance = erc20.allowance(call, address(), spender).or( - Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { - token, - })), - )?; - self.force_approve(token, spender, old_allowance + value) + let current_allowance = self.allowance(token, spender)?; + let new_allowance = current_allowance + .checked_add(value) + .expect("should not exceed `U256::MAX` for allowance"); + self.force_approve(token, spender, new_allowance) } fn safe_decrease_allowance( @@ -181,23 +190,15 @@ impl ISafeErc20 for SafeErc20 { spender: Address, requested_decrease: U256, ) -> Result<(), Self::Error> { - let erc20 = IERC20::new(token); - let call = Call::new_in(self); - let current_allowance = - erc20.allowance(call, address(), spender).or({ - Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { - token, - })) - })?; + let current_allowance = self.allowance(token, spender)?; if current_allowance < requested_decrease { - return Err(Error::SafeErc20FailedDecreaseAllowance( - SafeErc20FailedDecreaseAllowance { - spender, - currentAllowance: current_allowance, - requestedDecrease: requested_decrease, - }, - )); + return Err(SafeErc20FailedDecreaseAllowance { + spender, + current_allowance, + requested_decrease, + } + .into()); } self.force_approve( @@ -261,13 +262,21 @@ impl SafeErc20 { { Ok(()) } - _ => { - Err(Error::SafeErc20FailedOperation(SafeErc20FailedOperation { - token, - })) - } + _ => Err(SafeErc20FailedOperation { token }.into()), } } + + fn allowance( + &mut self, + token: Address, + spender: Address, + ) -> Result { + let erc20 = IErc20::new(token); + let call = Call::new_in(self); + erc20 + .allowance(call, address(), spender) + .map_err(|_| SafeErc20FailedOperation { token }.into()) + } } fn encodes_true(data: &[u8]) -> bool { From 6f3f69effce517196131632ae5adde85558d326d Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 18:00:43 +0100 Subject: [PATCH 65/77] ref: abi encode based on solidity interface --- contracts/src/token/erc20/utils/safe_erc20.rs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index c0a2f0eb..1977a3ab 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -5,12 +5,11 @@ //! To use this library you can add a `#[inherit(SafeErc20)]` attribute to //! your contract, which allows you to call the safe operations as //! `contract.safe_transfer(token_addr, ...)`, etc. -use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use alloy_sol_types::{sol, SolValue}; +use alloy_sol_types::{sol, SolCall}; use stylus_sdk::{ - call::{Call, RawCall}, + call::RawCall, contract::address, evm::gas_left, function_selector, @@ -54,16 +53,16 @@ pub enum Error { SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } -pub use token::IErc20; +pub use token::*; #[allow(missing_docs)] mod token { - stylus_sdk::stylus_proc::sol_interface! { + alloy_sol_types::sol! { /// Interface of the ERC-20 standard as defined in the ERC. interface IErc20 { function allowance(address owner, address spender) external view returns (uint256); - function approve(address spender, uint256 amount) external returns (bool); - function transfer(address recipient, uint256 amount) external returns (bool); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); + function transfer(address to, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); } } } @@ -149,11 +148,9 @@ impl ISafeErc20 for SafeErc20 { to: Address, value: U256, ) -> Result<(), Self::Error> { - let encoded_args = (to, value).abi_encode_params(); - let selector = function_selector!("transfer", Address, U256); - let data = [&selector[..4], &encoded_args].concat(); + let call = IErc20::transferCall { to, value }; - self._call_optional_return(token, &data) + self._call_optional_return(token, &call) } fn safe_transfer_from( @@ -163,12 +160,9 @@ impl ISafeErc20 for SafeErc20 { to: Address, value: U256, ) -> Result<(), Self::Error> { - let encoded_args = (from, to, value).abi_encode_params(); - let selector = - function_selector!("transferFrom", Address, Address, U256); - let data = [&selector[..4], &encoded_args].concat(); + let call = IErc20::transferFromCall { from, to, value }; - self._call_optional_return(token, &data) + self._call_optional_return(token, &call) } fn safe_increase_allowance( @@ -214,29 +208,19 @@ impl ISafeErc20 for SafeErc20 { spender: Address, value: U256, ) -> Result<(), Self::Error> { - let selector = function_selector!("approve", Address, U256); - - // Helper function to construct calldata - fn build_approve_calldata( - spender: Address, - value: U256, - selector: &[u8], - ) -> Vec { - let encoded_args = (spender, value).abi_encode_params(); - [&selector[..4], &encoded_args].concat() - } + let approve_call = IErc20::approveCall { spender, value }; // Try performing the approval with the desired value - let approve_data = build_approve_calldata(spender, value, &selector); - if self._call_optional_return(token, &approve_data).is_ok() { + if self._call_optional_return(token, &approve_call).is_ok() { return Ok(()); } // If that fails, reset allowance to zero, then retry the desired // approval - let reset_data = build_approve_calldata(spender, U256::ZERO, &selector); - self._call_optional_return(token, &reset_data)?; - self._call_optional_return(token, &approve_data)?; + let reset_approval_call = + IErc20::approveCall { spender, value: U256::ZERO }; + self._call_optional_return(token, &reset_approval_call)?; + self._call_optional_return(token, &approve_call)?; Ok(()) } @@ -249,15 +233,19 @@ impl SafeErc20 { fn _call_optional_return( &self, token: Address, - data: &[u8], + call: &impl SolCall, ) -> Result<(), Error> { + if !Address::has_code(&token) { + return Err(SafeErc20FailedOperation { token }.into()); + } + match RawCall::new() .gas(gas_left()) .limit_return_data(0, 32) - .call(token, data) + .call(token, &call.abi_encode()) { Ok(data) - if (data.is_empty() && Address::has_code(&token)) + if data.is_empty() || (!data.is_empty() && encodes_true(&data)) => { Ok(()) @@ -271,11 +259,22 @@ impl SafeErc20 { token: Address, spender: Address, ) -> Result { - let erc20 = IErc20::new(token); - let call = Call::new_in(self); - erc20 - .allowance(call, address(), spender) - .map_err(|_| SafeErc20FailedOperation { token }.into()) + if !Address::has_code(&token) { + return Err(SafeErc20FailedOperation { token }.into()); + } + + let call = IErc20::allowanceCall { owner: address(), spender }; + let allowance = RawCall::new() + .gas(gas_left()) + .limit_return_data(0, 32) + .call(token, &call.abi_encode()) + .map_err(|_| { + Error::SafeErc20FailedOperation(SafeErc20FailedOperation { + token, + }) + })?; + + Ok(U256::from_be_slice(&allowance)) } } From caf1abb0b88dad009de9456defd4aaf121a18946 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 6 Nov 2024 19:23:08 +0100 Subject: [PATCH 66/77] Apply comment update suggestions from code review Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/utils/safe_erc20.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 1977a3ab..9327cc47 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -57,7 +57,7 @@ pub use token::*; #[allow(missing_docs)] mod token { alloy_sol_types::sol! { - /// Interface of the ERC-20 standard as defined in the ERC. + /// Interface of the ERC-20 token. interface IErc20 { function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 value) external returns (bool); @@ -67,7 +67,7 @@ mod token { } } sol_storage! { - /// State of the SafeErc20 Contract. + /// State of the [`SafeErc20`] Contract. pub struct SafeErc20 {} } @@ -78,7 +78,7 @@ unsafe impl TopLevelStorage for SafeErc20 {} /// Required interface of an [`SafeErc20`] utility contract. pub trait ISafeErc20 { - /// The error type associated to this ERC-20 trait implementation. + /// The error type associated to this trait implementation. type Error: Into>; /// Transfer `value` amount of `token` from the calling contract to `to`. If From 498693850990980607814a13ed54c8859241e27c Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 19:29:19 +0100 Subject: [PATCH 67/77] ref: optimize the code --- contracts/src/token/erc20/utils/safe_erc20.rs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 9327cc47..91475e91 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -150,7 +150,7 @@ impl ISafeErc20 for SafeErc20 { ) -> Result<(), Self::Error> { let call = IErc20::transferCall { to, value }; - self._call_optional_return(token, &call) + self.call_optional_return(token, &call) } fn safe_transfer_from( @@ -162,7 +162,7 @@ impl ISafeErc20 for SafeErc20 { ) -> Result<(), Self::Error> { let call = IErc20::transferFromCall { from, to, value }; - self._call_optional_return(token, &call) + self.call_optional_return(token, &call) } fn safe_increase_allowance( @@ -211,7 +211,7 @@ impl ISafeErc20 for SafeErc20 { let approve_call = IErc20::approveCall { spender, value }; // Try performing the approval with the desired value - if self._call_optional_return(token, &approve_call).is_ok() { + if self.call_optional_return(token, &approve_call).is_ok() { return Ok(()); } @@ -219,8 +219,8 @@ impl ISafeErc20 for SafeErc20 { // approval let reset_approval_call = IErc20::approveCall { spender, value: U256::ZERO }; - self._call_optional_return(token, &reset_approval_call)?; - self._call_optional_return(token, &approve_call)?; + self.call_optional_return(token, &reset_approval_call)?; + self.call_optional_return(token, &approve_call)?; Ok(()) } @@ -230,7 +230,7 @@ impl SafeErc20 { /// Imitates a Stylus high-level call, relaxing the requirement on the /// return value: if data is returned, it must not be `false`, otherwise /// calls are assumed to be successful. - fn _call_optional_return( + fn call_optional_return( &self, token: Address, call: &impl SolCall, @@ -244,12 +244,7 @@ impl SafeErc20 { .limit_return_data(0, 32) .call(token, &call.abi_encode()) { - Ok(data) - if data.is_empty() - || (!data.is_empty() && encodes_true(&data)) => - { - Ok(()) - } + Ok(data) if data.is_empty() || encodes_true(&data) => Ok(()), _ => Err(SafeErc20FailedOperation { token }.into()), } } @@ -279,6 +274,7 @@ impl SafeErc20 { } fn encodes_true(data: &[u8]) -> bool { - data[..data.len() - 1].iter().all(|&byte| byte == 0) - && data[data.len() - 1] == 1 + data.split_last().map_or(false, |(last, rest)| { + *last == 1 && rest.iter().all(|&byte| byte == 0) + }) } From a52c2c0b06e2e0ca5bb29b391b1978ea24842906 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 19:59:49 +0100 Subject: [PATCH 68/77] docs: add Rust docs --- contracts/src/token/erc20/utils/safe_erc20.rs | 136 ++++++++++++++++-- 1 file changed, 126 insertions(+), 10 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 91475e91..b2641f7f 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -49,7 +49,7 @@ pub enum Error { Erc20(erc20::Error), /// An operation with an ERC-20 token failed. SafeErc20FailedOperation(SafeErc20FailedOperation), - /// Indicates a failed [`ISafeErc20::decrease_allowance`] request. + /// Indicates a failed [`ISafeErc20::safe_decrease_allowance`] request. SafeErc20FailedDecreaseAllowance(SafeErc20FailedDecreaseAllowance), } @@ -84,6 +84,22 @@ pub trait ISafeErc20 { /// Transfer `value` amount of `token` from the calling contract to `to`. If /// `token` returns no value, non-reverting calls are assumed to be /// successful. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `to` - Account to transfer tokens to. + /// * `value` - Number of tokens to transfer. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the contract fails to execute the call, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the call returns value that is not `true`, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. fn safe_transfer( &mut self, token: Address, @@ -94,6 +110,23 @@ pub trait ISafeErc20 { /// Transfer `value` amount of `token` from `from` to `to`, spending the /// approval given by `from` to the calling contract. If `token` returns /// no value, non-reverting calls are assumed to be successful. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `from` - Account to transfer tokens from. + /// * `to` - Account to transfer tokens to. + /// * `value` - Number of tokens to transfer. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the contract fails to execute the call, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the call returns value that is not `true`, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. fn safe_transfer_from( &mut self, token: Address, @@ -106,6 +139,22 @@ pub trait ISafeErc20 { /// If `token` returns no value, non-reverting calls are assumed to be /// successful. /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `spender` - Account that will spend the tokens. + /// * `value` - Value to increase current allowance for `spender`. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the contract fails to execute the call, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the call returns value that is not `true`, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// /// # Panics /// /// If increased allowance exceeds `U256::MAX`. @@ -119,6 +168,24 @@ pub trait ISafeErc20 { /// Decrease the calling contract's allowance toward `spender` by /// `requested_decrease`. If `token` returns no value, non-reverting /// calls are assumed to be successful. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `spender` - Account that will spend the tokens. + /// * `requested_decrease` - Value allowed to be spent by `spender`. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the current allowance is less than `requested_decrease`, then the + /// error [`Error::SafeErc20FailedDecreaseAllowance`] is returned. + /// If the contract fails to execute the call, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the call returns value that is not `true`, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. fn safe_decrease_allowance( &mut self, token: Address, @@ -130,6 +197,22 @@ pub trait ISafeErc20 { /// `token` returns no value, non-reverting calls are assumed to be /// successful. Meant to be used with tokens that require the approval /// to be set to zero before setting it to a non-zero value, such as USDT. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `spender` - Account that will spend the tokens. + /// * `value` - Value allowed to be spent by `spender`. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the contract fails to execute the call, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the call returns value that is not `true`, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. fn force_approve( &mut self, token: Address, @@ -220,9 +303,7 @@ impl ISafeErc20 for SafeErc20 { let reset_approval_call = IErc20::approveCall { spender, value: U256::ZERO }; self.call_optional_return(token, &reset_approval_call)?; - self.call_optional_return(token, &approve_call)?; - - Ok(()) + self.call_optional_return(token, &approve_call) } } @@ -230,6 +311,21 @@ impl SafeErc20 { /// Imitates a Stylus high-level call, relaxing the requirement on the /// return value: if data is returned, it must not be `false`, otherwise /// calls are assumed to be successful. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `call` - [`IErc20`] call that implements [`SolCall`] trait. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the contract fails to execute the call, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the call returns value that is not `true`, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. fn call_optional_return( &self, token: Address, @@ -244,11 +340,26 @@ impl SafeErc20 { .limit_return_data(0, 32) .call(token, &call.abi_encode()) { - Ok(data) if data.is_empty() || encodes_true(&data) => Ok(()), + Ok(data) if data.is_empty() || Self::encodes_true(&data) => Ok(()), _ => Err(SafeErc20FailedOperation { token }.into()), } } + /// Returns the remaining number of ERC-20 tokens that `spender` + /// will be allowed to spend on behalf of owner. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token` - Address of the ERC-20 token contract. + /// * `spender` - Account that will spend the tokens. + /// + /// # Errors + /// + /// If the `token` address is not a contract, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. + /// If the contract fails to read `spender`'s allowance, then the error + /// [`Error::SafeErc20FailedOperation`] is returned. fn allowance( &mut self, token: Address, @@ -271,10 +382,15 @@ impl SafeErc20 { Ok(U256::from_be_slice(&allowance)) } -} -fn encodes_true(data: &[u8]) -> bool { - data.split_last().map_or(false, |(last, rest)| { - *last == 1 && rest.iter().all(|&byte| byte == 0) - }) + /// Returns true if a slice of bytes is an ABI encoded `true` value. + /// + /// # Arguments + /// + /// * `data` - Slice of bytes. + fn encodes_true(data: &[u8]) -> bool { + data.split_last().map_or(false, |(last, rest)| { + *last == 1 && rest.iter().all(|&byte| byte == 0) + }) + } } From d28435b09fb9e0ef9b789d0d3ee3a5dfd13492e6 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 20:01:18 +0100 Subject: [PATCH 69/77] docs: update docs Co-authored-by: Nenad --- contracts/src/token/erc20/utils/safe_erc20.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index b2641f7f..34df51a5 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -28,7 +28,7 @@ sol! { #[allow(missing_docs)] error SafeErc20FailedOperation(address token); - /// Indicates a failed [`ISafeErc20::decrease_allowance`] request. + /// Indicates a failed [`ISafeErc20::safe_decrease_allowance`] request. /// /// * `spender` - Address of future tokens' spender. /// * `current_allowance` - Current allowance of the `spender`. From 95f4d0807f5819f99a0e583663496fb9e7dc65bd Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Nov 2024 20:11:07 +0100 Subject: [PATCH 70/77] test: add unit tests --- contracts/src/token/erc20/utils/safe_erc20.rs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index 34df51a5..beb5efe0 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -394,3 +394,46 @@ impl SafeErc20 { }) } } + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::SafeErc20; + #[test] + fn encodes_true_empty_slice() { + assert_eq!(false, SafeErc20::encodes_true(&vec![])); + } + + #[test] + fn encodes_false_single_byte() { + assert_eq!(false, SafeErc20::encodes_true(&vec![0])); + } + + #[test] + fn encodes_true_single_byte() { + assert_eq!(true, SafeErc20::encodes_true(&vec![1])); + } + + #[test] + fn encodes_false_many_bytes() { + assert_eq!( + false, + SafeErc20::encodes_true(&vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + ); + } + + #[test] + fn encodes_true_many_bytes() { + assert_eq!( + true, + SafeErc20::encodes_true(&vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + ); + } + + #[test] + fn encodes_true_wrong_bytes() { + assert_eq!( + false, + SafeErc20::encodes_true(&vec![0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + ); + } +} From 7aa05399bdfa864262fc39f1b44a5e25d954d691 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 00:21:53 +0100 Subject: [PATCH 71/77] test: check events in E2E tests --- examples/safe-erc20/tests/abi/mod.rs | 9 +- examples/safe-erc20/tests/erc20.rs | 94 +++++++++++++++---- ...eturn.rs => erc20_that_does_not_return.rs} | 94 +++++++++++++++---- .../tests/usdt_approval_behavior.rs | 34 +++++-- 4 files changed, 182 insertions(+), 49 deletions(-) rename examples/safe-erc20/tests/{er20_that_doesnt_return.rs => erc20_that_does_not_return.rs} (82%) diff --git a/examples/safe-erc20/tests/abi/mod.rs b/examples/safe-erc20/tests/abi/mod.rs index ac07892c..aedd16fd 100644 --- a/examples/safe-erc20/tests/abi/mod.rs +++ b/examples/safe-erc20/tests/abi/mod.rs @@ -3,7 +3,7 @@ use alloy::sol; sol!( #[sol(rpc)] - contract SafeErc20 { + contract SafeErc20 { function safeTransfer(address token, address to, uint256 value) external; function safeTransferFrom(address token, address from, address to, uint256 value) external; function safeIncreaseAllowance(address token, address spender, uint256 value) external; @@ -13,4 +13,11 @@ sol!( error SafeErc20FailedOperation(address token); error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); } + + contract Erc20 { + #[derive(Debug, PartialEq)] + event Transfer(address indexed from, address indexed to, uint256 value); + #[derive(Debug, PartialEq)] + event Approval(address indexed owner, address indexed spender, uint256 value); + } ); diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index 0d1617c7..8f6057de 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -1,9 +1,9 @@ #![cfg(feature = "e2e")] -use abi::SafeErc20; +use abi::{Erc20, SafeErc20}; use alloy::primitives::uint; use alloy_primitives::U256; -use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; +use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; use mock::{erc20, erc20::ERC20Mock}; mod abi; @@ -13,7 +13,7 @@ mod transfers { use super::*; #[e2e::test] - async fn doesnt_revert_on_transfer( + async fn does_not_revert_on_transfer( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -36,12 +36,18 @@ mod transfers { assert_eq!(initial_safe_erc20_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_alice.safeTransfer( + let receipt = receipt!(safe_erc20_alice.safeTransfer( erc20_address, bob_addr, value ))?; + assert!(receipt.emits(Erc20::Transfer { + from: safe_erc20_addr, + to: bob_addr, + value + })); + let safe_erc20_balance = erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; @@ -53,7 +59,7 @@ mod transfers { } #[e2e::test] - async fn doesnt_revert_on_transfer_from( + async fn does_not_revert_on_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -78,13 +84,19 @@ mod transfers { assert_eq!(initial_alice_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_alice.safeTransferFrom( + let receipt = receipt!(safe_erc20_alice.safeTransferFrom( erc20_address, alice_addr, bob_addr, value ))?; + assert!(receipt.emits(Erc20::Transfer { + from: alice_addr, + to: bob_addr, + value + })); + let alice_balance = erc20_alice.balanceOf(alice_addr).call().await?._0; let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; @@ -100,7 +112,7 @@ mod approvals { use super::super::*; #[e2e::test] - async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + async fn does_not_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -120,12 +132,18 @@ mod approvals { let value = uint!(100_U256); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -137,7 +155,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_force_approving_a_zero_allowance( + async fn does_not_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -155,12 +173,18 @@ mod approvals { U256::ZERO )); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: U256::ZERO, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -172,7 +196,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_increasing_the_allowance( + async fn does_not_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -192,12 +216,18 @@ mod approvals { let value = uint!(10_U256); - let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -251,7 +281,7 @@ mod approvals { use super::super::*; #[e2e::test] - async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + async fn does_not_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -273,12 +303,18 @@ mod approvals { let value = uint!(20_U256); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -290,7 +326,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_force_approving_a_zero_allowance( + async fn does_not_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -310,12 +346,18 @@ mod approvals { allowance )); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: U256::ZERO, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -327,7 +369,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_increasing_the_allowance( + async fn does_not_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -349,12 +391,18 @@ mod approvals { let value = uint!(10_U256); - let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: allowance + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -366,7 +414,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_decreasing_the_allowance_to_a_positive_value( + async fn does_not_revert_when_decreasing_the_allowance_to_a_positive_value( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -388,12 +436,18 @@ mod approvals { let value = uint!(50_U256); - let _ = receipt!(safe_erc20_alice.safeDecreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: allowance - value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() diff --git a/examples/safe-erc20/tests/er20_that_doesnt_return.rs b/examples/safe-erc20/tests/erc20_that_does_not_return.rs similarity index 82% rename from examples/safe-erc20/tests/er20_that_doesnt_return.rs rename to examples/safe-erc20/tests/erc20_that_does_not_return.rs index 31a13fae..66aafde9 100644 --- a/examples/safe-erc20/tests/er20_that_doesnt_return.rs +++ b/examples/safe-erc20/tests/erc20_that_does_not_return.rs @@ -1,9 +1,9 @@ #![cfg(feature = "e2e")] -use abi::SafeErc20; +use abi::{Erc20, SafeErc20}; use alloy::primitives::uint; use alloy_primitives::U256; -use e2e::{receipt, send, watch, Account, ReceiptExt, Revert}; +use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; use mock::{erc20_no_return, erc20_no_return::ERC20NoReturnMock}; mod abi; @@ -13,7 +13,7 @@ mod transfers { use super::*; #[e2e::test] - async fn doesnt_revert_on_transfer( + async fn does_not_revert_on_transfer( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -36,12 +36,18 @@ mod transfers { assert_eq!(initial_safe_erc20_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_alice.safeTransfer( + let receipt = receipt!(safe_erc20_alice.safeTransfer( erc20_address, bob_addr, value ))?; + assert!(receipt.emits(Erc20::Transfer { + from: safe_erc20_addr, + to: bob_addr, + value, + })); + let safe_erc20_balance = erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; @@ -53,7 +59,7 @@ mod transfers { } #[e2e::test] - async fn doesnt_revert_on_transfer_from( + async fn does_not_revert_on_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -78,13 +84,19 @@ mod transfers { assert_eq!(initial_alice_balance, balance); assert_eq!(initial_bob_balance, U256::ZERO); - let _ = receipt!(safe_erc20_alice.safeTransferFrom( + let receipt = receipt!(safe_erc20_alice.safeTransferFrom( erc20_address, alice_addr, bob_addr, value ))?; + assert!(receipt.emits(Erc20::Transfer { + from: alice_addr, + to: bob_addr, + value, + })); + let alice_balance = erc20_alice.balanceOf(alice_addr).call().await?._0; let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; @@ -100,7 +112,7 @@ mod approvals { use super::super::*; #[e2e::test] - async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + async fn does_not_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -121,12 +133,18 @@ mod approvals { let value = uint!(100_U256); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -138,7 +156,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_force_approving_a_zero_allowance( + async fn does_not_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -157,12 +175,18 @@ mod approvals { U256::ZERO )); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: U256::ZERO, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -174,7 +198,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_increasing_the_allowance( + async fn does_not_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -195,12 +219,18 @@ mod approvals { let value = uint!(10_U256); - let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -255,7 +285,7 @@ mod approvals { use super::super::*; #[e2e::test] - async fn doesnt_revert_when_force_approving_a_non_zero_allowance( + async fn does_not_revert_when_force_approving_a_non_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -278,12 +308,18 @@ mod approvals { let value = uint!(20_U256); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -295,7 +331,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_force_approving_a_zero_allowance( + async fn does_not_revert_when_force_approving_a_zero_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -316,12 +352,18 @@ mod approvals { allowance )); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, spender_addr, U256::ZERO ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: U256::ZERO, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -333,7 +375,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_increasing_the_allowance( + async fn does_not_revert_when_increasing_the_allowance( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -356,12 +398,18 @@ mod approvals { let value = uint!(10_U256); - let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: allowance + value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() @@ -373,7 +421,7 @@ mod approvals { } #[e2e::test] - async fn doesnt_revert_when_decreasing_the_allowance_to_a_positive_value( + async fn does_not_revert_when_decreasing_the_allowance_to_a_positive_value( alice: Account, ) -> eyre::Result<()> { let safe_erc20_addr = @@ -396,12 +444,18 @@ mod approvals { let value = uint!(50_U256); - let _ = receipt!(safe_erc20_alice.safeDecreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, spender_addr, value ))?; + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: spender_addr, + value: allowance - value, + })); + let spender_allowance = erc20_alice .allowance(safe_erc20_addr, spender_addr) .call() diff --git a/examples/safe-erc20/tests/usdt_approval_behavior.rs b/examples/safe-erc20/tests/usdt_approval_behavior.rs index 064fc92f..9530a3ca 100644 --- a/examples/safe-erc20/tests/usdt_approval_behavior.rs +++ b/examples/safe-erc20/tests/usdt_approval_behavior.rs @@ -1,8 +1,8 @@ #![cfg(feature = "e2e")] -use abi::SafeErc20; +use abi::{Erc20, SafeErc20}; use alloy::primitives::uint; -use e2e::{receipt, watch, Account, ReceiptExt}; +use e2e::{receipt, watch, Account, EventExt, ReceiptExt}; use mock::{erc20_force_approve, erc20_force_approve::ERC20ForceApproveMock}; mod abi; @@ -33,11 +33,17 @@ async fn safe_increase_allowance_works( erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(initial_bob_allowance, init_approval); - let _ = receipt!(safe_erc20_alice.safeIncreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeIncreaseAllowance( erc20_address, bob_addr, value - )); + ))?; + + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: bob_addr, + value: init_approval + value, + })); let bob_allowance = erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; @@ -71,11 +77,17 @@ async fn safe_decrease_allowance_works( erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(initial_bob_allowance, init_approval); - let _ = receipt!(safe_erc20_alice.safeDecreaseAllowance( + let receipt = receipt!(safe_erc20_alice.safeDecreaseAllowance( erc20_address, bob_addr, value - )); + ))?; + + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: bob_addr, + value: init_approval - value, + })); let bob_allowance = erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; @@ -106,11 +118,17 @@ async fn force_approve_works(alice: Account, bob: Account) -> eyre::Result<()> { erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; assert_eq!(initial_bob_allowance, init_approval); - let _ = receipt!(safe_erc20_alice.forceApprove( + let receipt = receipt!(safe_erc20_alice.forceApprove( erc20_address, bob_addr, updated_approval - )); + ))?; + + assert!(receipt.emits(Erc20::Approval { + owner: safe_erc20_addr, + spender: bob_addr, + value: updated_approval, + })); let bob_allowance = erc20_alice.allowance(safe_erc20_addr, bob_addr).call().await?._0; From 925e01ae61313721f36c10be42569c566fc99805 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 00:57:12 +0100 Subject: [PATCH 72/77] test: E2E tests for failed internal Erc20 operations --- examples/safe-erc20/tests/abi/mod.rs | 2 + examples/safe-erc20/tests/erc20.rs | 83 +++++++++++++++++++ .../tests/erc20_that_does_not_return.rs | 81 ++++++++++++++++++ 3 files changed, 166 insertions(+) diff --git a/examples/safe-erc20/tests/abi/mod.rs b/examples/safe-erc20/tests/abi/mod.rs index aedd16fd..8a441879 100644 --- a/examples/safe-erc20/tests/abi/mod.rs +++ b/examples/safe-erc20/tests/abi/mod.rs @@ -15,6 +15,8 @@ sol!( } contract Erc20 { + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + #[derive(Debug, PartialEq)] event Transfer(address indexed from, address indexed to, uint256 value); #[derive(Debug, PartialEq)] diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index 8f6057de..9b3dd6ed 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -58,6 +58,46 @@ mod transfers { Ok(()) } + #[e2e::test] + async fn reverts_on_transfer_with_internal_error( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); + let bob_addr = bob.address(); + + let value = uint!(1_U256); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let initial_safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; + + let err = send!(safe_erc20_alice.safeTransfer( + erc20_address, + bob_addr, + value + )) + .expect_err("should not transfer when insufficient balance"); + + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + let safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; + + assert_eq!(initial_safe_erc20_balance, safe_erc20_balance); + assert_eq!(initial_bob_balance, bob_balance); + + Ok(()) + } + #[e2e::test] async fn does_not_revert_on_transfer_from( alice: Account, @@ -105,6 +145,49 @@ mod transfers { Ok(()) } + + #[e2e::test] + async fn reverts_on_transfer_from_internal_error( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let value = uint!(1_U256); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.approve(safe_erc20_addr, value)); + + let initial_alice_balance = + erc20_alice.balanceOf(alice_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; + + let err = send!(safe_erc20_alice.safeTransferFrom( + erc20_address, + alice_addr, + bob_addr, + value + )) + .expect_err("should not transfer when insufficient balance"); + + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + let alice_balance = erc20_alice.balanceOf(alice_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; + + assert_eq!(initial_alice_balance, alice_balance); + assert_eq!(initial_bob_balance, bob_balance); + + Ok(()) + } } mod approvals { diff --git a/examples/safe-erc20/tests/erc20_that_does_not_return.rs b/examples/safe-erc20/tests/erc20_that_does_not_return.rs index 66aafde9..c7199ff5 100644 --- a/examples/safe-erc20/tests/erc20_that_does_not_return.rs +++ b/examples/safe-erc20/tests/erc20_that_does_not_return.rs @@ -58,6 +58,45 @@ mod transfers { Ok(()) } + #[e2e::test] + async fn reverts_on_transfer_with_internal_error( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); + let bob_addr = bob.address(); + + let value = uint!(1_U256); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let initial_safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; + + let err = send!(safe_erc20_alice.safeTransfer( + erc20_address, + bob_addr, + value + )) + .expect_err("should not transfer when insufficient balance"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + let safe_erc20_balance = + erc20_alice.balanceOf(safe_erc20_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; + + assert_eq!(initial_safe_erc20_balance, safe_erc20_balance); + assert_eq!(initial_bob_balance, bob_balance); + + Ok(()) + } + #[e2e::test] async fn does_not_revert_on_transfer_from( alice: Account, @@ -105,6 +144,48 @@ mod transfers { Ok(()) } + + #[e2e::test] + async fn reverts_on_transfer_from_internal_error( + alice: Account, + bob: Account, + ) -> eyre::Result<()> { + let safe_erc20_addr = alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = SafeErc20::new(safe_erc20_addr, &alice.wallet); + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let value = uint!(1_U256); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.approve(safe_erc20_addr, value)); + + let initial_alice_balance = + erc20_alice.balanceOf(alice_addr).call().await?._0; + let initial_bob_balance = + erc20_alice.balanceOf(bob_addr).call().await?._0; + + let err = send!(safe_erc20_alice.safeTransferFrom( + erc20_address, + alice_addr, + bob_addr, + value + )) + .expect_err("should not transfer when insufficient balance"); + assert!(err.reverted_with(SafeErc20::SafeErc20FailedOperation { + token: erc20_address + })); + + let alice_balance = erc20_alice.balanceOf(alice_addr).call().await?._0; + let bob_balance = erc20_alice.balanceOf(bob_addr).call().await?._0; + + assert_eq!(initial_alice_balance, alice_balance); + assert_eq!(initial_bob_balance, bob_balance); + + Ok(()) + } } mod approvals { From b48ea0efd5abd3764d3c77fb0bd1ae26c90d4732 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 01:04:50 +0100 Subject: [PATCH 73/77] test: E2E check for math overflow --- examples/safe-erc20/tests/erc20.rs | 38 +++++++++++++++++- .../tests/erc20_that_does_not_return.rs | 39 ++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/examples/safe-erc20/tests/erc20.rs b/examples/safe-erc20/tests/erc20.rs index 9b3dd6ed..ae534333 100644 --- a/examples/safe-erc20/tests/erc20.rs +++ b/examples/safe-erc20/tests/erc20.rs @@ -3,7 +3,10 @@ use abi::{Erc20, SafeErc20}; use alloy::primitives::uint; use alloy_primitives::U256; -use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; +use e2e::{ + receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, + Revert, +}; use mock::{erc20, erc20::ERC20Mock}; mod abi; @@ -321,6 +324,39 @@ mod approvals { Ok(()) } + #[e2e::test] + async fn panics_when_increasing_the_allowance_overflow( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20::deploy(&alice.wallet).await?; + let erc20_alice = ERC20Mock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_addr, + spender_addr, + U256::MAX + )); + + let value = uint!(1_U256); + + let err = send!(safe_erc20_alice.safeIncreaseAllowance( + erc20_address, + spender_addr, + value + )) + .expect_err("should not exceed U256::MAX"); + + assert!(err.panicked_with(PanicCode::ArithmeticOverflow)); + + Ok(()) + } + #[e2e::test] async fn reverts_when_decreasing_the_allowance( alice: Account, diff --git a/examples/safe-erc20/tests/erc20_that_does_not_return.rs b/examples/safe-erc20/tests/erc20_that_does_not_return.rs index c7199ff5..07709621 100644 --- a/examples/safe-erc20/tests/erc20_that_does_not_return.rs +++ b/examples/safe-erc20/tests/erc20_that_does_not_return.rs @@ -3,7 +3,10 @@ use abi::{Erc20, SafeErc20}; use alloy::primitives::uint; use alloy_primitives::U256; -use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; +use e2e::{ + receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, + Revert, +}; use mock::{erc20_no_return, erc20_no_return::ERC20NoReturnMock}; mod abi; @@ -322,6 +325,40 @@ mod approvals { Ok(()) } + #[e2e::test] + async fn panics_when_increasing_the_allowance_overflow( + alice: Account, + ) -> eyre::Result<()> { + let safe_erc20_addr = + alice.as_deployer().deploy().await?.address()?; + let safe_erc20_alice = + SafeErc20::new(safe_erc20_addr, &alice.wallet); + let spender_addr = alice.address(); + + let erc20_address = erc20_no_return::deploy(&alice.wallet).await?; + let erc20_alice = + ERC20NoReturnMock::new(erc20_address, &alice.wallet); + + let _ = watch!(erc20_alice.regular_approve( + safe_erc20_addr, + spender_addr, + U256::MAX + )); + + let value = uint!(1_U256); + + let err = send!(safe_erc20_alice.safeIncreaseAllowance( + erc20_address, + spender_addr, + value + )) + .expect_err("should not exceed U256::MAX"); + + assert!(err.panicked_with(PanicCode::ArithmeticOverflow)); + + Ok(()) + } + #[e2e::test] async fn reverts_when_decreasing_the_allowance( alice: Account, From b765521293dd15419d7815902235c9ba11084ab4 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 01:07:01 +0100 Subject: [PATCH 74/77] docs: update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3bfdb06..a418140d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- +- `SafeErc20` Utility. #289 ### Changed From 893a5b679b598b32a948739399edd756f178f7cf Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 01:15:27 +0100 Subject: [PATCH 75/77] fix: apply clippy comments --- contracts/src/token/erc20/utils/safe_erc20.rs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index beb5efe0..bd6a0b36 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -2,6 +2,7 @@ //! contract returns false). Tokens that return no value (and instead revert or //! throw on failure) are also supported, non-reverting calls are assumed to be //! successful. +//! //! To use this library you can add a `#[inherit(SafeErc20)]` attribute to //! your contract, which allows you to call the safe operations as //! `contract.safe_transfer(token_addr, ...)`, etc. @@ -233,7 +234,7 @@ impl ISafeErc20 for SafeErc20 { ) -> Result<(), Self::Error> { let call = IErc20::transferCall { to, value }; - self.call_optional_return(token, &call) + Self::call_optional_return(token, &call) } fn safe_transfer_from( @@ -245,7 +246,7 @@ impl ISafeErc20 for SafeErc20 { ) -> Result<(), Self::Error> { let call = IErc20::transferFromCall { from, to, value }; - self.call_optional_return(token, &call) + Self::call_optional_return(token, &call) } fn safe_increase_allowance( @@ -254,7 +255,7 @@ impl ISafeErc20 for SafeErc20 { spender: Address, value: U256, ) -> Result<(), Self::Error> { - let current_allowance = self.allowance(token, spender)?; + let current_allowance = Self::allowance(token, spender)?; let new_allowance = current_allowance .checked_add(value) .expect("should not exceed `U256::MAX` for allowance"); @@ -267,7 +268,7 @@ impl ISafeErc20 for SafeErc20 { spender: Address, requested_decrease: U256, ) -> Result<(), Self::Error> { - let current_allowance = self.allowance(token, spender)?; + let current_allowance = Self::allowance(token, spender)?; if current_allowance < requested_decrease { return Err(SafeErc20FailedDecreaseAllowance { @@ -294,7 +295,7 @@ impl ISafeErc20 for SafeErc20 { let approve_call = IErc20::approveCall { spender, value }; // Try performing the approval with the desired value - if self.call_optional_return(token, &approve_call).is_ok() { + if Self::call_optional_return(token, &approve_call).is_ok() { return Ok(()); } @@ -302,8 +303,8 @@ impl ISafeErc20 for SafeErc20 { // approval let reset_approval_call = IErc20::approveCall { spender, value: U256::ZERO }; - self.call_optional_return(token, &reset_approval_call)?; - self.call_optional_return(token, &approve_call) + Self::call_optional_return(token, &reset_approval_call)?; + Self::call_optional_return(token, &approve_call) } } @@ -314,7 +315,6 @@ impl SafeErc20 { /// /// # Arguments /// - /// * `&mut self` - Write access to the contract's state. /// * `token` - Address of the ERC-20 token contract. /// * `call` - [`IErc20`] call that implements [`SolCall`] trait. /// @@ -327,7 +327,6 @@ impl SafeErc20 { /// If the call returns value that is not `true`, then the error /// [`Error::SafeErc20FailedOperation`] is returned. fn call_optional_return( - &self, token: Address, call: &impl SolCall, ) -> Result<(), Error> { @@ -350,7 +349,6 @@ impl SafeErc20 { /// /// # Arguments /// - /// * `&mut self` - Write access to the contract's state. /// * `token` - Address of the ERC-20 token contract. /// * `spender` - Account that will spend the tokens. /// @@ -360,11 +358,7 @@ impl SafeErc20 { /// [`Error::SafeErc20FailedOperation`] is returned. /// If the contract fails to read `spender`'s allowance, then the error /// [`Error::SafeErc20FailedOperation`] is returned. - fn allowance( - &mut self, - token: Address, - spender: Address, - ) -> Result { + fn allowance(token: Address, spender: Address) -> Result { if !Address::has_code(&token) { return Err(SafeErc20FailedOperation { token }.into()); } From 9a26dbf612265aa52e96737797038e7c5e7f6002 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 7 Nov 2024 13:36:45 +0400 Subject: [PATCH 76/77] grammar fixes --- contracts/src/token/erc20/utils/safe_erc20.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index bd6a0b36..f1820e43 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -3,7 +3,7 @@ //! throw on failure) are also supported, non-reverting calls are assumed to be //! successful. //! -//! To use this library you can add a `#[inherit(SafeErc20)]` attribute to +//! To use this library, you can add a `#[inherit(SafeErc20)]` attribute to //! your contract, which allows you to call the safe operations as //! `contract.safe_transfer(token_addr, ...)`, etc. @@ -299,7 +299,7 @@ impl ISafeErc20 for SafeErc20 { return Ok(()); } - // If that fails, reset allowance to zero, then retry the desired + // If that fails, reset the allowance to zero, then retry the desired // approval let reset_approval_call = IErc20::approveCall { spender, value: U256::ZERO }; From 1fe32cbbc884819023e8781c627a7bf2f4539f1f Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 7 Nov 2024 13:40:57 +0400 Subject: [PATCH 77/77] add dots on comments --- contracts/src/token/erc20/utils/safe_erc20.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc20/utils/safe_erc20.rs b/contracts/src/token/erc20/utils/safe_erc20.rs index f1820e43..1669a69c 100644 --- a/contracts/src/token/erc20/utils/safe_erc20.rs +++ b/contracts/src/token/erc20/utils/safe_erc20.rs @@ -294,13 +294,13 @@ impl ISafeErc20 for SafeErc20 { ) -> Result<(), Self::Error> { let approve_call = IErc20::approveCall { spender, value }; - // Try performing the approval with the desired value + // Try performing the approval with the desired value. if Self::call_optional_return(token, &approve_call).is_ok() { return Ok(()); } // If that fails, reset the allowance to zero, then retry the desired - // approval + // approval. let reset_approval_call = IErc20::approveCall { spender, value: U256::ZERO }; Self::call_optional_return(token, &reset_approval_call)?; @@ -345,7 +345,7 @@ impl SafeErc20 { } /// Returns the remaining number of ERC-20 tokens that `spender` - /// will be allowed to spend on behalf of owner. + /// will be allowed to spend on behalf of an owner. /// /// # Arguments ///