From c24e0bd4b4391ae414068882ee6337017dd2c7b1 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Wed, 4 Sep 2024 22:39:39 +0800 Subject: [PATCH 01/60] build erc1155 skeleton --- Cargo.lock | 8 + Cargo.toml | 2 +- .../src/token/erc1155/extensions/burnable.rs | 5 + contracts/src/token/erc1155/extensions/mod.rs | 4 + .../src/token/erc1155/extensions/supply.rs | 0 .../token/erc1155/extensions/uri_storage.rs | 0 contracts/src/token/erc1155/mod.rs | 193 ++++++++++++++++++ contracts/src/token/mod.rs | 1 + examples/erc1155/Cargo.toml | 17 ++ examples/erc1155/src/ERC1155ReceiverMock.sol | 7 + examples/erc1155/src/constructor.sol | 13 ++ examples/erc1155/src/lib.rs | 14 ++ examples/erc1155/tests/abi/mod.rs | 9 + examples/erc1155/tests/erc1155.rs | 12 ++ 14 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 contracts/src/token/erc1155/extensions/burnable.rs create mode 100644 contracts/src/token/erc1155/extensions/mod.rs create mode 100644 contracts/src/token/erc1155/extensions/supply.rs create mode 100644 contracts/src/token/erc1155/extensions/uri_storage.rs create mode 100644 contracts/src/token/erc1155/mod.rs create mode 100644 examples/erc1155/Cargo.toml create mode 100644 examples/erc1155/src/ERC1155ReceiverMock.sol create mode 100644 examples/erc1155/src/constructor.sol create mode 100644 examples/erc1155/src/lib.rs create mode 100644 examples/erc1155/tests/abi/mod.rs create mode 100644 examples/erc1155/tests/erc1155.rs diff --git a/Cargo.lock b/Cargo.lock index 6e380b922..610d74b76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1557,6 +1557,14 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erc1155-example" +version = "0.1.0" +dependencies = [ + "e2e", + "openzeppelin-stylus", +] + [[package]] name = "erc20-example" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 5e0d14f73..38448ea3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ "examples/basic/token", "examples/basic/script", "examples/ecdsa", - "benches", + "benches", "examples/erc1155", ] default-members = [ "contracts", diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs new file mode 100644 index 000000000..5be1fc4cd --- /dev/null +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -0,0 +1,5 @@ +use crate::token::erc1155::Erc1155; + +/// Extension of [`Erc1155`] that allows token holders to destroy both their +/// own tokens and those that they have been approved to use. +pub trait IErc1155Burnable {} diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs new file mode 100644 index 000000000..8ccacf3f8 --- /dev/null +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -0,0 +1,4 @@ +//! Common extensions to the ERC-1155 standard. +pub mod burnable; +pub mod supply; +pub mod uri_storage; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs new file mode 100644 index 000000000..477ba13ce --- /dev/null +++ b/contracts/src/token/erc1155/mod.rs @@ -0,0 +1,193 @@ +//! Implementation of the [`Erc1155`] token standard. + +use stylus_sdk::{alloy_sol_types::sol, call::MethodError, prelude::*}; + +pub mod extensions; + +sol! { + /// Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. + #[allow(missing_docs)] + event TransferSingle( + address indexed operator, + address indexed from, + address indexed to, + uint256 id, + uint256 value + ); + + /// Equivalent to multiple {TransferSingle} events, where `operator`. + /// `from` and `to` are the same for all transfers. + #[allow(missing_docs)] + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /// Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + /// `approved`. + #[allow(missing_docs)] + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + /// + /// If an {URI} event was emitted for `id`, the [standard] + /// (https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees]) that `value` will equal the value + /// returned by [`IERC1155MetadataURI-uri`]. + #[allow(missing_docs)] + event URI(string value, uint256 indexed id); +} + +sol! { + /// Indicates an error related to the current `balance` of a `sender`. Used + /// in transfers. + /// + /// * `sender` - Address whose tokens are being transferred. + /// * `balance` - Current balance for the interacting account. + /// * `needed` - Minimum amount required to perform a transfer. + /// * `tokenId` - Identifier number of a token. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /// Indicates a failure with the token `sender`. Used in transfers. + /// + /// * `sender` - Address whose tokens are being transferred. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155InvalidSender(address sender); + + /// Indicates a failure with the token `receiver`. Used in transfers. + /// + /// * `receiver` - Address to which tokens are being transferred. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155InvalidReceiver(address receiver); + + /// Indicates a failure with the `operator`’s approval. Used + /// in transfers. + /// + /// * `operator` - Address that may be allowed to operate on tokens without being their owner. + /// * `owner` - Address of the current owner of a token. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155MissingApprovalForAll(address operator, address owner); + + /// Indicates a failure with the `approver` of a token to be approved. Used + /// in approvals. + /// + /// * `approver` - Address initiating an approval operation. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155InvalidApprover(address approver); + + /// Indicates a failure with the `operator` to be approved. Used + /// in approvals. + /// + /// * `operator` - Address that may be allowed to operate on tokens without being their owner. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155InvalidOperator(address operator); + + /// Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + /// Used in batch transfers. + /// + /// * `idsLength` - Length of the array of token identifiers. + /// * `valuesLength` - Length of the array of token amounts. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} + +/// An [`Erc1155`] error defined as described in [ERC-6093]. +/// +/// [ERC-6093]: https://eips.ethereum.org/EIPS/eip-6093 +#[derive(SolidityError, Debug)] +pub enum Error { + /// Indicates an error related to the current `balance` of `sender`. Used + /// in transfers. + InsufficientBalance(ERC1155InsufficientBalance), + /// Indicates a failure with the token `sender`. Used in transfers. + InvalidSender(ERC1155InvalidSender), + /// Indicates a failure with the token `receiver`. Used in transfers. + InvalidReceiver(ERC1155InvalidReceiver), + /// Indicates a failure with the `operator`’s approval. Used in transfers. + MissingApprovalForAll(ERC1155MissingApprovalForAll), + /// Indicates a failure with the `approver` of a token to be approved. Used + /// in approvals. + InvalidApprover(ERC1155InvalidApprover), + /// Indicates a failure with the `operator` to be approved. Used in + /// approvals. + InvalidOperator(ERC1155InvalidOperator), + /// Indicates an array length mismatch between ids and values in a + /// safeBatchTransferFrom operation. Used in batch transfers. + InvalidArrayLength(ERC1155InvalidArrayLength), +} + +impl MethodError for Error { + fn encode(self) -> alloc::vec::Vec { + self.into() + } +} + +sol_interface! { + interface IERC1155Receiver { + + /// Handles the receipt of a single ERC-1155 token type. This function is + /// called at the end of a `safeTransferFrom` after the balance has been updated. + /// + /// NOTE: To accept the transfer, this must return + /// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + /// (i.e. 0xf23a6e61, or its own function selector). + /// + /// * `operator` - The address which initiated the transfer (i.e. msg.sender) + /// * `from` - The address which previously owned the token + /// * `id` - The ID of the token being transferred + /// * `value` - The amount of tokens being transferred + /// * `data` - Additional data with no specified format + /// Return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + #[allow(missing_docs)] + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /// Handles the receipt of a multiple ERC-1155 token types. This function + /// is called at the end of a `safeBatchTransferFrom` after the balances have + /// been updated. + /// + /// NOTE: To accept the transfer(s), this must return + /// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + /// (i.e. 0xbc197c81, or its own function selector). + /// + /// * `operator` - The address which initiated the batch transfer (i.e. msg.sender) + /// * `from` - The address which previously owned the token + /// * `ids` - An array containing ids of each token being transferred (order and length must match values array) + /// * `values` - An array containing amounts of each token being transferred (order and length must match ids array) + /// * `data` - Additional data with no specified format + /// * Return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + #[allow(missing_docs)] + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); + } +} + +sol_storage! { + /// State of an [`Erc1155`] token. + pub struct Erc1155 { + /// Maps users to balances. + mapping(uint256 => mapping(address => uint256)) _balances; + /// Maps owners to a mapping of operator approvals. + mapping(address => mapping(address => bool)) _operator_approvals; + } +} diff --git a/contracts/src/token/mod.rs b/contracts/src/token/mod.rs index 93385ec28..2bb3927b5 100644 --- a/contracts/src/token/mod.rs +++ b/contracts/src/token/mod.rs @@ -1,3 +1,4 @@ //! Token standards. +pub mod erc1155; pub mod erc20; pub mod erc721; diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml new file mode 100644 index 000000000..e192c7cf3 --- /dev/null +++ b/examples/erc1155/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "erc1155-example" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true + +[dependencies] +openzeppelin-stylus = { path = "../../contracts" } + +[dev-dependencies] +e2e = { path = "../../lib/e2e" } + +[lints] +workspace = true diff --git a/examples/erc1155/src/ERC1155ReceiverMock.sol b/examples/erc1155/src/ERC1155ReceiverMock.sol new file mode 100644 index 000000000..7c2f5f7e2 --- /dev/null +++ b/examples/erc1155/src/ERC1155ReceiverMock.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC1155/IERC1155Receiver.sol"; + +contract IERC1155ReceiverMock is IERC1155Receiver {} diff --git a/examples/erc1155/src/constructor.sol b/examples/erc1155/src/constructor.sol new file mode 100644 index 000000000..d7c52eafb --- /dev/null +++ b/examples/erc1155/src/constructor.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Erc1155Example { + mapping(address => mapping(uint256 => uint256)) private _balanceOf; + mapping(address => mapping(address => bool)) private _isApprovedForAll; + + bool _paused; + + constructor() { + _paused = false; + } +} diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs new file mode 100644 index 000000000..b93cf3ffd --- /dev/null +++ b/examples/erc1155/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs new file mode 100644 index 000000000..572b815bb --- /dev/null +++ b/examples/erc1155/tests/abi/mod.rs @@ -0,0 +1,9 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc1155 { + + } +); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs new file mode 100644 index 000000000..334ff39d4 --- /dev/null +++ b/examples/erc1155/tests/erc1155.rs @@ -0,0 +1,12 @@ +#![cfg(feature = "e2e")] + +use abi::Erc1155; + +mod abi; + +// ============================================================================ +// Integration Tests: ERC-1155 Token Standard +// ============================================================================ + +#[e2e::test] +async fn constructs(alice: Account) -> eyre::Result<()> {} From b0539cd786f58f867eb27d2ee0086df425c06bc6 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Thu, 5 Sep 2024 17:51:23 +0800 Subject: [PATCH 02/60] migrate erc1155 uri-storage --- .../src/token/erc1155/extensions/burnable.rs | 2 + .../src/token/erc1155/extensions/supply.rs | 16 +++++ .../token/erc1155/extensions/uri_storage.rs | 63 +++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index 5be1fc4cd..b44ecdd08 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -1,3 +1,5 @@ +//! Extension of {ERC1155} that allows token holders to destroy both their +//! own tokens and those that they have been approved to use. use crate::token::erc1155::Erc1155; /// Extension of [`Erc1155`] that allows token holders to destroy both their diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index e69de29bb..a599d9c74 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -0,0 +1,16 @@ +//! Extension of ERC-1155 that adds tracking of total supply per id. +//! +//! Useful for scenarios where Fungible and Non-fungible tokens have to be +//! clearly identified. Note: While a totalSupply of 1 might mean the +//! corresponding is an NFT, there is no guarantees that no other token +//! with the same id are not going to be minted. +//! +//! NOTE: This contract implies a global limit of 2**256 - 1 to the number +//! of tokens that can be minted. +//! +//! CAUTION: This extension should not be added in an upgrade to an already +//! deployed contract. +use alloy_sol_types::sol; +use stylus_proc::sol_storage; + +sol_storage! {} diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs index e69de29bb..3b0205a43 100644 --- a/contracts/src/token/erc1155/extensions/uri_storage.rs +++ b/contracts/src/token/erc1155/extensions/uri_storage.rs @@ -0,0 +1,63 @@ +//! ERC-1155 token with storage based token URI management. +//! +//! Inspired by the {ERC721URIStorage} extension +use alloc::string::String; + +use alloy_primitives::U256; +use alloy_sol_types::sol; +use stylus_proc::{external, sol_storage}; +use stylus_sdk::evm; + +sol! { + + /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + /// + /// If an {URI} event was emitted for `id`, the standard + /// https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + /// returned by {IERC1155MetadataURI-uri}. + #[allow(missing_docs)] + event URI(string value, uint256 indexed id); +} + +sol_storage! { + /// Uri Storage. + pub struct Erc1155UriStorage { + /// Optional mapping for token URIs. + mapping(uint256 => string) _token_uris; + /// Optional base URI + string _base_uri; + } +} + +impl Erc1155UriStorage { + /// Sets `tokenURI` as the tokenURI of `tokenId`. + pub fn _set_uri(&mut self, token_id: U256, token_uri: String) { + self._token_uris.setter(token_id).set_str(token_uri); + evm::log(URI { value: self.uri(token_id), id: token_id }); + } + + /// Sets `baseURI` as the `_baseURI` for all tokens + pub fn _set_base_uri(&mut self, base_uri: String) { + self._base_uri.set_str(base_uri); + } +} + +#[external] +impl Erc1155UriStorage { + /// Returns the Uniform Resource Identifier (URI) for `token_id` token. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Id of a token. + #[must_use] + pub fn uri(&self, token_id: U256) -> String { + if !self._token_uris.getter(token_id).is_empty() { + let update_uri = self._base_uri.get_string() + + &self._token_uris.getter(token_id).get_string(); + update_uri + } else { + todo!() + } + } +} From ae3a353c0c9da21aeecb86dc40f14f854c4f5f49 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Fri, 6 Sep 2024 15:37:33 +0800 Subject: [PATCH 03/60] add IErc1155 trait --- contracts/src/token/erc1155/mod.rs | 150 ++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 477ba13ce..666813ed5 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,6 +1,10 @@ //! Implementation of the [`Erc1155`] token standard. +use alloc::vec::Vec; -use stylus_sdk::{alloy_sol_types::sol, call::MethodError, prelude::*}; +use alloy_primitives::{Address, U256}; +use stylus_sdk::{ + abi::Bytes, alloy_sol_types::sol, call::MethodError, prelude::*, +}; pub mod extensions; @@ -191,3 +195,147 @@ sol_storage! { mapping(address => mapping(address => bool)) _operator_approvals; } } + +/// Required interface of an [`Erc1155`] compliant contract. +pub trait IErc1155 { + /// The error type associated to this ERC-721 trait implementation. + type Error: Into>; + + /// Returns the number of tokens in ``owner``'s account. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `owner` - Account of the token's owner. + fn balance_of(&self, owner: Address) -> Result; + + /// xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `accounts` - All account of the tokens' owner. + /// * `ids` - All token identifiers. + /// + /// Requirements: + /// + /// * - `accounts` and `ids` must have the same length. + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result; + + /// Grants or revokes permission to `operator` to transfer the caller's + /// tokens, according to `approved`, + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `operator` - Account to add to the set of authorized operators. + /// * `approved` - Flag that determines whether or not permission will be + /// granted to `operator`. If true, this means `operator` will be allowed + /// to manage `msg::sender`'s assets. + /// + /// # Errors + /// + /// * If `operator` is `Address::ZERO`, then the error + /// [`Error::InvalidOperator`] is returned. + /// + /// # Requirements: + /// + /// * The `operator` cannot be the `Address::ZERO`. + /// + /// # Events + /// + /// Emits an [`ApprovalForAll`] event. + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error>; + + /// Returns true if `operator` is approved to transfer ``account``'s + /// tokens. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `owner` - Account of the token's owner. + /// * `operator` - Account to be checked. + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool; + + /// Transfers a `value` amount of tokens of type `id` from `from` to `to`. + /// + /// WARNING: This function can potentially allow a reentrancy attack when + /// transferring tokens to an untrusted contract, when invoking + /// [`IERC1155Receiver::on_erc_1155_received`] on the receiver. Ensure to + /// follow the checks-effects-interactions pattern and consider + /// employing reentrancy guards when interacting with untrusted + /// contracts. + /// + /// Emits a [`TransferSingle`] event. + /// + /// # Errors + /// + /// If `to` is `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If `from` is `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. + /// If the `from` is not sender, then the error + /// [`Error::MissingApprovalForAll`] is returned. + /// If the caller does not have the right to approve, then the error + /// [`Error::MissingApprovalForAll`] is returned. + /// If the token does not exist, then the error + /// [`Error::NonexistentToken`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// + /// # Requirements: + /// * + /// * - `to` cannot be the zero address. + /// * - If the caller is not `from`, it must have been approved to spend + /// ``from``'s tokens via [`Self::set_approval_for_all`]. + /// * - `from` must have a balance of tokens of type `id` of at least + /// `value` amount. + /// * - If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_received`] and return the + /// acceptance magic value. + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error>; + + /// xref:ROOT:erc1155.adoc#batch-operations[Batched] version of + /// [`Self::safe_transfer_from`]. + /// + /// WARNING: This function can potentially allow a reentrancy attack when + /// transferring tokens to an untrusted contract, when invoking + /// [`IERC1155Receiver::on_erc_1155_batch_received`] on the receiver. Ensure + /// to follow the checks-effects-interactions pattern and consider + /// employing reentrancy guards when interacting with untrusted + /// contracts. + /// + /// Emits either a [`TransferSingle`] or a [`TransferBatch`] event, + /// depending on the length of the array arguments. + /// + /// * Requirements: + /// * + /// * - `ids` and `values` must have the same length. + /// * - If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the + /// acceptance magic value. + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error>; +} From f049f1d92a06e04e3c1cb7d96fb9f1f051569be9 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Sat, 7 Sep 2024 22:20:06 +0800 Subject: [PATCH 04/60] try the first erc1155 internal func implementation --- .../src/token/erc1155/extensions/burnable.rs | 66 +++++++++++++- contracts/src/token/erc1155/mod.rs | 86 ++++++++++++++++++- 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index b44ecdd08..299e8bd7b 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -1,7 +1,71 @@ //! Extension of {ERC1155} that allows token holders to destroy both their //! own tokens and those that they have been approved to use. + +use alloc::vec::Vec; + +use alloy_primitives::{Address, U256}; + use crate::token::erc1155::Erc1155; /// Extension of [`Erc1155`] that allows token holders to destroy both their /// own tokens and those that they have been approved to use. -pub trait IErc1155Burnable {} +pub trait IErc1155Burnable { + /// The error type associated to this ERC-1155 burnable trait + /// implementation. + type Error: Into>; + + /// Extension of {ERC1155} that allows token holders to destroy both their + /// own tokens and those that they have been approved to use. + /// + /// The approval is cleared when the token is burned. Relies on the `_burn` + /// mechanism. + /// + /// # Arguments + /// + /// * `value` - Amount to be burnt. + /// + /// # Errors + /// + /// If the caller is not account address and the account has not been + /// approved, then the error [`Error::MissingApprovalForAll`] is + /// returned. + /// + /// # Requirements: + /// + /// * `token_id` must exist. + /// * The caller or account must own `token_id` or be an approved operator. + fn burn( + &mut self, + account: Address, + token_id: U256, + value: U256, + ) -> Result<(), Self::Error>; + + /// Extension of {ERC1155} that allows token holders to destroy both their + /// own tokens and those that they have been approved to use. + /// + /// The approval is cleared when the token is burned. Relies on the `_burn` + /// mechanism. + /// + /// # Arguments + /// + /// * `values` - All amount to be burnt. + /// * `ids` - All amount to be burnt. + /// + /// # Errors + /// + /// If the caller is not account address and the account has not been + /// approved, then the error [`Error::MissingApprovalForAll`] is + /// returned. + /// + /// # Requirements: + /// + /// * `token_id` must exist. + /// * The caller or account must own `token_id` or be an approved operator. + fn burn_batch( + &mut self, + account: Address, + ids: Vec, + values: Vec, + ) -> Result<(), Self::Error>; +} diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 666813ed5..91dca8f1f 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,11 +1,13 @@ //! Implementation of the [`Erc1155`] token standard. use alloc::vec::Vec; -use alloy_primitives::{Address, U256}; +use alloy_primitives::{Address, Uint, U256}; use stylus_sdk::{ - abi::Bytes, alloy_sol_types::sol, call::MethodError, prelude::*, + abi::Bytes, alloy_sol_types::sol, call::MethodError, evm, msg, prelude::*, }; +use crate::utils::math::storage::{AddAssignUnchecked, SubAssignUnchecked}; + pub mod extensions; sol! { @@ -339,3 +341,83 @@ pub trait IErc1155 { data: Bytes, ) -> Result<(), Self::Error>; } + +impl Erc1155 { + /// Transfers a `value` amount of tokens of type `id` from `from` to `to`. + /// Will mint (or burn) if `from` (or `to`) is the zero address. + /// + /// Requirements: + /// + /// * - If `to` refers to a smart contract, it must implement either + /// [`IERC1155Receiver::on_erc_1155_received`] + /// * or [`IERC1155Receiver::on_erc_1155_received`] and return the + /// acceptance magic value. + /// * - `ids` and `values` must have the same length. + /// + /// /// # Errors + /// + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. + /// + /// NOTE: The ERC-1155 acceptance check is not performed in this function. + /// See [`Self::_updateWithAcceptanceCheck`] instead. + /// + /// Event + /// + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. + fn _update( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + ) -> Result<(), Error> { + if ids.len() != values.len() { + return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { + idsLength: Uint::<256, 4>::from(ids.len()), + valuesLength: Uint::<256, 4>::from(values.len()), + })); + } + + let operator = msg::sender(); + for (i, &id) in ids.iter().enumerate() { + let value = values[i]; + let from_balance = self._balances.get(id).get(from); + if from_balance < value { + return Err(Error::InsufficientBalance( + ERC1155InsufficientBalance { + sender: from, + balance: from_balance, + needed: value, + tokenId: id, + }, + )); + } + self._balances.setter(id).setter(from).sub_assign_unchecked(value); + + if to != Address::ZERO { + self._balances + .setter(id) + .setter(to) + .add_assign_unchecked(value); + } + } + + if ids.len() == 1 { + evm::log(TransferSingle { + operator, + from, + to, + id: ids[0], + value: values[0], + }); + } else { + evm::log(TransferBatch { operator, from, to, ids, values }); + } + + Ok(()) + } +} From 0188be03eb03f75a600511b6e7e86a1b9d707af9 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Mon, 9 Sep 2024 17:27:27 +0800 Subject: [PATCH 05/60] add balanceof func and some tests --- .../src/token/erc1155/extensions/burnable.rs | 2 - .../src/token/erc1155/extensions/supply.rs | 4 - contracts/src/token/erc1155/mod.rs | 425 ++++++++++++++++-- 3 files changed, 379 insertions(+), 52 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index 299e8bd7b..7fb83ff2d 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -5,8 +5,6 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use crate::token::erc1155::Erc1155; - /// Extension of [`Erc1155`] that allows token holders to destroy both their /// own tokens and those that they have been approved to use. pub trait IErc1155Burnable { diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index a599d9c74..3f7a68e29 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -10,7 +10,3 @@ //! //! CAUTION: This extension should not be added in an upgrade to an already //! deployed contract. -use alloy_sol_types::sol; -use stylus_proc::sol_storage; - -sol_storage! {} diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 91dca8f1f..a5045edcf 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,9 +1,13 @@ //! Implementation of the [`Erc1155`] token standard. use alloc::vec::Vec; -use alloy_primitives::{Address, Uint, U256}; +use alloy_primitives::{fixed_bytes, Address, FixedBytes, Uint, U256}; use stylus_sdk::{ - abi::Bytes, alloy_sol_types::sol, call::MethodError, evm, msg, prelude::*, + abi::Bytes, + alloy_sol_types::sol, + call::{self, Call, MethodError}, + evm, msg, + prelude::*, }; use crate::utils::math::storage::{AddAssignUnchecked, SubAssignUnchecked}; @@ -11,13 +15,13 @@ use crate::utils::math::storage::{AddAssignUnchecked, SubAssignUnchecked}; pub mod extensions; sol! { - /// Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. + /// Emitted when `value` amount of tokens of type `token_id` are transferred from `from` to `to` by `operator`. #[allow(missing_docs)] event TransferSingle( address indexed operator, address indexed from, address indexed to, - uint256 id, + uint256 token_id, uint256 value ); @@ -28,7 +32,7 @@ sol! { address indexed operator, address indexed from, address indexed to, - uint256[] ids, + uint256[] token_ids, uint256[] values ); @@ -37,13 +41,13 @@ sol! { #[allow(missing_docs)] event ApprovalForAll(address indexed account, address indexed operator, bool approved); - /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + /// Emitted when the URI for token type `token_id` changes to `value`, if it is a non-programmatic URI. /// - /// If an {URI} event was emitted for `id`, the [standard] + /// If an {URI} event was emitted for `token_id`, the [standard] /// (https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees]) that `value` will equal the value /// returned by [`IERC1155MetadataURI-uri`]. #[allow(missing_docs)] - event URI(string value, uint256 indexed id); + event URI(string value, uint256 indexed token_id); } sol! { @@ -53,10 +57,10 @@ sol! { /// * `sender` - Address whose tokens are being transferred. /// * `balance` - Current balance for the interacting account. /// * `needed` - Minimum amount required to perform a transfer. - /// * `tokenId` - Identifier number of a token. + /// * `token_id` - Identifier number of a token. #[derive(Debug)] #[allow(missing_docs)] - error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 token_id); /// Indicates a failure with the token `sender`. Used in transfers. /// @@ -97,14 +101,14 @@ sol! { #[allow(missing_docs)] error ERC1155InvalidOperator(address operator); - /// Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + /// Indicates an array length mismatch between token_ids and values in a safeBatchTransferFrom operation. /// Used in batch transfers. /// - /// * `idsLength` - Length of the array of token identifiers. - /// * `valuesLength` - Length of the array of token amounts. + /// * `ids_length` - Length of the array of token identifiers. + /// * `values_length` - Length of the array of token amounts. #[derive(Debug)] #[allow(missing_docs)] - error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); + error ERC1155InvalidArrayLength(uint256 ids_length, uint256 values_length); } /// An [`Erc1155`] error defined as described in [ERC-6093]. @@ -119,6 +123,9 @@ pub enum Error { InvalidSender(ERC1155InvalidSender), /// Indicates a failure with the token `receiver`. Used in transfers. InvalidReceiver(ERC1155InvalidReceiver), + /// Indicates a failure with the token `receiver`, with the reason + /// specified by it. + InvalidReceiverWithReason(call::Error), /// Indicates a failure with the `operator`’s approval. Used in transfers. MissingApprovalForAll(ERC1155MissingApprovalForAll), /// Indicates a failure with the `approver` of a token to be approved. Used @@ -127,7 +134,7 @@ pub enum Error { /// Indicates a failure with the `operator` to be approved. Used in /// approvals. InvalidOperator(ERC1155InvalidOperator), - /// Indicates an array length mismatch between ids and values in a + /// Indicates an array length mismatch between token_ids and values in a /// safeBatchTransferFrom operation. Used in batch transfers. InvalidArrayLength(ERC1155InvalidArrayLength), } @@ -150,7 +157,7 @@ sol_interface! { /// /// * `operator` - The address which initiated the transfer (i.e. msg.sender) /// * `from` - The address which previously owned the token - /// * `id` - The ID of the token being transferred + /// * `token_id` - The ID of the token being transferred /// * `value` - The amount of tokens being transferred /// * `data` - Additional data with no specified format /// Return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed @@ -158,7 +165,7 @@ sol_interface! { function onERC1155Received( address operator, address from, - uint256 id, + uint256 token_id, uint256 value, bytes calldata data ) external returns (bytes4); @@ -173,7 +180,7 @@ sol_interface! { /// /// * `operator` - The address which initiated the batch transfer (i.e. msg.sender) /// * `from` - The address which previously owned the token - /// * `ids` - An array containing ids of each token being transferred (order and length must match values array) + /// * `token_ids` - An array containing ids of each token being transferred (order and length must match values array) /// * `values` - An array containing amounts of each token being transferred (order and length must match ids array) /// * `data` - Additional data with no specified format /// * Return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed @@ -181,7 +188,7 @@ sol_interface! { function onERC1155BatchReceived( address operator, address from, - uint256[] calldata ids, + uint256[] calldata token_ids, uint256[] calldata values, bytes calldata data ) external returns (bytes4); @@ -198,6 +205,11 @@ sol_storage! { } } +/// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when +/// calling other contracts and not `&mut (impl TopLevelStorage + +/// BorrowMut)`. Should be fixed in the future by the Stylus team. +unsafe impl TopLevelStorage for Erc1155 {} + /// Required interface of an [`Erc1155`] compliant contract. pub trait IErc1155 { /// The error type associated to this ERC-721 trait implementation. @@ -209,7 +221,11 @@ pub trait IErc1155 { /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. - fn balance_of(&self, owner: Address) -> Result; + fn balance_of( + &self, + account: Address, + token_id: U256, + ) -> Result; /// xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. /// @@ -217,16 +233,16 @@ pub trait IErc1155 { /// /// * `&self` - Read access to the contract's state. /// * `accounts` - All account of the tokens' owner. - /// * `ids` - All token identifiers. + /// * `token_ids` - All token identifiers. /// /// Requirements: /// - /// * - `accounts` and `ids` must have the same length. + /// * - `accounts` and `token_ids` must have the same length. fn balance_of_batch( &self, accounts: Vec
, - ids: Vec, - ) -> Result; + token_ids: Vec, + ) -> Result, Self::Error>; /// Grants or revokes permission to `operator` to transfer the caller's /// tokens, according to `approved`, @@ -265,9 +281,10 @@ pub trait IErc1155 { /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `operator` - Account to be checked. - fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool; + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; - /// Transfers a `value` amount of tokens of type `id` from `from` to `to`. + /// Transfers a `value` amount of tokens of type `token_id` from `from` to + /// `to`. /// /// WARNING: This function can potentially allow a reentrancy attack when /// transferring tokens to an untrusted contract, when invoking @@ -299,7 +316,7 @@ pub trait IErc1155 { /// * - `to` cannot be the zero address. /// * - If the caller is not `from`, it must have been approved to spend /// ``from``'s tokens via [`Self::set_approval_for_all`]. - /// * - `from` must have a balance of tokens of type `id` of at least + /// * - `from` must have a balance of tokens of type `token_id` of at least /// `value` amount. /// * - If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_received`] and return the @@ -308,7 +325,7 @@ pub trait IErc1155 { &mut self, from: Address, to: Address, - id: U256, + token_id: U256, value: U256, data: Bytes, ) -> Result<(), Self::Error>; @@ -328,7 +345,7 @@ pub trait IErc1155 { /// /// * Requirements: /// * - /// * - `ids` and `values` must have the same length. + /// * - `token_ids` and `values` must have the same length. /// * - If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the /// acceptance magic value. @@ -336,15 +353,83 @@ pub trait IErc1155 { &mut self, from: Address, to: Address, - ids: Vec, + token_ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Self::Error>; } +#[external] +impl IErc1155 for Erc1155 { + type Error = Error; + + fn balance_of( + &self, + account: Address, + token_id: U256, + ) -> Result { + Ok(self._balances.get(token_id).get(account)) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + token_ids: Vec, + ) -> Result, Self::Error> { + if accounts.len() != token_ids.len() { + return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length: Uint::<256, 4>::from(token_ids.len()), + values_length: Uint::<256, 4>::from(accounts.len()), + })); + } + + let mut balances = Vec::with_capacity(accounts.len()); + for (i, &account) in accounts.iter().enumerate() { + let token_id = token_ids[i]; + let balance = self._balances.get(token_id).get(account); + balances.push(balance); + } + Ok(balances) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self._operator_approvals.get(account).get(operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + token_ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + Ok(()) + } +} + impl Erc1155 { - /// Transfers a `value` amount of tokens of type `id` from `from` to `to`. - /// Will mint (or burn) if `from` (or `to`) is the zero address. + /// Transfers a `value` amount of tokens of type `token_ids` from `from` to + /// `to`. Will mint (or burn) if `from` (or `to`) is the zero address. /// /// Requirements: /// @@ -352,11 +437,11 @@ impl Erc1155 { /// [`IERC1155Receiver::on_erc_1155_received`] /// * or [`IERC1155Receiver::on_erc_1155_received`] and return the /// acceptance magic value. - /// * - `ids` and `values` must have the same length. + /// * - `token_ids` and `values` must have the same length. /// /// /// # Errors /// - /// If length of `ids` is not equal to length of `values`, then the + /// If length of `token_ids` is not equal to length of `values`, then the /// error [`Error::InvalidArrayLength`] is returned. /// If `value` is greater than the balance of the `from` account, /// then the error [`Error::InsufficientBalance`] is returned. @@ -372,52 +457,300 @@ impl Erc1155 { &mut self, from: Address, to: Address, - ids: Vec, + token_ids: Vec, values: Vec, ) -> Result<(), Error> { - if ids.len() != values.len() { + if token_ids.len() != values.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - idsLength: Uint::<256, 4>::from(ids.len()), - valuesLength: Uint::<256, 4>::from(values.len()), + ids_length: Uint::<256, 4>::from(token_ids.len()), + values_length: Uint::<256, 4>::from(values.len()), })); } let operator = msg::sender(); - for (i, &id) in ids.iter().enumerate() { + for (i, &token_id) in token_ids.iter().enumerate() { let value = values[i]; - let from_balance = self._balances.get(id).get(from); + let from_balance = self._balances.get(token_id).get(from); if from_balance < value { return Err(Error::InsufficientBalance( ERC1155InsufficientBalance { sender: from, balance: from_balance, needed: value, - tokenId: id, + token_id, }, )); } - self._balances.setter(id).setter(from).sub_assign_unchecked(value); + self._balances + .setter(token_id) + .setter(from) + .sub_assign_unchecked(value); if to != Address::ZERO { self._balances - .setter(id) + .setter(token_id) .setter(to) .add_assign_unchecked(value); } } - if ids.len() == 1 { + if token_ids.len() == 1 { evm::log(TransferSingle { operator, from, to, - id: ids[0], + token_id: token_ids[0], value: values[0], }); } else { - evm::log(TransferBatch { operator, from, to, ids, values }); + evm::log(TransferBatch { operator, from, to, token_ids, values }); } Ok(()) } + + /// Version of [`Self::_update`] that performs the token acceptance check by + /// calling [`IERC1155Receiver::on_erc_1155_received`] or + /// [`IERC1155Receiver::on_erc_1155_received`] on the receiver address if it + /// contains code (eg. is a smart contract at the moment of execution). + /// + /// IMPORTANT: Overriding this function is discouraged because it poses a + /// reentrancy risk from the receiver. So any update to the contract + /// state after this function would break the check-effect-interaction + /// pattern. Consider overriding [`Self::_update`] instead. + /// + /// # Arguments + /// + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_ids` - Array of all token id. + /// * `values` - Array of all amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + fn _update_with_acceptance_check( + &mut self, + from: Address, + to: Address, + token_ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Error> { + self._update(from, to, token_ids.clone(), values.clone())?; + if to != Address::ZERO { + let operator = msg::sender(); + if token_ids.len() == 1 { + let token_id = token_ids[0]; + let value = values[0]; + self._check_on_erc1155_received( + operator, from, to, token_id, value, data, + )?; + } else { + self._check_on_erc1155_batch_received( + operator, from, to, token_ids, values, data, + )?; + } + } + Ok(()) + } + + /// Performs an acceptance check for the provided `operator` by + /// calling [`IERC1155Receiver::on_erc_1155_received`] on the `to` address. + /// The `operator` is generally the address that initiated the token + /// transfer (i.e. `msg.sender`). + /// + /// The acceptance call is not executed and treated as a no-op if the + /// target address is doesn't contain code (i.e. an EOA). Otherwise, + /// the recipient must implement [`IERC1155Receiver::on_erc_1155_received`] + /// and return the acceptance magic value to accept the transfer. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `operator` - Account to add to the set of authorized operators. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `value` - Amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + fn _check_on_erc1155_received( + &mut self, + operator: Address, + from: Address, + to: Address, + token_id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Error> { + const RECEIVER_FN_SELECTOR: FixedBytes<4> = fixed_bytes!("f23a6e61"); + + if !to.has_code() { + return Ok(()); + } + + let receiver = IERC1155Receiver::new(to); + let call = Call::new_in(self); + let data = data.to_vec(); + let result = receiver + .on_erc_1155_received(call, operator, from, token_id, value, data); + + let id = match result { + Ok(id) => id, + Err(e) => { + if let call::Error::Revert(ref reason) = e { + if reason.len() > 0 { + // Non-IERC1155Receiver implementer. + return Err(Error::InvalidReceiverWithReason(e)); + } + } + + return Err(ERC1155InvalidReceiver { receiver: to }.into()); + } + }; + + // Token rejected. + if id != RECEIVER_FN_SELECTOR { + return Err(ERC1155InvalidReceiver { receiver: to }.into()); + } + + Ok(()) + } + + /// Performs a batch acceptance check for the provided `operator` by + /// calling [`IERC1155Receiver::on_erc_1155_received`] on the `to` address. + /// The `operator` is generally the address that initiated the token + /// transfer (i.e. `msg.sender`). + /// + /// The acceptance call is not executed and treated as a no-op if the + /// target address is doesn't contain code (i.e. an EOA). Otherwise, + /// the recipient must implement [`IERC1155Receiver::on_erc_1155_received`] + /// and return the acceptance magic value to accept the transfer. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `operator` - Account to add to the set of authorized operators. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_ids` - Array of all token id. + /// * `values` - Array of all amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + fn _check_on_erc1155_batch_received( + &mut self, + operator: Address, + from: Address, + to: Address, + token_ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Error> { + const RECEIVER_FN_SELECTOR: FixedBytes<4> = fixed_bytes!("bc197c81"); + + if !to.has_code() { + return Ok(()); + } + + let receiver = IERC1155Receiver::new(to); + let call = Call::new_in(self); + let data = data.to_vec(); + let result = receiver.on_erc_1155_batch_received( + call, operator, from, token_ids, values, data, + ); + + let id = match result { + Ok(id) => id, + Err(e) => { + if let call::Error::Revert(ref reason) = e { + if reason.len() > 0 { + // Non-IERC1155Receiver implementer. + return Err(Error::InvalidReceiverWithReason(e)); + } + } + + return Err(ERC1155InvalidReceiver { receiver: to }.into()); + } + }; + + // Token rejected. + if id != RECEIVER_FN_SELECTOR { + return Err(ERC1155InvalidReceiver { receiver: to }.into()); + } + + Ok(()) + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::{address, uint, Address, U256}; + use alloy_sol_types::token; + use stylus_sdk::{contract, msg}; + + use super::{ERC1155InvalidArrayLength, Erc1155, Error, IErc1155}; + + const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); + const DAVE: Address = address!("0BB78F7e7132d1651B4Fd884B7624394e92156F1"); + const CHARLIE: Address = + address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); + + pub(crate) fn random_token_ids(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() + } + + #[motsu::test] + fn test_balance_of_zero_balance(contract: Erc1155) { + let owner = msg::sender(); + let token_id = random_token_ids(1)[0]; + let balance = contract + .balance_of(owner, token_id) + .expect("should return `U256::ZERO`"); + assert_eq!(U256::ZERO, balance); + } + + #[motsu::test] + fn test_error_array_length_mismatch(contract: Erc1155) { + let token_ids = random_token_ids(3); + let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; + let ids_length = U256::from(token_ids.len()); + let accounts_length = U256::from(accounts.len()); + + let err = contract + .balance_of_batch(accounts, token_ids) + .expect_err("should return `Error::InvalidArrayLength`"); + + assert!(matches!( + err, + Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length, + values_length: accounts_length, + }) + )); + } + + #[motsu::test] + fn test_balance_of_batch_zero_balance(contract: Erc1155) { + let token_ids = random_token_ids(4); + let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; + let balances = contract + .balance_of_batch(accounts, token_ids) + .expect("should return a vector of `U256::ZERO`"); + + for balance in balances { + assert_eq!(U256::ZERO, balance); + } + } } From 5b871cc3e44afb9f1bcd595014966d5be30dd1d1 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Sat, 14 Sep 2024 00:13:26 +0800 Subject: [PATCH 06/60] fix all errors --- Cargo.lock | 2 +- Cargo.toml | 4 +- .../src/token/erc1155/extensions/burnable.rs | 22 +++--- contracts/src/token/erc1155/extensions/mod.rs | 2 + .../token/erc1155/extensions/uri_storage.rs | 14 ++-- contracts/src/token/erc1155/mod.rs | 67 +++++++++++-------- examples/erc1155/Cargo.toml | 2 +- 7 files changed, 62 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afae999ff..ea355e4e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,7 +1514,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erc1155-example" -version = "0.1.0" +version = "0.0.0" dependencies = [ "e2e", "openzeppelin-stylus", diff --git a/Cargo.toml b/Cargo.toml index 0b4000ec4..580983ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,14 @@ members = [ "examples/erc721", "examples/erc721-consecutive", "examples/erc721-metadata", + "examples/erc1155", "examples/merkle-proofs", "examples/ownable", "examples/access-control", "examples/basic/token", "examples/basic/script", "examples/ecdsa", - "benches", "examples/erc1155", + "benches", ] default-members = [ "contracts", @@ -30,6 +31,7 @@ default-members = [ "examples/erc721", "examples/erc721-consecutive", "examples/erc721-metadata", + "examples/erc1155", "examples/merkle-proofs", "examples/ownable", "examples/access-control", diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index 7fb83ff2d..1143b03a2 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -1,10 +1,11 @@ -//! Extension of {ERC1155} that allows token holders to destroy both their -//! own tokens and those that they have been approved to use. +//! Optional Burnable extension of the ERC-1155 standard. use alloc::vec::Vec; use alloy_primitives::{Address, U256}; +use crate::token::erc1155::{Erc1155, Error}; + /// Extension of [`Erc1155`] that allows token holders to destroy both their /// own tokens and those that they have been approved to use. pub trait IErc1155Burnable { @@ -12,14 +13,13 @@ pub trait IErc1155Burnable { /// implementation. type Error: Into>; - /// Extension of {ERC1155} that allows token holders to destroy both their - /// own tokens and those that they have been approved to use. - /// /// The approval is cleared when the token is burned. Relies on the `_burn` /// mechanism. /// /// # Arguments /// + /// * `account` - Account to burn tokens from. + /// * `token_id` - Token id to be burnt. /// * `value` - Amount to be burnt. /// /// # Errors @@ -39,16 +39,14 @@ pub trait IErc1155Burnable { value: U256, ) -> Result<(), Self::Error>; - /// Extension of {ERC1155} that allows token holders to destroy both their - /// own tokens and those that they have been approved to use. - /// - /// The approval is cleared when the token is burned. Relies on the `_burn` - /// mechanism. + /// The approval is cleared when the token is burned. Relies on the + /// `_burn_batch` mechanism. /// /// # Arguments /// + /// * `account` - Accounts to burn tokens from. /// * `values` - All amount to be burnt. - /// * `ids` - All amount to be burnt. + /// * `token_ids` - All token id to be burnt. /// /// # Errors /// @@ -63,7 +61,7 @@ pub trait IErc1155Burnable { fn burn_batch( &mut self, account: Address, - ids: Vec, + token_ids: Vec, values: Vec, ) -> Result<(), Self::Error>; } diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs index 8ccacf3f8..95cdd0662 100644 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -2,3 +2,5 @@ pub mod burnable; pub mod supply; pub mod uri_storage; + +pub use burnable::IErc1155Burnable; diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs index 3b0205a43..9a4f04fa0 100644 --- a/contracts/src/token/erc1155/extensions/uri_storage.rs +++ b/contracts/src/token/erc1155/extensions/uri_storage.rs @@ -1,6 +1,6 @@ //! ERC-1155 token with storage based token URI management. //! -//! Inspired by the {ERC721URIStorage} extension +//! Inspired by the [contracts::token::erc721::extensions::Erc721UriStorage] use alloc::string::String; use alloy_primitives::U256; @@ -10,13 +10,13 @@ use stylus_sdk::evm; sol! { - /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + /// Emitted when the URI for token type `token_id` changes to `value`, if it is a non-programmatic URI. /// - /// If an {URI} event was emitted for `id`, the standard + /// If an [`URI`] event was emitted for `token_id`, the standard /// https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value /// returned by {IERC1155MetadataURI-uri}. #[allow(missing_docs)] - event URI(string value, uint256 indexed id); + event URI(string value, uint256 indexed token_id); } sol_storage! { @@ -30,13 +30,13 @@ sol_storage! { } impl Erc1155UriStorage { - /// Sets `tokenURI` as the tokenURI of `tokenId`. + /// Sets `token_uri` as the `_token_uris` of `token_id`. pub fn _set_uri(&mut self, token_id: U256, token_uri: String) { self._token_uris.setter(token_id).set_str(token_uri); - evm::log(URI { value: self.uri(token_id), id: token_id }); + evm::log(URI { value: self.uri(token_id), token_id }); } - /// Sets `baseURI` as the `_baseURI` for all tokens + /// Sets `base_uri` as the `_base_uri` for all tokens pub fn _set_base_uri(&mut self, base_uri: String) { self._base_uri.set_str(base_uri); } diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index a5045edcf..e081b6360 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -25,7 +25,7 @@ sol! { uint256 value ); - /// Equivalent to multiple {TransferSingle} events, where `operator`. + /// Equivalent to multiple [`TransferSingle`] events, where `operator`. /// `from` and `to` are the same for all transfers. #[allow(missing_docs)] event TransferBatch( @@ -43,7 +43,7 @@ sol! { /// Emitted when the URI for token type `token_id` changes to `value`, if it is a non-programmatic URI. /// - /// If an {URI} event was emitted for `token_id`, the [standard] + /// If an [`URI`] event was emitted for `token_id`, the [standard] /// (https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees]) that `value` will equal the value /// returned by [`IERC1155MetadataURI-uri`]. #[allow(missing_docs)] @@ -171,7 +171,7 @@ sol_interface! { ) external returns (bytes4); /// Handles the receipt of a multiple ERC-1155 token types. This function - /// is called at the end of a `safeBatchTransferFrom` after the balances have + /// is called at the end of a [`Erc1155::safe_batch_transfer_from`] after the balances have /// been updated. /// /// NOTE: To accept the transfer(s), this must return @@ -212,10 +212,10 @@ unsafe impl TopLevelStorage for Erc1155 {} /// Required interface of an [`Erc1155`] compliant contract. pub trait IErc1155 { - /// The error type associated to this ERC-721 trait implementation. + /// The error type associated to this ERC-1155 trait implementation. type Error: Into>; - /// Returns the number of tokens in ``owner``'s account. + /// Returns the value of tokens of token type `token_id` owned by `account`. /// /// # Arguments /// @@ -227,7 +227,10 @@ pub trait IErc1155 { token_id: U256, ) -> Result; - /// xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + /// Refer to: + /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#IERC1155-balanceOfBatch-address---uint256--- + /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) + /// version of [`Erc1155::balance_of`]. /// /// # Arguments /// @@ -238,6 +241,11 @@ pub trait IErc1155 { /// Requirements: /// /// * - `accounts` and `token_ids` must have the same length. + /// + /// # Errors + /// + /// * If the length of `accounts` is not equal to the length of `token_ids`, + /// then the error [`Error::InvalidArrayLength`] is returned. fn balance_of_batch( &self, accounts: Vec
, @@ -273,7 +281,7 @@ pub trait IErc1155 { approved: bool, ) -> Result<(), Self::Error>; - /// Returns true if `operator` is approved to transfer ``account``'s + /// Returns true if `operator` is approved to transfer `account`'s /// tokens. /// /// # Arguments @@ -305,8 +313,6 @@ pub trait IErc1155 { /// [`Error::MissingApprovalForAll`] is returned. /// If the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. - /// If the token does not exist, then the error - /// [`Error::NonexistentToken`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. @@ -315,7 +321,7 @@ pub trait IErc1155 { /// * /// * - `to` cannot be the zero address. /// * - If the caller is not `from`, it must have been approved to spend - /// ``from``'s tokens via [`Self::set_approval_for_all`]. + /// ``from``'s tokens via [`IErc1155::set_approval_for_all`]. /// * - `from` must have a balance of tokens of type `token_id` of at least /// `value` amount. /// * - If `to` refers to a smart contract, it must implement @@ -330,8 +336,10 @@ pub trait IErc1155 { data: Bytes, ) -> Result<(), Self::Error>; - /// xref:ROOT:erc1155.adoc#batch-operations[Batched] version of - /// [`Self::safe_transfer_from`]. + /// Refer to: + /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- + /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) + /// version of [`IErc1155::safe_transfer_from`]. /// /// WARNING: This function can potentially allow a reentrancy attack when /// transferring tokens to an untrusted contract, when invoking @@ -378,17 +386,18 @@ impl IErc1155 for Erc1155 { ) -> Result, Self::Error> { if accounts.len() != token_ids.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: Uint::<256, 4>::from(token_ids.len()), - values_length: Uint::<256, 4>::from(accounts.len()), + ids_length: uint!(token_ids.len()), + values_length: uint!(accounts.len()), })); } - let mut balances = Vec::with_capacity(accounts.len()); - for (i, &account) in accounts.iter().enumerate() { - let token_id = token_ids[i]; - let balance = self._balances.get(token_id).get(account); - balances.push(balance); - } + let mut balances = accounts + .iter() + .zip(token_ids.iter()) + .map(|(&account, &token_id)| { + self._balances.get(token_id).get(account) + }) + .collect(); Ok(balances) } @@ -439,7 +448,7 @@ impl Erc1155 { /// acceptance magic value. /// * - `token_ids` and `values` must have the same length. /// - /// /// # Errors + /// # Errors /// /// If length of `token_ids` is not equal to length of `values`, then the /// error [`Error::InvalidArrayLength`] is returned. @@ -462,14 +471,13 @@ impl Erc1155 { ) -> Result<(), Error> { if token_ids.len() != values.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: Uint::<256, 4>::from(token_ids.len()), - values_length: Uint::<256, 4>::from(values.len()), + ids_length: uint!(token_ids.len()), + values_length: uint!(values.len()), })); } let operator = msg::sender(); - for (i, &token_id) in token_ids.iter().enumerate() { - let value = values[i]; + token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { let from_balance = self._balances.get(token_id).get(from); if from_balance < value { return Err(Error::InsufficientBalance( @@ -486,13 +494,14 @@ impl Erc1155 { .setter(from) .sub_assign_unchecked(value); - if to != Address::ZERO { + if !to.is_zero() { self._balances .setter(token_id) .setter(to) - .add_assign_unchecked(value); + .checked_add(value) + .expect("should not exceed `U256::MAX` for `_balances`"); } - } + }); if token_ids.len() == 1 { evm::log(TransferSingle { @@ -536,7 +545,7 @@ impl Erc1155 { data: Bytes, ) -> Result<(), Error> { self._update(from, to, token_ids.clone(), values.clone())?; - if to != Address::ZERO { + if !to.is_zero() { let operator = msg::sender(); if token_ids.len() == 1 { let token_id = token_ids[0]; diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml index e192c7cf3..38945130a 100644 --- a/examples/erc1155/Cargo.toml +++ b/examples/erc1155/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "erc1155-example" -version = "0.1.0" +version = "0.0.0" authors.workspace = true edition.workspace = true license.workspace = true From b65f68b015d1ae18ec5aef23db1e714db5e18a2b Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Mon, 16 Sep 2024 19:20:47 +0800 Subject: [PATCH 07/60] add approval func and related tests --- contracts/src/token/erc1155/mod.rs | 104 ++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index e081b6360..d5a06302d 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -10,7 +10,7 @@ use stylus_sdk::{ prelude::*, }; -use crate::utils::math::storage::{AddAssignUnchecked, SubAssignUnchecked}; +use crate::utils::math::storage::SubAssignUnchecked; pub mod extensions; @@ -367,7 +367,7 @@ pub trait IErc1155 { ) -> Result<(), Self::Error>; } -#[external] +#[public] impl IErc1155 for Erc1155 { type Error = Error; @@ -386,12 +386,12 @@ impl IErc1155 for Erc1155 { ) -> Result, Self::Error> { if accounts.len() != token_ids.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: uint!(token_ids.len()), - values_length: uint!(accounts.len()), + ids_length: U256::from(token_ids.len()), + values_length: U256::from(accounts.len()), })); } - let mut balances = accounts + let balances: Vec> = accounts .iter() .zip(token_ids.iter()) .map(|(&account, &token_id)| { @@ -406,6 +406,7 @@ impl IErc1155 for Erc1155 { operator: Address, approved: bool, ) -> Result<(), Self::Error> { + self._set_approval_for_all(msg::sender(), operator, approved)?; Ok(()) } @@ -471,13 +472,13 @@ impl Erc1155 { ) -> Result<(), Error> { if token_ids.len() != values.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: uint!(token_ids.len()), - values_length: uint!(values.len()), + ids_length: U256::from(token_ids.len()), + values_length: U256::from(values.len()), })); } let operator = msg::sender(); - token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { let from_balance = self._balances.get(token_id).get(from); if from_balance < value { return Err(Error::InsufficientBalance( @@ -501,7 +502,7 @@ impl Erc1155 { .checked_add(value) .expect("should not exceed `U256::MAX` for `_balances`"); } - }); + } if token_ids.len() == 1 { evm::log(TransferSingle { @@ -562,6 +563,34 @@ impl Erc1155 { Ok(()) } + /// Approve `operator` to operate on all of `owner` tokens + /// + /// Emits an [`ApprovalForAll`] event. + /// + /// Requirements: + /// + /// - `operator` cannot be the zero address. + /// + /// # Errors + /// + /// If `operator` is the zero address, then the error + /// [`Error::InvalidOperator`] is returned. + fn _set_approval_for_all( + &mut self, + owner: Address, + operator: Address, + approved: bool, + ) -> Result<(), Error> { + if operator.is_zero() { + return Err(Error::InvalidOperator(ERC1155InvalidOperator { + operator, + })); + } + self._operator_approvals.setter(owner).setter(operator).set(approved); + evm::log(ApprovalForAll { account: owner, operator, approved }); + Ok(()) + } + /// Performs an acceptance check for the provided `operator` by /// calling [`IERC1155Receiver::on_erc_1155_received`] on the `to` address. /// The `operator` is generally the address that initiated the token @@ -605,9 +634,14 @@ impl Erc1155 { let receiver = IERC1155Receiver::new(to); let call = Call::new_in(self); - let data = data.to_vec(); - let result = receiver - .on_erc_1155_received(call, operator, from, token_id, value, data); + let result = receiver.on_erc_1155_received( + call, + operator, + from, + token_id, + value, + data.to_vec().into(), + ); let id = match result { Ok(id) => id, @@ -674,9 +708,13 @@ impl Erc1155 { let receiver = IERC1155Receiver::new(to); let call = Call::new_in(self); - let data = data.to_vec(); let result = receiver.on_erc_1155_batch_received( - call, operator, from, token_ids, values, data, + call, + operator, + from, + token_ids, + values, + data.to_vec().into(), ); let id = match result { @@ -705,10 +743,12 @@ impl Erc1155 { #[cfg(all(test, feature = "std"))] mod tests { use alloy_primitives::{address, uint, Address, U256}; - use alloy_sol_types::token; use stylus_sdk::{contract, msg}; - use super::{ERC1155InvalidArrayLength, Erc1155, Error, IErc1155}; + use super::{ + ERC1155InvalidArrayLength, ERC1155InvalidOperator, Erc1155, Error, + IErc1155, + }; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); @@ -762,4 +802,36 @@ mod tests { assert_eq!(U256::ZERO, balance); } } + + #[motsu::test] + fn test_set_approval_for_all(contract: Erc1155) { + let alice = msg::sender(); + contract._operator_approvals.setter(alice).setter(BOB).set(false); + + contract + .set_approval_for_all(BOB, true) + .expect("should approve Bob for operations on all Alice's tokens"); + assert_eq!(contract.is_approved_for_all(alice, BOB), true); + + contract.set_approval_for_all(BOB, false).expect( + "should disapprove Bob for operations on all Alice's tokens", + ); + assert_eq!(contract.is_approved_for_all(alice, BOB), false); + } + + #[motsu::test] + fn test_error_invalid_operator_when_approval_for_all(contract: Erc1155) { + let invalid_operator = Address::ZERO; + + let err = contract + .set_approval_for_all(invalid_operator, true) + .expect_err("should not approve for all for invalid operator"); + + assert!(matches!( + err, + Error::InvalidOperator(ERC1155InvalidOperator { + operator + }) if operator == invalid_operator + )); + } } From 654b51bce4c62b6645d539580a1472052273214c Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Fri, 20 Sep 2024 11:48:03 +0800 Subject: [PATCH 08/60] develop supply extension --- .../src/token/erc1155/extensions/supply.rs | 187 ++++++++++++ .../token/erc1155/extensions/uri_storage.rs | 4 +- contracts/src/token/erc1155/mod.rs | 274 +++++++++++++++++- 3 files changed, 448 insertions(+), 17 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 3f7a68e29..ea6aad9a4 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -10,3 +10,190 @@ //! //! CAUTION: This extension should not be added in an upgrade to an already //! deployed contract. + +use alloy_primitives::{uint, Address, U256}; +use stylus_proc::sol_storage; +use stylus_sdk::prelude::*; + +use crate::{ + token::erc1155::{Erc1155, Error}, + utils::math::storage::SubAssignUnchecked, +}; + +sol_storage! { + /// State of [`crate::token::erc1155::Erc1155`] token's supply. + pub struct Erc1155Supply { + /// Erc1155 contract storage. + Erc1155 erc1155; + /// Mapping from token ID to total supply. + mapping(uint256 => uint256) _total_supply; + /// Total supply of all token IDs. + uint256 _total_supply_all; + } +} + +#[public] +impl Erc1155Supply { + fn total_supply(&self, token_id: U256) -> U256 { + self._total_supply.get(token_id) + } + + fn total_supply_all(&self) -> U256 { + *self._total_supply_all + } + + fn exists(&self, token_id: U256) -> bool { + self.total_supply(token_id) > uint!(0_U256) + } +} + +impl Erc1155Supply { + /// Override of [`Erc1155::_update`] that restricts normal minting to after + /// construction. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_ids` - Array of all token id. + /// * `values` - Array of all amount of tokens to be supplied. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. + fn _update( + &mut self, + from: Address, + to: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), Error> { + self.erc1155._update(from, to, token_ids.clone(), values.clone())?; + + if from.is_zero() { + let mut total_mint_value = uint!(0_U256); + token_ids.iter().zip(values.iter()).for_each( + |(&token_id, &value)| { + let total_supply = + self.total_supply(token_id).checked_add(value).expect( + "should not exceed `U256::MAX` for `_total_supply`", + ); + self._total_supply.setter(token_id).set(total_supply); + total_mint_value += value; + }, + ); + let total_supply_all = + self.total_supply_all().checked_add(total_mint_value).expect( + "should not exceed `U256::MAX` for `_total_supply_all`", + ); + self._total_supply_all.set(total_supply_all); + } + + if to.is_zero() { + let mut total_burn_value = uint!(0_U256); + token_ids.iter().zip(values.iter()).for_each( + |(&token_id, &value)| { + self._total_supply + .setter(token_id) + .sub_assign_unchecked(value); + total_burn_value += value; + }, + ); + let total_supply_all = + self._total_supply_all.get() - total_burn_value; + self._total_supply_all.set(total_supply_all); + } + Ok(()) + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::{address, Address, U256}; + + use super::Erc1155Supply; + use crate::token::erc1155::IErc1155; + + const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); + + pub(crate) fn random_token_ids(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() + } + + pub(crate) fn random_values(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() + } + + fn init( + contract: &mut Erc1155Supply, + reciever: Address, + size: usize, + ) -> (Vec, Vec) { + let token_ids = random_token_ids(size); + let values = random_values(size); + + contract + ._update(Address::ZERO, reciever, token_ids.clone(), values.clone()) + .expect("Supply failed"); + (token_ids, values) + } + + #[motsu::test] + fn test_supply_of_zero_supply(contract: Erc1155Supply) { + let token_ids = random_token_ids(1); + assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); + assert_eq!(U256::ZERO, contract.total_supply_all()); + assert!(!contract.exists(token_ids[0])); + } + + #[motsu::test] + fn test_supply_with_zero_address_sender(contract: Erc1155Supply) { + let token_ids = random_token_ids(1); + let values = random_values(1); + contract + ._update(Address::ZERO, ALICE, token_ids.clone(), values.clone()) + .expect("Supply failed"); + assert_eq!(values[0], contract.total_supply(token_ids[0])); + assert_eq!(values[0], contract.total_supply_all()); + assert!(contract.exists(token_ids[0])); + } + + #[motsu::test] + fn test_supply_with_zero_address_receiver(contract: Erc1155Supply) { + let (token_ids, values) = init(contract, ALICE, 1); + contract + ._update(ALICE, Address::ZERO, token_ids.clone(), values.clone()) + .expect("Supply failed"); + assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); + assert_eq!(U256::ZERO, contract.total_supply_all()); + assert!(!contract.exists(token_ids[0])); + } + + #[motsu::test] + fn test_supply_batch(contract: Erc1155Supply) { + let (token_ids, values) = init(contract, BOB, 4); + assert_eq!( + values[0], + contract.erc1155.balance_of(BOB, token_ids[0]).unwrap() + ); + assert_eq!( + values[1], + contract.erc1155.balance_of(BOB, token_ids[1]).unwrap() + ); + assert_eq!( + values[2], + contract.erc1155.balance_of(BOB, token_ids[2]).unwrap() + ); + assert_eq!( + values[3], + contract.erc1155.balance_of(BOB, token_ids[3]).unwrap() + ); + assert!(contract.exists(token_ids[0])); + assert!(contract.exists(token_ids[1])); + assert!(contract.exists(token_ids[2])); + assert!(contract.exists(token_ids[3])); + } +} diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs index 9a4f04fa0..f30a183de 100644 --- a/contracts/src/token/erc1155/extensions/uri_storage.rs +++ b/contracts/src/token/erc1155/extensions/uri_storage.rs @@ -5,7 +5,7 @@ use alloc::string::String; use alloy_primitives::U256; use alloy_sol_types::sol; -use stylus_proc::{external, sol_storage}; +use stylus_proc::{public, sol_storage}; use stylus_sdk::evm; sol! { @@ -42,7 +42,7 @@ impl Erc1155UriStorage { } } -#[external] +#[public] impl Erc1155UriStorage { /// Returns the Uniform Resource Identifier (URI) for `token_id` token. /// diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index d5a06302d..ba8c35c0f 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -422,6 +422,13 @@ impl IErc1155 for Erc1155 { value: U256, data: Bytes, ) -> Result<(), Self::Error> { + let sender = msg::sender(); + if from != sender && !self.is_approved_for_all(from, sender) { + return Err(Error::MissingApprovalForAll( + ERC1155MissingApprovalForAll { operator: sender, owner: from }, + )); + } + self._safe_transfer_from(from, to, token_id, value, data)?; Ok(()) } @@ -433,6 +440,13 @@ impl IErc1155 for Erc1155 { values: Vec, data: Bytes, ) -> Result<(), Self::Error> { + let sender = msg::sender(); + if from != sender && !self.is_approved_for_all(from, sender) { + return Err(Error::MissingApprovalForAll( + ERC1155MissingApprovalForAll { operator: sender, owner: from }, + )); + } + self._safe_batch_transfer_from(from, to, token_ids, values, data)?; Ok(()) } } @@ -479,28 +493,33 @@ impl Erc1155 { let operator = msg::sender(); for (&token_id, &value) in token_ids.iter().zip(values.iter()) { - let from_balance = self._balances.get(token_id).get(from); - if from_balance < value { - return Err(Error::InsufficientBalance( - ERC1155InsufficientBalance { - sender: from, - balance: from_balance, - needed: value, - token_id, - }, - )); + if !from.is_zero() { + // let from_balance = self._balances.get(token_id).get(from); + let from_balance = self.balance_of(from, token_id)?; + if from_balance < value { + return Err(Error::InsufficientBalance( + ERC1155InsufficientBalance { + sender: from, + balance: from_balance, + needed: value, + token_id, + }, + )); + } + self._balances + .setter(token_id) + .setter(from) + .sub_assign_unchecked(value); } - self._balances - .setter(token_id) - .setter(from) - .sub_assign_unchecked(value); if !to.is_zero() { - self._balances + let new_value = self + ._balances .setter(token_id) .setter(to) .checked_add(value) .expect("should not exceed `U256::MAX` for `_balances`"); + self._balances.setter(token_id).setter(to).set(new_value); } } @@ -563,6 +582,189 @@ impl Erc1155 { Ok(()) } + /// Transfers a `value` tokens of token type `token_id` from `from` to `to`. + /// + /// Requirements: + /// + /// - `to` cannot be the zero address. + /// - `from` must have a balance of tokens of type `id` of at least `value` + /// amount. + /// - If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_received`] and return the + /// acceptance magic value. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `value` - Amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If `to` is the zero address, then the error [`Error::InvalidReceiver`] + /// is returned. + /// If `from` is the zero address, then the error + /// [`Error::InvalidSender`] is returned. + /// + /// Event + /// + /// Emits a [`TransferSingle`] event. + fn _safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Error> { + if to.is_zero() { + return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver: to, + })); + } + if from.is_zero() { + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); + } + self._update_with_acceptance_check( + from, + to, + vec![token_id], + vec![value], + data, + ) + } + + /// Refer to: + /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- + /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) + /// version of [`Self::_safe_transfer_from`]. + /// + /// Requirements: + /// + /// - If `to` refers to a smart contract, it must implement + /// {IERC1155Receiver-onERC1155BatchReceived} and return the + /// acceptance magic value. + /// - `ids` and `values` must have the same length. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_ids` - Array of all token id. + /// * `values` - Array of all amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If `to` is the zero address, then the error [`Error::InvalidReceiver`] + /// is returned. + /// If `from` is the zero address, then the error + /// [`Error::InvalidSender`] is returned. + /// + /// Event + /// + /// Emits a [`TransferBatch`] event. + fn _safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + token_ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Error> { + if to.is_zero() { + return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver: to, + })); + } + if from.is_zero() { + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); + } + self._update_with_acceptance_check(from, to, token_ids, values, data) + } + + /// Creates a `value` amount of tokens of type `token_id`, and assigns + /// them to `to`. + /// + /// Requirements: + /// + /// - `to` cannot be the zero address. + /// - If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_received`] and return the + /// acceptance magic value. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event. + fn _mint( + &mut self, + to: Address, + token_id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Error> { + if to.is_zero() { + return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver: to, + })); + } + self._update_with_acceptance_check( + Address::ZERO, + to, + vec![token_id], + vec![value], + data, + )?; + Ok(()) + } + + /// Refer to: + /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155-_mintBatch-address-uint256---uint256---bytes- + /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) + /// version of [`Self::_mint`]. + /// + /// Requirements: + /// + /// - `to` cannot be the zero address. + /// - If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_received`] and return the + /// acceptance magic value. + /// + /// # Events + /// + /// Emits a [`TransferBatch`] event. + fn _mint_batch( + &mut self, + to: Address, + token_ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Error> { + if to.is_zero() { + return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver: to, + })); + } + self._update_with_acceptance_check( + Address::ZERO, + to, + token_ids, + values, + data, + )?; + Ok(()) + } + /// Approve `operator` to operate on all of `owner` tokens /// /// Emits an [`ApprovalForAll`] event. @@ -743,6 +945,7 @@ impl Erc1155 { #[cfg(all(test, feature = "std"))] mod tests { use alloy_primitives::{address, uint, Address, U256}; + use alloy_sol_types::abi::token; use stylus_sdk::{contract, msg}; use super::{ @@ -760,6 +963,10 @@ mod tests { (0..size).map(|_| U256::from(rand::random::())).collect() } + pub(crate) fn random_values(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() + } + #[motsu::test] fn test_balance_of_zero_balance(contract: Erc1155) { let owner = msg::sender(); @@ -834,4 +1041,41 @@ mod tests { }) if operator == invalid_operator )); } + + #[motsu::test] + fn test_mints(contract: Erc1155) { + let alice = msg::sender(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let initial_balance = contract + .balance_of(alice, token_id) + .expect("should return the balance of Alice"); + + contract + ._mint(alice, token_id, value, vec![0, 1, 2, 3].into()) + .expect("should mint tokens for Alice"); + + let balance = contract + .balance_of(alice, token_id) + .expect("should return the balance of Alice"); + + assert_eq!(balance, initial_balance + value); + } + + #[motsu::test] + fn test_safe_transfer_from(contract: Erc1155) { + // let alice = msg::sender(); + // let token_id = U256::from(1); + // let value = U256::from(10); + + // contract + // .safe_transfer_from(alice, BOB, token_id, value, vec![]) + // .expect("should transfer tokens from Alice to Bob"); + + // let balance = contract + // .balance_of(BOB, token_id) + // .expect("should return Bob's balance of the token"); + // assert_eq!(value, balance); + } } From 1b2d10e113eb8250af172bf3fd5f9b793afa8dfc Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Sat, 21 Sep 2024 13:26:19 +0800 Subject: [PATCH 09/60] finish burnable and mod --- .../src/token/erc1155/extensions/burnable.rs | 244 +++++- contracts/src/token/erc1155/mod.rs | 764 +++++++++++++++++- 2 files changed, 983 insertions(+), 25 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index 1143b03a2..7ba9825b9 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -3,8 +3,11 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; +use stylus_sdk::msg; -use crate::token::erc1155::{Erc1155, Error}; +use crate::token::erc1155::{ + ERC1155MissingApprovalForAll, Erc1155, Error, IErc1155, +}; /// Extension of [`Erc1155`] that allows token holders to destroy both their /// own tokens and those that they have been approved to use. @@ -65,3 +68,242 @@ pub trait IErc1155Burnable { values: Vec, ) -> Result<(), Self::Error>; } + +impl IErc1155Burnable for Erc1155 { + type Error = Error; + + fn burn( + &mut self, + account: Address, + token_id: U256, + value: U256, + ) -> Result<(), Self::Error> { + let sender = msg::sender(); + if account != sender && !self.is_approved_for_all(account, sender) { + return Err(Error::MissingApprovalForAll( + ERC1155MissingApprovalForAll { + owner: account, + operator: sender, + }, + )); + } + self._burn(account, token_id, value)?; + Ok(()) + } + + fn burn_batch( + &mut self, + account: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), Self::Error> { + let sender = msg::sender(); + if account != sender && !self.is_approved_for_all(account, sender) { + return Err(Error::MissingApprovalForAll( + ERC1155MissingApprovalForAll { + owner: account, + operator: sender, + }, + )); + } + self._burn_batch(account, token_ids, values)?; + Ok(()) + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::{address, Address, U256}; + use stylus_sdk::msg; + + use super::IErc1155Burnable; + use crate::token::erc1155::{ + ERC1155InvalidSender, ERC1155MissingApprovalForAll, Erc1155, Error, + IErc1155, + }; + + const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); + + pub(crate) fn random_token_ids(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() + } + + pub(crate) fn random_values(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() + } + + fn init( + contract: &mut Erc1155, + reciever: Address, + size: usize, + ) -> (Vec, Vec) { + let token_ids = random_token_ids(size); + let values = random_values(size); + + contract + ._mint_batch( + reciever, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("Mint failed"); + (token_ids, values) + } + + #[motsu::test] + fn test_burns(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 1); + + let initial_balance = contract + .balance_of(BOB, token_ids[0]) + .expect("should return the BOB's balance of token 0"); + + assert_eq!(values[0], initial_balance); + + contract._operator_approvals.setter(BOB).setter(alice).set(true); + + contract + .burn(BOB, token_ids[0], values[0]) + .expect("should burn alice's token"); + + let balance = contract + .balance_of(BOB, token_ids[0]) + .expect("should return the BOB's balance of token 0"); + + assert_eq!(U256::ZERO, balance); + } + + #[motsu::test] + fn test_error_missing_approval_when_burn(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 1); + + let err = contract + .burn(BOB, token_ids[0], values[0]) + .expect_err("should not burn tokens without approval"); + + assert!(matches!( + err, + Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { + owner, + operator + }) if owner == BOB && operator == alice + )); + } + + #[motsu::test] + fn test_error_invalid_sender_when_burn(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 1); + let invalid_sender = Address::ZERO; + + contract + ._operator_approvals + .setter(invalid_sender) + .setter(alice) + .set(true); + + let err = contract + .burn(invalid_sender, token_ids[0], values[0]) + .expect_err("should not burn tokens from the zero address"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender, + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn test_burns_batch(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 4); + + let initial_balance_0 = contract + .balance_of(BOB, token_ids[0]) + .expect("should return the BOB's balance of token 0"); + let initial_balance_1 = contract + .balance_of(BOB, token_ids[1]) + .expect("should return the BOB's balance of token 1"); + let initial_balance_2 = contract + .balance_of(BOB, token_ids[2]) + .expect("should return the BOB's balance of token 2"); + let initial_balance_3 = contract + .balance_of(BOB, token_ids[3]) + .expect("should return the BOB's balance of token 3"); + + assert_eq!(values[0], initial_balance_0); + assert_eq!(values[1], initial_balance_1); + assert_eq!(values[2], initial_balance_2); + assert_eq!(values[3], initial_balance_3); + + contract._operator_approvals.setter(BOB).setter(alice).set(true); + + contract + .burn_batch(BOB, token_ids.clone(), values.clone()) + .expect("should burn alice's tokens"); + + let balance_0 = contract + .balance_of(BOB, token_ids[0]) + .expect("should return the BOB's balance of token 0"); + let balance_1 = contract + .balance_of(BOB, token_ids[1]) + .expect("should return the BOB's balance of token 1"); + let balance_2 = contract + .balance_of(BOB, token_ids[2]) + .expect("should return the BOB's balance of token 2"); + let balance_3 = contract + .balance_of(BOB, token_ids[3]) + .expect("should return the BOB's balance of token 3"); + + assert_eq!(U256::ZERO, balance_0); + assert_eq!(U256::ZERO, balance_1); + assert_eq!(U256::ZERO, balance_2); + assert_eq!(U256::ZERO, balance_3); + } + + #[motsu::test] + fn test_error_missing_approval_when_burn_batch(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 2); + + let err = contract + .burn_batch(BOB, token_ids.clone(), values.clone()) + .expect_err("should not burn tokens without approval"); + + assert!(matches!( + err, + Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { + owner, + operator + }) if owner == BOB && operator == alice + )); + } + + #[motsu::test] + fn test_error_invalid_sender_when_burn_batch(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 5); + let invalid_sender = Address::ZERO; + + contract + ._operator_approvals + .setter(invalid_sender) + .setter(alice) + .set(true); + + let err = contract + .burn_batch(invalid_sender, token_ids.clone(), values.clone()) + .expect_err("should not burn tokens from the zero address"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender, + }) if sender == invalid_sender + )); + } +} diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index ba8c35c0f..4e01cf7da 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -765,6 +765,82 @@ impl Erc1155 { Ok(()) } + /// Destroys a `value` amount of tokens of type `id` from `from` + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event. + /// + /// # Errors + /// + /// If `from` is the zero address, then the error [`Error::InvalidSender`] + /// is returned. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `from` must have at least `value` amount of tokens of type `id`. + fn _burn( + &mut self, + from: Address, + token_id: U256, + value: U256, + ) -> Result<(), Error> { + if from.is_zero() { + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); + } + self._update_with_acceptance_check( + from, + Address::ZERO, + vec![token_id], + vec![value], + vec![].into(), + )?; + Ok(()) + } + + /// Refer to: + /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155-_burnBatch-address-uint256---uint256--- + /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) + /// [`Self::_burn`]. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event. + /// + /// # Errors + /// + /// If `from` is the zero address, then the error [`Error::InvalidSender`] + /// is returned. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `from` must have at least `value` amount of tokens of type `token_id`. + /// - `token_ids` and `values` must have the same length. + fn _burn_batch( + &mut self, + from: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), Error> { + if from.is_zero() { + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); + } + self._update_with_acceptance_check( + from, + Address::ZERO, + token_ids, + values, + vec![].into(), + )?; + Ok(()) + } + /// Approve `operator` to operate on all of `owner` tokens /// /// Emits an [`ApprovalForAll`] event. @@ -945,12 +1021,12 @@ impl Erc1155 { #[cfg(all(test, feature = "std"))] mod tests { use alloy_primitives::{address, uint, Address, U256}; - use alloy_sol_types::abi::token; - use stylus_sdk::{contract, msg}; + use stylus_sdk::msg; use super::{ - ERC1155InvalidArrayLength, ERC1155InvalidOperator, Erc1155, Error, - IErc1155, + ERC1155InsufficientBalance, ERC1155InvalidArrayLength, + ERC1155InvalidOperator, ERC1155InvalidReceiver, ERC1155InvalidSender, + ERC1155MissingApprovalForAll, Erc1155, Error, IErc1155, }; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); @@ -967,6 +1043,25 @@ mod tests { (0..size).map(|_| U256::from(rand::random::())).collect() } + fn init( + contract: &mut Erc1155, + reciever: Address, + size: usize, + ) -> (Vec, Vec) { + let token_ids = random_token_ids(size); + let values = random_values(size); + + contract + ._mint_batch( + reciever, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("Mint failed"); + (token_ids, values) + } + #[motsu::test] fn test_balance_of_zero_balance(contract: Erc1155) { let owner = msg::sender(); @@ -991,9 +1086,9 @@ mod tests { assert!(matches!( err, Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length, - values_length: accounts_length, - }) + ids_length: ids_l, + values_length: accounts_l, + }) if ids_l == ids_length && accounts_l == accounts_length )); } @@ -1048,10 +1143,6 @@ mod tests { let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; - let initial_balance = contract - .balance_of(alice, token_id) - .expect("should return the balance of Alice"); - contract ._mint(alice, token_id, value, vec![0, 1, 2, 3].into()) .expect("should mint tokens for Alice"); @@ -1060,22 +1151,647 @@ mod tests { .balance_of(alice, token_id) .expect("should return the balance of Alice"); - assert_eq!(balance, initial_balance + value); + assert_eq!(balance, value); + } + + #[motsu::test] + fn test_mints_batch(contract: Erc1155) { + let token_ids = random_token_ids(4); + let values = random_values(4); + let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; + + contract + ._mint_batch( + ALICE, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("should mint tokens for Alice"); + token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { + let balance = contract + .balance_of(ALICE, token_id) + .expect("should return the balance of Alice"); + assert_eq!(balance, value); + }); + + contract + ._mint_batch( + BOB, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("should mint tokens for BOB"); + token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { + let balance = contract + .balance_of(BOB, token_id) + .expect("should return the balance of BOB"); + assert_eq!(balance, value); + }); + + contract + ._mint_batch( + DAVE, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("should mint tokens for DAVE"); + token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { + let balance = contract + .balance_of(DAVE, token_id) + .expect("should return the balance of DAVE"); + assert_eq!(balance, value); + }); + + contract + ._mint_batch( + CHARLIE, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("should mint tokens for CHARLIE"); + token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { + let balance = contract + .balance_of(CHARLIE, token_id) + .expect("should return the balance of CHARLIE"); + assert_eq!(balance, value); + }); + + let balances = contract + .balance_of_batch(accounts.clone(), token_ids.clone()) + .expect("should return the balances of all accounts"); + + balances.iter().zip(values.iter()).for_each(|(&balance, &value)| { + assert_eq!(balance, value); + }); } #[motsu::test] fn test_safe_transfer_from(contract: Erc1155) { - // let alice = msg::sender(); - // let token_id = U256::from(1); - // let value = U256::from(10); - - // contract - // .safe_transfer_from(alice, BOB, token_id, value, vec![]) - // .expect("should transfer tokens from Alice to Bob"); - - // let balance = contract - // .balance_of(BOB, token_id) - // .expect("should return Bob's balance of the token"); - // assert_eq!(value, balance); + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 2); + let amount_one = values[0] - uint!(1_U256); + let amount_two = values[1] - uint!(1_U256); + + contract._operator_approvals.setter(BOB).setter(alice).set(true); + + contract + .safe_transfer_from( + BOB, + DAVE, + token_ids[0], + amount_one, + vec![].into(), + ) + .expect("should transfer tokens from Alice to Bob"); + contract + .safe_transfer_from( + BOB, + DAVE, + token_ids[1], + amount_two, + vec![].into(), + ) + .expect("should transfer tokens from Alice to Bob"); + + let balance_id_one = contract + .balance_of(DAVE, token_ids[0]) + .expect("should return Bob's balance of the token 0"); + let balance_id_two = contract + .balance_of(DAVE, token_ids[1]) + .expect("should return Bob's balance of the token 1"); + + assert_eq!(amount_one, balance_id_one); + assert_eq!(amount_two, balance_id_two); + } + + #[motsu::test] + fn test_error_invalid_receiver_when_safe_transfer_from(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 1); + let invalid_receiver = Address::ZERO; + + let err = contract + .safe_transfer_from( + alice, + invalid_receiver, + token_ids[0], + values[0], + vec![].into(), + ) + .expect_err("should not transfer tokens to the zero address"); + + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } + + #[motsu::test] + fn test_error_invalid_sender_when_safe_transfer_from(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 1); + let invalid_sender = Address::ZERO; + + contract + ._operator_approvals + .setter(invalid_sender) + .setter(alice) + .set(true); + + let err = contract + .safe_transfer_from( + invalid_sender, + BOB, + token_ids[0], + values[0], + vec![].into(), + ) + .expect_err("should not transfer tokens from the zero address"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn test_error_missing_approval_when_safe_transfer_from(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 1); + + let err = contract + .safe_transfer_from( + ALICE, + BOB, + token_ids[0], + values[0], + vec![].into(), + ) + .expect_err("should not transfer tokens without approval"); + + assert!(matches!( + err, + Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { + operator, + owner + }) if operator == msg::sender() && owner == ALICE + )); + } + + #[motsu::test] + fn test_error_insufficient_balance_when_safe_transfer_from( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 1); + + contract._operator_approvals.setter(BOB).setter(alice).set(true); + + let err = contract + .safe_transfer_from( + BOB, + DAVE, + token_ids[0], + values[0] + uint!(1_U256), + vec![].into(), + ) + .expect_err("should not transfer tokens with insufficient balance"); + + assert!(matches!( + err, + Error::InsufficientBalance(ERC1155InsufficientBalance { + sender, + balance, + needed, + token_id + }) if sender == BOB && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] + )); + } + + #[motsu::test] + fn test_safe_transfer_from_with_data(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, DAVE, 1); + + contract._operator_approvals.setter(DAVE).setter(alice).set(true); + + contract + .safe_transfer_from( + DAVE, + CHARLIE, + token_ids[0], + values[0], + vec![0, 1, 2, 3].into(), + ) + .expect("should transfer tokens from Alice to Bob"); + + let balance = contract + .balance_of(CHARLIE, token_ids[0]) + .expect("should return Bob's balance of the token 0"); + + assert_eq!(values[0], balance); + } + + #[motsu::test] + fn test_error_invalid_receiver_when_safe_transfer_from_with_data( + contract: Erc1155, + ) { + let (token_ids, values) = init(contract, DAVE, 1); + let invalid_receiver = Address::ZERO; + + let err = contract + ._safe_transfer_from( + DAVE, + invalid_receiver, + token_ids[0], + values[0], + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens to the zero address"); + + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } + + #[motsu::test] + fn test_error_invalid_sender_when_safe_transfer_from_with_data( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 1); + let invalid_sender = Address::ZERO; + + contract + ._operator_approvals + .setter(invalid_sender) + .setter(alice) + .set(true); + + let err = contract + .safe_transfer_from( + invalid_sender, + CHARLIE, + token_ids[0], + values[0], + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens from the zero address"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn test_error_missing_approval_when_safe_transfer_from_with_data( + contract: Erc1155, + ) { + let (token_ids, values) = init(contract, ALICE, 1); + + let err = contract + .safe_transfer_from( + ALICE, + BOB, + token_ids[0], + values[0], + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens without approval"); + + assert!(matches!( + err, + Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { + operator, + owner + }) if operator == msg::sender() && owner == ALICE + )); + } + + #[motsu::test] + fn test_error_insufficient_balance_when_safe_transfer_from_with_data( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, BOB, 1); + + contract._operator_approvals.setter(BOB).setter(alice).set(true); + + let err = contract + .safe_transfer_from( + BOB, + DAVE, + token_ids[0], + values[0] + uint!(1_U256), + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens with insufficient balance"); + + assert!(matches!( + err, + Error::InsufficientBalance(ERC1155InsufficientBalance { + sender, + balance, + needed, + token_id + }) if sender == BOB && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] + )); + } + + #[motsu::test] + fn test_safe_batch_transfer_from(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, DAVE, 2); + let amount_one = values[0] - uint!(1_U256); + let amount_two = values[1] - uint!(1_U256); + + contract._operator_approvals.setter(DAVE).setter(alice).set(true); + + contract + .safe_batch_transfer_from( + DAVE, + BOB, + token_ids.clone(), + vec![amount_one, amount_two], + vec![].into(), + ) + .expect("should transfer tokens from Alice to Bob"); + + let balance_id_one = contract + .balance_of(BOB, token_ids[0]) + .expect("should return Bob's balance of the token 0"); + let balance_id_two = contract + .balance_of(BOB, token_ids[1]) + .expect("should return Bob's balance of the token 1"); + + assert_eq!(amount_one, balance_id_one); + assert_eq!(amount_two, balance_id_two); + } + + #[motsu::test] + fn test_error_invalid_receiver_when_safe_batch_transfer_from( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 4); + let invalid_receiver = Address::ZERO; + + let err = contract + .safe_batch_transfer_from( + alice, + invalid_receiver, + token_ids.clone(), + values.clone(), + vec![].into(), + ) + .expect_err("should not transfer tokens to the zero address"); + + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } + + #[motsu::test] + fn test_error_invalid_sender_when_safe_batch_transfer_from( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 4); + let invalid_sender = Address::ZERO; + + contract + ._operator_approvals + .setter(invalid_sender) + .setter(alice) + .set(true); + + let err = contract + .safe_batch_transfer_from( + invalid_sender, + CHARLIE, + token_ids.clone(), + values.clone(), + vec![].into(), + ) + .expect_err("should not transfer tokens from the zero address"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn test_error_missing_approval_when_safe_batch_transfer_from( + contract: Erc1155, + ) { + let (token_ids, values) = init(contract, ALICE, 2); + + let err = contract + .safe_batch_transfer_from( + ALICE, + BOB, + token_ids.clone(), + values.clone(), + vec![].into(), + ) + .expect_err("should not transfer tokens without approval"); + + assert!(matches!( + err, + Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { + operator, + owner + }) if operator == msg::sender() && owner == ALICE + )); + } + + #[motsu::test] + fn test_error_insufficient_balance_when_safe_batch_transfer_from( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, CHARLIE, 2); + + contract._operator_approvals.setter(CHARLIE).setter(alice).set(true); + + let err = contract + .safe_batch_transfer_from( + CHARLIE, + BOB, + token_ids.clone(), + vec![values[0] + uint!(1_U256), values[1]], + vec![].into(), + ) + .expect_err("should not transfer tokens with insufficient balance"); + + assert!(matches!( + err, + Error::InsufficientBalance(ERC1155InsufficientBalance { + sender, + balance, + needed, + token_id + }) if sender == CHARLIE && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] + )); + } + + #[motsu::test] + fn test_safe_batch_transfer_from_with_data(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, DAVE, 2); + + contract._operator_approvals.setter(DAVE).setter(alice).set(true); + + contract + .safe_batch_transfer_from( + DAVE, + BOB, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("should transfer tokens from Alice to Bob"); + + let balance_id_one = contract + .balance_of(BOB, token_ids[0]) + .expect("should return Bob's balance of the token 0"); + let balance_id_two = contract + .balance_of(BOB, token_ids[1]) + .expect("should return Bob's balance of the token 1"); + + assert_eq!(values[0], balance_id_one); + assert_eq!(values[1], balance_id_two); + } + + #[motsu::test] + fn test_error_invalid_receiver_when_safe_batch_transfer_from_with_data( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 4); + let invalid_receiver = Address::ZERO; + + let err = contract + .safe_batch_transfer_from( + alice, + invalid_receiver, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens to the zero address"); + + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } + + #[motsu::test] + fn test_error_invalid_sender_when_safe_batch_transfer_from_with_data( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 4); + let invalid_sender = Address::ZERO; + + contract + ._operator_approvals + .setter(invalid_sender) + .setter(alice) + .set(true); + + let err = contract + .safe_batch_transfer_from( + invalid_sender, + CHARLIE, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens from the zero address"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn test_error_missing_approval_when_safe_batch_transfer_from_with_data( + contract: Erc1155, + ) { + let (token_ids, values) = init(contract, ALICE, 2); + + let err = contract + .safe_batch_transfer_from( + ALICE, + BOB, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens without approval"); + + assert!(matches!( + err, + Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { + operator, + owner + }) if operator == msg::sender() && owner == ALICE + )); + } + + #[motsu::test] + fn test_error_insufficient_balance_when_safe_batch_transfer_from_with_data( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, CHARLIE, 2); + + contract._operator_approvals.setter(CHARLIE).setter(alice).set(true); + + let err = contract + .safe_batch_transfer_from( + CHARLIE, + BOB, + token_ids.clone(), + vec![values[0] + uint!(1_U256), values[1]], + vec![0, 1, 2, 3].into(), + ) + .expect_err("should not transfer tokens with insufficient balance"); + + assert!(matches!( + err, + Error::InsufficientBalance(ERC1155InsufficientBalance { + sender, + balance, + needed, + token_id + }) if sender == CHARLIE && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] + )); } } From dd932cd7e2ec68003b2681cb195b6dde0fdaf72f Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Sat, 21 Sep 2024 19:57:50 +0800 Subject: [PATCH 10/60] rest extensions done --- .../token/erc1155/extensions/metadata_uri.rs | 31 +++++++++++ contracts/src/token/erc1155/extensions/mod.rs | 2 + .../src/token/erc1155/extensions/supply.rs | 1 + .../token/erc1155/extensions/uri_storage.rs | 54 +++++++++++++------ contracts/src/token/erc1155/mod.rs | 5 +- docs/modules/ROOT/nav.adoc | 2 + docs/modules/ROOT/pages/ERC1155.adoc | 3 ++ docs/modules/ROOT/pages/tokens.adoc | 3 +- 8 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 contracts/src/token/erc1155/extensions/metadata_uri.rs create mode 100644 docs/modules/ROOT/pages/ERC1155.adoc diff --git a/contracts/src/token/erc1155/extensions/metadata_uri.rs b/contracts/src/token/erc1155/extensions/metadata_uri.rs new file mode 100644 index 000000000..ba5004523 --- /dev/null +++ b/contracts/src/token/erc1155/extensions/metadata_uri.rs @@ -0,0 +1,31 @@ +//! Optional MetadataURI of the ERC-1155 standard. + +use alloc::string::String; + +use stylus_proc::{public, sol_storage}; + +sol_storage! { + /// MetadataURI of an [`crate::token::erc1155::Erc1155`] token. + pub struct Erc1155MetadataURI { + /// Optional base URI for tokens. + string _base_uri; + } +} + +/// Interface for the optional metadata functions from the ERC-1155 standard. +pub trait IErc1155MetadataURI { + /// Returns the base of Uniform Resource Identifier (URI) for tokens' + /// collection. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + fn base_uri(&self) -> String; +} + +#[public] +impl IErc1155MetadataURI for Erc1155MetadataURI { + fn base_uri(&self) -> String { + self._base_uri.get_string() + } +} diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs index 95cdd0662..c92cf31d6 100644 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -1,6 +1,8 @@ //! Common extensions to the ERC-1155 standard. pub mod burnable; +pub mod metadata_uri; pub mod supply; pub mod uri_storage; pub use burnable::IErc1155Burnable; +pub use uri_storage::Erc1155UriStorage; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index ea6aad9a4..7f36b13e6 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -10,6 +10,7 @@ //! //! CAUTION: This extension should not be added in an upgrade to an already //! deployed contract. +use alloc::vec::Vec; use alloy_primitives::{uint, Address, U256}; use stylus_proc::sol_storage; diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs index f30a183de..32bbb3abe 100644 --- a/contracts/src/token/erc1155/extensions/uri_storage.rs +++ b/contracts/src/token/erc1155/extensions/uri_storage.rs @@ -14,7 +14,7 @@ sol! { /// /// If an [`URI`] event was emitted for `token_id`, the standard /// https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value - /// returned by {IERC1155MetadataURI-uri}. + /// returned by [`Self::uri`]. #[allow(missing_docs)] event URI(string value, uint256 indexed token_id); } @@ -24,22 +24,15 @@ sol_storage! { pub struct Erc1155UriStorage { /// Optional mapping for token URIs. mapping(uint256 => string) _token_uris; - /// Optional base URI - string _base_uri; } } impl Erc1155UriStorage { /// Sets `token_uri` as the `_token_uris` of `token_id`. - pub fn _set_uri(&mut self, token_id: U256, token_uri: String) { + pub fn _set_token_uri(&mut self, token_id: U256, token_uri: String) { self._token_uris.setter(token_id).set_str(token_uri); evm::log(URI { value: self.uri(token_id), token_id }); } - - /// Sets `base_uri` as the `_base_uri` for all tokens - pub fn _set_base_uri(&mut self, base_uri: String) { - self._base_uri.set_str(base_uri); - } } #[public] @@ -52,12 +45,41 @@ impl Erc1155UriStorage { /// * `token_id` - Id of a token. #[must_use] pub fn uri(&self, token_id: U256) -> String { - if !self._token_uris.getter(token_id).is_empty() { - let update_uri = self._base_uri.get_string() - + &self._token_uris.getter(token_id).get_string(); - update_uri - } else { - todo!() - } + self._token_uris.getter(token_id).get_string() + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::U256; + use stylus_sdk::contract; + + use super::Erc1155UriStorage; + + fn random_token_id() -> U256 { + let num: u32 = rand::random(); + U256::from(num) + } + + #[motsu::test] + fn test_get_uri(contract: Erc1155UriStorage) { + let token_id = random_token_id(); + + let token_uri = "https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155URIStorage".to_string(); + + contract._token_uris.setter(token_id).set_str(token_uri.clone()); + + assert_eq!(token_uri, contract.uri(token_id)); + } + + #[motsu::test] + fn test_set_uri(contract: Erc1155UriStorage) { + let token_id = random_token_id(); + + let token_uri = "https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155URIStorage".to_string(); + + contract._set_token_uri(token_id, token_uri.clone()); + + assert_eq!(token_uri, contract.uri(token_id)); } } diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 4e01cf7da..6836ca3a4 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,5 +1,6 @@ //! Implementation of the [`Erc1155`] token standard. -use alloc::vec::Vec; +// use alloc::{vec, vec::Vec}; +use alloc::{vec, vec::Vec}; use alloy_primitives::{fixed_bytes, Address, FixedBytes, Uint, U256}; use stylus_sdk::{ @@ -45,7 +46,7 @@ sol! { /// /// If an [`URI`] event was emitted for `token_id`, the [standard] /// (https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees]) that `value` will equal the value - /// returned by [`IERC1155MetadataURI-uri`]. + /// returned by [`Erc1155UriStorage::uri`]. #[allow(missing_docs)] event URI(string value, uint256 indexed token_id); } diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 05060ec64..32549c484 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -6,6 +6,8 @@ *** xref:erc20.adoc#erc20-token-extensions[Extensions] ** xref:erc721.adoc[ERC-721] *** xref:erc721.adoc#erc721-token-extensions[Extensions] +** xref:erc1155.adoc[ERC-1155] +*** xref:erc1155.adoc#erc1155-token-extensions[Extensions] * xref:access-control.adoc[Access Control] * xref:crypto.adoc[Cryptography] diff --git a/docs/modules/ROOT/pages/ERC1155.adoc b/docs/modules/ROOT/pages/ERC1155.adoc new file mode 100644 index 000000000..8ff14a414 --- /dev/null +++ b/docs/modules/ROOT/pages/ERC1155.adoc @@ -0,0 +1,3 @@ += ERC-1155 + +ERC1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract]. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/tokens.adoc b/docs/modules/ROOT/pages/tokens.adoc index b9c959384..e2caae271 100644 --- a/docs/modules/ROOT/pages/tokens.adoc +++ b/docs/modules/ROOT/pages/tokens.adoc @@ -24,7 +24,8 @@ In a nutshell, when dealing with non-fungibles (like your house) you care about Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of *standards* (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts. -You've probably heard of the ERC-20 or ERC-721 token standards, and that's why you're here. Head to our specialized guides to learn more about these: +You've probably heard of the ERC-20, ERC-721 or ERC-1155 token standards, and that's why you're here. Head to our specialized guides to learn more about these: * xref:erc20.adoc[ERC-20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. * xref:erc721.adoc[ERC-721]: the de-facto solution for non-fungible tokens, often used for collectibles and games. + * xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency. \ No newline at end of file From ccaf2cc148ceb12026849a823005cf449df8768e Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Tue, 24 Sep 2024 13:38:11 +0800 Subject: [PATCH 11/60] init lib --- Cargo.lock | 8 ++++++++ contracts/src/token/erc1155/mod.rs | 1 - examples/erc1155/Cargo.toml | 20 +++++++++++++++----- examples/erc1155/src/lib.rs | 29 +++++++++++++++++++---------- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b6992d9d..976877395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1516,8 +1516,16 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" name = "erc1155-example" version = "0.0.0" dependencies = [ + "alloy", + "alloy-primitives", "e2e", + "eyre", + "mini-alloc", "openzeppelin-stylus", + "rand", + "stylus-proc", + "stylus-sdk", + "tokio", ] [[package]] diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 6836ca3a4..66c4748eb 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,5 +1,4 @@ //! Implementation of the [`Erc1155`] token standard. -// use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec}; use alloy_primitives::{fixed_bytes, Address, FixedBytes, Uint, U256}; diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml index 38945130a..2dc069da9 100644 --- a/examples/erc1155/Cargo.toml +++ b/examples/erc1155/Cargo.toml @@ -1,6 +1,5 @@ [package] name = "erc1155-example" -version = "0.0.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -8,10 +7,21 @@ keywords.workspace = true repository.workspace = true [dependencies] -openzeppelin-stylus = { path = "../../contracts" } +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true +stylus-sdk.workspace = true +stylus-proc.workspace = true +mini-alloc.workspace = true [dev-dependencies] -e2e = { path = "../../lib/e2e" } +alloy.workspace = true +e2e.workspace = true +tokio.workspace = true +eyre.workspace = true +rand.workspace = true -[lints] -workspace = true +[features] +e2e = [] + +[lib] +crate-type = ["lib", "cdylib"] diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index b93cf3ffd..11e01db06 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -1,14 +1,23 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![cfg_attr(not(test), no_main, no_std)] +extern crate alloc; + +use alloc::vec::Vec; -#[cfg(test)] -mod tests { - use super::*; +use alloy_primitives::{Address, U256}; +use openzeppelin_stylus::token::erc1155::Erc1155; +use stylus_sdk::{ + abi::Bytes, + prelude::{entrypoint, public, sol_storage}, +}; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +sol_storage! { + #[entrypoint] + struct Erc1155Example { + #[borrow] + Erc1155 erc1155; } } + +#[public] +#[inherit(Erc1155)] +impl Erc1155Example {} From cb1ce66a526fb42e03d3780f48e3c223de2a3a6d Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Tue, 24 Sep 2024 20:52:53 +0800 Subject: [PATCH 12/60] run the first test --- README.md | 2 +- contracts/src/token/erc1155/extensions/burnable.rs | 4 ++-- contracts/src/token/erc1155/extensions/supply.rs | 4 ++-- .../src/token/erc1155/extensions/uri_storage.rs | 2 +- contracts/src/token/erc1155/mod.rs | 8 ++++---- examples/erc1155/src/ERC1155ReceiverMock.sol | 2 +- examples/erc1155/src/constructor.sol | 2 +- examples/erc1155/src/lib.rs | 14 +++++--------- examples/erc1155/tests/abi/mod.rs | 6 +++++- examples/erc1155/tests/erc1155.rs | 13 ++++++++++++- scripts/e2e-tests.sh | 2 +- 11 files changed, 35 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5eab8f1b9..ee41d5e2b 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20)] impl Erc20Example { } ``` diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index 7ba9825b9..976af14f7 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -134,7 +134,7 @@ mod tests { fn init( contract: &mut Erc1155, - reciever: Address, + receiver: Address, size: usize, ) -> (Vec, Vec) { let token_ids = random_token_ids(size); @@ -142,7 +142,7 @@ mod tests { contract ._mint_batch( - reciever, + receiver, token_ids.clone(), values.clone(), vec![0, 1, 2, 3].into(), diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 7f36b13e6..2b832f38a 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -130,14 +130,14 @@ mod tests { fn init( contract: &mut Erc1155Supply, - reciever: Address, + receiver: Address, size: usize, ) -> (Vec, Vec) { let token_ids = random_token_ids(size); let values = random_values(size); contract - ._update(Address::ZERO, reciever, token_ids.clone(), values.clone()) + ._update(Address::ZERO, receiver, token_ids.clone(), values.clone()) .expect("Supply failed"); (token_ids, values) } diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs index 32bbb3abe..fc69f2cbf 100644 --- a/contracts/src/token/erc1155/extensions/uri_storage.rs +++ b/contracts/src/token/erc1155/extensions/uri_storage.rs @@ -1,6 +1,6 @@ //! ERC-1155 token with storage based token URI management. //! -//! Inspired by the [contracts::token::erc721::extensions::Erc721UriStorage] +//! Inspired by the [`crate::token::erc721::extensions::Erc721UriStorage`] use alloc::string::String; use alloy_primitives::U256; diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 66c4748eb..ff8a02e05 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -228,7 +228,7 @@ pub trait IErc1155 { ) -> Result; /// Refer to: - /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#IERC1155-balanceOfBatch-address---uint256--- + /// /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) /// version of [`Erc1155::balance_of`]. /// @@ -337,7 +337,7 @@ pub trait IErc1155 { ) -> Result<(), Self::Error>; /// Refer to: - /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- + /// /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) /// version of [`IErc1155::safe_transfer_from`]. /// @@ -1045,7 +1045,7 @@ mod tests { fn init( contract: &mut Erc1155, - reciever: Address, + receiver: Address, size: usize, ) -> (Vec, Vec) { let token_ids = random_token_ids(size); @@ -1053,7 +1053,7 @@ mod tests { contract ._mint_batch( - reciever, + receiver, token_ids.clone(), values.clone(), vec![0, 1, 2, 3].into(), diff --git a/examples/erc1155/src/ERC1155ReceiverMock.sol b/examples/erc1155/src/ERC1155ReceiverMock.sol index 7c2f5f7e2..9c24586cf 100644 --- a/examples/erc1155/src/ERC1155ReceiverMock.sol +++ b/examples/erc1155/src/ERC1155ReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC1155/IERC1155Receiver.sol"; diff --git a/examples/erc1155/src/constructor.sol b/examples/erc1155/src/constructor.sol index d7c52eafb..5ea6045ae 100644 --- a/examples/erc1155/src/constructor.sol +++ b/examples/erc1155/src/constructor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; contract Erc1155Example { mapping(address => mapping(uint256 => uint256)) private _balanceOf; diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 11e01db06..e70a75b47 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -1,23 +1,19 @@ #![cfg_attr(not(test), no_main, no_std)] extern crate alloc; -use alloc::vec::Vec; - -use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::token::erc1155::Erc1155; -use stylus_sdk::{ - abi::Bytes, - prelude::{entrypoint, public, sol_storage}, -}; +use openzeppelin_stylus::{token::erc1155::Erc1155, utils::Pausable}; +use stylus_sdk::prelude::{entrypoint, public, sol_storage}; sol_storage! { #[entrypoint] struct Erc1155Example { #[borrow] Erc1155 erc1155; + #[borrow] + Pausable pausable; } } #[public] -#[inherit(Erc1155)] +#[inherit(Erc1155, Pausable)] impl Erc1155Example {} diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 572b815bb..7e59091df 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -4,6 +4,10 @@ use alloy::sol; sol!( #[sol(rpc)] contract Erc1155 { - + function balanceOf(address account, uint256 id) external view returns (uint256 balance); + // function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory); + function paused() external view returns (bool paused); + function pause() external; + function unpause() external; } ); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 334ff39d4..6668ddeb9 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -1,6 +1,8 @@ #![cfg(feature = "e2e")] use abi::Erc1155; +use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; +use stylus_sdk::contract; mod abi; @@ -9,4 +11,13 @@ mod abi; // ============================================================================ #[e2e::test] -async fn constructs(alice: Account) -> eyre::Result<()> {} +async fn constructs_erc1155(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let Erc1155::pausedReturn { paused } = contract.paused().call().await?; + + assert_eq!(false, paused); + + Ok(()) +} diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index f7b9cdeb1..9d4f5f743 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test erc1155 From 6a87bf9c1d385b87c3e94a91ef1faf6f602ce030 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Tue, 24 Sep 2024 22:56:55 +0800 Subject: [PATCH 13/60] delete keyword --- Cargo.lock | 2 +- examples/erc1155/Cargo.toml | 4 ++-- scripts/e2e-tests.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 976877395..9414a817b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,7 +1514,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erc1155-example" -version = "0.0.0" +version = "0.1.0-rc" dependencies = [ "alloy", "alloy-primitives", diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml index 2dc069da9..fcfe0b196 100644 --- a/examples/erc1155/Cargo.toml +++ b/examples/erc1155/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "erc1155-example" -authors.workspace = true edition.workspace = true license.workspace = true -keywords.workspace = true repository.workspace = true +publish = false +version.workspace = true [dependencies] openzeppelin-stylus.workspace = true diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 9d4f5f743..f7b9cdeb1 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test erc1155 +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" From bb9627d33763f460643f20301763bb48d68b4434 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Mon, 30 Sep 2024 13:34:27 +0800 Subject: [PATCH 14/60] erc1155 example --- contracts/src/token/erc1155/mod.rs | 4 +- examples/erc1155/src/lib.rs | 108 +++- examples/erc1155/tests/abi/mod.rs | 26 +- examples/erc1155/tests/erc1155.rs | 872 ++++++++++++++++++++++++++++- scripts/e2e-tests.sh | 2 +- 5 files changed, 1003 insertions(+), 9 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index ff8a02e05..3b28c0fc2 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -706,7 +706,7 @@ impl Erc1155 { /// # Events /// /// Emits a [`TransferSingle`] event. - fn _mint( + pub fn _mint( &mut self, to: Address, token_id: U256, @@ -743,7 +743,7 @@ impl Erc1155 { /// # Events /// /// Emits a [`TransferBatch`] event. - fn _mint_batch( + pub fn _mint_batch( &mut self, to: Address, token_ids: Vec, diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index e70a75b47..879f15ecb 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -1,8 +1,17 @@ #![cfg_attr(not(test), no_main, no_std)] extern crate alloc; -use openzeppelin_stylus::{token::erc1155::Erc1155, utils::Pausable}; -use stylus_sdk::prelude::{entrypoint, public, sol_storage}; +use alloc::vec::Vec; + +use alloy_primitives::{Address, U256}; +use openzeppelin_stylus::{ + token::erc1155::{extensions::IErc1155Burnable, Erc1155, IErc1155}, + utils::Pausable, +}; +use stylus_sdk::{ + abi::Bytes, + prelude::{entrypoint, public, sol_storage}, +}; sol_storage! { #[entrypoint] @@ -16,4 +25,97 @@ sol_storage! { #[public] #[inherit(Erc1155, Pausable)] -impl Erc1155Example {} +impl Erc1155Example { + pub fn set_operator_approvals( + &mut self, + owner: Address, + operator: Address, + approved: bool, + ) -> Result<(), Vec> { + self.erc1155 + ._operator_approvals + .setter(owner) + .setter(operator) + .set(approved); + Ok(()) + } + + pub fn burn( + &mut self, + account: Address, + token_id: U256, + value: U256, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + + self.erc1155.burn(account, token_id, value)?; + Ok(()) + } + + pub fn burn_batch( + &mut self, + account: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + + self.erc1155.burn_batch(account, token_ids, values)?; + Ok(()) + } + + pub fn mint( + &mut self, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + + self.erc1155._mint(to, token_id, amount, data)?; + Ok(()) + } + + pub fn mint_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + + self.erc1155._mint_batch(to, token_ids, amounts, data)?; + Ok(()) + } + + pub fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + + self.erc1155.safe_transfer_from(from, to, token_id, amount, data)?; + Ok(()) + } + + pub fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + + self.erc1155 + .safe_batch_transfer_from(from, to, token_ids, amounts, data)?; + Ok(()) + } +} diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 7e59091df..5f21af098 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -5,9 +5,33 @@ sol!( #[sol(rpc)] contract Erc1155 { function balanceOf(address account, uint256 id) external view returns (uint256 balance); - // function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory); + #[derive(Debug)] + function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory balances); + function isApprovedForAll(address account, address operator) external view returns (bool approved); + function setApprovalForAll(address operator, bool approved) external; + function setOperatorApprovals(address owner, address operator, bool approved) external; + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) external; + function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; + function mint(address to, uint256 id, uint256 amount, bytes memory data) external; + function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; + function burn(address account, uint256 id, uint256 value) external; + function burnBatch(address account, uint256[] memory ids, uint256[] memory values) external; function paused() external view returns (bool paused); function pause() external; function unpause() external; + + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); + error ERC1155InvalidOperator(address operator); + error ERC1155InvalidSender(address sender); + error ERC1155InvalidReceiver(address receiver); + error ERC1155MissingApprovalForAll(address operator, address owner); + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id); + + #[derive(Debug, PartialEq)] + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + #[derive(Debug, PartialEq)] + event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); + #[derive(Debug, PartialEq)] + event ApprovalForAll(address indexed account, address indexed operator, bool approved); } ); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 6668ddeb9..6007d80f1 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -1,17 +1,30 @@ #![cfg(feature = "e2e")] +use std::println; + use abi::Erc1155; +use alloy::{ + primitives::{uint, Address, U256}, + signers::k256::sha2::digest::typenum::uint, +}; use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; -use stylus_sdk::contract; mod abi; +fn random_token_ids(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() +} + +fn random_values(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() +} + // ============================================================================ // Integration Tests: ERC-1155 Token Standard // ============================================================================ #[e2e::test] -async fn constructs_erc1155(alice: Account) -> eyre::Result<()> { +async fn constructs(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -21,3 +34,858 @@ async fn constructs_erc1155(alice: Account) -> eyre::Result<()> { Ok(()) } + +#[e2e::test] +async fn test_error_array_length_mismatch( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let token_ids = random_token_ids(3); + let accounts = vec![alice.address(), bob.address()]; + + let err = contract + .balanceOfBatch(accounts, token_ids) + .call() + .await + .expect_err("should return `ERC1155InvalidArrayLength`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidArrayLength { + idsLength: uint!(3_U256), + valuesLength: uint!(2_U256) + })); + + Ok(()) +} + +#[e2e::test] +async fn test_balance_of_zero_balance(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + let token_ids = random_token_ids(1); + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice.address(), token_ids[0]).call().await?; + assert_eq!(uint!(0_U256), balance); + + Ok(()) +} + +#[e2e::test] +async fn test_balance_of_batch_zero_balance( + alice: Account, + bob: Account, + dave: Account, + charlie: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + let accounts = + vec![alice.address(), bob.address(), dave.address(), charlie.address()]; + let token_ids = random_token_ids(4); + + let Erc1155::balanceOfBatchReturn { balances } = + contract.balanceOfBatch(accounts, token_ids).call().await?; + assert_eq!( + vec![uint!(0_U256), uint!(0_U256), uint!(0_U256), uint!(0_U256)], + balances + ); + + Ok(()) +} + +#[e2e::test] +async fn test_set_approval_for_all( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let approved_value = true; + let receipt = + receipt!(contract.setApprovalForAll(bob_addr, approved_value))?; + + assert!(receipt.emits(Erc1155::ApprovalForAll { + account: alice_addr, + operator: bob_addr, + approved: approved_value, + })); + + let Erc1155::isApprovedForAllReturn { approved } = + contract.isApprovedForAll(alice_addr, bob_addr).call().await?; + assert_eq!(approved_value, approved); + + let approved_value = false; + let receipt = + receipt!(contract.setApprovalForAll(bob_addr, approved_value))?; + + assert!(receipt.emits(Erc1155::ApprovalForAll { + account: alice_addr, + operator: bob_addr, + approved: approved_value, + })); + + let Erc1155::isApprovedForAllReturn { approved } = + contract.isApprovedForAll(alice_addr, bob_addr).call().await?; + assert_eq!(approved_value, approved); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_operator_when_approval_for_all( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let invalid_operator = Address::ZERO; + + let err = send!(contract.setApprovalForAll(invalid_operator, true)) + .expect_err("should return `ERC1155InvalidOperator`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidOperator { + operator: invalid_operator + })); + + Ok(()) +} + +#[e2e::test] +async fn is_approved_for_all_invalid_operator( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let invalid_operator = Address::ZERO; + + let Erc1155::isApprovedForAllReturn { approved } = contract + .isApprovedForAll(alice.address(), invalid_operator) + .call() + .await?; + + assert_eq!(false, approved); + + Ok(()) +} + +#[e2e::test] +async fn test_mints(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let receipt = receipt!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + id: token_id, + value + })); + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(value, balance); + + Ok(()) +} + +#[e2e::test] +async fn test_mint_batch( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + + let receipt = receipt!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + for (token_id, value) in token_ids.iter().zip(values.iter()) { + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice_addr, *token_id).call().await?; + assert_eq!(*value, balance); + } + + let receipt = receipt!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: bob_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + for (token_id, value) in token_ids.iter().zip(values.iter()) { + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(bob_addr, *token_id).call().await?; + assert_eq!(*value, balance); + } + + let receipt = receipt!(contract.mintBatch( + dave_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: dave_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + for (token_id, value) in token_ids.iter().zip(values.iter()) { + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(dave_addr, *token_id).call().await?; + assert_eq!(*value, balance); + } + + Ok(()) +} + +#[e2e::test] +async fn test_safe_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )); + + let receipt = receipt!(contract.safeTransferFrom( + alice_addr, + bob_addr, + token_id, + value, + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: bob_addr, + id: token_id, + value + })); + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(bob_addr, token_id).call().await?; + assert_eq!(value, balance); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_receiver_when_safe_transfer_from( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let invalid_receiver = Address::ZERO; + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint(alice_addr, token_id, value, vec![].into())); + + let err = send!(contract.safeTransferFrom( + alice_addr, + invalid_receiver, + token_id, + value, + vec![].into() + )) + .expect_err("should return `ERC1155InvalidReceiver`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { + receiver: invalid_receiver + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_sender_when_safe_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let invalid_sender = Address::ZERO; + let bob_addr = bob.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + // let _ = watch!(contract.mint(alice_addr, token_id, value, + // vec![].into())); + let _ = watch!(contract.setOperatorApprovals( + invalid_sender, + alice.address(), + true + )); + + let err = send!(contract.safeTransferFrom( + invalid_sender, + bob_addr, + token_id, + value, + vec![].into() + )) + .expect_err("should return `ERC1155InvalidSender`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { + sender: invalid_sender + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_missing_approval_when_safe_transfer_from( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint(bob_addr, token_id, value, vec![].into())); + + let err = send!(contract.safeTransferFrom( + bob_addr, + dave_addr, + token_id, + value, + vec![].into() + )) + .expect_err("should return `ERC1155MissingApprovalForAll`"); + + assert!(err.reverted_with(Erc1155::ERC1155MissingApprovalForAll { + operator: alice_addr, + owner: bob_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_insufficient_balance_when_safe_transfer_from( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = watch!(contract.mint(bob_addr, token_id, value, vec![].into())); + let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + + let err = send!(contract.safeTransferFrom( + bob_addr, + dave_addr, + token_id, + value + uint!(1_U256), + vec![].into() + )) + .expect_err("should return `ERC1155InsufficientBalance`"); + + assert!(err.reverted_with(Erc1155::ERC1155InsufficientBalance { + sender: bob_addr, + balance: value, + needed: value + uint!(1_U256), + id: token_id + })); + + Ok(()) +} + +#[e2e::test] +async fn test_safe_batch_transfer_from( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + let amount_one = values[0] - uint!(1_U256); + let amount_two = values[1] - uint!(1_U256); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + + let receipt = receipt!(contract.safeBatchTransferFrom( + bob_addr, + dave_addr, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: bob_addr, + to: dave_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + let balance_id_one = + contract.balanceOf(dave_addr, token_ids[0]).call().await?.balance; + let balance_id_two = + contract.balanceOf(dave_addr, token_ids[1]).call().await?.balance; + + assert_eq!(values[0], balance_id_one); + assert_eq!(values[1], balance_id_two); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_receiver_when_safe_batch_transfer_from( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let invalid_receiver = Address::ZERO; + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let err = send!(contract.safeBatchTransferFrom( + alice_addr, + invalid_receiver, + token_ids.clone(), + values.clone(), + vec![].into() + )) + .expect_err("should return `ERC1155InvalidReceiver`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { + receiver: invalid_receiver + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_sender_when_safe_batch_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let invalid_sender = Address::ZERO; + let bob_addr = bob.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + let _ = watch!(contract.setOperatorApprovals( + invalid_sender, + alice.address(), + true + )); + + let err = send!(contract.safeBatchTransferFrom( + invalid_sender, + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )) + .expect_err("should return `ERC1155InvalidSender`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { + sender: invalid_sender + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_missing_approval_when_safe_batch_transfer_from( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_ids = random_token_ids(4); + let values = random_values(4); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let err = send!(contract.safeBatchTransferFrom( + bob_addr, + dave_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )) + .expect_err("should return `ERC1155MissingApprovalForAll`"); + + assert!(err.reverted_with(Erc1155::ERC1155MissingApprovalForAll { + operator: alice_addr, + owner: bob_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_insufficient_balance_when_safe_batch_transfer_from( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + + let err = send!(contract.safeBatchTransferFrom( + bob_addr, + dave_addr, + token_ids.clone(), + vec![values[0] + uint!(1_U256), values[1]], + vec![].into() + )) + .expect_err("should return `ERC1155InsufficientBalance`"); + + assert!(err.reverted_with(Erc1155::ERC1155InsufficientBalance { + sender: bob_addr, + balance: values[0], + needed: values[0] + uint!(1_U256), + id: token_ids[0] + })); + + Ok(()) +} + +#[e2e::test] +async fn test_burns(alice: Account, bob: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(1); + let values = random_values(1); + + let _ = + watch!(contract.mint(bob_addr, token_ids[0], values[0], vec![].into())); + + let initial_balance = + contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; + assert_eq!(values[0], initial_balance); + + let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + + let receipt = receipt!(contract.burn(bob_addr, token_ids[0], values[0]))?; + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(bob_addr, token_ids[0]).call().await?; + assert_eq!(uint!(0_U256), balance); + + Ok(()) +} + +#[e2e::test] +async fn test_error_missing_approval_when_burn( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(1); + let values = random_values(1); + + let _ = + watch!(contract.mint(bob_addr, token_ids[0], values[0], vec![].into())); + + let err = send!(contract.burn(bob_addr, token_ids[0], values[0])) + .expect_err("should return `ERC1155MissingApprovalForAll`"); + + assert!(err.reverted_with(Erc1155::ERC1155MissingApprovalForAll { + operator: alice_addr, + owner: bob_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_sender_when_burn( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(1); + let values = random_values(1); + let invalid_sender = Address::ZERO; + + let _ = watch!(contract.mint( + alice_addr, + token_ids[0], + values[0], + vec![].into() + )); + + let _ = + watch!(contract.setOperatorApprovals(invalid_sender, alice_addr, true)); + + let err = send!(contract.burn(invalid_sender, token_ids[0], values[0])) + .expect_err("should not burn tokens from the zero address"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { + sender: invalid_sender + })); + + Ok(()) +} + +#[e2e::test] +async fn test_burns_batch(alice: Account, bob: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(4); + let values = random_values(4); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let initial_balance_0 = + contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; + let initial_balance_1 = + contract.balanceOf(bob_addr, token_ids[1]).call().await?.balance; + let initial_balance_2 = + contract.balanceOf(bob_addr, token_ids[2]).call().await?.balance; + let initial_balance_3 = + contract.balanceOf(bob_addr, token_ids[3]).call().await?.balance; + + assert_eq!(values[0], initial_balance_0); + assert_eq!(values[1], initial_balance_1); + assert_eq!(values[2], initial_balance_2); + assert_eq!(values[3], initial_balance_3); + + let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + + let receipt = receipt!(contract.burnBatch( + bob_addr, + token_ids.clone(), + values.clone() + ))?; + + let final_balance_0 = + contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; + let final_balance_1 = + contract.balanceOf(bob_addr, token_ids[1]).call().await?.balance; + let final_balance_2 = + contract.balanceOf(bob_addr, token_ids[2]).call().await?.balance; + let final_balance_3 = + contract.balanceOf(bob_addr, token_ids[3]).call().await?.balance; + + assert_eq!(uint!(0_U256), final_balance_0); + assert_eq!(uint!(0_U256), final_balance_1); + assert_eq!(uint!(0_U256), final_balance_2); + assert_eq!(uint!(0_U256), final_balance_3); + + Ok(()) +} + +#[e2e::test] +async fn test_error_missing_approval_when_burn_batch( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let err = + send!(contract.burnBatch(bob_addr, token_ids.clone(), values.clone())) + .expect_err("should return `ERC1155MissingApprovalForAll`"); + + assert!(err.reverted_with(Erc1155::ERC1155MissingApprovalForAll { + operator: alice_addr, + owner: bob_addr + })); + + Ok(()) +} + +#[e2e::test] +async fn test_error_invalid_sender_when_burn_batch( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let invalid_sender = Address::ZERO; + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + let _ = watch!(contract.setOperatorApprovals( + invalid_sender, + alice.address(), + true + )); + + let err = send!(contract.burnBatch( + invalid_sender, + token_ids.clone(), + values.clone() + )) + .expect_err("should return `ERC1155InvalidSender`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { + sender: invalid_sender + })); + + Ok(()) +} diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index f7b9cdeb1..9d4f5f743 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test erc1155 From 3ee507b14840b988b652b8c8607a20c8c32b6d1f Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 30 Sep 2024 19:21:33 +0400 Subject: [PATCH 15/60] add IErc165 to Erc1155` --- .../token/erc1155/extensions/metadata_uri.rs | 2 ++ contracts/src/token/erc1155/mod.rs | 35 ++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/metadata_uri.rs b/contracts/src/token/erc1155/extensions/metadata_uri.rs index ba5004523..81b8e3f1a 100644 --- a/contracts/src/token/erc1155/extensions/metadata_uri.rs +++ b/contracts/src/token/erc1155/extensions/metadata_uri.rs @@ -2,6 +2,7 @@ use alloc::string::String; +use openzeppelin_stylus_proc::interface_id; use stylus_proc::{public, sol_storage}; sol_storage! { @@ -13,6 +14,7 @@ sol_storage! { } /// Interface for the optional metadata functions from the ERC-1155 standard. +#[interface_id] pub trait IErc1155MetadataURI { /// Returns the base of Uniform Resource Identifier (URI) for tokens' /// collection. diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 3b28c0fc2..eb709312f 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -2,6 +2,7 @@ use alloc::{vec, vec::Vec}; use alloy_primitives::{fixed_bytes, Address, FixedBytes, Uint, U256}; +use openzeppelin_stylus_proc::interface_id; use stylus_sdk::{ abi::Bytes, alloy_sol_types::sol, @@ -10,7 +11,10 @@ use stylus_sdk::{ prelude::*, }; -use crate::utils::math::storage::SubAssignUnchecked; +use crate::utils::{ + introspection::erc165::{Erc165, IErc165}, + math::storage::SubAssignUnchecked, +}; pub mod extensions; @@ -211,6 +215,7 @@ sol_storage! { unsafe impl TopLevelStorage for Erc1155 {} /// Required interface of an [`Erc1155`] compliant contract. +#[interface_id] pub trait IErc1155 { /// The error type associated to this ERC-1155 trait implementation. type Error: Into>; @@ -392,9 +397,9 @@ impl IErc1155 for Erc1155 { } let balances: Vec> = accounts - .iter() - .zip(token_ids.iter()) - .map(|(&account, &token_id)| { + .into_iter() + .zip(token_ids.into_iter()) + .map(|(account, token_id)| { self._balances.get(token_id).get(account) }) .collect(); @@ -451,6 +456,13 @@ impl IErc1155 for Erc1155 { } } +impl IErc165 for Erc1155 { + fn supports_interface(interface_id: FixedBytes<4>) -> bool { + ::INTERFACE_ID == u32::from_be_bytes(*interface_id) + || Erc165::supports_interface(interface_id) + } +} + impl Erc1155 { /// Transfers a `value` amount of tokens of type `token_ids` from `from` to /// `to`. Will mint (or burn) if `from` (or `to`) is the zero address. @@ -1028,6 +1040,10 @@ mod tests { ERC1155InvalidOperator, ERC1155InvalidReceiver, ERC1155InvalidSender, ERC1155MissingApprovalForAll, Erc1155, Error, IErc1155, }; + use crate::{ + token::erc721::{Erc721, IErc721}, + utils::introspection::erc165::IErc165, + }; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); @@ -1794,4 +1810,15 @@ mod tests { }) if sender == CHARLIE && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] )); } + + #[motsu::test] + fn interface_id() { + let actual = ::INTERFACE_ID; + let expected = 0xd9b67a26; + assert_eq!(actual, expected); + + let actual = ::INTERFACE_ID; + let expected = 0x01ffc9a7; + assert_eq!(actual, expected); + } } From 3e7618e14207df2575e517970f932f2e2a726d7a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 30 Sep 2024 19:30:56 +0400 Subject: [PATCH 16/60] optimise imports --- contracts/src/token/erc1155/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index eb709312f..2aee548a2 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1041,8 +1041,7 @@ mod tests { ERC1155MissingApprovalForAll, Erc1155, Error, IErc1155, }; use crate::{ - token::erc721::{Erc721, IErc721}, - utils::introspection::erc165::IErc165, + token::erc721::IErc721, utils::introspection::erc165::IErc165, }; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); From 11247de0dcc98271efa30485a300b6555b631761 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:31:27 +0200 Subject: [PATCH 17/60] build(deps): bump crate-ci/typos from 1.24.6 to 1.25.0 (#303) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.24.6 to 1.25.0.
Release notes

Sourced from crate-ci/typos's releases.

v1.25.0

[1.25.0] - 2024-10-01

Fixes

Changelog

Sourced from crate-ci/typos's changelog.

[1.25.0] - 2024-10-01

Fixes

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.24.6&new-version=1.25.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6ac80efda..ff13e16b3 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -110,4 +110,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling of files in the workspace - uses: crate-ci/typos@v1.24.6 + uses: crate-ci/typos@v1.25.0 From d9a4b62523cf0eba4ae468f6b31bc53921637a96 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Fri, 4 Oct 2024 15:35:00 +0800 Subject: [PATCH 18/60] fix all and add e2e tests --- Cargo.lock | 17 +++ Cargo.toml | 6 +- .../src/token/erc1155/extensions/burnable.rs | 12 +- .../token/erc1155/extensions/metadata_uri.rs | 33 ------ contracts/src/token/erc1155/extensions/mod.rs | 3 - .../src/token/erc1155/extensions/supply.rs | 26 +++-- .../token/erc1155/extensions/uri_storage.rs | 85 -------------- contracts/src/token/erc1155/mod.rs | 63 +++++----- examples/erc1155-supply/Cargo.toml | 28 +++++ examples/erc1155-supply/src/constructor.sol | 14 +++ examples/erc1155-supply/src/lib.rs | 42 +++++++ examples/erc1155-supply/tests/abi/mod.rs | 12 ++ .../erc1155-supply/tests/erc1155-supply.rs | 110 ++++++++++++++++++ examples/erc1155/tests/erc1155.rs | 55 ++++----- scripts/e2e-tests.sh | 2 +- 15 files changed, 300 insertions(+), 208 deletions(-) delete mode 100644 contracts/src/token/erc1155/extensions/metadata_uri.rs delete mode 100644 contracts/src/token/erc1155/extensions/uri_storage.rs create mode 100644 examples/erc1155-supply/Cargo.toml create mode 100644 examples/erc1155-supply/src/constructor.sol create mode 100644 examples/erc1155-supply/src/lib.rs create mode 100644 examples/erc1155-supply/tests/abi/mod.rs create mode 100644 examples/erc1155-supply/tests/erc1155-supply.rs diff --git a/Cargo.lock b/Cargo.lock index b196e9caa..1f34ae207 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1529,6 +1529,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc1155-supply-example" +version = "0.1.0-rc" +dependencies = [ + "alloy", + "alloy-primitives", + "alloy-sol-types", + "e2e", + "eyre", + "mini-alloc", + "openzeppelin-stylus", + "rand", + "stylus-proc", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc20-example" version = "0.1.0-rc" diff --git a/Cargo.toml b/Cargo.toml index 9cc82e871..d52128d69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "examples/erc721-consecutive", "examples/erc721-metadata", "examples/erc1155", + "examples/erc1155-supply", "examples/merkle-proofs", "examples/ownable", "examples/access-control", @@ -34,6 +35,7 @@ default-members = [ "examples/erc721-consecutive", "examples/erc721-metadata", "examples/erc1155", + "examples/erc1155-supply", "examples/merkle-proofs", "examples/ownable", "examples/access-control", @@ -104,10 +106,10 @@ quote = "1.0.35" openzeppelin-stylus = { path = "contracts" } openzeppelin-stylus-proc = { path = "contracts-proc" } openzeppelin-crypto = { path = "lib/crypto" } -motsu = { path = "lib/motsu"} +motsu = { path = "lib/motsu" } motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } e2e = { path = "lib/e2e" } -e2e-proc = {path = "lib/e2e-proc"} +e2e-proc = { path = "lib/e2e-proc" } [profile.release] codegen-units = 1 diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs index 976af14f7..c255ef466 100644 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ b/contracts/src/token/erc1155/extensions/burnable.rs @@ -152,7 +152,7 @@ mod tests { } #[motsu::test] - fn test_burns(contract: Erc1155) { + fn burns(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, BOB, 1); @@ -176,7 +176,7 @@ mod tests { } #[motsu::test] - fn test_error_missing_approval_when_burn(contract: Erc1155) { + fn error_when_missing_approval_burns(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, BOB, 1); @@ -194,7 +194,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_sender_when_burn(contract: Erc1155) { + fn error_when_invalid_sender_burns(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, alice, 1); let invalid_sender = Address::ZERO; @@ -218,7 +218,7 @@ mod tests { } #[motsu::test] - fn test_burns_batch(contract: Erc1155) { + fn burns_batch(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, BOB, 4); @@ -266,7 +266,7 @@ mod tests { } #[motsu::test] - fn test_error_missing_approval_when_burn_batch(contract: Erc1155) { + fn error_when_missing_approval_burn_batch(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, BOB, 2); @@ -284,7 +284,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_sender_when_burn_batch(contract: Erc1155) { + fn error_when_invalid_sender_burn_batch(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, alice, 5); let invalid_sender = Address::ZERO; diff --git a/contracts/src/token/erc1155/extensions/metadata_uri.rs b/contracts/src/token/erc1155/extensions/metadata_uri.rs deleted file mode 100644 index 81b8e3f1a..000000000 --- a/contracts/src/token/erc1155/extensions/metadata_uri.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Optional MetadataURI of the ERC-1155 standard. - -use alloc::string::String; - -use openzeppelin_stylus_proc::interface_id; -use stylus_proc::{public, sol_storage}; - -sol_storage! { - /// MetadataURI of an [`crate::token::erc1155::Erc1155`] token. - pub struct Erc1155MetadataURI { - /// Optional base URI for tokens. - string _base_uri; - } -} - -/// Interface for the optional metadata functions from the ERC-1155 standard. -#[interface_id] -pub trait IErc1155MetadataURI { - /// Returns the base of Uniform Resource Identifier (URI) for tokens' - /// collection. - /// - /// # Arguments - /// - /// * `&self` - Read access to the contract's state. - fn base_uri(&self) -> String; -} - -#[public] -impl IErc1155MetadataURI for Erc1155MetadataURI { - fn base_uri(&self) -> String { - self._base_uri.get_string() - } -} diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs index c92cf31d6..13644ddb2 100644 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -1,8 +1,5 @@ //! Common extensions to the ERC-1155 standard. pub mod burnable; -pub mod metadata_uri; pub mod supply; -pub mod uri_storage; pub use burnable::IErc1155Burnable; -pub use uri_storage::Erc1155UriStorage; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 2b832f38a..3b31107ea 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -35,15 +35,19 @@ sol_storage! { #[public] impl Erc1155Supply { - fn total_supply(&self, token_id: U256) -> U256 { + /// Total value of tokens in with a given id. + pub fn total_supply(&self, token_id: U256) -> U256 { self._total_supply.get(token_id) } - fn total_supply_all(&self) -> U256 { + /// Total value of tokens. + #[selector(name = "totalSupply")] + pub fn total_supply_all(&self) -> U256 { *self._total_supply_all } - fn exists(&self, token_id: U256) -> bool { + /// Indicates whether any token exist with a given id, or not. + pub fn exists(&self, token_id: U256) -> bool { self.total_supply(token_id) > uint!(0_U256) } } @@ -64,7 +68,7 @@ impl Erc1155Supply { /// /// Emits a [`TransferSingle`] event if the arrays contain one element, and /// [`TransferBatch`] otherwise. - fn _update( + pub fn _update( &mut self, from: Address, to: Address, @@ -138,12 +142,12 @@ mod tests { contract ._update(Address::ZERO, receiver, token_ids.clone(), values.clone()) - .expect("Supply failed"); + .expect("should supply"); (token_ids, values) } #[motsu::test] - fn test_supply_of_zero_supply(contract: Erc1155Supply) { + fn supply_of_zero_supply(contract: Erc1155Supply) { let token_ids = random_token_ids(1); assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); assert_eq!(U256::ZERO, contract.total_supply_all()); @@ -151,30 +155,30 @@ mod tests { } #[motsu::test] - fn test_supply_with_zero_address_sender(contract: Erc1155Supply) { + fn supply_with_zero_address_sender(contract: Erc1155Supply) { let token_ids = random_token_ids(1); let values = random_values(1); contract ._update(Address::ZERO, ALICE, token_ids.clone(), values.clone()) - .expect("Supply failed"); + .expect("should supply"); assert_eq!(values[0], contract.total_supply(token_ids[0])); assert_eq!(values[0], contract.total_supply_all()); assert!(contract.exists(token_ids[0])); } #[motsu::test] - fn test_supply_with_zero_address_receiver(contract: Erc1155Supply) { + fn supply_with_zero_address_receiver(contract: Erc1155Supply) { let (token_ids, values) = init(contract, ALICE, 1); contract ._update(ALICE, Address::ZERO, token_ids.clone(), values.clone()) - .expect("Supply failed"); + .expect("should supply"); assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); assert_eq!(U256::ZERO, contract.total_supply_all()); assert!(!contract.exists(token_ids[0])); } #[motsu::test] - fn test_supply_batch(contract: Erc1155Supply) { + fn supply_batch(contract: Erc1155Supply) { let (token_ids, values) = init(contract, BOB, 4); assert_eq!( values[0], diff --git a/contracts/src/token/erc1155/extensions/uri_storage.rs b/contracts/src/token/erc1155/extensions/uri_storage.rs deleted file mode 100644 index fc69f2cbf..000000000 --- a/contracts/src/token/erc1155/extensions/uri_storage.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! ERC-1155 token with storage based token URI management. -//! -//! Inspired by the [`crate::token::erc721::extensions::Erc721UriStorage`] -use alloc::string::String; - -use alloy_primitives::U256; -use alloy_sol_types::sol; -use stylus_proc::{public, sol_storage}; -use stylus_sdk::evm; - -sol! { - - /// Emitted when the URI for token type `token_id` changes to `value`, if it is a non-programmatic URI. - /// - /// If an [`URI`] event was emitted for `token_id`, the standard - /// https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value - /// returned by [`Self::uri`]. - #[allow(missing_docs)] - event URI(string value, uint256 indexed token_id); -} - -sol_storage! { - /// Uri Storage. - pub struct Erc1155UriStorage { - /// Optional mapping for token URIs. - mapping(uint256 => string) _token_uris; - } -} - -impl Erc1155UriStorage { - /// Sets `token_uri` as the `_token_uris` of `token_id`. - pub fn _set_token_uri(&mut self, token_id: U256, token_uri: String) { - self._token_uris.setter(token_id).set_str(token_uri); - evm::log(URI { value: self.uri(token_id), token_id }); - } -} - -#[public] -impl Erc1155UriStorage { - /// Returns the Uniform Resource Identifier (URI) for `token_id` token. - /// - /// # Arguments - /// - /// * `&self` - Read access to the contract's state. - /// * `token_id` - Id of a token. - #[must_use] - pub fn uri(&self, token_id: U256) -> String { - self._token_uris.getter(token_id).get_string() - } -} - -#[cfg(all(test, feature = "std"))] -mod tests { - use alloy_primitives::U256; - use stylus_sdk::contract; - - use super::Erc1155UriStorage; - - fn random_token_id() -> U256 { - let num: u32 = rand::random(); - U256::from(num) - } - - #[motsu::test] - fn test_get_uri(contract: Erc1155UriStorage) { - let token_id = random_token_id(); - - let token_uri = "https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155URIStorage".to_string(); - - contract._token_uris.setter(token_id).set_str(token_uri.clone()); - - assert_eq!(token_uri, contract.uri(token_id)); - } - - #[motsu::test] - fn test_set_uri(contract: Erc1155UriStorage) { - let token_id = random_token_id(); - - let token_uri = "https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155URIStorage".to_string(); - - contract._set_token_uri(token_id, token_uri.clone()); - - assert_eq!(token_uri, contract.uri(token_id)); - } -} diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 2aee548a2..048c0dc8e 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -506,7 +506,6 @@ impl Erc1155 { let operator = msg::sender(); for (&token_id, &value) in token_ids.iter().zip(values.iter()) { if !from.is_zero() { - // let from_balance = self._balances.get(token_id).get(from); let from_balance = self.balance_of(from, token_id)?; if from_balance < value { return Err(Error::InsufficientBalance( @@ -1078,7 +1077,7 @@ mod tests { } #[motsu::test] - fn test_balance_of_zero_balance(contract: Erc1155) { + fn balance_of_zero_balance(contract: Erc1155) { let owner = msg::sender(); let token_id = random_token_ids(1)[0]; let balance = contract @@ -1088,7 +1087,7 @@ mod tests { } #[motsu::test] - fn test_error_array_length_mismatch(contract: Erc1155) { + fn error_when_array_length_mismatch(contract: Erc1155) { let token_ids = random_token_ids(3); let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; let ids_length = U256::from(token_ids.len()); @@ -1108,7 +1107,7 @@ mod tests { } #[motsu::test] - fn test_balance_of_batch_zero_balance(contract: Erc1155) { + fn balance_of_batch_zero_balance(contract: Erc1155) { let token_ids = random_token_ids(4); let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; let balances = contract @@ -1121,7 +1120,7 @@ mod tests { } #[motsu::test] - fn test_set_approval_for_all(contract: Erc1155) { + fn set_approval_for_all(contract: Erc1155) { let alice = msg::sender(); contract._operator_approvals.setter(alice).setter(BOB).set(false); @@ -1137,7 +1136,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_operator_when_approval_for_all(contract: Erc1155) { + fn error_when_invalid_operator_set_approval_for_all(contract: Erc1155) { let invalid_operator = Address::ZERO; let err = contract @@ -1153,7 +1152,7 @@ mod tests { } #[motsu::test] - fn test_mints(contract: Erc1155) { + fn mints(contract: Erc1155) { let alice = msg::sender(); let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; @@ -1170,7 +1169,7 @@ mod tests { } #[motsu::test] - fn test_mints_batch(contract: Erc1155) { + fn mints_batch(contract: Erc1155) { let token_ids = random_token_ids(4); let values = random_values(4); let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; @@ -1245,7 +1244,7 @@ mod tests { } #[motsu::test] - fn test_safe_transfer_from(contract: Erc1155) { + fn safe_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, BOB, 2); let amount_one = values[0] - uint!(1_U256); @@ -1284,7 +1283,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_receiver_when_safe_transfer_from(contract: Erc1155) { + fn error_when_invalid_receiver_safe_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, alice, 1); let invalid_receiver = Address::ZERO; @@ -1308,7 +1307,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_sender_when_safe_transfer_from(contract: Erc1155) { + fn error_when_invalid_sender_safe_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, alice, 1); let invalid_sender = Address::ZERO; @@ -1338,7 +1337,7 @@ mod tests { } #[motsu::test] - fn test_error_missing_approval_when_safe_transfer_from(contract: Erc1155) { + fn error_when_missing_approval_safe_transfer_from(contract: Erc1155) { let (token_ids, values) = init(contract, ALICE, 1); let err = contract @@ -1361,9 +1360,7 @@ mod tests { } #[motsu::test] - fn test_error_insufficient_balance_when_safe_transfer_from( - contract: Erc1155, - ) { + fn error_when_insufficient_balance_safe_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, BOB, 1); @@ -1391,7 +1388,7 @@ mod tests { } #[motsu::test] - fn test_safe_transfer_from_with_data(contract: Erc1155) { + fn safe_transfer_from_with_data(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, DAVE, 1); @@ -1415,7 +1412,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_receiver_when_safe_transfer_from_with_data( + fn error_when_invalid_receiver_safe_transfer_from_with_data( contract: Erc1155, ) { let (token_ids, values) = init(contract, DAVE, 1); @@ -1440,7 +1437,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_sender_when_safe_transfer_from_with_data( + fn error_when_invalid_sender_safe_transfer_from_with_data( contract: Erc1155, ) { let alice = msg::sender(); @@ -1472,7 +1469,7 @@ mod tests { } #[motsu::test] - fn test_error_missing_approval_when_safe_transfer_from_with_data( + fn error_when_missing_approval_safe_transfer_from_with_data( contract: Erc1155, ) { let (token_ids, values) = init(contract, ALICE, 1); @@ -1497,7 +1494,7 @@ mod tests { } #[motsu::test] - fn test_error_insufficient_balance_when_safe_transfer_from_with_data( + fn error_when_insufficient_balance_safe_transfer_from_with_data( contract: Erc1155, ) { let alice = msg::sender(); @@ -1527,7 +1524,7 @@ mod tests { } #[motsu::test] - fn test_safe_batch_transfer_from(contract: Erc1155) { + fn safe_batch_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, DAVE, 2); let amount_one = values[0] - uint!(1_U256); @@ -1557,9 +1554,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_receiver_when_safe_batch_transfer_from( - contract: Erc1155, - ) { + fn error_when_invalid_receiver_safe_batch_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, alice, 4); let invalid_receiver = Address::ZERO; @@ -1583,9 +1578,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_sender_when_safe_batch_transfer_from( - contract: Erc1155, - ) { + fn error_when_invalid_sender_safe_batch_transfer_from(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, alice, 4); let invalid_sender = Address::ZERO; @@ -1615,9 +1608,7 @@ mod tests { } #[motsu::test] - fn test_error_missing_approval_when_safe_batch_transfer_from( - contract: Erc1155, - ) { + fn error_when_missing_approval_safe_batch_transfer_from(contract: Erc1155) { let (token_ids, values) = init(contract, ALICE, 2); let err = contract @@ -1640,7 +1631,7 @@ mod tests { } #[motsu::test] - fn test_error_insufficient_balance_when_safe_batch_transfer_from( + fn error_when_insufficient_balance_safe_batch_transfer_from( contract: Erc1155, ) { let alice = msg::sender(); @@ -1670,7 +1661,7 @@ mod tests { } #[motsu::test] - fn test_safe_batch_transfer_from_with_data(contract: Erc1155) { + fn safe_batch_transfer_from_with_data(contract: Erc1155) { let alice = msg::sender(); let (token_ids, values) = init(contract, DAVE, 2); @@ -1698,7 +1689,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_receiver_when_safe_batch_transfer_from_with_data( + fn error_when_invalid_receiver_safe_batch_transfer_from_with_data( contract: Erc1155, ) { let alice = msg::sender(); @@ -1724,7 +1715,7 @@ mod tests { } #[motsu::test] - fn test_error_invalid_sender_when_safe_batch_transfer_from_with_data( + fn error_when_invalid_sender_safe_batch_transfer_from_with_data( contract: Erc1155, ) { let alice = msg::sender(); @@ -1756,7 +1747,7 @@ mod tests { } #[motsu::test] - fn test_error_missing_approval_when_safe_batch_transfer_from_with_data( + fn error_when_missing_approval_safe_batch_transfer_from_with_data( contract: Erc1155, ) { let (token_ids, values) = init(contract, ALICE, 2); @@ -1781,7 +1772,7 @@ mod tests { } #[motsu::test] - fn test_error_insufficient_balance_when_safe_batch_transfer_from_with_data( + fn error_when_insufficient_balance_safe_batch_transfer_from_with_data( contract: Erc1155, ) { let alice = msg::sender(); diff --git a/examples/erc1155-supply/Cargo.toml b/examples/erc1155-supply/Cargo.toml new file mode 100644 index 000000000..ea2bd6384 --- /dev/null +++ b/examples/erc1155-supply/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "erc1155-supply-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 +stylus-proc.workspace = true +mini-alloc.workspace = true + +[dev-dependencies] +alloy.workspace = true +e2e.workspace = true +tokio.workspace = true +eyre.workspace = true +rand.workspace = true + +[features] +e2e = [] + +[lib] +crate-type = ["lib", "cdylib"] diff --git a/examples/erc1155-supply/src/constructor.sol b/examples/erc1155-supply/src/constructor.sol new file mode 100644 index 000000000..57af904f6 --- /dev/null +++ b/examples/erc1155-supply/src/constructor.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +contract Erc1155SupplyExample { + mapping(address => mapping(uint256 => uint256)) private _balanceOf; + mapping(address => mapping(address => bool)) private _isApprovedForAll; + + mapping(uint256 id => uint256) private _totalSupply; + uint256 private _totalSupplyAll; + + constructor() { + _totalSupplyAll = 0; + } +} diff --git a/examples/erc1155-supply/src/lib.rs b/examples/erc1155-supply/src/lib.rs new file mode 100644 index 000000000..0586a3852 --- /dev/null +++ b/examples/erc1155-supply/src/lib.rs @@ -0,0 +1,42 @@ +#![cfg_attr(not(test), no_main, no_std)] +extern crate alloc; + +use alloc::vec::Vec; + +use alloy_primitives::{Address, U256}; +use openzeppelin_stylus::token::erc1155::extensions::supply::Erc1155Supply; +use stylus_sdk::prelude::{entrypoint, public, sol_storage}; + +sol_storage! { + #[entrypoint] + struct Erc1155SupplyExample { + #[borrow] + Erc1155Supply erc1155_supply; + } +} + +#[public] +#[inherit(Erc1155Supply)] +impl Erc1155SupplyExample { + pub fn mint( + &mut self, + to: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), Vec> { + self.erc1155_supply._update(Address::ZERO, to, token_ids, values)?; + Ok(()) + } + + pub fn total_supply(&self, token_id: U256) -> U256 { + self.erc1155_supply.total_supply(token_id) + } + + pub fn total_supply_all(&self) -> U256 { + self.erc1155_supply.total_supply_all() + } + + pub fn exists(&self, token_id: U256) -> bool { + self.erc1155_supply.exists(token_id) + } +} diff --git a/examples/erc1155-supply/tests/abi/mod.rs b/examples/erc1155-supply/tests/abi/mod.rs new file mode 100644 index 000000000..9802f96d4 --- /dev/null +++ b/examples/erc1155-supply/tests/abi/mod.rs @@ -0,0 +1,12 @@ +#![allow(dead_code)] +use alloy::sol; + +sol! { + #[sol(rpc)] + contract Erc1155Supply { + function totalSupply(uint256 id) external view returns (uint256 total_supply); + function totalSupplyAll() external view returns (uint256 total_supply_all); + function exists(uint256 id) external view returns (bool existed); + function mint(address to, uint256[] memory ids, uint256[] memory values) external; + } +} diff --git a/examples/erc1155-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs new file mode 100644 index 000000000..675905e2e --- /dev/null +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -0,0 +1,110 @@ +#![cfg(feature = "e2e")] + +use std::assert_eq; + +use abi::Erc1155Supply; +use alloy::{ + primitives::{uint, Address, U256}, + signers::k256::sha2::digest::typenum::uint, +}; +use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; + +mod abi; + +fn random_token_ids(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() +} + +fn random_values(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() +} + +#[e2e::test] +async fn constructs(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let Erc1155Supply::totalSupplyAllReturn { total_supply_all } = + contract.totalSupplyAll().call().await?; + + assert_eq!(total_supply_all, uint!(0_U256)); + + Ok(()) +} + +#[e2e::test] +async fn supply_of_zero_supply(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let token_ids = random_token_ids(1); + let total_supply = + contract.totalSupply(token_ids[0]).call().await?.total_supply; + let total_supply_all = + contract.totalSupplyAll().call().await?.total_supply_all; + let exists = contract.exists(token_ids[0]).call().await?.existed; + + assert_eq!(total_supply, uint!(0_U256)); + assert_eq!(total_supply_all, uint!(0_U256)); + assert!(!exists); + + Ok(()) +} + +#[e2e::test] +async fn supply_with_zero_address_sender(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let token_ids = random_token_ids(1); + let values = random_values(1); + let _ = watch!(contract.mint( + alice.address(), + token_ids.clone(), + values.clone() + )); + + let total_supply = + contract.totalSupply(token_ids[0]).call().await?.total_supply; + let total_supply_all = + contract.totalSupplyAll().call().await?.total_supply_all; + let exists = contract.exists(token_ids[0]).call().await?.existed; + + assert_eq!(total_supply, values[0]); + assert_eq!(total_supply_all, values[0]); + assert!(exists); + + Ok(()) +} + +#[e2e::test] +async fn supply_batch(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let token_ids = random_token_ids(4); + let values = random_values(4); + let _ = watch!(contract.mint( + alice.address(), + token_ids.clone(), + values.clone() + )); + + for i in 0..4 { + let total_supply = + contract.totalSupply(token_ids[i]).call().await?.total_supply; + assert_eq!(total_supply, values[i]); + } + + for i in 0..4 { + let exists = contract.exists(token_ids[i]).call().await?.existed; + assert!(exists); + } + + let total_supply_all = + contract.totalSupplyAll().call().await?.total_supply_all; + + assert_eq!(total_supply_all, values.iter().sum()); + + Ok(()) +} diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 6007d80f1..49a40ff32 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -1,7 +1,5 @@ #![cfg(feature = "e2e")] -use std::println; - use abi::Erc1155; use alloy::{ primitives::{uint, Address, U256}, @@ -36,7 +34,7 @@ async fn constructs(alice: Account) -> eyre::Result<()> { } #[e2e::test] -async fn test_error_array_length_mismatch( +async fn error_when_array_length_mismatch( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -61,7 +59,7 @@ async fn test_error_array_length_mismatch( } #[e2e::test] -async fn test_balance_of_zero_balance(alice: Account) -> eyre::Result<()> { +async fn balance_of_zero_balance(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); let token_ids = random_token_ids(1); @@ -74,7 +72,7 @@ async fn test_balance_of_zero_balance(alice: Account) -> eyre::Result<()> { } #[e2e::test] -async fn test_balance_of_batch_zero_balance( +async fn balance_of_batch_zero_balance( alice: Account, bob: Account, dave: Account, @@ -97,7 +95,7 @@ async fn test_balance_of_batch_zero_balance( } #[e2e::test] -async fn test_set_approval_for_all( +async fn set_approval_for_all( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -139,7 +137,7 @@ async fn test_set_approval_for_all( } #[e2e::test] -async fn test_error_invalid_operator_when_approval_for_all( +async fn error_when_invalid_operator_approval_for_all( alice: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -158,7 +156,7 @@ async fn test_error_invalid_operator_when_approval_for_all( } #[e2e::test] -async fn is_approved_for_all_invalid_operator( +async fn error_when_invalid_operator_is_approved_for_all_( alice: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -177,7 +175,7 @@ async fn is_approved_for_all_invalid_operator( } #[e2e::test] -async fn test_mints(alice: Account) -> eyre::Result<()> { +async fn mints(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -208,7 +206,7 @@ async fn test_mints(alice: Account) -> eyre::Result<()> { } #[e2e::test] -async fn test_mint_batch( +async fn mint_batch( alice: Account, bob: Account, dave: Account, @@ -289,10 +287,7 @@ async fn test_mint_batch( } #[e2e::test] -async fn test_safe_transfer_from( - alice: Account, - bob: Account, -) -> eyre::Result<()> { +async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -331,7 +326,7 @@ async fn test_safe_transfer_from( } #[e2e::test] -async fn test_error_invalid_receiver_when_safe_transfer_from( +async fn error_when_invalid_receiver_safe_transfer_from( alice: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -360,7 +355,7 @@ async fn test_error_invalid_receiver_when_safe_transfer_from( } #[e2e::test] -async fn test_error_invalid_sender_when_safe_transfer_from( +async fn error_when_invalid_sender_safe_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -396,7 +391,7 @@ async fn test_error_invalid_sender_when_safe_transfer_from( } #[e2e::test] -async fn test_error_missing_approval_when_safe_transfer_from( +async fn error_when_missing_approval_safe_transfer_from( alice: Account, bob: Account, dave: Account, @@ -429,7 +424,7 @@ async fn test_error_missing_approval_when_safe_transfer_from( } #[e2e::test] -async fn test_error_insufficient_balance_when_safe_transfer_from( +async fn error_when_insufficient_balance_safe_transfer_from( alice: Account, bob: Account, dave: Account, @@ -466,7 +461,7 @@ async fn test_error_insufficient_balance_when_safe_transfer_from( } #[e2e::test] -async fn test_safe_batch_transfer_from( +async fn safe_batch_transfer_from( alice: Account, bob: Account, dave: Account, @@ -518,7 +513,7 @@ async fn test_safe_batch_transfer_from( } #[e2e::test] -async fn test_error_invalid_receiver_when_safe_batch_transfer_from( +async fn error_when_invalid_receiver_safe_batch_transfer_from( alice: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -553,7 +548,7 @@ async fn test_error_invalid_receiver_when_safe_batch_transfer_from( } #[e2e::test] -async fn test_error_invalid_sender_when_safe_batch_transfer_from( +async fn error_when_invalid_sender_safe_batch_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -594,7 +589,7 @@ async fn test_error_invalid_sender_when_safe_batch_transfer_from( } #[e2e::test] -async fn test_error_missing_approval_when_safe_batch_transfer_from( +async fn error_when_missing_approval_safe_batch_transfer_from( alice: Account, bob: Account, dave: Account, @@ -633,7 +628,7 @@ async fn test_error_missing_approval_when_safe_batch_transfer_from( } #[e2e::test] -async fn test_error_insufficient_balance_when_safe_batch_transfer_from( +async fn error_when_insufficient_balance_safe_batch_transfer_from( alice: Account, bob: Account, dave: Account, @@ -675,7 +670,7 @@ async fn test_error_insufficient_balance_when_safe_batch_transfer_from( } #[e2e::test] -async fn test_burns(alice: Account, bob: Account) -> eyre::Result<()> { +async fn burns(alice: Account, bob: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -703,7 +698,7 @@ async fn test_burns(alice: Account, bob: Account) -> eyre::Result<()> { } #[e2e::test] -async fn test_error_missing_approval_when_burn( +async fn error_when_missing_approval_burn( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -730,9 +725,7 @@ async fn test_error_missing_approval_when_burn( } #[e2e::test] -async fn test_error_invalid_sender_when_burn( - alice: Account, -) -> eyre::Result<()> { +async fn error_when_invalid_sender_burn(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -762,7 +755,7 @@ async fn test_error_invalid_sender_when_burn( } #[e2e::test] -async fn test_burns_batch(alice: Account, bob: Account) -> eyre::Result<()> { +async fn burns_batch(alice: Account, bob: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -818,7 +811,7 @@ async fn test_burns_batch(alice: Account, bob: Account) -> eyre::Result<()> { } #[e2e::test] -async fn test_error_missing_approval_when_burn_batch( +async fn error_when_missing_approval_burn_batch( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -850,7 +843,7 @@ async fn test_error_missing_approval_when_burn_batch( } #[e2e::test] -async fn test_error_invalid_sender_when_burn_batch( +async fn error_when_invalid_sender_burn_batch( alice: Account, bob: Account, dave: Account, diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 9d4f5f743..f7b9cdeb1 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -11,4 +11,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test erc1155 +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" From 3c52026aa309faee60d8a397f839b1d224c919bd Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Fri, 4 Oct 2024 14:59:16 +0200 Subject: [PATCH 19/60] fix: remove redundat stylus-proc dependency --- Cargo.lock | 2 -- contracts/src/token/erc1155/extensions/supply.rs | 3 +-- examples/erc1155-supply/Cargo.toml | 1 - examples/erc1155/Cargo.toml | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 666e73b77..203122903 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1521,7 +1521,6 @@ dependencies = [ "mini-alloc", "openzeppelin-stylus", "rand", - "stylus-proc", "stylus-sdk", "tokio", ] @@ -1538,7 +1537,6 @@ dependencies = [ "mini-alloc", "openzeppelin-stylus", "rand", - "stylus-proc", "stylus-sdk", "tokio", ] diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 3b31107ea..6d9f50cad 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -13,8 +13,7 @@ use alloc::vec::Vec; use alloy_primitives::{uint, Address, U256}; -use stylus_proc::sol_storage; -use stylus_sdk::prelude::*; +use stylus_sdk::{prelude::*, stylus_proc::sol_storage}; use crate::{ token::erc1155::{Erc1155, Error}, diff --git a/examples/erc1155-supply/Cargo.toml b/examples/erc1155-supply/Cargo.toml index ea2bd6384..8e9c8a4ad 100644 --- a/examples/erc1155-supply/Cargo.toml +++ b/examples/erc1155-supply/Cargo.toml @@ -11,7 +11,6 @@ openzeppelin-stylus.workspace = true alloy-primitives.workspace = true alloy-sol-types.workspace = true stylus-sdk.workspace = true -stylus-proc.workspace = true mini-alloc.workspace = true [dev-dependencies] diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml index fcfe0b196..ce0cb4853 100644 --- a/examples/erc1155/Cargo.toml +++ b/examples/erc1155/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true openzeppelin-stylus.workspace = true alloy-primitives.workspace = true stylus-sdk.workspace = true -stylus-proc.workspace = true mini-alloc.workspace = true [dev-dependencies] From c22de71e4ccf095e2ccb385e62286e1b5ac3bf9d Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Fri, 4 Oct 2024 16:35:02 +0200 Subject: [PATCH 20/60] fix: cargo doc --- contracts/src/token/erc1155/extensions/mod.rs | 1 + contracts/src/token/erc1155/extensions/supply.rs | 7 ++++--- contracts/src/token/erc1155/mod.rs | 4 ++-- docs/modules/ROOT/pages/ERC1155.adoc | 2 +- docs/modules/ROOT/pages/tokens.adoc | 2 +- docs/modules/ROOT/pages/utilities.adoc | 2 +- examples/erc1155-supply/src/lib.rs | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs index 13644ddb2..c296f07cf 100644 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -3,3 +3,4 @@ pub mod burnable; pub mod supply; pub use burnable::IErc1155Burnable; +pub use supply::Erc1155Supply; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 6d9f50cad..1c8bbfcae 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -21,7 +21,7 @@ use crate::{ }; sol_storage! { - /// State of [`crate::token::erc1155::Erc1155`] token's supply. + /// State of [`Erc1155`] token's supply. pub struct Erc1155Supply { /// Erc1155 contract storage. Erc1155 erc1155; @@ -65,8 +65,9 @@ impl Erc1155Supply { /// /// # Events /// - /// Emits a [`TransferSingle`] event if the arrays contain one element, and - /// [`TransferBatch`] otherwise. + /// Emits a [`crate::token::erc1155::TransferSingle`] + /// event if the arrays contain one element, + /// and [`crate::token::erc1155::TransferBatch`] otherwise. pub fn _update( &mut self, from: Address, diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 048c0dc8e..4440377bb 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -652,7 +652,7 @@ impl Erc1155 { } /// Refer to: - /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- + /// /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) /// version of [`Self::_safe_transfer_from`]. /// @@ -740,7 +740,7 @@ impl Erc1155 { } /// Refer to: - /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155-_mintBatch-address-uint256---uint256---bytes- + /// /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) /// version of [`Self::_mint`]. /// diff --git a/docs/modules/ROOT/pages/ERC1155.adoc b/docs/modules/ROOT/pages/ERC1155.adoc index 8ff14a414..d21721959 100644 --- a/docs/modules/ROOT/pages/ERC1155.adoc +++ b/docs/modules/ROOT/pages/ERC1155.adoc @@ -1,3 +1,3 @@ = ERC-1155 -ERC1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract]. \ No newline at end of file +ERC1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract]. diff --git a/docs/modules/ROOT/pages/tokens.adoc b/docs/modules/ROOT/pages/tokens.adoc index e2caae271..97d48b376 100644 --- a/docs/modules/ROOT/pages/tokens.adoc +++ b/docs/modules/ROOT/pages/tokens.adoc @@ -28,4 +28,4 @@ You've probably heard of the ERC-20, ERC-721 or ERC-1155 token standards, and th * xref:erc20.adoc[ERC-20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. * xref:erc721.adoc[ERC-721]: the de-facto solution for non-fungible tokens, often used for collectibles and games. - * xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency. \ No newline at end of file + * xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency. diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 249752b70..35faf4327 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -40,4 +40,4 @@ Some use cases require more powerful data structures than arrays and mappings of Contracts for Stylus provides these libraries for enhanced data structure management: - https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. -- https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. \ No newline at end of file +- https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. diff --git a/examples/erc1155-supply/src/lib.rs b/examples/erc1155-supply/src/lib.rs index 0586a3852..7e28e23e9 100644 --- a/examples/erc1155-supply/src/lib.rs +++ b/examples/erc1155-supply/src/lib.rs @@ -4,7 +4,7 @@ extern crate alloc; use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::token::erc1155::extensions::supply::Erc1155Supply; +use openzeppelin_stylus::token::erc1155::extensions::Erc1155Supply; use stylus_sdk::prelude::{entrypoint, public, sol_storage}; sol_storage! { From 7901af742faf5dbc50f4044dcdfbe99d216b98e4 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 4 Oct 2024 21:10:50 +0400 Subject: [PATCH 21/60] split erc1155 and erc1155-supply --- Cargo.lock | 16 -- Cargo.toml | 2 - contracts/src/token/erc1155/extensions/mod.rs | 3 - .../src/token/erc1155/extensions/supply.rs | 204 ------------------ examples/erc1155-supply/Cargo.toml | 27 --- examples/erc1155-supply/src/constructor.sol | 14 -- examples/erc1155-supply/src/lib.rs | 42 ---- examples/erc1155-supply/tests/abi/mod.rs | 12 -- .../erc1155-supply/tests/erc1155-supply.rs | 110 ---------- 9 files changed, 430 deletions(-) delete mode 100644 contracts/src/token/erc1155/extensions/supply.rs delete mode 100644 examples/erc1155-supply/Cargo.toml delete mode 100644 examples/erc1155-supply/src/constructor.sol delete mode 100644 examples/erc1155-supply/src/lib.rs delete mode 100644 examples/erc1155-supply/tests/abi/mod.rs delete mode 100644 examples/erc1155-supply/tests/erc1155-supply.rs diff --git a/Cargo.lock b/Cargo.lock index 203122903..6858bc233 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1525,22 +1525,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "erc1155-supply-example" -version = "0.1.0-rc" -dependencies = [ - "alloy", - "alloy-primitives", - "alloy-sol-types", - "e2e", - "eyre", - "mini-alloc", - "openzeppelin-stylus", - "rand", - "stylus-sdk", - "tokio", -] - [[package]] name = "erc20-example" version = "0.1.0-rc" diff --git a/Cargo.toml b/Cargo.toml index 1e7ba773f..12f200ad7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ members = [ "examples/erc721-consecutive", "examples/erc721-metadata", "examples/erc1155", - "examples/erc1155-supply", "examples/merkle-proofs", "examples/ownable", "examples/access-control", @@ -35,7 +34,6 @@ default-members = [ "examples/erc721-consecutive", "examples/erc721-metadata", "examples/erc1155", - "examples/erc1155-supply", "examples/merkle-proofs", "examples/ownable", "examples/access-control", diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs index c296f07cf..b68c2b1e8 100644 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -1,6 +1,3 @@ //! Common extensions to the ERC-1155 standard. pub mod burnable; -pub mod supply; - pub use burnable::IErc1155Burnable; -pub use supply::Erc1155Supply; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs deleted file mode 100644 index 1c8bbfcae..000000000 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Extension of ERC-1155 that adds tracking of total supply per id. -//! -//! Useful for scenarios where Fungible and Non-fungible tokens have to be -//! clearly identified. Note: While a totalSupply of 1 might mean the -//! corresponding is an NFT, there is no guarantees that no other token -//! with the same id are not going to be minted. -//! -//! NOTE: This contract implies a global limit of 2**256 - 1 to the number -//! of tokens that can be minted. -//! -//! CAUTION: This extension should not be added in an upgrade to an already -//! deployed contract. -use alloc::vec::Vec; - -use alloy_primitives::{uint, Address, U256}; -use stylus_sdk::{prelude::*, stylus_proc::sol_storage}; - -use crate::{ - token::erc1155::{Erc1155, Error}, - utils::math::storage::SubAssignUnchecked, -}; - -sol_storage! { - /// State of [`Erc1155`] token's supply. - pub struct Erc1155Supply { - /// Erc1155 contract storage. - Erc1155 erc1155; - /// Mapping from token ID to total supply. - mapping(uint256 => uint256) _total_supply; - /// Total supply of all token IDs. - uint256 _total_supply_all; - } -} - -#[public] -impl Erc1155Supply { - /// Total value of tokens in with a given id. - pub fn total_supply(&self, token_id: U256) -> U256 { - self._total_supply.get(token_id) - } - - /// Total value of tokens. - #[selector(name = "totalSupply")] - pub fn total_supply_all(&self) -> U256 { - *self._total_supply_all - } - - /// Indicates whether any token exist with a given id, or not. - pub fn exists(&self, token_id: U256) -> bool { - self.total_supply(token_id) > uint!(0_U256) - } -} - -impl Erc1155Supply { - /// Override of [`Erc1155::_update`] that restricts normal minting to after - /// construction. - /// - /// # Arguments - /// - /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender. - /// * `to` - Account of the recipient. - /// * `token_ids` - Array of all token id. - /// * `values` - Array of all amount of tokens to be supplied. - /// - /// # Events - /// - /// Emits a [`crate::token::erc1155::TransferSingle`] - /// event if the arrays contain one element, - /// and [`crate::token::erc1155::TransferBatch`] otherwise. - pub fn _update( - &mut self, - from: Address, - to: Address, - token_ids: Vec, - values: Vec, - ) -> Result<(), Error> { - self.erc1155._update(from, to, token_ids.clone(), values.clone())?; - - if from.is_zero() { - let mut total_mint_value = uint!(0_U256); - token_ids.iter().zip(values.iter()).for_each( - |(&token_id, &value)| { - let total_supply = - self.total_supply(token_id).checked_add(value).expect( - "should not exceed `U256::MAX` for `_total_supply`", - ); - self._total_supply.setter(token_id).set(total_supply); - total_mint_value += value; - }, - ); - let total_supply_all = - self.total_supply_all().checked_add(total_mint_value).expect( - "should not exceed `U256::MAX` for `_total_supply_all`", - ); - self._total_supply_all.set(total_supply_all); - } - - if to.is_zero() { - let mut total_burn_value = uint!(0_U256); - token_ids.iter().zip(values.iter()).for_each( - |(&token_id, &value)| { - self._total_supply - .setter(token_id) - .sub_assign_unchecked(value); - total_burn_value += value; - }, - ); - let total_supply_all = - self._total_supply_all.get() - total_burn_value; - self._total_supply_all.set(total_supply_all); - } - Ok(()) - } -} - -#[cfg(all(test, feature = "std"))] -mod tests { - use alloy_primitives::{address, Address, U256}; - - use super::Erc1155Supply; - use crate::token::erc1155::IErc1155; - - const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); - const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); - - pub(crate) fn random_token_ids(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() - } - - pub(crate) fn random_values(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() - } - - fn init( - contract: &mut Erc1155Supply, - receiver: Address, - size: usize, - ) -> (Vec, Vec) { - let token_ids = random_token_ids(size); - let values = random_values(size); - - contract - ._update(Address::ZERO, receiver, token_ids.clone(), values.clone()) - .expect("should supply"); - (token_ids, values) - } - - #[motsu::test] - fn supply_of_zero_supply(contract: Erc1155Supply) { - let token_ids = random_token_ids(1); - assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); - assert_eq!(U256::ZERO, contract.total_supply_all()); - assert!(!contract.exists(token_ids[0])); - } - - #[motsu::test] - fn supply_with_zero_address_sender(contract: Erc1155Supply) { - let token_ids = random_token_ids(1); - let values = random_values(1); - contract - ._update(Address::ZERO, ALICE, token_ids.clone(), values.clone()) - .expect("should supply"); - assert_eq!(values[0], contract.total_supply(token_ids[0])); - assert_eq!(values[0], contract.total_supply_all()); - assert!(contract.exists(token_ids[0])); - } - - #[motsu::test] - fn supply_with_zero_address_receiver(contract: Erc1155Supply) { - let (token_ids, values) = init(contract, ALICE, 1); - contract - ._update(ALICE, Address::ZERO, token_ids.clone(), values.clone()) - .expect("should supply"); - assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); - assert_eq!(U256::ZERO, contract.total_supply_all()); - assert!(!contract.exists(token_ids[0])); - } - - #[motsu::test] - fn supply_batch(contract: Erc1155Supply) { - let (token_ids, values) = init(contract, BOB, 4); - assert_eq!( - values[0], - contract.erc1155.balance_of(BOB, token_ids[0]).unwrap() - ); - assert_eq!( - values[1], - contract.erc1155.balance_of(BOB, token_ids[1]).unwrap() - ); - assert_eq!( - values[2], - contract.erc1155.balance_of(BOB, token_ids[2]).unwrap() - ); - assert_eq!( - values[3], - contract.erc1155.balance_of(BOB, token_ids[3]).unwrap() - ); - assert!(contract.exists(token_ids[0])); - assert!(contract.exists(token_ids[1])); - assert!(contract.exists(token_ids[2])); - assert!(contract.exists(token_ids[3])); - } -} diff --git a/examples/erc1155-supply/Cargo.toml b/examples/erc1155-supply/Cargo.toml deleted file mode 100644 index 8e9c8a4ad..000000000 --- a/examples/erc1155-supply/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "erc1155-supply-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 -mini-alloc.workspace = true - -[dev-dependencies] -alloy.workspace = true -e2e.workspace = true -tokio.workspace = true -eyre.workspace = true -rand.workspace = true - -[features] -e2e = [] - -[lib] -crate-type = ["lib", "cdylib"] diff --git a/examples/erc1155-supply/src/constructor.sol b/examples/erc1155-supply/src/constructor.sol deleted file mode 100644 index 57af904f6..000000000 --- a/examples/erc1155-supply/src/constructor.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -contract Erc1155SupplyExample { - mapping(address => mapping(uint256 => uint256)) private _balanceOf; - mapping(address => mapping(address => bool)) private _isApprovedForAll; - - mapping(uint256 id => uint256) private _totalSupply; - uint256 private _totalSupplyAll; - - constructor() { - _totalSupplyAll = 0; - } -} diff --git a/examples/erc1155-supply/src/lib.rs b/examples/erc1155-supply/src/lib.rs deleted file mode 100644 index 7e28e23e9..000000000 --- a/examples/erc1155-supply/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![cfg_attr(not(test), no_main, no_std)] -extern crate alloc; - -use alloc::vec::Vec; - -use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::token::erc1155::extensions::Erc1155Supply; -use stylus_sdk::prelude::{entrypoint, public, sol_storage}; - -sol_storage! { - #[entrypoint] - struct Erc1155SupplyExample { - #[borrow] - Erc1155Supply erc1155_supply; - } -} - -#[public] -#[inherit(Erc1155Supply)] -impl Erc1155SupplyExample { - pub fn mint( - &mut self, - to: Address, - token_ids: Vec, - values: Vec, - ) -> Result<(), Vec> { - self.erc1155_supply._update(Address::ZERO, to, token_ids, values)?; - Ok(()) - } - - pub fn total_supply(&self, token_id: U256) -> U256 { - self.erc1155_supply.total_supply(token_id) - } - - pub fn total_supply_all(&self) -> U256 { - self.erc1155_supply.total_supply_all() - } - - pub fn exists(&self, token_id: U256) -> bool { - self.erc1155_supply.exists(token_id) - } -} diff --git a/examples/erc1155-supply/tests/abi/mod.rs b/examples/erc1155-supply/tests/abi/mod.rs deleted file mode 100644 index 9802f96d4..000000000 --- a/examples/erc1155-supply/tests/abi/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![allow(dead_code)] -use alloy::sol; - -sol! { - #[sol(rpc)] - contract Erc1155Supply { - function totalSupply(uint256 id) external view returns (uint256 total_supply); - function totalSupplyAll() external view returns (uint256 total_supply_all); - function exists(uint256 id) external view returns (bool existed); - function mint(address to, uint256[] memory ids, uint256[] memory values) external; - } -} diff --git a/examples/erc1155-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs deleted file mode 100644 index 675905e2e..000000000 --- a/examples/erc1155-supply/tests/erc1155-supply.rs +++ /dev/null @@ -1,110 +0,0 @@ -#![cfg(feature = "e2e")] - -use std::assert_eq; - -use abi::Erc1155Supply; -use alloy::{ - primitives::{uint, Address, U256}, - signers::k256::sha2::digest::typenum::uint, -}; -use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; - -mod abi; - -fn random_token_ids(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() -} - -fn random_values(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() -} - -#[e2e::test] -async fn constructs(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155Supply::new(contract_addr, &alice.wallet); - - let Erc1155Supply::totalSupplyAllReturn { total_supply_all } = - contract.totalSupplyAll().call().await?; - - assert_eq!(total_supply_all, uint!(0_U256)); - - Ok(()) -} - -#[e2e::test] -async fn supply_of_zero_supply(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155Supply::new(contract_addr, &alice.wallet); - - let token_ids = random_token_ids(1); - let total_supply = - contract.totalSupply(token_ids[0]).call().await?.total_supply; - let total_supply_all = - contract.totalSupplyAll().call().await?.total_supply_all; - let exists = contract.exists(token_ids[0]).call().await?.existed; - - assert_eq!(total_supply, uint!(0_U256)); - assert_eq!(total_supply_all, uint!(0_U256)); - assert!(!exists); - - Ok(()) -} - -#[e2e::test] -async fn supply_with_zero_address_sender(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155Supply::new(contract_addr, &alice.wallet); - - let token_ids = random_token_ids(1); - let values = random_values(1); - let _ = watch!(contract.mint( - alice.address(), - token_ids.clone(), - values.clone() - )); - - let total_supply = - contract.totalSupply(token_ids[0]).call().await?.total_supply; - let total_supply_all = - contract.totalSupplyAll().call().await?.total_supply_all; - let exists = contract.exists(token_ids[0]).call().await?.existed; - - assert_eq!(total_supply, values[0]); - assert_eq!(total_supply_all, values[0]); - assert!(exists); - - Ok(()) -} - -#[e2e::test] -async fn supply_batch(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155Supply::new(contract_addr, &alice.wallet); - - let token_ids = random_token_ids(4); - let values = random_values(4); - let _ = watch!(contract.mint( - alice.address(), - token_ids.clone(), - values.clone() - )); - - for i in 0..4 { - let total_supply = - contract.totalSupply(token_ids[i]).call().await?.total_supply; - assert_eq!(total_supply, values[i]); - } - - for i in 0..4 { - let exists = contract.exists(token_ids[i]).call().await?.existed; - assert!(exists); - } - - let total_supply_all = - contract.totalSupplyAll().call().await?.total_supply_all; - - assert_eq!(total_supply_all, values.iter().sum()); - - Ok(()) -} From 1977335ac44e8b1580e2b45037d10a8dc3f8b1d6 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 4 Oct 2024 21:42:37 +0400 Subject: [PATCH 22/60] split erc1155 and erc1155-burnable --- .../src/token/erc1155/extensions/burnable.rs | 309 ------------------ contracts/src/token/erc1155/extensions/mod.rs | 3 - contracts/src/token/erc1155/mod.rs | 2 - examples/erc1155/src/lib.rs | 26 +- examples/erc1155/tests/abi/mod.rs | 2 - examples/erc1155/tests/erc1155.rs | 214 ------------ 6 files changed, 1 insertion(+), 555 deletions(-) delete mode 100644 contracts/src/token/erc1155/extensions/burnable.rs delete mode 100644 contracts/src/token/erc1155/extensions/mod.rs diff --git a/contracts/src/token/erc1155/extensions/burnable.rs b/contracts/src/token/erc1155/extensions/burnable.rs deleted file mode 100644 index c255ef466..000000000 --- a/contracts/src/token/erc1155/extensions/burnable.rs +++ /dev/null @@ -1,309 +0,0 @@ -//! Optional Burnable extension of the ERC-1155 standard. - -use alloc::vec::Vec; - -use alloy_primitives::{Address, U256}; -use stylus_sdk::msg; - -use crate::token::erc1155::{ - ERC1155MissingApprovalForAll, Erc1155, Error, IErc1155, -}; - -/// Extension of [`Erc1155`] that allows token holders to destroy both their -/// own tokens and those that they have been approved to use. -pub trait IErc1155Burnable { - /// The error type associated to this ERC-1155 burnable trait - /// implementation. - type Error: Into>; - - /// The approval is cleared when the token is burned. Relies on the `_burn` - /// mechanism. - /// - /// # Arguments - /// - /// * `account` - Account to burn tokens from. - /// * `token_id` - Token id to be burnt. - /// * `value` - Amount to be burnt. - /// - /// # Errors - /// - /// If the caller is not account address and the account has not been - /// approved, then the error [`Error::MissingApprovalForAll`] is - /// returned. - /// - /// # Requirements: - /// - /// * `token_id` must exist. - /// * The caller or account must own `token_id` or be an approved operator. - fn burn( - &mut self, - account: Address, - token_id: U256, - value: U256, - ) -> Result<(), Self::Error>; - - /// The approval is cleared when the token is burned. Relies on the - /// `_burn_batch` mechanism. - /// - /// # Arguments - /// - /// * `account` - Accounts to burn tokens from. - /// * `values` - All amount to be burnt. - /// * `token_ids` - All token id to be burnt. - /// - /// # Errors - /// - /// If the caller is not account address and the account has not been - /// approved, then the error [`Error::MissingApprovalForAll`] is - /// returned. - /// - /// # Requirements: - /// - /// * `token_id` must exist. - /// * The caller or account must own `token_id` or be an approved operator. - fn burn_batch( - &mut self, - account: Address, - token_ids: Vec, - values: Vec, - ) -> Result<(), Self::Error>; -} - -impl IErc1155Burnable for Erc1155 { - type Error = Error; - - fn burn( - &mut self, - account: Address, - token_id: U256, - value: U256, - ) -> Result<(), Self::Error> { - let sender = msg::sender(); - if account != sender && !self.is_approved_for_all(account, sender) { - return Err(Error::MissingApprovalForAll( - ERC1155MissingApprovalForAll { - owner: account, - operator: sender, - }, - )); - } - self._burn(account, token_id, value)?; - Ok(()) - } - - fn burn_batch( - &mut self, - account: Address, - token_ids: Vec, - values: Vec, - ) -> Result<(), Self::Error> { - let sender = msg::sender(); - if account != sender && !self.is_approved_for_all(account, sender) { - return Err(Error::MissingApprovalForAll( - ERC1155MissingApprovalForAll { - owner: account, - operator: sender, - }, - )); - } - self._burn_batch(account, token_ids, values)?; - Ok(()) - } -} - -#[cfg(all(test, feature = "std"))] -mod tests { - use alloy_primitives::{address, Address, U256}; - use stylus_sdk::msg; - - use super::IErc1155Burnable; - use crate::token::erc1155::{ - ERC1155InvalidSender, ERC1155MissingApprovalForAll, Erc1155, Error, - IErc1155, - }; - - const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); - - pub(crate) fn random_token_ids(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() - } - - pub(crate) fn random_values(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() - } - - fn init( - contract: &mut Erc1155, - receiver: Address, - size: usize, - ) -> (Vec, Vec) { - let token_ids = random_token_ids(size); - let values = random_values(size); - - contract - ._mint_batch( - receiver, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into(), - ) - .expect("Mint failed"); - (token_ids, values) - } - - #[motsu::test] - fn burns(contract: Erc1155) { - let alice = msg::sender(); - let (token_ids, values) = init(contract, BOB, 1); - - let initial_balance = contract - .balance_of(BOB, token_ids[0]) - .expect("should return the BOB's balance of token 0"); - - assert_eq!(values[0], initial_balance); - - contract._operator_approvals.setter(BOB).setter(alice).set(true); - - contract - .burn(BOB, token_ids[0], values[0]) - .expect("should burn alice's token"); - - let balance = contract - .balance_of(BOB, token_ids[0]) - .expect("should return the BOB's balance of token 0"); - - assert_eq!(U256::ZERO, balance); - } - - #[motsu::test] - fn error_when_missing_approval_burns(contract: Erc1155) { - let alice = msg::sender(); - let (token_ids, values) = init(contract, BOB, 1); - - let err = contract - .burn(BOB, token_ids[0], values[0]) - .expect_err("should not burn tokens without approval"); - - assert!(matches!( - err, - Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { - owner, - operator - }) if owner == BOB && operator == alice - )); - } - - #[motsu::test] - fn error_when_invalid_sender_burns(contract: Erc1155) { - let alice = msg::sender(); - let (token_ids, values) = init(contract, alice, 1); - let invalid_sender = Address::ZERO; - - contract - ._operator_approvals - .setter(invalid_sender) - .setter(alice) - .set(true); - - let err = contract - .burn(invalid_sender, token_ids[0], values[0]) - .expect_err("should not burn tokens from the zero address"); - - assert!(matches!( - err, - Error::InvalidSender(ERC1155InvalidSender { - sender, - }) if sender == invalid_sender - )); - } - - #[motsu::test] - fn burns_batch(contract: Erc1155) { - let alice = msg::sender(); - let (token_ids, values) = init(contract, BOB, 4); - - let initial_balance_0 = contract - .balance_of(BOB, token_ids[0]) - .expect("should return the BOB's balance of token 0"); - let initial_balance_1 = contract - .balance_of(BOB, token_ids[1]) - .expect("should return the BOB's balance of token 1"); - let initial_balance_2 = contract - .balance_of(BOB, token_ids[2]) - .expect("should return the BOB's balance of token 2"); - let initial_balance_3 = contract - .balance_of(BOB, token_ids[3]) - .expect("should return the BOB's balance of token 3"); - - assert_eq!(values[0], initial_balance_0); - assert_eq!(values[1], initial_balance_1); - assert_eq!(values[2], initial_balance_2); - assert_eq!(values[3], initial_balance_3); - - contract._operator_approvals.setter(BOB).setter(alice).set(true); - - contract - .burn_batch(BOB, token_ids.clone(), values.clone()) - .expect("should burn alice's tokens"); - - let balance_0 = contract - .balance_of(BOB, token_ids[0]) - .expect("should return the BOB's balance of token 0"); - let balance_1 = contract - .balance_of(BOB, token_ids[1]) - .expect("should return the BOB's balance of token 1"); - let balance_2 = contract - .balance_of(BOB, token_ids[2]) - .expect("should return the BOB's balance of token 2"); - let balance_3 = contract - .balance_of(BOB, token_ids[3]) - .expect("should return the BOB's balance of token 3"); - - assert_eq!(U256::ZERO, balance_0); - assert_eq!(U256::ZERO, balance_1); - assert_eq!(U256::ZERO, balance_2); - assert_eq!(U256::ZERO, balance_3); - } - - #[motsu::test] - fn error_when_missing_approval_burn_batch(contract: Erc1155) { - let alice = msg::sender(); - let (token_ids, values) = init(contract, BOB, 2); - - let err = contract - .burn_batch(BOB, token_ids.clone(), values.clone()) - .expect_err("should not burn tokens without approval"); - - assert!(matches!( - err, - Error::MissingApprovalForAll(ERC1155MissingApprovalForAll { - owner, - operator - }) if owner == BOB && operator == alice - )); - } - - #[motsu::test] - fn error_when_invalid_sender_burn_batch(contract: Erc1155) { - let alice = msg::sender(); - let (token_ids, values) = init(contract, alice, 5); - let invalid_sender = Address::ZERO; - - contract - ._operator_approvals - .setter(invalid_sender) - .setter(alice) - .set(true); - - let err = contract - .burn_batch(invalid_sender, token_ids.clone(), values.clone()) - .expect_err("should not burn tokens from the zero address"); - - assert!(matches!( - err, - Error::InvalidSender(ERC1155InvalidSender { - sender, - }) if sender == invalid_sender - )); - } -} diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs deleted file mode 100644 index b68c2b1e8..000000000 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Common extensions to the ERC-1155 standard. -pub mod burnable; -pub use burnable::IErc1155Burnable; diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 4440377bb..e33ad539e 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -16,8 +16,6 @@ use crate::utils::{ math::storage::SubAssignUnchecked, }; -pub mod extensions; - sol! { /// Emitted when `value` amount of tokens of type `token_id` are transferred from `from` to `to` by `operator`. #[allow(missing_docs)] diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 879f15ecb..feda0d32e 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; use openzeppelin_stylus::{ - token::erc1155::{extensions::IErc1155Burnable, Erc1155, IErc1155}, + token::erc1155::{Erc1155, IErc1155}, utils::Pausable, }; use stylus_sdk::{ @@ -40,30 +40,6 @@ impl Erc1155Example { Ok(()) } - pub fn burn( - &mut self, - account: Address, - token_id: U256, - value: U256, - ) -> Result<(), Vec> { - self.pausable.when_not_paused()?; - - self.erc1155.burn(account, token_id, value)?; - Ok(()) - } - - pub fn burn_batch( - &mut self, - account: Address, - token_ids: Vec, - values: Vec, - ) -> Result<(), Vec> { - self.pausable.when_not_paused()?; - - self.erc1155.burn_batch(account, token_ids, values)?; - Ok(()) - } - pub fn mint( &mut self, to: Address, diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 5f21af098..3db1472a4 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -14,8 +14,6 @@ sol!( function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; function mint(address to, uint256 id, uint256 amount, bytes memory data) external; function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; - function burn(address account, uint256 id, uint256 value) external; - function burnBatch(address account, uint256[] memory ids, uint256[] memory values) external; function paused() external view returns (bool paused); function pause() external; function unpause() external; diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 49a40ff32..460028ce6 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -668,217 +668,3 @@ async fn error_when_insufficient_balance_safe_batch_transfer_from( Ok(()) } - -#[e2e::test] -async fn burns(alice: Account, bob: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let bob_addr = bob.address(); - let token_ids = random_token_ids(1); - let values = random_values(1); - - let _ = - watch!(contract.mint(bob_addr, token_ids[0], values[0], vec![].into())); - - let initial_balance = - contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; - assert_eq!(values[0], initial_balance); - - let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); - - let receipt = receipt!(contract.burn(bob_addr, token_ids[0], values[0]))?; - - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(bob_addr, token_ids[0]).call().await?; - assert_eq!(uint!(0_U256), balance); - - Ok(()) -} - -#[e2e::test] -async fn error_when_missing_approval_burn( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let bob_addr = bob.address(); - let token_ids = random_token_ids(1); - let values = random_values(1); - - let _ = - watch!(contract.mint(bob_addr, token_ids[0], values[0], vec![].into())); - - let err = send!(contract.burn(bob_addr, token_ids[0], values[0])) - .expect_err("should return `ERC1155MissingApprovalForAll`"); - - assert!(err.reverted_with(Erc1155::ERC1155MissingApprovalForAll { - operator: alice_addr, - owner: bob_addr - })); - - Ok(()) -} - -#[e2e::test] -async fn error_when_invalid_sender_burn(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_ids = random_token_ids(1); - let values = random_values(1); - let invalid_sender = Address::ZERO; - - let _ = watch!(contract.mint( - alice_addr, - token_ids[0], - values[0], - vec![].into() - )); - - let _ = - watch!(contract.setOperatorApprovals(invalid_sender, alice_addr, true)); - - let err = send!(contract.burn(invalid_sender, token_ids[0], values[0])) - .expect_err("should not burn tokens from the zero address"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { - sender: invalid_sender - })); - - Ok(()) -} - -#[e2e::test] -async fn burns_batch(alice: Account, bob: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let bob_addr = bob.address(); - let token_ids = random_token_ids(4); - let values = random_values(4); - - let _ = watch!(contract.mintBatch( - bob_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )); - - let initial_balance_0 = - contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; - let initial_balance_1 = - contract.balanceOf(bob_addr, token_ids[1]).call().await?.balance; - let initial_balance_2 = - contract.balanceOf(bob_addr, token_ids[2]).call().await?.balance; - let initial_balance_3 = - contract.balanceOf(bob_addr, token_ids[3]).call().await?.balance; - - assert_eq!(values[0], initial_balance_0); - assert_eq!(values[1], initial_balance_1); - assert_eq!(values[2], initial_balance_2); - assert_eq!(values[3], initial_balance_3); - - let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); - - let receipt = receipt!(contract.burnBatch( - bob_addr, - token_ids.clone(), - values.clone() - ))?; - - let final_balance_0 = - contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; - let final_balance_1 = - contract.balanceOf(bob_addr, token_ids[1]).call().await?.balance; - let final_balance_2 = - contract.balanceOf(bob_addr, token_ids[2]).call().await?.balance; - let final_balance_3 = - contract.balanceOf(bob_addr, token_ids[3]).call().await?.balance; - - assert_eq!(uint!(0_U256), final_balance_0); - assert_eq!(uint!(0_U256), final_balance_1); - assert_eq!(uint!(0_U256), final_balance_2); - assert_eq!(uint!(0_U256), final_balance_3); - - Ok(()) -} - -#[e2e::test] -async fn error_when_missing_approval_burn_batch( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let bob_addr = bob.address(); - let token_ids = random_token_ids(2); - let values = random_values(2); - - let _ = watch!(contract.mintBatch( - bob_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )); - - let err = - send!(contract.burnBatch(bob_addr, token_ids.clone(), values.clone())) - .expect_err("should return `ERC1155MissingApprovalForAll`"); - - assert!(err.reverted_with(Erc1155::ERC1155MissingApprovalForAll { - operator: alice_addr, - owner: bob_addr - })); - - Ok(()) -} - -#[e2e::test] -async fn error_when_invalid_sender_burn_batch( - alice: Account, - bob: Account, - dave: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let invalid_sender = Address::ZERO; - let bob_addr = bob.address(); - let dave_addr = dave.address(); - let token_ids = random_token_ids(2); - let values = random_values(2); - - let _ = watch!(contract.mintBatch( - bob_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )); - let _ = watch!(contract.setOperatorApprovals( - invalid_sender, - alice.address(), - true - )); - - let err = send!(contract.burnBatch( - invalid_sender, - token_ids.clone(), - values.clone() - )) - .expect_err("should return `ERC1155InvalidSender`"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { - sender: invalid_sender - })); - - Ok(()) -} From b2c7354bd7d59cc54bc171b8e3c4d350308bad0d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 7 Oct 2024 18:23:44 +0400 Subject: [PATCH 23/60] review fixes --- contracts/src/token/erc1155/mod.rs | 262 +++++++++++++---------------- 1 file changed, 121 insertions(+), 141 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index e33ad539e..205f0870b 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -17,13 +17,13 @@ use crate::utils::{ }; sol! { - /// Emitted when `value` amount of tokens of type `token_id` are transferred from `from` to `to` by `operator`. + /// Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. #[allow(missing_docs)] event TransferSingle( address indexed operator, address indexed from, address indexed to, - uint256 token_id, + uint256 id, uint256 value ); @@ -34,7 +34,7 @@ sol! { address indexed operator, address indexed from, address indexed to, - uint256[] token_ids, + uint256[] ids, uint256[] values ); @@ -43,13 +43,13 @@ sol! { #[allow(missing_docs)] event ApprovalForAll(address indexed account, address indexed operator, bool approved); - /// Emitted when the URI for token type `token_id` changes to `value`, if it is a non-programmatic URI. + /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. /// - /// If an [`URI`] event was emitted for `token_id`, the [standard] + /// If an [`URI`] event was emitted for `id`, the [standard] /// (https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees]) that `value` will equal the value /// returned by [`Erc1155UriStorage::uri`]. #[allow(missing_docs)] - event URI(string value, uint256 indexed token_id); + event URI(string value, uint256 indexed id); } sol! { @@ -87,23 +87,23 @@ sol! { #[allow(missing_docs)] error ERC1155MissingApprovalForAll(address operator, address owner); - /// Indicates a failure with the `approver` of a token to be approved. Used - /// in approvals. + /// Indicates a failure with the `approver` of a token to be approved. + /// Used in approvals. /// /// * `approver` - Address initiating an approval operation. #[derive(Debug)] #[allow(missing_docs)] error ERC1155InvalidApprover(address approver); - /// Indicates a failure with the `operator` to be approved. Used - /// in approvals. + /// Indicates a failure with the `operator` to be approved. + /// Used in approvals. /// /// * `operator` - Address that may be allowed to operate on tokens without being their owner. #[derive(Debug)] #[allow(missing_docs)] error ERC1155InvalidOperator(address operator); - /// Indicates an array length mismatch between token_ids and values in a safeBatchTransferFrom operation. + /// Indicates an array length mismatch between token ids and values in a safeBatchTransferFrom operation. /// Used in batch transfers. /// /// * `ids_length` - Length of the array of token identifiers. @@ -136,7 +136,7 @@ pub enum Error { /// Indicates a failure with the `operator` to be approved. Used in /// approvals. InvalidOperator(ERC1155InvalidOperator), - /// Indicates an array length mismatch between token_ids and values in a + /// Indicates an array length mismatch between token ids and values in a /// safeBatchTransferFrom operation. Used in batch transfers. InvalidArrayLength(ERC1155InvalidArrayLength), } @@ -157,17 +157,16 @@ sol_interface! { /// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` /// (i.e. 0xf23a6e61, or its own function selector). /// - /// * `operator` - The address which initiated the transfer (i.e. msg.sender) - /// * `from` - The address which previously owned the token - /// * `token_id` - The ID of the token being transferred - /// * `value` - The amount of tokens being transferred - /// * `data` - Additional data with no specified format - /// Return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + /// * `operator` - The address which initiated the transfer (i.e. msg.sender). + /// * `from` - The address which previously owned the token. + /// * `id` - The ID of the token being transferred. + /// * `value` - The amount of tokens being transferred. + /// * `data` - Additional data with no specified format. #[allow(missing_docs)] function onERC1155Received( address operator, address from, - uint256 token_id, + uint256 id, uint256 value, bytes calldata data ) external returns (bytes4); @@ -182,15 +181,14 @@ sol_interface! { /// /// * `operator` - The address which initiated the batch transfer (i.e. msg.sender) /// * `from` - The address which previously owned the token - /// * `token_ids` - An array containing ids of each token being transferred (order and length must match values array) + /// * `ids` - An array containing ids of each token being transferred (order and length must match values array) /// * `values` - An array containing amounts of each token being transferred (order and length must match ids array) /// * `data` - Additional data with no specified format - /// * Return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed #[allow(missing_docs)] function onERC1155BatchReceived( address operator, address from, - uint256[] calldata token_ids, + uint256[] calldata ids, uint256[] calldata values, bytes calldata data ) external returns (bytes4); @@ -218,45 +216,46 @@ pub trait IErc1155 { /// The error type associated to this ERC-1155 trait implementation. type Error: Into>; - /// Returns the value of tokens of token type `token_id` owned by `account`. + /// Returns the value of tokens of type `id` owned by `account`. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. + /// * `id` - Token id as a number. fn balance_of( &self, account: Address, - token_id: U256, + id: U256, ) -> Result; /// Refer to: /// /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) - /// version of [`Erc1155::balance_of`]. + /// version of [`IErc1155::balance_of`]. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. /// * `accounts` - All account of the tokens' owner. - /// * `token_ids` - All token identifiers. + /// * `ids` - All token identifiers. /// /// Requirements: /// - /// * - `accounts` and `token_ids` must have the same length. + /// * `accounts` and `ids` must have the same length. /// /// # Errors /// - /// * If the length of `accounts` is not equal to the length of `token_ids`, - /// then the error [`Error::InvalidArrayLength`] is returned. + /// * If the length of `accounts` is not equal to the length of `ids`, then + /// the error [`Error::InvalidArrayLength`] is returned. fn balance_of_batch( &self, accounts: Vec
, - token_ids: Vec, + ids: Vec, ) -> Result, Self::Error>; /// Grants or revokes permission to `operator` to transfer the caller's - /// tokens, according to `approved`, + /// tokens, according to `approved`. /// /// # Arguments /// @@ -294,16 +293,9 @@ pub trait IErc1155 { /// * `operator` - Account to be checked. fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; - /// Transfers a `value` amount of tokens of type `token_id` from `from` to + /// Transfers a `value` amount of tokens of type `id` from `from` to /// `to`. /// - /// WARNING: This function can potentially allow a reentrancy attack when - /// transferring tokens to an untrusted contract, when invoking - /// [`IERC1155Receiver::on_erc_1155_received`] on the receiver. Ensure to - /// follow the checks-effects-interactions pattern and consider - /// employing reentrancy guards when interacting with untrusted - /// contracts. - /// /// Emits a [`TransferSingle`] event. /// /// # Errors @@ -322,19 +314,19 @@ pub trait IErc1155 { /// /// # Requirements: /// * - /// * - `to` cannot be the zero address. - /// * - If the caller is not `from`, it must have been approved to spend - /// ``from``'s tokens via [`IErc1155::set_approval_for_all`]. - /// * - `from` must have a balance of tokens of type `token_id` of at least - /// `value` amount. - /// * - If `to` refers to a smart contract, it must implement + /// * `to` cannot be the zero address. + /// * If the caller is not `from`, it must have been approved to spend + /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. + /// * `from` must have a balance of tokens of type `id` of at least `value` + /// amount. + /// * If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_received`] and return the /// acceptance magic value. fn safe_transfer_from( &mut self, from: Address, to: Address, - token_id: U256, + id: U256, value: U256, data: Bytes, ) -> Result<(), Self::Error>; @@ -344,27 +336,20 @@ pub trait IErc1155 { /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) /// version of [`IErc1155::safe_transfer_from`]. /// - /// WARNING: This function can potentially allow a reentrancy attack when - /// transferring tokens to an untrusted contract, when invoking - /// [`IERC1155Receiver::on_erc_1155_batch_received`] on the receiver. Ensure - /// to follow the checks-effects-interactions pattern and consider - /// employing reentrancy guards when interacting with untrusted - /// contracts. - /// /// Emits either a [`TransferSingle`] or a [`TransferBatch`] event, /// depending on the length of the array arguments. /// /// * Requirements: - /// * - /// * - `token_ids` and `values` must have the same length. - /// * - If `to` refers to a smart contract, it must implement + /// + /// * `ids` and `values` must have the same length. + /// * If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the /// acceptance magic value. fn safe_batch_transfer_from( &mut self, from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Self::Error>; @@ -377,26 +362,26 @@ impl IErc1155 for Erc1155 { fn balance_of( &self, account: Address, - token_id: U256, + id: U256, ) -> Result { - Ok(self._balances.get(token_id).get(account)) + Ok(self._balances.get(id).get(account)) } fn balance_of_batch( &self, accounts: Vec
, - token_ids: Vec, + ids: Vec, ) -> Result, Self::Error> { - if accounts.len() != token_ids.len() { + if accounts.len() != ids.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: U256::from(token_ids.len()), + ids_length: U256::from(ids.len()), values_length: U256::from(accounts.len()), })); } let balances: Vec> = accounts .into_iter() - .zip(token_ids.into_iter()) + .zip(ids.into_iter()) .map(|(account, token_id)| { self._balances.get(token_id).get(account) }) @@ -421,7 +406,7 @@ impl IErc1155 for Erc1155 { &mut self, from: Address, to: Address, - token_id: U256, + id: U256, value: U256, data: Bytes, ) -> Result<(), Self::Error> { @@ -431,7 +416,7 @@ impl IErc1155 for Erc1155 { ERC1155MissingApprovalForAll { operator: sender, owner: from }, )); } - self._safe_transfer_from(from, to, token_id, value, data)?; + self._safe_transfer_from(from, to, id, value, data)?; Ok(()) } @@ -439,7 +424,7 @@ impl IErc1155 for Erc1155 { &mut self, from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Self::Error> { @@ -449,7 +434,7 @@ impl IErc1155 for Erc1155 { ERC1155MissingApprovalForAll { operator: sender, owner: from }, )); } - self._safe_batch_transfer_from(from, to, token_ids, values, data)?; + self._safe_batch_transfer_from(from, to, ids, values, data)?; Ok(()) } } @@ -462,20 +447,20 @@ impl IErc165 for Erc1155 { } impl Erc1155 { - /// Transfers a `value` amount of tokens of type `token_ids` from `from` to + /// Transfers a `value` amount of tokens of type `ids` from `from` to /// `to`. Will mint (or burn) if `from` (or `to`) is the zero address. /// /// Requirements: /// - /// * - If `to` refers to a smart contract, it must implement either - /// [`IERC1155Receiver::on_erc_1155_received`] - /// * or [`IERC1155Receiver::on_erc_1155_received`] and return the + /// * If `to` refers to a smart contract, it must implement either + /// [`IERC1155Receiver::on_erc_1155_received`] or + /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the /// acceptance magic value. - /// * - `token_ids` and `values` must have the same length. + /// * `ids` and `values` must have the same length. /// /// # Errors /// - /// If length of `token_ids` is not equal to length of `values`, then the + /// If length of `ids` is not equal to length of `values`, then the /// error [`Error::InvalidArrayLength`] is returned. /// If `value` is greater than the balance of the `from` account, /// then the error [`Error::InsufficientBalance`] is returned. @@ -491,18 +476,18 @@ impl Erc1155 { &mut self, from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, ) -> Result<(), Error> { - if token_ids.len() != values.len() { + if ids.len() != values.len() { return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: U256::from(token_ids.len()), + ids_length: U256::from(ids.len()), values_length: U256::from(values.len()), })); } let operator = msg::sender(); - for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + for (&token_id, &value) in ids.iter().zip(values.iter()) { if !from.is_zero() { let from_balance = self.balance_of(from, token_id)?; if from_balance < value { @@ -532,16 +517,16 @@ impl Erc1155 { } } - if token_ids.len() == 1 { + if ids.len() == 1 { evm::log(TransferSingle { operator, from, to, - token_id: token_ids[0], + id: ids[0], value: values[0], }); } else { - evm::log(TransferBatch { operator, from, to, token_ids, values }); + evm::log(TransferBatch { operator, from, to, ids, values }); } Ok(()) @@ -552,16 +537,11 @@ impl Erc1155 { /// [`IERC1155Receiver::on_erc_1155_received`] on the receiver address if it /// contains code (eg. is a smart contract at the moment of execution). /// - /// IMPORTANT: Overriding this function is discouraged because it poses a - /// reentrancy risk from the receiver. So any update to the contract - /// state after this function would break the check-effect-interaction - /// pattern. Consider overriding [`Self::_update`] instead. - /// /// # Arguments /// /// * `from` - Account of the sender. /// * `to` - Account of the recipient. - /// * `token_ids` - Array of all token id. + /// * `ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -569,36 +549,36 @@ impl Erc1155 { &mut self, from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Error> { - self._update(from, to, token_ids.clone(), values.clone())?; + self._update(from, to, ids.clone(), values.clone())?; if !to.is_zero() { let operator = msg::sender(); - if token_ids.len() == 1 { - let token_id = token_ids[0]; + if ids.len() == 1 { + let token_id = ids[0]; let value = values[0]; self._check_on_erc1155_received( operator, from, to, token_id, value, data, )?; } else { self._check_on_erc1155_batch_received( - operator, from, to, token_ids, values, data, + operator, from, to, ids, values, data, )?; } } Ok(()) } - /// Transfers a `value` tokens of token type `token_id` from `from` to `to`. + /// Transfers a `value` tokens of token type `id` from `from` to `to`. /// /// Requirements: /// - /// - `to` cannot be the zero address. - /// - `from` must have a balance of tokens of type `id` of at least `value` + /// * `to` cannot be the `Address::ZERO`. + /// * `from` must have a balance of tokens of type `id` of at least `value` /// amount. - /// - If `to` refers to a smart contract, it must implement + /// * If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_received`] and return the /// acceptance magic value. /// @@ -607,26 +587,26 @@ impl Erc1155 { /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. - /// * `token_id` - Token id as a number. + /// * `id` - Token id as a number. /// * `value` - Amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. /// /// # Errors /// - /// If `to` is the zero address, then the error [`Error::InvalidReceiver`] - /// is returned. + /// If `to` is the `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. /// If `from` is the zero address, then the error /// [`Error::InvalidSender`] is returned. /// - /// Event + /// # Event /// /// Emits a [`TransferSingle`] event. fn _safe_transfer_from( &mut self, from: Address, to: Address, - token_id: U256, + id: U256, value: U256, data: Bytes, ) -> Result<(), Error> { @@ -643,7 +623,7 @@ impl Erc1155 { self._update_with_acceptance_check( from, to, - vec![token_id], + vec![id], vec![value], data, ) @@ -656,17 +636,17 @@ impl Erc1155 { /// /// Requirements: /// - /// - If `to` refers to a smart contract, it must implement - /// {IERC1155Receiver-onERC1155BatchReceived} and return the + /// * If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the /// acceptance magic value. - /// - `ids` and `values` must have the same length. + /// * `ids` and `values` must have the same length. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. - /// * `token_ids` - Array of all token id. + /// * `ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -685,7 +665,7 @@ impl Erc1155 { &mut self, from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Error> { @@ -699,18 +679,18 @@ impl Erc1155 { sender: from, })); } - self._update_with_acceptance_check(from, to, token_ids, values, data) + self._update_with_acceptance_check(from, to, ids, values, data) } - /// Creates a `value` amount of tokens of type `token_id`, and assigns + /// Creates a `value` amount of tokens of type `id`, and assigns /// them to `to`. /// /// Requirements: /// - /// - `to` cannot be the zero address. - /// - If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_received`] and return the - /// acceptance magic value. + /// * `to` cannot be the `Address::ZERO`. + /// * If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_received`] and return the acceptance + /// magic value. /// /// # Events /// @@ -718,7 +698,7 @@ impl Erc1155 { pub fn _mint( &mut self, to: Address, - token_id: U256, + id: U256, value: U256, data: Bytes, ) -> Result<(), Error> { @@ -730,7 +710,7 @@ impl Erc1155 { self._update_with_acceptance_check( Address::ZERO, to, - vec![token_id], + vec![id], vec![value], data, )?; @@ -744,10 +724,10 @@ impl Erc1155 { /// /// Requirements: /// - /// - `to` cannot be the zero address. - /// - If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_received`] and return the - /// acceptance magic value. + /// * `to` cannot be the `Address::ZERO`. + /// * If `to` refers to a smart contract, it must implement + /// [`IERC1155Receiver::on_erc_1155_received`] and return the acceptance + /// magic value. /// /// # Events /// @@ -755,7 +735,7 @@ impl Erc1155 { pub fn _mint_batch( &mut self, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Error> { @@ -767,7 +747,7 @@ impl Erc1155 { self._update_with_acceptance_check( Address::ZERO, to, - token_ids, + ids, values, data, )?; @@ -787,12 +767,12 @@ impl Erc1155 { /// /// Requirements: /// - /// - `from` cannot be the zero address. - /// - `from` must have at least `value` amount of tokens of type `id`. + /// * `from` cannot be the `Address::ZERO`. + /// * `from` must have at least `value` amount of tokens of type `id`. fn _burn( &mut self, from: Address, - token_id: U256, + id: U256, value: U256, ) -> Result<(), Error> { if from.is_zero() { @@ -803,7 +783,7 @@ impl Erc1155 { self._update_with_acceptance_check( from, Address::ZERO, - vec![token_id], + vec![id], vec![value], vec![].into(), )?; @@ -826,13 +806,13 @@ impl Erc1155 { /// /// Requirements: /// - /// - `from` cannot be the zero address. - /// - `from` must have at least `value` amount of tokens of type `token_id`. - /// - `token_ids` and `values` must have the same length. + /// * `from` cannot be the `Address::ZERO`. + /// * `from` must have at least `value` amount of tokens of type `id`. + /// * `ids` and `values` must have the same length. fn _burn_batch( &mut self, from: Address, - token_ids: Vec, + ids: Vec, values: Vec, ) -> Result<(), Error> { if from.is_zero() { @@ -843,24 +823,24 @@ impl Erc1155 { self._update_with_acceptance_check( from, Address::ZERO, - token_ids, + ids, values, vec![].into(), )?; Ok(()) } - /// Approve `operator` to operate on all of `owner` tokens + /// Approve `operator` to operate on all of `owner` tokens. /// /// Emits an [`ApprovalForAll`] event. /// /// Requirements: /// - /// - `operator` cannot be the zero address. + /// * `operator` cannot be the `Address::ZERO`. /// /// # Errors /// - /// If `operator` is the zero address, then the error + /// If `operator` is the `Address::ZERO`, then the error /// [`Error::InvalidOperator`] is returned. fn _set_approval_for_all( &mut self, @@ -894,7 +874,7 @@ impl Erc1155 { /// * `operator` - Account to add to the set of authorized operators. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. - /// * `token_id` - Token id as a number. + /// * `id` - Token id as a number. /// * `value` - Amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -909,7 +889,7 @@ impl Erc1155 { operator: Address, from: Address, to: Address, - token_id: U256, + id: U256, value: U256, data: Bytes, ) -> Result<(), Error> { @@ -925,12 +905,12 @@ impl Erc1155 { call, operator, from, - token_id, + id, value, data.to_vec().into(), ); - let id = match result { + let response = match result { Ok(id) => id, Err(e) => { if let call::Error::Revert(ref reason) = e { @@ -945,7 +925,7 @@ impl Erc1155 { }; // Token rejected. - if id != RECEIVER_FN_SELECTOR { + if response != RECEIVER_FN_SELECTOR { return Err(ERC1155InvalidReceiver { receiver: to }.into()); } @@ -958,7 +938,7 @@ impl Erc1155 { /// transfer (i.e. `msg.sender`). /// /// The acceptance call is not executed and treated as a no-op if the - /// target address is doesn't contain code (i.e. an EOA). Otherwise, + /// target address doesn't contain code (i.e. an EOA). Otherwise, /// the recipient must implement [`IERC1155Receiver::on_erc_1155_received`] /// and return the acceptance magic value to accept the transfer. /// @@ -968,7 +948,7 @@ impl Erc1155 { /// * `operator` - Account to add to the set of authorized operators. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. - /// * `token_ids` - Array of all token id. + /// * `ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -983,7 +963,7 @@ impl Erc1155 { operator: Address, from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Error> { @@ -999,7 +979,7 @@ impl Erc1155 { call, operator, from, - token_ids, + ids, values, data.to_vec().into(), ); From ef23ba949f5901a75e60291494a9f425acc7dcc5 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 7 Oct 2024 18:50:46 +0400 Subject: [PATCH 24/60] remove pausable --- examples/erc1155/src/constructor.sol | 6 +--- examples/erc1155/src/lib.rs | 42 ++-------------------------- examples/erc1155/tests/abi/mod.rs | 3 -- 3 files changed, 3 insertions(+), 48 deletions(-) diff --git a/examples/erc1155/src/constructor.sol b/examples/erc1155/src/constructor.sol index 5ea6045ae..47304d9fb 100644 --- a/examples/erc1155/src/constructor.sol +++ b/examples/erc1155/src/constructor.sol @@ -5,9 +5,5 @@ contract Erc1155Example { mapping(address => mapping(uint256 => uint256)) private _balanceOf; mapping(address => mapping(address => bool)) private _isApprovedForAll; - bool _paused; - - constructor() { - _paused = false; - } + constructor() {} } diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index feda0d32e..51a94c6ce 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -4,10 +4,7 @@ extern crate alloc; use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::{ - token::erc1155::{Erc1155, IErc1155}, - utils::Pausable, -}; +use openzeppelin_stylus::token::erc1155::{Erc1155, IErc1155}; use stylus_sdk::{ abi::Bytes, prelude::{entrypoint, public, sol_storage}, @@ -18,13 +15,11 @@ sol_storage! { struct Erc1155Example { #[borrow] Erc1155 erc1155; - #[borrow] - Pausable pausable; } } #[public] -#[inherit(Erc1155, Pausable)] +#[inherit(Erc1155)] impl Erc1155Example { pub fn set_operator_approvals( &mut self, @@ -47,8 +42,6 @@ impl Erc1155Example { amount: U256, data: Bytes, ) -> Result<(), Vec> { - self.pausable.when_not_paused()?; - self.erc1155._mint(to, token_id, amount, data)?; Ok(()) } @@ -60,38 +53,7 @@ impl Erc1155Example { amounts: Vec, data: Bytes, ) -> Result<(), Vec> { - self.pausable.when_not_paused()?; - self.erc1155._mint_batch(to, token_ids, amounts, data)?; Ok(()) } - - pub fn safe_transfer_from( - &mut self, - from: Address, - to: Address, - token_id: U256, - amount: U256, - data: Bytes, - ) -> Result<(), Vec> { - self.pausable.when_not_paused()?; - - self.erc1155.safe_transfer_from(from, to, token_id, amount, data)?; - Ok(()) - } - - pub fn safe_batch_transfer_from( - &mut self, - from: Address, - to: Address, - token_ids: Vec, - amounts: Vec, - data: Bytes, - ) -> Result<(), Vec> { - self.pausable.when_not_paused()?; - - self.erc1155 - .safe_batch_transfer_from(from, to, token_ids, amounts, data)?; - Ok(()) - } } diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 3db1472a4..798021868 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -14,9 +14,6 @@ sol!( function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; function mint(address to, uint256 id, uint256 amount, bytes memory data) external; function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; - function paused() external view returns (bool paused); - function pause() external; - function unpause() external; error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); error ERC1155InvalidOperator(address operator); From d3230e494d3bc51cae6781b0ff0a678f42539d8c Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 7 Oct 2024 19:06:12 +0400 Subject: [PATCH 25/60] remove pausable from constructs test --- examples/erc1155/tests/erc1155.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 460028ce6..c3ccaeed0 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -1,10 +1,7 @@ #![cfg(feature = "e2e")] use abi::Erc1155; -use alloy::{ - primitives::{uint, Address, U256}, - signers::k256::sha2::digest::typenum::uint, -}; +use alloy::primitives::{uint, Address, U256}; use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; mod abi; @@ -24,11 +21,7 @@ fn random_values(size: usize) -> Vec { #[e2e::test] async fn constructs(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let Erc1155::pausedReturn { paused } = contract.paused().call().await?; - - assert_eq!(false, paused); + let _contract = Erc1155::new(contract_addr, &alice.wallet); Ok(()) } From f2c2a0fba42ca0fcb1fc5acd748186d8f04794dc Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Tue, 8 Oct 2024 09:07:58 +0200 Subject: [PATCH 26/60] chore: rename in docs --- contracts/src/token/erc1155/mod.rs | 121 ++++++++++++++++------------- examples/erc1155/src/lib.rs | 2 +- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 205f0870b..4f0fc732d 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,4 +1,4 @@ -//! Implementation of the [`Erc1155`] token standard. +//! Implementation of the ERC-1155 token standard. use alloc::{vec, vec::Vec}; use alloy_primitives::{fixed_bytes, Address, FixedBytes, Uint, U256}; @@ -8,7 +8,8 @@ use stylus_sdk::{ alloy_sol_types::sol, call::{self, Call, MethodError}, evm, msg, - prelude::*, + prelude::{public, sol_interface, sol_storage, AddressVM, SolidityError}, + storage::TopLevelStorage, }; use crate::utils::{ @@ -17,7 +18,8 @@ use crate::utils::{ }; sol! { - /// Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. + /// Emitted when `value` amount of tokens of type `id` are + /// transferred from `from` to `to` by `operator`. #[allow(missing_docs)] event TransferSingle( address indexed operator, @@ -38,23 +40,19 @@ sol! { uint256[] values ); - /// Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to - /// `approved`. + /// Emitted when `account` grants or revokes permission to `operator` + /// to transfer their tokens, according to `approved`. #[allow(missing_docs)] - event ApprovalForAll(address indexed account, address indexed operator, bool approved); - - /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. - /// - /// If an [`URI`] event was emitted for `id`, the [standard] - /// (https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees]) that `value` will equal the value - /// returned by [`Erc1155UriStorage::uri`]. - #[allow(missing_docs)] - event URI(string value, uint256 indexed id); + event ApprovalForAll( + address indexed account, + address indexed operator, + bool approved + ); } sol! { - /// Indicates an error related to the current `balance` of a `sender`. Used - /// in transfers. + /// Indicates an error related to the current `balance` of a `sender`. + /// Used in transfers. /// /// * `sender` - Address whose tokens are being transferred. /// * `balance` - Current balance for the interacting account. @@ -62,7 +60,12 @@ sol! { /// * `token_id` - Identifier number of a token. #[derive(Debug)] #[allow(missing_docs)] - error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 token_id); + error ERC1155InsufficientBalance( + address sender, + uint256 balance, + uint256 needed, + uint256 token_id + ); /// Indicates a failure with the token `sender`. Used in transfers. /// @@ -81,7 +84,8 @@ sol! { /// Indicates a failure with the `operator`’s approval. Used /// in transfers. /// - /// * `operator` - Address that may be allowed to operate on tokens without being their owner. + /// * `operator` - Address that may be allowed to operate on tokens + /// without being their owner. /// * `owner` - Address of the current owner of a token. #[derive(Debug)] #[allow(missing_docs)] @@ -98,12 +102,14 @@ sol! { /// Indicates a failure with the `operator` to be approved. /// Used in approvals. /// - /// * `operator` - Address that may be allowed to operate on tokens without being their owner. + /// * `operator` - Address that may be allowed to operate on tokens + /// without being their owner. #[derive(Debug)] #[allow(missing_docs)] error ERC1155InvalidOperator(address operator); - /// Indicates an array length mismatch between token ids and values in a safeBatchTransferFrom operation. + /// Indicates an array length mismatch between token ids and values in a + /// [`IErc1155::safe_batch_transfer_from`] operation. /// Used in batch transfers. /// /// * `ids_length` - Length of the array of token identifiers. @@ -118,8 +124,8 @@ sol! { /// [ERC-6093]: https://eips.ethereum.org/EIPS/eip-6093 #[derive(SolidityError, Debug)] pub enum Error { - /// Indicates an error related to the current `balance` of `sender`. Used - /// in transfers. + /// Indicates an error related to the current `balance` of `sender`. + /// Used in transfers. InsufficientBalance(ERC1155InsufficientBalance), /// Indicates a failure with the token `sender`. Used in transfers. InvalidSender(ERC1155InvalidSender), @@ -130,14 +136,15 @@ pub enum Error { InvalidReceiverWithReason(call::Error), /// Indicates a failure with the `operator`’s approval. Used in transfers. MissingApprovalForAll(ERC1155MissingApprovalForAll), - /// Indicates a failure with the `approver` of a token to be approved. Used - /// in approvals. + /// Indicates a failure with the `approver` of a token to be approved. + /// Used in approvals. InvalidApprover(ERC1155InvalidApprover), /// Indicates a failure with the `operator` to be approved. Used in /// approvals. InvalidOperator(ERC1155InvalidOperator), /// Indicates an array length mismatch between token ids and values in a - /// safeBatchTransferFrom operation. Used in batch transfers. + /// [`Erc1155::safe_batch_transfer_from`] operation. + /// Used in batch transfers. InvalidArrayLength(ERC1155InvalidArrayLength), } @@ -148,16 +155,19 @@ impl MethodError for Error { } sol_interface! { + /// Interface that must be implemented by smart contracts + /// in order to receive ERC-1155 token transfers. interface IERC1155Receiver { - - /// Handles the receipt of a single ERC-1155 token type. This function is - /// called at the end of a `safeTransferFrom` after the balance has been updated. + /// Handles the receipt of a single ERC-1155 token type. + /// This function is called at the end of a + /// [`Erc1155::safe_batch_transfer_from`] + /// after the balance has been updated. /// /// NOTE: To accept the transfer, this must return /// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` /// (i.e. 0xf23a6e61, or its own function selector). /// - /// * `operator` - The address which initiated the transfer (i.e. msg.sender). + /// * `operator` - The address which initiated the transfer. /// * `from` - The address which previously owned the token. /// * `id` - The ID of the token being transferred. /// * `value` - The amount of tokens being transferred. @@ -171,19 +181,22 @@ sol_interface! { bytes calldata data ) external returns (bytes4); - /// Handles the receipt of a multiple ERC-1155 token types. This function - /// is called at the end of a [`Erc1155::safe_batch_transfer_from`] after the balances have + /// Handles the receipt of a multiple ERC-1155 token types. + /// This function is called at the end of a + /// [`Erc1155::safe_batch_transfer_from`] after the balances have /// been updated. /// /// NOTE: To accept the transfer(s), this must return /// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` /// (i.e. 0xbc197c81, or its own function selector). /// - /// * `operator` - The address which initiated the batch transfer (i.e. msg.sender) - /// * `from` - The address which previously owned the token - /// * `ids` - An array containing ids of each token being transferred (order and length must match values array) - /// * `values` - An array containing amounts of each token being transferred (order and length must match ids array) - /// * `data` - Additional data with no specified format + /// * `operator` - The address which initiated the batch transfer. + /// * `from` - The address which previously owned the token. + /// * `ids` - An array containing ids of each token being transferred + /// (order and length must match values array). + /// * `values` - An array containing amounts of each token + /// being transferred (order and length must match ids array). + /// * `data` - Additional data with no specified format. #[allow(missing_docs)] function onERC1155BatchReceived( address operator, @@ -314,7 +327,7 @@ pub trait IErc1155 { /// /// # Requirements: /// * - /// * `to` cannot be the zero address. + /// * `to` cannot be the `Address::ZERO`. /// * If the caller is not `from`, it must have been approved to spend /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. /// * `from` must have a balance of tokens of type `id` of at least `value` @@ -448,7 +461,7 @@ impl IErc165 for Erc1155 { impl Erc1155 { /// Transfers a `value` amount of tokens of type `ids` from `from` to - /// `to`. Will mint (or burn) if `from` (or `to`) is the zero address. + /// `to`. Will mint (or burn) if `from` (or `to`) is the `Address::ZERO`. /// /// Requirements: /// @@ -596,7 +609,7 @@ impl Erc1155 { /// /// If `to` is the `Address::ZERO`, then the error /// [`Error::InvalidReceiver`] is returned. - /// If `from` is the zero address, then the error + /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. /// /// # Event @@ -653,9 +666,9 @@ impl Erc1155 { /// /// # Errors /// - /// If `to` is the zero address, then the error [`Error::InvalidReceiver`] - /// is returned. - /// If `from` is the zero address, then the error + /// If `to` is the `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. /// /// Event @@ -762,8 +775,8 @@ impl Erc1155 { /// /// # Errors /// - /// If `from` is the zero address, then the error [`Error::InvalidSender`] - /// is returned. + /// If `from` is the Address::ZERO, then the error + /// [`Error::InvalidSender`] is returned. /// /// Requirements: /// @@ -801,8 +814,8 @@ impl Erc1155 { /// /// # Errors /// - /// If `from` is the zero address, then the error [`Error::InvalidSender`] - /// is returned. + /// If `from` is the Address::ZERO, then the error + /// [`Error::InvalidSender`] is returned. /// /// Requirements: /// @@ -1274,7 +1287,7 @@ mod tests { values[0], vec![].into(), ) - .expect_err("should not transfer tokens to the zero address"); + .expect_err("should not transfer tokens to the `Address::ZERO`"); assert!(matches!( err, @@ -1304,7 +1317,7 @@ mod tests { values[0], vec![].into(), ) - .expect_err("should not transfer tokens from the zero address"); + .expect_err("should not transfer tokens from the `Address::ZERO`"); assert!(matches!( err, @@ -1404,7 +1417,7 @@ mod tests { values[0], vec![0, 1, 2, 3].into(), ) - .expect_err("should not transfer tokens to the zero address"); + .expect_err("should not transfer tokens to the `Address::ZERO`"); assert!(matches!( err, @@ -1436,7 +1449,7 @@ mod tests { values[0], vec![0, 1, 2, 3].into(), ) - .expect_err("should not transfer tokens from the zero address"); + .expect_err("should not transfer tokens from the `Address::ZERO`"); assert!(matches!( err, @@ -1545,7 +1558,7 @@ mod tests { values.clone(), vec![].into(), ) - .expect_err("should not transfer tokens to the zero address"); + .expect_err("should not transfer tokens to the `Address::ZERO`"); assert!(matches!( err, @@ -1575,7 +1588,7 @@ mod tests { values.clone(), vec![].into(), ) - .expect_err("should not transfer tokens from the zero address"); + .expect_err("should not transfer tokens from the `Address::ZERO`"); assert!(matches!( err, @@ -1682,7 +1695,7 @@ mod tests { values.clone(), vec![0, 1, 2, 3].into(), ) - .expect_err("should not transfer tokens to the zero address"); + .expect_err("should not transfer tokens to the `Address::ZERO`"); assert!(matches!( err, @@ -1714,7 +1727,7 @@ mod tests { values.clone(), vec![0, 1, 2, 3].into(), ) - .expect_err("should not transfer tokens from the zero address"); + .expect_err("should not transfer tokens from the `Address::ZERO`"); assert!(matches!( err, diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 51a94c6ce..9d1007157 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -4,7 +4,7 @@ extern crate alloc; use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::token::erc1155::{Erc1155, IErc1155}; +use openzeppelin_stylus::token::erc1155::Erc1155; use stylus_sdk::{ abi::Bytes, prelude::{entrypoint, public, sol_storage}, From 3e436dfbde1b847b5a0182e3692ee8ac835930d3 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Tue, 8 Oct 2024 09:17:03 +0200 Subject: [PATCH 27/60] docs: remove redundant links --- contracts/src/token/erc1155/mod.rs | 51 +++++++++++------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 4f0fc732d..d6d0cfa15 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -242,10 +242,7 @@ pub trait IErc1155 { id: U256, ) -> Result; - /// Refer to: - /// - /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) - /// version of [`IErc1155::balance_of`]. + /// Batched version of [`IErc1155::balance_of`]. /// /// # Arguments /// @@ -253,7 +250,7 @@ pub trait IErc1155 { /// * `accounts` - All account of the tokens' owner. /// * `ids` - All token identifiers. /// - /// Requirements: + /// # Requirements /// /// * `accounts` and `ids` must have the same length. /// @@ -283,7 +280,7 @@ pub trait IErc1155 { /// * If `operator` is `Address::ZERO`, then the error /// [`Error::InvalidOperator`] is returned. /// - /// # Requirements: + /// # Requirements /// /// * The `operator` cannot be the `Address::ZERO`. /// @@ -325,8 +322,8 @@ pub trait IErc1155 { /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. /// - /// # Requirements: - /// * + /// # Requirements + /// /// * `to` cannot be the `Address::ZERO`. /// * If the caller is not `from`, it must have been approved to spend /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. @@ -344,15 +341,12 @@ pub trait IErc1155 { data: Bytes, ) -> Result<(), Self::Error>; - /// Refer to: - /// - /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) - /// version of [`IErc1155::safe_transfer_from`]. + /// Batched version of [`IErc1155::safe_transfer_from`]. /// /// Emits either a [`TransferSingle`] or a [`TransferBatch`] event, /// depending on the length of the array arguments. /// - /// * Requirements: + /// # Requirements /// /// * `ids` and `values` must have the same length. /// * If `to` refers to a smart contract, it must implement @@ -463,7 +457,7 @@ impl Erc1155 { /// Transfers a `value` amount of tokens of type `ids` from `from` to /// `to`. Will mint (or burn) if `from` (or `to`) is the `Address::ZERO`. /// - /// Requirements: + /// # Requirements /// /// * If `to` refers to a smart contract, it must implement either /// [`IERC1155Receiver::on_erc_1155_received`] or @@ -586,7 +580,7 @@ impl Erc1155 { /// Transfers a `value` tokens of token type `id` from `from` to `to`. /// - /// Requirements: + /// # Requirements /// /// * `to` cannot be the `Address::ZERO`. /// * `from` must have a balance of tokens of type `id` of at least `value` @@ -642,12 +636,9 @@ impl Erc1155 { ) } - /// Refer to: - /// - /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) - /// version of [`Self::_safe_transfer_from`]. + /// Batched version of [`Self::_safe_transfer_from`]. /// - /// Requirements: + /// # Requirements /// /// * If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the @@ -698,7 +689,7 @@ impl Erc1155 { /// Creates a `value` amount of tokens of type `id`, and assigns /// them to `to`. /// - /// Requirements: + /// # Requirements /// /// * `to` cannot be the `Address::ZERO`. /// * If `to` refers to a smart contract, it must implement @@ -730,12 +721,9 @@ impl Erc1155 { Ok(()) } - /// Refer to: - /// - /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) - /// version of [`Self::_mint`]. + /// Batched version of [`Self::_mint`]. /// - /// Requirements: + /// # Requirements /// /// * `to` cannot be the `Address::ZERO`. /// * If `to` refers to a smart contract, it must implement @@ -778,7 +766,7 @@ impl Erc1155 { /// If `from` is the Address::ZERO, then the error /// [`Error::InvalidSender`] is returned. /// - /// Requirements: + /// # Requirements /// /// * `from` cannot be the `Address::ZERO`. /// * `from` must have at least `value` amount of tokens of type `id`. @@ -803,10 +791,7 @@ impl Erc1155 { Ok(()) } - /// Refer to: - /// https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155#ERC1155-_burnBatch-address-uint256---uint256--- - /// [Batched](https://docs.openzeppelin.com/contracts/5.x/erc1155#batch-operations) - /// [`Self::_burn`]. + /// Batched version of [`Self::_burn`]. /// /// # Events /// @@ -817,7 +802,7 @@ impl Erc1155 { /// If `from` is the Address::ZERO, then the error /// [`Error::InvalidSender`] is returned. /// - /// Requirements: + /// # Requirements /// /// * `from` cannot be the `Address::ZERO`. /// * `from` must have at least `value` amount of tokens of type `id`. @@ -847,7 +832,7 @@ impl Erc1155 { /// /// Emits an [`ApprovalForAll`] event. /// - /// Requirements: + /// # Requirements /// /// * `operator` cannot be the `Address::ZERO`. /// From 541345b20b99cc1217db4e5fb05fb97c6b156a71 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Tue, 8 Oct 2024 15:15:14 +0200 Subject: [PATCH 28/60] docs: improve docs for IErc1155 trait --- contracts/src/token/erc1155/mod.rs | 161 ++++++++++++++++------------- 1 file changed, 87 insertions(+), 74 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index d6d0cfa15..ee0666753 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -29,7 +29,7 @@ sol! { uint256 value ); - /// Equivalent to multiple [`TransferSingle`] events, where `operator`. + /// Equivalent to multiple [`TransferSingle`] events, where `operator` /// `from` and `to` are the same for all transfers. #[allow(missing_docs)] event TransferBatch( @@ -67,25 +67,27 @@ sol! { uint256 token_id ); - /// Indicates a failure with the token `sender`. Used in transfers. + /// Indicates a failure with the token `sender`. + /// Used in transfers. /// /// * `sender` - Address whose tokens are being transferred. #[derive(Debug)] #[allow(missing_docs)] error ERC1155InvalidSender(address sender); - /// Indicates a failure with the token `receiver`. Used in transfers. + /// Indicates a failure with the token `receiver`. + /// Used in transfers. /// /// * `receiver` - Address to which tokens are being transferred. #[derive(Debug)] #[allow(missing_docs)] error ERC1155InvalidReceiver(address receiver); - /// Indicates a failure with the `operator`’s approval. Used - /// in transfers. + /// Indicates a failure with the `operator`’s approval. + /// Used in transfers. /// /// * `operator` - Address that may be allowed to operate on tokens - /// without being their owner. + /// without being their owner. /// * `owner` - Address of the current owner of a token. #[derive(Debug)] #[allow(missing_docs)] @@ -160,7 +162,7 @@ sol_interface! { interface IERC1155Receiver { /// Handles the receipt of a single ERC-1155 token type. /// This function is called at the end of a - /// [`Erc1155::safe_batch_transfer_from`] + /// [`IErc1155::safe_batch_transfer_from`] /// after the balance has been updated. /// /// NOTE: To accept the transfer, this must return @@ -183,8 +185,8 @@ sol_interface! { /// Handles the receipt of a multiple ERC-1155 token types. /// This function is called at the end of a - /// [`Erc1155::safe_batch_transfer_from`] after the balances have - /// been updated. + /// [`IErc1155::safe_batch_transfer_from`] + /// after the balances have been updated. /// /// NOTE: To accept the transfer(s), this must return /// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` @@ -193,9 +195,9 @@ sol_interface! { /// * `operator` - The address which initiated the batch transfer. /// * `from` - The address which previously owned the token. /// * `ids` - An array containing ids of each token being transferred - /// (order and length must match values array). + /// (order and length must match values array). /// * `values` - An array containing amounts of each token - /// being transferred (order and length must match ids array). + /// being transferred (order and length must match ids array). /// * `data` - Additional data with no specified format. #[allow(missing_docs)] function onERC1155BatchReceived( @@ -236,11 +238,7 @@ pub trait IErc1155 { /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `id` - Token id as a number. - fn balance_of( - &self, - account: Address, - id: U256, - ) -> Result; + fn balance_of(&self, account: Address, id: U256) -> U256; /// Batched version of [`IErc1155::balance_of`]. /// @@ -256,16 +254,16 @@ pub trait IErc1155 { /// /// # Errors /// - /// * If the length of `accounts` is not equal to the length of `ids`, then - /// the error [`Error::InvalidArrayLength`] is returned. + /// * If the length of `accounts` is not equal to the length of `ids`, + /// then the error [`Error::InvalidArrayLength`] is returned. fn balance_of_batch( &self, accounts: Vec
, ids: Vec, ) -> Result, Self::Error>; - /// Grants or revokes permission to `operator` to transfer the caller's - /// tokens, according to `approved`. + /// Grants or revokes permission to `operator` + /// to transfer the caller's tokens, according to `approved`. /// /// # Arguments /// @@ -273,7 +271,7 @@ pub trait IErc1155 { /// * `operator` - Account to add to the set of authorized operators. /// * `approved` - Flag that determines whether or not permission will be /// granted to `operator`. If true, this means `operator` will be allowed - /// to manage `msg::sender`'s assets. + /// to manage `msg::sender()`'s assets. /// /// # Errors /// @@ -299,14 +297,22 @@ pub trait IErc1155 { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `owner` - Account of the token's owner. + /// * `account` - Account of the token's owner. /// * `operator` - Account to be checked. fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; /// Transfers a `value` amount of tokens of type `id` from `from` to /// `to`. /// - /// Emits a [`TransferSingle`] event. + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `id` - Token id as a number. + /// * `value` - Amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. /// /// # Errors /// @@ -331,7 +337,11 @@ pub trait IErc1155 { /// amount. /// * If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_received`] and return the - /// acceptance magic value. + /// acceptance magic value. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event. fn safe_transfer_from( &mut self, from: Address, @@ -343,15 +353,48 @@ pub trait IErc1155 { /// Batched version of [`IErc1155::safe_transfer_from`]. /// - /// Emits either a [`TransferSingle`] or a [`TransferBatch`] event, - /// depending on the length of the array arguments. + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `ids` - Array of all tokens ids. + /// * `values` - Array of all amount of tokens to be transferred. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If `to` is `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If `from` is `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. + /// If the `from` is not sender, then the error + /// [`Error::MissingApprovalForAll`] is returned. + /// If the caller does not have the right to approve, then the error + /// [`Error::MissingApprovalForAll`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If `ids` length is not equal to `values` length, then the error + /// [`Error::InvalidArrayLength`] /// /// # Requirements /// + /// * `to` cannot be the `Address::ZERO`. + /// * If the caller is not `from`, it must have been approved to spend + /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. + /// * `from` must have a balance of tokens being transferred of at least + /// transferred amount. /// * `ids` and `values` must have the same length. /// * If `to` refers to a smart contract, it must implement /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the - /// acceptance magic value. + /// acceptance magic value. + /// + /// # Events + /// + /// Emits either a [`TransferSingle`] or a [`TransferBatch`] event, + /// depending on the length of the array arguments. fn safe_batch_transfer_from( &mut self, from: Address, @@ -366,12 +409,8 @@ pub trait IErc1155 { impl IErc1155 for Erc1155 { type Error = Error; - fn balance_of( - &self, - account: Address, - id: U256, - ) -> Result { - Ok(self._balances.get(id).get(account)) + fn balance_of(&self, account: Address, id: U256) -> U256 { + self._balances.get(id).get(account) } fn balance_of_batch( @@ -475,7 +514,7 @@ impl Erc1155 { /// NOTE: The ERC-1155 acceptance check is not performed in this function. /// See [`Self::_updateWithAcceptanceCheck`] instead. /// - /// Event + /// # Events /// /// Emits a [`TransferSingle`] event if the arrays contain one element, and /// [`TransferBatch`] otherwise. @@ -496,7 +535,7 @@ impl Erc1155 { let operator = msg::sender(); for (&token_id, &value) in ids.iter().zip(values.iter()) { if !from.is_zero() { - let from_balance = self.balance_of(from, token_id)?; + let from_balance = self.balance_of(from, token_id); if from_balance < value { return Err(Error::InsufficientBalance( ERC1155InsufficientBalance { @@ -1056,9 +1095,7 @@ mod tests { fn balance_of_zero_balance(contract: Erc1155) { let owner = msg::sender(); let token_id = random_token_ids(1)[0]; - let balance = contract - .balance_of(owner, token_id) - .expect("should return `U256::ZERO`"); + let balance = contract.balance_of(owner, token_id); assert_eq!(U256::ZERO, balance); } @@ -1137,9 +1174,7 @@ mod tests { ._mint(alice, token_id, value, vec![0, 1, 2, 3].into()) .expect("should mint tokens for Alice"); - let balance = contract - .balance_of(alice, token_id) - .expect("should return the balance of Alice"); + let balance = contract.balance_of(alice, token_id); assert_eq!(balance, value); } @@ -1159,9 +1194,7 @@ mod tests { ) .expect("should mint tokens for Alice"); token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract - .balance_of(ALICE, token_id) - .expect("should return the balance of Alice"); + let balance = contract.balance_of(ALICE, token_id); assert_eq!(balance, value); }); @@ -1174,9 +1207,7 @@ mod tests { ) .expect("should mint tokens for BOB"); token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract - .balance_of(BOB, token_id) - .expect("should return the balance of BOB"); + let balance = contract.balance_of(BOB, token_id); assert_eq!(balance, value); }); @@ -1189,9 +1220,7 @@ mod tests { ) .expect("should mint tokens for DAVE"); token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract - .balance_of(DAVE, token_id) - .expect("should return the balance of DAVE"); + let balance = contract.balance_of(DAVE, token_id); assert_eq!(balance, value); }); @@ -1204,9 +1233,7 @@ mod tests { ) .expect("should mint tokens for CHARLIE"); token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract - .balance_of(CHARLIE, token_id) - .expect("should return the balance of CHARLIE"); + let balance = contract.balance_of(CHARLIE, token_id); assert_eq!(balance, value); }); @@ -1247,12 +1274,8 @@ mod tests { ) .expect("should transfer tokens from Alice to Bob"); - let balance_id_one = contract - .balance_of(DAVE, token_ids[0]) - .expect("should return Bob's balance of the token 0"); - let balance_id_two = contract - .balance_of(DAVE, token_ids[1]) - .expect("should return Bob's balance of the token 1"); + let balance_id_one = contract.balance_of(DAVE, token_ids[0]); + let balance_id_two = contract.balance_of(DAVE, token_ids[1]); assert_eq!(amount_one, balance_id_one); assert_eq!(amount_two, balance_id_two); @@ -1380,9 +1403,7 @@ mod tests { ) .expect("should transfer tokens from Alice to Bob"); - let balance = contract - .balance_of(CHARLIE, token_ids[0]) - .expect("should return Bob's balance of the token 0"); + let balance = contract.balance_of(CHARLIE, token_ids[0]); assert_eq!(values[0], balance); } @@ -1518,12 +1539,8 @@ mod tests { ) .expect("should transfer tokens from Alice to Bob"); - let balance_id_one = contract - .balance_of(BOB, token_ids[0]) - .expect("should return Bob's balance of the token 0"); - let balance_id_two = contract - .balance_of(BOB, token_ids[1]) - .expect("should return Bob's balance of the token 1"); + let balance_id_one = contract.balance_of(BOB, token_ids[0]); + let balance_id_two = contract.balance_of(BOB, token_ids[1]); assert_eq!(amount_one, balance_id_one); assert_eq!(amount_two, balance_id_two); @@ -1653,12 +1670,8 @@ mod tests { ) .expect("should transfer tokens from Alice to Bob"); - let balance_id_one = contract - .balance_of(BOB, token_ids[0]) - .expect("should return Bob's balance of the token 0"); - let balance_id_two = contract - .balance_of(BOB, token_ids[1]) - .expect("should return Bob's balance of the token 1"); + let balance_id_one = contract.balance_of(BOB, token_ids[0]); + let balance_id_two = contract.balance_of(BOB, token_ids[1]); assert_eq!(values[0], balance_id_one); assert_eq!(values[1], balance_id_two); From 5452245c856ee433d3dd768a46c44c75f6afe3e7 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Tue, 8 Oct 2024 16:45:49 +0200 Subject: [PATCH 29/60] ref: IErc1155 trait impl --- contracts/src/token/erc1155/mod.rs | 96 ++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index ee0666753..730835c6c 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1,7 +1,7 @@ //! Implementation of the ERC-1155 token standard. use alloc::{vec, vec::Vec}; -use alloy_primitives::{fixed_bytes, Address, FixedBytes, Uint, U256}; +use alloy_primitives::{fixed_bytes, Address, FixedBytes, U256}; use openzeppelin_stylus_proc::interface_id; use stylus_sdk::{ abi::Bytes, @@ -320,9 +320,8 @@ pub trait IErc1155 { /// [`Error::InvalidReceiver`] is returned. /// If `from` is `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. - /// If the `from` is not sender, then the error - /// [`Error::MissingApprovalForAll`] is returned. - /// If the caller does not have the right to approve, then the error + /// If the `from` is not the caller (`msg::sender()`), + /// and the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error @@ -369,9 +368,8 @@ pub trait IErc1155 { /// [`Error::InvalidReceiver`] is returned. /// If `from` is `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. - /// If the `from` is not sender, then the error - /// [`Error::MissingApprovalForAll`] is returned. - /// If the caller does not have the right to approve, then the error + /// If the `from` is not the caller (`msg::sender()`), + /// and the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error @@ -418,20 +416,14 @@ impl IErc1155 for Erc1155 { accounts: Vec
, ids: Vec, ) -> Result, Self::Error> { - if accounts.len() != ids.len() { - return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: U256::from(ids.len()), - values_length: U256::from(accounts.len()), - })); - } + Self::require_equal_arrays::(&ids, &accounts)?; - let balances: Vec> = accounts - .into_iter() - .zip(ids.into_iter()) - .map(|(account, token_id)| { - self._balances.get(token_id).get(account) - }) + let balances: Vec = accounts + .iter() + .zip(ids.iter()) + .map(|(account, token_id)| self.balance_of(*account, *token_id)) .collect(); + Ok(balances) } @@ -456,12 +448,7 @@ impl IErc1155 for Erc1155 { value: U256, data: Bytes, ) -> Result<(), Self::Error> { - let sender = msg::sender(); - if from != sender && !self.is_approved_for_all(from, sender) { - return Err(Error::MissingApprovalForAll( - ERC1155MissingApprovalForAll { operator: sender, owner: from }, - )); - } + self.authorize_transfer(from)?; self._safe_transfer_from(from, to, id, value, data)?; Ok(()) } @@ -474,12 +461,7 @@ impl IErc1155 for Erc1155 { values: Vec, data: Bytes, ) -> Result<(), Self::Error> { - let sender = msg::sender(); - if from != sender && !self.is_approved_for_all(from, sender) { - return Err(Error::MissingApprovalForAll( - ERC1155MissingApprovalForAll { operator: sender, owner: from }, - )); - } + self.authorize_transfer(from)?; self._safe_batch_transfer_from(from, to, ids, values, data)?; Ok(()) } @@ -493,6 +475,58 @@ impl IErc165 for Erc1155 { } impl Erc1155 { + /// Checks if `ids` array has same length as `values`. + /// + /// # Arguments + /// + /// * `ids` - array of `ids`. + /// * `values` - array of `values`. + /// + /// # Errors + /// + /// If length of `ids` is not equal to length of `values`, then the error + /// [`Error::InvalidArrayLength`] is returned. + fn require_equal_arrays( + ids: &[T], + values: &[U], + ) -> Result<(), Error> { + if ids.len() != values.len() { + return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length: U256::from(ids.len()), + values_length: U256::from(values.len()), + })); + } + Ok(()) + } + + /// Checks if `sender` is authorized to transfer tokens. + /// + /// # Arguments + /// + /// * `&self` - Write access to the contract's state. + /// * `from` - Tokens' owner. + /// + /// # Errors + /// + /// If the `from` is not the caller (`msg::sender()`), + /// and the caller does not have the right to approve, then the error + /// [`Error::MissingApprovalForAll`] is returned. + /// + /// # Requirements + /// + /// * If the caller is not `from`, it must have been approved to spend + /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. + fn authorize_transfer(&self, from: Address) -> Result<(), Error> { + let sender = msg::sender(); + if from != sender && !self.is_approved_for_all(from, sender) { + return Err(Error::MissingApprovalForAll( + ERC1155MissingApprovalForAll { operator: sender, owner: from }, + )); + } + + Ok(()) + } + /// Transfers a `value` amount of tokens of type `ids` from `from` to /// `to`. Will mint (or burn) if `from` (or `to`) is the `Address::ZERO`. /// From dded81618931cfb2e24887b0fb9db1eece305cfd Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Tue, 8 Oct 2024 19:05:05 +0200 Subject: [PATCH 30/60] ref: re-use common code --- contracts/src/token/erc1155/mod.rs | 610 ++++++++++++++--------------- 1 file changed, 298 insertions(+), 312 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 730835c6c..128c42fbf 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -307,7 +307,7 @@ pub trait IErc1155 { /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender. + /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. /// * `id` - Token id as a number. /// * `value` - Amount of tokens to be transferred. @@ -355,7 +355,7 @@ pub trait IErc1155 { /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender. + /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. /// * `ids` - Array of all tokens ids. /// * `values` - Array of all amount of tokens to be transferred. @@ -371,7 +371,7 @@ pub trait IErc1155 { /// If the `from` is not the caller (`msg::sender()`), /// and the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. - /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. /// If `ids` length is not equal to `values` length, then the error @@ -416,7 +416,7 @@ impl IErc1155 for Erc1155 { accounts: Vec
, ids: Vec, ) -> Result, Self::Error> { - Self::require_equal_arrays::(&ids, &accounts)?; + Self::require_equal_arrays(&ids, &accounts)?; let balances: Vec = accounts .iter() @@ -432,8 +432,7 @@ impl IErc1155 for Erc1155 { operator: Address, approved: bool, ) -> Result<(), Self::Error> { - self._set_approval_for_all(msg::sender(), operator, approved)?; - Ok(()) + self._set_approval_for_all(msg::sender(), operator, approved) } fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { @@ -449,8 +448,7 @@ impl IErc1155 for Erc1155 { data: Bytes, ) -> Result<(), Self::Error> { self.authorize_transfer(from)?; - self._safe_transfer_from(from, to, id, value, data)?; - Ok(()) + self._safe_transfer_from(from, to, id, value, data) } fn safe_batch_transfer_from( @@ -462,8 +460,7 @@ impl IErc1155 for Erc1155 { data: Bytes, ) -> Result<(), Self::Error> { self.authorize_transfer(from)?; - self._safe_batch_transfer_from(from, to, ids, values, data)?; - Ok(()) + self._safe_batch_transfer_from(from, to, ids, values, data) } } @@ -475,68 +472,16 @@ impl IErc165 for Erc1155 { } impl Erc1155 { - /// Checks if `ids` array has same length as `values`. - /// - /// # Arguments - /// - /// * `ids` - array of `ids`. - /// * `values` - array of `values`. - /// - /// # Errors - /// - /// If length of `ids` is not equal to length of `values`, then the error - /// [`Error::InvalidArrayLength`] is returned. - fn require_equal_arrays( - ids: &[T], - values: &[U], - ) -> Result<(), Error> { - if ids.len() != values.len() { - return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: U256::from(ids.len()), - values_length: U256::from(values.len()), - })); - } - Ok(()) - } - - /// Checks if `sender` is authorized to transfer tokens. - /// - /// # Arguments - /// - /// * `&self` - Write access to the contract's state. - /// * `from` - Tokens' owner. - /// - /// # Errors - /// - /// If the `from` is not the caller (`msg::sender()`), - /// and the caller does not have the right to approve, then the error - /// [`Error::MissingApprovalForAll`] is returned. - /// - /// # Requirements - /// - /// * If the caller is not `from`, it must have been approved to spend - /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. - fn authorize_transfer(&self, from: Address) -> Result<(), Error> { - let sender = msg::sender(); - if from != sender && !self.is_approved_for_all(from, sender) { - return Err(Error::MissingApprovalForAll( - ERC1155MissingApprovalForAll { operator: sender, owner: from }, - )); - } - - Ok(()) - } - /// Transfers a `value` amount of tokens of type `ids` from `from` to /// `to`. Will mint (or burn) if `from` (or `to`) is the `Address::ZERO`. /// - /// # Requirements + /// # Arguments /// - /// * If `to` refers to a smart contract, it must implement either - /// [`IERC1155Receiver::on_erc_1155_received`] or - /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the - /// acceptance magic value. - /// * `ids` and `values` must have the same length. + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account to transfer tokens from. + /// * `to` - Account of the recipient. + /// * `ids` - Array of all tokens ids. + /// * `values` - Array of all amount of tokens to be transferred. /// /// # Errors /// @@ -546,7 +491,7 @@ impl Erc1155 { /// then the error [`Error::InsufficientBalance`] is returned. /// /// NOTE: The ERC-1155 acceptance check is not performed in this function. - /// See [`Self::_updateWithAcceptanceCheck`] instead. + /// See [`Self::_update_with_acceptance_check`] instead. /// /// # Events /// @@ -559,52 +504,18 @@ impl Erc1155 { ids: Vec, values: Vec, ) -> Result<(), Error> { - if ids.len() != values.len() { - return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { - ids_length: U256::from(ids.len()), - values_length: U256::from(values.len()), - })); - } + Self::require_equal_arrays(&ids, &values)?; let operator = msg::sender(); - for (&token_id, &value) in ids.iter().zip(values.iter()) { - if !from.is_zero() { - let from_balance = self.balance_of(from, token_id); - if from_balance < value { - return Err(Error::InsufficientBalance( - ERC1155InsufficientBalance { - sender: from, - balance: from_balance, - needed: value, - token_id, - }, - )); - } - self._balances - .setter(token_id) - .setter(from) - .sub_assign_unchecked(value); - } - if !to.is_zero() { - let new_value = self - ._balances - .setter(token_id) - .setter(to) - .checked_add(value) - .expect("should not exceed `U256::MAX` for `_balances`"); - self._balances.setter(token_id).setter(to).set(new_value); - } - } + ids.iter().zip(values.iter()).try_for_each(|(&token_id, &value)| { + self.do_update(from, to, token_id, value) + })?; if ids.len() == 1 { - evm::log(TransferSingle { - operator, - from, - to, - id: ids[0], - value: values[0], - }); + let id = ids[0]; + let value = values[0]; + evm::log(TransferSingle { operator, from, to, id, value }); } else { evm::log(TransferBatch { operator, from, to, ids, values }); } @@ -619,12 +530,31 @@ impl Erc1155 { /// /// # Arguments /// - /// * `from` - Account of the sender. + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. /// * `ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. + /// + /// # Errors + /// + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. fn _update_with_acceptance_check( &mut self, from: Address, @@ -634,38 +564,26 @@ impl Erc1155 { data: Bytes, ) -> Result<(), Error> { self._update(from, to, ids.clone(), values.clone())?; + if !to.is_zero() { - let operator = msg::sender(); - if ids.len() == 1 { - let token_id = ids[0]; - let value = values[0]; - self._check_on_erc1155_received( - operator, from, to, token_id, value, data, - )?; - } else { - self._check_on_erc1155_batch_received( - operator, from, to, ids, values, data, - )?; - } + self.do_check_on_erc1155_received( + msg::sender(), + from, + to, + ReceiverData::new(ids, values), + data.to_vec().into(), + )? } + Ok(()) } /// Transfers a `value` tokens of token type `id` from `from` to `to`. /// - /// # Requirements - /// - /// * `to` cannot be the `Address::ZERO`. - /// * `from` must have a balance of tokens of type `id` of at least `value` - /// amount. - /// * If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_received`] and return the - /// acceptance magic value. - /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender. + /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. /// * `id` - Token id as a number. /// * `value` - Amount of tokens to be transferred. @@ -678,8 +596,11 @@ impl Erc1155 { /// [`Error::InvalidReceiver`] is returned. /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. /// - /// # Event + /// # Events /// /// Emits a [`TransferSingle`] event. fn _safe_transfer_from( @@ -690,38 +611,15 @@ impl Erc1155 { value: U256, data: Bytes, ) -> Result<(), Error> { - if to.is_zero() { - return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { - receiver: to, - })); - } - if from.is_zero() { - return Err(Error::InvalidSender(ERC1155InvalidSender { - sender: from, - })); - } - self._update_with_acceptance_check( - from, - to, - vec![id], - vec![value], - data, - ) + self.do_safe_transfer_from(from, to, vec![id], vec![value], data) } /// Batched version of [`Self::_safe_transfer_from`]. /// - /// # Requirements - /// - /// * If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_batch_received`] and return the - /// acceptance magic value. - /// * `ids` and `values` must have the same length. - /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender. + /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. /// * `ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be transferred. @@ -735,7 +633,7 @@ impl Erc1155 { /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. /// - /// Event + /// # Events /// /// Emits a [`TransferBatch`] event. fn _safe_batch_transfer_from( @@ -746,32 +644,28 @@ impl Erc1155 { values: Vec, data: Bytes, ) -> Result<(), Error> { - if to.is_zero() { - return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { - receiver: to, - })); - } - if from.is_zero() { - return Err(Error::InvalidSender(ERC1155InvalidSender { - sender: from, - })); - } - self._update_with_acceptance_check(from, to, ids, values, data) + self.do_safe_transfer_from(from, to, ids, values, data) } /// Creates a `value` amount of tokens of type `id`, and assigns /// them to `to`. /// - /// # Requirements + /// # Arguments /// - /// * `to` cannot be the `Address::ZERO`. - /// * If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_received`] and return the acceptance - /// magic value. + /// * `&mut self` - Write access to the contract's state. + /// * `to` - Account of the recipient. + /// * `id` - Token id. + /// * `value` - Amount of tokens to be minted. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. /// /// # Events /// /// Emits a [`TransferSingle`] event. + /// + /// # Panics + /// + /// If balance exceeds `U256::MAX`. It may happen during `mint` operation. pub fn _mint( &mut self, to: Address, @@ -779,19 +673,7 @@ impl Erc1155 { value: U256, data: Bytes, ) -> Result<(), Error> { - if to.is_zero() { - return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { - receiver: to, - })); - } - self._update_with_acceptance_check( - Address::ZERO, - to, - vec![id], - vec![value], - data, - )?; - Ok(()) + self.do_mint(to, vec![id], vec![value], data) } /// Batched version of [`Self::_mint`]. @@ -806,6 +688,10 @@ impl Erc1155 { /// # Events /// /// Emits a [`TransferBatch`] event. + /// + /// # Panics + /// + /// If balance exceeds `U256::MAX`. It may happen during `mint` operation. pub fn _mint_batch( &mut self, to: Address, @@ -813,19 +699,7 @@ impl Erc1155 { values: Vec, data: Bytes, ) -> Result<(), Error> { - if to.is_zero() { - return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { - receiver: to, - })); - } - self._update_with_acceptance_check( - Address::ZERO, - to, - ids, - values, - data, - )?; - Ok(()) + self.do_mint(to, ids, values, data) } /// Destroys a `value` amount of tokens of type `id` from `from` @@ -849,19 +723,7 @@ impl Erc1155 { id: U256, value: U256, ) -> Result<(), Error> { - if from.is_zero() { - return Err(Error::InvalidSender(ERC1155InvalidSender { - sender: from, - })); - } - self._update_with_acceptance_check( - from, - Address::ZERO, - vec![id], - vec![value], - vec![].into(), - )?; - Ok(()) + self.do_burn(from, vec![id], vec![value]) } /// Batched version of [`Self::_burn`]. @@ -886,19 +748,7 @@ impl Erc1155 { ids: Vec, values: Vec, ) -> Result<(), Error> { - if from.is_zero() { - return Err(Error::InvalidSender(ERC1155InvalidSender { - sender: from, - })); - } - self._update_with_acceptance_check( - from, - Address::ZERO, - ids, - values, - vec![].into(), - )?; - Ok(()) + self.do_burn(from, ids, values) } /// Approve `operator` to operate on all of `owner` tokens. @@ -928,60 +778,68 @@ impl Erc1155 { evm::log(ApprovalForAll { account: owner, operator, approved }); Ok(()) } +} - /// Performs an acceptance check for the provided `operator` by - /// calling [`IERC1155Receiver::on_erc_1155_received`] on the `to` address. - /// The `operator` is generally the address that initiated the token - /// transfer (i.e. `msg.sender`). - /// - /// The acceptance call is not executed and treated as a no-op if the - /// target address is doesn't contain code (i.e. an EOA). Otherwise, - /// the recipient must implement [`IERC1155Receiver::on_erc_1155_received`] - /// and return the acceptance magic value to accept the transfer. - /// - /// # Arguments - /// - /// * `&mut self` - Write access to the contract's state. - /// * `operator` - Account to add to the set of authorized operators. - /// * `from` - Account of the sender. - /// * `to` - Account of the recipient. - /// * `id` - Token id as a number. - /// * `value` - Amount of tokens to be transferred. - /// * `data` - Additional data with no specified format, sent in call to - /// `to`. - /// - /// # Errors - /// - /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its - /// interface id or returned with error, then the error - /// [`Error::InvalidReceiver`] is returned. - fn _check_on_erc1155_received( +struct ReceiverData { + selector: FixedBytes<4>, + transfer: Transfer, +} + +impl ReceiverData { + fn new(ids: Vec, values: Vec) -> Self { + if ids.len() == 1 { + Self::single(ids[0], values[0]) + } else { + Self::batch(ids, values) + } + } + + fn single(id: U256, value: U256) -> Self { + Self { + selector: fixed_bytes!("f23a6e61"), + transfer: Transfer::Single { id, value }, + } + } + + fn batch(ids: Vec, values: Vec) -> Self { + Self { + selector: fixed_bytes!("bc197c81"), + transfer: Transfer::Batch { ids, values }, + } + } +} + +pub enum Transfer { + Single { id: U256, value: U256 }, + Batch { ids: Vec, values: Vec }, +} + +impl Erc1155 { + fn do_check_on_erc1155_received( &mut self, operator: Address, from: Address, to: Address, - id: U256, - value: U256, - data: Bytes, + details: ReceiverData, + data: alloy_primitives::Bytes, ) -> Result<(), Error> { - const RECEIVER_FN_SELECTOR: FixedBytes<4> = fixed_bytes!("f23a6e61"); - if !to.has_code() { return Ok(()); } let receiver = IERC1155Receiver::new(to); let call = Call::new_in(self); - let result = receiver.on_erc_1155_received( - call, - operator, - from, - id, - value, - data.to_vec().into(), - ); + let result = match details.transfer { + Transfer::Single { id, value } => receiver + .on_erc_1155_received(call, operator, from, id, value, data), + + Transfer::Batch { ids, values } => receiver + .on_erc_1155_batch_received( + call, operator, from, ids, values, data, + ), + }; - let response = match result { + let id = match result { Ok(id) => id, Err(e) => { if let call::Error::Revert(ref reason) = e { @@ -996,28 +854,62 @@ impl Erc1155 { }; // Token rejected. - if response != RECEIVER_FN_SELECTOR { + if id != details.selector { return Err(ERC1155InvalidReceiver { receiver: to }.into()); } Ok(()) } - /// Performs a batch acceptance check for the provided `operator` by - /// calling [`IERC1155Receiver::on_erc_1155_received`] on the `to` address. - /// The `operator` is generally the address that initiated the token - /// transfer (i.e. `msg.sender`). - /// - /// The acceptance call is not executed and treated as a no-op if the - /// target address doesn't contain code (i.e. an EOA). Otherwise, - /// the recipient must implement [`IERC1155Receiver::on_erc_1155_received`] - /// and return the acceptance magic value to accept the transfer. + fn do_mint( + &mut self, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Error> { + if to.is_zero() { + return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver: to, + })); + } + self._update_with_acceptance_check( + Address::ZERO, + to, + ids, + values, + data, + )?; + Ok(()) + } + + fn do_burn( + &mut self, + from: Address, + ids: Vec, + values: Vec, + ) -> Result<(), Error> { + if from.is_zero() { + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); + } + self._update_with_acceptance_check( + from, + Address::ZERO, + ids, + values, + vec![].into(), + )?; + Ok(()) + } + + // TODO /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `operator` - Account to add to the set of authorized operators. - /// * `from` - Account of the sender. + /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. /// * `ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be transferred. @@ -1026,58 +918,152 @@ impl Erc1155 { /// /// # Errors /// + /// If `to` is the `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If `from` is the `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. - fn _check_on_erc1155_batch_received( + /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. + fn do_safe_transfer_from( &mut self, - operator: Address, from: Address, to: Address, ids: Vec, values: Vec, data: Bytes, ) -> Result<(), Error> { - const RECEIVER_FN_SELECTOR: FixedBytes<4> = fixed_bytes!("bc197c81"); + if to.is_zero() { + return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver: to, + })); + } + if from.is_zero() { + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); + } + self._update_with_acceptance_check(from, to, ids, values, data) + } - if !to.has_code() { - return Ok(()); + /// Transfers a `value` amount of `token_id` from `from` to + /// `to`. Will mint (or burn) if `from` (or `to`) is the `Address::ZERO`. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account to transfer tokens from. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id. + /// * `value` - Amount of tokens to be transferred. + /// + /// # Errors + /// + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. + /// + /// + /// # Panics + /// + /// If balance exceeds `U256::MAX`. It may happen during `mint` operation. + fn do_update( + &mut self, + from: Address, + to: Address, + token_id: U256, + value: U256, + ) -> Result<(), Error> { + if !from.is_zero() { + let from_balance = self.balance_of(from, token_id); + if from_balance < value { + return Err(Error::InsufficientBalance( + ERC1155InsufficientBalance { + sender: from, + balance: from_balance, + needed: value, + token_id, + }, + )); + } + self._balances + .setter(token_id) + .setter(from) + .sub_assign_unchecked(value); } - let receiver = IERC1155Receiver::new(to); - let call = Call::new_in(self); - let result = receiver.on_erc_1155_batch_received( - call, - operator, - from, - ids, - values, - data.to_vec().into(), - ); + if !to.is_zero() { + let new_balance = self + ._balances + .setter(token_id) + .setter(to) + .checked_add(value) + .expect("should not exceed `U256::MAX` for `_balances`"); + self._balances.setter(token_id).setter(to).set(new_balance); + } - let id = match result { - Ok(id) => id, - Err(e) => { - if let call::Error::Revert(ref reason) = e { - if reason.len() > 0 { - // Non-IERC1155Receiver implementer. - return Err(Error::InvalidReceiverWithReason(e)); - } - } + Ok(()) + } - return Err(ERC1155InvalidReceiver { receiver: to }.into()); - } - }; + /// Checks if `ids` array has same length as `values`. + /// + /// # Arguments + /// + /// * `ids` - array of `ids`. + /// * `values` - array of `values`. + /// + /// # Errors + /// + /// If length of `ids` is not equal to length of `values`, then the error + /// [`Error::InvalidArrayLength`] is returned. + fn require_equal_arrays( + ids: &[T], + values: &[U], + ) -> Result<(), Error> { + if ids.len() != values.len() { + return Err(Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length: U256::from(ids.len()), + values_length: U256::from(values.len()), + })); + } + Ok(()) + } - // Token rejected. - if id != RECEIVER_FN_SELECTOR { - return Err(ERC1155InvalidReceiver { receiver: to }.into()); + /// Checks if `sender` is authorized to transfer tokens. + /// + /// # Arguments + /// + /// * `&self` - Write access to the contract's state. + /// * `from` - Account to transfer tokens from. + /// + /// # Errors + /// + /// If the `from` is not the caller (`msg::sender()`), + /// and the caller does not have the right to approve, then the error + /// [`Error::MissingApprovalForAll`] is returned. + /// + /// # Requirements + /// + /// * If the caller is not `from`, it must have been approved to spend + /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. + fn authorize_transfer(&self, from: Address) -> Result<(), Error> { + let sender = msg::sender(); + if from != sender && !self.is_approved_for_all(from, sender) { + return Err(Error::MissingApprovalForAll( + ERC1155MissingApprovalForAll { operator: sender, owner: from }, + )); } Ok(()) } } - #[cfg(all(test, feature = "std"))] mod tests { use alloy_primitives::{address, uint, Address, U256}; From dc1fc03deddf73129e5213dc7bf9210cb67ee3e4 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 9 Oct 2024 13:04:43 +0200 Subject: [PATCH 31/60] docs: docs for Erc1155ReceiverData --- contracts/src/token/erc1155/mod.rs | 118 ++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 37 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 128c42fbf..038657bef 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -225,6 +225,84 @@ sol_storage! { /// BorrowMut)`. Should be fixed in the future by the Stylus team. unsafe impl TopLevelStorage for Erc1155 {} +/// Data structure to be passed to contract +/// implementing [`IErc1155Receiver`] interface. +struct Erc1155ReceiverData { + /// Function Selector + fn_selector: FixedBytes<4>, + /// Transfer details, either [`Transfer::Single`] or [`Transfer::Batch`]. + transfer: Transfer, +} + +impl Erc1155ReceiverData { + /// Creates a new instance based on transfer details. + /// + /// If `ids` array has only 1 element, + /// it means that it is a [`Transfer::Single`]. + /// If `ids` array has many elements, + /// it means that it is a [`Transfer::Batch`]. + /// + /// NOTE: Does not check if `ids` length is equal to `values`. + /// + /// # Arguments + /// + /// * `ids` - Array of tokens ids being transferred. + /// * `values` - Array of all amount of tokens being transferred. + fn new(ids: Vec, values: Vec) -> Self { + if ids.len() == 1 { + Self::single(ids[0], values[0]) + } else { + Self::batch(ids, values) + } + } + + /// Creates a new instance for a [`Transfer::Single`]. + /// Check [`IErc1155Receiver::on_erc_1155_received`]. + /// + /// # Arguments + /// + /// * `id` - Token id being transferred. + /// * `value` - Amount of tokens being transferred. + fn single(id: U256, value: U256) -> Self { + Self { + fn_selector: fixed_bytes!("f23a6e61"), + transfer: Transfer::Single { id, value }, + } + } + + /// Creates a new instance for a [`Transfer::Batch`]. + /// Check [`IErc1155Receiver::on_erc_1155_batch_received`]. + /// + /// # Arguments + /// + /// * `ids` - Array of tokens ids being transferred. + /// * `values` - Array of all amount of tokens being transferred. + fn batch(ids: Vec, values: Vec) -> Self { + Self { + fn_selector: fixed_bytes!("bc197c81"), + transfer: Transfer::Batch { ids, values }, + } + } +} + +/// Struct representing token transfer details. +enum Transfer { + /// Transfer of a single token. + /// + /// # Attributes + /// + /// * `id` - Token id being transferred. + /// * `value` - Amount of tokens being transferred. + Single { id: U256, value: U256 }, + /// Batch tokens transfer. + /// + /// # Attributes + /// + /// * `ids` - Array of tokens ids being transferred. + /// * `values` - Array of all amount of tokens being transferred. + Batch { ids: Vec, values: Vec }, +} + /// Required interface of an [`Erc1155`] compliant contract. #[interface_id] pub trait IErc1155 { @@ -570,7 +648,7 @@ impl Erc1155 { msg::sender(), from, to, - ReceiverData::new(ids, values), + Erc1155ReceiverData::new(ids, values), data.to_vec().into(), )? } @@ -780,47 +858,13 @@ impl Erc1155 { } } -struct ReceiverData { - selector: FixedBytes<4>, - transfer: Transfer, -} - -impl ReceiverData { - fn new(ids: Vec, values: Vec) -> Self { - if ids.len() == 1 { - Self::single(ids[0], values[0]) - } else { - Self::batch(ids, values) - } - } - - fn single(id: U256, value: U256) -> Self { - Self { - selector: fixed_bytes!("f23a6e61"), - transfer: Transfer::Single { id, value }, - } - } - - fn batch(ids: Vec, values: Vec) -> Self { - Self { - selector: fixed_bytes!("bc197c81"), - transfer: Transfer::Batch { ids, values }, - } - } -} - -pub enum Transfer { - Single { id: U256, value: U256 }, - Batch { ids: Vec, values: Vec }, -} - impl Erc1155 { fn do_check_on_erc1155_received( &mut self, operator: Address, from: Address, to: Address, - details: ReceiverData, + details: Erc1155ReceiverData, data: alloy_primitives::Bytes, ) -> Result<(), Error> { if !to.has_code() { @@ -854,7 +898,7 @@ impl Erc1155 { }; // Token rejected. - if id != details.selector { + if id != details.fn_selector { return Err(ERC1155InvalidReceiver { receiver: to }.into()); } From 1674f63e72f853b3e6d3a60c9fb0a68a76c76e06 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 9 Oct 2024 13:34:15 +0200 Subject: [PATCH 32/60] test: add unit tests for Erc1155ReceiverData --- contracts/src/token/erc1155/mod.rs | 54 ++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 038657bef..243a9d9e0 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -17,6 +17,18 @@ use crate::utils::{ math::storage::SubAssignUnchecked, }; +/// `bytes4( +/// keccak256( +/// "onERC1155Received(address,address,uint256,uint256,bytes)" +/// ))` +const SINGLE_TRANSFER_FN_SELECTOR: FixedBytes<4> = fixed_bytes!("f23a6e61"); + +/// `bytes4( +/// keccak256( +/// "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)" +/// ))` +const BATCH_TRANSFER_FN_SELECTOR: FixedBytes<4> = fixed_bytes!("bc197c81"); + sol! { /// Emitted when `value` amount of tokens of type `id` are /// transferred from `from` to `to` by `operator`. @@ -165,9 +177,9 @@ sol_interface! { /// [`IErc1155::safe_batch_transfer_from`] /// after the balance has been updated. /// - /// NOTE: To accept the transfer, this must return - /// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` - /// (i.e. 0xf23a6e61, or its own function selector). + /// NOTE: To accept the transfer, + /// this must return [`SINGLE_TRANSFER_FN_SELECTOR`], + /// or its own function selector. /// /// * `operator` - The address which initiated the transfer. /// * `from` - The address which previously owned the token. @@ -188,9 +200,9 @@ sol_interface! { /// [`IErc1155::safe_batch_transfer_from`] /// after the balances have been updated. /// - /// NOTE: To accept the transfer(s), this must return - /// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` - /// (i.e. 0xbc197c81, or its own function selector). + /// NOTE: To accept the transfer(s), + /// this must return [`BATCH_TRANSFER_FN_SELECTOR`], + /// or its own function selector. /// /// * `operator` - The address which initiated the batch transfer. /// * `from` - The address which previously owned the token. @@ -236,6 +248,7 @@ struct Erc1155ReceiverData { impl Erc1155ReceiverData { /// Creates a new instance based on transfer details. + /// Assumes that `ids` is not empty. /// /// If `ids` array has only 1 element, /// it means that it is a [`Transfer::Single`]. @@ -265,7 +278,7 @@ impl Erc1155ReceiverData { /// * `value` - Amount of tokens being transferred. fn single(id: U256, value: U256) -> Self { Self { - fn_selector: fixed_bytes!("f23a6e61"), + fn_selector: SINGLE_TRANSFER_FN_SELECTOR, transfer: Transfer::Single { id, value }, } } @@ -279,13 +292,14 @@ impl Erc1155ReceiverData { /// * `values` - Array of all amount of tokens being transferred. fn batch(ids: Vec, values: Vec) -> Self { Self { - fn_selector: fixed_bytes!("bc197c81"), + fn_selector: BATCH_TRANSFER_FN_SELECTOR, transfer: Transfer::Batch { ids, values }, } } } /// Struct representing token transfer details. +#[derive(Debug, PartialEq)] enum Transfer { /// Transfer of a single token. /// @@ -1116,7 +1130,9 @@ mod tests { use super::{ ERC1155InsufficientBalance, ERC1155InvalidArrayLength, ERC1155InvalidOperator, ERC1155InvalidReceiver, ERC1155InvalidSender, - ERC1155MissingApprovalForAll, Erc1155, Error, IErc1155, + ERC1155MissingApprovalForAll, Erc1155, Erc1155ReceiverData, Error, + IErc1155, Transfer, BATCH_TRANSFER_FN_SELECTOR, + SINGLE_TRANSFER_FN_SELECTOR, }; use crate::{ token::erc721::IErc721, utils::introspection::erc165::IErc165, @@ -1129,7 +1145,7 @@ mod tests { address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); pub(crate) fn random_token_ids(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() + (0..size).map(U256::from).collect() } pub(crate) fn random_values(size: usize) -> Vec { @@ -1155,6 +1171,24 @@ mod tests { (token_ids, values) } + #[test] + fn should_create_transfer_single() { + let id = uint!(1_U256); + let value = uint!(10_U256); + let details = Erc1155ReceiverData::new(vec![id], vec![value]); + assert_eq!(SINGLE_TRANSFER_FN_SELECTOR, details.fn_selector); + assert_eq!(Transfer::Single { id, value }, details.transfer); + } + + #[test] + fn should_create_transfer_batch() { + let ids = random_token_ids(5); + let values = random_values(5); + let details = Erc1155ReceiverData::new(ids.clone(), values.clone()); + assert_eq!(BATCH_TRANSFER_FN_SELECTOR, details.fn_selector); + assert_eq!(Transfer::Batch { ids, values }, details.transfer); + } + #[motsu::test] fn balance_of_zero_balance(contract: Erc1155) { let owner = msg::sender(); From 090244ade0de491c1c5930609043cd2c770b073a Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 9 Oct 2024 18:49:32 +0200 Subject: [PATCH 33/60] docs: update code docs for ERC-1155 token --- contracts/src/token/erc1155/mod.rs | 358 ++++++++++++++++++----------- 1 file changed, 229 insertions(+), 129 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 243a9d9e0..fe476f0e5 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -238,10 +238,10 @@ sol_storage! { unsafe impl TopLevelStorage for Erc1155 {} /// Data structure to be passed to contract -/// implementing [`IErc1155Receiver`] interface. +/// implementing [`IERC1155Receiver`] interface. struct Erc1155ReceiverData { - /// Function Selector - fn_selector: FixedBytes<4>, + /// ERC-1155 Receiver function selector. + receiver_fn_selector: FixedBytes<4>, /// Transfer details, either [`Transfer::Single`] or [`Transfer::Batch`]. transfer: Transfer, } @@ -270,7 +270,7 @@ impl Erc1155ReceiverData { } /// Creates a new instance for a [`Transfer::Single`]. - /// Check [`IErc1155Receiver::on_erc_1155_received`]. + /// Check [`IERC1155Receiver::on_erc_1155_received`]. /// /// # Arguments /// @@ -278,13 +278,13 @@ impl Erc1155ReceiverData { /// * `value` - Amount of tokens being transferred. fn single(id: U256, value: U256) -> Self { Self { - fn_selector: SINGLE_TRANSFER_FN_SELECTOR, + receiver_fn_selector: SINGLE_TRANSFER_FN_SELECTOR, transfer: Transfer::Single { id, value }, } } /// Creates a new instance for a [`Transfer::Batch`]. - /// Check [`IErc1155Receiver::on_erc_1155_batch_received`]. + /// Check [`IERC1155Receiver::on_erc_1155_batch_received`]. /// /// # Arguments /// @@ -292,7 +292,7 @@ impl Erc1155ReceiverData { /// * `values` - Array of all amount of tokens being transferred. fn batch(ids: Vec, values: Vec) -> Self { Self { - fn_selector: BATCH_TRANSFER_FN_SELECTOR, + receiver_fn_selector: BATCH_TRANSFER_FN_SELECTOR, transfer: Transfer::Batch { ids, values }, } } @@ -415,6 +415,8 @@ pub trait IErc1155 { /// If the `from` is not the caller (`msg::sender()`), /// and the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. @@ -427,12 +429,16 @@ pub trait IErc1155 { /// * `from` must have a balance of tokens of type `id` of at least `value` /// amount. /// * If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_received`] and return the - /// acceptance magic value. + /// [`IERC1155Receiver::on_erc_1155_received`] and return the acceptance + /// value. /// /// # Events /// /// Emits a [`TransferSingle`] event. + /// + /// # Panics + /// + /// Should not panic. fn safe_transfer_from( &mut self, from: Address, @@ -460,14 +466,16 @@ pub trait IErc1155 { /// [`Error::InvalidReceiver`] is returned. /// If `from` is `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. /// If the `from` is not the caller (`msg::sender()`), /// and the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. - /// If `ids` length is not equal to `values` length, then the error - /// [`Error::InvalidArrayLength`] /// /// # Requirements /// @@ -485,6 +493,10 @@ pub trait IErc1155 { /// /// Emits either a [`TransferSingle`] or a [`TransferBatch`] event, /// depending on the length of the array arguments. + /// + /// # Panics + /// + /// Should not panic. fn safe_batch_transfer_from( &mut self, from: Address, @@ -540,7 +552,7 @@ impl IErc1155 for Erc1155 { data: Bytes, ) -> Result<(), Self::Error> { self.authorize_transfer(from)?; - self._safe_transfer_from(from, to, id, value, data) + self.do_safe_transfer_from(from, to, vec![id], vec![value], data) } fn safe_batch_transfer_from( @@ -552,7 +564,7 @@ impl IErc1155 for Erc1155 { data: Bytes, ) -> Result<(), Self::Error> { self.authorize_transfer(from)?; - self._safe_batch_transfer_from(from, to, ids, values, data) + self.do_safe_transfer_from(from, to, ids, values, data) } } @@ -589,6 +601,11 @@ impl Erc1155 { /// /// Emits a [`TransferSingle`] event if the arrays contain one element, and /// [`TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`, may happen during `mint` + /// operation. fn _update( &mut self, from: Address, @@ -617,8 +634,8 @@ impl Erc1155 { /// Version of [`Self::_update`] that performs the token acceptance check by /// calling [`IERC1155Receiver::on_erc_1155_received`] or - /// [`IERC1155Receiver::on_erc_1155_received`] on the receiver address if it - /// contains code (eg. is a smart contract at the moment of execution). + /// [`IERC1155Receiver::on_erc_1155_batch_received`] on the receiver address + /// if it contains code. /// /// # Arguments /// @@ -647,6 +664,11 @@ impl Erc1155 { /// /// Emits a [`TransferSingle`] event if the arrays contain one element, and /// [`TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`, may happen during `mint` + /// operation. fn _update_with_acceptance_check( &mut self, from: Address, @@ -658,7 +680,7 @@ impl Erc1155 { self._update(from, to, ids.clone(), values.clone())?; if !to.is_zero() { - self.do_check_on_erc1155_received( + self._check_on_erc1155_received( msg::sender(), from, to, @@ -670,24 +692,22 @@ impl Erc1155 { Ok(()) } - /// Transfers a `value` tokens of token type `id` from `from` to `to`. + /// Creates a `value` amount of tokens of type `id`, and assigns + /// them to `to`. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. - /// * `id` - Token id as a number. - /// * `value` - Amount of tokens to be transferred. + /// * `id` - Token id. + /// * `value` - Amount of tokens to be minted. /// * `data` - Additional data with no specified format, sent in call to /// `to`. /// /// # Errors /// - /// If `to` is the `Address::ZERO`, then the error + /// If `to` is `Address::ZERO`, then the error /// [`Error::InvalidReceiver`] is returned. - /// If `from` is the `Address::ZERO`, then the error - /// [`Error::InvalidSender`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. @@ -695,95 +715,52 @@ impl Erc1155 { /// # Events /// /// Emits a [`TransferSingle`] event. - fn _safe_transfer_from( + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + pub fn _mint( &mut self, - from: Address, to: Address, id: U256, value: U256, data: Bytes, ) -> Result<(), Error> { - self.do_safe_transfer_from(from, to, vec![id], vec![value], data) + self._do_mint(to, vec![id], vec![value], data) } - /// Batched version of [`Self::_safe_transfer_from`]. + /// Batched version of [`Self::_mint`]. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. - /// * `ids` - Array of all token id. - /// * `values` - Array of all amount of tokens to be transferred. + /// * `ids` - Array of all tokens ids to be minted. + /// * `values` - Array of all amounts of tokens to be minted. /// * `data` - Additional data with no specified format, sent in call to /// `to`. /// /// # Errors /// - /// If `to` is the `Address::ZERO`, then the error + /// If `to` is `Address::ZERO`, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its + /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. - /// If `from` is the `Address::ZERO`, then the error - /// [`Error::InvalidSender`] is returned. - /// - /// # Events - /// - /// Emits a [`TransferBatch`] event. - fn _safe_batch_transfer_from( - &mut self, - from: Address, - to: Address, - ids: Vec, - values: Vec, - data: Bytes, - ) -> Result<(), Error> { - self.do_safe_transfer_from(from, to, ids, values, data) - } - - /// Creates a `value` amount of tokens of type `id`, and assigns - /// them to `to`. - /// - /// # Arguments - /// - /// * `&mut self` - Write access to the contract's state. - /// * `to` - Account of the recipient. - /// * `id` - Token id. - /// * `value` - Amount of tokens to be minted. - /// * `data` - Additional data with no specified format, sent in call to - /// `to`. - /// - /// # Events - /// - /// Emits a [`TransferSingle`] event. - /// - /// # Panics - /// - /// If balance exceeds `U256::MAX`. It may happen during `mint` operation. - pub fn _mint( - &mut self, - to: Address, - id: U256, - value: U256, - data: Bytes, - ) -> Result<(), Error> { - self.do_mint(to, vec![id], vec![value], data) - } - - /// Batched version of [`Self::_mint`]. - /// - /// # Requirements - /// - /// * `to` cannot be the `Address::ZERO`. - /// * If `to` refers to a smart contract, it must implement - /// [`IERC1155Receiver::on_erc_1155_received`] and return the acceptance - /// magic value. /// /// # Events /// - /// Emits a [`TransferBatch`] event. + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. /// /// # Panics /// - /// If balance exceeds `U256::MAX`. It may happen during `mint` operation. + /// If updated balance exceeds `U256::MAX`. pub fn _mint_batch( &mut self, to: Address, @@ -791,70 +768,95 @@ impl Erc1155 { values: Vec, data: Bytes, ) -> Result<(), Error> { - self.do_mint(to, ids, values, data) + self._do_mint(to, ids, values, data) } - /// Destroys a `value` amount of tokens of type `id` from `from` + /// Destroys a `value` amount of tokens of type `id` from `from`. /// - /// # Events + /// # Arguments /// - /// Emits a [`TransferSingle`] event. + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account to burn tokens from. + /// * `id` - Token id to be burnt. + /// * `value` - Amount of tokens to be burnt. /// /// # Errors /// - /// If `from` is the Address::ZERO, then the error + /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. /// - /// # Requirements + /// # Events + /// + /// Emits a [`TransferSingle`] event. /// - /// * `from` cannot be the `Address::ZERO`. - /// * `from` must have at least `value` amount of tokens of type `id`. + /// # Panics + /// + /// Should not panic. fn _burn( &mut self, from: Address, id: U256, value: U256, ) -> Result<(), Error> { - self.do_burn(from, vec![id], vec![value]) + self._do_burn(from, vec![id], vec![value]) } /// Batched version of [`Self::_burn`]. /// - /// # Events + /// # Arguments /// - /// Emits a [`TransferSingle`] event. + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account to burn tokens from. + /// * `ids` - Array of all tokens ids to be burnt. + /// * `values` - Array of all amounts of tokens to be burnt. /// /// # Errors /// - /// If `from` is the Address::ZERO, then the error + /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. /// - /// # Requirements + /// # Events /// - /// * `from` cannot be the `Address::ZERO`. - /// * `from` must have at least `value` amount of tokens of type `id`. - /// * `ids` and `values` must have the same length. + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. + /// + /// # Panics + /// + /// Should not panic. fn _burn_batch( &mut self, from: Address, ids: Vec, values: Vec, ) -> Result<(), Error> { - self.do_burn(from, ids, values) + self._do_burn(from, ids, values) } /// Approve `operator` to operate on all of `owner` tokens. /// - /// Emits an [`ApprovalForAll`] event. - /// - /// # Requirements + /// # Arguments /// - /// * `operator` cannot be the `Address::ZERO`. + /// * `&mut self` - Write access to the contract's state. + /// * `owner` - Tokens owner (`msg::sender`). + /// * `operator` - Account to add to the set of authorized operators. + /// * `approved` - Flag that determines whether or not permission will be + /// granted to `operator`. If true, this means `operator` will be allowed + /// to manage `owner`'s assets. /// /// # Errors /// /// If `operator` is the `Address::ZERO`, then the error /// [`Error::InvalidOperator`] is returned. + /// + /// # Events + /// + /// Emits an [`ApprovalForAll`] event. fn _set_approval_for_all( &mut self, owner: Address, @@ -873,7 +875,39 @@ impl Erc1155 { } impl Erc1155 { - fn do_check_on_erc1155_received( + /// Performs an acceptance check for the provided `operator` by calling + /// [`IERC1155Receiver::on_erc_1155_received`] in case of single token + /// transfer, or [`IERC1155Receiver::on_erc_1155_batch_received`] in + /// case of batch transfer on the `to` address. + /// + /// The acceptance call is not executed and treated as a no-op if the + /// target address is doesn't contain code (i.e. an EOA). Otherwise, + /// the recipient must implement either + /// [`IERC1155Receiver::on_erc_1155_received`] for single transfer, or + /// [`IERC1155Receiver::on_erc_1155_batch_received`] for a batch transfer, + /// and return the acceptance value to accept the transfer. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `operator` - Generally the address that initiated the token transfer + /// (e.g. `msg.sender`). + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `details` - Details about token transfer, check + /// [`Erc1155ReceiverData`]. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + fn _check_on_erc1155_received( &mut self, operator: Address, from: Address, @@ -912,14 +946,51 @@ impl Erc1155 { }; // Token rejected. - if id != details.fn_selector { + if id != details.receiver_fn_selector { return Err(ERC1155InvalidReceiver { receiver: to }.into()); } Ok(()) } - fn do_mint( + /// Creates `values` of tokens specified by `ids`, and assigns + /// them to `to`. Performs the token acceptance check by + /// calling [`IERC1155Receiver::on_erc_1155_received`] or + /// [`IERC1155Receiver::on_erc_1155_batch_received`] on the `to` address + /// if it contains code. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `to` - Account of the recipient. + /// * `ids` - Array of all tokens ids to be minted. + /// * `values` - Array of all amounts of tokens to be minted. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// + /// # Errors + /// + /// If `to` is `Address::ZERO`, then the error + /// [`Error:InvalidReceiver`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_batch_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`, may happen during `mint` + /// operation. + fn _do_mint( &mut self, to: Address, ids: Vec, @@ -941,7 +1012,33 @@ impl Erc1155 { Ok(()) } - fn do_burn( + /// Destroys `values` amounts of tokens specified by `ids` from `from`. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account to burn tokens from. + /// * `ids` - Array of all token ids to be burnt. + /// * `values` - Array of all amount of tokens to be burnt. + /// + /// # Errors + /// + /// If `from` is the `Address::ZERO`, then the error + /// [`Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. + /// + /// # Events + /// + /// Emits a [`TransferSingle`] event if the arrays contain one element, and + /// [`TransferBatch`] otherwise. + /// + /// # Panics + /// + /// Should not panic. + fn _do_burn( &mut self, from: Address, ids: Vec, @@ -962,14 +1059,14 @@ impl Erc1155 { Ok(()) } - // TODO + /// Transfers `values` of tokens specified by `ids` from `from` to `to`. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. - /// * `ids` - Array of all token id. + /// * `ids` - Array of all token ids. /// * `values` - Array of all amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -980,6 +1077,10 @@ impl Erc1155 { /// [`Error::InvalidReceiver`] is returned. /// If `from` is the `Address::ZERO`, then the error /// [`Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`Error::InsufficientBalance`] is returned. /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its /// interface id or returned with error, then the error /// [`Error::InvalidReceiver`] is returned. @@ -991,6 +1092,10 @@ impl Erc1155 { /// /// Emits a [`TransferSingle`] event if the arrays contain one element, and /// [`TransferBatch`] otherwise. + /// + /// # Panics + /// + /// Should not panic. fn do_safe_transfer_from( &mut self, from: Address, @@ -1028,10 +1133,10 @@ impl Erc1155 { /// If `value` is greater than the balance of the `from` account, /// then the error [`Error::InsufficientBalance`] is returned. /// - /// /// # Panics /// - /// If balance exceeds `U256::MAX`. It may happen during `mint` operation. + /// If updated balance exceeds `U256::MAX`, may happen during `mint` + /// operation. fn do_update( &mut self, from: Address, @@ -1070,7 +1175,7 @@ impl Erc1155 { Ok(()) } - /// Checks if `ids` array has same length as `values`. + /// Checks if `ids` array has same length as `values` array. /// /// # Arguments /// @@ -1094,11 +1199,11 @@ impl Erc1155 { Ok(()) } - /// Checks if `sender` is authorized to transfer tokens. + /// Checks if `msg::sender` is authorized to transfer tokens. /// /// # Arguments /// - /// * `&self` - Write access to the contract's state. + /// * `&self` - Read access to the contract's state. /// * `from` - Account to transfer tokens from. /// /// # Errors @@ -1106,11 +1211,6 @@ impl Erc1155 { /// If the `from` is not the caller (`msg::sender()`), /// and the caller does not have the right to approve, then the error /// [`Error::MissingApprovalForAll`] is returned. - /// - /// # Requirements - /// - /// * If the caller is not `from`, it must have been approved to spend - /// `from`'s tokens via [`IErc1155::set_approval_for_all`]. fn authorize_transfer(&self, from: Address) -> Result<(), Error> { let sender = msg::sender(); if from != sender && !self.is_approved_for_all(from, sender) { @@ -1176,7 +1276,7 @@ mod tests { let id = uint!(1_U256); let value = uint!(10_U256); let details = Erc1155ReceiverData::new(vec![id], vec![value]); - assert_eq!(SINGLE_TRANSFER_FN_SELECTOR, details.fn_selector); + assert_eq!(SINGLE_TRANSFER_FN_SELECTOR, details.receiver_fn_selector); assert_eq!(Transfer::Single { id, value }, details.transfer); } @@ -1185,7 +1285,7 @@ mod tests { let ids = random_token_ids(5); let values = random_values(5); let details = Erc1155ReceiverData::new(ids.clone(), values.clone()); - assert_eq!(BATCH_TRANSFER_FN_SELECTOR, details.fn_selector); + assert_eq!(BATCH_TRANSFER_FN_SELECTOR, details.receiver_fn_selector); assert_eq!(Transfer::Batch { ids, values }, details.transfer); } @@ -1514,11 +1614,11 @@ mod tests { let invalid_receiver = Address::ZERO; let err = contract - ._safe_transfer_from( + .do_safe_transfer_from( DAVE, invalid_receiver, - token_ids[0], - values[0], + token_ids, + values, vec![0, 1, 2, 3].into(), ) .expect_err("should not transfer tokens to the `Address::ZERO`"); From 35a66ebf945cfb6126638e1951e6baf41feb8f5e Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 10 Oct 2024 01:23:47 +0200 Subject: [PATCH 34/60] test(e2e): remove impossible scenarios --- .github/workflows/check-links.yml | 5 +- examples/erc1155/src/lib.rs | 14 ---- examples/erc1155/tests/abi/mod.rs | 1 - examples/erc1155/tests/erc1155.rs | 113 +++++------------------------- 4 files changed, 21 insertions(+), 112 deletions(-) diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index fc7894162..0339a6b25 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - name: Link Checker uses: lycheeverse/lychee-action@v1 with: @@ -30,9 +30,8 @@ jobs: if: startsWith(github.ref, 'refs/heads/v') || startsWith(github.base_ref, 'v') steps: - uses: actions/checkout@v4 - + - name: Run linkspector uses: umbrelladocs/action-linkspector@v1 with: fail_on_error: true - diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 9d1007157..7e624eedf 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -21,20 +21,6 @@ sol_storage! { #[public] #[inherit(Erc1155)] impl Erc1155Example { - pub fn set_operator_approvals( - &mut self, - owner: Address, - operator: Address, - approved: bool, - ) -> Result<(), Vec> { - self.erc1155 - ._operator_approvals - .setter(owner) - .setter(operator) - .set(approved); - Ok(()) - } - pub fn mint( &mut self, to: Address, diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index 798021868..b7b13213d 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -9,7 +9,6 @@ sol!( function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory balances); function isApprovedForAll(address account, address operator) external view returns (bool approved); function setApprovalForAll(address operator, bool approved) external; - function setOperatorApprovals(address owner, address operator, bool approved) external; function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) external; function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; function mint(address to, uint256 id, uint256 amount, bytes memory data) external; diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index c3ccaeed0..ca393cd87 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -7,7 +7,7 @@ use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; mod abi; fn random_token_ids(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() + (0..size).map(U256::from).collect() } fn random_values(size: usize) -> Vec { @@ -347,42 +347,6 @@ async fn error_when_invalid_receiver_safe_transfer_from( Ok(()) } -#[e2e::test] -async fn error_when_invalid_sender_safe_transfer_from( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let invalid_sender = Address::ZERO; - let bob_addr = bob.address(); - let token_id = random_token_ids(1)[0]; - let value = random_values(1)[0]; - // let _ = watch!(contract.mint(alice_addr, token_id, value, - // vec![].into())); - let _ = watch!(contract.setOperatorApprovals( - invalid_sender, - alice.address(), - true - )); - - let err = send!(contract.safeTransferFrom( - invalid_sender, - bob_addr, - token_id, - value, - vec![].into() - )) - .expect_err("should return `ERC1155InvalidSender`"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { - sender: invalid_sender - })); - - Ok(()) -} - #[e2e::test] async fn error_when_missing_approval_safe_transfer_from( alice: Account, @@ -423,7 +387,8 @@ async fn error_when_insufficient_balance_safe_transfer_from( dave: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155::new(contract_addr, &bob.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -431,10 +396,11 @@ async fn error_when_insufficient_balance_safe_transfer_from( let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; - let _ = watch!(contract.mint(bob_addr, token_id, value, vec![].into())); - let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + let _ = + watch!(contract_alice.mint(bob_addr, token_id, value, vec![].into())); + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); - let err = send!(contract.safeTransferFrom( + let err = send!(contract_alice.safeTransferFrom( bob_addr, dave_addr, token_id, @@ -460,25 +426,24 @@ async fn safe_batch_transfer_from( dave: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155::new(contract_addr, &bob.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); let dave_addr = dave.address(); let token_ids = random_token_ids(2); let values = random_values(2); - let amount_one = values[0] - uint!(1_U256); - let amount_two = values[1] - uint!(1_U256); - let _ = watch!(contract.mintBatch( + let _ = watch!(contract_alice.mintBatch( bob_addr, token_ids.clone(), values.clone(), vec![].into() )); - let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); - let receipt = receipt!(contract.safeBatchTransferFrom( + let receipt = receipt!(contract_alice.safeBatchTransferFrom( bob_addr, dave_addr, token_ids.clone(), @@ -495,9 +460,9 @@ async fn safe_batch_transfer_from( })); let balance_id_one = - contract.balanceOf(dave_addr, token_ids[0]).call().await?.balance; + contract_alice.balanceOf(dave_addr, token_ids[0]).call().await?.balance; let balance_id_two = - contract.balanceOf(dave_addr, token_ids[1]).call().await?.balance; + contract_alice.balanceOf(dave_addr, token_ids[1]).call().await?.balance; assert_eq!(values[0], balance_id_one); assert_eq!(values[1], balance_id_two); @@ -540,47 +505,6 @@ async fn error_when_invalid_receiver_safe_batch_transfer_from( Ok(()) } -#[e2e::test] -async fn error_when_invalid_sender_safe_batch_transfer_from( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let invalid_sender = Address::ZERO; - let bob_addr = bob.address(); - let token_ids = random_token_ids(2); - let values = random_values(2); - - let _ = watch!(contract.mintBatch( - bob_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )); - let _ = watch!(contract.setOperatorApprovals( - invalid_sender, - alice.address(), - true - )); - - let err = send!(contract.safeBatchTransferFrom( - invalid_sender, - bob_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )) - .expect_err("should return `ERC1155InvalidSender`"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { - sender: invalid_sender - })); - - Ok(()) -} - #[e2e::test] async fn error_when_missing_approval_safe_batch_transfer_from( alice: Account, @@ -627,7 +551,8 @@ async fn error_when_insufficient_balance_safe_batch_transfer_from( dave: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155::new(contract_addr, &bob.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -635,15 +560,15 @@ async fn error_when_insufficient_balance_safe_batch_transfer_from( let token_ids = random_token_ids(2); let values = random_values(2); - let _ = watch!(contract.mintBatch( + let _ = watch!(contract_alice.mintBatch( bob_addr, token_ids.clone(), values.clone(), vec![].into() )); - let _ = watch!(contract.setOperatorApprovals(bob_addr, alice_addr, true)); + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); - let err = send!(contract.safeBatchTransferFrom( + let err = send!(contract_alice.safeBatchTransferFrom( bob_addr, dave_addr, token_ids.clone(), From c8f321df1e7c9cb25456394e8c6cf5c2345e05aa Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 10 Oct 2024 12:49:46 +0200 Subject: [PATCH 35/60] feat: add benches for Erc1155 contract --- benches/src/erc1155.rs | 106 +++++++++++++++++++++++++++++++++++++++++ benches/src/lib.rs | 1 + 2 files changed, 107 insertions(+) create mode 100644 benches/src/erc1155.rs diff --git a/benches/src/erc1155.rs b/benches/src/erc1155.rs new file mode 100644 index 000000000..54b0e67cb --- /dev/null +++ b/benches/src/erc1155.rs @@ -0,0 +1,106 @@ +use alloy::{ + network::{AnyNetwork, EthereumWallet}, + primitives::Address, + providers::ProviderBuilder, + sol, + sol_types::{SolCall, SolConstructor}, + uint, +}; +use e2e::{receipt, Account}; + +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; + +sol!( + #[sol(rpc)] + contract Erc1155 { + function balanceOf(address account, uint256 id) external view returns (uint256 balance); + function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory balances); + function isApprovedForAll(address account, address operator) external view returns (bool approved); + function setApprovalForAll(address operator, bool approved) external; + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) external; + function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; + function mint(address to, uint256 id, uint256 amount, bytes memory data) external; + function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; + } +); + +sol!("../examples/erc1155/src/constructor.sol"); + +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("Erc1155"), ContractReport::add)?; + + let cached_reports = run_with(CacheOpt::Bid(0)).await?; + let report = cached_reports + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} + +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { + let alice = Account::new().await?; + let alice_addr = alice.address(); + let alice_wallet = ProviderBuilder::new() + .network::() + .with_recommended_fillers() + .wallet(EthereumWallet::from(alice.signer.clone())) + .on_http(alice.url().parse()?); + + let bob = Account::new().await?; + let bob_addr = bob.address(); + + let contract_addr = deploy(&alice, cache_opt).await?; + + let contract = Erc1155::new(contract_addr, &alice_wallet); + + let token_1 = uint!(1_U256); + let token_2 = uint!(2_U256); + let token_3 = uint!(3_U256); + let token_4 = uint!(4_U256); + + let value_1 = uint!(100_U256); + let value_2 = uint!(200_U256); + let value_3 = uint!(300_U256); + let value_4 = uint!(400_U256); + + let ids = vec![token_1, token_2, token_3, token_4]; + let values = vec![value_1, value_2, value_3, value_4]; + + let data: alloy_primitives::Bytes = vec![].into(); + + // IMPORTANT: Order matters! + use Erc1155::*; + #[rustfmt::skip] + let receipts = vec![ + (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_1, value_1, data.clone()))?), + (mintBatchCall::SIGNATURE, receipt!(contract.mintBatch(alice_addr, ids.clone(), values.clone(), data.clone()))?), + (balanceOfCall::SIGNATURE, receipt!(contract.balanceOf(alice_addr, token_1))?), + (balanceOfBatchCall::SIGNATURE, receipt!(contract.balanceOfBatch(vec![alice_addr, bob_addr], vec![token_1, token_2]))?), + (setApprovalForAllCall::SIGNATURE, receipt!(contract.setApprovalForAll(bob_addr, true))?), + (isApprovedForAllCall::SIGNATURE, receipt!(contract.isApprovedForAll(alice_addr, bob_addr))?), + (safeTransferFromCall::SIGNATURE, receipt!(contract.safeTransferFrom(alice_addr, bob_addr, token_1, value_1, data.clone()))?), + (safeBatchTransferFromCall::SIGNATURE, receipt!(contract.safeBatchTransferFrom(alice_addr, bob_addr, ids, values, data.clone()))?) + ]; + + receipts + .into_iter() + .map(FunctionReport::new) + .collect::>>() +} + +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ + let args = Erc1155Example::constructorCall {}; + let args = alloy::hex::encode(args.abi_encode()); + crate::deploy(account, "erc1155", Some(args), cache_opt).await +} diff --git a/benches/src/lib.rs b/benches/src/lib.rs index c884bcdb8..44e36faf4 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -14,6 +14,7 @@ use koba::config::{Deploy, Generate, PrivateKey}; use serde::Deserialize; pub mod access_control; +pub mod erc1155; pub mod erc20; pub mod erc721; pub mod merkle_proofs; From 2046916174770ca6cdccc59f200c6e85323fc422 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 24 Oct 2024 19:56:35 +0200 Subject: [PATCH 36/60] ref: remove warning around IERC1155Receiver --- Cargo.lock | 2 +- contracts/src/token/erc1155/mod.rs | 108 +++++++++++++++-------------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 565b11981..c13c0076e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,7 +1512,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erc1155-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index fe476f0e5..098165131 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -8,7 +8,7 @@ use stylus_sdk::{ alloy_sol_types::sol, call::{self, Call, MethodError}, evm, msg, - prelude::{public, sol_interface, sol_storage, AddressVM, SolidityError}, + prelude::{public, sol_storage, AddressVM, SolidityError}, storage::TopLevelStorage, }; @@ -168,57 +168,61 @@ impl MethodError for Error { } } -sol_interface! { - /// Interface that must be implemented by smart contracts - /// in order to receive ERC-1155 token transfers. - interface IERC1155Receiver { - /// Handles the receipt of a single ERC-1155 token type. - /// This function is called at the end of a - /// [`IErc1155::safe_batch_transfer_from`] - /// after the balance has been updated. - /// - /// NOTE: To accept the transfer, - /// this must return [`SINGLE_TRANSFER_FN_SELECTOR`], - /// or its own function selector. - /// - /// * `operator` - The address which initiated the transfer. - /// * `from` - The address which previously owned the token. - /// * `id` - The ID of the token being transferred. - /// * `value` - The amount of tokens being transferred. - /// * `data` - Additional data with no specified format. - #[allow(missing_docs)] - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external returns (bytes4); - - /// Handles the receipt of a multiple ERC-1155 token types. - /// This function is called at the end of a - /// [`IErc1155::safe_batch_transfer_from`] - /// after the balances have been updated. - /// - /// NOTE: To accept the transfer(s), - /// this must return [`BATCH_TRANSFER_FN_SELECTOR`], - /// or its own function selector. - /// - /// * `operator` - The address which initiated the batch transfer. - /// * `from` - The address which previously owned the token. - /// * `ids` - An array containing ids of each token being transferred - /// (order and length must match values array). - /// * `values` - An array containing amounts of each token - /// being transferred (order and length must match ids array). - /// * `data` - Additional data with no specified format. - #[allow(missing_docs)] - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external returns (bytes4); +pub use receiver::IERC1155Receiver; + +mod receiver { + stylus_sdk::stylus_proc::sol_interface! { + /// Interface that must be implemented by smart contracts + /// in order to receive ERC-1155 token transfers. + interface IERC1155Receiver { + /// Handles the receipt of a single ERC-1155 token type. + /// This function is called at the end of a + /// [`IErc1155::safe_batch_transfer_from`] + /// after the balance has been updated. + /// + /// NOTE: To accept the transfer, + /// this must return [`SINGLE_TRANSFER_FN_SELECTOR`], + /// or its own function selector. + /// + /// * `operator` - The address which initiated the transfer. + /// * `from` - The address which previously owned the token. + /// * `id` - The ID of the token being transferred. + /// * `value` - The amount of tokens being transferred. + /// * `data` - Additional data with no specified format. + #[allow(missing_docs)] + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /// Handles the receipt of a multiple ERC-1155 token types. + /// This function is called at the end of a + /// [`IErc1155::safe_batch_transfer_from`] + /// after the balances have been updated. + /// + /// NOTE: To accept the transfer(s), + /// this must return [`BATCH_TRANSFER_FN_SELECTOR`], + /// or its own function selector. + /// + /// * `operator` - The address which initiated the batch transfer. + /// * `from` - The address which previously owned the token. + /// * `ids` - An array containing ids of each token being transferred + /// (order and length must match values array). + /// * `values` - An array containing amounts of each token + /// being transferred (order and length must match ids array). + /// * `data` - Additional data with no specified format. + #[allow(missing_docs)] + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); + } } } From ce661f84738b4f55e1a42fb2f0e0affae26586fa Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 24 Oct 2024 20:08:38 +0200 Subject: [PATCH 37/60] fix: bump Rust version --- .github/workflows/check-publish.yml | 2 +- .github/workflows/check-wasm.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/gas-bench.yml | 2 +- contracts/src/token/erc1155/mod.rs | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-publish.yml b/.github/workflows/check-publish.yml index 16956b9fd..1c05aab7f 100644 --- a/.github/workflows/check-publish.yml +++ b/.github/workflows/check-publish.yml @@ -24,7 +24,7 @@ jobs: with: target: wasm32-unknown-unknown components: rust-src - toolchain: nightly-2024-01-01 + toolchain: nightly-2024-07-25 - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/check-wasm.yml b/.github/workflows/check-wasm.yml index f452c4fa2..8d2c7f7e8 100644 --- a/.github/workflows/check-wasm.yml +++ b/.github/workflows/check-wasm.yml @@ -25,7 +25,7 @@ jobs: with: target: wasm32-unknown-unknown components: rust-src - toolchain: nightly-2024-01-01 + toolchain: nightly-2024-07-25 - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index cebd03798..676731098 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -32,7 +32,7 @@ jobs: with: target: wasm32-unknown-unknown components: rust-src - toolchain: nightly-2024-01-01 + toolchain: nightly-2024-07-25 - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/gas-bench.yml b/.github/workflows/gas-bench.yml index a14bf25c3..cba6d73d0 100644 --- a/.github/workflows/gas-bench.yml +++ b/.github/workflows/gas-bench.yml @@ -25,7 +25,7 @@ jobs: with: target: wasm32-unknown-unknown components: rust-src - toolchain: nightly-2024-01-01 + toolchain: nightly-2024-07-25 - uses: Swatinem/rust-cache@v2 with: diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 098165131..497ba9560 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -170,6 +170,7 @@ impl MethodError for Error { pub use receiver::IERC1155Receiver; +#[allow(missing_docs)] mod receiver { stylus_sdk::stylus_proc::sol_interface! { /// Interface that must be implemented by smart contracts From 5e66a8fa091b409314c1001b28803f94c90bf22e Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Fri, 25 Oct 2024 14:07:49 +0200 Subject: [PATCH 38/60] test(e2e): improve E2E tests for Erc1155 contract --- examples/erc1155/tests/erc1155.rs | 371 +++++++++++++++++++++--------- 1 file changed, 264 insertions(+), 107 deletions(-) diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index ca393cd87..7804ac5af 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -27,7 +27,7 @@ async fn constructs(alice: Account) -> eyre::Result<()> { } #[e2e::test] -async fn error_when_array_length_mismatch( +async fn invalid_array_length_error_in_balance_of_batch( alice: Account, bob: Account, ) -> eyre::Result<()> { @@ -87,6 +87,86 @@ async fn balance_of_batch_zero_balance( Ok(()) } +#[e2e::test] +async fn mints(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let receipt = receipt!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + id: token_id, + value + })); + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(value, balance); + + Ok(()) +} + +#[e2e::test] +async fn mint_batch( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let dave_addr = dave.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + + let accounts = vec![alice_addr, bob_addr, dave_addr]; + + for account in accounts { + let receipt = receipt!(contract.mintBatch( + account, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: account, + ids: token_ids.clone(), + values: values.clone() + })); + + for (token_id, value) in token_ids.iter().zip(values.iter()) { + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(account, *token_id).call().await?; + assert_eq!(*value, balance); + } + + let Erc1155::balanceOfBatchReturn { balances } = contract + .balanceOfBatch(vec![account, account, account], token_ids.clone()) + .call() + .await?; + + assert_eq!(values, balances); + } + Ok(()) +} + #[e2e::test] async fn set_approval_for_all( alice: Account, @@ -149,9 +229,7 @@ async fn error_when_invalid_operator_approval_for_all( } #[e2e::test] -async fn error_when_invalid_operator_is_approved_for_all_( - alice: Account, -) -> eyre::Result<()> { +async fn is_approved_for_all_zero_address(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); @@ -168,136 +246,84 @@ async fn error_when_invalid_operator_is_approved_for_all_( } #[e2e::test] -async fn mints(alice: Account) -> eyre::Result<()> { +async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); + let bob_addr = bob.address(); let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; - - let receipt = receipt!(contract.mint( + let _ = watch!(contract.mint( alice_addr, token_id, value, vec![0, 1, 2, 3].into() - ))?; - - assert!(receipt.emits(Erc1155::TransferSingle { - operator: alice_addr, - from: Address::ZERO, - to: alice_addr, - id: token_id, - value - })); + )); - let Erc1155::balanceOfReturn { balance } = + let Erc1155::balanceOfReturn { balance: initial_alice_balance } = contract.balanceOf(alice_addr, token_id).call().await?; - assert_eq!(value, balance); - - Ok(()) -} - -#[e2e::test] -async fn mint_batch( - alice: Account, - bob: Account, - dave: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let bob_addr = bob.address(); - let dave_addr = dave.address(); - let token_ids = random_token_ids(3); - let values = random_values(3); + let Erc1155::balanceOfReturn { balance: initial_bob_balance } = + contract.balanceOf(bob_addr, token_id).call().await?; - let receipt = receipt!(contract.mintBatch( + let receipt = receipt!(contract.safeTransferFrom( alice_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - assert!(receipt.emits(Erc1155::TransferBatch { - operator: alice_addr, - from: Address::ZERO, - to: alice_addr, - ids: token_ids.clone(), - values: values.clone() - })); - - for (token_id, value) in token_ids.iter().zip(values.iter()) { - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(alice_addr, *token_id).call().await?; - assert_eq!(*value, balance); - } - - let receipt = receipt!(contract.mintBatch( bob_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() + token_id, + value, + vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferBatch { + assert!(receipt.emits(Erc1155::TransferSingle { operator: alice_addr, - from: Address::ZERO, + from: alice_addr, to: bob_addr, - ids: token_ids.clone(), - values: values.clone() + id: token_id, + value })); - for (token_id, value) in token_ids.iter().zip(values.iter()) { - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(bob_addr, *token_id).call().await?; - assert_eq!(*value, balance); - } - - let receipt = receipt!(contract.mintBatch( - dave_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - assert!(receipt.emits(Erc1155::TransferBatch { - operator: alice_addr, - from: Address::ZERO, - to: dave_addr, - ids: token_ids.clone(), - values: values.clone() - })); + let Erc1155::balanceOfReturn { balance: alice_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(initial_alice_balance - value, alice_balance); - for (token_id, value) in token_ids.iter().zip(values.iter()) { - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(dave_addr, *token_id).call().await?; - assert_eq!(*value, balance); - } + let Erc1155::balanceOfReturn { balance: bob_balance } = + contract.balanceOf(bob_addr, token_id).call().await?; + assert_eq!(initial_bob_balance + value, bob_balance); Ok(()) } #[e2e::test] -async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { +async fn safe_transfer_from_with_approval( + alice: Account, + bob: Account, +) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155::new(contract_addr, &bob.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); let token_id = random_token_ids(1)[0]; let value = random_values(1)[0]; - let _ = watch!(contract.mint( - alice_addr, + + let _ = watch!(contract_bob.mint( + bob_addr, token_id, value, vec![0, 1, 2, 3].into() )); - let receipt = receipt!(contract.safeTransferFrom( - alice_addr, + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); + + let Erc1155::balanceOfReturn { balance: initial_alice_balance } = + contract_alice.balanceOf(alice_addr, token_id).call().await?; + let Erc1155::balanceOfReturn { balance: initial_bob_balance } = + contract_alice.balanceOf(bob_addr, token_id).call().await?; + + let receipt = receipt!(contract_alice.safeTransferFrom( bob_addr, + alice_addr, token_id, value, vec![].into() @@ -305,15 +331,19 @@ async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { assert!(receipt.emits(Erc1155::TransferSingle { operator: alice_addr, - from: alice_addr, - to: bob_addr, + from: bob_addr, + to: alice_addr, id: token_id, value })); - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(bob_addr, token_id).call().await?; - assert_eq!(value, balance); + let Erc1155::balanceOfReturn { balance: alice_balance } = + contract_alice.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(initial_alice_balance + value, alice_balance); + + let Erc1155::balanceOfReturn { balance: bob_balance } = + contract_alice.balanceOf(bob_addr, token_id).call().await?; + assert_eq!(initial_bob_balance - value, bob_balance); Ok(()) } @@ -423,6 +453,74 @@ async fn error_when_insufficient_balance_safe_transfer_from( async fn safe_batch_transfer_from( alice: Account, bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract_alice.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let Erc1155::balanceOfBatchReturn { balances: initial_alice_balances } = + contract_alice + .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) + .call() + .await?; + + let Erc1155::balanceOfBatchReturn { balances: initial_bob_balances } = + contract_alice + .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) + .call() + .await?; + + let receipt = receipt!(contract_alice.safeBatchTransferFrom( + alice_addr, + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: alice_addr, + to: bob_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + let Erc1155::balanceOfBatchReturn { balances: alice_balances } = + contract_alice + .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) + .call() + .await?; + + let Erc1155::balanceOfBatchReturn { balances: bob_balances } = + contract_alice + .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) + .call() + .await?; + + for (idx, value) in values.iter().enumerate() { + assert_eq!(initial_alice_balances[idx] - value, alice_balances[idx]); + assert_eq!(initial_bob_balances[idx] + value, bob_balances[idx]); + } + + Ok(()) +} + +#[e2e::test] +async fn safe_batch_transfer_from_with_approval( + alice: Account, + bob: Account, dave: Account, ) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -441,8 +539,21 @@ async fn safe_batch_transfer_from( values.clone(), vec![].into() )); + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); + let Erc1155::balanceOfBatchReturn { balances: initial_dave_balances } = + contract_alice + .balanceOfBatch(vec![dave_addr, dave_addr], token_ids.clone()) + .call() + .await?; + + let Erc1155::balanceOfBatchReturn { balances: initial_bob_balances } = + contract_alice + .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) + .call() + .await?; + let receipt = receipt!(contract_alice.safeBatchTransferFrom( bob_addr, dave_addr, @@ -459,13 +570,22 @@ async fn safe_batch_transfer_from( values: values.clone() })); - let balance_id_one = - contract_alice.balanceOf(dave_addr, token_ids[0]).call().await?.balance; - let balance_id_two = - contract_alice.balanceOf(dave_addr, token_ids[1]).call().await?.balance; - - assert_eq!(values[0], balance_id_one); - assert_eq!(values[1], balance_id_two); + let Erc1155::balanceOfBatchReturn { balances: bob_balances } = + contract_alice + .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) + .call() + .await?; + + let Erc1155::balanceOfBatchReturn { balances: dave_balances } = + contract_alice + .balanceOfBatch(vec![dave_addr, dave_addr], token_ids.clone()) + .call() + .await?; + + for (idx, value) in values.iter().enumerate() { + assert_eq!(initial_bob_balances[idx] - value, bob_balances[idx]); + assert_eq!(initial_dave_balances[idx] + value, dave_balances[idx]); + } Ok(()) } @@ -505,6 +625,43 @@ async fn error_when_invalid_receiver_safe_batch_transfer_from( Ok(()) } +#[e2e::test] +async fn error_invalid_array_length_in_safe_batch_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract_alice.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let err = send!(contract_alice.safeBatchTransferFrom( + alice_addr, + bob_addr, + vec![token_ids[0]], + values.clone(), + vec![].into() + )) + .expect_err("should return `ERC1155InvalidArrayLength`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidArrayLength { + idsLength: uint!(1_U256), + valuesLength: uint!(2_U256) + })); + + Ok(()) +} + #[e2e::test] async fn error_when_missing_approval_safe_batch_transfer_from( alice: Account, From 4eb9f69fb8a3aecfc408e06cb50dc5df138b5bf5 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Fri, 25 Oct 2024 14:18:01 +0200 Subject: [PATCH 39/60] ref: split IERC1155Receiver interface to another file --- contracts/src/token/erc1155/mod.rs | 227 +++++++++--------------- contracts/src/token/erc1155/receiver.rs | 59 ++++++ 2 files changed, 144 insertions(+), 142 deletions(-) create mode 100644 contracts/src/token/erc1155/receiver.rs diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 497ba9560..db6b79550 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -17,6 +17,9 @@ use crate::utils::{ math::storage::SubAssignUnchecked, }; +mod receiver; +pub use receiver::IERC1155Receiver; + /// `bytes4( /// keccak256( /// "onERC1155Received(address,address,uint256,uint256,bytes)" @@ -168,65 +171,6 @@ impl MethodError for Error { } } -pub use receiver::IERC1155Receiver; - -#[allow(missing_docs)] -mod receiver { - stylus_sdk::stylus_proc::sol_interface! { - /// Interface that must be implemented by smart contracts - /// in order to receive ERC-1155 token transfers. - interface IERC1155Receiver { - /// Handles the receipt of a single ERC-1155 token type. - /// This function is called at the end of a - /// [`IErc1155::safe_batch_transfer_from`] - /// after the balance has been updated. - /// - /// NOTE: To accept the transfer, - /// this must return [`SINGLE_TRANSFER_FN_SELECTOR`], - /// or its own function selector. - /// - /// * `operator` - The address which initiated the transfer. - /// * `from` - The address which previously owned the token. - /// * `id` - The ID of the token being transferred. - /// * `value` - The amount of tokens being transferred. - /// * `data` - Additional data with no specified format. - #[allow(missing_docs)] - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external returns (bytes4); - - /// Handles the receipt of a multiple ERC-1155 token types. - /// This function is called at the end of a - /// [`IErc1155::safe_batch_transfer_from`] - /// after the balances have been updated. - /// - /// NOTE: To accept the transfer(s), - /// this must return [`BATCH_TRANSFER_FN_SELECTOR`], - /// or its own function selector. - /// - /// * `operator` - The address which initiated the batch transfer. - /// * `from` - The address which previously owned the token. - /// * `ids` - An array containing ids of each token being transferred - /// (order and length must match values array). - /// * `values` - An array containing amounts of each token - /// being transferred (order and length must match ids array). - /// * `data` - Additional data with no specified format. - #[allow(missing_docs)] - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external returns (bytes4); - } - } -} - sol_storage! { /// State of an [`Erc1155`] token. pub struct Erc1155 { @@ -242,86 +186,6 @@ sol_storage! { /// BorrowMut)`. Should be fixed in the future by the Stylus team. unsafe impl TopLevelStorage for Erc1155 {} -/// Data structure to be passed to contract -/// implementing [`IERC1155Receiver`] interface. -struct Erc1155ReceiverData { - /// ERC-1155 Receiver function selector. - receiver_fn_selector: FixedBytes<4>, - /// Transfer details, either [`Transfer::Single`] or [`Transfer::Batch`]. - transfer: Transfer, -} - -impl Erc1155ReceiverData { - /// Creates a new instance based on transfer details. - /// Assumes that `ids` is not empty. - /// - /// If `ids` array has only 1 element, - /// it means that it is a [`Transfer::Single`]. - /// If `ids` array has many elements, - /// it means that it is a [`Transfer::Batch`]. - /// - /// NOTE: Does not check if `ids` length is equal to `values`. - /// - /// # Arguments - /// - /// * `ids` - Array of tokens ids being transferred. - /// * `values` - Array of all amount of tokens being transferred. - fn new(ids: Vec, values: Vec) -> Self { - if ids.len() == 1 { - Self::single(ids[0], values[0]) - } else { - Self::batch(ids, values) - } - } - - /// Creates a new instance for a [`Transfer::Single`]. - /// Check [`IERC1155Receiver::on_erc_1155_received`]. - /// - /// # Arguments - /// - /// * `id` - Token id being transferred. - /// * `value` - Amount of tokens being transferred. - fn single(id: U256, value: U256) -> Self { - Self { - receiver_fn_selector: SINGLE_TRANSFER_FN_SELECTOR, - transfer: Transfer::Single { id, value }, - } - } - - /// Creates a new instance for a [`Transfer::Batch`]. - /// Check [`IERC1155Receiver::on_erc_1155_batch_received`]. - /// - /// # Arguments - /// - /// * `ids` - Array of tokens ids being transferred. - /// * `values` - Array of all amount of tokens being transferred. - fn batch(ids: Vec, values: Vec) -> Self { - Self { - receiver_fn_selector: BATCH_TRANSFER_FN_SELECTOR, - transfer: Transfer::Batch { ids, values }, - } - } -} - -/// Struct representing token transfer details. -#[derive(Debug, PartialEq)] -enum Transfer { - /// Transfer of a single token. - /// - /// # Attributes - /// - /// * `id` - Token id being transferred. - /// * `value` - Amount of tokens being transferred. - Single { id: U256, value: U256 }, - /// Batch tokens transfer. - /// - /// # Attributes - /// - /// * `ids` - Array of tokens ids being transferred. - /// * `values` - Array of all amount of tokens being transferred. - Batch { ids: Vec, values: Vec }, -} - /// Required interface of an [`Erc1155`] compliant contract. #[interface_id] pub trait IErc1155 { @@ -1227,6 +1091,87 @@ impl Erc1155 { Ok(()) } } + +/// Data struct to be passed to a contract that +/// implements [`IERC1155Receiver`] interface. +struct Erc1155ReceiverData { + /// ERC-1155 Receiver function selector. + receiver_fn_selector: FixedBytes<4>, + /// Transfer details, either [`Transfer::Single`] or [`Transfer::Batch`]. + transfer: Transfer, +} + +impl Erc1155ReceiverData { + /// Creates a new instance based on transfer details. + /// Assumes that `ids` is not empty. + /// + /// If `ids` array has only 1 element, + /// it means that it is a [`Transfer::Single`]. + /// If `ids` array has many elements, + /// it means that it is a [`Transfer::Batch`]. + /// + /// NOTE: Does not check if `ids` length is equal to `values`. + /// + /// # Arguments + /// + /// * `ids` - Array of tokens ids being transferred. + /// * `values` - Array of all amount of tokens being transferred. + fn new(ids: Vec, values: Vec) -> Self { + if ids.len() == 1 { + Self::single(ids[0], values[0]) + } else { + Self::batch(ids, values) + } + } + + /// Creates a new instance for a [`Transfer::Single`]. + /// Check [`IERC1155Receiver::on_erc_1155_received`]. + /// + /// # Arguments + /// + /// * `id` - Token id being transferred. + /// * `value` - Amount of tokens being transferred. + fn single(id: U256, value: U256) -> Self { + Self { + receiver_fn_selector: SINGLE_TRANSFER_FN_SELECTOR, + transfer: Transfer::Single { id, value }, + } + } + + /// Creates a new instance for a [`Transfer::Batch`]. + /// Check [`IERC1155Receiver::on_erc_1155_batch_received`]. + /// + /// # Arguments + /// + /// * `ids` - Array of tokens ids being transferred. + /// * `values` - Array of all amount of tokens being transferred. + fn batch(ids: Vec, values: Vec) -> Self { + Self { + receiver_fn_selector: BATCH_TRANSFER_FN_SELECTOR, + transfer: Transfer::Batch { ids, values }, + } + } +} + +/// Struct representing token transfer details. +#[derive(Debug, PartialEq)] +enum Transfer { + /// Transfer of a single token. + /// + /// # Attributes + /// + /// * `id` - Token id being transferred. + /// * `value` - Amount of tokens being transferred. + Single { id: U256, value: U256 }, + /// Batch tokens transfer. + /// + /// # Attributes + /// + /// * `ids` - Array of tokens ids being transferred. + /// * `values` - Array of all amount of tokens being transferred. + Batch { ids: Vec, values: Vec }, +} + #[cfg(all(test, feature = "std"))] mod tests { use alloy_primitives::{address, uint, Address, U256}; @@ -1239,9 +1184,7 @@ mod tests { IErc1155, Transfer, BATCH_TRANSFER_FN_SELECTOR, SINGLE_TRANSFER_FN_SELECTOR, }; - use crate::{ - token::erc721::IErc721, utils::introspection::erc165::IErc165, - }; + use crate::utils::introspection::erc165::IErc165; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); diff --git a/contracts/src/token/erc1155/receiver.rs b/contracts/src/token/erc1155/receiver.rs new file mode 100644 index 000000000..64bb9dd01 --- /dev/null +++ b/contracts/src/token/erc1155/receiver.rs @@ -0,0 +1,59 @@ +#![allow(missing_docs)] +//! Module with an interface required for smart contract +//! in order to receive ERC-1155 token transfers. + +use stylus_sdk::stylus_proc::sol_interface; + +sol_interface! { + /// Interface that must be implemented by smart contracts + /// in order to receive ERC-1155 token transfers. + interface IERC1155Receiver { + /// Handles the receipt of a single ERC-1155 token type. + /// This function is called at the end of a + /// [`IErc1155::safe_batch_transfer_from`] + /// after the balance has been updated. + /// + /// NOTE: To accept the transfer, + /// this must return [`SINGLE_TRANSFER_FN_SELECTOR`], + /// or its own function selector. + /// + /// * `operator` - The address which initiated the transfer. + /// * `from` - The address which previously owned the token. + /// * `id` - The ID of the token being transferred. + /// * `value` - The amount of tokens being transferred. + /// * `data` - Additional data with no specified format. + #[allow(missing_docs)] + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /// Handles the receipt of a multiple ERC-1155 token types. + /// This function is called at the end of a + /// [`IErc1155::safe_batch_transfer_from`] + /// after the balances have been updated. + /// + /// NOTE: To accept the transfer(s), + /// this must return [`BATCH_TRANSFER_FN_SELECTOR`], + /// or its own function selector. + /// + /// * `operator` - The address which initiated the batch transfer. + /// * `from` - The address which previously owned the token. + /// * `ids` - An array containing ids of each token being transferred + /// (order and length must match values array). + /// * `values` - An array containing amounts of each token + /// being transferred (order and length must match ids array). + /// * `data` - Additional data with no specified format. + #[allow(missing_docs)] + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); + } +} From f98e5b335537fd8e264651de6303b1891680c98b Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Mon, 28 Oct 2024 16:48:19 +0100 Subject: [PATCH 40/60] test(e2e): add E2E tests for ERC1155ReceiverMock transfers --- examples/erc1155/src/ERC1155ReceiverMock.sol | 73 ++++++- examples/erc1155/tests/erc1155.rs | 189 ++++++++++++++++++- examples/erc1155/tests/mock/mod.rs | 1 + examples/erc1155/tests/mock/receiver.rs | 97 ++++++++++ 4 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 examples/erc1155/tests/mock/mod.rs create mode 100644 examples/erc1155/tests/mock/receiver.rs diff --git a/examples/erc1155/src/ERC1155ReceiverMock.sol b/examples/erc1155/src/ERC1155ReceiverMock.sol index 9c24586cf..0ff6e73bc 100644 --- a/examples/erc1155/src/ERC1155ReceiverMock.sol +++ b/examples/erc1155/src/ERC1155ReceiverMock.sol @@ -1,7 +1,74 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.21; -import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/introspection/ERC165.sol"; -contract IERC1155ReceiverMock is IERC1155Receiver {} +contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + bytes4 private immutable _recRetval; + bytes4 private immutable _batRetval; + RevertType private immutable _error; + + event Received(address operator, address from, uint256 id, uint256 value, bytes data); + 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; + } +} diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 7804ac5af..76dd2bc57 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -1,10 +1,12 @@ #![cfg(feature = "e2e")] use abi::Erc1155; -use alloy::primitives::{uint, Address, U256}; +use alloy::primitives::{fixed_bytes, uint, Address, U256}; use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; +use mock::{receiver, receiver::ERC1155ReceiverMock}; mod abi; +mod mock; fn random_token_ids(size: usize) -> Vec { (0..size).map(U256::from).collect() @@ -15,7 +17,7 @@ fn random_values(size: usize) -> Vec { } // ============================================================================ -// Integration Tests: ERC-1155 Token Standard +// Integration Tests: ERC-1155 Token // ============================================================================ #[e2e::test] @@ -348,6 +350,189 @@ async fn safe_transfer_from_with_approval( Ok(()) } +#[e2e::test] +async fn safe_transfer_to_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_addr = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) + .await?; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )); + + let Erc1155::balanceOfReturn { balance: initial_alice_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + let Erc1155::balanceOfReturn { balance: initial_receiver_balance } = + contract.balanceOf(receiver_addr, token_id).call().await?; + + let receipt = receipt!(contract.safeTransferFrom( + alice_addr, + receiver_addr, + token_id, + value, + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: receiver_addr, + id: token_id, + value + })); + + assert!(receipt.emits(ERC1155ReceiverMock::Received { + operator: alice_addr, + from: alice_addr, + id: token_id, + value, + data: fixed_bytes!("").into(), + })); + + let Erc1155::balanceOfReturn { balance: alice_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(initial_alice_balance - value, alice_balance); + + let Erc1155::balanceOfReturn { balance: receiver_balance } = + contract.balanceOf(receiver_addr, token_id).call().await?; + assert_eq!(initial_receiver_balance + value, receiver_balance); + + Ok(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_reverts_with_reason( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithMessage, + ) + .await?; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = 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 errors with reason"); + + // assert!(err.reverted_with(stylus_sdk::call::Error::Revert( + // b"ERC1155ReceiverMock: reverting on receive".to_vec() + // ))); + Ok(()) +} + +#[e2e::test] +async fn errors_when_receiver_reverts_without_reason( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithoutMessage, + ) + .await?; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = 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(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_panics(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) + .await?; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = 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 panics"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { + receiver: receiver_address + })); + + Ok(()) +} + #[e2e::test] async fn error_when_invalid_receiver_safe_transfer_from( alice: Account, diff --git a/examples/erc1155/tests/mock/mod.rs b/examples/erc1155/tests/mock/mod.rs new file mode 100644 index 000000000..4c0db7eaa --- /dev/null +++ b/examples/erc1155/tests/mock/mod.rs @@ -0,0 +1 @@ +pub mod receiver; diff --git a/examples/erc1155/tests/mock/receiver.rs b/examples/erc1155/tests/mock/receiver.rs new file mode 100644 index 000000000..da021c4cb --- /dev/null +++ b/examples/erc1155/tests/mock/receiver.rs @@ -0,0 +1,97 @@ +#![allow(dead_code)] +#![cfg(feature = "e2e")] +use alloy::{ + primitives::{fixed_bytes, Address, FixedBytes}, + sol, +}; +use e2e::Wallet; + +const REC_RETVAL: FixedBytes<4> = fixed_bytes!("f23a6e61"); +const BAT_RETVAL: FixedBytes<4> = fixed_bytes!("bc197c81"); + +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 652007ece9eb47c9503dcf486c7c5a776d146c65 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Tue, 29 Oct 2024 17:26:42 +0100 Subject: [PATCH 41/60] test(e2e): E2E tests for batch transfers to ERC1155Receiver contract --- examples/erc1155/tests/erc1155.rs | 269 ++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 76dd2bc57..1bc8ea160 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -533,6 +533,36 @@ async fn errors_when_receiver_panics(alice: Account) -> eyre::Result<()> { Ok(()) } +#[e2e::test] +async fn errors_when_invalid_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + let _err = send!(contract.safeTransferFrom( + alice_addr, + contract_addr, + token_id, + value, + vec![].into() + )) + .expect_err("should not transfer when invalid receiver contract"); + + Ok(()) +} + #[e2e::test] async fn error_when_invalid_receiver_safe_transfer_from( alice: Account, @@ -702,6 +732,245 @@ async fn safe_batch_transfer_from( Ok(()) } +#[e2e::test] +async fn safe_batch_transfer_to_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_addr = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) + .await?; + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let Erc1155::balanceOfBatchReturn { balances: initial_alice_balances } = + contract + .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) + .call() + .await?; + + let Erc1155::balanceOfBatchReturn { balances: initial_receiver_balances } = + contract + .balanceOfBatch( + vec![receiver_addr, receiver_addr], + token_ids.clone(), + ) + .call() + .await?; + + let receipt = receipt!(contract.safeBatchTransferFrom( + alice_addr, + receiver_addr, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: alice_addr, + to: receiver_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + assert!(receipt.emits(ERC1155ReceiverMock::BatchReceived { + operator: alice_addr, + from: alice_addr, + ids: token_ids.clone(), + values: values.clone(), + data: fixed_bytes!("").into(), + })); + + let Erc1155::balanceOfBatchReturn { balances: alice_balances } = contract + .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) + .call() + .await?; + + let Erc1155::balanceOfBatchReturn { balances: receiver_balances } = + contract + .balanceOfBatch( + vec![receiver_addr, receiver_addr], + token_ids.clone(), + ) + .call() + .await?; + + for (idx, value) in values.iter().enumerate() { + assert_eq!(initial_alice_balances[idx] - value, alice_balances[idx]); + assert_eq!( + initial_receiver_balances[idx] + value, + receiver_balances[idx] + ); + } + + Ok(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_reverts_with_reason_in_batch_transfer( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithMessage, + ) + .await?; + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = 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 errors with reason"); + + // assert!(err.reverted_with(stylus_sdk::call::Error::Revert( + // b"ERC1155ReceiverMock: reverting on receive".to_vec() + // ))); + 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?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithoutMessage, + ) + .await?; + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = 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(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_panics_in_batch_transfer( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) + .await?; + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = 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 panics"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { + receiver: receiver_address + })); + + Ok(()) +} + +#[e2e::test] +async fn errors_when_invalid_receiver_contract_in_batch_transfer( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let _err = send!(contract.safeBatchTransferFrom( + alice_addr, + contract_addr, + token_ids, + values, + vec![].into() + )) + .expect_err("should not transfer when invalid receiver contract"); + + Ok(()) +} + #[e2e::test] async fn safe_batch_transfer_from_with_approval( alice: Account, From 78e3ea7d56c4618429468b04e892f84467c814f6 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 00:52:21 +0100 Subject: [PATCH 42/60] test: improve unit tests for Erc1155 --- Cargo.lock | 3 +- contracts/src/token/erc1155/mod.rs | 128 +++++++++++++++++------------ examples/erc1155/Cargo.toml | 1 - 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cf8caaa1..f34a84b62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1503,13 +1503,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erc1155-example" -version = "0.1.0" +version = "0.1.1" dependencies = [ "alloy", "alloy-primitives", "e2e", "eyre", - "mini-alloc", "openzeppelin-stylus", "rand", "stylus-sdk", diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index db6b79550..e263fbf8c 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1325,71 +1325,97 @@ mod tests { assert_eq!(balance, value); } + #[motsu::test] + fn error_when_mints_to_invalid_receiver(contract: Erc1155) { + let invalid_receiver = Address::ZERO; + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let err = contract + ._mint(invalid_receiver, token_id, value, vec![0, 1, 2, 3].into()) + .expect_err("should not mint tokens for invalid receiver"); + + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } + #[motsu::test] fn mints_batch(contract: Erc1155) { let token_ids = random_token_ids(4); let values = random_values(4); let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; - contract - ._mint_batch( - ALICE, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into(), - ) - .expect("should mint tokens for Alice"); - token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract.balance_of(ALICE, token_id); - assert_eq!(balance, value); - }); + for account in accounts { + contract + ._mint_batch( + account, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into(), + ) + .expect("should batch mint tokens"); + + token_ids.iter().zip(values.iter()).for_each( + |(&token_id, &value)| { + assert_eq!(value, contract.balance_of(account, token_id)); + }, + ); + + let balances = contract + .balance_of_batch( + vec![account, account, account, account], + token_ids.clone(), + ) + .expect("should return balances"); + + assert_eq!(values, balances); + } + } - contract - ._mint_batch( - BOB, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into(), - ) - .expect("should mint tokens for BOB"); - token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract.balance_of(BOB, token_id); - assert_eq!(balance, value); - }); + #[motsu::test] + fn error_when_batch_mints_to_invalid_receiver(contract: Erc1155) { + let token_ids = random_token_ids(1); + let values = random_values(1); + let invalid_receiver = Address::ZERO; - contract + let err = contract ._mint_batch( - DAVE, - token_ids.clone(), - values.clone(), + invalid_receiver, + token_ids, + values, vec![0, 1, 2, 3].into(), ) - .expect("should mint tokens for DAVE"); - token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract.balance_of(DAVE, token_id); - assert_eq!(balance, value); - }); + .expect_err("should not batch mint tokens for invalid receiver"); - contract - ._mint_batch( - CHARLIE, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into(), - ) - .expect("should mint tokens for CHARLIE"); - token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { - let balance = contract.balance_of(CHARLIE, token_id); - assert_eq!(balance, value); - }); + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } - let balances = contract - .balance_of_batch(accounts.clone(), token_ids.clone()) - .expect("should return the balances of all accounts"); + #[motsu::test] + fn error_when_batch_mints_not_equal_arrays(contract: Erc1155) { + let token_ids = random_token_ids(3); + let values = random_values(4); + + let err = contract + ._mint_batch(ALICE, token_ids, values, vec![0, 1, 2, 3].into()) + .expect_err( + "should not batch mint tokens when not equal array lengths", + ); - balances.iter().zip(values.iter()).for_each(|(&balance, &value)| { - assert_eq!(balance, value); - }); + assert!(matches!( + err, + Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length, values_length + }) if ids_length == uint!(3_U256) && values_length == uint!(4_U256) + )); } #[motsu::test] diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml index ce0cb4853..1bb24e360 100644 --- a/examples/erc1155/Cargo.toml +++ b/examples/erc1155/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true openzeppelin-stylus.workspace = true alloy-primitives.workspace = true stylus-sdk.workspace = true -mini-alloc.workspace = true [dev-dependencies] alloy.workspace = true From c2ffbde38846208810fe7b58ac2c08a897d85710 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 01:25:46 +0100 Subject: [PATCH 43/60] test: add E2E tests for ERC1155Receiver mints --- examples/erc1155/src/lib.rs | 2 +- examples/erc1155/tests/erc1155.rs | 371 ++++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+), 1 deletion(-) diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 7e624eedf..1739daf7f 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(test), no_main, no_std)] +#![cfg_attr(not(test), no_main)] extern crate alloc; use alloc::vec::Vec; diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 1bc8ea160..d693e56f6 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -120,6 +120,166 @@ async fn mints(alice: Account) -> eyre::Result<()> { Ok(()) } +#[e2e::test] +async fn mints_to_receiver_contract(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_addr = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) + .await?; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let Erc1155::balanceOfReturn { balance: initial_receiver_balance } = + contract.balanceOf(receiver_addr, token_id).call().await?; + + let receipt = + receipt!(contract.mint(receiver_addr, token_id, value, vec![].into()))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: Address::ZERO, + to: receiver_addr, + id: token_id, + value + })); + + assert!(receipt.emits(ERC1155ReceiverMock::Received { + operator: alice_addr, + from: Address::ZERO, + id: token_id, + value, + data: fixed_bytes!("").into(), + })); + + let Erc1155::balanceOfReturn { balance: receiver_balance } = + contract.balanceOf(receiver_addr, token_id).call().await?; + assert_eq!(initial_receiver_balance + value, receiver_balance); + + Ok(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_reverts_with_reason_in_mint( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithMessage, + ) + .await?; + + 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 errors with reason"); + + // assert!(err.reverted_with(stylus_sdk::call::Error::Revert( + // b"ERC1155ReceiverMock: reverting on receive".to_vec() + // ))); + 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?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithoutMessage, + ) + .await?; + + 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(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_panics_in_mint( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) + .await?; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let err = send!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )) + .expect_err("should not mint when receiver panics"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { + receiver: receiver_address + })); + + Ok(()) +} + +#[e2e::test] +async fn errors_when_invalid_receiver_contract_in_mint( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _err = send!(contract.mint( + contract_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )) + .expect_err("should not mint when invalid receiver contract"); + + Ok(()) +} + #[e2e::test] async fn mint_batch( alice: Account, @@ -169,6 +329,217 @@ async fn mint_batch( Ok(()) } +#[e2e::test] +async fn mint_batch_transfer_to_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_addr = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) + .await?; + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let Erc1155::balanceOfBatchReturn { balances: initial_receiver_balances } = + contract + .balanceOfBatch( + vec![receiver_addr, receiver_addr], + token_ids.clone(), + ) + .call() + .await?; + + let receipt = receipt!(contract.mintBatch( + receiver_addr, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: receiver_addr, + ids: token_ids.clone(), + values: values.clone() + })); + + assert!(receipt.emits(ERC1155ReceiverMock::BatchReceived { + operator: alice_addr, + from: Address::ZERO, + ids: token_ids.clone(), + values: values.clone(), + data: fixed_bytes!("").into(), + })); + + let Erc1155::balanceOfBatchReturn { balances: receiver_balances } = + contract + .balanceOfBatch( + vec![receiver_addr, receiver_addr], + token_ids.clone(), + ) + .call() + .await?; + + for (idx, value) in values.iter().enumerate() { + assert_eq!( + initial_receiver_balances[idx] + value, + receiver_balances[idx] + ); + } + + Ok(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_reverts_with_reason_in_batch_mint( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithMessage, + ) + .await?; + + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _err = watch!(contract.mintBatch( + receiver_address, + token_ids.clone(), + values.clone(), + vec![].into() + )) + .expect_err("should not mint batch when receiver errors with reason"); + + // assert!(err.reverted_with(stylus_sdk::call::Error::Revert( + // b"ERC1155ReceiverMock: reverting on receive".to_vec() + // ))); + 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?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = receiver::deploy( + &alice.wallet, + ERC1155ReceiverMock::RevertType::RevertWithoutMessage, + ) + .await?; + + 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(()) +} + +// FIXME: Update our `reverted_with` implementation such that we can also check +// when the error is a `stylus_sdk::call::Error`. +#[e2e::test] +#[ignore] +async fn errors_when_receiver_panics_in_batch_mint( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receiver_address = + receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::Panic) + .await?; + + 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 panics"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidReceiver { + receiver: receiver_address + })); + + Ok(()) +} + +#[e2e::test] +async fn errors_when_invalid_receiver_contract_in_batch_mint( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _err = send!(contract.mintBatch( + contract_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )) + .expect_err("should not mint batch when invalid receiver contract"); + + Ok(()) +} + +#[e2e::test] +async fn error_invalid_array_length_in_batch_mint( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + + let bob_addr = bob.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let err = send!(contract_alice.mintBatch( + bob_addr, + vec![token_ids[0]], + values, + vec![].into() + )) + .expect_err("should return `ERC1155InvalidArrayLength`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidArrayLength { + idsLength: uint!(1_U256), + valuesLength: uint!(2_U256) + })); + + Ok(()) +} + #[e2e::test] async fn set_approval_for_all( alice: Account, From ed1720ce59e14b4118eb9c395b15c977da9d62a6 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 01:38:25 +0100 Subject: [PATCH 44/60] fix: clippy --- contracts/src/token/erc1155/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index e263fbf8c..98cb65d83 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -555,7 +555,7 @@ impl Erc1155 { to, Erc1155ReceiverData::new(ids, values), data.to_vec().into(), - )? + )?; } Ok(()) @@ -804,7 +804,7 @@ impl Erc1155 { Ok(id) => id, Err(e) => { if let call::Error::Revert(ref reason) = e { - if reason.len() > 0 { + if !reason.is_empty() { // Non-IERC1155Receiver implementer. return Err(Error::InvalidReceiverWithReason(e)); } From ffa4cc837a4759e33b94472e334705e9d1d3193f Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 01:52:16 +0100 Subject: [PATCH 45/60] fix: clippy pass by reference --- contracts/src/token/erc1155/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 98cb65d83..75d520111 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -544,7 +544,7 @@ impl Erc1155 { to: Address, ids: Vec, values: Vec, - data: Bytes, + data: &Bytes, ) -> Result<(), Error> { self._update(from, to, ids.clone(), values.clone())?; @@ -876,7 +876,7 @@ impl Erc1155 { to, ids, values, - data, + &data, )?; Ok(()) } @@ -923,7 +923,7 @@ impl Erc1155 { Address::ZERO, ids, values, - vec![].into(), + &vec![].into(), )?; Ok(()) } @@ -983,7 +983,7 @@ impl Erc1155 { sender: from, })); } - self._update_with_acceptance_check(from, to, ids, values, data) + self._update_with_acceptance_check(from, to, ids, values, &data) } /// Transfers a `value` amount of `token_id` from `from` to From d576da84441a736652e51a0c8108d398a412d474 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 02:03:36 +0100 Subject: [PATCH 46/60] fix: clippy --- contracts/src/token/erc1155/mod.rs | 18 +++++++++--------- examples/erc1155/src/lib.rs | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 75d520111..4e07e919b 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -421,7 +421,7 @@ impl IErc1155 for Erc1155 { data: Bytes, ) -> Result<(), Self::Error> { self.authorize_transfer(from)?; - self.do_safe_transfer_from(from, to, vec![id], vec![value], data) + self.do_safe_transfer_from(from, to, vec![id], vec![value], &data) } fn safe_batch_transfer_from( @@ -433,7 +433,7 @@ impl IErc1155 for Erc1155 { data: Bytes, ) -> Result<(), Self::Error> { self.authorize_transfer(from)?; - self.do_safe_transfer_from(from, to, ids, values, data) + self.do_safe_transfer_from(from, to, ids, values, &data) } } @@ -593,7 +593,7 @@ impl Erc1155 { to: Address, id: U256, value: U256, - data: Bytes, + data: &Bytes, ) -> Result<(), Error> { self._do_mint(to, vec![id], vec![value], data) } @@ -635,7 +635,7 @@ impl Erc1155 { to: Address, ids: Vec, values: Vec, - data: Bytes, + data: &Bytes, ) -> Result<(), Error> { self._do_mint(to, ids, values, data) } @@ -864,7 +864,7 @@ impl Erc1155 { to: Address, ids: Vec, values: Vec, - data: Bytes, + data: &Bytes, ) -> Result<(), Error> { if to.is_zero() { return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { @@ -876,7 +876,7 @@ impl Erc1155 { to, ids, values, - &data, + data, )?; Ok(()) } @@ -971,7 +971,7 @@ impl Erc1155 { to: Address, ids: Vec, values: Vec, - data: Bytes, + data: &Bytes, ) -> Result<(), Error> { if to.is_zero() { return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { @@ -983,7 +983,7 @@ impl Erc1155 { sender: from, })); } - self._update_with_acceptance_check(from, to, ids, values, &data) + self._update_with_acceptance_check(from, to, ids, values, data) } /// Transfers a `value` amount of `token_id` from `from` to @@ -1593,7 +1593,7 @@ mod tests { invalid_receiver, token_ids, values, - vec![0, 1, 2, 3].into(), + &vec![0, 1, 2, 3].into(), ) .expect_err("should not transfer tokens to the `Address::ZERO`"); diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 1739daf7f..d3fa59c6e 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -28,7 +28,7 @@ impl Erc1155Example { amount: U256, data: Bytes, ) -> Result<(), Vec> { - self.erc1155._mint(to, token_id, amount, data)?; + self.erc1155._mint(to, token_id, amount, &data)?; Ok(()) } @@ -39,7 +39,7 @@ impl Erc1155Example { amounts: Vec, data: Bytes, ) -> Result<(), Vec> { - self.erc1155._mint_batch(to, token_ids, amounts, data)?; + self.erc1155._mint_batch(to, token_ids, amounts, &data)?; Ok(()) } } From 4bd4d12cf983f9f972c72031152383bfafe0da55 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 14:38:40 +0100 Subject: [PATCH 47/60] fix: tests --- contracts/src/token/erc1155/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 4e07e919b..c2128a16a 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1213,7 +1213,7 @@ mod tests { receiver, token_ids.clone(), values.clone(), - vec![0, 1, 2, 3].into(), + &vec![0, 1, 2, 3].into(), ) .expect("Mint failed"); (token_ids, values) @@ -1317,7 +1317,7 @@ mod tests { let value = random_values(1)[0]; contract - ._mint(alice, token_id, value, vec![0, 1, 2, 3].into()) + ._mint(alice, token_id, value, &vec![0, 1, 2, 3].into()) .expect("should mint tokens for Alice"); let balance = contract.balance_of(alice, token_id); @@ -1332,7 +1332,7 @@ mod tests { let value = random_values(1)[0]; let err = contract - ._mint(invalid_receiver, token_id, value, vec![0, 1, 2, 3].into()) + ._mint(invalid_receiver, token_id, value, &vec![0, 1, 2, 3].into()) .expect_err("should not mint tokens for invalid receiver"); assert!(matches!( @@ -1355,7 +1355,7 @@ mod tests { account, token_ids.clone(), values.clone(), - vec![0, 1, 2, 3].into(), + &vec![0, 1, 2, 3].into(), ) .expect("should batch mint tokens"); @@ -1387,7 +1387,7 @@ mod tests { invalid_receiver, token_ids, values, - vec![0, 1, 2, 3].into(), + &vec![0, 1, 2, 3].into(), ) .expect_err("should not batch mint tokens for invalid receiver"); @@ -1405,7 +1405,7 @@ mod tests { let values = random_values(4); let err = contract - ._mint_batch(ALICE, token_ids, values, vec![0, 1, 2, 3].into()) + ._mint_batch(ALICE, token_ids, values, &vec![0, 1, 2, 3].into()) .expect_err( "should not batch mint tokens when not equal array lengths", ); From e3af8f613665542f7f6afe05315a5eb403fb9f10 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Wed, 30 Oct 2024 14:39:00 +0100 Subject: [PATCH 48/60] fix: disable clippy used_underscore_items --- contracts/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 27b4eaa3d..a3a77988f 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -39,7 +39,11 @@ impl MyContract { } ``` */ -#![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] +#![allow( + clippy::pub_underscore_fields, + clippy::module_name_repetitions, + clippy::used_underscore_items +)] #![cfg_attr(not(feature = "std"), no_std, no_main)] #![deny(rustdoc::broken_intra_doc_links)] extern crate alloc; From 4f16cba3328c8d59e69b792e83bc5b287104c0c2 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Sat, 2 Nov 2024 12:29:20 +0100 Subject: [PATCH 49/60] ref: apply CR changes --- contracts/src/token/erc1155/mod.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index c2128a16a..0ed4e095d 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -389,7 +389,7 @@ impl IErc1155 for Erc1155 { accounts: Vec
, ids: Vec, ) -> Result, Self::Error> { - Self::require_equal_arrays(&ids, &accounts)?; + Self::require_equal_arrays_length(&ids, &accounts)?; let balances: Vec = accounts .iter() @@ -482,13 +482,13 @@ impl Erc1155 { ids: Vec, values: Vec, ) -> Result<(), Error> { - Self::require_equal_arrays(&ids, &values)?; + Self::require_equal_arrays_length(&ids, &values)?; let operator = msg::sender(); - ids.iter().zip(values.iter()).try_for_each(|(&token_id, &value)| { - self.do_update(from, to, token_id, value) - })?; + for (&token_id, &value) in ids.iter().zip(values.iter()) { + self.do_update(from, to, token_id, value)?; + } if ids.len() == 1 { let id = ids[0]; @@ -1032,10 +1032,8 @@ impl Erc1155 { } if !to.is_zero() { - let new_balance = self - ._balances - .setter(token_id) - .setter(to) + let balance = self._balances.getter(token_id).get(to); + let new_balance = balance .checked_add(value) .expect("should not exceed `U256::MAX` for `_balances`"); self._balances.setter(token_id).setter(to).set(new_balance); @@ -1055,7 +1053,7 @@ impl Erc1155 { /// /// If length of `ids` is not equal to length of `values`, then the error /// [`Error::InvalidArrayLength`] is returned. - fn require_equal_arrays( + fn require_equal_arrays_length( ids: &[T], values: &[U], ) -> Result<(), Error> { From 4710faf8df5de3faea4598dbd869cdc51e4f6ec4 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 12:47:53 +0100 Subject: [PATCH 50/60] owner->account in balance_of comment --- contracts/src/token/erc1155/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 0ed4e095d..2fb321372 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -197,7 +197,7 @@ pub trait IErc1155 { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `owner` - Account of the token's owner. + /// * `account` - Account of the token's owner. /// * `id` - Token id as a number. fn balance_of(&self, account: Address, id: U256) -> U256; From 412745d15b2466aebdeb2f2a1afcbd16e9bb0f62 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 12:52:01 +0100 Subject: [PATCH 51/60] Fix minor comment issues --- contracts/src/token/erc1155/mod.rs | 12 ++++++------ contracts/src/token/erc1155/receiver.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 2fb321372..d73f0cf5b 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -511,7 +511,7 @@ impl Erc1155 { /// * `&mut self` - Write access to the contract's state. /// * `from` - Account to transfer tokens from. /// * `to` - Account of the recipient. - /// * `ids` - Array of all token id. + /// * `ids` - Array of all token ids. /// * `values` - Array of all amount of tokens to be transferred. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -750,7 +750,7 @@ impl Erc1155 { /// case of batch transfer on the `to` address. /// /// The acceptance call is not executed and treated as a no-op if the - /// target address is doesn't contain code (i.e. an EOA). Otherwise, + /// target address doesn't contain code (i.e. an EOA). Otherwise, /// the recipient must implement either /// [`IERC1155Receiver::on_erc_1155_received`] for single transfer, or /// [`IERC1155Receiver::on_erc_1155_batch_received`] for a batch transfer, @@ -760,7 +760,7 @@ impl Erc1155 { /// /// * `&mut self` - Write access to the contract's state. /// * `operator` - Generally the address that initiated the token transfer - /// (e.g. `msg.sender`). + /// (e.g. `msg::sender()`). /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `details` - Details about token transfer, check @@ -832,7 +832,7 @@ impl Erc1155 { /// /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient. - /// * `ids` - Array of all tokens ids to be minted. + /// * `ids` - Array of all token ids to be minted. /// * `values` - Array of all amounts of tokens to be minted. /// * `data` - Additional data with no specified format, sent in call to /// `to`. @@ -964,7 +964,7 @@ impl Erc1155 { /// /// # Panics /// - /// Should not panic. + /// If updated balance exceeds `U256::MAX`. fn do_safe_transfer_from( &mut self, from: Address, @@ -1066,7 +1066,7 @@ impl Erc1155 { Ok(()) } - /// Checks if `msg::sender` is authorized to transfer tokens. + /// Checks if `msg::sender()` is authorized to transfer tokens. /// /// # Arguments /// diff --git a/contracts/src/token/erc1155/receiver.rs b/contracts/src/token/erc1155/receiver.rs index 64bb9dd01..49da4798f 100644 --- a/contracts/src/token/erc1155/receiver.rs +++ b/contracts/src/token/erc1155/receiver.rs @@ -31,7 +31,7 @@ sol_interface! { bytes calldata data ) external returns (bytes4); - /// Handles the receipt of a multiple ERC-1155 token types. + /// Handles the receipt of multiple ERC-1155 token types. /// This function is called at the end of a /// [`IErc1155::safe_batch_transfer_from`] /// after the balances have been updated. From 361a2b1aac4e0a3bf2a32fdab73d91345afc5b55 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 13:05:41 +0100 Subject: [PATCH 52/60] docs: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd754d3f5..94cc5d580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- ERC-1155 Multi Token Standard. #275 - `SafeErc20` Utility. #289 - Finite Fields arithmetics. #376 From 537c2aacc34634d8670f1afaa5a9b3c38878ae01 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 12:53:16 +0100 Subject: [PATCH 53/60] more comment fixes --- contracts/src/token/erc1155/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index d73f0cf5b..5e880a91a 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -857,8 +857,7 @@ impl Erc1155 { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`, may happen during `mint` - /// operation. + /// If updated balance exceeds `U256::MAX`. fn _do_mint( &mut self, to: Address, @@ -1004,8 +1003,7 @@ impl Erc1155 { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`, may happen during `mint` - /// operation. + /// If updated balance exceeds `U256::MAX`. fn do_update( &mut self, from: Address, From b48c1fe3b6b5c71a03569851a0577464b39a8cfe Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 12:55:54 +0100 Subject: [PATCH 54/60] remove redundant multiple accounts in mints_batch --- contracts/src/token/erc1155/mod.rs | 40 ++++++++++++------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 5e880a91a..554ae75ca 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1343,33 +1343,25 @@ mod tests { fn mints_batch(contract: Erc1155) { let token_ids = random_token_ids(4); let values = random_values(4); - let accounts = vec![ALICE, BOB, DAVE, CHARLIE]; - for account in accounts { - contract - ._mint_batch( - account, - token_ids.clone(), - values.clone(), - &vec![0, 1, 2, 3].into(), - ) - .expect("should batch mint tokens"); - - token_ids.iter().zip(values.iter()).for_each( - |(&token_id, &value)| { - assert_eq!(value, contract.balance_of(account, token_id)); - }, - ); + contract + ._mint_batch( + ALICE, + token_ids.clone(), + values.clone(), + &vec![0, 1, 2, 3].into(), + ) + .expect("should batch mint tokens"); - let balances = contract - .balance_of_batch( - vec![account, account, account, account], - token_ids.clone(), - ) - .expect("should return balances"); + token_ids.iter().zip(values.iter()).for_each(|(&token_id, &value)| { + assert_eq!(value, contract.balance_of(ALICE, token_id)); + }); - assert_eq!(values, balances); - } + let balances = contract + .balance_of_batch(vec![ALICE; 4], token_ids.clone()) + .expect("should return balances"); + + assert_eq!(values, balances); } #[motsu::test] From 2f531143d912054da0a992c5a458a5f66cb7b7bb Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 12:56:19 +0100 Subject: [PATCH 55/60] remove empty ctor from Erc1155Example --- examples/erc1155/src/constructor.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/erc1155/src/constructor.sol b/examples/erc1155/src/constructor.sol index 47304d9fb..f6452d95e 100644 --- a/examples/erc1155/src/constructor.sol +++ b/examples/erc1155/src/constructor.sol @@ -4,6 +4,4 @@ pragma solidity ^0.8.24; contract Erc1155Example { mapping(address => mapping(uint256 => uint256)) private _balanceOf; mapping(address => mapping(address => bool)) private _isApprovedForAll; - - constructor() {} } From cb1e8c6a1795400147c0f8c944a8d0264b99a104 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 13:42:58 +0100 Subject: [PATCH 56/60] Add missing unit test cases --- contracts/src/token/erc1155/mod.rs | 91 +++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 554ae75ca..4244e23b7 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1269,9 +1269,8 @@ mod tests { .balance_of_batch(accounts, token_ids) .expect("should return a vector of `U256::ZERO`"); - for balance in balances { - assert_eq!(U256::ZERO, balance); - } + let expected = vec![U256::ZERO; 4]; + assert_eq!(expected, balances); } #[motsu::test] @@ -1364,6 +1363,30 @@ mod tests { assert_eq!(values, balances); } + #[motsu::test] + fn mints_batch_same_token(contract: Erc1155) { + let token_id = U256::from(1); + let values = random_values(4); + let expected_balance: U256 = values.iter().sum(); + + contract + ._mint_batch( + ALICE, + vec![token_id; 4], + values.clone(), + &vec![0, 1, 2, 3].into(), + ) + .expect("should batch mint tokens"); + + assert_eq!(expected_balance, contract.balance_of(ALICE, token_id)); + + let balances = contract + .balance_of_batch(vec![ALICE; 4], vec![token_id; 4]) + .expect("should return balances"); + + assert_eq!(vec![expected_balance; 4], balances); + } + #[motsu::test] fn error_when_batch_mints_to_invalid_receiver(contract: Erc1155) { let token_ids = random_token_ids(1); @@ -1813,6 +1836,36 @@ mod tests { )); } + #[motsu::test] + fn error_when_not_equal_arrays_safe_batch_transfer_from(contract: Erc1155) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 4); + + contract._operator_approvals.setter(DAVE).setter(alice).set(true); + + let err = contract + .safe_batch_transfer_from( + DAVE, + CHARLIE, + token_ids.clone(), + values + .into_iter() + .chain(std::iter::once(U256::from(4))) + .collect(), + vec![].into(), + ) + .expect_err( + "should not transfer tokens when not equal array lengths", + ); + + assert!(matches!( + err, + Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length, values_length + }) if ids_length == uint!(4_U256) && values_length == uint!(5_U256) + )); + } + #[motsu::test] fn safe_batch_transfer_from_with_data(contract: Erc1155) { let alice = msg::sender(); @@ -1950,6 +2003,38 @@ mod tests { )); } + #[motsu::test] + fn error_when_not_equal_arrays_safe_batch_transfer_from_with_data( + contract: Erc1155, + ) { + let alice = msg::sender(); + let (token_ids, values) = init(contract, alice, 4); + + contract._operator_approvals.setter(DAVE).setter(alice).set(true); + + let err = contract + .safe_batch_transfer_from( + DAVE, + CHARLIE, + token_ids.clone(), + values + .into_iter() + .chain(std::iter::once(U256::from(4))) + .collect(), + vec![0, 1, 2, 3].into(), + ) + .expect_err( + "should not transfer tokens when not equal array lengths", + ); + + assert!(matches!( + err, + Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length, values_length + }) if ids_length == uint!(4_U256) && values_length == uint!(5_U256) + )); + } + #[motsu::test] fn interface_id() { let actual = ::INTERFACE_ID; From d892603ce1e10de57aa36c99091d7b1f2e4210c5 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 14:13:03 +0100 Subject: [PATCH 57/60] add missing unit tests --- contracts/src/token/erc1155/mod.rs | 165 +++++++++++++++++++++++++++-- scripts/e2e-tests.sh | 2 +- 2 files changed, 157 insertions(+), 10 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 4244e23b7..119503665 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -1215,6 +1215,10 @@ mod tests { (token_ids, values) } + fn append(values: Vec, value: u64) -> Vec { + values.into_iter().chain(std::iter::once(U256::from(value))).collect() + } + #[test] fn should_create_transfer_single() { let id = uint!(1_U256); @@ -1365,7 +1369,7 @@ mod tests { #[motsu::test] fn mints_batch_same_token(contract: Erc1155) { - let token_id = U256::from(1); + let token_id = uint!(1_U256); let values = random_values(4); let expected_balance: U256 = values.iter().sum(); @@ -1429,6 +1433,155 @@ mod tests { )); } + #[motsu::test] + fn burns(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 1); + let token_id = token_ids[0]; + let value = values[0]; + + contract._burn(ALICE, token_id, value).expect("should burn tokens"); + + let balances = contract.balance_of(ALICE, token_id); + + assert_eq!(U256::ZERO, balances); + } + + #[motsu::test] + fn error_when_burns_from_invalid_sender(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 1); + let invalid_sender = Address::ZERO; + + let err = contract + ._burn(invalid_sender, token_ids[0], values[0]) + .expect_err("should not burn token for invalid sender"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn error_when_burns_with_insufficient_balance(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 1); + + let err = contract + ._burn(ALICE, token_ids[0], values[0] + uint!(1_U256)) + .expect_err("should not burn token when insufficient balance"); + + assert!(matches!( + err, + Error::InsufficientBalance(ERC1155InsufficientBalance { + sender, + balance, + needed, + token_id + }) if sender == ALICE && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] + )); + } + + #[motsu::test] + fn burns_batch(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 4); + + contract + ._burn_batch(ALICE, token_ids.clone(), values.clone()) + .expect("should batch burn tokens"); + + let balances = contract + .balance_of_batch(vec![ALICE; 4], token_ids.clone()) + .expect("should return balances"); + + assert_eq!(vec![U256::ZERO; 4], balances); + } + + #[motsu::test] + fn burns_batch_same_token(contract: Erc1155) { + let token_id = uint!(1_U256); + let value = uint!(80_U256); + + contract + ._mint(ALICE, token_id, value, &vec![0, 1, 2, 3].into()) + .expect("should mint token"); + + contract + ._burn_batch( + ALICE, + vec![token_id; 4], + vec![ + uint!(20_U256), + uint!(10_U256), + uint!(30_U256), + uint!(20_U256), + ], + ) + .expect("should batch burn tokens"); + + assert_eq!(U256::ZERO, contract.balance_of(ALICE, token_id)); + } + + #[motsu::test] + fn error_when_batch_burns_from_invalid_sender(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 4); + let invalid_sender = Address::ZERO; + + let err = contract + ._burn_batch(invalid_sender, token_ids, values) + .expect_err("should not batch burn tokens for invalid sender"); + + assert!(matches!( + err, + Error::InvalidSender(ERC1155InvalidSender { + sender + }) if sender == invalid_sender + )); + } + + #[motsu::test] + fn error_when_batch_burns_with_insufficient_balance(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 4); + + let err = contract + ._burn_batch( + ALICE, + token_ids.clone(), + values.clone().into_iter().map(|x| x + uint!(1_U256)).collect(), + ) + .expect_err( + "should not batch burn tokens when insufficient balance", + ); + + assert!(matches!( + err, + Error::InsufficientBalance(ERC1155InsufficientBalance { + sender, + balance, + needed, + token_id + }) if sender == ALICE && balance == values[0] && needed == values[0] + uint!(1_U256) && token_id == token_ids[0] + )); + } + + #[motsu::test] + fn error_when_batch_burns_not_equal_arrays(contract: Erc1155) { + let (token_ids, values) = init(contract, ALICE, 3); + + let err = contract + ._burn_batch(ALICE, token_ids, append(values, 4)) + .expect_err( + "should not batch burn tokens when not equal array lengths", + ); + + assert!(matches!( + err, + Error::InvalidArrayLength(ERC1155InvalidArrayLength { + ids_length, values_length + }) if ids_length == uint!(3_U256) && values_length == uint!(4_U256) + )); + } + #[motsu::test] fn safe_transfer_from(contract: Erc1155) { let alice = msg::sender(); @@ -1848,10 +2001,7 @@ mod tests { DAVE, CHARLIE, token_ids.clone(), - values - .into_iter() - .chain(std::iter::once(U256::from(4))) - .collect(), + append(values, 4), vec![].into(), ) .expect_err( @@ -2017,10 +2167,7 @@ mod tests { DAVE, CHARLIE, token_ids.clone(), - values - .into_iter() - .chain(std::iter::once(U256::from(4))) - .collect(), + append(values, 4), vec![0, 1, 2, 3].into(), ) .expect_err( diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 87c4fe654..16ab17e1f 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -9,4 +9,4 @@ cargo build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abo export RPC_URL=http://localhost:8547 -cargo test --features std,e2e --test "*" +cargo test --features std,e2e --test "erc1155" From 4ac18dcda59a5c9bddd95fd9f06b8033266596b2 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 7 Nov 2024 14:54:08 +0100 Subject: [PATCH 58/60] Add missing burn-related e2e tests --- contracts/src/token/erc1155/mod.rs | 4 +- examples/erc1155/src/lib.rs | 20 ++ examples/erc1155/tests/abi/mod.rs | 2 + examples/erc1155/tests/erc1155.rs | 290 +++++++++++++++++++++++++++++ scripts/e2e-tests.sh | 2 +- 5 files changed, 315 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index 119503665..e128f6868 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -663,7 +663,7 @@ impl Erc1155 { /// # Panics /// /// Should not panic. - fn _burn( + pub fn _burn( &mut self, from: Address, id: U256, @@ -698,7 +698,7 @@ impl Erc1155 { /// # Panics /// /// Should not panic. - fn _burn_batch( + pub fn _burn_batch( &mut self, from: Address, ids: Vec, diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index d3fa59c6e..1e9481e53 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -42,4 +42,24 @@ impl Erc1155Example { self.erc1155._mint_batch(to, token_ids, amounts, &data)?; Ok(()) } + + pub fn burn( + &mut self, + to: Address, + token_id: U256, + amount: U256, + ) -> Result<(), Vec> { + self.erc1155._burn(to, token_id, amount)?; + Ok(()) + } + + pub fn burn_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + ) -> Result<(), Vec> { + self.erc1155._burn_batch(to, token_ids, amounts)?; + Ok(()) + } } diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index b7b13213d..da9237313 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -13,6 +13,8 @@ sol!( function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; function mint(address to, uint256 id, uint256 amount, bytes memory data) external; function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; + function burn(address from, uint256 id, uint256 amount) external; + function burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) external; error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); error ERC1155InvalidOperator(address operator); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index d693e56f6..d839e5c76 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -540,6 +540,296 @@ async fn error_invalid_array_length_in_batch_mint( Ok(()) } +#[e2e::test] +async fn burns(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + let Erc1155::balanceOfReturn { balance: initial_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(value, initial_balance); + + let receipt = receipt!(contract.burn(alice_addr, token_id, value,))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + id: token_id, + value + })); + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + + assert_eq!(U256::ZERO, balance); + + Ok(()) +} + +#[e2e::test] +async fn error_when_burns_from_invalid_sender( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + let invalid_sender = Address::ZERO; + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + let err = send!(contract.burn(invalid_sender, token_id, value,)) + .expect_err("should return `ERC1155InvalidSender`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { + sender: invalid_sender, + })); + + Ok(()) +} + +#[e2e::test] +async fn error_when_burns_with_insufficient_balance( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + let err = + send!(contract.burn(alice_addr, token_id, value + uint!(1_U256),)) + .expect_err("should return `ERC1155InsufficientBalance`"); + + assert!(err.reverted_with(Erc1155::ERC1155InsufficientBalance { + sender: alice_addr, + balance: value, + needed: value + uint!(1_U256), + id: token_id + })); + + Ok(()) +} + +#[e2e::test] +async fn burns_batch(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + let Erc1155::balanceOfBatchReturn { balances: initial_balances } = contract + .balanceOfBatch(vec![alice_addr; 3], token_ids.clone()) + .call() + .await?; + assert_eq!(values, initial_balances); + + let receipt = receipt!(contract.burnBatch( + alice_addr, + token_ids.clone(), + values.clone(), + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + ids: token_ids.clone(), + values: values.clone(), + })); + + let Erc1155::balanceOfBatchReturn { balances } = + contract.balanceOfBatch(vec![alice_addr; 3], token_ids).call().await?; + + assert_eq!(vec![U256::ZERO; 3], balances); + + Ok(()) +} + +#[e2e::test] +async fn burns_batch_same_token(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = uint!(80_U256); + + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + ))?; + + let Erc1155::balanceOfReturn { balance: initial_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(value, initial_balance); + + let token_ids = vec![token_id; 4]; + let values = + vec![uint!(20_U256), uint!(10_U256), uint!(30_U256), uint!(20_U256)]; + let receipt = receipt!(contract.burnBatch( + alice_addr, + token_ids.clone(), + values.clone(), + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + ids: token_ids, + values, + })); + + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + + assert_eq!(U256::ZERO, balance); + + Ok(()) +} + +#[e2e::test] +async fn error_when_batch_burns_from_invalid_sender( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + let invalid_sender = Address::ZERO; + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + let err = send!(contract.burnBatch( + invalid_sender, + token_ids.clone(), + values.clone(), + )) + .expect_err("should return `ERC1155InvalidSender`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { + sender: invalid_sender, + })); + + Ok(()) +} + +#[e2e::test] +async fn error_when_batch_burns_with_insufficient_balance( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + let err = send!(contract.burnBatch( + alice_addr, + token_ids.clone(), + values.clone().into_iter().map(|x| x + uint!(1_U256)).collect(), + )) + .expect_err("should return `ERC1155InsufficientBalance`"); + + assert!(err.reverted_with(Erc1155::ERC1155InsufficientBalance { + sender: alice_addr, + balance: values[0], + needed: values[0] + uint!(1_U256), + id: token_ids[0] + })); + + Ok(()) +} + +#[e2e::test] +async fn error_when_batch_burns_not_equal_arrays( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; + + let err = send!(contract.burnBatch( + alice_addr, + token_ids.clone(), + values + .clone() + .into_iter() + .chain(std::iter::once(U256::from(4))) + .collect(), + )) + .expect_err("should return `ERC1155InvalidArrayLength`"); + + assert!(err.reverted_with(Erc1155::ERC1155InvalidArrayLength { + idsLength: uint!(3_U256), + valuesLength: uint!(4_U256) + })); + + Ok(()) +} + #[e2e::test] async fn set_approval_for_all( alice: Account, diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 16ab17e1f..87c4fe654 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -9,4 +9,4 @@ cargo build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abo export RPC_URL=http://localhost:8547 -cargo test --features std,e2e --test "erc1155" +cargo test --features std,e2e --test "*" From 37d0ce144b60c1acf013aca9b2243aa99e73c8e0 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 17:58:56 +0100 Subject: [PATCH 59/60] chore: revert burnable E2E tests --- examples/erc1155/src/lib.rs | 20 --- examples/erc1155/tests/abi/mod.rs | 2 - examples/erc1155/tests/erc1155.rs | 290 ------------------------------ 3 files changed, 312 deletions(-) diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 1e9481e53..d3fa59c6e 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -42,24 +42,4 @@ impl Erc1155Example { self.erc1155._mint_batch(to, token_ids, amounts, &data)?; Ok(()) } - - pub fn burn( - &mut self, - to: Address, - token_id: U256, - amount: U256, - ) -> Result<(), Vec> { - self.erc1155._burn(to, token_id, amount)?; - Ok(()) - } - - pub fn burn_batch( - &mut self, - to: Address, - token_ids: Vec, - amounts: Vec, - ) -> Result<(), Vec> { - self.erc1155._burn_batch(to, token_ids, amounts)?; - Ok(()) - } } diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index da9237313..b7b13213d 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -13,8 +13,6 @@ sol!( function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; function mint(address to, uint256 id, uint256 amount, bytes memory data) external; function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; - function burn(address from, uint256 id, uint256 amount) external; - function burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) external; error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); error ERC1155InvalidOperator(address operator); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index d839e5c76..d693e56f6 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -540,296 +540,6 @@ async fn error_invalid_array_length_in_batch_mint( Ok(()) } -#[e2e::test] -async fn burns(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_id = random_token_ids(1)[0]; - let value = random_values(1)[0]; - let _ = watch!(contract.mint( - alice_addr, - token_id, - value, - vec![0, 1, 2, 3].into() - ))?; - - let Erc1155::balanceOfReturn { balance: initial_balance } = - contract.balanceOf(alice_addr, token_id).call().await?; - assert_eq!(value, initial_balance); - - let receipt = receipt!(contract.burn(alice_addr, token_id, value,))?; - - assert!(receipt.emits(Erc1155::TransferSingle { - operator: alice_addr, - from: alice_addr, - to: Address::ZERO, - id: token_id, - value - })); - - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(alice_addr, token_id).call().await?; - - assert_eq!(U256::ZERO, balance); - - Ok(()) -} - -#[e2e::test] -async fn error_when_burns_from_invalid_sender( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - let invalid_sender = Address::ZERO; - - let alice_addr = alice.address(); - let token_id = random_token_ids(1)[0]; - let value = random_values(1)[0]; - let _ = watch!(contract.mint( - alice_addr, - token_id, - value, - vec![0, 1, 2, 3].into() - ))?; - - let err = send!(contract.burn(invalid_sender, token_id, value,)) - .expect_err("should return `ERC1155InvalidSender`"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { - sender: invalid_sender, - })); - - Ok(()) -} - -#[e2e::test] -async fn error_when_burns_with_insufficient_balance( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_id = random_token_ids(1)[0]; - let value = random_values(1)[0]; - let _ = watch!(contract.mint( - alice_addr, - token_id, - value, - vec![0, 1, 2, 3].into() - ))?; - - let err = - send!(contract.burn(alice_addr, token_id, value + uint!(1_U256),)) - .expect_err("should return `ERC1155InsufficientBalance`"); - - assert!(err.reverted_with(Erc1155::ERC1155InsufficientBalance { - sender: alice_addr, - balance: value, - needed: value + uint!(1_U256), - id: token_id - })); - - Ok(()) -} - -#[e2e::test] -async fn burns_batch(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_ids = random_token_ids(3); - let values = random_values(3); - - let _ = watch!(contract.mintBatch( - alice_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - let Erc1155::balanceOfBatchReturn { balances: initial_balances } = contract - .balanceOfBatch(vec![alice_addr; 3], token_ids.clone()) - .call() - .await?; - assert_eq!(values, initial_balances); - - let receipt = receipt!(contract.burnBatch( - alice_addr, - token_ids.clone(), - values.clone(), - ))?; - - assert!(receipt.emits(Erc1155::TransferBatch { - operator: alice_addr, - from: alice_addr, - to: Address::ZERO, - ids: token_ids.clone(), - values: values.clone(), - })); - - let Erc1155::balanceOfBatchReturn { balances } = - contract.balanceOfBatch(vec![alice_addr; 3], token_ids).call().await?; - - assert_eq!(vec![U256::ZERO; 3], balances); - - Ok(()) -} - -#[e2e::test] -async fn burns_batch_same_token(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_id = random_token_ids(1)[0]; - let value = uint!(80_U256); - - let _ = watch!(contract.mint( - alice_addr, - token_id, - value, - vec![0, 1, 2, 3].into() - ))?; - - let Erc1155::balanceOfReturn { balance: initial_balance } = - contract.balanceOf(alice_addr, token_id).call().await?; - assert_eq!(value, initial_balance); - - let token_ids = vec![token_id; 4]; - let values = - vec![uint!(20_U256), uint!(10_U256), uint!(30_U256), uint!(20_U256)]; - let receipt = receipt!(contract.burnBatch( - alice_addr, - token_ids.clone(), - values.clone(), - ))?; - - assert!(receipt.emits(Erc1155::TransferBatch { - operator: alice_addr, - from: alice_addr, - to: Address::ZERO, - ids: token_ids, - values, - })); - - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(alice_addr, token_id).call().await?; - - assert_eq!(U256::ZERO, balance); - - Ok(()) -} - -#[e2e::test] -async fn error_when_batch_burns_from_invalid_sender( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_ids = random_token_ids(3); - let values = random_values(3); - let invalid_sender = Address::ZERO; - - let _ = watch!(contract.mintBatch( - alice_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - let err = send!(contract.burnBatch( - invalid_sender, - token_ids.clone(), - values.clone(), - )) - .expect_err("should return `ERC1155InvalidSender`"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidSender { - sender: invalid_sender, - })); - - Ok(()) -} - -#[e2e::test] -async fn error_when_batch_burns_with_insufficient_balance( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_ids = random_token_ids(3); - let values = random_values(3); - - let _ = watch!(contract.mintBatch( - alice_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - let err = send!(contract.burnBatch( - alice_addr, - token_ids.clone(), - values.clone().into_iter().map(|x| x + uint!(1_U256)).collect(), - )) - .expect_err("should return `ERC1155InsufficientBalance`"); - - assert!(err.reverted_with(Erc1155::ERC1155InsufficientBalance { - sender: alice_addr, - balance: values[0], - needed: values[0] + uint!(1_U256), - id: token_ids[0] - })); - - Ok(()) -} - -#[e2e::test] -async fn error_when_batch_burns_not_equal_arrays( - alice: Account, -) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_ids = random_token_ids(3); - let values = random_values(3); - - let _ = watch!(contract.mintBatch( - alice_addr, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - let err = send!(contract.burnBatch( - alice_addr, - token_ids.clone(), - values - .clone() - .into_iter() - .chain(std::iter::once(U256::from(4))) - .collect(), - )) - .expect_err("should return `ERC1155InvalidArrayLength`"); - - assert!(err.reverted_with(Erc1155::ERC1155InvalidArrayLength { - idsLength: uint!(3_U256), - valuesLength: uint!(4_U256) - })); - - Ok(()) -} - #[e2e::test] async fn set_approval_for_all( alice: Account, From 0b37cf523da7fc42ed10f16026cf70a1c8d2d9bc Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Thu, 7 Nov 2024 22:33:58 +0100 Subject: [PATCH 60/60] fix: benches --- benches/src/erc1155.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/benches/src/erc1155.rs b/benches/src/erc1155.rs index 54b0e67cb..4806a1155 100644 --- a/benches/src/erc1155.rs +++ b/benches/src/erc1155.rs @@ -3,7 +3,7 @@ use alloy::{ primitives::Address, providers::ProviderBuilder, sol, - sol_types::{SolCall, SolConstructor}, + sol_types::SolCall, uint, }; use e2e::{receipt, Account}; @@ -100,7 +100,5 @@ async fn deploy( account: &Account, cache_opt: CacheOpt, ) -> eyre::Result
{ - let args = Erc1155Example::constructorCall {}; - let args = alloy::hex::encode(args.abi_encode()); - crate::deploy(account, "erc1155", Some(args), cache_opt).await + crate::deploy(account, "erc1155", None, cache_opt).await }