From c7006e0190bc0078b59710ea48a4d2fb612ea543 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Mon, 4 Aug 2025 09:10:56 +0200 Subject: [PATCH 01/11] ref: cleanup IErc721Wrapper trait --- .../src/token/erc721/extensions/wrapper.rs | 90 +++++++++---------- contracts/src/token/erc721/utils/holder.rs | 6 +- 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/contracts/src/token/erc721/extensions/wrapper.rs b/contracts/src/token/erc721/extensions/wrapper.rs index b4825ac37..e1b7643b8 100644 --- a/contracts/src/token/erc721/extensions/wrapper.rs +++ b/contracts/src/token/erc721/extensions/wrapper.rs @@ -21,7 +21,8 @@ use stylus_sdk::{ }; use crate::token::erc721::{ - self, interface::Erc721Interface, Erc721, RECEIVER_FN_SELECTOR, + self, interface::Erc721Interface, receiver::IErc721Receiver, Erc721, + RECEIVER_FN_SELECTOR, }; #[cfg_attr(coverage_nightly, coverage(off))] @@ -120,7 +121,7 @@ unsafe impl TopLevelStorage for Erc721Wrapper {} /// Interface of an extension of the ERC-721 token contract that supports token /// wrapping. #[interface_id] -pub trait IErc721Wrapper { +pub trait IErc721Wrapper: IErc721Receiver { /// The error type associated to this trait implementation. type Error: Into>; @@ -176,34 +177,6 @@ pub trait IErc721Wrapper { token_ids: Vec, ) -> Result; - /// Overrides [`erc721::IErc721Receiver::on_erc721_received`] to allow - /// minting on direct ERC-721 transfers to this contract. - /// - /// # Arguments - /// - /// * `&mut self` - Write access to the contract's state. - /// * `operator` - The operator of the transfer. - /// * `from` - The sender of the transfer. - /// * `token_id` - The token id of the transfer. - /// * `data` - The data of the transfer. - /// - /// # Errors - /// - /// * [`Error::UnsupportedToken`] - If `msg::sender()` is not the underlying - /// token. - /// * [`Error::InvalidSender`] - If `token_id` already exists. - /// * [`Error::InvalidReceiver`] - If `to` is [`Address::ZERO`]. - /// * [`Error::InvalidReceiver`] - If - /// [`erc721::IErc721Receiver::on_erc721_received`] hasn't returned its - /// interface id or returned with an error. - fn on_erc721_received( - &mut self, - operator: Address, - from: Address, - token_id: U256, - data: Bytes, - ) -> Result; - /// Returns the underlying token. /// /// # Arguments @@ -317,7 +290,25 @@ impl Erc721Wrapper { Ok(true) } - /// Check [`IErc721Wrapper::on_erc721_received()`] for more information. + /// Allow minting on direct ERC-721 transfers to this contract. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `operator` - The operator of the transfer. + /// * `from` - The sender of the transfer. + /// * `token_id` - The token id of the transfer. + /// * `data` - The data of the transfer. + /// + /// # Errors + /// + /// * [`Error::UnsupportedToken`] - If `msg::sender()` is not the underlying + /// token. + /// * [`Error::InvalidSender`] - If `token_id` already exists. + /// * [`Error::InvalidReceiver`] - If `to` is [`Address::ZERO`]. + /// * [`Error::InvalidReceiver`] - If + /// [`erc721::IErc721Receiver::on_erc721_received`] hasn't returned its + /// interface id or returned with an error. #[allow(clippy::missing_errors_doc)] pub fn on_erc721_received( &mut self, @@ -403,6 +394,7 @@ impl Erc721Wrapper { #[cfg(test)] mod tests { use alloy_primitives::uint; + use alloy_sol_types::SolError; use motsu::prelude::*; use stylus_sdk::abi::Bytes; @@ -534,21 +526,26 @@ mod tests { ) -> Result { self.wrapper.withdraw_to(account, token_ids, &mut self.erc721) } + } + #[public] + impl IErc721Receiver for Erc721WrapperTestExample { fn on_erc721_received( &mut self, operator: Address, from: Address, token_id: U256, data: Bytes, - ) -> Result { - self.wrapper.on_erc721_received( - operator, - from, - token_id, - &data, - &mut self.erc721, - ) + ) -> Result> { + self.wrapper + .on_erc721_received( + operator, + from, + token_id, + &data, + &mut self.erc721, + ) + .map_err(|e| e.into()) } } @@ -991,11 +988,10 @@ mod tests { ) .motsu_expect_err("should return Error::UnsupportedToken"); - assert!(matches!( + assert_eq!( err, - Error::UnsupportedToken(ERC721UnsupportedToken { token }) - if token == invalid_operator - )); + ERC721UnsupportedToken { token: invalid_operator }.abi_encode() + ); } #[motsu::test] @@ -1022,12 +1018,10 @@ mod tests { .on_erc721_received(operator, alice, token_id, vec![].into()) .motsu_expect_err("should return Error::Erc721"); - assert!(matches!( + assert_eq!( err, - Error::InvalidSender( - erc721::ERC721InvalidSender { sender } - ) if sender.is_zero() - )); + erc721::ERC721InvalidSender { sender: Address::ZERO }.abi_encode() + ); } #[motsu::test] diff --git a/contracts/src/token/erc721/utils/holder.rs b/contracts/src/token/erc721/utils/holder.rs index 83610e39a..66c953022 100644 --- a/contracts/src/token/erc721/utils/holder.rs +++ b/contracts/src/token/erc721/utils/holder.rs @@ -11,13 +11,11 @@ use crate::token::erc721::receiver::{IErc721Receiver, RECEIVER_FN_SELECTOR}; pub struct Erc721Holder; #[public] -#[implements(IErc721Receiver>)] +#[implements(IErc721Receiver)] impl Erc721Holder {} #[public] impl IErc721Receiver for Erc721Holder { - type Error = Vec; - #[selector(name = "onERC721Received")] fn on_erc721_received( &mut self, @@ -25,7 +23,7 @@ impl IErc721Receiver for Erc721Holder { _from: Address, _token_id: U256, _data: Bytes, - ) -> Result { + ) -> Result> { Ok(RECEIVER_FN_SELECTOR) } } From 4d67e811bf2a81d04ecb078e552fa6d53137829c Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Mon, 4 Aug 2025 09:12:16 +0200 Subject: [PATCH 02/11] ref: cleanup IErc1155Receiver trait --- contracts/src/token/erc1155/receiver.rs | 7 ++----- contracts/src/token/erc1155/utils/holder.rs | 8 +++----- contracts/src/token/erc721/receiver.rs | 5 +---- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/contracts/src/token/erc1155/receiver.rs b/contracts/src/token/erc1155/receiver.rs index 77d26c95c..7749878ad 100644 --- a/contracts/src/token/erc1155/receiver.rs +++ b/contracts/src/token/erc1155/receiver.rs @@ -62,9 +62,6 @@ sol_interface! { /// ERC-1155 token transfers. #[interface_id] pub trait IErc1155Receiver: IErc165 { - /// The error type associated to the trait implementation. - type Error: Into>; - /// Handles the receipt of a single ERC-1155 token type. This function /// is called at the end of [`super::IErc1155::safe_transfer_from`] after /// the balance has been updated. @@ -89,7 +86,7 @@ pub trait IErc1155Receiver: IErc165 { id: U256, value: U256, data: Bytes, - ) -> Result; + ) -> Result>; /// Handles the receipt of multiple ERC-1155 token types. This function /// is called at the end of @@ -118,5 +115,5 @@ pub trait IErc1155Receiver: IErc165 { ids: Vec, values: Vec, data: Bytes, - ) -> Result; + ) -> Result>; } diff --git a/contracts/src/token/erc1155/utils/holder.rs b/contracts/src/token/erc1155/utils/holder.rs index d83625a5b..d3a748c00 100644 --- a/contracts/src/token/erc1155/utils/holder.rs +++ b/contracts/src/token/erc1155/utils/holder.rs @@ -18,13 +18,11 @@ use crate::{ pub struct Erc1155Holder; #[public] -#[implements(IErc1155Receiver>, IErc165)] +#[implements(IErc1155Receiver, IErc165)] impl Erc1155Holder {} #[public] impl IErc1155Receiver for Erc1155Holder { - type Error = Vec; - #[selector(name = "onERC1155Received")] fn on_erc1155_received( &mut self, @@ -33,7 +31,7 @@ impl IErc1155Receiver for Erc1155Holder { _id: U256, _value: U256, _data: Bytes, - ) -> Result { + ) -> Result> { Ok(SINGLE_TRANSFER_FN_SELECTOR) } @@ -45,7 +43,7 @@ impl IErc1155Receiver for Erc1155Holder { _ids: Vec, _values: Vec, _data: Bytes, - ) -> Result { + ) -> Result> { Ok(BATCH_TRANSFER_FN_SELECTOR) } } diff --git a/contracts/src/token/erc721/receiver.rs b/contracts/src/token/erc721/receiver.rs index e61124904..cddeff391 100644 --- a/contracts/src/token/erc721/receiver.rs +++ b/contracts/src/token/erc721/receiver.rs @@ -41,9 +41,6 @@ sol_interface! { /// contracts. #[interface_id] pub trait IErc721Receiver { - /// The error type associated to the trait implementation. - type Error: Into>; - /// This function is called whenever an [`super::Erc721`] `token_id` /// token is transferred to this contract via /// [`super::IErc721::safe_transfer_from`] or @@ -70,5 +67,5 @@ pub trait IErc721Receiver { from: Address, token_id: U256, data: Bytes, - ) -> Result; + ) -> Result>; } From 7a38223232867f54fb52d45aa17bdfa29204479f Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Mon, 4 Aug 2025 09:18:59 +0200 Subject: [PATCH 03/11] fix: erc721-wrapper example --- examples/erc721-wrapper/src/lib.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/erc721-wrapper/src/lib.rs b/examples/erc721-wrapper/src/lib.rs index eb3d39fd2..6d79420ae 100644 --- a/examples/erc721-wrapper/src/lib.rs +++ b/examples/erc721-wrapper/src/lib.rs @@ -7,6 +7,7 @@ use openzeppelin_stylus::{ token::erc721::{ self, extensions::{wrapper, Erc721Wrapper, IErc721Burnable, IErc721Wrapper}, + receiver::IErc721Receiver, Erc721, IErc721, }, utils::introspection::erc165::IErc165, @@ -25,7 +26,7 @@ struct Erc721WrapperExample { } #[public] -#[implements(IErc721, IErc721Burnable, IErc721Wrapper, IErc165)] +#[implements(IErc721, IErc721Burnable, IErc721Wrapper, IErc165, IErc721Receiver)] impl Erc721WrapperExample { #[constructor] fn constructor(&mut self, underlying_token: Address) { @@ -130,21 +131,26 @@ impl IErc721Wrapper for Erc721WrapperExample { ) -> Result { self.erc721_wrapper.withdraw_to(account, token_ids, &mut self.erc721) } +} +#[public] +impl IErc721Receiver for Erc721WrapperExample { fn on_erc721_received( &mut self, operator: Address, from: Address, token_id: U256, data: Bytes, - ) -> Result { - self.erc721_wrapper.on_erc721_received( - operator, - from, - token_id, - &data, - &mut self.erc721, - ) + ) -> Result> { + self.erc721_wrapper + .on_erc721_received( + operator, + from, + token_id, + &data, + &mut self.erc721, + ) + .map_err(|e| e.into()) } } From d26380e31eeed8be7623eb3beeed3f9d06553f82 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Mon, 4 Aug 2025 13:56:35 +0200 Subject: [PATCH 04/11] ref: use Stylus Erc1155ReceiverMock in E2E tests + apply clippy suggestions --- Cargo.lock | 15 ++ Cargo.toml | 2 + contracts/src/access/ownable.rs | 3 +- .../token/erc1155/extensions/metadata_uri.rs | 2 +- .../src/token/erc20/extensions/flash_mint.rs | 2 +- .../src/token/erc721/extensions/wrapper.rs | 2 +- examples/erc1155-receiver-mock/Cargo.toml | 31 +++ examples/erc1155-receiver-mock/src/lib.rs | 211 ++++++++++++++++ examples/erc1155-receiver-mock/src/main.rs | 10 + examples/erc1155/tests/abi/mod.rs | 7 + examples/erc1155/tests/erc1155.rs | 228 ++++++++++++------ examples/erc1155/tests/mock/receiver.rs | 113 --------- 12 files changed, 435 insertions(+), 191 deletions(-) create mode 100644 examples/erc1155-receiver-mock/Cargo.toml create mode 100644 examples/erc1155-receiver-mock/src/lib.rs create mode 100644 examples/erc1155-receiver-mock/src/main.rs delete mode 100644 examples/erc1155/tests/mock/receiver.rs diff --git a/Cargo.lock b/Cargo.lock index 04ef08e0f..16347fdbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1776,6 +1776,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc1155-receiver-mock-example" +version = "0.3.0-alpha.1" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "e2e", + "eyre", + "openzeppelin-stylus", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc1155-supply-example" version = "0.3.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 7b458ac2e..a7fb31c63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ members = [ "examples/basic/script", "examples/upgradeable-beacon", "examples/beacon-proxy", + "examples/erc1155-receiver-mock", ] default-members = [ "contracts", @@ -65,6 +66,7 @@ default-members = [ "examples/basic/token", "examples/upgradeable-beacon", "examples/beacon-proxy", + "examples/erc1155-receiver-mock", ] exclude = ["fuzz"] diff --git a/contracts/src/access/ownable.rs b/contracts/src/access/ownable.rs index fcc2ca4ef..2ee59e822 100644 --- a/contracts/src/access/ownable.rs +++ b/contracts/src/access/ownable.rs @@ -199,8 +199,7 @@ impl Ownable { if new_owner.is_zero() { return Err(Error::InvalidOwner(OwnableInvalidOwner { owner: Address::ZERO, - }) - .into()); + })); } self._transfer_ownership(new_owner); diff --git a/contracts/src/token/erc1155/extensions/metadata_uri.rs b/contracts/src/token/erc1155/extensions/metadata_uri.rs index 038579d11..55ac9868e 100644 --- a/contracts/src/token/erc1155/extensions/metadata_uri.rs +++ b/contracts/src/token/erc1155/extensions/metadata_uri.rs @@ -34,7 +34,7 @@ mod sol { #[storage] pub struct Erc1155MetadataUri { /// Used as the URI for all token types by relying on ID substitution, - /// e.g. https://token-cdn-domain/{id}.json. + /// e.g. . pub(crate) uri: StorageString, } diff --git a/contracts/src/token/erc20/extensions/flash_mint.rs b/contracts/src/token/erc20/extensions/flash_mint.rs index ff767ae67..4b725d48f 100644 --- a/contracts/src/token/erc20/extensions/flash_mint.rs +++ b/contracts/src/token/erc20/extensions/flash_mint.rs @@ -125,7 +125,7 @@ mod borrower { use stylus_sdk::prelude::sol_interface; sol_interface! { - /// Interface of the ERC-3156 FlashBorrower, as defined in [ERC-3156]. + /// Interface of the ERC-3156 `FlashBorrower`, as defined in [ERC-3156]. /// /// [ERC-3156]: https://eips.ethereum.org/EIPS/eip-3156 interface IERC3156FlashBorrower { diff --git a/contracts/src/token/erc721/extensions/wrapper.rs b/contracts/src/token/erc721/extensions/wrapper.rs index e1b7643b8..ae7c1d923 100644 --- a/contracts/src/token/erc721/extensions/wrapper.rs +++ b/contracts/src/token/erc721/extensions/wrapper.rs @@ -327,7 +327,7 @@ impl Erc721Wrapper { erc721._safe_mint(from, token_id, &vec![].into())?; - Ok(RECEIVER_FN_SELECTOR.into()) + Ok(RECEIVER_FN_SELECTOR) } /// Check [`IErc721Wrapper::underlying()`] for more information. diff --git a/examples/erc1155-receiver-mock/Cargo.toml b/examples/erc1155-receiver-mock/Cargo.toml new file mode 100644 index 000000000..13ed9dcb9 --- /dev/null +++ b/examples/erc1155-receiver-mock/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "erc1155-receiver-mock-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true +alloy-sol-macro.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true + +[dev-dependencies] +alloy.workspace = true +e2e.workspace = true +tokio.workspace = true +eyre.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] + +[features] +e2e = [] +export-abi = ["stylus-sdk/export-abi"] + +[[bin]] +name = "erc1155-receiver-mock-example" +path = "src/main.rs" diff --git a/examples/erc1155-receiver-mock/src/lib.rs b/examples/erc1155-receiver-mock/src/lib.rs new file mode 100644 index 000000000..8bfea4e62 --- /dev/null +++ b/examples/erc1155-receiver-mock/src/lib.rs @@ -0,0 +1,211 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] +extern crate alloc; + +use alloc::vec::Vec; + +use openzeppelin_stylus::{ + token::erc1155::receiver::IErc1155Receiver, + utils::introspection::erc165::IErc165, +}; +pub use sol::*; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{ + aliases::{B32, U8}, + Address, U256, + }, + evm, + prelude::*, + storage::{StorageB32, StorageU8}, +}; + +mod sol { + use alloy_sol_macro::sol; + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + event Received(address operator, address from, uint256 id, uint256 value, bytes data); + + #[derive(Debug)] + #[allow(missing_docs)] + event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data); + } + + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + error CustomError(bytes4); + } +} + +/// Enum representing different revert types for testing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic, +} + +impl From for RevertType { + fn from(value: u8) -> Self { + match value { + 0 => RevertType::None, + 1 => RevertType::RevertWithoutMessage, + 2 => RevertType::RevertWithMessage, + 3 => RevertType::RevertWithCustomError, + 4 => RevertType::Panic, + _ => RevertType::None, + } + } +} + +impl From for RevertType { + fn from(value: U8) -> Self { + let revert_type: u8 = value.to_be_bytes::<1>()[0]; + RevertType::from(revert_type) + } +} + +impl From for u8 { + fn from(value: RevertType) -> Self { + match value { + RevertType::None => 0, + RevertType::RevertWithoutMessage => 1, + RevertType::RevertWithMessage => 2, + RevertType::RevertWithCustomError => 3, + RevertType::Panic => 4, + } + } +} + +#[entrypoint] +#[storage] +struct Erc1155ReceiverMock { + rec_retval: StorageB32, + bat_retval: StorageB32, + error_type: StorageU8, +} + +#[public] +#[implements(IErc1155Receiver, IErc165)] +impl Erc1155ReceiverMock { + #[constructor] + fn constructor( + &mut self, + rec_retval: B32, + bat_retval: B32, + error_type: U8, + ) -> Result<(), Vec> { + self.rec_retval.set(rec_retval); + self.bat_retval.set(bat_retval); + self.error_type.set(error_type); + Ok(()) + } +} + +#[public] +impl IErc1155Receiver for Erc1155ReceiverMock { + #[allow(deprecated)] + fn on_erc1155_received( + &mut self, + operator: Address, + from: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result> { + let error_type: RevertType = self.error_type.get().into(); + + match error_type { + RevertType::RevertWithoutMessage => { + return Err(vec![]); + } + RevertType::RevertWithMessage => { + return Err( + b"ERC1155ReceiverMock: reverting on receive".to_vec() + ); + } + RevertType::RevertWithCustomError => { + // For custom error, we'll just revert with the return value as + // data + let retval = self.rec_retval.get(); + return Err(retval.to_vec()); + } + RevertType::Panic => { + // Simulate a panic by dividing by zero + let _ = U256::from(0) / U256::from(0); + return Err(vec![]); + } + RevertType::None => {} + } + + // Emit the Received event + evm::log(Received { + operator, + from, + id, + value, + data: data.to_vec().into(), + }); + + // Return the configured return value + Ok(self.rec_retval.get()) + } + + #[allow(deprecated)] + fn on_erc1155_batch_received( + &mut self, + operator: Address, + from: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result> { + let error_type: RevertType = self.error_type.get().into(); + + match error_type { + RevertType::RevertWithoutMessage => { + return Err(vec![]); + } + RevertType::RevertWithMessage => { + return Err( + b"ERC1155ReceiverMock: reverting on batch receive".to_vec() + ); + } + RevertType::RevertWithCustomError => { + // For custom error, we'll just revert with the return value as + // data + let retval = self.bat_retval.get(); + return Err(retval.to_vec()); + } + RevertType::Panic => { + // Simulate a panic by dividing by zero + let _ = U256::from(0) / U256::from(0); + return Err(vec![]); + } + RevertType::None => {} + } + + // Emit the BatchReceived event + evm::log(BatchReceived { + operator, + from, + ids, + values, + data: data.to_vec().into(), + }); + + // Return the configured return value + Ok(self.bat_retval.get()) + } +} + +#[public] +impl IErc165 for Erc1155ReceiverMock { + fn supports_interface(&self, interface_id: B32) -> bool { + ::interface_id() == interface_id + || ::interface_id() == interface_id + } +} diff --git a/examples/erc1155-receiver-mock/src/main.rs b/examples/erc1155-receiver-mock/src/main.rs new file mode 100644 index 000000000..07c7b7b16 --- /dev/null +++ b/examples/erc1155-receiver-mock/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] + +#[cfg(not(any(test, feature = "export-abi")))] +#[no_mangle] +pub extern "C" fn main() {} + +#[cfg(feature = "export-abi")] +fn main() { + erc1155_receiver_mock_example::print_from_args(); +} diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 7eca18970..00e6a3c35 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -34,4 +34,11 @@ sol!( #[derive(Debug, PartialEq)] event ApprovalForAll(address indexed account, address indexed operator, bool approved); } + + contract Erc1155ReceiverMock { + #[derive(Debug, PartialEq)] + event Received(address operator, address from, uint256 id, uint256 value, bytes data); + #[derive(Debug, PartialEq)] + event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data); + } ); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 18a54db1f..c2ab0555e 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -1,15 +1,33 @@ #![cfg(feature = "e2e")] -use abi::Erc1155; +use abi::{Erc1155, Erc1155ReceiverMock}; use alloy::{ - primitives::{uint, Address, U256}, + primitives::{uint, Address, U256, U8}, sol_types::SolError, }; -use e2e::{receipt, send, watch, Account, EventExt, PanicCode, Revert}; -use mock::{receiver, receiver::ERC1155ReceiverMock}; +use e2e::{ + constructor, receipt, send, watch, Account, Constructor, EventExt, + PanicCode, Revert, +}; +use openzeppelin_stylus::token::erc1155::receiver::{ + BATCH_TRANSFER_FN_SELECTOR, SINGLE_TRANSFER_FN_SELECTOR, +}; mod abi; -mod mock; + +fn mock_receiver_constructor(error_type: U8) -> Constructor { + constructor!( + SINGLE_TRANSFER_FN_SELECTOR, + BATCH_TRANSFER_FN_SELECTOR, + error_type + ) +} + +const REVERT_TYPE_NONE: U8 = uint!(0_U8); +const REVERT_TYPE_REVERT_WITHOUT_MESSAGE: U8 = uint!(1_U8); +const REVERT_TYPE_REVERT_WITH_MESSAGE: U8 = uint!(2_U8); +const REVERT_TYPE_REVERT_WITH_CUSTOM_ERROR: U8 = uint!(3_U8); +const REVERT_TYPE_PANIC: U8 = uint!(4_U8); fn random_token_ids(size: usize) -> Vec { (0..size).map(U256::from).collect() @@ -128,9 +146,13 @@ async fn mints_to_receiver_contract(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_addr = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) - .await?; + let receiver_addr = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_NONE)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_id = random_token_ids(1)[0]; @@ -150,7 +172,7 @@ async fn mints_to_receiver_contract(alice: Account) -> eyre::Result<()> { value })); - assert!(receipt.emits(ERC1155ReceiverMock::Received { + assert!(receipt.emits(Erc1155ReceiverMock::Received { operator: alice_addr, from: Address::ZERO, id: token_id, @@ -172,11 +194,15 @@ async fn errors_when_receiver_reverts_with_reason_in_mint( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITH_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; @@ -206,11 +232,15 @@ async fn errors_when_receiver_reverts_without_reason_in_mint( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithoutMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITHOUT_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; @@ -237,9 +267,13 @@ async fn errors_when_receiver_panics_in_mint( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_PANIC)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; @@ -335,9 +369,13 @@ async fn mint_batch_transfer_to_receiver_contract( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_addr = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) - .await?; + let receiver_addr = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_NONE)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_ids = random_token_ids(2); @@ -367,7 +405,7 @@ async fn mint_batch_transfer_to_receiver_contract( values: values.clone() })); - assert!(receipt.emits(ERC1155ReceiverMock::BatchReceived { + assert!(receipt.emits(Erc1155ReceiverMock::BatchReceived { operator: alice_addr, from: Address::ZERO, ids: token_ids.clone(), @@ -401,11 +439,15 @@ async fn errors_when_receiver_reverts_with_reason_in_batch_mint( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITH_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let token_ids = random_token_ids(2); let values = random_values(2); @@ -435,11 +477,15 @@ async fn errors_when_receiver_reverts_without_reason_in_batch_mint( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithoutMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITHOUT_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let token_ids = random_token_ids(2); let values = random_values(2); @@ -466,9 +512,13 @@ async fn errors_when_receiver_panics_in_batch_mint( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_PANIC)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let token_ids = random_token_ids(2); let values = random_values(2); @@ -731,9 +781,13 @@ async fn safe_transfer_to_receiver_contract( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_addr = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) - .await?; + let receiver_addr = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_NONE)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_id = random_token_ids(1)[0]; @@ -767,7 +821,7 @@ async fn safe_transfer_to_receiver_contract( value })); - assert!(receipt.emits(ERC1155ReceiverMock::Received { + assert!(receipt.emits(Erc1155ReceiverMock::Received { operator: alice_addr, from: alice_addr, id: token_id, @@ -793,11 +847,15 @@ async fn errors_when_receiver_reverts_with_reason( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITH_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_id = random_token_ids(1)[0]; @@ -836,11 +894,15 @@ async fn errors_when_receiver_reverts_without_reason( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithoutMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITHOUT_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_id = random_token_ids(1)[0]; @@ -874,9 +936,13 @@ async fn errors_when_receiver_panics(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_PANIC)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_id = random_token_ids(1)[0]; @@ -1116,9 +1182,13 @@ async fn safe_batch_transfer_to_receiver_contract( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_addr = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) - .await?; + let receiver_addr = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_NONE)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_ids = random_token_ids(2); @@ -1162,7 +1232,7 @@ async fn safe_batch_transfer_to_receiver_contract( values: values.clone() })); - assert!(receipt.emits(ERC1155ReceiverMock::BatchReceived { + assert!(receipt.emits(Erc1155ReceiverMock::BatchReceived { operator: alice_addr, from: alice_addr, ids: token_ids.clone(), @@ -1202,11 +1272,15 @@ async fn errors_when_receiver_reverts_with_reason_in_batch_transfer( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITH_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_ids = random_token_ids(2); @@ -1245,11 +1319,15 @@ async fn errors_when_receiver_reverts_without_reason_in_batch_transfer( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = receiver::deploy( - &alice.wallet, - ERC1155ReceiverMock::RevertType::RevertWithoutMessage, - ) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor( + REVERT_TYPE_REVERT_WITHOUT_MESSAGE, + )) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_ids = random_token_ids(2); @@ -1285,9 +1363,13 @@ async fn errors_when_receiver_panics_in_batch_transfer( let contract_addr = alice.as_deployer().deploy().await?.contract_address; let contract = Erc1155::new(contract_addr, &alice.wallet); - let receiver_address = - receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) - .await?; + let receiver_address = alice + .as_deployer() + .with_constructor(mock_receiver_constructor(REVERT_TYPE_PANIC)) + .with_example_name("erc1155-receiver-mock") + .deploy() + .await? + .contract_address; let alice_addr = alice.address(); let token_ids = random_token_ids(2); diff --git a/examples/erc1155/tests/mock/receiver.rs b/examples/erc1155/tests/mock/receiver.rs deleted file mode 100644 index 15e7f0168..000000000 --- a/examples/erc1155/tests/mock/receiver.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![allow(dead_code)] -#![cfg(feature = "e2e")] -use alloy::{ - primitives::{aliases::B32, Address, U256}, - sol, -}; -use e2e::Wallet; -use stylus_sdk::{abi::Bytes, function_selector}; - -const REC_RETVAL: B32 = B32::new(function_selector!( - "onERC1155Received", - Address, - Address, - U256, - U256, - Bytes -)); - -const BAT_RETVAL: B32 = B32::new(function_selector!( - "onERC1155BatchReceived", - Address, - Address, - Vec, - Vec, - Bytes -)); - -sol! { - #[allow(missing_docs)] - // Built with Remix IDE; solc 0.8.24+commit.e11b9ed9 - #[sol(rpc, bytecode="60e060405234801562000010575f80fd5b5060405162000f7e38038062000f7e833981810160405281019062000036919062000181565b827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166080817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681525050817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191660a0817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681525050806004811115620000d857620000d7620001da565b5b60c0816004811115620000f057620000ef620001da565b5b8152505050505062000207565b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b620001378162000101565b811462000142575f80fd5b50565b5f8151905062000155816200012c565b92915050565b6005811062000168575f80fd5b50565b5f815190506200017b816200015b565b92915050565b5f805f606084860312156200019b576200019a620000fd565b5b5f620001aa8682870162000145565b9350506020620001bd8682870162000145565b9250506040620001d0868287016200016b565b9150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60805160a05160c051610d0d620002715f395f8181610153015281816101a30152818161022a015281816102d2015281816103a4015281816103f40152818161047b015261052301525f61036001525f8181610262015281816104b301526105ad0152610d0d5ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c806301ffc9a714610043578063bc197c8114610073578063f23a6e61146100a3575b5f80fd5b61005d60048036038101906100589190610635565b6100d3565b60405161006a919061067a565b60405180910390f35b61008d600480360381019061008891906107a3565b61013c565b60405161009a9190610889565b60405180910390f35b6100bd60048036038101906100b891906108d5565b61038d565b6040516100ca9190610889565b60405180910390f35b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f600160048111156101515761015061096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156101845761018361096b565b5b0361018d575f80fd5b600260048111156101a1576101a061096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156101d4576101d361096b565b5b03610214576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161020b90610a18565b60405180910390fd5b600360048111156102285761022761096b565b5b7f0000000000000000000000000000000000000000000000000000000000000000600481111561025b5761025a61096b565b5b036102bd577f00000000000000000000000000000000000000000000000000000000000000006040517f66435bc00000000000000000000000000000000000000000000000000000000081526004016102b49190610889565b60405180910390fd5b6004808111156102d0576102cf61096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156103035761030261096b565b5b03610319575f805f6103159190610a63565b9050505b7f9facaeece8596899cc39b65f0d1e262008ade8403076a2dfb6df2004fc8d96528989898989898989604051610356989796959493929190610b74565b60405180910390a17f0000000000000000000000000000000000000000000000000000000000000000905098975050505050505050565b5f600160048111156103a2576103a161096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156103d5576103d461096b565b5b036103de575f80fd5b600260048111156103f2576103f161096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156104255761042461096b565b5b03610465576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045c90610c50565b60405180910390fd5b600360048111156104795761047861096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156104ac576104ab61096b565b5b0361050e577f00000000000000000000000000000000000000000000000000000000000000006040517f66435bc00000000000000000000000000000000000000000000000000000000081526004016105059190610889565b60405180910390fd5b6004808111156105215761052061096b565b5b7f000000000000000000000000000000000000000000000000000000000000000060048111156105545761055361096b565b5b0361056a575f805f6105669190610a63565b9050505b7fe4b060c773f3fcca980bf840b0e2856ca36598bb4da2c0c3913b89050630df378787878787876040516105a396959493929190610c7d565b60405180910390a17f000000000000000000000000000000000000000000000000000000000000000090509695505050505050565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610614816105e0565b811461061e575f80fd5b50565b5f8135905061062f8161060b565b92915050565b5f6020828403121561064a576106496105d8565b5b5f61065784828501610621565b91505092915050565b5f8115159050919050565b61067481610660565b82525050565b5f60208201905061068d5f83018461066b565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6106bc82610693565b9050919050565b6106cc816106b2565b81146106d6575f80fd5b50565b5f813590506106e7816106c3565b92915050565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261070e5761070d6106ed565b5b8235905067ffffffffffffffff81111561072b5761072a6106f1565b5b602083019150836020820283011115610747576107466106f5565b5b9250929050565b5f8083601f840112610763576107626106ed565b5b8235905067ffffffffffffffff8111156107805761077f6106f1565b5b60208301915083600182028301111561079c5761079b6106f5565b5b9250929050565b5f805f805f805f8060a0898b0312156107bf576107be6105d8565b5b5f6107cc8b828c016106d9565b98505060206107dd8b828c016106d9565b975050604089013567ffffffffffffffff8111156107fe576107fd6105dc565b5b61080a8b828c016106f9565b9650965050606089013567ffffffffffffffff81111561082d5761082c6105dc565b5b6108398b828c016106f9565b9450945050608089013567ffffffffffffffff81111561085c5761085b6105dc565b5b6108688b828c0161074e565b92509250509295985092959890939650565b610883816105e0565b82525050565b5f60208201905061089c5f83018461087a565b92915050565b5f819050919050565b6108b4816108a2565b81146108be575f80fd5b50565b5f813590506108cf816108ab565b92915050565b5f805f805f8060a087890312156108ef576108ee6105d8565b5b5f6108fc89828a016106d9565b965050602061090d89828a016106d9565b955050604061091e89828a016108c1565b945050606061092f89828a016108c1565b935050608087013567ffffffffffffffff8111156109505761094f6105dc565b5b61095c89828a0161074e565b92509250509295509295509295565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b5f82825260208201905092915050565b7f4552433131353552656365697665724d6f636b3a20726576657274696e67206f5f8201527f6e20626174636820726563656976650000000000000000000000000000000000602082015250565b5f610a02602f83610998565b9150610a0d826109a8565b604082019050919050565b5f6020820190508181035f830152610a2f816109f6565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f610a6d826108a2565b9150610a78836108a2565b925082610a8857610a87610a36565b5b828204905092915050565b610a9c816106b2565b82525050565b5f82825260208201905092915050565b5f80fd5b82818337505050565b5f610aca8385610aa2565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115610afd57610afc610ab2565b5b602083029250610b0e838584610ab6565b82840190509392505050565b5f82825260208201905092915050565b828183375f83830152505050565b5f601f19601f8301169050919050565b5f610b538385610b1a565b9350610b60838584610b2a565b610b6983610b38565b840190509392505050565b5f60a082019050610b875f83018b610a93565b610b94602083018a610a93565b8181036040830152610ba781888a610abf565b90508181036060830152610bbc818688610abf565b90508181036080830152610bd1818486610b48565b90509998505050505050505050565b7f4552433131353552656365697665724d6f636b3a20726576657274696e67206f5f8201527f6e20726563656976650000000000000000000000000000000000000000000000602082015250565b5f610c3a602983610998565b9150610c4582610be0565b604082019050919050565b5f6020820190508181035f830152610c6781610c2e565b9050919050565b610c77816108a2565b82525050565b5f60a082019050610c905f830189610a93565b610c9d6020830188610a93565b610caa6040830187610c6e565b610cb76060830186610c6e565b8181036080830152610cca818486610b48565b905097965050505050505056fea26469706673582212208c442b680a6062015caa02f3c4c74cff54e26169331c5af35a3fa1703a3cc02364736f6c63430008180033")] - contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { - enum RevertType { - None, - RevertWithoutMessage, - RevertWithMessage, - RevertWithCustomError, - Panic - } - - bytes4 private immutable _recRetval; - bytes4 private immutable _batRetval; - RevertType private immutable _error; - - #[derive(Debug, PartialEq)] - event Received(address operator, address from, uint256 id, uint256 value, bytes data); - - #[derive(Debug, PartialEq)] - event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data); - - error CustomError(bytes4); - - constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { - _recRetval = recRetval; - _batRetval = batRetval; - _error = error; - } - - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external returns (bytes4) { - if (_error == RevertType.RevertWithoutMessage) { - revert(); - } else if (_error == RevertType.RevertWithMessage) { - revert("ERC1155ReceiverMock: reverting on receive"); - } else if (_error == RevertType.RevertWithCustomError) { - revert CustomError(_recRetval); - } else if (_error == RevertType.Panic) { - uint256 a = uint256(0) / uint256(0); - a; - } - - emit Received(operator, from, id, value, data); - return _recRetval; - } - - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external returns (bytes4) { - if (_error == RevertType.RevertWithoutMessage) { - revert(); - } else if (_error == RevertType.RevertWithMessage) { - revert("ERC1155ReceiverMock: reverting on batch receive"); - } else if (_error == RevertType.RevertWithCustomError) { - revert CustomError(_recRetval); - } else if (_error == RevertType.Panic) { - uint256 a = uint256(0) / uint256(0); - a; - } - - emit BatchReceived(operator, from, ids, values, data); - return _batRetval; - } - } -} - -pub async fn deploy( - wallet: &Wallet, - error: ERC1155ReceiverMock::RevertType, -) -> eyre::Result
{ - let contract = - ERC1155ReceiverMock::deploy(wallet, REC_RETVAL, BAT_RETVAL, error) - .await?; - Ok(*contract.address()) -} From 515cc7adab8edc3bbb32a400b4119e3f520bdcaf Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 01:51:18 +0200 Subject: [PATCH 05/11] feat: add Erc721 Holder example --- Cargo.lock | 14 ++++++ Cargo.toml | 6 ++- examples/erc721-holder/Cargo.toml | 30 ++++++++++++ examples/erc721-holder/src/lib.rs | 35 ++++++++++++++ examples/erc721-holder/src/main.rs | 10 ++++ examples/erc721-holder/tests/abi/mod.rs | 14 ++++++ examples/erc721-holder/tests/erc721-holder.rs | 46 +++++++++++++++++++ 7 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 examples/erc721-holder/Cargo.toml create mode 100644 examples/erc721-holder/src/lib.rs create mode 100644 examples/erc721-holder/src/main.rs create mode 100644 examples/erc721-holder/tests/abi/mod.rs create mode 100644 examples/erc721-holder/tests/erc721-holder.rs diff --git a/Cargo.lock b/Cargo.lock index 3da6fb517..cb6fd39ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1913,6 +1913,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc721-holder-example" +version = "0.3.0-alpha.1" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-types", + "e2e", + "eyre", + "openzeppelin-stylus", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc721-metadata-example" version = "0.3.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 83439f29f..e77b5c8f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,9 @@ members = [ "examples/basic/script", "examples/upgradeable-beacon", "examples/beacon-proxy", - "examples/erc1155-receiver-mock", "examples/uups-proxy", + "examples/erc721-holder", + "examples/erc1155-receiver-mock", ] default-members = [ "contracts", @@ -67,8 +68,9 @@ default-members = [ "examples/basic/token", "examples/upgradeable-beacon", "examples/beacon-proxy", - "examples/erc1155-receiver-mock", "examples/uups-proxy", + "examples/erc721-holder", + "examples/erc1155-receiver-mock", ] exclude = ["fuzz"] diff --git a/examples/erc721-holder/Cargo.toml b/examples/erc721-holder/Cargo.toml new file mode 100644 index 000000000..20ece14e9 --- /dev/null +++ b/examples/erc721-holder/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "erc721-holder-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true + +[dev-dependencies] +alloy.workspace = true +e2e.workspace = true +tokio.workspace = true +eyre.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] + +[features] +e2e = [] +export-abi = ["openzeppelin-stylus/export-abi"] + +[[bin]] +name = "erc721-holder-example" +path = "src/main.rs" diff --git a/examples/erc721-holder/src/lib.rs b/examples/erc721-holder/src/lib.rs new file mode 100644 index 000000000..a57b4d451 --- /dev/null +++ b/examples/erc721-holder/src/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] +extern crate alloc; + +use openzeppelin_stylus::token::erc721::{ + receiver::IErc721Receiver, utils::Erc721Holder, +}; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{aliases::B32, Address, U256}, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct Erc721HolderExample { + holder: Erc721Holder, +} + +#[public] +#[implements(IErc721Receiver)] +impl Erc721HolderExample {} + +#[public] +impl IErc721Receiver for Erc721HolderExample { + #[selector(name = "onERC721Received")] + fn on_erc721_received( + &mut self, + operator: Address, + from: Address, + token_id: U256, + data: Bytes, + ) -> Result> { + self.holder.on_erc721_received(operator, from, token_id, data) + } +} diff --git a/examples/erc721-holder/src/main.rs b/examples/erc721-holder/src/main.rs new file mode 100644 index 000000000..13107b82d --- /dev/null +++ b/examples/erc721-holder/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] + +#[cfg(not(any(test, feature = "export-abi")))] +#[no_mangle] +pub extern "C" fn main() {} + +#[cfg(feature = "export-abi")] +fn main() { + erc721_holder_example::print_from_args(); +} diff --git a/examples/erc721-holder/tests/abi/mod.rs b/examples/erc721-holder/tests/abi/mod.rs new file mode 100644 index 000000000..46b31a1a3 --- /dev/null +++ b/examples/erc721-holder/tests/abi/mod.rs @@ -0,0 +1,14 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc721HolderExample { + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); + } +); diff --git a/examples/erc721-holder/tests/erc721-holder.rs b/examples/erc721-holder/tests/erc721-holder.rs new file mode 100644 index 000000000..c5b0c80d4 --- /dev/null +++ b/examples/erc721-holder/tests/erc721-holder.rs @@ -0,0 +1,46 @@ +#![cfg(feature = "e2e")] + +use abi::Erc721HolderExample; +use alloy::primitives::{Bytes, U256}; +use e2e::Account; +use eyre::Result; +use openzeppelin_stylus::token::erc721::RECEIVER_FN_SELECTOR; + +mod abi; + +#[e2e::test] +async fn returns_correct_selector(alice: Account) -> Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.contract_address; + + let contract = Erc721HolderExample::new(contract_addr, &alice.wallet); + + // call without data. + let interface_selector = contract + .onERC721Received( + alice.address(), + alice.address(), + U256::from(1), + Bytes::new(), + ) + .call() + .await? + ._0; + + assert_eq!(RECEIVER_FN_SELECTOR, interface_selector); + + // call with data. + let interface_selector = contract + .onERC721Received( + alice.address(), + alice.address(), + U256::from(42), + Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]), + ) + .call() + .await? + ._0; + + assert_eq!(RECEIVER_FN_SELECTOR, interface_selector); + + Ok(()) +} From 91e9cbe8896331189bba0b699b66f88aeca44d25 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 02:09:05 +0200 Subject: [PATCH 06/11] feat: add Erc1155 Holder example --- Cargo.lock | 14 ++ Cargo.toml | 2 + examples/erc1155-holder/Cargo.toml | 30 +++++ examples/erc1155-holder/src/lib.rs | 56 ++++++++ examples/erc1155-holder/src/main.rs | 10 ++ examples/erc1155-holder/tests/abi/mod.rs | 25 ++++ .../erc1155-holder/tests/erc1155-holder.rs | 124 ++++++++++++++++++ examples/erc721-holder/tests/erc721-holder.rs | 20 ++- 8 files changed, 269 insertions(+), 12 deletions(-) create mode 100644 examples/erc1155-holder/Cargo.toml create mode 100644 examples/erc1155-holder/src/lib.rs create mode 100644 examples/erc1155-holder/src/main.rs create mode 100644 examples/erc1155-holder/tests/abi/mod.rs create mode 100644 examples/erc1155-holder/tests/erc1155-holder.rs diff --git a/Cargo.lock b/Cargo.lock index cb6fd39ac..c2d2378ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1763,6 +1763,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc1155-holder-example" +version = "0.3.0-alpha.1" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-types", + "e2e", + "eyre", + "openzeppelin-stylus", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc1155-metadata-uri-example" version = "0.3.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index e77b5c8f2..ef200c5d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "examples/beacon-proxy", "examples/uups-proxy", "examples/erc721-holder", + "examples/erc1155-holder", "examples/erc1155-receiver-mock", ] default-members = [ @@ -70,6 +71,7 @@ default-members = [ "examples/beacon-proxy", "examples/uups-proxy", "examples/erc721-holder", + "examples/erc1155-holder", "examples/erc1155-receiver-mock", ] exclude = ["fuzz"] diff --git a/examples/erc1155-holder/Cargo.toml b/examples/erc1155-holder/Cargo.toml new file mode 100644 index 000000000..16172121c --- /dev/null +++ b/examples/erc1155-holder/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "erc1155-holder-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true + +[dev-dependencies] +alloy.workspace = true +e2e.workspace = true +tokio.workspace = true +eyre.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] + +[features] +e2e = [] +export-abi = ["openzeppelin-stylus/export-abi"] + +[[bin]] +name = "erc1155-holder-example" +path = "src/main.rs" diff --git a/examples/erc1155-holder/src/lib.rs b/examples/erc1155-holder/src/lib.rs new file mode 100644 index 000000000..0ba5c45ec --- /dev/null +++ b/examples/erc1155-holder/src/lib.rs @@ -0,0 +1,56 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] +extern crate alloc; + +use openzeppelin_stylus::{ + token::erc1155::{receiver::IErc1155Receiver, utils::Erc1155Holder}, + utils::introspection::erc165::IErc165, +}; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{aliases::B32, Address, U256}, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct Erc1155HolderExample { + holder: Erc1155Holder, +} + +#[public] +#[implements(IErc1155Receiver, IErc165)] +impl Erc1155HolderExample {} + +#[public] +impl IErc1155Receiver for Erc1155HolderExample { + #[selector(name = "onERC1155Received")] + fn on_erc1155_received( + &mut self, + operator: Address, + from: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result> { + self.holder.on_erc1155_received(operator, from, id, value, data) + } + + #[selector(name = "onERC1155BatchReceived")] + fn on_erc1155_batch_received( + &mut self, + operator: Address, + from: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result> { + self.holder.on_erc1155_batch_received(operator, from, ids, values, data) + } +} + +#[public] +impl IErc165 for Erc1155HolderExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.holder.supports_interface(interface_id) + } +} diff --git a/examples/erc1155-holder/src/main.rs b/examples/erc1155-holder/src/main.rs new file mode 100644 index 000000000..8aca57e48 --- /dev/null +++ b/examples/erc1155-holder/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] + +#[cfg(not(any(test, feature = "export-abi")))] +#[no_mangle] +pub extern "C" fn main() {} + +#[cfg(feature = "export-abi")] +fn main() { + erc1155_holder_example::print_from_args(); +} diff --git a/examples/erc1155-holder/tests/abi/mod.rs b/examples/erc1155-holder/tests/abi/mod.rs new file mode 100644 index 000000000..965dd9505 --- /dev/null +++ b/examples/erc1155-holder/tests/abi/mod.rs @@ -0,0 +1,25 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc1155HolderExample { + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); + + function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); + } +); diff --git a/examples/erc1155-holder/tests/erc1155-holder.rs b/examples/erc1155-holder/tests/erc1155-holder.rs new file mode 100644 index 000000000..a0f643301 --- /dev/null +++ b/examples/erc1155-holder/tests/erc1155-holder.rs @@ -0,0 +1,124 @@ +#![cfg(feature = "e2e")] + +use abi::Erc1155HolderExample; +use alloy::primitives::{Bytes, U256}; +use e2e::Account; +use eyre::Result; +use openzeppelin_stylus::token::erc1155::receiver::{ + BATCH_TRANSFER_FN_SELECTOR, SINGLE_TRANSFER_FN_SELECTOR, +}; + +mod abi; + +#[e2e::test] +async fn returns_correct_selector_for_single_transfer( + alice: Account, +) -> Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.contract_address; + + let contract = Erc1155HolderExample::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let id = U256::from(1); + let value = U256::from(1); + let data = Bytes::new(); + + // call without data. + let interface_selector = contract + .onERC1155Received(operator, from, id, value, data) + .call() + .await? + ._0; + + assert_eq!(SINGLE_TRANSFER_FN_SELECTOR, interface_selector); + + // call with data. + let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); + let interface_selector = contract + .onERC1155Received(operator, from, id, value, data) + .call() + .await? + ._0; + + assert_eq!(SINGLE_TRANSFER_FN_SELECTOR, interface_selector); + + Ok(()) +} + +#[e2e::test] +async fn returns_correct_selector_for_batch_transfer( + alice: Account, +) -> Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.contract_address; + + let contract = Erc1155HolderExample::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let ids = vec![U256::from(1), U256::from(2)]; + let values = vec![U256::from(1), U256::from(2)]; + let data = Bytes::new(); + + // call without data. + let interface_selector = contract + .onERC1155BatchReceived( + operator, + from, + ids.clone(), + values.clone(), + data, + ) + .call() + .await? + ._0; + + assert_eq!(BATCH_TRANSFER_FN_SELECTOR, interface_selector); + + // call with data. + let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); + let interface_selector = contract + .onERC1155BatchReceived(operator, from, ids, values, data) + .call() + .await? + ._0; + + assert_eq!(BATCH_TRANSFER_FN_SELECTOR, interface_selector); + + Ok(()) +} + +#[e2e::test] +async fn supports_interface(alice: Account) -> Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.contract_address; + let contract = Erc1155HolderExample::new(contract_addr, &alice.wallet); + + let invalid_interface_id: u32 = 0xffffffff; + assert!( + !contract + .supportsInterface(invalid_interface_id.into()) + .call() + .await? + .supportsInterface + ); + + let erc1155_holder_interface_id: u32 = 0x4e2312e0; + assert!( + contract + .supportsInterface(erc1155_holder_interface_id.into()) + .call() + .await? + .supportsInterface + ); + + let erc165_interface_id: u32 = 0x01ffc9a7; + assert!( + contract + .supportsInterface(erc165_interface_id.into()) + .call() + .await? + .supportsInterface + ); + + Ok(()) +} diff --git a/examples/erc721-holder/tests/erc721-holder.rs b/examples/erc721-holder/tests/erc721-holder.rs index c5b0c80d4..6a8d62c7e 100644 --- a/examples/erc721-holder/tests/erc721-holder.rs +++ b/examples/erc721-holder/tests/erc721-holder.rs @@ -14,14 +14,14 @@ async fn returns_correct_selector(alice: Account) -> Result<()> { let contract = Erc721HolderExample::new(contract_addr, &alice.wallet); + let operator = alice.address(); + let from = alice.address(); + let token_id = U256::from(1); + let data = Bytes::new(); + // call without data. let interface_selector = contract - .onERC721Received( - alice.address(), - alice.address(), - U256::from(1), - Bytes::new(), - ) + .onERC721Received(operator, from, token_id, data) .call() .await? ._0; @@ -29,13 +29,9 @@ async fn returns_correct_selector(alice: Account) -> Result<()> { assert_eq!(RECEIVER_FN_SELECTOR, interface_selector); // call with data. + let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); let interface_selector = contract - .onERC721Received( - alice.address(), - alice.address(), - U256::from(42), - Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]), - ) + .onERC721Received(operator, from, token_id, data) .call() .await? ._0; From ae8d4812ea6be1591a9d1c5c943b61b6bd14d92e Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 03:10:53 +0200 Subject: [PATCH 07/11] fix: Erc1155MockReceiver works --- examples/erc1155-receiver-mock/src/lib.rs | 154 +++++------ .../erc1155-receiver-mock/tests/abi/mod.rs | 30 +++ .../tests/erc1155-receiver-mock.rs | 253 ++++++++++++++++++ examples/erc1155/tests/abi/mod.rs | 3 + examples/erc1155/tests/erc1155.rs | 12 +- examples/erc1155/tests/mock/mod.rs | 1 - 6 files changed, 353 insertions(+), 100 deletions(-) create mode 100644 examples/erc1155-receiver-mock/tests/abi/mod.rs create mode 100644 examples/erc1155-receiver-mock/tests/erc1155-receiver-mock.rs delete mode 100644 examples/erc1155/tests/mock/mod.rs diff --git a/examples/erc1155-receiver-mock/src/lib.rs b/examples/erc1155-receiver-mock/src/lib.rs index 8bfea4e62..2c5c9b84f 100644 --- a/examples/erc1155-receiver-mock/src/lib.rs +++ b/examples/erc1155-receiver-mock/src/lib.rs @@ -4,7 +4,13 @@ extern crate alloc; use alloc::vec::Vec; use openzeppelin_stylus::{ - token::erc1155::receiver::IErc1155Receiver, + token::erc1155::{ + receiver::{ + IErc1155Receiver, BATCH_TRANSFER_FN_SELECTOR, + SINGLE_TRANSFER_FN_SELECTOR, + }, + utils::Erc1155Holder, + }, utils::introspection::erc165::IErc165, }; pub use sol::*; @@ -16,7 +22,7 @@ use stylus_sdk::{ }, evm, prelude::*, - storage::{StorageB32, StorageU8}, + storage::StorageU8, }; mod sol { @@ -34,28 +40,28 @@ mod sol { sol! { #[derive(Debug)] #[allow(missing_docs)] - error CustomError(bytes4); + error CustomError(bytes4 data); } } +#[derive(SolidityError, Debug)] +pub enum Error { + MockCustomError(CustomError), +} + /// Enum representing different revert types for testing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RevertType { None, - RevertWithoutMessage, - RevertWithMessage, - RevertWithCustomError, + CustomError, Panic, } impl From for RevertType { fn from(value: u8) -> Self { match value { - 0 => RevertType::None, - 1 => RevertType::RevertWithoutMessage, - 2 => RevertType::RevertWithMessage, - 3 => RevertType::RevertWithCustomError, - 4 => RevertType::Panic, + 1 => RevertType::CustomError, + 2 => RevertType::Panic, _ => RevertType::None, } } @@ -63,8 +69,8 @@ impl From for RevertType { impl From for RevertType { fn from(value: U8) -> Self { - let revert_type: u8 = value.to_be_bytes::<1>()[0]; - RevertType::from(revert_type) + let revert_type: u8 = u8::try_from(value).expect("should be valid"); + revert_type.into() } } @@ -72,10 +78,8 @@ impl From for u8 { fn from(value: RevertType) -> Self { match value { RevertType::None => 0, - RevertType::RevertWithoutMessage => 1, - RevertType::RevertWithMessage => 2, - RevertType::RevertWithCustomError => 3, - RevertType::Panic => 4, + RevertType::CustomError => 1, + RevertType::Panic => 2, } } } @@ -83,31 +87,22 @@ impl From for u8 { #[entrypoint] #[storage] struct Erc1155ReceiverMock { - rec_retval: StorageB32, - bat_retval: StorageB32, error_type: StorageU8, + holder: Erc1155Holder, } #[public] #[implements(IErc1155Receiver, IErc165)] impl Erc1155ReceiverMock { #[constructor] - fn constructor( - &mut self, - rec_retval: B32, - bat_retval: B32, - error_type: U8, - ) -> Result<(), Vec> { - self.rec_retval.set(rec_retval); - self.bat_retval.set(bat_retval); - self.error_type.set(error_type); - Ok(()) + fn constructor(&mut self, error_type: U8) { + self.error_type.set(error_type) } } #[public] impl IErc1155Receiver for Erc1155ReceiverMock { - #[allow(deprecated)] + #[selector(name = "onERC1155Received")] fn on_erc1155_received( &mut self, operator: Address, @@ -119,42 +114,32 @@ impl IErc1155Receiver for Erc1155ReceiverMock { let error_type: RevertType = self.error_type.get().into(); match error_type { - RevertType::RevertWithoutMessage => { - return Err(vec![]); - } - RevertType::RevertWithMessage => { - return Err( - b"ERC1155ReceiverMock: reverting on receive".to_vec() - ); - } - RevertType::RevertWithCustomError => { - // For custom error, we'll just revert with the return value as - // data - let retval = self.rec_retval.get(); - return Err(retval.to_vec()); + RevertType::CustomError => { + Err(Error::MockCustomError(CustomError { + data: SINGLE_TRANSFER_FN_SELECTOR, + }) + .into()) } RevertType::Panic => { - // Simulate a panic by dividing by zero + // simulate a panic by dividing by [`U256::ZERO`]. let _ = U256::from(0) / U256::from(0); - return Err(vec![]); + unreachable!() + } + RevertType::None => { + #[allow(deprecated)] + evm::log(Received { + operator, + from, + id, + value, + data: data.to_vec().into(), + }); + self.holder.on_erc1155_received(operator, from, id, value, data) } - RevertType::None => {} } - - // Emit the Received event - evm::log(Received { - operator, - from, - id, - value, - data: data.to_vec().into(), - }); - - // Return the configured return value - Ok(self.rec_retval.get()) } - #[allow(deprecated)] + #[selector(name = "onERC1155BatchReceived")] fn on_erc1155_batch_received( &mut self, operator: Address, @@ -166,46 +151,37 @@ impl IErc1155Receiver for Erc1155ReceiverMock { let error_type: RevertType = self.error_type.get().into(); match error_type { - RevertType::RevertWithoutMessage => { - return Err(vec![]); - } - RevertType::RevertWithMessage => { - return Err( - b"ERC1155ReceiverMock: reverting on batch receive".to_vec() - ); - } - RevertType::RevertWithCustomError => { - // For custom error, we'll just revert with the return value as - // data - let retval = self.bat_retval.get(); - return Err(retval.to_vec()); + RevertType::CustomError => { + Err(Error::MockCustomError(CustomError { + data: BATCH_TRANSFER_FN_SELECTOR, + }) + .into()) } RevertType::Panic => { - // Simulate a panic by dividing by zero + // simulate a panic by dividing by [`U256::ZERO`]. let _ = U256::from(0) / U256::from(0); - return Err(vec![]); + unreachable!() + } + RevertType::None => { + #[allow(deprecated)] + evm::log(BatchReceived { + operator, + from, + ids: ids.clone(), + values: values.clone(), + data: data.to_vec().into(), + }); + self.holder.on_erc1155_batch_received( + operator, from, ids, values, data, + ) } - RevertType::None => {} } - - // Emit the BatchReceived event - evm::log(BatchReceived { - operator, - from, - ids, - values, - data: data.to_vec().into(), - }); - - // Return the configured return value - Ok(self.bat_retval.get()) } } #[public] impl IErc165 for Erc1155ReceiverMock { fn supports_interface(&self, interface_id: B32) -> bool { - ::interface_id() == interface_id - || ::interface_id() == interface_id + self.holder.supports_interface(interface_id) } } diff --git a/examples/erc1155-receiver-mock/tests/abi/mod.rs b/examples/erc1155-receiver-mock/tests/abi/mod.rs new file mode 100644 index 000000000..823fc673c --- /dev/null +++ b/examples/erc1155-receiver-mock/tests/abi/mod.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc1155ReceiverMock { + #[derive(Debug)] + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + #[derive(Debug)] + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); + + function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); + + #[derive(Debug, PartialEq)] + error CustomError(bytes4 data); + } +); diff --git a/examples/erc1155-receiver-mock/tests/erc1155-receiver-mock.rs b/examples/erc1155-receiver-mock/tests/erc1155-receiver-mock.rs new file mode 100644 index 000000000..bd49a039c --- /dev/null +++ b/examples/erc1155-receiver-mock/tests/erc1155-receiver-mock.rs @@ -0,0 +1,253 @@ +#![cfg(feature = "e2e")] + +use abi::Erc1155ReceiverMock; +use alloy::primitives::{uint, Bytes, U256, U8}; +use e2e::{constructor, Account, Constructor, Panic, PanicCode, Revert}; +use eyre::Result; +use openzeppelin_stylus::token::erc1155::receiver::{ + BATCH_TRANSFER_FN_SELECTOR, SINGLE_TRANSFER_FN_SELECTOR, +}; + +mod abi; + +const REVERT_TYPE_NONE: U8 = uint!(0_U8); +const REVERT_TYPE_CUSTOM_ERROR: U8 = uint!(1_U8); +const REVERT_TYPE_PANIC: U8 = uint!(2_U8); + +fn constructor(error_type: U8) -> Constructor { + constructor!(error_type) +} + +// ============================================================================ +// Happy Path Tests +// ============================================================================ + +#[e2e::test] +async fn returns_correct_selector_for_single_transfer( + alice: Account, +) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_NONE)) + .deploy() + .await? + .contract_address; + + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let id = U256::from(1); + let value = U256::from(1); + let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); + let interface_selector = contract + .onERC1155Received(operator, from, id, value, data) + .call() + .await? + ._0; + + assert_eq!(SINGLE_TRANSFER_FN_SELECTOR, interface_selector); + + Ok(()) +} + +#[e2e::test] +async fn returns_correct_selector_for_batch_transfer( + alice: Account, +) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_NONE)) + .deploy() + .await? + .contract_address; + + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let ids = vec![U256::from(1), U256::from(2)]; + let values = vec![U256::from(1), U256::from(2)]; + let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); + let interface_selector = contract + .onERC1155BatchReceived(operator, from, ids, values, data) + .call() + .await? + ._0; + + assert_eq!(BATCH_TRANSFER_FN_SELECTOR, interface_selector); + + Ok(()) +} + +// ============================================================================ +// Error Handling Tests - Single Transfer +// ============================================================================ + +#[e2e::test] +async fn reverts_without_message_for_single_transfer( + alice: Account, +) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_CUSTOM_ERROR)) + .deploy() + .await? + .contract_address; + + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let id = U256::from(1); + let value = U256::from(1); + let data = Bytes::new(); + + let err = contract + .onERC1155Received(operator, from, id, value, data) + .call() + .await + .expect_err("should revert with custom error"); + + assert!(err.reverted_with(Erc1155ReceiverMock::CustomError { + data: SINGLE_TRANSFER_FN_SELECTOR + })); + + Ok(()) +} + +#[e2e::test] +async fn panics_for_single_transfer(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_PANIC)) + .deploy() + .await? + .contract_address; + + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let id = U256::from(1); + let value = U256::from(1); + let data = Bytes::new(); + + let err = contract + .onERC1155Received(operator, from, id, value, data) + .call() + .await + .expect_err("should panic"); + + assert!(err.panicked_with(PanicCode::DivisionByZero)); + + Ok(()) +} + +// ============================================================================ +// Error Handling Tests - Batch Transfer +// ============================================================================ + +#[e2e::test] +async fn reverts_with_custom_error_for_batch_transfer( + alice: Account, +) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_CUSTOM_ERROR)) + .deploy() + .await? + .contract_address; + + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let ids = vec![U256::from(1), U256::from(2)]; + let values = vec![U256::from(1), U256::from(2)]; + let data = Bytes::new(); + + let err = contract + .onERC1155BatchReceived(operator, from, ids, values, data) + .call() + .await + .expect_err("should revert with custom error"); + + assert!(err.reverted_with(Erc1155ReceiverMock::CustomError { + data: BATCH_TRANSFER_FN_SELECTOR + })); + + Ok(()) +} + +#[e2e::test] +async fn panics_for_batch_transfer(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_PANIC)) + .deploy() + .await? + .contract_address; + + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let ids = vec![U256::from(1), U256::from(2)]; + let values = vec![U256::from(1), U256::from(2)]; + let data = Bytes::new(); + + let err = contract + .onERC1155BatchReceived(operator, from, ids, values, data) + .call() + .await + .expect_err("should panic"); + + assert!(err.panicked_with(PanicCode::DivisionByZero)); + + Ok(()) +} + +// ============================================================================ +// Interface Support Tests +// ============================================================================ + +#[e2e::test] +async fn supports_interface(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_NONE)) + .deploy() + .await? + .contract_address; + let contract = Erc1155ReceiverMock::new(contract_addr, &alice.wallet); + + let invalid_interface_id: u32 = 0xffffffff; + assert!( + !contract + .supportsInterface(invalid_interface_id.into()) + .call() + .await? + .supportsInterface + ); + + let erc1155_holder_interface_id: u32 = 0x4e2312e0; + assert!( + contract + .supportsInterface(erc1155_holder_interface_id.into()) + .call() + .await? + .supportsInterface + ); + + let erc165_interface_id: u32 = 0x01ffc9a7; + assert!( + contract + .supportsInterface(erc165_interface_id.into()) + .call() + .await? + .supportsInterface + ); + + Ok(()) +} diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 00e6a3c35..5de50fe7c 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -40,5 +40,8 @@ sol!( event Received(address operator, address from, uint256 id, uint256 value, bytes data); #[derive(Debug, PartialEq)] event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data); + + #[derive(Debug, PartialEq)] + error CustomError(bytes4 data); } ); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index c2ab0555e..ad9a61bdb 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -9,25 +9,17 @@ use e2e::{ constructor, receipt, send, watch, Account, Constructor, EventExt, PanicCode, Revert, }; -use openzeppelin_stylus::token::erc1155::receiver::{ - BATCH_TRANSFER_FN_SELECTOR, SINGLE_TRANSFER_FN_SELECTOR, -}; mod abi; fn mock_receiver_constructor(error_type: U8) -> Constructor { - constructor!( - SINGLE_TRANSFER_FN_SELECTOR, - BATCH_TRANSFER_FN_SELECTOR, - error_type - ) + constructor!(error_type) } const REVERT_TYPE_NONE: U8 = uint!(0_U8); const REVERT_TYPE_REVERT_WITHOUT_MESSAGE: U8 = uint!(1_U8); const REVERT_TYPE_REVERT_WITH_MESSAGE: U8 = uint!(2_U8); -const REVERT_TYPE_REVERT_WITH_CUSTOM_ERROR: U8 = uint!(3_U8); -const REVERT_TYPE_PANIC: U8 = uint!(4_U8); +const REVERT_TYPE_PANIC: U8 = uint!(3_U8); fn random_token_ids(size: usize) -> Vec { (0..size).map(U256::from).collect() diff --git a/examples/erc1155/tests/mock/mod.rs b/examples/erc1155/tests/mock/mod.rs deleted file mode 100644 index 4c0db7eaa..000000000 --- a/examples/erc1155/tests/mock/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod receiver; From 4a459613d303c07d8c80afae5271b73d431144be Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 03:32:07 +0200 Subject: [PATCH 08/11] wip: refactor E2E tests for Erc1155 to use mock receiver --- examples/erc1155/tests/erc1155.rs | 179 +----------------------------- 1 file changed, 6 insertions(+), 173 deletions(-) diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index ad9a61bdb..d07f1f965 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -17,9 +17,8 @@ fn mock_receiver_constructor(error_type: U8) -> Constructor { } const REVERT_TYPE_NONE: U8 = uint!(0_U8); -const REVERT_TYPE_REVERT_WITHOUT_MESSAGE: U8 = uint!(1_U8); -const REVERT_TYPE_REVERT_WITH_MESSAGE: U8 = uint!(2_U8); -const REVERT_TYPE_PANIC: U8 = uint!(3_U8); +const REVERT_TYPE_CUSTOM_ERROR: U8 = uint!(1_U8); +const REVERT_TYPE_PANIC: U8 = uint!(2_U8); fn random_token_ids(size: usize) -> Vec { (0..size).map(U256::from).collect() @@ -188,9 +187,7 @@ async fn errors_when_receiver_reverts_with_reason_in_mint( let receiver_address = alice .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITH_MESSAGE, - )) + .with_constructor(mock_receiver_constructor(REVERT_TYPE_CUSTOM_ERROR)) .with_example_name("erc1155-receiver-mock") .deploy() .await? @@ -217,41 +214,6 @@ async fn errors_when_receiver_reverts_with_reason_in_mint( Ok(()) } -#[e2e::test] -async fn errors_when_receiver_reverts_without_reason_in_mint( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.contract_address; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let receiver_address = alice - .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITHOUT_MESSAGE, - )) - .with_example_name("erc1155-receiver-mock") - .deploy() - .await? - .contract_address; - - let token_id = random_token_ids(1)[0]; - let value = random_values(1)[0]; - - let err = send!(contract.mint( - receiver_address, - token_id, - value, - vec![0, 1, 2, 3].into() - )) - .expect_err("should not mint when receiver reverts"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { - receiver: receiver_address - })); - - Ok(()) -} - #[e2e::test] async fn errors_when_receiver_panics_in_mint( alice: Account, @@ -433,9 +395,7 @@ async fn errors_when_receiver_reverts_with_reason_in_batch_mint( let receiver_address = alice .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITH_MESSAGE, - )) + .with_constructor(mock_receiver_constructor(REVERT_TYPE_CUSTOM_ERROR)) .with_example_name("erc1155-receiver-mock") .deploy() .await? @@ -462,41 +422,6 @@ async fn errors_when_receiver_reverts_with_reason_in_batch_mint( Ok(()) } -#[e2e::test] -async fn errors_when_receiver_reverts_without_reason_in_batch_mint( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.contract_address; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let receiver_address = alice - .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITHOUT_MESSAGE, - )) - .with_example_name("erc1155-receiver-mock") - .deploy() - .await? - .contract_address; - - let token_ids = random_token_ids(2); - let values = random_values(2); - - let err = send!(contract.mintBatch( - receiver_address, - token_ids.clone(), - values.clone(), - vec![].into() - )) - .expect_err("should not mint batch when receiver reverts"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { - receiver: receiver_address - })); - - Ok(()) -} - #[e2e::test] async fn errors_when_receiver_panics_in_batch_mint( alice: Account, @@ -841,9 +766,7 @@ async fn errors_when_receiver_reverts_with_reason( let receiver_address = alice .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITH_MESSAGE, - )) + .with_constructor(mock_receiver_constructor(REVERT_TYPE_CUSTOM_ERROR)) .with_example_name("erc1155-receiver-mock") .deploy() .await? @@ -879,50 +802,6 @@ async fn errors_when_receiver_reverts_with_reason( Ok(()) } -#[e2e::test] -async fn errors_when_receiver_reverts_without_reason( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.contract_address; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let receiver_address = alice - .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITHOUT_MESSAGE, - )) - .with_example_name("erc1155-receiver-mock") - .deploy() - .await? - .contract_address; - - let alice_addr = alice.address(); - let token_id = random_token_ids(1)[0]; - let value = random_values(1)[0]; - - watch!(contract.mint( - alice_addr, - token_id, - value, - vec![0, 1, 2, 3].into() - ))?; - - let err = send!(contract.safeTransferFrom( - alice_addr, - receiver_address, - token_id, - value, - vec![].into() - )) - .expect_err("should not transfer when receiver reverts"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { - receiver: receiver_address - })); - - Ok(()) -} - #[e2e::test] async fn errors_when_receiver_panics(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.contract_address; @@ -1266,9 +1145,7 @@ async fn errors_when_receiver_reverts_with_reason_in_batch_transfer( let receiver_address = alice .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITH_MESSAGE, - )) + .with_constructor(mock_receiver_constructor(REVERT_TYPE_CUSTOM_ERROR)) .with_example_name("erc1155-receiver-mock") .deploy() .await? @@ -1304,50 +1181,6 @@ async fn errors_when_receiver_reverts_with_reason_in_batch_transfer( Ok(()) } -#[e2e::test] -async fn errors_when_receiver_reverts_without_reason_in_batch_transfer( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.contract_address; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let receiver_address = alice - .as_deployer() - .with_constructor(mock_receiver_constructor( - REVERT_TYPE_REVERT_WITHOUT_MESSAGE, - )) - .with_example_name("erc1155-receiver-mock") - .deploy() - .await? - .contract_address; - - let alice_addr = alice.address(); - let token_ids = random_token_ids(2); - let values = random_values(2); - - watch!(contract.mintBatch( - alice_addr, - token_ids.clone(), - values.clone(), - vec![].into() - ))?; - - let err = send!(contract.safeBatchTransferFrom( - alice_addr, - receiver_address, - token_ids, - values, - vec![].into() - )) - .expect_err("should not transfer when receiver reverts"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { - receiver: receiver_address - })); - - Ok(()) -} - #[e2e::test] async fn errors_when_receiver_panics_in_batch_transfer( alice: Account, From 8e3c4e8d3d57eab43cfb1cc65823bb140ec0a340 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 03:45:24 +0200 Subject: [PATCH 09/11] feat: add Erc721ReceiverMock --- Cargo.lock | 15 ++ Cargo.toml | 2 + examples/erc721-holder/tests/erc721-holder.rs | 12 -- examples/erc721-receiver-mock/Cargo.toml | 31 +++++ examples/erc721-receiver-mock/src/lib.rs | 129 ++++++++++++++++++ examples/erc721-receiver-mock/src/main.rs | 10 ++ .../erc721-receiver-mock/tests/abi/mod.rs | 18 +++ .../tests/erc721-receiver-mock.rs | 107 +++++++++++++++ 8 files changed, 312 insertions(+), 12 deletions(-) create mode 100644 examples/erc721-receiver-mock/Cargo.toml create mode 100644 examples/erc721-receiver-mock/src/lib.rs create mode 100644 examples/erc721-receiver-mock/src/main.rs create mode 100644 examples/erc721-receiver-mock/tests/abi/mod.rs create mode 100644 examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs diff --git a/Cargo.lock b/Cargo.lock index c2d2378ae..d79f841a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,6 +1955,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc721-receiver-mock-example" +version = "0.3.0-alpha.1" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "e2e", + "eyre", + "openzeppelin-stylus", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc721-wrapper-example" version = "0.3.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index ef200c5d0..4ca06d83e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "examples/beacon-proxy", "examples/uups-proxy", "examples/erc721-holder", + "examples/erc721-receiver-mock", "examples/erc1155-holder", "examples/erc1155-receiver-mock", ] @@ -71,6 +72,7 @@ default-members = [ "examples/beacon-proxy", "examples/uups-proxy", "examples/erc721-holder", + "examples/erc721-receiver-mock", "examples/erc1155-holder", "examples/erc1155-receiver-mock", ] diff --git a/examples/erc721-holder/tests/erc721-holder.rs b/examples/erc721-holder/tests/erc721-holder.rs index 6a8d62c7e..f54e504f4 100644 --- a/examples/erc721-holder/tests/erc721-holder.rs +++ b/examples/erc721-holder/tests/erc721-holder.rs @@ -17,18 +17,6 @@ async fn returns_correct_selector(alice: Account) -> Result<()> { let operator = alice.address(); let from = alice.address(); let token_id = U256::from(1); - let data = Bytes::new(); - - // call without data. - let interface_selector = contract - .onERC721Received(operator, from, token_id, data) - .call() - .await? - ._0; - - assert_eq!(RECEIVER_FN_SELECTOR, interface_selector); - - // call with data. let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); let interface_selector = contract .onERC721Received(operator, from, token_id, data) diff --git a/examples/erc721-receiver-mock/Cargo.toml b/examples/erc721-receiver-mock/Cargo.toml new file mode 100644 index 000000000..b068e9e87 --- /dev/null +++ b/examples/erc721-receiver-mock/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "erc721-receiver-mock-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true +alloy-sol-macro.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true + +[dev-dependencies] +alloy.workspace = true +e2e.workspace = true +tokio.workspace = true +eyre.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] + +[features] +e2e = [] +export-abi = ["stylus-sdk/export-abi"] + +[[bin]] +name = "erc721-receiver-mock-example" +path = "src/main.rs" diff --git a/examples/erc721-receiver-mock/src/lib.rs b/examples/erc721-receiver-mock/src/lib.rs new file mode 100644 index 000000000..6d41b5779 --- /dev/null +++ b/examples/erc721-receiver-mock/src/lib.rs @@ -0,0 +1,129 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] +extern crate alloc; + +use alloc::vec::Vec; + +use openzeppelin_stylus::token::erc721::{ + receiver::{IErc721Receiver, RECEIVER_FN_SELECTOR}, + utils::Erc721Holder, +}; +pub use sol::*; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{ + aliases::{B32, U8}, + Address, U256, + }, + evm, + prelude::*, + storage::StorageU8, +}; + +mod sol { + use alloy_sol_macro::sol; + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + event Received(address operator, address from, uint256 token_id, bytes data); + } + + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + error CustomError(bytes4 data); + } +} + +#[derive(SolidityError, Debug)] +pub enum Error { + MockCustomError(CustomError), +} + +/// Enum representing different revert types for testing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RevertType { + None, + CustomError, + Panic, +} + +impl From for RevertType { + fn from(value: u8) -> Self { + match value { + 1 => RevertType::CustomError, + 2 => RevertType::Panic, + _ => RevertType::None, + } + } +} + +impl From for RevertType { + fn from(value: U8) -> Self { + let revert_type: u8 = u8::try_from(value).expect("should be valid"); + revert_type.into() + } +} + +impl From for u8 { + fn from(value: RevertType) -> Self { + match value { + RevertType::None => 0, + RevertType::CustomError => 1, + RevertType::Panic => 2, + } + } +} + +#[entrypoint] +#[storage] +struct Erc721ReceiverMock { + error_type: StorageU8, + holder: Erc721Holder, +} + +#[public] +#[implements(IErc721Receiver)] +impl Erc721ReceiverMock { + #[constructor] + fn constructor(&mut self, error_type: U8) { + self.error_type.set(error_type) + } +} + +#[public] +impl IErc721Receiver for Erc721ReceiverMock { + #[selector(name = "onERC721Received")] + fn on_erc721_received( + &mut self, + operator: Address, + from: Address, + token_id: U256, + data: Bytes, + ) -> Result> { + let error_type: RevertType = self.error_type.get().into(); + + match error_type { + RevertType::CustomError => { + Err(Error::MockCustomError(CustomError { + data: RECEIVER_FN_SELECTOR, + }) + .into()) + } + RevertType::Panic => { + // simulate a panic by dividing by [`U256::ZERO`]. + let _ = U256::from(0) / U256::from(0); + unreachable!() + } + RevertType::None => { + #[allow(deprecated)] + evm::log(Received { + operator, + from, + token_id, + data: data.to_vec().into(), + }); + self.holder.on_erc721_received(operator, from, token_id, data) + } + } + } +} diff --git a/examples/erc721-receiver-mock/src/main.rs b/examples/erc721-receiver-mock/src/main.rs new file mode 100644 index 000000000..4ba43ccaf --- /dev/null +++ b/examples/erc721-receiver-mock/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] + +#[cfg(not(any(test, feature = "export-abi")))] +#[no_mangle] +pub extern "C" fn main() {} + +#[cfg(feature = "export-abi")] +fn main() { + erc721_receiver_mock_example::print_from_args(); +} diff --git a/examples/erc721-receiver-mock/tests/abi/mod.rs b/examples/erc721-receiver-mock/tests/abi/mod.rs new file mode 100644 index 000000000..598194f64 --- /dev/null +++ b/examples/erc721-receiver-mock/tests/abi/mod.rs @@ -0,0 +1,18 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc721ReceiverMock { + #[derive(Debug)] + function onERC721Received( + address operator, + address from, + uint256 token_id, + bytes calldata data + ) external returns (bytes4); + + #[derive(Debug, PartialEq)] + error CustomError(bytes4 data); + } +); diff --git a/examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs b/examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs new file mode 100644 index 000000000..d39f1682f --- /dev/null +++ b/examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs @@ -0,0 +1,107 @@ +#![cfg(feature = "e2e")] + +use abi::Erc721ReceiverMock; +use alloy::primitives::{uint, Bytes, U256, U8}; +use e2e::{constructor, Account, Constructor, Panic, PanicCode, Revert}; +use eyre::Result; +use openzeppelin_stylus::token::erc721::receiver::RECEIVER_FN_SELECTOR; + +mod abi; + +const REVERT_TYPE_NONE: U8 = uint!(0_U8); +const REVERT_TYPE_CUSTOM_ERROR: U8 = uint!(1_U8); +const REVERT_TYPE_PANIC: U8 = uint!(2_U8); + +fn constructor(error_type: U8) -> Constructor { + constructor!(error_type) +} + +// ============================================================================ +// Happy Path Tests +// ============================================================================ + +#[e2e::test] +async fn returns_correct_selector(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_NONE)) + .deploy() + .await? + .contract_address; + + let contract = Erc721ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let token_id = U256::from(1); + let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); + let interface_selector = contract + .onERC721Received(operator, from, token_id, data) + .call() + .await? + ._0; + + assert_eq!(RECEIVER_FN_SELECTOR, interface_selector); + + Ok(()) +} + +// ============================================================================ +// Error Handling Tests - Single Transfer +// ============================================================================ + +#[e2e::test] +async fn reverts_without_message(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_CUSTOM_ERROR)) + .deploy() + .await? + .contract_address; + + let contract = Erc721ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let token_id = U256::from(1); + let data = Bytes::new(); + + let err = contract + .onERC721Received(operator, from, token_id, data) + .call() + .await + .expect_err("should revert with custom error"); + + assert!(err.reverted_with(Erc721ReceiverMock::CustomError { + data: RECEIVER_FN_SELECTOR + })); + + Ok(()) +} + +#[e2e::test] +async fn panics(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(constructor(REVERT_TYPE_PANIC)) + .deploy() + .await? + .contract_address; + + let contract = Erc721ReceiverMock::new(contract_addr, &alice.wallet); + + let operator = alice.address(); + let from = alice.address(); + let token_id = U256::from(1); + let data = Bytes::new(); + + let err = contract + .onERC721Received(operator, from, token_id, data) + .call() + .await + .expect_err("should panic"); + + assert!(err.panicked_with(PanicCode::DivisionByZero)); + + Ok(()) +} From b34fe70b5e87ede2b604e542eae838a6b232b82f Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 03:52:36 +0200 Subject: [PATCH 10/11] fix: set proper expor-abi feature --- examples/erc1155-holder/Cargo.toml | 6 +++--- examples/erc1155-receiver-mock/Cargo.toml | 8 ++++---- examples/erc721-holder/Cargo.toml | 6 +++--- examples/erc721-receiver-mock/Cargo.toml | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/erc1155-holder/Cargo.toml b/examples/erc1155-holder/Cargo.toml index 16172121c..b2934c2f1 100644 --- a/examples/erc1155-holder/Cargo.toml +++ b/examples/erc1155-holder/Cargo.toml @@ -18,13 +18,13 @@ e2e.workspace = true tokio.workspace = true eyre.workspace = true -[lib] -crate-type = ["lib", "cdylib"] - [features] e2e = [] export-abi = ["openzeppelin-stylus/export-abi"] +[lib] +crate-type = ["lib", "cdylib"] + [[bin]] name = "erc1155-holder-example" path = "src/main.rs" diff --git a/examples/erc1155-receiver-mock/Cargo.toml b/examples/erc1155-receiver-mock/Cargo.toml index 13ed9dcb9..32113f8cf 100644 --- a/examples/erc1155-receiver-mock/Cargo.toml +++ b/examples/erc1155-receiver-mock/Cargo.toml @@ -19,12 +19,12 @@ e2e.workspace = true tokio.workspace = true eyre.workspace = true -[lib] -crate-type = ["lib", "cdylib"] - [features] e2e = [] -export-abi = ["stylus-sdk/export-abi"] +export-abi = ["openzeppelin-stylus/export-abi"] + +[lib] +crate-type = ["lib", "cdylib"] [[bin]] name = "erc1155-receiver-mock-example" diff --git a/examples/erc721-holder/Cargo.toml b/examples/erc721-holder/Cargo.toml index 20ece14e9..82be01342 100644 --- a/examples/erc721-holder/Cargo.toml +++ b/examples/erc721-holder/Cargo.toml @@ -18,13 +18,13 @@ e2e.workspace = true tokio.workspace = true eyre.workspace = true -[lib] -crate-type = ["lib", "cdylib"] - [features] e2e = [] export-abi = ["openzeppelin-stylus/export-abi"] +[lib] +crate-type = ["lib", "cdylib"] + [[bin]] name = "erc721-holder-example" path = "src/main.rs" diff --git a/examples/erc721-receiver-mock/Cargo.toml b/examples/erc721-receiver-mock/Cargo.toml index b068e9e87..ae41ff75b 100644 --- a/examples/erc721-receiver-mock/Cargo.toml +++ b/examples/erc721-receiver-mock/Cargo.toml @@ -19,12 +19,12 @@ e2e.workspace = true tokio.workspace = true eyre.workspace = true -[lib] -crate-type = ["lib", "cdylib"] - [features] e2e = [] -export-abi = ["stylus-sdk/export-abi"] +export-abi = ["openzeppelin-stylus/export-abi"] + +[lib] +crate-type = ["lib", "cdylib"] [[bin]] name = "erc721-receiver-mock-example" From 9c7d3632d28e60719624ff513c9e4c082d97aba9 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 6 Aug 2025 18:00:10 +0200 Subject: [PATCH 11/11] chore: remove Erc721ReceiverMock --- Cargo.lock | 15 -- Cargo.toml | 2 - examples/erc721-receiver-mock/Cargo.toml | 31 ----- examples/erc721-receiver-mock/src/lib.rs | 129 ------------------ examples/erc721-receiver-mock/src/main.rs | 10 -- .../erc721-receiver-mock/tests/abi/mod.rs | 18 --- .../tests/erc721-receiver-mock.rs | 107 --------------- 7 files changed, 312 deletions(-) delete mode 100644 examples/erc721-receiver-mock/Cargo.toml delete mode 100644 examples/erc721-receiver-mock/src/lib.rs delete mode 100644 examples/erc721-receiver-mock/src/main.rs delete mode 100644 examples/erc721-receiver-mock/tests/abi/mod.rs delete mode 100644 examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs diff --git a/Cargo.lock b/Cargo.lock index d90deaad7..b686078e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,21 +1955,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "erc721-receiver-mock-example" -version = "0.3.0-alpha.1" -dependencies = [ - "alloy", - "alloy-primitives", - "alloy-sol-macro", - "alloy-sol-types", - "e2e", - "eyre", - "openzeppelin-stylus", - "stylus-sdk", - "tokio", -] - [[package]] name = "erc721-wrapper-example" version = "0.3.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 4ca06d83e..ef200c5d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ members = [ "examples/beacon-proxy", "examples/uups-proxy", "examples/erc721-holder", - "examples/erc721-receiver-mock", "examples/erc1155-holder", "examples/erc1155-receiver-mock", ] @@ -72,7 +71,6 @@ default-members = [ "examples/beacon-proxy", "examples/uups-proxy", "examples/erc721-holder", - "examples/erc721-receiver-mock", "examples/erc1155-holder", "examples/erc1155-receiver-mock", ] diff --git a/examples/erc721-receiver-mock/Cargo.toml b/examples/erc721-receiver-mock/Cargo.toml deleted file mode 100644 index ae41ff75b..000000000 --- a/examples/erc721-receiver-mock/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "erc721-receiver-mock-example" -edition.workspace = true -license.workspace = true -repository.workspace = true -publish = false -version.workspace = true - -[dependencies] -openzeppelin-stylus.workspace = true -alloy-primitives.workspace = true -alloy-sol-macro.workspace = true -alloy-sol-types.workspace = true -stylus-sdk.workspace = true - -[dev-dependencies] -alloy.workspace = true -e2e.workspace = true -tokio.workspace = true -eyre.workspace = true - -[features] -e2e = [] -export-abi = ["openzeppelin-stylus/export-abi"] - -[lib] -crate-type = ["lib", "cdylib"] - -[[bin]] -name = "erc721-receiver-mock-example" -path = "src/main.rs" diff --git a/examples/erc721-receiver-mock/src/lib.rs b/examples/erc721-receiver-mock/src/lib.rs deleted file mode 100644 index 6d41b5779..000000000 --- a/examples/erc721-receiver-mock/src/lib.rs +++ /dev/null @@ -1,129 +0,0 @@ -#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] -extern crate alloc; - -use alloc::vec::Vec; - -use openzeppelin_stylus::token::erc721::{ - receiver::{IErc721Receiver, RECEIVER_FN_SELECTOR}, - utils::Erc721Holder, -}; -pub use sol::*; -use stylus_sdk::{ - abi::Bytes, - alloy_primitives::{ - aliases::{B32, U8}, - Address, U256, - }, - evm, - prelude::*, - storage::StorageU8, -}; - -mod sol { - use alloy_sol_macro::sol; - sol! { - #[derive(Debug)] - #[allow(missing_docs)] - event Received(address operator, address from, uint256 token_id, bytes data); - } - - sol! { - #[derive(Debug)] - #[allow(missing_docs)] - error CustomError(bytes4 data); - } -} - -#[derive(SolidityError, Debug)] -pub enum Error { - MockCustomError(CustomError), -} - -/// Enum representing different revert types for testing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RevertType { - None, - CustomError, - Panic, -} - -impl From for RevertType { - fn from(value: u8) -> Self { - match value { - 1 => RevertType::CustomError, - 2 => RevertType::Panic, - _ => RevertType::None, - } - } -} - -impl From for RevertType { - fn from(value: U8) -> Self { - let revert_type: u8 = u8::try_from(value).expect("should be valid"); - revert_type.into() - } -} - -impl From for u8 { - fn from(value: RevertType) -> Self { - match value { - RevertType::None => 0, - RevertType::CustomError => 1, - RevertType::Panic => 2, - } - } -} - -#[entrypoint] -#[storage] -struct Erc721ReceiverMock { - error_type: StorageU8, - holder: Erc721Holder, -} - -#[public] -#[implements(IErc721Receiver)] -impl Erc721ReceiverMock { - #[constructor] - fn constructor(&mut self, error_type: U8) { - self.error_type.set(error_type) - } -} - -#[public] -impl IErc721Receiver for Erc721ReceiverMock { - #[selector(name = "onERC721Received")] - fn on_erc721_received( - &mut self, - operator: Address, - from: Address, - token_id: U256, - data: Bytes, - ) -> Result> { - let error_type: RevertType = self.error_type.get().into(); - - match error_type { - RevertType::CustomError => { - Err(Error::MockCustomError(CustomError { - data: RECEIVER_FN_SELECTOR, - }) - .into()) - } - RevertType::Panic => { - // simulate a panic by dividing by [`U256::ZERO`]. - let _ = U256::from(0) / U256::from(0); - unreachable!() - } - RevertType::None => { - #[allow(deprecated)] - evm::log(Received { - operator, - from, - token_id, - data: data.to_vec().into(), - }); - self.holder.on_erc721_received(operator, from, token_id, data) - } - } - } -} diff --git a/examples/erc721-receiver-mock/src/main.rs b/examples/erc721-receiver-mock/src/main.rs deleted file mode 100644 index 4ba43ccaf..000000000 --- a/examples/erc721-receiver-mock/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![cfg_attr(not(any(test, feature = "export-abi")), no_main)] - -#[cfg(not(any(test, feature = "export-abi")))] -#[no_mangle] -pub extern "C" fn main() {} - -#[cfg(feature = "export-abi")] -fn main() { - erc721_receiver_mock_example::print_from_args(); -} diff --git a/examples/erc721-receiver-mock/tests/abi/mod.rs b/examples/erc721-receiver-mock/tests/abi/mod.rs deleted file mode 100644 index 598194f64..000000000 --- a/examples/erc721-receiver-mock/tests/abi/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![allow(dead_code)] -use alloy::sol; - -sol!( - #[sol(rpc)] - contract Erc721ReceiverMock { - #[derive(Debug)] - function onERC721Received( - address operator, - address from, - uint256 token_id, - bytes calldata data - ) external returns (bytes4); - - #[derive(Debug, PartialEq)] - error CustomError(bytes4 data); - } -); diff --git a/examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs b/examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs deleted file mode 100644 index d39f1682f..000000000 --- a/examples/erc721-receiver-mock/tests/erc721-receiver-mock.rs +++ /dev/null @@ -1,107 +0,0 @@ -#![cfg(feature = "e2e")] - -use abi::Erc721ReceiverMock; -use alloy::primitives::{uint, Bytes, U256, U8}; -use e2e::{constructor, Account, Constructor, Panic, PanicCode, Revert}; -use eyre::Result; -use openzeppelin_stylus::token::erc721::receiver::RECEIVER_FN_SELECTOR; - -mod abi; - -const REVERT_TYPE_NONE: U8 = uint!(0_U8); -const REVERT_TYPE_CUSTOM_ERROR: U8 = uint!(1_U8); -const REVERT_TYPE_PANIC: U8 = uint!(2_U8); - -fn constructor(error_type: U8) -> Constructor { - constructor!(error_type) -} - -// ============================================================================ -// Happy Path Tests -// ============================================================================ - -#[e2e::test] -async fn returns_correct_selector(alice: Account) -> Result<()> { - let contract_addr = alice - .as_deployer() - .with_constructor(constructor(REVERT_TYPE_NONE)) - .deploy() - .await? - .contract_address; - - let contract = Erc721ReceiverMock::new(contract_addr, &alice.wallet); - - let operator = alice.address(); - let from = alice.address(); - let token_id = U256::from(1); - let data = Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]); - let interface_selector = contract - .onERC721Received(operator, from, token_id, data) - .call() - .await? - ._0; - - assert_eq!(RECEIVER_FN_SELECTOR, interface_selector); - - Ok(()) -} - -// ============================================================================ -// Error Handling Tests - Single Transfer -// ============================================================================ - -#[e2e::test] -async fn reverts_without_message(alice: Account) -> Result<()> { - let contract_addr = alice - .as_deployer() - .with_constructor(constructor(REVERT_TYPE_CUSTOM_ERROR)) - .deploy() - .await? - .contract_address; - - let contract = Erc721ReceiverMock::new(contract_addr, &alice.wallet); - - let operator = alice.address(); - let from = alice.address(); - let token_id = U256::from(1); - let data = Bytes::new(); - - let err = contract - .onERC721Received(operator, from, token_id, data) - .call() - .await - .expect_err("should revert with custom error"); - - assert!(err.reverted_with(Erc721ReceiverMock::CustomError { - data: RECEIVER_FN_SELECTOR - })); - - Ok(()) -} - -#[e2e::test] -async fn panics(alice: Account) -> Result<()> { - let contract_addr = alice - .as_deployer() - .with_constructor(constructor(REVERT_TYPE_PANIC)) - .deploy() - .await? - .contract_address; - - let contract = Erc721ReceiverMock::new(contract_addr, &alice.wallet); - - let operator = alice.address(); - let from = alice.address(); - let token_id = U256::from(1); - let data = Bytes::new(); - - let err = contract - .onERC721Received(operator, from, token_id, data) - .call() - .await - .expect_err("should panic"); - - assert!(err.panicked_with(PanicCode::DivisionByZero)); - - Ok(()) -}