From ffe29aa56387d6e8f7c23e303358a0a5044c207a Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 22 Nov 2024 12:52:23 +0100 Subject: [PATCH 01/54] feat: add erc1155 supply extension --- contracts/src/token/erc1155/extensions/mod.rs | 4 + .../src/token/erc1155/extensions/supply.rs | 562 ++++++++++++++++++ contracts/src/token/erc1155/mod.rs | 2 + 3 files changed, 568 insertions(+) create mode 100644 contracts/src/token/erc1155/extensions/mod.rs create mode 100644 contracts/src/token/erc1155/extensions/supply.rs diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs new file mode 100644 index 000000000..772d8e2b9 --- /dev/null +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -0,0 +1,4 @@ +//! Common extensions to the ERC-1155 standard. +pub mod supply; + +pub use supply::Erc1155Supply; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs new file mode 100644 index 000000000..c2f4738e9 --- /dev/null +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -0,0 +1,562 @@ +//! 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::Vec}; + +use alloy_primitives::{Address, U256}; +use stylus_sdk::{ + abi::Bytes, + msg, + prelude::{public, sol_storage}, + storage::TopLevelStorage, +}; + +use crate::{ + token::erc1155::{self, Erc1155, IErc1155}, + utils::math::storage::SubAssignUnchecked, +}; + +sol_storage! { + /// State of an [`Erc1155Supply`] token. + 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; + } +} + +/// 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 Erc1155Supply {} + +#[public] +impl Erc1155Supply { + /// Total value of tokens in with a given id. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `id` - Token id. + pub fn total_supply(&self, token_id: U256) -> U256 { + self._total_supply.get(token_id) + } + + /// Total value of tokens. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + #[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. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `id` - Token id. + pub fn exists(&self, token_id: U256) -> bool { + self.total_supply(token_id) > U256::ZERO + } + + /// Returns the value of tokens of type `id` owned by `account`. + /// + /// Re-export of [`Erc1155::balance_of`] + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `account` - Account of the token's owner. + /// * `id` - Token id as a number. + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + /// Batched version of [`Erc1155::balance_of`]. + /// + /// Re-export of [`Erc1155::balance_of_batch`] + /// + /// # 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. + /// + /// # Errors + /// + /// * If the length of `accounts` is not equal to the length of `ids`, + /// then the error [`erc1155::Error::InvalidArrayLength`] is returned. + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, erc1155::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + /// Grants or revokes permission to `operator` + /// to transfer the caller's tokens, according to `approved`. + /// + /// Re-export of [`Erc1155::set_approval_for_all`] + /// + /// # 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 + /// [`erc1155::Error::InvalidOperator`] is returned. + /// + /// # Requirements + /// + /// * The `operator` cannot be the `Address::ZERO`. + /// + /// # Events + /// + /// Emits an [`erc1155::ApprovalForAll`] event. + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), erc1155::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + /// Returns true if `operator` is approved to transfer `account`'s + /// tokens. + /// + /// Re-export of [`Erc1155::is_approved_for_all`] + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `account` - Account of the token's owner. + /// * `operator` - Account to be checked. + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + /// Transfers a `value` amount of tokens of type `id` 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. + /// * `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 `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If `from` is `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidSender`] is returned. + /// If the `from` is not the caller (`msg::sender()`), + /// and the caller does not have the right to approve, then the error + /// [`erc1155::Error::MissingApprovalForAll`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # 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 of type `id` of at least `value` + /// amount. + /// * If `to` refers to a smart contract, it must implement + /// [`erc1155::IERC1155Receiver::on_erc_1155_received`] and return the + /// acceptance value. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event. + /// + /// # Panics + /// + /// Should not panic. + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155.authorize_transfer(from)?; + self.do_safe_transfer_from(from, to, vec![id], vec![value], &data) + } + + /// Batched version of [`Erc1155::safe_transfer_from`]. + /// + /// # 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 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 + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If `from` is `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::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 + /// [`erc1155::Error::MissingApprovalForAll`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't + /// returned its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # 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 + /// [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] and return + /// the acceptance magic value. + /// + /// # Events + /// + /// Emits either a [`TransferSingle`] or a [`erc1155::TransferBatch`] event, + /// depending on the length of the array arguments. + /// + /// # Panics + /// + /// Should not panic. + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155.authorize_transfer(from)?; + self.do_safe_transfer_from(from, to, ids, values, &data) + } +} + +impl Erc1155Supply { + /// 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 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 the `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If `from` is the `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't + /// returned its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + fn do_safe_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: &Bytes, + ) -> Result<(), erc1155::Error> { + if to.is_zero() { + return Err(erc1155::Error::InvalidReceiver( + erc1155::ERC1155InvalidReceiver { receiver: to }, + )); + } + if from.is_zero() { + return Err(erc1155::Error::InvalidSender( + erc1155::ERC1155InvalidSender { sender: from }, + )); + } + self._update_with_acceptance_check(from, to, ids, values, data) + } + + /// Version of [`Self::_update`] that performs the token acceptance check by + /// calling [`erc1155::IERC1155Receiver::on_erc_1155_received`] or + /// [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] on the + /// receiver address if it contains code. + /// + /// # 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 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 length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't + /// returned its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`, may happen during `mint` + /// operation. + fn _update_with_acceptance_check( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: &Bytes, + ) -> Result<(), erc1155::Error> { + self._update(from, to, ids.clone(), values.clone())?; + + if !to.is_zero() { + self.erc1155._check_on_erc1155_received( + msg::sender(), + from, + to, + erc1155::Erc1155ReceiverData::new(ids, values), + data.to_vec().into(), + )?; + } + + Ok(()) + } + + /// 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 [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + pub fn _update( + &mut self, + from: Address, + to: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), erc1155::Error> { + self.erc1155._update(from, to, token_ids.clone(), values.clone())?; + + if from.is_zero() { + let mut total_mint_value = U256::ZERO; + 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 = U256::ZERO; + token_ids.iter().zip(values.iter()).for_each( + |(&token_id, &value)| { + /* + SAFETY: Overflow not possible: + values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) + */ + self._total_supply + .setter(token_id) + .sub_assign_unchecked(value); + /* + SAFETY: Overflow not possible: + sum_i(values[i]) <= sum_i(total_supply(token_ids[i])) <= total_supply_all + */ + total_burn_value += value; + }, + ); + /* + SAFETY: Overflow not possible: + totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + */ + 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/contracts/src/token/erc1155/mod.rs b/contracts/src/token/erc1155/mod.rs index e128f6868..95813ae0d 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -17,6 +17,8 @@ use crate::utils::{ math::storage::SubAssignUnchecked, }; +pub mod extensions; + mod receiver; pub use receiver::IERC1155Receiver; From 4f3cbf0eecd485242acddff06b01b48828898e19 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 08:24:49 +0100 Subject: [PATCH 02/54] docs: add missing colon (:) in _do_mint errors --- 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 95813ae0d..baf429402 100644 --- a/contracts/src/token/erc1155/mod.rs +++ b/contracts/src/token/erc1155/mod.rs @@ -842,7 +842,7 @@ impl Erc1155 { /// # Errors /// /// If `to` is `Address::ZERO`, then the error - /// [`Error:InvalidReceiver`] is returned. + /// [`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 From 07280d7c8c5c17d4061af8dc2806af8dc532b141 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 08:26:55 +0100 Subject: [PATCH 03/54] feat: add missing fns that use _update --- .../src/token/erc1155/extensions/supply.rs | 432 ++++++++++++++---- 1 file changed, 342 insertions(+), 90 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index c2f4738e9..aa854e51a 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::Vec}; use alloy_primitives::{Address, U256}; @@ -281,62 +282,76 @@ impl Erc1155Supply { } impl Erc1155Supply { - /// Transfers `values` of tokens specified by `ids` from `from` to `to`. + /// Override of [`Erc1155::_update`] that restricts normal minting to after + /// construction. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account to transfer tokens from. + /// * `from` - Account of the sender. /// * `to` - Account of the recipient. - /// * `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`. - /// - /// # Errors - /// - /// If `to` is the `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If `from` is the `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidSender`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't - /// returned its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. + /// * `token_ids` - Array of all token id. + /// * `values` - Array of all amount of tokens to be supplied. /// /// # Events /// /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// If updated balance exceeds `U256::MAX`. - fn do_safe_transfer_from( + pub fn _update( &mut self, from: Address, to: Address, - ids: Vec, + token_ids: Vec, values: Vec, - data: &Bytes, ) -> Result<(), erc1155::Error> { - if to.is_zero() { - return Err(erc1155::Error::InvalidReceiver( - erc1155::ERC1155InvalidReceiver { receiver: to }, - )); - } + self.erc1155._update(from, to, token_ids.clone(), values.clone())?; + if from.is_zero() { - return Err(erc1155::Error::InvalidSender( - erc1155::ERC1155InvalidSender { sender: from }, - )); + let mut total_mint_value = U256::ZERO; + 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); } - self._update_with_acceptance_check(from, to, ids, values, data) + + if to.is_zero() { + let mut total_burn_value = U256::ZERO; + token_ids.iter().zip(values.iter()).for_each( + |(&token_id, &value)| { + /* + SAFETY: Overflow not possible: + values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) + */ + self._total_supply + .setter(token_id) + .sub_assign_unchecked(value); + /* + SAFETY: Overflow not possible: + sum_i(values[i]) <= sum_i(total_supply(token_ids[i])) <= total_supply_all + */ + total_burn_value += value; + }, + ); + /* + SAFETY: Overflow not possible: + totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + */ + let total_supply_all = + self._total_supply_all.get() - total_burn_value; + self._total_supply_all.set(total_supply_all); + } + Ok(()) } /// Version of [`Self::_update`] that performs the token acceptance check by @@ -399,76 +414,313 @@ impl Erc1155Supply { Ok(()) } - /// Override of [`Erc1155::_update`] that restricts normal minting to after - /// construction. + /// Creates `values` of tokens specified by `ids`, and assigns + /// them to `to`. Performs the token acceptance check by + /// calling [`erc1155::IERC1155Receiver::on_erc_1155_received`] or + /// [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] on the `to` + /// address if it contains code. /// /// # 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. + /// * `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`. + /// + /// # Errors + /// + /// If `to` is `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't + /// returned its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. /// /// # Events /// /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one /// element, and [`erc1155::TransferBatch`] otherwise. - pub fn _update( + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + fn _do_mint( &mut self, - from: Address, to: Address, - token_ids: Vec, + ids: Vec, values: Vec, - ) -> Result<(), erc1155::Error> { - self.erc1155._update(from, to, token_ids.clone(), values.clone())?; + 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(()) + } + /// 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 + /// [`erc1155::Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// Should not panic. + fn _do_burn( + &mut self, + from: Address, + ids: Vec, + values: Vec, + ) -> Result<(), Error> { if from.is_zero() { - let mut total_mint_value = U256::ZERO; - 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); + return Err(Error::InvalidSender(ERC1155InvalidSender { + sender: from, + })); } + self._update_with_acceptance_check( + from, + Address::ZERO, + ids, + values, + &vec![].into(), + )?; + Ok(()) + } + + /// 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`. + /// + /// # Errors + /// + /// If `to` is `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + 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`]. + /// + /// # 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 + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't + /// returned its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + pub fn _mint_batch( + &mut self, + to: Address, + ids: Vec, + values: Vec, + data: &Bytes, + ) -> Result<(), Error> { + self._do_mint(to, ids, values, data) + } + + /// Destroys a `value` amount of tokens of type `id` from `from`. + /// + /// # Arguments + /// + /// * `&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 + /// [`erc1155::Error::InvalidSender`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event. + /// + /// # Panics + /// + /// Should not panic. + pub fn _burn( + &mut self, + from: Address, + id: U256, + value: U256, + ) -> Result<(), Error> { + self._do_burn(from, vec![id], vec![value]) + } + + /// Batched version of [`Self::_burn`]. + /// + /// # Arguments + /// + /// * `&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 + /// [`erc1155::Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// Should not panic. + pub fn _burn_batch( + &mut self, + from: Address, + ids: Vec, + values: Vec, + ) -> Result<(), Error> { + self._do_burn(from, ids, values) + } + + /// 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 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 the `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If `from` is the `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't + /// returned its interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + fn do_safe_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: &Bytes, + ) -> Result<(), erc1155::Error> { if to.is_zero() { - let mut total_burn_value = U256::ZERO; - token_ids.iter().zip(values.iter()).for_each( - |(&token_id, &value)| { - /* - SAFETY: Overflow not possible: - values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) - */ - self._total_supply - .setter(token_id) - .sub_assign_unchecked(value); - /* - SAFETY: Overflow not possible: - sum_i(values[i]) <= sum_i(total_supply(token_ids[i])) <= total_supply_all - */ - total_burn_value += value; - }, - ); - /* - SAFETY: Overflow not possible: - totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll - */ - let total_supply_all = - self._total_supply_all.get() - total_burn_value; - self._total_supply_all.set(total_supply_all); + return Err(erc1155::Error::InvalidReceiver( + erc1155::ERC1155InvalidReceiver { receiver: to }, + )); } - Ok(()) + if from.is_zero() { + return Err(erc1155::Error::InvalidSender( + erc1155::ERC1155InvalidSender { sender: from }, + )); + } + self._update_with_acceptance_check(from, to, ids, values, data) } } From 593382f940d23a192bdaa0b9a2a7f3f00866ce6a Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 08:43:28 +0100 Subject: [PATCH 04/54] test: fix tests --- .../src/token/erc1155/extensions/supply.rs | 62 +++++++------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index aa854e51a..8b321dd48 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -456,11 +456,11 @@ impl Erc1155Supply { ids: Vec, values: Vec, data: &Bytes, - ) -> Result<(), Error> { + ) -> Result<(), erc1155::Error> { if to.is_zero() { - return Err(Error::InvalidReceiver(ERC1155InvalidReceiver { - receiver: to, - })); + return Err(erc1155::Error::InvalidReceiver( + erc1155::ERC1155InvalidReceiver { receiver: to }, + )); } self._update_with_acceptance_check( Address::ZERO, @@ -503,11 +503,11 @@ impl Erc1155Supply { from: Address, ids: Vec, values: Vec, - ) -> Result<(), Error> { + ) -> Result<(), erc1155::Error> { if from.is_zero() { - return Err(Error::InvalidSender(ERC1155InvalidSender { - sender: from, - })); + return Err(erc1155::Error::InvalidSender( + erc1155::ERC1155InvalidSender { sender: from }, + )); } self._update_with_acceptance_check( from, @@ -552,7 +552,7 @@ impl Erc1155Supply { id: U256, value: U256, data: &Bytes, - ) -> Result<(), Error> { + ) -> Result<(), erc1155::Error> { self._do_mint(to, vec![id], vec![value], data) } @@ -594,7 +594,7 @@ impl Erc1155Supply { ids: Vec, values: Vec, data: &Bytes, - ) -> Result<(), Error> { + ) -> Result<(), erc1155::Error> { self._do_mint(to, ids, values, data) } @@ -626,7 +626,7 @@ impl Erc1155Supply { from: Address, id: U256, value: U256, - ) -> Result<(), Error> { + ) -> Result<(), erc1155::Error> { self._do_burn(from, vec![id], vec![value]) } @@ -661,7 +661,7 @@ impl Erc1155Supply { from: Address, ids: Vec, values: Vec, - ) -> Result<(), Error> { + ) -> Result<(), erc1155::Error> { self._do_burn(from, ids, values) } @@ -758,19 +758,15 @@ mod tests { #[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])); + let token_id = random_token_ids(1)[0]; + assert_eq!(U256::ZERO, contract.total_supply(token_id)); assert_eq!(U256::ZERO, contract.total_supply_all()); - assert!(!contract.exists(token_ids[0])); + assert!(!contract.exists(token_id)); } #[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"); + let (token_ids, values) = init(contract, ALICE, 1); assert_eq!(values[0], contract.total_supply(token_ids[0])); assert_eq!(values[0], contract.total_supply_all()); assert!(contract.exists(token_ids[0])); @@ -790,25 +786,11 @@ mod tests { #[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])); + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + assert_eq!(value, contract.erc1155.balance_of(BOB, token_id)); + assert!(contract.exists(token_ids[0])); + } + let total_supply: U256 = values.iter().sum(); + assert_eq!(total_supply, contract.total_supply_all()); } } From f03095ad73a4b72f409660f527431a75ee614095 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 08:48:10 +0100 Subject: [PATCH 05/54] ref: reorganize internal fns --- .../src/token/erc1155/extensions/supply.rs | 214 +++++++++--------- 1 file changed, 110 insertions(+), 104 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 8b321dd48..8df38b794 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -282,6 +282,10 @@ impl Erc1155Supply { } impl Erc1155Supply { + // ========================================================================= + // Overriding _update requires reimplementing all the functions that use it. + // ========================================================================= + /// Override of [`Erc1155::_update`] that restricts normal minting to after /// construction. /// @@ -414,17 +418,50 @@ impl Erc1155Supply { Ok(()) } - /// Creates `values` of tokens specified by `ids`, and assigns - /// them to `to`. Performs the token acceptance check by - /// calling [`erc1155::IERC1155Receiver::on_erc_1155_received`] or - /// [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] on the `to` - /// address if it contains code. + /// 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. - /// * `ids` - Array of all token ids to be minted. + /// * `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 `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its + /// interface id or returned with error, then the error + /// [`erc1155::Error::InvalidReceiver`] is returned. + /// + /// # Events + /// + /// Emits a [`erc1155::TransferSingle`] event. + /// + /// # Panics + /// + /// If updated balance exceeds `U256::MAX`. + pub fn _mint( + &mut self, + to: Address, + id: U256, + value: U256, + data: &Bytes, + ) -> Result<(), erc1155::Error> { + self._do_mint(to, vec![id], vec![value], data) + } + + /// Batched version of [`Self::_mint`]. + /// + /// # 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`. @@ -450,119 +487,96 @@ impl Erc1155Supply { /// # Panics /// /// If updated balance exceeds `U256::MAX`. - fn _do_mint( + pub fn _mint_batch( &mut self, to: Address, ids: Vec, values: Vec, data: &Bytes, ) -> Result<(), erc1155::Error> { - if to.is_zero() { - return Err(erc1155::Error::InvalidReceiver( - erc1155::ERC1155InvalidReceiver { receiver: to }, - )); - } - self._update_with_acceptance_check( - Address::ZERO, - to, - ids, - values, - data, - )?; - Ok(()) + self._do_mint(to, ids, values, data) } - /// Destroys `values` amounts of tokens specified by `ids` from `from`. + /// Destroys a `value` amount of tokens of type `id` 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. + /// * `id` - Token id to be burnt. + /// * `value` - Amount of tokens to be burnt. /// /// # Errors /// /// If `from` is the `Address::ZERO`, then the error /// [`erc1155::Error::InvalidSender`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. /// If `value` is greater than the balance of the `from` account, /// then the error [`erc1155::Error::InsufficientBalance`] is returned. /// /// # Events /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. + /// Emits a [`erc1155::TransferSingle`] event. /// /// # Panics /// /// Should not panic. - fn _do_burn( + pub fn _burn( &mut self, from: Address, - ids: Vec, - values: Vec, + id: U256, + value: U256, ) -> Result<(), erc1155::Error> { - if from.is_zero() { - return Err(erc1155::Error::InvalidSender( - erc1155::ERC1155InvalidSender { sender: from }, - )); - } - self._update_with_acceptance_check( - from, - Address::ZERO, - ids, - values, - &vec![].into(), - )?; - Ok(()) + self._do_burn(from, vec![id], vec![value]) } - /// Creates a `value` amount of tokens of type `id`, and assigns - /// them to `to`. + /// Batched version of [`Self::_burn`]. /// /// # 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`. + /// * `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 `to` is `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its - /// interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. + /// If `from` is the `Address::ZERO`, then the error + /// [`erc1155::Error::InvalidSender`] is returned. + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. /// /// # Events /// - /// Emits a [`erc1155::TransferSingle`] event. + /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one + /// element, and [`erc1155::TransferBatch`] otherwise. /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`. - pub fn _mint( + /// Should not panic. + pub fn _burn_batch( &mut self, - to: Address, - id: U256, - value: U256, - data: &Bytes, + from: Address, + ids: Vec, + values: Vec, ) -> Result<(), erc1155::Error> { - self._do_mint(to, vec![id], vec![value], data) + self._do_burn(from, ids, values) } +} - /// Batched version of [`Self::_mint`]. +impl Erc1155Supply { + /// Creates `values` of tokens specified by `ids`, and assigns + /// them to `to`. Performs the token acceptance check by + /// calling [`erc1155::IERC1155Receiver::on_erc_1155_received`] or + /// [`erc1155::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. + /// * `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`. @@ -588,56 +602,36 @@ impl Erc1155Supply { /// # Panics /// /// If updated balance exceeds `U256::MAX`. - pub fn _mint_batch( + fn _do_mint( &mut self, to: Address, ids: Vec, values: Vec, data: &Bytes, ) -> Result<(), erc1155::Error> { - self._do_mint(to, ids, values, data) - } - - /// Destroys a `value` amount of tokens of type `id` from `from`. - /// - /// # Arguments - /// - /// * `&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 - /// [`erc1155::Error::InvalidSender`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event. - /// - /// # Panics - /// - /// Should not panic. - pub fn _burn( - &mut self, - from: Address, - id: U256, - value: U256, - ) -> Result<(), erc1155::Error> { - self._do_burn(from, vec![id], vec![value]) + if to.is_zero() { + return Err(erc1155::Error::InvalidReceiver( + erc1155::ERC1155InvalidReceiver { receiver: to }, + )); + } + self._update_with_acceptance_check( + Address::ZERO, + to, + ids, + values, + data, + )?; + Ok(()) } - /// Batched version of [`Self::_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 tokens ids to be burnt. - /// * `values` - Array of all amounts of tokens to be burnt. + /// * `ids` - Array of all token ids to be burnt. + /// * `values` - Array of all amount of tokens to be burnt. /// /// # Errors /// @@ -656,13 +650,25 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - pub fn _burn_batch( + fn _do_burn( &mut self, from: Address, ids: Vec, values: Vec, ) -> Result<(), erc1155::Error> { - self._do_burn(from, ids, values) + if from.is_zero() { + return Err(erc1155::Error::InvalidSender( + erc1155::ERC1155InvalidSender { sender: from }, + )); + } + self._update_with_acceptance_check( + from, + Address::ZERO, + ids, + values, + &vec![].into(), + )?; + Ok(()) } /// Transfers `values` of tokens specified by `ids` from `from` to `to`. From 84b22d2f58d1340f36fa208ba93a79d5090c26d0 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:03:59 +0100 Subject: [PATCH 06/54] ref(test): rename tests to refer to mint/burn --- .../src/token/erc1155/extensions/supply.rs | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 8df38b794..b207ed8c9 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -748,7 +748,7 @@ mod tests { (0..size).map(|_| U256::from(rand::random::())).collect() } - fn init( + fn setup( contract: &mut Erc1155Supply, receiver: Address, size: usize, @@ -757,13 +757,18 @@ mod tests { let values = random_values(size); contract - ._update(Address::ZERO, receiver, token_ids.clone(), values.clone()) - .expect("should supply"); + ._mint_batch( + receiver, + token_ids.clone(), + values.clone(), + &vec![].into(), + ) + .expect("should mint"); (token_ids, values) } #[motsu::test] - fn supply_of_zero_supply(contract: Erc1155Supply) { + fn before_mint(contract: Erc1155Supply) { let token_id = random_token_ids(1)[0]; assert_eq!(U256::ZERO, contract.total_supply(token_id)); assert_eq!(U256::ZERO, contract.total_supply_all()); @@ -771,32 +776,48 @@ mod tests { } #[motsu::test] - fn supply_with_zero_address_sender(contract: Erc1155Supply) { - let (token_ids, values) = init(contract, ALICE, 1); + fn after_mint_single(contract: Erc1155Supply) { + let (token_ids, values) = setup(contract, ALICE, 1); 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"); + fn after_mint_batch(contract: Erc1155Supply) { + let (token_ids, values) = setup(contract, ALICE, 4); + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + assert_eq!(value, contract.erc1155.balance_of(ALICE, token_id)); + assert!(contract.exists(token_id)); + } + let total_supply: U256 = values.iter().sum(); + assert_eq!(total_supply, contract.total_supply_all()); + } + + #[motsu::test] + fn after_burn_single(contract: Erc1155Supply) { + let (token_ids, values) = setup(contract, ALICE, 1); + contract._burn(ALICE, token_ids[0], values[0]).expect("should burn"); + 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); - for (&token_id, &value) in token_ids.iter().zip(values.iter()) { - assert_eq!(value, contract.erc1155.balance_of(BOB, token_id)); - assert!(contract.exists(token_ids[0])); + fn after_burn_batch(contract: Erc1155Supply) { + let (token_ids, values) = setup(contract, ALICE, 4); + contract + ._burn_batch(ALICE, token_ids.clone(), values.clone()) + .expect("should burn batch"); + + for &token_id in token_ids.iter() { + assert_eq!( + U256::ZERO, + contract.erc1155.balance_of(ALICE, token_id) + ); + assert!(!contract.exists(token_id)); } - let total_supply: U256 = values.iter().sum(); - assert_eq!(total_supply, contract.total_supply_all()); + assert_eq!(U256::ZERO, contract.total_supply_all()); } } From 8ab4013e12c4878875d392d077611fb4ba9065b1 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:07:43 +0100 Subject: [PATCH 07/54] test: add noop test case --- contracts/src/token/erc1155/extensions/supply.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index b207ed8c9..1d387f638 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -820,4 +820,17 @@ mod tests { } assert_eq!(U256::ZERO, contract.total_supply_all()); } + + #[motsu::test] + fn supply_unaffected_by_no_op(contract: Erc1155Supply) { + let token_ids = random_token_ids(1); + let values = random_values(1); + + contract + ._update(Address::ZERO, Address::ZERO, token_ids.clone(), values) + .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])); + } } From d73d0280b32178f97970ec54f1cbf47c36737fb1 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:08:29 +0100 Subject: [PATCH 08/54] fix: remove pub from internal fns --- contracts/src/token/erc1155/extensions/supply.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 1d387f638..356c36d76 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -301,7 +301,7 @@ impl Erc1155Supply { /// /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one /// element, and [`erc1155::TransferBatch`] otherwise. - pub fn _update( + fn _update( &mut self, from: Address, to: Address, @@ -445,7 +445,7 @@ impl Erc1155Supply { /// # Panics /// /// If updated balance exceeds `U256::MAX`. - pub fn _mint( + fn _mint( &mut self, to: Address, id: U256, @@ -487,7 +487,7 @@ impl Erc1155Supply { /// # Panics /// /// If updated balance exceeds `U256::MAX`. - pub fn _mint_batch( + fn _mint_batch( &mut self, to: Address, ids: Vec, @@ -520,7 +520,7 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - pub fn _burn( + fn _burn( &mut self, from: Address, id: U256, @@ -555,7 +555,7 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - pub fn _burn_batch( + fn _burn_batch( &mut self, from: Address, ids: Vec, From c6d528c946f62cf988405766a29c218aed999fcd Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:09:21 +0100 Subject: [PATCH 09/54] ref(test): remove unused BOB --- contracts/src/token/erc1155/extensions/supply.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 356c36d76..e718dff2d 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -738,7 +738,6 @@ mod tests { 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() From 00cc395934280572be8772d64e884fc2348490e0 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:34:21 +0100 Subject: [PATCH 10/54] docs: fix comments --- .../src/token/erc1155/extensions/supply.rs | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index e718dff2d..6e68ffd30 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -1,8 +1,8 @@ //! 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 +//! clearly identified. Note: While a total_supply of 1 might mean the +//! corresponding is an NFT, there are no guarantees that no other tokens //! with the same id are not going to be minted. //! //! NOTE: This contract implies a global limit of 2**256 - 1 to the number @@ -29,11 +29,11 @@ use crate::{ sol_storage! { /// State of an [`Erc1155Supply`] token. pub struct Erc1155Supply { - /// Erc1155 contract storage. + /// ERC-1155 contract storage. Erc1155 erc1155; - /// Mapping from token ID to total supply. + /// Mapping from token id to total supply. mapping(uint256 => uint256) _total_supply; - /// Total supply of all token IDs. + /// Total supply of all token ids. uint256 _total_supply_all; } } @@ -50,9 +50,9 @@ impl Erc1155Supply { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `id` - Token id. - pub fn total_supply(&self, token_id: U256) -> U256 { - self._total_supply.get(token_id) + /// * `id` - Token id as a number. + pub fn total_supply(&self, id: U256) -> U256 { + self._total_supply.get(id) } /// Total value of tokens. @@ -70,9 +70,9 @@ impl Erc1155Supply { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `id` - Token id. - pub fn exists(&self, token_id: U256) -> bool { - self.total_supply(token_id) > U256::ZERO + /// * `id` - Token id as a number. + pub fn exists(&self, id: U256) -> bool { + self.total_supply(id) > U256::ZERO } /// Returns the value of tokens of type `id` owned by `account`. @@ -282,12 +282,10 @@ impl Erc1155Supply { } impl Erc1155Supply { - // ========================================================================= - // Overriding _update requires reimplementing all the functions that use it. - // ========================================================================= + // Note: overriding `_update` requires reimplementing all of the functions + // that use it. - /// Override of [`Erc1155::_update`] that restricts normal minting to after - /// construction. + /// Override of [`Erc1155::_update`] that updates the supply of tokens. /// /// # Arguments /// @@ -297,10 +295,25 @@ impl Erc1155Supply { /// * `token_ids` - Array of all token id. /// * `values` - Array of all amount of tokens to be supplied. /// + /// # Errors + /// + /// If length of `ids` is not equal to length of `values`, then the + /// error [`erc1155::Error::InvalidArrayLength`] is returned. + /// If `value` is greater than the balance of the `from` account, + /// then the error [`erc1155::Error::InsufficientBalance`] is returned. + /// + /// NOTE: The ERC-1155 acceptance check is not performed in this function. + /// See [`Self::_update_with_acceptance_check`] instead. + /// /// # Events /// /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one /// element, and [`erc1155::TransferBatch`] otherwise. + /// + /// # Panics + /// + /// If updated balance and/or supply exceeds `U256::MAX`, may happen during + /// the `mint` operation. fn _update( &mut self, from: Address, @@ -393,8 +406,8 @@ impl Erc1155Supply { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`, may happen during `mint` - /// operation. + /// If updated balance and/or supply exceeds `U256::MAX`, may happen during + /// the `mint` operation. fn _update_with_acceptance_check( &mut self, from: Address, @@ -444,7 +457,7 @@ impl Erc1155Supply { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`. + /// If updated balance and/or supply exceeds `U256::MAX`. fn _mint( &mut self, to: Address, @@ -486,7 +499,7 @@ impl Erc1155Supply { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`. + /// If updated balance and/or supply exceeds `U256::MAX`. fn _mint_batch( &mut self, to: Address, @@ -601,7 +614,7 @@ impl Erc1155Supply { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`. + /// If updated balance and/or supply exceeds `U256::MAX`. fn _do_mint( &mut self, to: Address, @@ -707,7 +720,7 @@ impl Erc1155Supply { /// /// # Panics /// - /// If updated balance exceeds `U256::MAX`. + /// If updated balance and/or supply exceeds `U256::MAX`. fn do_safe_transfer_from( &mut self, from: Address, From 5e1f59bd8af82c8b69621ede3fcbf4f008e1091e Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:42:06 +0100 Subject: [PATCH 11/54] docs: add ADOC --- .../src/token/erc1155/extensions/supply.rs | 2 +- docs/modules/ROOT/pages/ERC1155.adoc | 7 +++++ docs/modules/ROOT/pages/erc1155-supply.adoc | 28 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 docs/modules/ROOT/pages/erc1155-supply.adoc diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 6e68ffd30..e391f8453 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -1,4 +1,4 @@ -//! Extension of ERC-1155 that adds tracking of total supply per id. +//! Extension of ERC-1155 that adds tracking of total supply per token id. //! //! Useful for scenarios where Fungible and Non-fungible tokens have to be //! clearly identified. Note: While a total_supply of 1 might mean the diff --git a/docs/modules/ROOT/pages/ERC1155.adoc b/docs/modules/ROOT/pages/ERC1155.adoc index d21721959..05b044622 100644 --- a/docs/modules/ROOT/pages/ERC1155.adoc +++ b/docs/modules/ROOT/pages/ERC1155.adoc @@ -1,3 +1,10 @@ = 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]. + +[[erc1155-token-extensions]] +== Extensions + +Additionally, there are multiple custom extensions, including: + +* xref:erc1155-supply.adoc[ERC-1155 Supply]: Extension of that adds tracking of total supply per token id. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/erc1155-supply.adoc b/docs/modules/ROOT/pages/erc1155-supply.adoc new file mode 100644 index 000000000..9c6c1e789 --- /dev/null +++ b/docs/modules/ROOT/pages/erc1155-supply.adoc @@ -0,0 +1,28 @@ += ERC-1155 Supply + +The OpenZeppelin xref:erc1155.adoc[ERC-1155] Supply extension that adds tracking of total supply per token id. +Useful for scenarios where Fungible and Non-fungible tokens have to be clearly identified. + +[[usage]] +== Usage + +In order to make an xref:erc1155.adoc[ERC-1155] token with https://docs.rs/openzeppelin-stylus/0.2.0-alpha/openzeppelin_stylus/token/erc1155/extensions/supply/index.html[Metadata URI] flavour, +you need to add the following code to your contract: + +[source,rust] +---- +use openzeppelin_stylus::token::erc1155::extensions::Erc1155Supply; + +sol_storage! { + #[entrypoint] + struct Erc1155Example { + #[borrow] + Erc1155Supply erc1155_supply; + } +} +#[public] +#[inherit(Erc1155Supply)] +impl Erc1155Example { + // ... +} +---- \ No newline at end of file From 7603acf3ade5eb3c387b23be3c989eec65da873c Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 09:44:09 +0100 Subject: [PATCH 12/54] docs: add CHANGELOG --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f2e55ff..ff04de912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- `Erc1155Supply` extension. #418 + +### Changed + +- + +### Fixed + +- + + ## [v0.2.0-alpha.1] - 2024-11-15 ### Added From 2c52a3509f7c3004b919a2d77103954d7f4d9dd1 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 10:22:42 +0100 Subject: [PATCH 13/54] fix: return 'pub' keyword to all relevant fns --- .../src/token/erc1155/extensions/supply.rs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index e391f8453..cd6d30f9c 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -84,7 +84,7 @@ impl Erc1155Supply { /// * `&self` - Read access to the contract's state. /// * `account` - Account of the token's owner. /// * `id` - Token id as a number. - fn balance_of(&self, account: Address, id: U256) -> U256 { + pub fn balance_of(&self, account: Address, id: U256) -> U256 { self.erc1155.balance_of(account, id) } @@ -106,7 +106,7 @@ impl Erc1155Supply { /// /// * If the length of `accounts` is not equal to the length of `ids`, /// then the error [`erc1155::Error::InvalidArrayLength`] is returned. - fn balance_of_batch( + pub fn balance_of_batch( &self, accounts: Vec
, ids: Vec, @@ -139,7 +139,7 @@ impl Erc1155Supply { /// # Events /// /// Emits an [`erc1155::ApprovalForAll`] event. - fn set_approval_for_all( + pub fn set_approval_for_all( &mut self, operator: Address, approved: bool, @@ -157,7 +157,11 @@ impl Erc1155Supply { /// * `&self` - Read access to the contract's state. /// * `account` - Account of the token's owner. /// * `operator` - Account to be checked. - fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + pub fn is_approved_for_all( + &self, + account: Address, + operator: Address, + ) -> bool { self.erc1155.is_approved_for_all(account, operator) } @@ -207,7 +211,7 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - fn safe_transfer_from( + pub fn safe_transfer_from( &mut self, from: Address, to: Address, @@ -268,7 +272,7 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - fn safe_batch_transfer_from( + pub fn safe_batch_transfer_from( &mut self, from: Address, to: Address, @@ -314,7 +318,7 @@ impl Erc1155Supply { /// /// If updated balance and/or supply exceeds `U256::MAX`, may happen during /// the `mint` operation. - fn _update( + pub fn _update( &mut self, from: Address, to: Address, @@ -408,7 +412,7 @@ impl Erc1155Supply { /// /// If updated balance and/or supply exceeds `U256::MAX`, may happen during /// the `mint` operation. - fn _update_with_acceptance_check( + pub fn _update_with_acceptance_check( &mut self, from: Address, to: Address, @@ -458,7 +462,7 @@ impl Erc1155Supply { /// # Panics /// /// If updated balance and/or supply exceeds `U256::MAX`. - fn _mint( + pub fn _mint( &mut self, to: Address, id: U256, @@ -500,7 +504,7 @@ impl Erc1155Supply { /// # Panics /// /// If updated balance and/or supply exceeds `U256::MAX`. - fn _mint_batch( + pub fn _mint_batch( &mut self, to: Address, ids: Vec, @@ -533,7 +537,7 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - fn _burn( + pub fn _burn( &mut self, from: Address, id: U256, @@ -568,7 +572,7 @@ impl Erc1155Supply { /// # Panics /// /// Should not panic. - fn _burn_batch( + pub fn _burn_batch( &mut self, from: Address, ids: Vec, From 205fd09a89d86d2c9d2fc03a1c1927cbacdc5924 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 10:42:32 +0100 Subject: [PATCH 14/54] test: add panic unit tests --- .../src/token/erc1155/extensions/supply.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index cd6d30f9c..4cb4b7050 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -755,6 +755,7 @@ mod tests { use crate::token::erc1155::IErc1155; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); + const BOB: Address = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); pub(crate) fn random_token_ids(size: usize) -> Vec { (0..size).map(|_| U256::from(rand::random::())).collect() @@ -810,6 +811,31 @@ mod tests { assert_eq!(total_supply, contract.total_supply_all()); } + #[motsu::test] + #[should_panic = "should not exceed `U256::MAX` for `_total_supply`"] + fn mint_panics_on_total_supply_overflow(contract: Erc1155Supply) { + let token_id = random_token_ids(1)[0]; + let one = U256::from(1); + let two = U256::from(2); + contract + ._mint(ALICE, token_id, U256::MAX / two + one, &vec![].into()) + .expect("should mint to ALICE"); + contract + ._mint(BOB, token_id, U256::MAX / two + one, &vec![].into()) + .expect("should mint to BOB"); + let _ = contract._mint(ALICE, token_id, one, &vec![].into()); + } + + #[motsu::test] + #[should_panic = "should not exceed `U256::MAX` for `_total_supply_all`"] + fn mint_panics_on_total_supply_all_overflow(contract: Erc1155Supply) { + contract + ._mint(ALICE, U256::from(1), U256::MAX, &vec![].into()) + .expect("should mint"); + let _ = + contract._mint(ALICE, U256::from(2), U256::from(1), &vec![].into()); + } + #[motsu::test] fn after_burn_single(contract: Erc1155Supply) { let (token_ids, values) = setup(contract, ALICE, 1); From 4a22ec4b66c5e37637f6562db6efa20583b105ff Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 11:18:25 +0100 Subject: [PATCH 15/54] test: check both balance_of and total_supply for mint in unit tests --- contracts/src/token/erc1155/extensions/supply.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 4cb4b7050..a7bf8a8cb 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -795,6 +795,7 @@ mod tests { #[motsu::test] fn after_mint_single(contract: Erc1155Supply) { let (token_ids, values) = setup(contract, ALICE, 1); + assert_eq!(values[0], contract.balance_of(ALICE, token_ids[0])); assert_eq!(values[0], contract.total_supply(token_ids[0])); assert_eq!(values[0], contract.total_supply_all()); assert!(contract.exists(token_ids[0])); @@ -804,7 +805,8 @@ mod tests { fn after_mint_batch(contract: Erc1155Supply) { let (token_ids, values) = setup(contract, ALICE, 4); for (&token_id, &value) in token_ids.iter().zip(values.iter()) { - assert_eq!(value, contract.erc1155.balance_of(ALICE, token_id)); + assert_eq!(value, contract.balance_of(ALICE, token_id)); + assert_eq!(value, contract.total_supply(token_id)); assert!(contract.exists(token_id)); } let total_supply: U256 = values.iter().sum(); From 360afa2fad8d27714e4da3791c30621d0ef40568 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 11:48:22 +0100 Subject: [PATCH 16/54] ref(test): use random_token_ids in mint_panics_on_total_supply_all_overflow --- contracts/src/token/erc1155/extensions/supply.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index a7bf8a8cb..7a75b3552 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -831,11 +831,12 @@ mod tests { #[motsu::test] #[should_panic = "should not exceed `U256::MAX` for `_total_supply_all`"] fn mint_panics_on_total_supply_all_overflow(contract: Erc1155Supply) { + let token_ids = random_token_ids(2); contract - ._mint(ALICE, U256::from(1), U256::MAX, &vec![].into()) + ._mint(ALICE, token_ids[0], U256::MAX, &vec![].into()) .expect("should mint"); let _ = - contract._mint(ALICE, U256::from(2), U256::from(1), &vec![].into()); + contract._mint(ALICE, token_ids[1], U256::from(1), &vec![].into()); } #[motsu::test] From f45fd5b9e15813135ed7d0bfbb2d672c3a0d3692 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 11:51:57 +0100 Subject: [PATCH 17/54] test: fix mint_panics_on_total_supply_overflow unit text --- contracts/src/token/erc1155/extensions/supply.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 7a75b3552..64e3f67a3 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -817,15 +817,15 @@ mod tests { #[should_panic = "should not exceed `U256::MAX` for `_total_supply`"] fn mint_panics_on_total_supply_overflow(contract: Erc1155Supply) { let token_id = random_token_ids(1)[0]; - let one = U256::from(1); let two = U256::from(2); + let three = U256::from(3); contract - ._mint(ALICE, token_id, U256::MAX / two + one, &vec![].into()) + ._mint(ALICE, token_id, U256::MAX / two, &vec![].into()) .expect("should mint to ALICE"); contract - ._mint(BOB, token_id, U256::MAX / two + one, &vec![].into()) + ._mint(BOB, token_id, U256::MAX / two, &vec![].into()) .expect("should mint to BOB"); - let _ = contract._mint(ALICE, token_id, one, &vec![].into()); + let _ = contract._mint(ALICE, token_id, three, &vec![].into()); } #[motsu::test] From f8209fe1ade4dd3eef7665c5116e684d5c3fe48e Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 11:52:56 +0100 Subject: [PATCH 18/54] test: add integration tests --- Cargo.lock | 14 + Cargo.toml | 6 +- examples/erc1155-supply/Cargo.toml | 25 ++ examples/erc1155-supply/src/lib.rs | 66 +++++ examples/erc1155-supply/tests/abi/mod.rs | 36 +++ .../erc1155-supply/tests/erc1155-supply.rs | 256 ++++++++++++++++++ scripts/e2e-tests.sh | 2 +- 7 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 examples/erc1155-supply/Cargo.toml 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 953b3db07..8a8dc3f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1561,6 +1561,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc1155-supply-example" +version = "0.2.0-alpha.1" +dependencies = [ + "alloy", + "alloy-primitives", + "e2e", + "eyre", + "openzeppelin-stylus", + "rand", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc20-example" version = "0.2.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 46b037075..9c979bfa5 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", @@ -36,6 +37,7 @@ default-members = [ "examples/erc721-consecutive", "examples/erc721-metadata", "examples/erc1155", + "examples/erc1155-supply", "examples/safe-erc20", "examples/merkle-proofs", "examples/ownable", @@ -100,7 +102,9 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } tokio = { version = "1.12.0", features = ["full"] } futures = "0.3.30" dashmap = "6.1.0" -crypto-bigint = { version = "0.5.5", default-features = false, features = ["zeroize"] } +crypto-bigint = { version = "0.5.5", default-features = false, features = [ + "zeroize", +] } num-traits = "0.2.14" zeroize = { version = "1.8.1", features = ["derive"] } proptest = "1" diff --git a/examples/erc1155-supply/Cargo.toml b/examples/erc1155-supply/Cargo.toml new file mode 100644 index 000000000..915dd4095 --- /dev/null +++ b/examples/erc1155-supply/Cargo.toml @@ -0,0 +1,25 @@ +[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, features = ["tiny-keccak"] } +stylus-sdk.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e.workspace = true +rand.workspace = true + +[features] +e2e = [] + +[lib] +crate-type = ["lib", "cdylib"] diff --git a/examples/erc1155-supply/src/lib.rs b/examples/erc1155-supply/src/lib.rs new file mode 100644 index 000000000..b91ec153b --- /dev/null +++ b/examples/erc1155-supply/src/lib.rs @@ -0,0 +1,66 @@ +#![cfg_attr(not(test), no_main)] +extern crate alloc; + +use alloc::vec::Vec; + +use alloy_primitives::{Address, U256}; +use openzeppelin_stylus::token::erc1155::extensions::Erc1155Supply; +use stylus_sdk::{ + abi::Bytes, + prelude::{entrypoint, public, sol_storage}, +}; + +sol_storage! { + #[entrypoint] + struct Erc1155Example { + #[borrow] + Erc1155Supply erc1155_supply; + } +} +#[public] +#[inherit(Erc1155Supply)] +impl Erc1155Example { + // Add token minting feature. + pub fn mint( + &mut self, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Vec> { + self.erc1155_supply._mint(to, id, value, &data)?; + Ok(()) + } + + pub fn mint_batch( + &mut self, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Vec> { + self.erc1155_supply._mint_batch(to, ids, values, &data)?; + Ok(()) + } + + // Add token burning feature. + pub fn burn( + &mut self, + from: Address, + id: U256, + value: U256, + ) -> Result<(), Vec> { + self.erc1155_supply._burn(from, id, value)?; + Ok(()) + } + + pub fn burn_batch( + &mut self, + from: Address, + ids: Vec, + values: Vec, + ) -> Result<(), Vec> { + self.erc1155_supply._burn_batch(from, ids, values)?; + Ok(()) + } +} diff --git a/examples/erc1155-supply/tests/abi/mod.rs b/examples/erc1155-supply/tests/abi/mod.rs new file mode 100644 index 000000000..360c33a6e --- /dev/null +++ b/examples/erc1155-supply/tests/abi/mod.rs @@ -0,0 +1,36 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc1155Supply { + function balanceOf(address account, uint256 id) external view returns (uint256 balance); + #[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 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 totalSupply(uint256 id) external view returns (uint256); + function totalSupply() external view returns (uint256); + function exists(uint256 id) external view returns (bool); + + 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-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs new file mode 100644 index 000000000..1d995ba02 --- /dev/null +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -0,0 +1,256 @@ +#![cfg(feature = "e2e")] + +use abi::Erc1155Supply; +use alloy::{ + primitives::{Address, U256}, + rpc::types::TransactionReceipt, +}; +use e2e::{ + receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, +}; +mod abi; + +fn random_token_ids(size: usize) -> Vec { + (0..size).map(U256::from).collect() +} + +fn random_values(size: usize) -> Vec { + (0..size).map(|_| U256::from(rand::random::())).collect() +} + +async fn setup( + receiver: &Account, + size: usize, +) -> eyre::Result<(Address, Vec, Vec, TransactionReceipt)> { + let contract_addr = receiver.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &receiver.wallet); + + let token_ids = random_token_ids(size); + let values = random_values(size); + + let receipt = receipt!(contract.mintBatch( + receiver.address(), + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + + Ok((contract_addr, token_ids, values, receipt)) +} + +// ============================================================================ +// Integration Tests: ERC-1155 Supply Extension +// ============================================================================ + +#[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 token_id = random_token_ids(1)[0]; + + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + let total_supply_all = contract.totalSupply_1().call().await?._0; + let token_exists = contract.exists(token_id).call().await?._0; + + assert_eq!(U256::ZERO, total_supply); + assert_eq!(U256::ZERO, total_supply_all); + assert!(!token_exists); + + Ok(()) +} + +#[e2e::test] +async fn after_mint_single(alice: Account) -> eyre::Result<()> { + let (contract_addr, token_ids, values, receipt) = setup(&alice, 1).await?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + + let balance = + contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; + let total_supply = contract.totalSupply_0(token_ids[0]).call().await?._0; + let total_supply_all = contract.totalSupply_1().call().await?._0; + let token_exists = contract.exists(token_ids[0]).call().await?._0; + + assert_eq!(values[0], balance); + assert_eq!(values[0], total_supply); + assert_eq!(values[0], total_supply_all); + assert!(token_exists); + + assert!(receipt.emits(Erc1155Supply::TransferSingle { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + id: token_ids[0], + value: values[0], + })); + + Ok(()) +} + +#[e2e::test] +async fn after_mint_batch(alice: Account) -> eyre::Result<()> { + let (contract_addr, token_ids, values, receipt) = setup(&alice, 4).await?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + let token_exists = contract.exists(token_id).call().await?._0; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + let balance = + contract.balanceOf(alice_addr, token_id).call().await?.balance; + + assert_eq!(value, balance); + assert_eq!(value, total_supply); + assert!(token_exists); + } + + let total_supply_all = contract.totalSupply_1().call().await?._0; + assert_eq!(values.iter().sum::(), total_supply_all); + + assert!(receipt.emits(Erc1155Supply::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + ids: token_ids, + values, + })); + + Ok(()) +} + +#[e2e::test] +async fn mint_panics_on_total_supply_overflow( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_id = random_token_ids(1)[0]; + let two = U256::from(2); + let three = U256::from(3); + + let _ = watch!(contract.mint( + alice_addr, + token_id, + U256::MAX / two, + vec![].into() + )); + let _ = watch!(contract.mint( + bob_addr, + token_id, + U256::MAX / two, + vec![].into() + )); + + let err = send!(contract.mint(alice_addr, token_id, three, vec![].into())) + .expect_err("should panic due to total_supply overflow"); + + assert!(err.panicked_with(PanicCode::ArithmeticOverflow)); + + Ok(()) +} + +#[e2e::test] +async fn mint_panics_on_total_supply_all_overflow( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(2); + + let _ = watch!(contract.mint( + alice_addr, + token_ids[0], + U256::MAX, + vec![].into() + )); + + let err = send!(contract.mint( + alice_addr, + token_ids[1], + U256::from(1), + vec![].into() + )) + .expect_err("should panic due to total_supply_all overflow"); + + assert!(err.panicked_with(PanicCode::ArithmeticOverflow)); + + Ok(()) +} + +#[e2e::test] +async fn after_burn_single(alice: Account) -> eyre::Result<()> { + let (contract_addr, token_ids, values, _) = setup(&alice, 1).await?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + + let receipt = receipt!(contract.burn(alice_addr, token_ids[0], values[0]))?; + + let token_exists = contract.exists(token_ids[0]).call().await?._0; + let balance = + contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; + let total_supply = contract.totalSupply_0(token_ids[0]).call().await?._0; + let total_supply_all = contract.totalSupply_1().call().await?._0; + + assert_eq!(U256::ZERO, balance); + assert_eq!(U256::ZERO, total_supply); + assert_eq!(U256::ZERO, total_supply_all); + assert!(!token_exists); + + assert!(receipt.emits(Erc1155Supply::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + id: token_ids[0], + value: values[0], + })); + + Ok(()) +} + +#[e2e::test] +async fn after_burn_batch(alice: Account) -> eyre::Result<()> { + let (contract_addr, token_ids, values, _) = setup(&alice, 4).await?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + + let receipt = receipt!(contract.burnBatch( + alice_addr, + token_ids.clone(), + values.clone() + ))?; + + for &token_id in token_ids.iter() { + let balance = + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let token_exists = contract.exists(token_id).call().await?._0; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + + assert_eq!(U256::ZERO, balance); + assert_eq!(U256::ZERO, total_supply); + assert!(!token_exists); + } + + let total_supply_all = contract.totalSupply_1().call().await?._0; + assert_eq!(U256::ZERO, total_supply_all); + + assert!(receipt.emits(Erc1155Supply::TransferBatch { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + ids: token_ids, + values, + })); + + Ok(()) +} diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 87c4fe654..c81a6a126 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-supply" From 6262b55190359348f9aa2b44a25591380763df1a Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 12:11:36 +0100 Subject: [PATCH 19/54] ref(test): rename variable in after_mint_batch unit test --- contracts/src/token/erc1155/extensions/supply.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 64e3f67a3..4b3ecc5d5 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -809,8 +809,8 @@ mod tests { assert_eq!(value, contract.total_supply(token_id)); assert!(contract.exists(token_id)); } - let total_supply: U256 = values.iter().sum(); - assert_eq!(total_supply, contract.total_supply_all()); + let total_supply_all: U256 = values.iter().sum(); + assert_eq!(total_supply_all, contract.total_supply_all()); } #[motsu::test] From e584fe450fbec0359885f2023b39f4cf0e4b0109 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 12:33:36 +0100 Subject: [PATCH 20/54] ref(test): add safe transfer (+ batch) tests --- examples/erc1155-supply/tests/abi/mod.rs | 2 - .../erc1155-supply/tests/erc1155-supply.rs | 209 +++++++++++++++--- 2 files changed, 177 insertions(+), 34 deletions(-) diff --git a/examples/erc1155-supply/tests/abi/mod.rs b/examples/erc1155-supply/tests/abi/mod.rs index 360c33a6e..a500aedd7 100644 --- a/examples/erc1155-supply/tests/abi/mod.rs +++ b/examples/erc1155-supply/tests/abi/mod.rs @@ -7,8 +7,6 @@ sol!( function balanceOf(address account, uint256 id) external view returns (uint256 balance); #[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 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-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs index 1d995ba02..d1e3e6ca1 100644 --- a/examples/erc1155-supply/tests/erc1155-supply.rs +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -8,6 +8,7 @@ use alloy::{ use e2e::{ receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, }; + mod abi; fn random_token_ids(size: usize) -> Vec { @@ -18,7 +19,7 @@ fn random_values(size: usize) -> Vec { (0..size).map(|_| U256::from(rand::random::())).collect() } -async fn setup( +async fn deploy_and_mint( receiver: &Account, size: usize, ) -> eyre::Result<(Address, Vec, Vec, TransactionReceipt)> { @@ -62,11 +63,20 @@ async fn constructs(alice: Account) -> eyre::Result<()> { #[e2e::test] async fn after_mint_single(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, receipt) = setup(&alice, 1).await?; + let (contract_addr, token_ids, values, receipt) = + deploy_and_mint(&alice, 1).await?; let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); + assert!(receipt.emits(Erc1155Supply::TransferSingle { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + id: token_ids[0], + value: values[0], + })); + let balance = contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; let total_supply = contract.totalSupply_0(token_ids[0]).call().await?._0; @@ -78,24 +88,25 @@ async fn after_mint_single(alice: Account) -> eyre::Result<()> { assert_eq!(values[0], total_supply_all); assert!(token_exists); - assert!(receipt.emits(Erc1155Supply::TransferSingle { - operator: alice_addr, - from: Address::ZERO, - to: alice_addr, - id: token_ids[0], - value: values[0], - })); - Ok(()) } #[e2e::test] async fn after_mint_batch(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, receipt) = setup(&alice, 4).await?; + let (contract_addr, token_ids, values, receipt) = + deploy_and_mint(&alice, 4).await?; let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); + assert!(receipt.emits(Erc1155Supply::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 token_exists = contract.exists(token_id).call().await?._0; let total_supply = contract.totalSupply_0(token_id).call().await?._0; @@ -110,14 +121,6 @@ async fn after_mint_batch(alice: Account) -> eyre::Result<()> { let total_supply_all = contract.totalSupply_1().call().await?._0; assert_eq!(values.iter().sum::(), total_supply_all); - assert!(receipt.emits(Erc1155Supply::TransferBatch { - operator: alice_addr, - from: Address::ZERO, - to: alice_addr, - ids: token_ids, - values, - })); - Ok(()) } @@ -188,13 +191,22 @@ async fn mint_panics_on_total_supply_all_overflow( #[e2e::test] async fn after_burn_single(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, _) = setup(&alice, 1).await?; + let (contract_addr, token_ids, values, _) = + deploy_and_mint(&alice, 1).await?; let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); let receipt = receipt!(contract.burn(alice_addr, token_ids[0], values[0]))?; + assert!(receipt.emits(Erc1155Supply::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + id: token_ids[0], + value: values[0], + })); + let token_exists = contract.exists(token_ids[0]).call().await?._0; let balance = contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; @@ -206,20 +218,13 @@ async fn after_burn_single(alice: Account) -> eyre::Result<()> { assert_eq!(U256::ZERO, total_supply_all); assert!(!token_exists); - assert!(receipt.emits(Erc1155Supply::TransferSingle { - operator: alice_addr, - from: alice_addr, - to: Address::ZERO, - id: token_ids[0], - value: values[0], - })); - Ok(()) } #[e2e::test] async fn after_burn_batch(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, _) = setup(&alice, 4).await?; + let (contract_addr, token_ids, values, _) = + deploy_and_mint(&alice, 4).await?; let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); @@ -230,6 +235,14 @@ async fn after_burn_batch(alice: Account) -> eyre::Result<()> { values.clone() ))?; + assert!(receipt.emits(Erc1155Supply::TransferBatch { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + ids: token_ids.clone(), + values, + })); + for &token_id in token_ids.iter() { let balance = contract.balanceOf(alice_addr, token_id).call().await?.balance; @@ -244,13 +257,145 @@ async fn after_burn_batch(alice: Account) -> eyre::Result<()> { let total_supply_all = contract.totalSupply_1().call().await?._0; assert_eq!(U256::ZERO, total_supply_all); + Ok(()) +} + +#[e2e::test] +async fn supply_unaffected_by_safe_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let (contract_addr, token_ids, values, _) = + deploy_and_mint(&alice, 1).await?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_id = token_ids[0]; + let value = values[0]; + + // assert balances as expected after mint + let alice_balance = + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let bob_balance = + contract.balanceOf(bob_addr, token_id).call().await?.balance; + + assert_eq!(value, alice_balance); + assert_eq!(U256::ZERO, bob_balance); + + // total supplies (all) logic has been checked in other tests, assume valid + let initial_total_supply = + contract.totalSupply_0(token_id).call().await?._0; + let initial_total_supply_all = contract.totalSupply_1().call().await?._0; + + let receipt = receipt!(contract.safeTransferFrom( + alice_addr, + bob_addr, + token_id, + value, + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155Supply::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: bob_addr, + id: token_id, + value, + })); + + // assert balances updated as expected + let alice_balance = + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let bob_balance = + contract.balanceOf(bob_addr, token_id).call().await?.balance; + + assert_eq!(U256::ZERO, alice_balance); + assert_eq!(value, bob_balance); + + // assert supply-related data remains unchanged + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + let total_supply_all = contract.totalSupply_1().call().await?._0; + let token_exists = contract.exists(token_id).call().await?._0; + + assert_eq!(initial_total_supply, total_supply); + assert_eq!(initial_total_supply_all, total_supply_all); + assert!(token_exists); + + Ok(()) +} + +#[e2e::test] +async fn supply_unaffected_by_safe_transfer_from_batch( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let (contract_addr, token_ids, values, _) = + deploy_and_mint(&alice, 4).await?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + // assert balances as expected after mint + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + let alice_balance = + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let bob_balance = + contract.balanceOf(bob_addr, token_id).call().await?.balance; + + assert_eq!(value, alice_balance); + assert_eq!(U256::ZERO, bob_balance); + } + + // total supplies (all) logic has been checked in other tests, assume valid + let mut initial_total_supplies: Vec = vec![]; + for &token_id in token_ids.iter() { + let supply = contract.totalSupply_0(token_id).call().await?._0; + initial_total_supplies.push(supply); + } + let initial_total_supply_all = contract.totalSupply_1().call().await?._0; + + let receipt = receipt!(contract.safeBatchTransferFrom( + alice_addr, + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + assert!(receipt.emits(Erc1155Supply::TransferBatch { operator: alice_addr, from: alice_addr, - to: Address::ZERO, - ids: token_ids, - values, + to: bob_addr, + ids: token_ids.clone(), + values: values.clone(), })); + // assert balances updated as expected + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + let alice_balance = + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let bob_balance = + contract.balanceOf(bob_addr, token_id).call().await?.balance; + + assert_eq!(U256::ZERO, alice_balance); + assert_eq!(value, bob_balance); + } + + // assert supply-related data remains unchanged + for (&token_id, &initial_total_supply) in + token_ids.iter().zip(initial_total_supplies.iter()) + { + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + let token_exists = contract.exists(token_id).call().await?._0; + + assert_eq!(initial_total_supply, total_supply); + assert!(token_exists); + } + + let total_supply_all = contract.totalSupply_1().call().await?._0; + assert_eq!(initial_total_supply_all, total_supply_all); + Ok(()) } From b9b4041b6d7e2b73a1f5148f85b7541d51bee0a8 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 12:38:17 +0100 Subject: [PATCH 21/54] chore: add benches --- benches/src/erc1155_supply.rs | 104 ++++++++++++++++++++++++++++++++++ benches/src/lib.rs | 1 + 2 files changed, 105 insertions(+) create mode 100644 benches/src/erc1155_supply.rs diff --git a/benches/src/erc1155_supply.rs b/benches/src/erc1155_supply.rs new file mode 100644 index 000000000..83d76c5df --- /dev/null +++ b/benches/src/erc1155_supply.rs @@ -0,0 +1,104 @@ +use alloy::{ + network::{AnyNetwork, EthereumWallet}, + primitives::Address, + providers::ProviderBuilder, + sol, + sol_types::SolCall, + uint, +}; +use e2e::{receipt, Account}; + +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; + +sol!( + #[sol(rpc)] + contract Erc1155Supply { + 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 totalSupply(uint256 id) external view returns (uint256); + function totalSupply() external view returns (uint256); + function exists(uint256 id) external view returns (bool); + } +); + +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("Erc1155Supply"), 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 = Erc1155Supply::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 Erc1155Supply::*; + #[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()))?), + (existsCall::SIGNATURE, receipt!(contract.exists(token_1))?), + (totalSupply_0Call::SIGNATURE, receipt!(contract.totalSupply_0(token_1))?), + (totalSupply_1Call::SIGNATURE, receipt!(contract.totalSupply_1())?), + (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.clone(), values.clone(), data.clone()))?), + (burnCall::SIGNATURE, receipt!(contract.burn(bob_addr, token_1, value_1))?), + (burnBatchCall::SIGNATURE, receipt!(contract.burnBatch(bob_addr, ids, values))?), + ]; + + receipts + .into_iter() + .map(FunctionReport::new) + .collect::>>() +} + +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ + crate::deploy(account, "erc1155-supply", None, cache_opt).await +} diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 44e36faf4..6d0b5bbd7 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -15,6 +15,7 @@ use serde::Deserialize; pub mod access_control; pub mod erc1155; +pub mod erc1155_supply; pub mod erc20; pub mod erc721; pub mod merkle_proofs; From bc014050b052636b24310c5f4932a7a09710a26d Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 12:38:47 +0100 Subject: [PATCH 22/54] docs: remove newline from changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff04de912..d8392a1f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - ## [v0.2.0-alpha.1] - 2024-11-15 ### Added From 5f4c666a7bf499ed71fb5cb64147e862925ed20a Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 12:39:20 +0100 Subject: [PATCH 23/54] docs: add missing newline in adoc --- docs/modules/ROOT/pages/erc1155-supply.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/modules/ROOT/pages/erc1155-supply.adoc b/docs/modules/ROOT/pages/erc1155-supply.adoc index 9c6c1e789..d77215887 100644 --- a/docs/modules/ROOT/pages/erc1155-supply.adoc +++ b/docs/modules/ROOT/pages/erc1155-supply.adoc @@ -20,6 +20,7 @@ sol_storage! { Erc1155Supply erc1155_supply; } } + #[public] #[inherit(Erc1155Supply)] impl Erc1155Example { From 273e699c49f02814289e42752a73f3e70ba2bd05 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Mon, 25 Nov 2024 12:40:07 +0100 Subject: [PATCH 24/54] test: revert change to e2e script --- scripts/e2e-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index c81a6a126..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-supply" +cargo test --features std,e2e --test "*" From b7a29438069d1fb5f4b4e9d88b85183fdffe0290 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 27 Nov 2024 09:52:40 +0100 Subject: [PATCH 25/54] ref: _update calculation --- .../src/token/erc1155/extensions/supply.rs | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 4b3ecc5d5..7a4c46ef8 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -328,17 +328,15 @@ impl Erc1155Supply { self.erc1155._update(from, to, token_ids.clone(), values.clone())?; if from.is_zero() { - let mut total_mint_value = U256::ZERO; - 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; - }, - ); + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + 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); + } + + let total_mint_value = values.iter().sum(); let total_supply_all = self.total_supply_all().checked_add(total_mint_value).expect( "should not exceed `U256::MAX` for `_total_supply_all`", @@ -347,26 +345,18 @@ impl Erc1155Supply { } if to.is_zero() { - let mut total_burn_value = U256::ZERO; - token_ids.iter().zip(values.iter()).for_each( - |(&token_id, &value)| { - /* - SAFETY: Overflow not possible: - values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) - */ - self._total_supply - .setter(token_id) - .sub_assign_unchecked(value); - /* - SAFETY: Overflow not possible: - sum_i(values[i]) <= sum_i(total_supply(token_ids[i])) <= total_supply_all - */ - total_burn_value += value; - }, - ); + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + /* + SAFETY: Overflow not possible: + values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) + */ + self._total_supply.setter(token_id).sub_assign_unchecked(value); + } + + let total_burn_value: U256 = values.iter().sum(); /* SAFETY: Overflow not possible: - totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + total_burn_value = sum_i(values[i]) <= sum_i(total_supply(ids[i])) <= total_supply_all */ let total_supply_all = self._total_supply_all.get() - total_burn_value; From 88b631d2098626cdb08940771a02bdfeb1ad5761 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 27 Nov 2024 09:54:23 +0100 Subject: [PATCH 26/54] ref: make _update & _update_with_acceptance_check non-public --- contracts/src/token/erc1155/extensions/supply.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 7a4c46ef8..bc7600e61 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -318,7 +318,7 @@ impl Erc1155Supply { /// /// If updated balance and/or supply exceeds `U256::MAX`, may happen during /// the `mint` operation. - pub fn _update( + fn _update( &mut self, from: Address, to: Address, @@ -402,7 +402,7 @@ impl Erc1155Supply { /// /// If updated balance and/or supply exceeds `U256::MAX`, may happen during /// the `mint` operation. - pub fn _update_with_acceptance_check( + fn _update_with_acceptance_check( &mut self, from: Address, to: Address, From 4494898466377194ebe4e0f11c744ea1b5d000bc Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 27 Nov 2024 10:04:06 +0100 Subject: [PATCH 27/54] docs: fix paths in comments --- contracts/src/token/erc1155/extensions/supply.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index bc7600e61..495fd94c6 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -266,8 +266,9 @@ impl Erc1155Supply { /// /// # Events /// - /// Emits either a [`TransferSingle`] or a [`erc1155::TransferBatch`] event, - /// depending on the length of the array arguments. + /// Emits either a [`erc1155::TransferSingle`] or a + /// [`erc1155::TransferBatch`] event, depending on the length of the array + /// arguments. /// /// # Panics /// @@ -441,8 +442,8 @@ impl Erc1155Supply { /// /// If `to` is `Address::ZERO`, then the error /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`IERC1155Receiver::on_erc_1155_received`] hasn't returned its - /// interface id or returned with error, then the error + /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned + /// its interface id or returned with error, then the error /// [`erc1155::Error::InvalidReceiver`] is returned. /// /// # Events From 785d875188c03aa7477017d295219ea53a8aaa62 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 27 Nov 2024 10:08:34 +0100 Subject: [PATCH 28/54] docs: fix missing backticks warning --- contracts/src/token/erc1155/extensions/supply.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 495fd94c6..0b07940cb 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -1,7 +1,7 @@ //! Extension of ERC-1155 that adds tracking of total supply per token id. //! //! Useful for scenarios where Fungible and Non-fungible tokens have to be -//! clearly identified. Note: While a total_supply of 1 might mean the +//! clearly identified. Note: While a `_total_supply` of 1 might mean the //! corresponding is an NFT, there are no guarantees that no other tokens //! with the same id are not going to be minted. //! From 98d57ac72466bb57f8643ff235c85a2c27f80401 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 27 Nov 2024 10:38:37 +0100 Subject: [PATCH 29/54] docs: fix URL and fix Metadata mention to Supply --- docs/modules/ROOT/pages/erc1155-supply.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/erc1155-supply.adoc b/docs/modules/ROOT/pages/erc1155-supply.adoc index d77215887..bbb7997d8 100644 --- a/docs/modules/ROOT/pages/erc1155-supply.adoc +++ b/docs/modules/ROOT/pages/erc1155-supply.adoc @@ -6,7 +6,7 @@ Useful for scenarios where Fungible and Non-fungible tokens have to be clearly i [[usage]] == Usage -In order to make an xref:erc1155.adoc[ERC-1155] token with https://docs.rs/openzeppelin-stylus/0.2.0-alpha/openzeppelin_stylus/token/erc1155/extensions/supply/index.html[Metadata URI] flavour, +In order to make an xref:erc1155.adoc[ERC-1155] token with https://docs.rs/openzeppelin-stylus/0.2.0-alpha.2/openzeppelin_stylus/token/erc1155/extensions/supply/index.html[Supply] flavour, you need to add the following code to your contract: [source,rust] From ba5bb23a794c6484828bae7da45d53dc63821e9d Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 27 Nov 2024 10:50:15 +0100 Subject: [PATCH 30/54] ref: iterate over slice instead of calling .iter() --- contracts/src/token/erc1155/extensions/supply.rs | 2 +- examples/erc1155-supply/tests/erc1155-supply.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 0b07940cb..334e2a71c 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -847,7 +847,7 @@ mod tests { ._burn_batch(ALICE, token_ids.clone(), values.clone()) .expect("should burn batch"); - for &token_id in token_ids.iter() { + for &token_id in &token_ids { assert_eq!( U256::ZERO, contract.erc1155.balance_of(ALICE, token_id) diff --git a/examples/erc1155-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs index d1e3e6ca1..3b38f1530 100644 --- a/examples/erc1155-supply/tests/erc1155-supply.rs +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -243,7 +243,7 @@ async fn after_burn_batch(alice: Account) -> eyre::Result<()> { values, })); - for &token_id in token_ids.iter() { + for &token_id in &token_ids { let balance = contract.balanceOf(alice_addr, token_id).call().await?.balance; let token_exists = contract.exists(token_id).call().await?._0; @@ -350,7 +350,7 @@ async fn supply_unaffected_by_safe_transfer_from_batch( // total supplies (all) logic has been checked in other tests, assume valid let mut initial_total_supplies: Vec = vec![]; - for &token_id in token_ids.iter() { + for &token_id in &token_ids { let supply = contract.totalSupply_0(token_id).call().await?._0; initial_total_supplies.push(supply); } From 2e600672bd39021869994c643da78974d29073ad Mon Sep 17 00:00:00 2001 From: Nenad Date: Mon, 2 Dec 2024 11:30:06 +0100 Subject: [PATCH 31/54] docs: Apply adoc update from code review Co-authored-by: Daniel Bigos --- docs/modules/ROOT/pages/ERC1155.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/ERC1155.adoc b/docs/modules/ROOT/pages/ERC1155.adoc index b6ab641a7..ad94d522a 100644 --- a/docs/modules/ROOT/pages/ERC1155.adoc +++ b/docs/modules/ROOT/pages/ERC1155.adoc @@ -8,4 +8,4 @@ ERC1155 is a novel token standard that aims to take the best from previous stand Additionally, there are multiple custom extensions, including: * xref:erc1155-burnable.adoc[ERC-1155 Burnable]: Optional Burnable extension of the ERC-1155 standard. -* xref:erc1155-supply.adoc[ERC-1155 Supply]: Extension of that adds tracking of total supply per token id. +* xref:erc1155-supply.adoc[ERC-1155 Supply]: Extension of the ERC-1155 standard that adds tracking of total supply per token id. From 1ce5d282271c213bea413b6017f63954b66c8a6f Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 08:29:09 +0100 Subject: [PATCH 32/54] ref: extract IErc1155Supply trait with shorter comments --- .../src/token/erc1155/extensions/supply.rs | 254 ++++++------------ 1 file changed, 82 insertions(+), 172 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 334e2a71c..1d2f9e04b 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -14,6 +14,7 @@ use alloc::{vec, vec::Vec}; use alloy_primitives::{Address, U256}; +use openzeppelin_stylus_proc::interface_id; use stylus_sdk::{ abi::Bytes, msg, @@ -43,17 +44,19 @@ sol_storage! { /// BorrowMut)`. Should be fixed in the future by the Stylus team. unsafe impl TopLevelStorage for Erc1155Supply {} -#[public] -impl Erc1155Supply { +/// Required interface of a [`Erc1155Supply`] contract. +#[interface_id] +pub trait IErc1155Supply { + /// The error type associated to this trait implementation. + type Error: Into>; + /// Total value of tokens in with a given id. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. /// * `id` - Token id as a number. - pub fn total_supply(&self, id: U256) -> U256 { - self._total_supply.get(id) - } + fn total_supply(&self, id: U256) -> U256; /// Total value of tokens. /// @@ -61,9 +64,7 @@ impl Erc1155Supply { /// /// * `&self` - Read access to the contract's state. #[selector(name = "totalSupply")] - pub fn total_supply_all(&self) -> U256 { - *self._total_supply_all - } + fn total_supply_all(&self) -> U256; /// Indicates whether any token exist with a given id, or not. /// @@ -71,147 +72,106 @@ impl Erc1155Supply { /// /// * `&self` - Read access to the contract's state. /// * `id` - Token id as a number. - pub fn exists(&self, id: U256) -> bool { - self.total_supply(id) > U256::ZERO - } + fn exists(&self, id: U256) -> bool; /// Returns the value of tokens of type `id` owned by `account`. /// /// Re-export of [`Erc1155::balance_of`] - /// - /// # Arguments - /// - /// * `&self` - Read access to the contract's state. - /// * `account` - Account of the token's owner. - /// * `id` - Token id as a number. - pub fn balance_of(&self, account: Address, id: U256) -> U256 { - self.erc1155.balance_of(account, id) - } + fn balance_of(&self, account: Address, id: U256) -> U256; /// Batched version of [`Erc1155::balance_of`]. /// - /// Re-export of [`Erc1155::balance_of_batch`] - /// - /// # 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. - /// - /// # Errors - /// - /// * If the length of `accounts` is not equal to the length of `ids`, - /// then the error [`erc1155::Error::InvalidArrayLength`] is returned. - pub fn balance_of_batch( + /// Re-export of [`Erc1155::balance_of_batch`]. + fn balance_of_batch( &self, accounts: Vec
, ids: Vec, - ) -> Result, erc1155::Error> { - self.erc1155.balance_of_batch(accounts, ids) - } + ) -> Result, erc1155::Error>; /// Grants or revokes permission to `operator` /// to transfer the caller's tokens, according to `approved`. /// - /// Re-export of [`Erc1155::set_approval_for_all`] - /// - /// # 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 - /// [`erc1155::Error::InvalidOperator`] is returned. - /// - /// # Requirements - /// - /// * The `operator` cannot be the `Address::ZERO`. - /// - /// # Events - /// - /// Emits an [`erc1155::ApprovalForAll`] event. - pub fn set_approval_for_all( + /// Re-export of [`Erc1155::set_approval_for_all`]. + fn set_approval_for_all( &mut self, operator: Address, approved: bool, - ) -> Result<(), erc1155::Error> { - self.erc1155.set_approval_for_all(operator, approved) - } + ) -> Result<(), erc1155::Error>; /// Returns true if `operator` is approved to transfer `account`'s /// tokens. /// - /// Re-export of [`Erc1155::is_approved_for_all`] + /// Re-export of [`Erc1155::is_approved_for_all`]. + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; + + /// Transfers a `value` amount of tokens of type `id` from `from` to + /// `to`. /// - /// # Arguments + /// Re-export of [`Erc1155::safe_transfer_from`]. + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), erc1155::Error>; + + /// Batched version of [`IErc1155Supply::safe_transfer_from`]. /// - /// * `&self` - Read access to the contract's state. - /// * `account` - Account of the token's owner. - /// * `operator` - Account to be checked. - pub fn is_approved_for_all( + /// Re-export of [`Erc1155::safe_batch_transfer_from`]. + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), erc1155::Error>; +} + +#[public] +impl IErc1155Supply for Erc1155Supply { + type Error = erc1155::Error; + + fn total_supply(&self, id: U256) -> U256 { + self._total_supply.get(id) + } + + #[selector(name = "totalSupply")] + fn total_supply_all(&self) -> U256 { + *self._total_supply_all + } + + fn exists(&self, id: U256) -> bool { + self.total_supply(id) > U256::ZERO + } + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( &self, - account: Address, + accounts: Vec
, + ids: Vec, + ) -> Result, erc1155::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, operator: Address, - ) -> bool { + approved: bool, + ) -> Result<(), erc1155::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { self.erc1155.is_approved_for_all(account, operator) } - /// Transfers a `value` amount of tokens of type `id` 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. - /// * `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 `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If `from` is `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidSender`] is returned. - /// If the `from` is not the caller (`msg::sender()`), - /// and the caller does not have the right to approve, then the error - /// [`erc1155::Error::MissingApprovalForAll`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # 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 of type `id` of at least `value` - /// amount. - /// * If `to` refers to a smart contract, it must implement - /// [`erc1155::IERC1155Receiver::on_erc_1155_received`] and return the - /// acceptance value. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event. - /// - /// # Panics - /// - /// Should not panic. - pub fn safe_transfer_from( + fn safe_transfer_from( &mut self, from: Address, to: Address, @@ -223,57 +183,7 @@ impl Erc1155Supply { self.do_safe_transfer_from(from, to, vec![id], vec![value], &data) } - /// Batched version of [`Erc1155::safe_transfer_from`]. - /// - /// # 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 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 - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If `from` is `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidSender`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::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 - /// [`erc1155::Error::MissingApprovalForAll`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't - /// returned its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # 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 - /// [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] and return - /// the acceptance magic value. - /// - /// # Events - /// - /// Emits either a [`erc1155::TransferSingle`] or a - /// [`erc1155::TransferBatch`] event, depending on the length of the array - /// arguments. - /// - /// # Panics - /// - /// Should not panic. - pub fn safe_batch_transfer_from( + fn safe_batch_transfer_from( &mut self, from: Address, to: Address, From 0f694feacae2df64dbaf258895b0d1dd09a5a48c Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 08:32:07 +0100 Subject: [PATCH 33/54] ref: remove erc1155-specific funcs from benches for supply ext. --- benches/src/erc1155_supply.rs | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/benches/src/erc1155_supply.rs b/benches/src/erc1155_supply.rs index 85615b24e..5b057e04e 100644 --- a/benches/src/erc1155_supply.rs +++ b/benches/src/erc1155_supply.rs @@ -16,12 +16,6 @@ use crate::{ sol!( #[sol(rpc)] contract Erc1155Supply { - 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 totalSupply(uint256 id) external view returns (uint256); function totalSupply() external view returns (uint256); function exists(uint256 id) external view returns (bool); @@ -46,48 +40,25 @@ 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 = Erc1155Supply::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(); + let token = uint!(1_U256); // IMPORTANT: Order matters! use Erc1155Supply::*; #[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()))?), - (existsCall::SIGNATURE, receipt!(contract.exists(token_1))?), - (totalSupply_0Call::SIGNATURE, receipt!(contract.totalSupply_0(token_1))?), + (existsCall::SIGNATURE, receipt!(contract.exists(token))?), + (totalSupply_0Call::SIGNATURE, receipt!(contract.totalSupply_0(token))?), (totalSupply_1Call::SIGNATURE, receipt!(contract.totalSupply_1())?), - (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.clone(), values.clone(), data.clone()))?), - (burnCall::SIGNATURE, receipt!(contract.burn(bob_addr, token_1, value_1))?), - (burnBatchCall::SIGNATURE, receipt!(contract.burnBatch(bob_addr, ids, values))?), ]; receipts From bcee629826bdbd50c44d6c16124ee1472ee36ae3 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 08:37:17 +0100 Subject: [PATCH 34/54] docs: allow missing error docs --- contracts/src/token/erc1155/extensions/supply.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 1d2f9e04b..d41268845 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -82,6 +82,7 @@ pub trait IErc1155Supply { /// Batched version of [`Erc1155::balance_of`]. /// /// Re-export of [`Erc1155::balance_of_batch`]. + #[allow(clippy::missing_errors_doc)] fn balance_of_batch( &self, accounts: Vec
, @@ -92,6 +93,7 @@ pub trait IErc1155Supply { /// to transfer the caller's tokens, according to `approved`. /// /// Re-export of [`Erc1155::set_approval_for_all`]. + #[allow(clippy::missing_errors_doc)] fn set_approval_for_all( &mut self, operator: Address, @@ -102,12 +104,14 @@ pub trait IErc1155Supply { /// tokens. /// /// Re-export of [`Erc1155::is_approved_for_all`]. + #[allow(clippy::missing_errors_doc)] fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; /// Transfers a `value` amount of tokens of type `id` from `from` to /// `to`. /// /// Re-export of [`Erc1155::safe_transfer_from`]. + #[allow(clippy::missing_errors_doc)] fn safe_transfer_from( &mut self, from: Address, @@ -120,6 +124,7 @@ pub trait IErc1155Supply { /// Batched version of [`IErc1155Supply::safe_transfer_from`]. /// /// Re-export of [`Erc1155::safe_batch_transfer_from`]. + #[allow(clippy::missing_errors_doc)] fn safe_batch_transfer_from( &mut self, from: Address, From beaaf52d1f9f8b9fb9c531f9abe9a375469d824f Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 08:39:55 +0100 Subject: [PATCH 35/54] test: add missing IErc1155Supply import in unit tests --- contracts/src/token/erc1155/extensions/supply.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index d41268845..287d10ae8 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -657,7 +657,7 @@ impl Erc1155Supply { mod tests { use alloy_primitives::{address, Address, U256}; - use super::Erc1155Supply; + use super::{Erc1155Supply, IErc1155Supply}; use crate::token::erc1155::IErc1155; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); From b6d6129cc028ebba065fc0b50c8c31db9a41589e Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 08:53:45 +0100 Subject: [PATCH 36/54] ref: fix clippy warnings --- contracts/src/token/erc1155/extensions/supply.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 287d10ae8..16fa38e42 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -261,7 +261,7 @@ impl Erc1155Supply { } if to.is_zero() { - for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + for (token_id, &value) in token_ids.into_iter().zip(values.iter()) { /* SAFETY: Overflow not possible: values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) @@ -269,7 +269,7 @@ impl Erc1155Supply { self._total_supply.setter(token_id).sub_assign_unchecked(value); } - let total_burn_value: U256 = values.iter().sum(); + let total_burn_value: U256 = values.into_iter().sum(); /* SAFETY: Overflow not possible: total_burn_value = sum_i(values[i]) <= sum_i(total_supply(ids[i])) <= total_supply_all From d37baf52fc85f5a6583dd4515c941b108e6916c0 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 09:02:23 +0100 Subject: [PATCH 37/54] test: add missing unit tests for invalid receiver/sender --- .../src/token/erc1155/extensions/supply.rs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 16fa38e42..ffe723276 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -658,7 +658,9 @@ mod tests { use alloy_primitives::{address, Address, U256}; use super::{Erc1155Supply, IErc1155Supply}; - use crate::token::erc1155::IErc1155; + use crate::token::erc1155::{ + ERC1155InvalidReceiver, ERC1155InvalidSender, Error, IErc1155, + }; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); const BOB: Address = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); @@ -719,6 +721,24 @@ mod tests { assert_eq!(total_supply_all, contract.total_supply_all()); } + #[motsu::test] + fn mint_reverts_on_invalid_receiver(contract: Erc1155Supply) { + let token_id = random_token_ids(1)[0]; + let two = U256::from(2); + let invalid_receiver = Address::ZERO; + + let err = contract + ._mint(invalid_receiver, token_id, two, &vec![].into()) + .expect_err("should revert with `InvalidReceiver`"); + + assert!(matches!( + err, + Error::InvalidReceiver(ERC1155InvalidReceiver { + receiver + }) if receiver == invalid_receiver + )); + } + #[motsu::test] #[should_panic = "should not exceed `U256::MAX` for `_total_supply`"] fn mint_panics_on_total_supply_overflow(contract: Erc1155Supply) { @@ -772,6 +792,23 @@ mod tests { assert_eq!(U256::ZERO, contract.total_supply_all()); } + #[motsu::test] + fn burn_reverts_when_invalid_sender(contract: Erc1155Supply) { + let (token_ids, values) = setup(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 supply_unaffected_by_no_op(contract: Erc1155Supply) { let token_ids = random_token_ids(1); From c6f91a666528de2dc4be6014f556b339a8f305c0 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 3 Dec 2024 09:02:37 +0100 Subject: [PATCH 38/54] test: setup->init --- contracts/src/token/erc1155/extensions/supply.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index ffe723276..c5d7315bc 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -673,7 +673,7 @@ mod tests { (0..size).map(|_| U256::from(rand::random::())).collect() } - fn setup( + fn init( contract: &mut Erc1155Supply, receiver: Address, size: usize, @@ -702,7 +702,7 @@ mod tests { #[motsu::test] fn after_mint_single(contract: Erc1155Supply) { - let (token_ids, values) = setup(contract, ALICE, 1); + let (token_ids, values) = init(contract, ALICE, 1); assert_eq!(values[0], contract.balance_of(ALICE, token_ids[0])); assert_eq!(values[0], contract.total_supply(token_ids[0])); assert_eq!(values[0], contract.total_supply_all()); @@ -711,7 +711,7 @@ mod tests { #[motsu::test] fn after_mint_batch(contract: Erc1155Supply) { - let (token_ids, values) = setup(contract, ALICE, 4); + let (token_ids, values) = init(contract, ALICE, 4); for (&token_id, &value) in token_ids.iter().zip(values.iter()) { assert_eq!(value, contract.balance_of(ALICE, token_id)); assert_eq!(value, contract.total_supply(token_id)); @@ -767,7 +767,7 @@ mod tests { #[motsu::test] fn after_burn_single(contract: Erc1155Supply) { - let (token_ids, values) = setup(contract, ALICE, 1); + let (token_ids, values) = init(contract, ALICE, 1); contract._burn(ALICE, token_ids[0], values[0]).expect("should burn"); assert_eq!(U256::ZERO, contract.total_supply(token_ids[0])); @@ -777,7 +777,7 @@ mod tests { #[motsu::test] fn after_burn_batch(contract: Erc1155Supply) { - let (token_ids, values) = setup(contract, ALICE, 4); + let (token_ids, values) = init(contract, ALICE, 4); contract ._burn_batch(ALICE, token_ids.clone(), values.clone()) .expect("should burn batch"); @@ -794,7 +794,7 @@ mod tests { #[motsu::test] fn burn_reverts_when_invalid_sender(contract: Erc1155Supply) { - let (token_ids, values) = setup(contract, ALICE, 1); + let (token_ids, values) = init(contract, ALICE, 1); let invalid_sender = Address::ZERO; let err = contract From e04f99f37ffd8ca719b2b2a4913afbb268887812 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 4 Dec 2024 08:39:09 +0100 Subject: [PATCH 39/54] docs: Apply comment suggestions from code review Co-authored-by: Daniel Bigos --- .../src/token/erc1155/extensions/supply.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index c5d7315bc..d961c6e05 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -76,12 +76,12 @@ pub trait IErc1155Supply { /// Returns the value of tokens of type `id` owned by `account`. /// - /// Re-export of [`Erc1155::balance_of`] + /// Re-export of [`IErc1155::balance_of`] fn balance_of(&self, account: Address, id: U256) -> U256; - /// Batched version of [`Erc1155::balance_of`]. + /// Batched version of [`IErc1155Supply::balance_of`]. /// - /// Re-export of [`Erc1155::balance_of_batch`]. + /// Re-export of [`IErc1155::balance_of_batch`]. #[allow(clippy::missing_errors_doc)] fn balance_of_batch( &self, @@ -92,7 +92,7 @@ pub trait IErc1155Supply { /// Grants or revokes permission to `operator` /// to transfer the caller's tokens, according to `approved`. /// - /// Re-export of [`Erc1155::set_approval_for_all`]. + /// Re-export of [`IErc1155::set_approval_for_all`]. #[allow(clippy::missing_errors_doc)] fn set_approval_for_all( &mut self, @@ -103,14 +103,14 @@ pub trait IErc1155Supply { /// Returns true if `operator` is approved to transfer `account`'s /// tokens. /// - /// Re-export of [`Erc1155::is_approved_for_all`]. + /// Re-export of [`IErc1155::is_approved_for_all`]. #[allow(clippy::missing_errors_doc)] fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; /// Transfers a `value` amount of tokens of type `id` from `from` to /// `to`. /// - /// Re-export of [`Erc1155::safe_transfer_from`]. + /// Re-export of [`IErc1155::safe_transfer_from`]. #[allow(clippy::missing_errors_doc)] fn safe_transfer_from( &mut self, @@ -123,7 +123,7 @@ pub trait IErc1155Supply { /// Batched version of [`IErc1155Supply::safe_transfer_from`]. /// - /// Re-export of [`Erc1155::safe_batch_transfer_from`]. + /// Re-export of [`IErc1155::safe_batch_transfer_from`]. #[allow(clippy::missing_errors_doc)] fn safe_batch_transfer_from( &mut self, @@ -202,10 +202,7 @@ impl IErc1155Supply for Erc1155Supply { } impl Erc1155Supply { - // Note: overriding `_update` requires reimplementing all of the functions - // that use it. - - /// Override of [`Erc1155::_update`] that updates the supply of tokens. + /// Extended version of [`Erc1155::_update`] that updates the supply of tokens. /// /// # Arguments /// @@ -263,16 +260,16 @@ impl Erc1155Supply { if to.is_zero() { for (token_id, &value) in token_ids.into_iter().zip(values.iter()) { /* - SAFETY: Overflow not possible: - values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) + * SAFETY: Overflow not possible: + * values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) */ self._total_supply.setter(token_id).sub_assign_unchecked(value); } let total_burn_value: U256 = values.into_iter().sum(); /* - SAFETY: Overflow not possible: - total_burn_value = sum_i(values[i]) <= sum_i(total_supply(ids[i])) <= total_supply_all + * SAFETY: Overflow not possible: + * total_burn_value = sum_i(values[i]) <= sum_i(total_supply(ids[i])) <= total_supply_all */ let total_supply_all = self._total_supply_all.get() - total_burn_value; From 2436791b11b1498e1f3c344b0b39a99d14a1777f Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 4 Dec 2024 08:46:20 +0100 Subject: [PATCH 40/54] feat: implement [Add/Sub]AssignUnchecked for StorageUint --- contracts/src/utils/math/storage.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/contracts/src/utils/math/storage.rs b/contracts/src/utils/math/storage.rs index 31193c47b..01ba059c5 100644 --- a/contracts/src/utils/math/storage.rs +++ b/contracts/src/utils/math/storage.rs @@ -15,6 +15,15 @@ impl<'a, const B: usize, const L: usize> AddAssignUnchecked> } } +impl AddAssignUnchecked> + for StorageUint +{ + fn add_assign_unchecked(&mut self, rhs: Uint) { + let new_balance = self.get() + rhs; + self.set(new_balance); + } +} + pub(crate) trait SubAssignUnchecked { fn sub_assign_unchecked(&mut self, rhs: T); } @@ -27,3 +36,12 @@ impl<'a, const B: usize, const L: usize> SubAssignUnchecked> self.set(new_balance); } } + +impl SubAssignUnchecked> + for StorageUint +{ + fn sub_assign_unchecked(&mut self, rhs: Uint) { + let new_balance = self.get() - rhs; + self.set(new_balance); + } +} From 33e38443803f06238c23b66312d9561ad054cb70 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 4 Dec 2024 08:47:24 +0100 Subject: [PATCH 41/54] ref: use sub_assign_unckeched in _update --- .../src/token/erc1155/extensions/supply.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index d961c6e05..b3f710696 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -202,7 +202,8 @@ impl IErc1155Supply for Erc1155Supply { } impl Erc1155Supply { - /// Extended version of [`Erc1155::_update`] that updates the supply of tokens. + /// Extended version of [`Erc1155::_update`] that updates the supply of + /// tokens. /// /// # Arguments /// @@ -260,20 +261,20 @@ impl Erc1155Supply { if to.is_zero() { for (token_id, &value) in token_ids.into_iter().zip(values.iter()) { /* - * SAFETY: Overflow not possible: - * values[i] <= balance_of(from, token_ids[i]) <= total_supply(token_ids[i]) + * SAFETY: Overflow not possible: + * values[i] <= balance_of(from, token_ids[i]) <= + * total_supply(token_ids[i]) */ self._total_supply.setter(token_id).sub_assign_unchecked(value); } let total_burn_value: U256 = values.into_iter().sum(); /* - * SAFETY: Overflow not possible: - * total_burn_value = sum_i(values[i]) <= sum_i(total_supply(ids[i])) <= total_supply_all + * SAFETY: Overflow not possible: + * total_burn_value = sum_i(values[i]) <= + * sum_i(total_supply(ids[i])) <= total_supply_all */ - let total_supply_all = - self._total_supply_all.get() - total_burn_value; - self._total_supply_all.set(total_supply_all); + self._total_supply_all.sub_assign_unchecked(total_burn_value); } Ok(()) } From 36e57ca5e08e543863b1773c4dd2145f67865bc0 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 4 Dec 2024 08:51:03 +0100 Subject: [PATCH 42/54] chore: add 'mint' to supply bench --- benches/src/erc1155_supply.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/benches/src/erc1155_supply.rs b/benches/src/erc1155_supply.rs index 5b057e04e..448e15272 100644 --- a/benches/src/erc1155_supply.rs +++ b/benches/src/erc1155_supply.rs @@ -16,6 +16,7 @@ use crate::{ sol!( #[sol(rpc)] contract Erc1155Supply { + function mint(address to, uint256 id, uint256 amount, bytes memory data) external; function totalSupply(uint256 id) external view returns (uint256); function totalSupply() external view returns (uint256); function exists(uint256 id) external view returns (bool); @@ -40,6 +41,7 @@ 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() @@ -51,11 +53,13 @@ pub async fn run_with( let contract = Erc1155Supply::new(contract_addr, &alice_wallet); let token = uint!(1_U256); + let value = uint!(100_U256); // IMPORTANT: Order matters! use Erc1155Supply::*; #[rustfmt::skip] let receipts = vec![ + (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token, value, vec![].into()))?), (existsCall::SIGNATURE, receipt!(contract.exists(token))?), (totalSupply_0Call::SIGNATURE, receipt!(contract.totalSupply_0(token))?), (totalSupply_1Call::SIGNATURE, receipt!(contract.totalSupply_1())?), From d31191092e0834cbbc35fe28db590edc96582e89 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 4 Dec 2024 09:02:53 +0100 Subject: [PATCH 43/54] test: add happy path erc1155 tests --- .../erc1155-supply/tests/erc1155-supply.rs | 932 ++++++++++++++++++ 1 file changed, 932 insertions(+) diff --git a/examples/erc1155-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs index 3b38f1530..6c26143cc 100644 --- a/examples/erc1155-supply/tests/erc1155-supply.rs +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -399,3 +399,935 @@ async fn supply_unaffected_by_safe_transfer_from_batch( Ok(()) } + +// ===================================================================== +// Integration Tests: Happy Paths of Re-exported functions from ERC-1155 +// ===================================================================== + +#[e2e::test] +async fn balance_of_zero_balance(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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!(U256::ZERO, balance); + + Ok(()) +} + +#[e2e::test] +async fn balance_of_batch_zero_balance( + alice: Account, + bob: Account, + dave: Account, + charlie: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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![U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO], balances); + + Ok(()) +} + +#[e2e::test] +async fn mints(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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 mints_to_receiver_contract(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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(()) +} + +#[e2e::test] +async fn mint_batch( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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 mint_batch_transfer_to_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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(()) +} + +#[e2e::test] +async fn set_approval_for_all( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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 is_approved_for_all_zero_address(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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 safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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 Erc1155::balanceOfReturn { balance: initial_alice_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + let Erc1155::balanceOfReturn { balance: initial_bob_balance } = + contract.balanceOf(bob_addr, token_id).call().await?; + + 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: alice_balance } = + contract.balanceOf(alice_addr, token_id).call().await?; + assert_eq!(initial_alice_balance - value, alice_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_with_approval( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + 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_bob.mint( + bob_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )); + + 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() + ))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: bob_addr, + to: alice_addr, + id: token_id, + value + })); + + 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(()) +} + +#[e2e::test] +async fn safe_transfer_to_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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(()) +} + +#[e2e::test] +async fn safe_batch_transfer_from( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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_to_receiver_contract( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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(()) +} + +#[e2e::test] +async fn safe_batch_transfer_from_with_approval( + alice: Account, + bob: Account, + dave: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + 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 _ = watch!(contract_alice.mintBatch( + bob_addr, + token_ids.clone(), + 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, + 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 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(()) +} +#[e2e::test] +async fn burns(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .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 _ = watch!(contract.mint( + alice_addr, + token_ids[0], + values[0], + vec![].into() + )); + + let initial_balance = + contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; + assert_eq!(values[0], initial_balance); + + let receipt = receipt!(contract.burn(alice_addr, token_ids[0], values[0]))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: alice_addr, + to: Address::ZERO, + id: token_ids[0], + value: values[0], + })); + + let balance = + contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; + assert_eq!(U256::ZERO, balance); + + Ok(()) +} + +#[e2e::test] +async fn burns_with_approval(alice: Account, bob: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = 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_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_bob.setApprovalForAll(alice_addr, true)); + + let receipt = receipt!(contract.burn(bob_addr, token_ids[0], values[0]))?; + + assert!(receipt.emits(Erc1155::TransferSingle { + operator: alice_addr, + from: bob_addr, + to: Address::ZERO, + id: token_ids[0], + value: values[0], + })); + + let balance = + contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; + assert_eq!(U256::ZERO, balance); + + Ok(()) +} + +#[e2e::test] +async fn burns_batch(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(4); + let values = random_values(4); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + for (&id, &value) in token_ids.iter().zip(values.iter()) { + let balance = contract.balanceOf(alice_addr, id).call().await?.balance; + assert_eq!(value, balance); + } + + 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, + })); + + for id in token_ids { + let balance = contract.balanceOf(alice_addr, id).call().await?.balance; + assert_eq!(U256::ZERO, balance); + } + + Ok(()) +} + +#[e2e::test] +async fn burns_batch_with_approval( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = 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_ids = random_token_ids(4); + let values = random_values(4); + + let _ = watch!(contract.mintBatch( + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + for (&id, &value) in token_ids.iter().zip(values.iter()) { + let balance = contract.balanceOf(bob_addr, id).call().await?.balance; + assert_eq!(value, balance); + } + + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); + + let receipt = receipt!(contract.burnBatch( + bob_addr, + token_ids.clone(), + values.clone() + ))?; + + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: bob_addr, + to: Address::ZERO, + ids: token_ids.clone(), + values, + })); + + for id in token_ids { + let balance = contract.balanceOf(bob_addr, id).call().await?.balance; + assert_eq!(U256::ZERO, balance); + } + + Ok(()) +} From b8195fb31d4352786d98a76e72396b41aba444f6 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Wed, 4 Dec 2024 10:00:11 +0100 Subject: [PATCH 44/54] test: refactor & fix e2e tests --- examples/erc1155-supply/tests/abi/mod.rs | 4 +- .../erc1155-supply/tests/erc1155-supply.rs | 991 +++++++----------- examples/erc1155-supply/tests/mock/mod.rs | 1 + .../erc1155-supply/tests/mock/receiver.rs | 97 ++ scripts/e2e-tests.sh | 2 +- 5 files changed, 493 insertions(+), 602 deletions(-) create mode 100644 examples/erc1155-supply/tests/mock/mod.rs create mode 100644 examples/erc1155-supply/tests/mock/receiver.rs diff --git a/examples/erc1155-supply/tests/abi/mod.rs b/examples/erc1155-supply/tests/abi/mod.rs index a500aedd7..ee967b7c4 100644 --- a/examples/erc1155-supply/tests/abi/mod.rs +++ b/examples/erc1155-supply/tests/abi/mod.rs @@ -7,6 +7,8 @@ sol!( function balanceOf(address account, uint256 id) external view returns (uint256 balance); #[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 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; @@ -22,7 +24,7 @@ sol!( error ERC1155InvalidSender(address sender); error ERC1155InvalidReceiver(address receiver); error ERC1155MissingApprovalForAll(address operator, address owner); - error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id); + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); #[derive(Debug, PartialEq)] event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); diff --git a/examples/erc1155-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs index 6c26143cc..75587a9b4 100644 --- a/examples/erc1155-supply/tests/erc1155-supply.rs +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -1,15 +1,14 @@ #![cfg(feature = "e2e")] use abi::Erc1155Supply; -use alloy::{ - primitives::{Address, U256}, - rpc::types::TransactionReceipt, -}; +use alloy::primitives::{Address, U256}; use e2e::{ receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, }; +use mock::{receiver, receiver::ERC1155ReceiverMock}; mod abi; +mod mock; fn random_token_ids(size: usize) -> Vec { (0..size).map(U256::from).collect() @@ -19,26 +18,6 @@ fn random_values(size: usize) -> Vec { (0..size).map(|_| U256::from(rand::random::())).collect() } -async fn deploy_and_mint( - receiver: &Account, - size: usize, -) -> eyre::Result<(Address, Vec, Vec, TransactionReceipt)> { - let contract_addr = receiver.as_deployer().deploy().await?.address()?; - let contract = Erc1155Supply::new(contract_addr, &receiver.wallet); - - let token_ids = random_token_ids(size); - let values = random_values(size); - - let receipt = receipt!(contract.mintBatch( - receiver.address(), - token_ids.clone(), - values.clone(), - vec![].into() - ))?; - - Ok((contract_addr, token_ids, values, receipt)) -} - // ============================================================================ // Integration Tests: ERC-1155 Supply Extension // ============================================================================ @@ -62,58 +41,201 @@ async fn constructs(alice: Account) -> eyre::Result<()> { } #[e2e::test] -async fn after_mint_single(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, receipt) = - deploy_and_mint(&alice, 1).await?; +async fn mint(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155Supply::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![].into()))?; assert!(receipt.emits(Erc1155Supply::TransferSingle { operator: alice_addr, from: Address::ZERO, to: alice_addr, - id: token_ids[0], - value: values[0], + id: token_id, + value, })); let balance = - contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; - let total_supply = contract.totalSupply_0(token_ids[0]).call().await?._0; + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; let total_supply_all = contract.totalSupply_1().call().await?._0; - let token_exists = contract.exists(token_ids[0]).call().await?._0; + let token_exists = contract.exists(token_id).call().await?._0; - assert_eq!(values[0], balance); - assert_eq!(values[0], total_supply); - assert_eq!(values[0], total_supply_all); + assert_eq!(value, balance); + assert_eq!(value, total_supply); + assert_eq!(value, total_supply_all); assert!(token_exists); Ok(()) } #[e2e::test] -async fn after_mint_batch(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, receipt) = - deploy_and_mint(&alice, 4).await?; +async fn mint_to_receiver_contract(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::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 initial_receiver_balance = + contract.balanceOf(receiver_addr, token_id).call().await?.balance; + + let receipt = + receipt!(contract.mint(receiver_addr, token_id, value, vec![].into()))?; + + assert!(receipt.emits(Erc1155Supply::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: vec![].into(), + })); + + let receiver_balance = + contract.balanceOf(receiver_addr, token_id).call().await?.balance; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + let total_supply_all = contract.totalSupply_1().call().await?._0; + let token_exists = contract.exists(token_id).call().await?._0; + + assert_eq!(initial_receiver_balance + value, receiver_balance); + assert_eq!(value, total_supply); + assert_eq!(value, total_supply_all); + assert!(token_exists); + + Ok(()) +} + +#[e2e::test] +async fn mint_batch(alice: Account, bob: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::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 accounts = vec![alice_addr, bob_addr]; + + for &account in &accounts { + let receipt = receipt!(contract.mintBatch( + account, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; + + assert!(receipt.emits(Erc1155Supply::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: account, + ids: token_ids.clone(), + values: values.clone() + })); + + let balances = contract + .balanceOfBatch(vec![account, account], token_ids.clone()) + .call() + .await? + .balances; + + assert_eq!(values, balances); + } + + let accounts_len = U256::from(accounts.len()); + + for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + let token_exists = contract.exists(token_id).call().await?._0; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + + assert_eq!(value * accounts_len, total_supply); + assert!(token_exists); + } + + let total_supply_all = contract.totalSupply_1().call().await?._0; + assert_eq!(values.iter().sum::() * accounts_len, total_supply_all); + + 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 = Erc1155Supply::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 initial_receiver_balances = contract + .balanceOfBatch(vec![receiver_addr, receiver_addr], token_ids.clone()) + .call() + .await? + .balances; + + let receipt = receipt!(contract.mintBatch( + receiver_addr, + token_ids.clone(), + values.clone(), + vec![].into() + ))?; assert!(receipt.emits(Erc1155Supply::TransferBatch { operator: alice_addr, from: Address::ZERO, - to: alice_addr, + 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: vec![].into(), })); - for (&token_id, &value) in token_ids.iter().zip(values.iter()) { + let receiver_balances = contract + .balanceOfBatch(vec![receiver_addr, receiver_addr], token_ids.clone()) + .call() + .await? + .balances; + + for (idx, (&token_id, &value)) in + token_ids.iter().zip(values.iter()).enumerate() + { let token_exists = contract.exists(token_id).call().await?._0; let total_supply = contract.totalSupply_0(token_id).call().await?._0; - let balance = - contract.balanceOf(alice_addr, token_id).call().await?.balance; - assert_eq!(value, balance); + assert_eq!( + initial_receiver_balances[idx] + value, + receiver_balances[idx] + ); assert_eq!(value, total_supply); assert!(token_exists); } @@ -190,27 +312,30 @@ async fn mint_panics_on_total_supply_all_overflow( } #[e2e::test] -async fn after_burn_single(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, _) = - deploy_and_mint(&alice, 1).await?; +async fn burn(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155Supply::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![].into())); - let receipt = receipt!(contract.burn(alice_addr, token_ids[0], values[0]))?; + let receipt = receipt!(contract.burn(alice_addr, token_id, value))?; assert!(receipt.emits(Erc1155Supply::TransferSingle { operator: alice_addr, from: alice_addr, to: Address::ZERO, - id: token_ids[0], - value: values[0], + id: token_id, + value, })); - let token_exists = contract.exists(token_ids[0]).call().await?._0; + let token_exists = contract.exists(token_id).call().await?._0; let balance = - contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; - let total_supply = contract.totalSupply_0(token_ids[0]).call().await?._0; + contract.balanceOf(alice_addr, token_id).call().await?.balance; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; let total_supply_all = contract.totalSupply_1().call().await?._0; assert_eq!(U256::ZERO, balance); @@ -222,12 +347,58 @@ async fn after_burn_single(alice: Account) -> eyre::Result<()> { } #[e2e::test] -async fn after_burn_batch(alice: Account) -> eyre::Result<()> { - let (contract_addr, token_ids, values, _) = - deploy_and_mint(&alice, 4).await?; +async fn burn_with_approval(alice: Account, bob: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155Supply::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(bob_addr, token_id, value, vec![].into())); + + let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); + + let receipt = receipt!(contract.burn(bob_addr, token_id, value))?; + + assert!(receipt.emits(Erc1155Supply::TransferSingle { + operator: alice_addr, + from: bob_addr, + to: Address::ZERO, + id: token_id, + value, + })); + + let token_exists = contract.exists(token_id).call().await?._0; + let balance = contract.balanceOf(bob_addr, token_id).call().await?.balance; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + let total_supply_all = contract.totalSupply_1().call().await?._0; + + assert_eq!(U256::ZERO, balance); + assert_eq!(U256::ZERO, total_supply); + assert_eq!(U256::ZERO, total_supply_all); + assert!(!token_exists); + + Ok(()) +} + +#[e2e::test] +async fn burn_batch(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(4); + let values = random_values(4); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); let receipt = receipt!(contract.burnBatch( alice_addr, @@ -243,7 +414,7 @@ async fn after_burn_batch(alice: Account) -> eyre::Result<()> { values, })); - for &token_id in &token_ids { + for token_id in token_ids { let balance = contract.balanceOf(alice_addr, token_id).call().await?.balance; let token_exists = contract.exists(token_id).call().await?._0; @@ -260,19 +431,74 @@ async fn after_burn_batch(alice: Account) -> eyre::Result<()> { Ok(()) } +#[e2e::test] +async fn burn_batch_with_approval( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155Supply::new(contract_addr, &bob.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 _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); + + let receipt = receipt!(contract.burnBatch( + bob_addr, + token_ids.clone(), + values.clone() + ))?; + + assert!(receipt.emits(Erc1155Supply::TransferBatch { + operator: alice_addr, + from: bob_addr, + to: Address::ZERO, + ids: token_ids.clone(), + values, + })); + + for token_id in token_ids { + let balance = + contract.balanceOf(bob_addr, token_id).call().await?.balance; + let token_exists = contract.exists(token_id).call().await?._0; + let total_supply = contract.totalSupply_0(token_id).call().await?._0; + + assert_eq!(U256::ZERO, balance); + assert_eq!(U256::ZERO, total_supply); + assert!(!token_exists); + } + + let total_supply_all = contract.totalSupply_1().call().await?._0; + assert_eq!(U256::ZERO, total_supply_all); + + Ok(()) +} + #[e2e::test] async fn supply_unaffected_by_safe_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { - let (contract_addr, token_ids, values, _) = - deploy_and_mint(&alice, 1).await?; + let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); - let token_id = token_ids[0]; - let value = values[0]; + 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())); // assert balances as expected after mint let alice_balance = @@ -330,12 +556,20 @@ async fn supply_unaffected_by_safe_transfer_from_batch( alice: Account, bob: Account, ) -> eyre::Result<()> { - let (contract_addr, token_ids, values, _) = - deploy_and_mint(&alice, 4).await?; + let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155Supply::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( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); // assert balances as expected after mint for (&token_id, &value) in token_ids.iter().zip(values.iter()) { @@ -402,255 +636,37 @@ async fn supply_unaffected_by_safe_transfer_from_batch( // ===================================================================== // Integration Tests: Happy Paths of Re-exported functions from ERC-1155 -// ===================================================================== - -#[e2e::test] -async fn balance_of_zero_balance(alice: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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!(U256::ZERO, balance); - - Ok(()) -} - -#[e2e::test] -async fn balance_of_batch_zero_balance( - alice: Account, - bob: Account, - dave: Account, - charlie: Account, -) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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![U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO], balances); - - Ok(()) -} - -#[e2e::test] -async fn mints(alice: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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 mints_to_receiver_contract(alice: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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(()) -} - -#[e2e::test] -async fn mint_batch( - alice: Account, - bob: Account, - dave: Account, -) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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); - } +#[e2e::test] +async fn balance_of_zero_balance(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 Erc1155::balanceOfBatchReturn { balances } = contract - .balanceOfBatch(vec![account, account, account], token_ids.clone()) - .call() - .await?; + let Erc1155Supply::balanceOfReturn { balance } = + contract.balanceOf(alice.address(), token_ids[0]).call().await?; + assert_eq!(U256::ZERO, balance); - assert_eq!(values, balances); - } Ok(()) } #[e2e::test] -async fn mint_batch_transfer_to_receiver_contract( +async fn balance_of_batch_zero_balance( alice: Account, + bob: Account, + dave: Account, + charlie: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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?; + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); + let accounts = + vec![alice.address(), bob.address(), dave.address(), charlie.address()]; + let token_ids = random_token_ids(4); - for (idx, value) in values.iter().enumerate() { - assert_eq!( - initial_receiver_balances[idx] + value, - receiver_balances[idx] - ); - } + let Erc1155Supply::balanceOfBatchReturn { balances } = + contract.balanceOfBatch(accounts, token_ids).call().await?; + assert_eq!(vec![U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO], balances); Ok(()) } @@ -660,13 +676,8 @@ async fn set_approval_for_all( alice: Account, bob: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -675,13 +686,13 @@ async fn set_approval_for_all( let receipt = receipt!(contract.setApprovalForAll(bob_addr, approved_value))?; - assert!(receipt.emits(Erc1155::ApprovalForAll { + assert!(receipt.emits(Erc1155Supply::ApprovalForAll { account: alice_addr, operator: bob_addr, approved: approved_value, })); - let Erc1155::isApprovedForAllReturn { approved } = + let Erc1155Supply::isApprovedForAllReturn { approved } = contract.isApprovedForAll(alice_addr, bob_addr).call().await?; assert_eq!(approved_value, approved); @@ -689,13 +700,13 @@ async fn set_approval_for_all( let receipt = receipt!(contract.setApprovalForAll(bob_addr, approved_value))?; - assert!(receipt.emits(Erc1155::ApprovalForAll { + assert!(receipt.emits(Erc1155Supply::ApprovalForAll { account: alice_addr, operator: bob_addr, approved: approved_value, })); - let Erc1155::isApprovedForAllReturn { approved } = + let Erc1155Supply::isApprovedForAllReturn { approved } = contract.isApprovedForAll(alice_addr, bob_addr).call().await?; assert_eq!(approved_value, approved); @@ -704,17 +715,12 @@ async fn set_approval_for_all( #[e2e::test] async fn is_approved_for_all_zero_address(alice: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let invalid_operator = Address::ZERO; - let Erc1155::isApprovedForAllReturn { approved } = contract + let Erc1155Supply::isApprovedForAllReturn { approved } = contract .isApprovedForAll(alice.address(), invalid_operator) .call() .await?; @@ -726,13 +732,8 @@ async fn is_approved_for_all_zero_address(alice: Account) -> eyre::Result<()> { #[e2e::test] async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -745,9 +746,9 @@ async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { vec![0, 1, 2, 3].into() )); - let Erc1155::balanceOfReturn { balance: initial_alice_balance } = + let Erc1155Supply::balanceOfReturn { balance: initial_alice_balance } = contract.balanceOf(alice_addr, token_id).call().await?; - let Erc1155::balanceOfReturn { balance: initial_bob_balance } = + let Erc1155Supply::balanceOfReturn { balance: initial_bob_balance } = contract.balanceOf(bob_addr, token_id).call().await?; let receipt = receipt!(contract.safeTransferFrom( @@ -758,7 +759,7 @@ async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferSingle { + assert!(receipt.emits(Erc1155Supply::TransferSingle { operator: alice_addr, from: alice_addr, to: bob_addr, @@ -766,11 +767,11 @@ async fn safe_transfer_from(alice: Account, bob: Account) -> eyre::Result<()> { value })); - let Erc1155::balanceOfReturn { balance: alice_balance } = + let Erc1155Supply::balanceOfReturn { balance: alice_balance } = contract.balanceOf(alice_addr, token_id).call().await?; assert_eq!(initial_alice_balance - value, alice_balance); - let Erc1155::balanceOfReturn { balance: bob_balance } = + let Erc1155Supply::balanceOfReturn { balance: bob_balance } = contract.balanceOf(bob_addr, token_id).call().await?; assert_eq!(initial_bob_balance + value, bob_balance); @@ -782,14 +783,9 @@ async fn safe_transfer_from_with_approval( alice: Account, bob: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract_alice = Erc1155::new(contract_addr, &alice.wallet); - let contract_bob = Erc1155::new(contract_addr, &bob.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155Supply::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155Supply::new(contract_addr, &bob.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -805,9 +801,9 @@ async fn safe_transfer_from_with_approval( let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); - let Erc1155::balanceOfReturn { balance: initial_alice_balance } = + let Erc1155Supply::balanceOfReturn { balance: initial_alice_balance } = contract_alice.balanceOf(alice_addr, token_id).call().await?; - let Erc1155::balanceOfReturn { balance: initial_bob_balance } = + let Erc1155Supply::balanceOfReturn { balance: initial_bob_balance } = contract_alice.balanceOf(bob_addr, token_id).call().await?; let receipt = receipt!(contract_alice.safeTransferFrom( @@ -818,7 +814,7 @@ async fn safe_transfer_from_with_approval( vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferSingle { + assert!(receipt.emits(Erc1155Supply::TransferSingle { operator: alice_addr, from: bob_addr, to: alice_addr, @@ -826,11 +822,11 @@ async fn safe_transfer_from_with_approval( value })); - let Erc1155::balanceOfReturn { balance: alice_balance } = + let Erc1155Supply::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 } = + let Erc1155Supply::balanceOfReturn { balance: bob_balance } = contract_alice.balanceOf(bob_addr, token_id).call().await?; assert_eq!(initial_bob_balance - value, bob_balance); @@ -841,13 +837,8 @@ async fn safe_transfer_from_with_approval( async fn safe_transfer_to_receiver_contract( alice: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let receiver_addr = receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) @@ -864,9 +855,9 @@ async fn safe_transfer_to_receiver_contract( vec![0, 1, 2, 3].into() )); - let Erc1155::balanceOfReturn { balance: initial_alice_balance } = + let Erc1155Supply::balanceOfReturn { balance: initial_alice_balance } = contract.balanceOf(alice_addr, token_id).call().await?; - let Erc1155::balanceOfReturn { balance: initial_receiver_balance } = + let Erc1155Supply::balanceOfReturn { balance: initial_receiver_balance } = contract.balanceOf(receiver_addr, token_id).call().await?; let receipt = receipt!(contract.safeTransferFrom( @@ -877,7 +868,7 @@ async fn safe_transfer_to_receiver_contract( vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferSingle { + assert!(receipt.emits(Erc1155Supply::TransferSingle { operator: alice_addr, from: alice_addr, to: receiver_addr, @@ -890,14 +881,14 @@ async fn safe_transfer_to_receiver_contract( from: alice_addr, id: token_id, value, - data: fixed_bytes!("").into(), + data: vec![].into(), })); - let Erc1155::balanceOfReturn { balance: alice_balance } = + let Erc1155Supply::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 } = + let Erc1155Supply::balanceOfReturn { balance: receiver_balance } = contract.balanceOf(receiver_addr, token_id).call().await?; assert_eq!(initial_receiver_balance + value, receiver_balance); @@ -909,13 +900,8 @@ async fn safe_batch_transfer_from( alice: Account, bob: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155Supply::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -929,13 +915,14 @@ async fn safe_batch_transfer_from( vec![].into() )); - let Erc1155::balanceOfBatchReturn { balances: initial_alice_balances } = - contract_alice - .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) - .call() - .await?; + let Erc1155Supply::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 } = + let Erc1155Supply::balanceOfBatchReturn { balances: initial_bob_balances } = contract_alice .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) .call() @@ -949,7 +936,7 @@ async fn safe_batch_transfer_from( vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferBatch { + assert!(receipt.emits(Erc1155Supply::TransferBatch { operator: alice_addr, from: alice_addr, to: bob_addr, @@ -957,13 +944,13 @@ async fn safe_batch_transfer_from( values: values.clone() })); - let Erc1155::balanceOfBatchReturn { balances: alice_balances } = + let Erc1155Supply::balanceOfBatchReturn { balances: alice_balances } = contract_alice .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) .call() .await?; - let Erc1155::balanceOfBatchReturn { balances: bob_balances } = + let Erc1155Supply::balanceOfBatchReturn { balances: bob_balances } = contract_alice .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) .call() @@ -981,13 +968,8 @@ async fn safe_batch_transfer_from( async fn safe_batch_transfer_to_receiver_contract( alice: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155Supply::new(contract_addr, &alice.wallet); let receiver_addr = receiver::deploy(&alice.wallet, ERC1155ReceiverMock::RevertType::None) @@ -1004,20 +986,19 @@ async fn safe_batch_transfer_to_receiver_contract( vec![].into() )); - let Erc1155::balanceOfBatchReturn { balances: initial_alice_balances } = - contract - .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) - .call() - .await?; + let Erc1155Supply::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 Erc1155Supply::balanceOfBatchReturn { + balances: initial_receiver_balances, + } = contract + .balanceOfBatch(vec![receiver_addr, receiver_addr], token_ids.clone()) + .call() + .await?; let receipt = receipt!(contract.safeBatchTransferFrom( alice_addr, @@ -1027,7 +1008,7 @@ async fn safe_batch_transfer_to_receiver_contract( vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferBatch { + assert!(receipt.emits(Erc1155Supply::TransferBatch { operator: alice_addr, from: alice_addr, to: receiver_addr, @@ -1040,15 +1021,16 @@ async fn safe_batch_transfer_to_receiver_contract( from: alice_addr, ids: token_ids.clone(), values: values.clone(), - data: fixed_bytes!("").into(), + data: vec![].into(), })); - let Erc1155::balanceOfBatchReturn { balances: alice_balances } = contract - .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) - .call() - .await?; + let Erc1155Supply::balanceOfBatchReturn { balances: alice_balances } = + contract + .balanceOfBatch(vec![alice_addr, alice_addr], token_ids.clone()) + .call() + .await?; - let Erc1155::balanceOfBatchReturn { balances: receiver_balances } = + let Erc1155Supply::balanceOfBatchReturn { balances: receiver_balances } = contract .balanceOfBatch( vec![receiver_addr, receiver_addr], @@ -1074,14 +1056,9 @@ async fn safe_batch_transfer_from_with_approval( bob: Account, dave: Account, ) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract_alice = Erc1155::new(contract_addr, &alice.wallet); - let contract_bob = Erc1155::new(contract_addr, &bob.wallet); + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155Supply::new(contract_addr, &alice.wallet); + let contract_bob = Erc1155Supply::new(contract_addr, &bob.wallet); let alice_addr = alice.address(); let bob_addr = bob.address(); @@ -1098,13 +1075,13 @@ async fn safe_batch_transfer_from_with_approval( let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); - let Erc1155::balanceOfBatchReturn { balances: initial_dave_balances } = + let Erc1155Supply::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 } = + let Erc1155Supply::balanceOfBatchReturn { balances: initial_bob_balances } = contract_alice .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) .call() @@ -1118,7 +1095,7 @@ async fn safe_batch_transfer_from_with_approval( vec![].into() ))?; - assert!(receipt.emits(Erc1155::TransferBatch { + assert!(receipt.emits(Erc1155Supply::TransferBatch { operator: alice_addr, from: bob_addr, to: dave_addr, @@ -1126,13 +1103,13 @@ async fn safe_batch_transfer_from_with_approval( values: values.clone() })); - let Erc1155::balanceOfBatchReturn { balances: bob_balances } = + let Erc1155Supply::balanceOfBatchReturn { balances: bob_balances } = contract_alice .balanceOfBatch(vec![bob_addr, bob_addr], token_ids.clone()) .call() .await?; - let Erc1155::balanceOfBatchReturn { balances: dave_balances } = + let Erc1155Supply::balanceOfBatchReturn { balances: dave_balances } = contract_alice .balanceOfBatch(vec![dave_addr, dave_addr], token_ids.clone()) .call() @@ -1145,189 +1122,3 @@ async fn safe_batch_transfer_from_with_approval( Ok(()) } -#[e2e::test] -async fn burns(alice: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .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 _ = watch!(contract.mint( - alice_addr, - token_ids[0], - values[0], - vec![].into() - )); - - let initial_balance = - contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; - assert_eq!(values[0], initial_balance); - - let receipt = receipt!(contract.burn(alice_addr, token_ids[0], values[0]))?; - - assert!(receipt.emits(Erc1155::TransferSingle { - operator: alice_addr, - from: alice_addr, - to: Address::ZERO, - id: token_ids[0], - value: values[0], - })); - - let balance = - contract.balanceOf(alice_addr, token_ids[0]).call().await?.balance; - assert_eq!(U256::ZERO, balance); - - Ok(()) -} - -#[e2e::test] -async fn burns_with_approval(alice: Account, bob: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = 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_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_bob.setApprovalForAll(alice_addr, true)); - - let receipt = receipt!(contract.burn(bob_addr, token_ids[0], values[0]))?; - - assert!(receipt.emits(Erc1155::TransferSingle { - operator: alice_addr, - from: bob_addr, - to: Address::ZERO, - id: token_ids[0], - value: values[0], - })); - - let balance = - contract.balanceOf(bob_addr, token_ids[0]).call().await?.balance; - assert_eq!(U256::ZERO, balance); - - Ok(()) -} - -#[e2e::test] -async fn burns_batch(alice: Account) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = Erc1155::new(contract_addr, &alice.wallet); - - let alice_addr = alice.address(); - let token_ids = random_token_ids(4); - let values = random_values(4); - - let _ = watch!(contract.mintBatch( - alice_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )); - - for (&id, &value) in token_ids.iter().zip(values.iter()) { - let balance = contract.balanceOf(alice_addr, id).call().await?.balance; - assert_eq!(value, balance); - } - - 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, - })); - - for id in token_ids { - let balance = contract.balanceOf(alice_addr, id).call().await?.balance; - assert_eq!(U256::ZERO, balance); - } - - Ok(()) -} - -#[e2e::test] -async fn burns_batch_with_approval( - alice: Account, - bob: Account, -) -> eyre::Result<()> { - let contract_addr = alice - .as_deployer() - .with_default_constructor::() - .deploy() - .await? - .address()?; - let contract = 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_ids = random_token_ids(4); - let values = random_values(4); - - let _ = watch!(contract.mintBatch( - bob_addr, - token_ids.clone(), - values.clone(), - vec![].into() - )); - - for (&id, &value) in token_ids.iter().zip(values.iter()) { - let balance = contract.balanceOf(bob_addr, id).call().await?.balance; - assert_eq!(value, balance); - } - - let _ = watch!(contract_bob.setApprovalForAll(alice_addr, true)); - - let receipt = receipt!(contract.burnBatch( - bob_addr, - token_ids.clone(), - values.clone() - ))?; - - assert!(receipt.emits(Erc1155::TransferBatch { - operator: alice_addr, - from: bob_addr, - to: Address::ZERO, - ids: token_ids.clone(), - values, - })); - - for id in token_ids { - let balance = contract.balanceOf(bob_addr, id).call().await?.balance; - assert_eq!(U256::ZERO, balance); - } - - Ok(()) -} diff --git a/examples/erc1155-supply/tests/mock/mod.rs b/examples/erc1155-supply/tests/mock/mod.rs new file mode 100644 index 000000000..4c0db7eaa --- /dev/null +++ b/examples/erc1155-supply/tests/mock/mod.rs @@ -0,0 +1 @@ +pub mod receiver; diff --git a/examples/erc1155-supply/tests/mock/receiver.rs b/examples/erc1155-supply/tests/mock/receiver.rs new file mode 100644 index 000000000..da021c4cb --- /dev/null +++ b/examples/erc1155-supply/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()) +} diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 87c4fe654..c81a6a126 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-supply" From ea0599c48aa88c3242801f9d8010198a5670bfd8 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 4 Dec 2024 12:03:41 +0100 Subject: [PATCH 45/54] test: revert change to e2e-tests.sh Co-authored-by: Daniel Bigos --- scripts/e2e-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index c81a6a126..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-supply" +cargo test --features std,e2e --test "*" From 36c1726f74e1126c6cbea49129f13c32af025757 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 4 Dec 2024 12:05:30 +0100 Subject: [PATCH 46/54] test: add missing total_supply assertion in motsu test Co-authored-by: Daniel Bigos --- contracts/src/token/erc1155/extensions/supply.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index b3f710696..6088787e9 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -786,6 +786,7 @@ mod tests { contract.erc1155.balance_of(ALICE, token_id) ); assert!(!contract.exists(token_id)); + assert_eq!(U256::ZERO, contract.total_supply(token_id)); } assert_eq!(U256::ZERO, contract.total_supply_all()); } From 21990cd7fa5b3ebe2466ecb4f02f744102928edc Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Thu, 5 Dec 2024 10:43:59 +0100 Subject: [PATCH 47/54] docs: update changelog to include changes to math/storage.rs --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fcfefc0a..8435f3df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `MethodError` for `safe_erc20::Error`. #402 - Use `function_selector!` to calculate transfer type selector in `Erc1155`. #417 +- Implement `AddAssignUnchecked` and `SubAssignUnchecked` for `StorageUint`. #418 ### Fixed From 3db5c35f7635a5b458d684aa171f110083d92913 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 09:01:34 +0100 Subject: [PATCH 48/54] ref(test): import random_token_ids, random_values from root erc1155 --- contracts/src/token/erc1155/extensions/supply.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index 6088787e9..b412a3fe2 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -657,20 +657,13 @@ mod tests { use super::{Erc1155Supply, IErc1155Supply}; use crate::token::erc1155::{ + tests::{random_token_ids, random_values}, ERC1155InvalidReceiver, ERC1155InvalidSender, Error, IErc1155, }; const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); const BOB: Address = address!("B0B0cB49ec2e96DF5F5fFB081acaE66A2cBBc2e2"); - 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, From 4d5fba229198f15b75ebb22d272bff2bf8a8fc7f Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 09:03:23 +0100 Subject: [PATCH 49/54] ref(test): refactor random_values in erc1155-supply e2e --- examples/erc1155-supply/tests/erc1155-supply.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/erc1155-supply/tests/erc1155-supply.rs b/examples/erc1155-supply/tests/erc1155-supply.rs index 75587a9b4..bf6682185 100644 --- a/examples/erc1155-supply/tests/erc1155-supply.rs +++ b/examples/erc1155-supply/tests/erc1155-supply.rs @@ -15,7 +15,7 @@ fn random_token_ids(size: usize) -> Vec { } fn random_values(size: usize) -> Vec { - (0..size).map(|_| U256::from(rand::random::())).collect() + (1..=size).map(U256::from).collect() } // ============================================================================ From 0564db44060c46bdcb5c9563ee9d1c8ab6b4133c Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 09:03:47 +0100 Subject: [PATCH 50/54] ref(test): refactor random_values in erc1155 e2e --- examples/erc1155/tests/erc1155.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 61144f3a7..25d0b6afd 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -13,7 +13,7 @@ fn random_token_ids(size: usize) -> Vec { } fn random_values(size: usize) -> Vec { - (1..size + 1).map(U256::from).collect() + (1..=size).map(U256::from).collect() } // ============================================================================ From bf2216d4cea7d85832f6cde97b44fa1d9feceb30 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 09:04:30 +0100 Subject: [PATCH 51/54] ref(test): remove 'rand' from erc1155-supply example --- Cargo.lock | 1 - examples/erc1155-supply/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f26537b95..ff7be5413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1583,7 +1583,6 @@ dependencies = [ "e2e", "eyre", "openzeppelin-stylus", - "rand", "stylus-sdk", "tokio", ] diff --git a/examples/erc1155-supply/Cargo.toml b/examples/erc1155-supply/Cargo.toml index 915dd4095..f35a31b34 100644 --- a/examples/erc1155-supply/Cargo.toml +++ b/examples/erc1155-supply/Cargo.toml @@ -16,7 +16,6 @@ alloy.workspace = true eyre.workspace = true tokio.workspace = true e2e.workspace = true -rand.workspace = true [features] e2e = [] From bf33f246a3f2f6a1595fe87987a3634b6042fe74 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 09:09:39 +0100 Subject: [PATCH 52/54] ref(test): use function_selector in receiver mock --- .../erc1155-supply/tests/mock/receiver.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/examples/erc1155-supply/tests/mock/receiver.rs b/examples/erc1155-supply/tests/mock/receiver.rs index da021c4cb..6959954ab 100644 --- a/examples/erc1155-supply/tests/mock/receiver.rs +++ b/examples/erc1155-supply/tests/mock/receiver.rs @@ -1,13 +1,29 @@ #![allow(dead_code)] #![cfg(feature = "e2e")] use alloy::{ - primitives::{fixed_bytes, Address, FixedBytes}, + primitives::{Address, FixedBytes, U256}, sol, }; use e2e::Wallet; +use stylus_sdk::{abi::Bytes, function_selector}; -const REC_RETVAL: FixedBytes<4> = fixed_bytes!("f23a6e61"); -const BAT_RETVAL: FixedBytes<4> = fixed_bytes!("bc197c81"); +const REC_RETVAL: FixedBytes<4> = FixedBytes(function_selector!( + "onERC1155Received", + Address, + Address, + U256, + U256, + Bytes +)); + +const BAT_RETVAL: FixedBytes<4> = FixedBytes(function_selector!( + "onERC1155BatchReceived", + Address, + Address, + Vec, + Vec, + Bytes +)); sol! { #[allow(missing_docs)] From f95fe095c276f2c2e103f36c3da4bc9c69e2888e Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 11:19:32 +0100 Subject: [PATCH 53/54] ref: set IErc1155Supply to only contain supply-related functions that need to be reexported --- contracts/src/token/erc1155/extensions/mod.rs | 2 +- .../src/token/erc1155/extensions/supply.rs | 419 +++--------------- docs/modules/ROOT/pages/erc1155-supply.adoc | 17 +- examples/erc1155-supply/src/lib.rs | 17 +- 4 files changed, 95 insertions(+), 360 deletions(-) diff --git a/contracts/src/token/erc1155/extensions/mod.rs b/contracts/src/token/erc1155/extensions/mod.rs index 76559920c..0e046f3b4 100644 --- a/contracts/src/token/erc1155/extensions/mod.rs +++ b/contracts/src/token/erc1155/extensions/mod.rs @@ -6,5 +6,5 @@ pub mod uri_storage; pub use burnable::IErc1155Burnable; pub use metadata_uri::{Erc1155MetadataUri, IErc1155MetadataUri}; -pub use supply::Erc1155Supply; +pub use supply::{Erc1155Supply, IErc1155Supply}; pub use uri_storage::Erc1155UriStorage; diff --git a/contracts/src/token/erc1155/extensions/supply.rs b/contracts/src/token/erc1155/extensions/supply.rs index b412a3fe2..0c5e218c3 100644 --- a/contracts/src/token/erc1155/extensions/supply.rs +++ b/contracts/src/token/erc1155/extensions/supply.rs @@ -19,7 +19,6 @@ use stylus_sdk::{ abi::Bytes, msg, prelude::{public, sol_storage}, - storage::TopLevelStorage, }; use crate::{ @@ -39,17 +38,9 @@ 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 Erc1155Supply {} - /// Required interface of a [`Erc1155Supply`] contract. #[interface_id] pub trait IErc1155Supply { - /// The error type associated to this trait implementation. - type Error: Into>; - /// Total value of tokens in with a given id. /// /// # Arguments @@ -73,77 +64,13 @@ pub trait IErc1155Supply { /// * `&self` - Read access to the contract's state. /// * `id` - Token id as a number. fn exists(&self, id: U256) -> bool; - - /// Returns the value of tokens of type `id` owned by `account`. - /// - /// Re-export of [`IErc1155::balance_of`] - fn balance_of(&self, account: Address, id: U256) -> U256; - - /// Batched version of [`IErc1155Supply::balance_of`]. - /// - /// Re-export of [`IErc1155::balance_of_batch`]. - #[allow(clippy::missing_errors_doc)] - fn balance_of_batch( - &self, - accounts: Vec
, - ids: Vec, - ) -> Result, erc1155::Error>; - - /// Grants or revokes permission to `operator` - /// to transfer the caller's tokens, according to `approved`. - /// - /// Re-export of [`IErc1155::set_approval_for_all`]. - #[allow(clippy::missing_errors_doc)] - fn set_approval_for_all( - &mut self, - operator: Address, - approved: bool, - ) -> Result<(), erc1155::Error>; - - /// Returns true if `operator` is approved to transfer `account`'s - /// tokens. - /// - /// Re-export of [`IErc1155::is_approved_for_all`]. - #[allow(clippy::missing_errors_doc)] - fn is_approved_for_all(&self, account: Address, operator: Address) -> bool; - - /// Transfers a `value` amount of tokens of type `id` from `from` to - /// `to`. - /// - /// Re-export of [`IErc1155::safe_transfer_from`]. - #[allow(clippy::missing_errors_doc)] - fn safe_transfer_from( - &mut self, - from: Address, - to: Address, - id: U256, - value: U256, - data: Bytes, - ) -> Result<(), erc1155::Error>; - - /// Batched version of [`IErc1155Supply::safe_transfer_from`]. - /// - /// Re-export of [`IErc1155::safe_batch_transfer_from`]. - #[allow(clippy::missing_errors_doc)] - fn safe_batch_transfer_from( - &mut self, - from: Address, - to: Address, - ids: Vec, - values: Vec, - data: Bytes, - ) -> Result<(), erc1155::Error>; } -#[public] impl IErc1155Supply for Erc1155Supply { - type Error = erc1155::Error; - fn total_supply(&self, id: U256) -> U256 { self._total_supply.get(id) } - #[selector(name = "totalSupply")] fn total_supply_all(&self) -> U256 { *self._total_supply_all } @@ -151,6 +78,11 @@ impl IErc1155Supply for Erc1155Supply { fn exists(&self, id: U256) -> bool { self.total_supply(id) > U256::ZERO } +} + +#[public] +impl IErc1155 for Erc1155Supply { + type Error = erc1155::Error; fn balance_of(&self, account: Address, id: U256) -> U256 { self.erc1155.balance_of(account, id) @@ -201,6 +133,63 @@ impl IErc1155Supply for Erc1155Supply { } } +impl Erc1155Supply { + /// Creates a `value` amount of tokens of type `id`, and assigns + /// them to `to`. + /// + /// Re-export of [`Erc1155::_mint`]. + #[allow(clippy::missing_errors_doc)] + pub fn _mint( + &mut self, + to: Address, + id: U256, + value: U256, + data: &Bytes, + ) -> Result<(), erc1155::Error> { + self._do_mint(to, vec![id], vec![value], data) + } + + /// Batched version of [`Self::_mint`]. + /// + /// Re-export of [`Erc1155::_mint_batch`]. + #[allow(clippy::missing_errors_doc)] + pub fn _mint_batch( + &mut self, + to: Address, + ids: Vec, + values: Vec, + data: &Bytes, + ) -> Result<(), erc1155::Error> { + self._do_mint(to, ids, values, data) + } + + /// Destroys a `value` amount of tokens of type `id` from `from`. + /// + /// Re-export of [`Erc1155::_burn`]. + #[allow(clippy::missing_errors_doc)] + pub fn _burn( + &mut self, + from: Address, + id: U256, + value: U256, + ) -> Result<(), erc1155::Error> { + self._do_burn(from, vec![id], vec![value]) + } + + /// Batched version of [`Self::_burn`]. + /// + /// Re-export of [`Erc1155::_burn_batch`]. + #[allow(clippy::missing_errors_doc)] + pub fn _burn_batch( + &mut self, + from: Address, + ids: Vec, + values: Vec, + ) -> Result<(), erc1155::Error> { + self._do_burn(from, ids, values) + } +} + impl Erc1155Supply { /// Extended version of [`Erc1155::_update`] that updates the supply of /// tokens. @@ -279,43 +268,6 @@ impl Erc1155Supply { Ok(()) } - /// Version of [`Self::_update`] that performs the token acceptance check by - /// calling [`erc1155::IERC1155Receiver::on_erc_1155_received`] or - /// [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] on the - /// receiver address if it contains code. - /// - /// # 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 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 length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't - /// returned its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// If updated balance and/or supply exceeds `U256::MAX`, may happen during - /// the `mint` operation. fn _update_with_acceptance_check( &mut self, from: Address, @@ -339,190 +291,6 @@ impl Erc1155Supply { Ok(()) } - /// 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`. - /// - /// # Errors - /// - /// If `to` is `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event. - /// - /// # Panics - /// - /// If updated balance and/or supply exceeds `U256::MAX`. - pub fn _mint( - &mut self, - to: Address, - id: U256, - value: U256, - data: &Bytes, - ) -> Result<(), erc1155::Error> { - self._do_mint(to, vec![id], vec![value], data) - } - - /// Batched version of [`Self::_mint`]. - /// - /// # 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 - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't - /// returned its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// If updated balance and/or supply exceeds `U256::MAX`. - pub fn _mint_batch( - &mut self, - to: Address, - ids: Vec, - values: Vec, - data: &Bytes, - ) -> Result<(), erc1155::Error> { - self._do_mint(to, ids, values, data) - } - - /// Destroys a `value` amount of tokens of type `id` from `from`. - /// - /// # Arguments - /// - /// * `&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 - /// [`erc1155::Error::InvalidSender`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event. - /// - /// # Panics - /// - /// Should not panic. - pub fn _burn( - &mut self, - from: Address, - id: U256, - value: U256, - ) -> Result<(), erc1155::Error> { - self._do_burn(from, vec![id], vec![value]) - } - - /// Batched version of [`Self::_burn`]. - /// - /// # Arguments - /// - /// * `&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 - /// [`erc1155::Error::InvalidSender`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// Should not panic. - pub fn _burn_batch( - &mut self, - from: Address, - ids: Vec, - values: Vec, - ) -> Result<(), erc1155::Error> { - self._do_burn(from, ids, values) - } -} - -impl Erc1155Supply { - /// Creates `values` of tokens specified by `ids`, and assigns - /// them to `to`. Performs the token acceptance check by - /// calling [`erc1155::IERC1155Receiver::on_erc_1155_received`] or - /// [`erc1155::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 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`. - /// - /// # Errors - /// - /// If `to` is `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't - /// returned its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// If updated balance and/or supply exceeds `U256::MAX`. fn _do_mint( &mut self, to: Address, @@ -545,32 +313,6 @@ impl Erc1155Supply { Ok(()) } - /// 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 - /// [`erc1155::Error::InvalidSender`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// Should not panic. fn _do_burn( &mut self, from: Address, @@ -592,43 +334,6 @@ impl Erc1155Supply { Ok(()) } - /// 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 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 the `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If `from` is the `Address::ZERO`, then the error - /// [`erc1155::Error::InvalidSender`] is returned. - /// If length of `ids` is not equal to length of `values`, then the - /// error [`erc1155::Error::InvalidArrayLength`] is returned. - /// If `value` is greater than the balance of the `from` account, - /// then the error [`erc1155::Error::InsufficientBalance`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_received`] hasn't returned - /// its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// If [`erc1155::IERC1155Receiver::on_erc_1155_batch_received`] hasn't - /// returned its interface id or returned with error, then the error - /// [`erc1155::Error::InvalidReceiver`] is returned. - /// - /// # Events - /// - /// Emits a [`erc1155::TransferSingle`] event if the arrays contain one - /// element, and [`erc1155::TransferBatch`] otherwise. - /// - /// # Panics - /// - /// If updated balance and/or supply exceeds `U256::MAX`. fn do_safe_transfer_from( &mut self, from: Address, diff --git a/docs/modules/ROOT/pages/erc1155-supply.adoc b/docs/modules/ROOT/pages/erc1155-supply.adoc index 230ebd864..f827e771a 100644 --- a/docs/modules/ROOT/pages/erc1155-supply.adoc +++ b/docs/modules/ROOT/pages/erc1155-supply.adoc @@ -11,7 +11,9 @@ you need to add the following code to your contract: [source,rust] ---- -use openzeppelin_stylus::token::erc1155::extensions::Erc1155Supply; +use openzeppelin_stylus::token::erc1155::extensions::{ + Erc1155Supply, IErc1155Supply, +}; sol_storage! { #[entrypoint] @@ -24,6 +26,19 @@ sol_storage! { #[public] #[inherit(Erc1155Supply)] impl Erc1155Example { + fn total_supply(&self, id: U256) -> U256 { + self.erc1155_supply.total_supply(id) + } + + #[selector(name = "totalSupply")] + fn total_supply_all(&self) -> U256 { + self.erc1155_supply.total_supply_all() + } + + fn exists(&self, id: U256) -> bool { + self.erc1155_supply.exists(id) + } + // ... } ---- diff --git a/examples/erc1155-supply/src/lib.rs b/examples/erc1155-supply/src/lib.rs index b91ec153b..dd18816ed 100644 --- a/examples/erc1155-supply/src/lib.rs +++ b/examples/erc1155-supply/src/lib.rs @@ -4,7 +4,9 @@ extern crate alloc; use alloc::vec::Vec; use alloy_primitives::{Address, U256}; -use openzeppelin_stylus::token::erc1155::extensions::Erc1155Supply; +use openzeppelin_stylus::token::erc1155::extensions::{ + Erc1155Supply, IErc1155Supply, +}; use stylus_sdk::{ abi::Bytes, prelude::{entrypoint, public, sol_storage}, @@ -20,6 +22,19 @@ sol_storage! { #[public] #[inherit(Erc1155Supply)] impl Erc1155Example { + fn total_supply(&self, id: U256) -> U256 { + self.erc1155_supply.total_supply(id) + } + + #[selector(name = "totalSupply")] + fn total_supply_all(&self) -> U256 { + self.erc1155_supply.total_supply_all() + } + + fn exists(&self, id: U256) -> bool { + self.erc1155_supply.exists(id) + } + // Add token minting feature. pub fn mint( &mut self, From cb76b30148dde71cee9d77ff3bdc007ceb66d1d4 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Tue, 10 Dec 2024 11:31:58 +0100 Subject: [PATCH 54/54] docs: add note on applying 'selector' to total_supply_all --- docs/modules/ROOT/pages/erc1155-supply.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/erc1155-supply.adoc b/docs/modules/ROOT/pages/erc1155-supply.adoc index f827e771a..ea9ba4405 100644 --- a/docs/modules/ROOT/pages/erc1155-supply.adoc +++ b/docs/modules/ROOT/pages/erc1155-supply.adoc @@ -7,7 +7,9 @@ Useful for scenarios where Fungible and Non-fungible tokens have to be clearly i == Usage In order to make an xref:erc1155.adoc[ERC-1155] token with https://docs.rs/openzeppelin-stylus/0.2.0-alpha.2/openzeppelin_stylus/token/erc1155/extensions/supply/index.html[Supply] flavour, -you need to add the following code to your contract: +you need to reexport all the supply-related functions. +Make sure to apply the `#[selector(name = "totalSupply")]` attribute to the `total_supply_all` function! +You need to create the specified contract as follows: [source,rust] ----