From 95570579e094be1cacf0ba1698bb956a64437246 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 25 Mar 2024 20:51:25 +0400 Subject: [PATCH 01/68] add prototype for erc721 --- contracts/src/erc721/mod.rs | 655 ++++++++++++++++++++++++++++++++++++ 1 file changed, 655 insertions(+) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 8b137891..275960ab 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1 +1,656 @@ +use std::borrow::BorrowMut; +use std::marker::PhantomData; +use std::prelude::v1::{String, ToString, Vec}; +use std::vec; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{Address, U256}, + alloy_sol_types::{sol, SolError}, + call::Call, + evm, function_selector, msg, + prelude::*, +}; +sol_storage! { + pub struct Erc721 { + mapping(uint256 => address) owners; + + mapping(address => uint256) balances; + + mapping(uint256 => address) token_approvals; + + mapping(address => mapping(address => bool)) operator_approvals; + + PhantomData phantom_data; + } +} + +sol! { + /// Emitted when `tokenId` token is transferred from `from` to `to`. + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /// Emitted when `owner` enables `approved` to manage the `tokenId` token. + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /// Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /// Indicates that an address can't be an owner. + /// For example, `address(0)` is a forbidden owner in ERC-20. Used in balance queries. + error ERC721InvalidOwner(address owner); + + /// Indicates a `tokenId` whose `owner` is the zero address. + error ERC721NonexistentToken(uint256 tokenId); + + /// Indicates an error related to the ownership over a particular token. Used in transfers. + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /// Indicates a failure with the token `sender`. Used in transfers. + error ERC721InvalidSender(address sender); + + /// Indicates a failure with the token `receiver`. Used in transfers. + error ERC721InvalidReceiver(address receiver); + + /// Indicates a failure with the `operator`’s approval. Used in transfers. + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /// Indicates a failure with the `approver` of a token to be approved. Used in approvals. + error ERC721InvalidApprover(address approver); + + /// Indicates a failure with the `operator` to be approved. Used in approvals. + error ERC721InvalidOperator(address operator); +} + +sol_interface! { + /// ERC-721 token receiver interface + /// Interface for any contract that wants to support safeTransfers + /// from ERC-721 asset contracts. + interface IERC721Receiver { + /// Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + /// by `operator` from `from`, this function is called. + /// + /// It must return its Solidity selector to confirm the token transfer. + /// If any other value is returned or the interface is not implemented by the recipient, the transfer will be + /// reverted. + /// + /// The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); + } +} + +pub trait Erc721Info { + const NAME: &'static str; + const SYMBOL: &'static str; + const BASE_URI: &'static str; +} + +pub struct Erc721Error(Vec); + +// NOTE: According to current implementation of stylus every error should be converted to Vec +impl From for Vec { + fn from(value: Erc721Error) -> Vec { + value.0 + } +} + +impl From for Erc721Error { + fn from(value: T) -> Self { + Self(value.encode()) + } +} + +#[external] +impl Erc721 { + /// Returns the Uniform Resource Identifier (URI) for `token_id` token. + #[selector(name = "tokenURI")] + pub fn token_uri(token_id: U256) -> Result { + let token_uri = if T::BASE_URI.is_empty() { + "".to_string() + } else { + T::BASE_URI.to_string() + &token_id.to_string() + }; + Ok(token_uri) + } + + /// Returns the number of tokens in ``owner``'s account. + pub fn balance_of(&self, owner: Address) -> Result { + if owner == Address::ZERO { + return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); + } + Ok(self.balances.get(owner)) + } + + /// Returns the owner of the `token_id` token. + /// + /// Requirements: + /// + /// - `token_id` must exist. + pub fn owner_of(&self, token_id: U256) -> Result { + self.require_owned(token_id) + } + + /// Safely transfers `token_id` token from `from` to `to`, checking first that contract recipients + /// are aware of the ERC-721 protocol to prevent tokens from being forever locked. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `to` cannot be the zero address. + /// - `token_id` token must exist and be owned by `from`. + /// - If the caller is not `from`, it must have been allowed to move this token by either {approve} or + /// {setApprovalForAll}. + /// - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + /// a safe transfer. + /// + /// Emits a {Transfer} event. + pub fn safe_transfer_from( + toplevel_storage: &mut (impl TopLevelStorage + BorrowMut), + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Erc721Error> { + Self::safe_transfer_from_with_data( + toplevel_storage, + from, + to, + token_id, + vec![].into(), + ) + } + + /// Safely transfers `token_id` token from `from` to `to`. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `to` cannot be the zero address. + /// - `token_id` token must exist and be owned by `from`. + /// - If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + /// a safe transfer. + /// + /// Emits a {Transfer} event. + #[selector(name = "safeTransferFrom")] + pub fn safe_transfer_from_with_data( + toplevel_storage: &mut (impl TopLevelStorage + BorrowMut), + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Erc721Error> { + toplevel_storage.borrow_mut().transfer_from(from, to, token_id)?; + Self::check_on_erc721_received( + toplevel_storage, + msg::sender(), + from, + to, + token_id, + data, + ) + } + + /// Transfers `token_id` token from `from` to `to`. + /// + /// WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721 + /// or else they may be permanently lost. Usage of {safe_transfer_from} prevents loss, though the caller must + /// understand this adds an external call which potentially creates a reentrancy vulnerability. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `to` cannot be the zero address. + /// - `token_id` token must be owned by `from`. + /// - If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// + /// Emits a {Transfer} event. + pub fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Erc721Error> { + if to == Address::ZERO { + return Err( + ERC721InvalidReceiver { receiver: Address::ZERO }.into() + ); + } + + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + let previous_owner = self.update(to, token_id, msg::sender())?; + if previous_owner != from { + return Err(ERC721IncorrectOwner { + sender: from, + tokenId: token_id, + owner: previous_owner, + } + .into()); + } + Ok(()) + } + + /// Gives permission to `to` to transfer `token_id` token to another account. + /// The approval is cleared when the token is transferred. + /// + /// Only a single account can be approved at a time, so approving the zero address clears previous approvals. + /// + /// Requirements: + /// + /// - The caller must own the token or be an approved operator. + /// - `token_id` must exist. + /// + /// Emits an {Approval} event. + pub fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Erc721Error> { + self.approve_inner(to, token_id, msg::sender(), true) + } + + /// Approve or remove `operator` as an operator for the caller. + /// Operators can call {transfer_from} or {safe_transfer_from} for any token owned by the caller. + /// + /// Requirements: + /// + /// - The `operator` cannot be the address zero. + /// + /// Emits an {ApprovalForAll} event. + pub fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Erc721Error> { + self.set_approval_for_all_inner(msg::sender(), operator, approved) + } + + /// Returns the account approved for `token_id` token. + /// + /// Requirements: + /// + /// - `token_id` must exist. + pub fn get_approved(&self, token_id: U256) -> Result { + self.require_owned(token_id)?; + self.get_approved_inner(token_id) + } + + /// Returns if the `operator` is allowed to manage all the assets of `owner`. + /// + /// See {set_approval_for_all} + pub fn is_approved_for_all( + &self, + owner: Address, + operator: Address, + ) -> Result { + Ok(self.operator_approvals.get(owner).get(operator)) + } +} + +impl Erc721 { + /// Returns the owner of the `token_id`. Does NOT revert if token doesn't exist + /// + /// IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the + /// core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances + /// consistent with ownership. The invariant to preserve is that for any address `a` the value returned by + /// `balance_of(a)` must be equal to the number of tokens such that `owner_of_inner(token_id)` is `a`. + pub fn owner_of_inner( + &self, + token_id: U256, + ) -> Result { + Ok(self.owners.get(token_id)) + } + + /// Returns the approved address for `token_id`. Returns 0 if `token_id` is not minted. + pub fn get_approved_inner( + &self, + token_id: U256, + ) -> Result { + Ok(self.token_approvals.get(token_id)) + } + + /// Returns whether `spender` is allowed to manage `owner`'s tokens, or `token_id` in + /// particular (ignoring whether it is owned by `owner`). + /// + /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this + /// assumption. + pub fn is_authorized( + &self, + owner: Address, + spender: Address, + token_id: U256, + ) -> Result { + let is_authorized = spender != Address::ZERO + && (owner == spender + || self.is_approved_for_all(owner, spender)? + || self.get_approved_inner(token_id)? == spender); + Ok(is_authorized) + } + + /// Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. + /// Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets + /// the `spender` for the specific `token_id`. + /// + /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this + /// assumption. + pub fn check_authorized( + &self, + owner: Address, + spender: Address, + token_id: U256, + ) -> Result<(), Erc721Error> { + if !self.is_authorized(owner, spender, token_id)? { + return if owner == Address::ZERO { + Err(ERC721NonexistentToken { tokenId: token_id }.into()) + } else { + Err(ERC721InsufficientApproval { + operator: spender, + tokenId: token_id, + } + .into()) + }; + } + Ok(()) + } + + /// Unsafe write access to the balances, used by extensions that "mint" tokens using an {owner_of} override. + /// + /// NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that + /// a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + /// + /// WARNING: Increasing an account's balance using this function tends to be paired with an override of the + /// {owner_of_inner} function to resolve the ownership of the corresponding tokens so that balances and ownership + /// remain consistent with one another. + pub fn increase_balance(&mut self, account: Address, value: U256) { + self.balances.setter(account).add_assign_unchecked(value) + } + + /// Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner + /// (or `to`) is the zero address. Returns the owner of the `token_id` before the update. + /// + /// The `auth` argument is optional. If the value passed is non 0, then this function will check that + /// `auth` is either the owner of the token, or approved to operate on the token (by the owner). + /// + /// Emits a {Transfer} event. + /// + /// NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + pub fn update( + &mut self, + to: Address, + token_id: U256, + auth: Address, + ) -> Result { + let from = self.owner_of_inner(token_id)?; + + // Perform (optional) operator check + if auth != Address::ZERO { + self.check_authorized(from, auth, token_id)?; + } + + // Execute the update + if from != Address::ZERO { + // Clear approval. No need to re-authorize or emit the Approval event + self.approve_inner(Address::ZERO, token_id, Address::ZERO, false)?; + self.balances.setter(from).sub_assign_unchecked(U256::from(1)); + } + + if to != Address::ZERO { + self.balances.setter(to).add_assign_unchecked(U256::from(1)) + } + + self.owners.setter(token_id).set(to); + + evm::log(Transfer { from, to, tokenId: token_id }); + + Ok(from) + } + + /// Mints `token_id` and transfers it to `to`. + /// + /// WARNING: Usage of this method is discouraged, use {safe_mint} whenever possible + /// + /// Requirements: + /// + /// - `token_id` must not exist. + /// - `to` cannot be the zero address. + /// + /// Emits a {Transfer} event. + pub fn mint( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Erc721Error> { + if to == Address::ZERO { + return Err( + ERC721InvalidReceiver { receiver: Address::ZERO }.into() + ); + } + + let previous_owner = self.update(to, token_id, Address::ZERO)?; + if previous_owner != Address::ZERO { + return Err(ERC721InvalidSender { sender: Address::ZERO }.into()); + } + Ok(()) + } + + /// Same as {xref-ERC721-safe_mint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + pub fn safe_mint( + toplevel_storage: &mut (impl TopLevelStorage + BorrowMut), + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Erc721Error> { + toplevel_storage.borrow_mut().mint(to, token_id)?; + Self::check_on_erc721_received( + toplevel_storage, + msg::sender(), + Address::ZERO, + to, + token_id, + data, + ) + } + + /// Destroys `token_id`. + /// The approval is cleared when the token is burned. + /// This is an internal function that does not check if the sender is authorized to operate on the token. + /// + /// Requirements: + /// + /// - `token_id` must exist. + /// + /// Emits a {Transfer} event. + pub fn burn(&mut self, token_id: U256) -> Result<(), Erc721Error> { + let previous_owner = + self.update(Address::ZERO, token_id, Address::ZERO)?; + if previous_owner == Address::ZERO { + Err(ERC721NonexistentToken { tokenId: token_id }.into()) + } else { + Ok(()) + } + } + + /// Transfers `token_id` from `from` to `to`. + /// As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + /// + /// Requirements: + /// + /// - `to` cannot be the zero address. + /// - `token_id` token must be owned by `from`. + /// + /// Emits a {Transfer} event. + pub fn transfer( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Erc721Error> { + if to == Address::ZERO { + return Err( + ERC721InvalidReceiver { receiver: Address::ZERO }.into() + ); + } + + let previous_owner = self.update(to, token_id, Address::ZERO)?; + if previous_owner == Address::ZERO { + Err(ERC721NonexistentToken { tokenId: token_id }.into()) + } else if previous_owner != from { + Err(ERC721IncorrectOwner { + sender: from, + tokenId: token_id, + owner: previous_owner, + } + .into()) + } else { + Ok(()) + } + } + + /// Same as {xref-ERC721-safe_transfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is + /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + pub fn safe_transfer( + storage: &mut (impl TopLevelStorage + BorrowMut), + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Erc721Error> { + storage.borrow_mut().transfer(from, to, token_id)?; + Self::check_on_erc721_received( + storage, + msg::sender(), + from, + to, + token_id, + data, + ) + } + + /// Variant of `approve_inner` with an optional flag to enable or disable the {Approval} event. The event is not + /// emitted in the context of transfers. + pub fn approve_inner( + &mut self, + to: Address, + token_id: U256, + auth: Address, + emit_event: bool, + ) -> Result<(), Erc721Error> { + // Avoid reading the owner unless necessary + if emit_event || auth != Address::ZERO { + let owner = self.require_owned(token_id)?; + + // We do not use _isAuthorized because single-token approvals should not be able to call approve + if auth != Address::ZERO + && owner != auth + && !self.is_approved_for_all(owner, auth)? + { + return Err(ERC721InvalidApprover { approver: auth }.into()); + } + + if emit_event { + evm::log(Approval { owner, approved: to, tokenId: token_id }) + } + } + + self.token_approvals.setter(token_id).set(to); + Ok(()) + } + + /// Approve `operator` to operate on all of `owner` tokens + /// + /// Requirements: + /// - operator can't be the address zero. + /// + /// Emits an {ApprovalForAll} event. + pub fn set_approval_for_all_inner( + &mut self, + owner: Address, + operator: Address, + approved: bool, + ) -> Result<(), Erc721Error> { + if operator == Address::ZERO { + return Err(ERC721InvalidOperator { operator }.into()); + } + self.operator_approvals.setter(owner).setter(operator).set(approved); + evm::log(ApprovalForAll { owner, operator, approved }); + Ok(()) + } + + /// Reverts if the `token_id` doesn't have a current owner (it hasn't been minted, or it has been burned). + /// Returns the owner. + /// + /// Overrides to ownership logic should be done to {owner_of_inner}. + pub fn require_owned( + &self, + token_id: U256, + ) -> Result { + let owner = self.owner_of_inner(token_id)?; + if owner == Address::ZERO { + return Err(ERC721NonexistentToken { tokenId: token_id }.into()); + } + Ok(owner) + } + + /// Performs an acceptance check for the provided `operator` by calling {IERC721-onERC721Received} + /// on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). + /// + /// The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA). + /// Otherwise, the recipient must implement {IERC721Receiver-onERC721Received} and return the acceptance magic value to accept + /// the transfer. + pub fn check_on_erc721_received( + storage: &mut impl TopLevelStorage, + operator: Address, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Erc721Error> { + const IERC721RECEIVER_INTERFACE_ID: u32 = 0x150b7a02; + if to.has_code() { + let call = Call::new_in(storage); + return match IERC721Receiver::new(to).on_erc_721_received( + call, + operator, + from, + token_id, + data.to_vec(), + ) { + Ok(result) => { + let received_interface_id = u32::from_be_bytes(result.0); + if received_interface_id != IERC721RECEIVER_INTERFACE_ID { + Err(ERC721InvalidReceiver { receiver: to }.into()) + } else { + Ok(()) + } + } + Err(err) => Err(Erc721Error(err.into())), + }; + } + Ok(()) + } +} + +use stylus_sdk::storage::{StorageGuardMut, StorageUint}; + +pub trait IncrementalMath { + fn add_assign_unchecked(&mut self, rhs: T); + + fn sub_assign_unchecked(&mut self, rhs: T); +} + +impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { + fn add_assign_unchecked(&mut self, rhs: U256) { + let new_balance = self.get() + rhs; + self.set(new_balance); + } + + fn sub_assign_unchecked(&mut self, rhs: U256) { + let new_balance = self.get() - rhs; + self.set(new_balance); + } +} From 3bace8f6faad36b5e0c70c6c865e782dc905ae15 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 25 Mar 2024 21:07:10 +0400 Subject: [PATCH 02/68] split events and errors in sol! macro --- contracts/src/erc721/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 275960ab..bafed172 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -28,13 +28,15 @@ sol_storage! { sol! { /// Emitted when `tokenId` token is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - + /// Emitted when `owner` enables `approved` to manage the `tokenId` token. event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - + /// Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. event ApprovalForAll(address indexed owner, address indexed operator, bool approved); +} +sol! { /// Indicates that an address can't be an owner. /// For example, `address(0)` is a forbidden owner in ERC-20. Used in balance queries. error ERC721InvalidOwner(address owner); From a551ccf956a99b27e87c74857f3f2dcec0fff633 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 26 Mar 2024 15:57:42 +0400 Subject: [PATCH 03/68] restructure same erc721 same as erc20 --- contracts/src/erc721/mod.rs | 49 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index bafed172..f55cc19e 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -11,20 +11,6 @@ use stylus_sdk::{ prelude::*, }; -sol_storage! { - pub struct Erc721 { - mapping(uint256 => address) owners; - - mapping(address => uint256) balances; - - mapping(uint256 => address) token_approvals; - - mapping(address => mapping(address => bool)) operator_approvals; - - PhantomData phantom_data; - } -} - sol! { /// Emitted when `tokenId` token is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -63,6 +49,20 @@ sol! { error ERC721InvalidOperator(address operator); } +pub struct Erc721Error(Vec); + +impl From for Vec { + fn from(value: Erc721Error) -> Vec { + value.0 + } +} + +impl From for Erc721Error { + fn from(value: T) -> Self { + Self(value.encode()) + } +} + sol_interface! { /// ERC-721 token receiver interface /// Interface for any contract that wants to support safeTransfers @@ -91,18 +91,17 @@ pub trait Erc721Info { const BASE_URI: &'static str; } -pub struct Erc721Error(Vec); +sol_storage! { + pub struct Erc721 { + mapping(uint256 => address) owners; -// NOTE: According to current implementation of stylus every error should be converted to Vec -impl From for Vec { - fn from(value: Erc721Error) -> Vec { - value.0 - } -} + mapping(address => uint256) balances; -impl From for Erc721Error { - fn from(value: T) -> Self { - Self(value.encode()) + mapping(uint256 => address) token_approvals; + + mapping(address => mapping(address => bool)) operator_approvals; + + PhantomData phantom_data; } } @@ -368,7 +367,7 @@ impl Erc721 { /// {owner_of_inner} function to resolve the ownership of the corresponding tokens so that balances and ownership /// remain consistent with one another. pub fn increase_balance(&mut self, account: Address, value: U256) { - self.balances.setter(account).add_assign_unchecked(value) + self.balances.setter(account).add_assign_unchecked(value); } /// Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner From b732325c7c4383dcc17e2f794aea386ec0092bf3 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 26 Mar 2024 19:22:07 +0400 Subject: [PATCH 04/68] add arguments to documentation --- contracts/src/erc721/mod.rs | 288 ++++++++++++++++++++++++++++-------- 1 file changed, 226 insertions(+), 62 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index f55cc19e..0a219303 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -14,10 +14,10 @@ use stylus_sdk::{ sol! { /// Emitted when `tokenId` token is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - + /// Emitted when `owner` enables `approved` to manage the `tokenId` token. event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - + /// Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. event ApprovalForAll(address indexed owner, address indexed operator, bool approved); } @@ -107,18 +107,16 @@ sol_storage! { #[external] impl Erc721 { - /// Returns the Uniform Resource Identifier (URI) for `token_id` token. - #[selector(name = "tokenURI")] - pub fn token_uri(token_id: U256) -> Result { - let token_uri = if T::BASE_URI.is_empty() { - "".to_string() - } else { - T::BASE_URI.to_string() + &token_id.to_string() - }; - Ok(token_uri) - } - /// Returns the number of tokens in ``owner``'s account. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `owner` - Account of the token's owner. + /// + /// # Returns + /// + /// The balance of the owner. pub fn balance_of(&self, owner: Address) -> Result { if owner == Address::ZERO { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); @@ -128,9 +126,18 @@ impl Erc721 { /// Returns the owner of the `token_id` token. /// - /// Requirements: + /// # Arguments /// - /// - `token_id` must exist. + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Token id as a number + /// + /// # Returns + /// + /// The owner of the token. + /// + /// # Requirements + /// + /// * `token_id` must exist. pub fn owner_of(&self, token_id: U256) -> Result { self.require_owned(token_id) } @@ -138,25 +145,38 @@ impl Erc721 { /// Safely transfers `token_id` token from `from` to `to`, checking first that contract recipients /// are aware of the ERC-721 protocol to prevent tokens from being forever locked. /// - /// Requirements: + /// # Arguments /// - /// - `from` cannot be the zero address. - /// - `to` cannot be the zero address. - /// - `token_id` token must exist and be owned by `from`. - /// - If the caller is not `from`, it must have been allowed to move this token by either {approve} or - /// {setApprovalForAll}. - /// - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon - /// a safe transfer. + /// * `storage` - Write access to the contract's state. + /// * `from` - Account of the sender + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number /// - /// Emits a {Transfer} event. + /// # Returns + /// + /// Result indicating success or failure. + /// + /// # Requirements + /// + /// * `from` cannot be the zero address. + /// * `to` cannot be the zero address. + /// * `token_id` token must exist and be owned by `from`. + /// * If the caller is not `from`, it must have been allowed to move this token by either {approve} or + /// * {setApprovalForAll}. + /// * If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + /// * a safe transfer. + /// + /// # Events + /// + /// A {Transfer} event. pub fn safe_transfer_from( - toplevel_storage: &mut (impl TopLevelStorage + BorrowMut), + storage: &mut (impl TopLevelStorage + BorrowMut), from: Address, to: Address, token_id: U256, ) -> Result<(), Erc721Error> { Self::safe_transfer_from_with_data( - toplevel_storage, + storage, from, to, token_id, @@ -166,27 +186,41 @@ impl Erc721 { /// Safely transfers `token_id` token from `from` to `to`. /// - /// Requirements: + /// # Arguments /// - /// - `from` cannot be the zero address. - /// - `to` cannot be the zero address. - /// - `token_id` token must exist and be owned by `from`. - /// - If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. - /// - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon - /// a safe transfer. + /// * `storage` - Write access to the contract's state. + /// * `from` - Account of the sender + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// * `data` - Additional data with no specified format, sent in call to `to` /// - /// Emits a {Transfer} event. + /// # Returns + /// + /// Result indicating success or failure. + /// + /// # Requirements + /// + /// * `from` cannot be the zero address. + /// * `to` cannot be the zero address. + /// * `token_id` token must exist and be owned by `from`. + /// * If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// * If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + /// * a safe transfer. + /// + /// # Events + /// + /// A {Transfer} event. #[selector(name = "safeTransferFrom")] pub fn safe_transfer_from_with_data( - toplevel_storage: &mut (impl TopLevelStorage + BorrowMut), + storage: &mut (impl TopLevelStorage + BorrowMut), from: Address, to: Address, token_id: U256, data: Bytes, ) -> Result<(), Erc721Error> { - toplevel_storage.borrow_mut().transfer_from(from, to, token_id)?; + storage.borrow_mut().transfer_from(from, to, token_id)?; Self::check_on_erc721_received( - toplevel_storage, + storage, msg::sender(), from, to, @@ -201,14 +235,23 @@ impl Erc721 { /// or else they may be permanently lost. Usage of {safe_transfer_from} prevents loss, though the caller must /// understand this adds an external call which potentially creates a reentrancy vulnerability. /// - /// Requirements: + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number /// - /// - `from` cannot be the zero address. - /// - `to` cannot be the zero address. - /// - `token_id` token must be owned by `from`. - /// - If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// # Requirements: /// - /// Emits a {Transfer} event. + /// * `from` cannot be the zero address. + /// * `to` cannot be the zero address. + /// * `token_id` token must be owned by `from`. + /// * If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// + /// # Events + /// + /// A {Transfer} event. pub fn transfer_from( &mut self, from: Address, @@ -240,11 +283,18 @@ impl Erc721 { /// /// Only a single account can be approved at a time, so approving the zero address clears previous approvals. /// - /// Requirements: + /// # Arguments + /// * `&mut self` - Write access to the contract's state. + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// + /// # Requirements: /// /// - The caller must own the token or be an approved operator. /// - `token_id` must exist. /// + /// # Events + /// /// Emits an {Approval} event. pub fn approve( &mut self, @@ -257,10 +307,17 @@ impl Erc721 { /// Approve or remove `operator` as an operator for the caller. /// Operators can call {transfer_from} or {safe_transfer_from} for any token owned by the caller. /// - /// Requirements: + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `operator` - Account add to the set of authorized operators. + /// * `approved` - Flag that that set approval or disapproval for the operator. + /// + /// # Requirements: /// /// - The `operator` cannot be the address zero. /// + /// /// Emits an {ApprovalForAll} event. pub fn set_approval_for_all( &mut self, @@ -272,7 +329,12 @@ impl Erc721 { /// Returns the account approved for `token_id` token. /// - /// Requirements: + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Token id as a number + /// + /// # Requirements: /// /// - `token_id` must exist. pub fn get_approved(&self, token_id: U256) -> Result { @@ -282,6 +344,12 @@ impl Erc721 { /// Returns if the `operator` is allowed to manage all the assets of `owner`. /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `owner` - Account of the token's owner. + /// * `operator` - Account to add to the set of authorized operators. + /// /// See {set_approval_for_all} pub fn is_approved_for_all( &self, @@ -299,6 +367,11 @@ impl Erc721 { /// core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances /// consistent with ownership. The invariant to preserve is that for any address `a` the value returned by /// `balance_of(a)` must be equal to the number of tokens such that `owner_of_inner(token_id)` is `a`. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Token id as a number pub fn owner_of_inner( &self, token_id: U256, @@ -307,6 +380,11 @@ impl Erc721 { } /// Returns the approved address for `token_id`. Returns 0 if `token_id` is not minted. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Token id as a number pub fn get_approved_inner( &self, token_id: U256, @@ -319,6 +397,12 @@ impl Erc721 { /// /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this /// assumption. + /// + /// # Arguments + /// * `&self` - Read access to the contract's state. + /// * `owner` - Account of the token's owner. + /// * `spender` - Account that will spend token. + /// * `token_id` - Token id as a number pub fn is_authorized( &self, owner: Address, @@ -338,6 +422,13 @@ impl Erc721 { /// /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this /// assumption. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `owner` - Account of the token's owner. + /// * `spender` - Account that will spend token. + /// * `token_id` - Token id as a number pub fn check_authorized( &self, owner: Address, @@ -366,6 +457,12 @@ impl Erc721 { /// WARNING: Increasing an account's balance using this function tends to be paired with an override of the /// {owner_of_inner} function to resolve the ownership of the corresponding tokens so that balances and ownership /// remain consistent with one another. + /// + /// # Arguments + /// * `account` - Account to increase balance. + /// * `value` - The number of tokens to increase balance. + /// + /// * `&mut self` - Write access to the contract's state. pub fn increase_balance(&mut self, account: Address, value: U256) { self.balances.setter(account).add_assign_unchecked(value); } @@ -376,9 +473,18 @@ impl Erc721 { /// The `auth` argument is optional. If the value passed is non 0, then this function will check that /// `auth` is either the owner of the token, or approved to operate on the token (by the owner). /// - /// Emits a {Transfer} event. - /// /// NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `auth` - Account used for authorization of the update. + /// + /// # Events + /// + /// Emits a {Transfer} event. pub fn update( &mut self, to: Address, @@ -414,10 +520,16 @@ impl Erc721 { /// /// WARNING: Usage of this method is discouraged, use {safe_mint} whenever possible /// - /// Requirements: + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// + /// # Requirements: /// - /// - `token_id` must not exist. - /// - `to` cannot be the zero address. + /// * `token_id` must not exist. + /// * `to` cannot be the zero address. /// /// Emits a {Transfer} event. pub fn mint( @@ -440,15 +552,22 @@ impl Erc721 { /// Same as {xref-ERC721-safe_mint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + /// + /// # Arguments + /// + /// * `storage` - Write access to the contract's state. + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// * `data` - Additional data with no specified format, sent in call to `to` pub fn safe_mint( - toplevel_storage: &mut (impl TopLevelStorage + BorrowMut), + storage: &mut (impl TopLevelStorage + BorrowMut), to: Address, token_id: U256, data: Bytes, ) -> Result<(), Erc721Error> { - toplevel_storage.borrow_mut().mint(to, token_id)?; + storage.borrow_mut().mint(to, token_id)?; Self::check_on_erc721_received( - toplevel_storage, + storage, msg::sender(), Address::ZERO, to, @@ -461,9 +580,14 @@ impl Erc721 { /// The approval is cleared when the token is burned. /// This is an internal function that does not check if the sender is authorized to operate on the token. /// - /// Requirements: + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `token_id` - Token id as a number /// - /// - `token_id` must exist. + /// # Requirements: + /// + /// * `token_id` must exist. /// /// Emits a {Transfer} event. pub fn burn(&mut self, token_id: U256) -> Result<(), Erc721Error> { @@ -479,10 +603,17 @@ impl Erc721 { /// Transfers `token_id` from `from` to `to`. /// As opposed to {transferFrom}, this imposes no restrictions on msg.sender. /// - /// Requirements: + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `from` - Account of the sender + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// + /// # Requirements: /// - /// - `to` cannot be the zero address. - /// - `token_id` token must be owned by `from`. + /// * `to` cannot be the zero address. + /// * `token_id` token must be owned by `from`. /// /// Emits a {Transfer} event. pub fn transfer( @@ -514,6 +645,14 @@ impl Erc721 { /// Same as {xref-ERC721-safe_transfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + /// + /// # Arguments + /// + /// * `storage` - Write access to the contract's state. + /// * `from` - Account of the sender + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// * `data` - Additional data with no specified format, sent in call to `to` pub fn safe_transfer( storage: &mut (impl TopLevelStorage + BorrowMut), from: Address, @@ -534,6 +673,13 @@ impl Erc721 { /// Variant of `approve_inner` with an optional flag to enable or disable the {Approval} event. The event is not /// emitted in the context of transfers. + /// + /// # Arguments + /// * `&mut self` - Write access to the contract's state. + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// * `auth` - Account used for authorization of the update. + /// * `emit_event` - Emit ['Approval'] event flag. pub fn approve_inner( &mut self, to: Address, @@ -554,7 +700,7 @@ impl Erc721 { } if emit_event { - evm::log(Approval { owner, approved: to, tokenId: token_id }) + evm::log(Approval { owner, approved: to, tokenId: token_id }); } } @@ -564,8 +710,14 @@ impl Erc721 { /// Approve `operator` to operate on all of `owner` tokens /// - /// Requirements: - /// - operator can't be the address zero. + /// # Arguments + /// * `&mut self` - Write access to the contract's state. + /// * `owner` - Account the token's owner. + /// * `operator` - Account to add to the set of authorized operators. + /// * `approved` - Flag that that set approval or disapproval for the operator. + /// + /// # Requirements: + /// * operator can't be the address zero. /// /// Emits an {ApprovalForAll} event. pub fn set_approval_for_all_inner( @@ -586,6 +738,10 @@ impl Erc721 { /// Returns the owner. /// /// Overrides to ownership logic should be done to {owner_of_inner}. + /// + /// # Arguments + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Token id as a number pub fn require_owned( &self, token_id: U256, @@ -603,6 +759,14 @@ impl Erc721 { /// The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA). /// Otherwise, the recipient must implement {IERC721Receiver-onERC721Received} and return the acceptance magic value to accept /// the transfer. + /// + /// # Arguments + /// * `storage` - Write access to the contract's state. + /// * `operator` - Account to add to the set of authorized operators. + /// * `from` - Account of the sender + /// * `to` - Account of the recipient + /// * `token_id` - Token id as a number + /// * `data` - Additional data with no specified format, sent in call to `to` pub fn check_on_erc721_received( storage: &mut impl TopLevelStorage, operator: Address, From 6cd1d755e2697e824f2ffb0808682ca8e45c5ada Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 26 Mar 2024 19:49:19 +0400 Subject: [PATCH 05/68] add events section to documentation --- contracts/src/erc721/mod.rs | 81 +++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 0a219303..b22051e3 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -7,7 +7,7 @@ use stylus_sdk::{ alloy_primitives::{Address, U256}, alloy_sol_types::{sol, SolError}, call::Call, - evm, function_selector, msg, + evm, msg, prelude::*, }; @@ -64,7 +64,7 @@ impl From for Erc721Error { } sol_interface! { - /// ERC-721 token receiver interface + /// ERC-721 token receiver interface. /// Interface for any contract that wants to support safeTransfers /// from ERC-721 asset contracts. interface IERC721Receiver { @@ -168,7 +168,7 @@ impl Erc721 { /// /// # Events /// - /// A {Transfer} event. + /// Emits a [`Transfer`] event. pub fn safe_transfer_from( storage: &mut (impl TopLevelStorage + BorrowMut), from: Address, @@ -203,13 +203,13 @@ impl Erc721 { /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must exist and be owned by `from`. - /// * If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// * If the caller is not `from`, it must be approved to move this token by either {approve} or [`set_approval_for_all`]. /// * If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon /// * a safe transfer. /// /// # Events /// - /// A {Transfer} event. + /// Emits a [`Transfer`] event. #[selector(name = "safeTransferFrom")] pub fn safe_transfer_from_with_data( storage: &mut (impl TopLevelStorage + BorrowMut), @@ -236,7 +236,7 @@ impl Erc721 { /// understand this adds an external call which potentially creates a reentrancy vulnerability. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender /// * `to` - Account of the recipient @@ -247,11 +247,11 @@ impl Erc721 { /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must be owned by `from`. - /// * If the caller is not `from`, it must be approved to move this token by either {approve} or {set_approval_for_all}. + /// * If the caller is not `from`, it must be approved to move this token by either {approve} or [`set_approval_for_all`]. /// /// # Events /// - /// A {Transfer} event. + /// Emits a [`Transfer`] event. pub fn transfer_from( &mut self, from: Address, @@ -287,7 +287,7 @@ impl Erc721 { /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient /// * `token_id` - Token id as a number - /// + /// /// # Requirements: /// /// - The caller must own the token or be an approved operator. @@ -295,7 +295,7 @@ impl Erc721 { /// /// # Events /// - /// Emits an {Approval} event. + /// Emits an [`Approval`] event. pub fn approve( &mut self, to: Address, @@ -315,10 +315,11 @@ impl Erc721 { /// /// # Requirements: /// - /// - The `operator` cannot be the address zero. + /// * The `operator` cannot be the address zero. /// + /// # Events /// - /// Emits an {ApprovalForAll} event. + /// Emits an [`ApprovalForAll`] event. pub fn set_approval_for_all( &mut self, operator: Address, @@ -330,13 +331,13 @@ impl Erc721 { /// Returns the account approved for `token_id` token. /// /// # Arguments - /// + /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number /// /// # Requirements: /// - /// - `token_id` must exist. + /// * `token_id` must exist. pub fn get_approved(&self, token_id: U256) -> Result { self.require_owned(token_id)?; self.get_approved_inner(token_id) @@ -345,12 +346,14 @@ impl Erc721 { /// Returns if the `operator` is allowed to manage all the assets of `owner`. /// /// # Arguments - /// + /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `operator` - Account to add to the set of authorized operators. /// - /// See {set_approval_for_all} + /// # Events + /// + /// Emits an [`set_approval_for_all`] event pub fn is_approved_for_all( &self, owner: Address, @@ -369,7 +372,7 @@ impl Erc721 { /// `balance_of(a)` must be equal to the number of tokens such that `owner_of_inner(token_id)` is `a`. /// /// # Arguments - /// + /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number pub fn owner_of_inner( @@ -382,7 +385,7 @@ impl Erc721 { /// Returns the approved address for `token_id`. Returns 0 if `token_id` is not minted. /// /// # Arguments - /// + /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number pub fn get_approved_inner( @@ -424,7 +427,7 @@ impl Erc721 { /// assumption. /// /// # Arguments - /// + /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. @@ -458,11 +461,11 @@ impl Erc721 { /// {owner_of_inner} function to resolve the ownership of the corresponding tokens so that balances and ownership /// remain consistent with one another. /// - /// # Arguments - /// * `account` - Account to increase balance. - /// * `value` - The number of tokens to increase balance. + /// # Arguments /// /// * `&mut self` - Write access to the contract's state. + /// * `account` - Account to increase balance. + /// * `value` - The number of tokens to increase balance. pub fn increase_balance(&mut self, account: Address, value: U256) { self.balances.setter(account).add_assign_unchecked(value); } @@ -476,15 +479,15 @@ impl Erc721 { /// NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `auth` - Account used for authorization of the update. + /// * `auth` - Account used for authorization of the update. /// /// # Events - /// - /// Emits a {Transfer} event. + /// + /// Emits a [`Transfer`] event. pub fn update( &mut self, to: Address, @@ -521,7 +524,7 @@ impl Erc721 { /// WARNING: Usage of this method is discouraged, use {safe_mint} whenever possible /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient /// * `token_id` - Token id as a number @@ -531,7 +534,9 @@ impl Erc721 { /// * `token_id` must not exist. /// * `to` cannot be the zero address. /// - /// Emits a {Transfer} event. + /// # Events + /// + /// Emits a [`Transfer`] event. pub fn mint( &mut self, to: Address, @@ -554,7 +559,7 @@ impl Erc721 { /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. /// /// # Arguments - /// + /// /// * `storage` - Write access to the contract's state. /// * `to` - Account of the recipient /// * `token_id` - Token id as a number @@ -581,7 +586,7 @@ impl Erc721 { /// This is an internal function that does not check if the sender is authorized to operate on the token. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `token_id` - Token id as a number /// @@ -589,7 +594,9 @@ impl Erc721 { /// /// * `token_id` must exist. /// - /// Emits a {Transfer} event. + /// # Events + /// + /// Emits a [`Transfer`] event. pub fn burn(&mut self, token_id: U256) -> Result<(), Erc721Error> { let previous_owner = self.update(Address::ZERO, token_id, Address::ZERO)?; @@ -604,7 +611,7 @@ impl Erc721 { /// As opposed to {transferFrom}, this imposes no restrictions on msg.sender. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender /// * `to` - Account of the recipient @@ -615,7 +622,9 @@ impl Erc721 { /// * `to` cannot be the zero address. /// * `token_id` token must be owned by `from`. /// - /// Emits a {Transfer} event. + /// # Events + /// + /// Emits a [`Transfer`] event. pub fn transfer( &mut self, from: Address, @@ -647,7 +656,7 @@ impl Erc721 { /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. /// /// # Arguments - /// + /// /// * `storage` - Write access to the contract's state. /// * `from` - Account of the sender /// * `to` - Account of the recipient @@ -678,7 +687,7 @@ impl Erc721 { /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient /// * `token_id` - Token id as a number - /// * `auth` - Account used for authorization of the update. + /// * `auth` - Account used for authorization of the update. /// * `emit_event` - Emit ['Approval'] event flag. pub fn approve_inner( &mut self, @@ -719,6 +728,8 @@ impl Erc721 { /// # Requirements: /// * operator can't be the address zero. /// + /// # Events + /// /// Emits an {ApprovalForAll} event. pub fn set_approval_for_all_inner( &mut self, From f797bc7d985b8d37769229323a0bd6b1f102a05d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 28 Mar 2024 13:11:24 +0400 Subject: [PATCH 06/68] add balance test --- contracts/src/erc721/mod.rs | 126 ++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 40 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index b22051e3..3a45ca1c 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -25,39 +25,48 @@ sol! { sol! { /// Indicates that an address can't be an owner. /// For example, `address(0)` is a forbidden owner in ERC-20. Used in balance queries. + #[derive(Debug)] error ERC721InvalidOwner(address owner); /// Indicates a `tokenId` whose `owner` is the zero address. + #[derive(Debug)] error ERC721NonexistentToken(uint256 tokenId); /// Indicates an error related to the ownership over a particular token. Used in transfers. + #[derive(Debug)] error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); /// Indicates a failure with the token `sender`. Used in transfers. + #[derive(Debug)] error ERC721InvalidSender(address sender); /// Indicates a failure with the token `receiver`. Used in transfers. + #[derive(Debug)] error ERC721InvalidReceiver(address receiver); /// Indicates a failure with the `operator`’s approval. Used in transfers. + #[derive(Debug)] error ERC721InsufficientApproval(address operator, uint256 tokenId); /// Indicates a failure with the `approver` of a token to be approved. Used in approvals. + #[derive(Debug)] error ERC721InvalidApprover(address approver); /// Indicates a failure with the `operator` to be approved. Used in approvals. + #[derive(Debug)] error ERC721InvalidOperator(address operator); } -pub struct Erc721Error(Vec); +#[derive(Debug)] +pub struct Error(Vec); -impl From for Vec { - fn from(value: Erc721Error) -> Vec { +impl From for Vec { + fn from(value: Error) -> Vec { value.0 } } -impl From for Erc721Error { +impl From for Error { fn from(value: T) -> Self { Self(value.encode()) } @@ -85,14 +94,8 @@ sol_interface! { } } -pub trait Erc721Info { - const NAME: &'static str; - const SYMBOL: &'static str; - const BASE_URI: &'static str; -} - sol_storage! { - pub struct Erc721 { + pub struct Erc721 { mapping(uint256 => address) owners; mapping(address => uint256) balances; @@ -100,13 +103,11 @@ sol_storage! { mapping(uint256 => address) token_approvals; mapping(address => mapping(address => bool)) operator_approvals; - - PhantomData phantom_data; } } #[external] -impl Erc721 { +impl Erc721 { /// Returns the number of tokens in ``owner``'s account. /// /// # Arguments @@ -117,7 +118,7 @@ impl Erc721 { /// # Returns /// /// The balance of the owner. - pub fn balance_of(&self, owner: Address) -> Result { + pub fn balance_of(&self, owner: Address) -> Result { if owner == Address::ZERO { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); } @@ -138,7 +139,7 @@ impl Erc721 { /// # Requirements /// /// * `token_id` must exist. - pub fn owner_of(&self, token_id: U256) -> Result { + pub fn owner_of(&self, token_id: U256) -> Result { self.require_owned(token_id) } @@ -174,7 +175,7 @@ impl Erc721 { from: Address, to: Address, token_id: U256, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { Self::safe_transfer_from_with_data( storage, from, @@ -217,7 +218,7 @@ impl Erc721 { to: Address, token_id: U256, data: Bytes, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { storage.borrow_mut().transfer_from(from, to, token_id)?; Self::check_on_erc721_received( storage, @@ -257,7 +258,7 @@ impl Erc721 { from: Address, to: Address, token_id: U256, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { if to == Address::ZERO { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() @@ -300,7 +301,7 @@ impl Erc721 { &mut self, to: Address, token_id: U256, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { self.approve_inner(to, token_id, msg::sender(), true) } @@ -324,7 +325,7 @@ impl Erc721 { &mut self, operator: Address, approved: bool, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { self.set_approval_for_all_inner(msg::sender(), operator, approved) } @@ -338,7 +339,7 @@ impl Erc721 { /// # Requirements: /// /// * `token_id` must exist. - pub fn get_approved(&self, token_id: U256) -> Result { + pub fn get_approved(&self, token_id: U256) -> Result { self.require_owned(token_id)?; self.get_approved_inner(token_id) } @@ -358,12 +359,12 @@ impl Erc721 { &self, owner: Address, operator: Address, - ) -> Result { + ) -> Result { Ok(self.operator_approvals.get(owner).get(operator)) } } -impl Erc721 { +impl Erc721 { /// Returns the owner of the `token_id`. Does NOT revert if token doesn't exist /// /// IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the @@ -378,7 +379,7 @@ impl Erc721 { pub fn owner_of_inner( &self, token_id: U256, - ) -> Result { + ) -> Result { Ok(self.owners.get(token_id)) } @@ -391,7 +392,7 @@ impl Erc721 { pub fn get_approved_inner( &self, token_id: U256, - ) -> Result { + ) -> Result { Ok(self.token_approvals.get(token_id)) } @@ -411,7 +412,7 @@ impl Erc721 { owner: Address, spender: Address, token_id: U256, - ) -> Result { + ) -> Result { let is_authorized = spender != Address::ZERO && (owner == spender || self.is_approved_for_all(owner, spender)? @@ -437,7 +438,7 @@ impl Erc721 { owner: Address, spender: Address, token_id: U256, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { if !self.is_authorized(owner, spender, token_id)? { return if owner == Address::ZERO { Err(ERC721NonexistentToken { tokenId: token_id }.into()) @@ -493,7 +494,7 @@ impl Erc721 { to: Address, token_id: U256, auth: Address, - ) -> Result { + ) -> Result { let from = self.owner_of_inner(token_id)?; // Perform (optional) operator check @@ -541,7 +542,7 @@ impl Erc721 { &mut self, to: Address, token_id: U256, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { if to == Address::ZERO { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() @@ -569,7 +570,7 @@ impl Erc721 { to: Address, token_id: U256, data: Bytes, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { storage.borrow_mut().mint(to, token_id)?; Self::check_on_erc721_received( storage, @@ -597,7 +598,7 @@ impl Erc721 { /// # Events /// /// Emits a [`Transfer`] event. - pub fn burn(&mut self, token_id: U256) -> Result<(), Erc721Error> { + pub fn burn(&mut self, token_id: U256) -> Result<(), Error> { let previous_owner = self.update(Address::ZERO, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { @@ -630,7 +631,7 @@ impl Erc721 { from: Address, to: Address, token_id: U256, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { if to == Address::ZERO { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() @@ -668,7 +669,7 @@ impl Erc721 { to: Address, token_id: U256, data: Bytes, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { storage.borrow_mut().transfer(from, to, token_id)?; Self::check_on_erc721_received( storage, @@ -695,7 +696,7 @@ impl Erc721 { token_id: U256, auth: Address, emit_event: bool, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { // Avoid reading the owner unless necessary if emit_event || auth != Address::ZERO { let owner = self.require_owned(token_id)?; @@ -736,7 +737,7 @@ impl Erc721 { owner: Address, operator: Address, approved: bool, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { if operator == Address::ZERO { return Err(ERC721InvalidOperator { operator }.into()); } @@ -756,7 +757,7 @@ impl Erc721 { pub fn require_owned( &self, token_id: U256, - ) -> Result { + ) -> Result { let owner = self.owner_of_inner(token_id)?; if owner == Address::ZERO { return Err(ERC721NonexistentToken { tokenId: token_id }.into()); @@ -785,7 +786,7 @@ impl Erc721 { to: Address, token_id: U256, data: Bytes, - ) -> Result<(), Erc721Error> { + ) -> Result<(), Error> { const IERC721RECEIVER_INTERFACE_ID: u32 = 0x150b7a02; if to.has_code() { let call = Call::new_in(storage); @@ -804,14 +805,14 @@ impl Erc721 { Ok(()) } } - Err(err) => Err(Erc721Error(err.into())), + Err(err) => Err(Error(err.into())), }; } Ok(()) } } -use stylus_sdk::storage::{StorageGuardMut, StorageUint}; +use stylus_sdk::storage::{StorageGuardMut, StorageMap, StorageUint}; pub trait IncrementalMath { fn add_assign_unchecked(&mut self, rhs: T); @@ -830,3 +831,48 @@ impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { self.set(new_balance); } } + + + +#[cfg(test)] +mod tests { + use alloy_primitives::{address, Address, U256}; + use stylus_sdk::{ + msg, + storage::{StorageMap, StorageType, StorageU256}, + }; + use crate::erc721::{Error, Erc721}; + #[allow(unused_imports)] + use crate::test_utils; + + impl Default for Erc721 { + fn default() -> Self { + let root = U256::ZERO; + let token = Erc721 { + owners: unsafe { StorageMap::new(root, 0) }, + balances: unsafe { StorageMap::new(root + U256::from(32), 0) }, + token_approvals: unsafe { StorageMap::new(root + U256::from(64), 0) }, + operator_approvals: unsafe { StorageMap::new(root + U256::from(96), 0) }, + }; + + token + } + } + + + #[test] + fn reads_balance() { + test_utils::with_storage::(|token| { + // TODO#q create random address + let address = address!("01fA6bf4Ee48B6C95900BCcf9BEA172EF5DBd478"); + let balance = token.balance_of(address); + assert_eq!(U256::ZERO, balance.unwrap()); + + let owner = msg::sender(); + let one = U256::from(1); + token.balances.setter(owner).set(one); + let balance = token.balance_of(owner); + assert_eq!(one, balance.unwrap()); + }); + } +} From 6a7b2a9feb3e700cad3c2670d1627861c7026e90 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 28 Mar 2024 13:37:10 +0400 Subject: [PATCH 07/68] create Error enum --- Cargo.lock | 1 + contracts/Cargo.toml | 1 + contracts/src/erc721/mod.rs | 90 ++++++++++++++++--------------------- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7e4d7be..6c58846c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,7 @@ version = "0.1.0" dependencies = [ "alloy-primitives 0.3.3", "alloy-sol-types", + "derive_more", "mini-alloc", "stylus-proc", "stylus-sdk", diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 92851e04..2ead5a43 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -15,6 +15,7 @@ alloy-sol-types = { version = "0.3.1", default-features = false } stylus-sdk = { version = "0.4.3", default-features = false } stylus-proc = { version = "0.4.3", default-features = false } mini-alloc = "0.4.2" +derive_more = "0.99.17" [dev-dependencies] wavm-shims = { path = "../lib/wavm-shims" } diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 3a45ca1c..943ec8e2 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,7 +1,7 @@ use std::borrow::BorrowMut; -use std::marker::PhantomData; -use std::prelude::v1::{String, ToString, Vec}; +use std::prelude::v1::{Box, String, ToString, Vec}; use std::vec; +use derive_more::From; use stylus_sdk::{ abi::Bytes, alloy_primitives::{Address, U256}, @@ -57,19 +57,19 @@ sol! { error ERC721InvalidOperator(address operator); } -#[derive(Debug)] -pub struct Error(Vec); - -impl From for Vec { - fn from(value: Error) -> Vec { - value.0 - } -} - -impl From for Error { - fn from(value: T) -> Self { - Self(value.encode()) - } +/// An ERC-721 error defined as described in [ERC-6093]. +/// +/// [ERC-6093]: https://eips.ethereum.org/EIPS/eip-6093 +#[derive(SolidityError, Debug, From)] +pub enum Error { + ERC721InvalidOwner(ERC721InvalidOwner), + ERC721NonexistentToken(ERC721NonexistentToken), + ERC721IncorrectOwner(ERC721IncorrectOwner), + ERC721InvalidSender(ERC721InvalidSender), + ERC721InvalidReceiver(ERC721InvalidReceiver), + ERC721InsufficientApproval(ERC721InsufficientApproval), + ERC721InvalidApprover(ERC721InvalidApprover), + ERC721InvalidOperator(ERC721InvalidOperator), } sol_interface! { @@ -353,7 +353,7 @@ impl Erc721 { /// * `operator` - Account to add to the set of authorized operators. /// /// # Events - /// + /// /// Emits an [`set_approval_for_all`] event pub fn is_approved_for_all( &self, @@ -376,10 +376,7 @@ impl Erc721 { /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number - pub fn owner_of_inner( - &self, - token_id: U256, - ) -> Result { + pub fn owner_of_inner(&self, token_id: U256) -> Result { Ok(self.owners.get(token_id)) } @@ -389,10 +386,7 @@ impl Erc721 { /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number - pub fn get_approved_inner( - &self, - token_id: U256, - ) -> Result { + pub fn get_approved_inner(&self, token_id: U256) -> Result { Ok(self.token_approvals.get(token_id)) } @@ -463,7 +457,7 @@ impl Erc721 { /// remain consistent with one another. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `account` - Account to increase balance. /// * `value` - The number of tokens to increase balance. @@ -536,13 +530,9 @@ impl Erc721 { /// * `to` cannot be the zero address. /// /// # Events - /// + /// /// Emits a [`Transfer`] event. - pub fn mint( - &mut self, - to: Address, - token_id: U256, - ) -> Result<(), Error> { + pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { if to == Address::ZERO { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() @@ -596,7 +586,7 @@ impl Erc721 { /// * `token_id` must exist. /// /// # Events - /// + /// /// Emits a [`Transfer`] event. pub fn burn(&mut self, token_id: U256) -> Result<(), Error> { let previous_owner = @@ -624,7 +614,7 @@ impl Erc721 { /// * `token_id` token must be owned by `from`. /// /// # Events - /// + /// /// Emits a [`Transfer`] event. pub fn transfer( &mut self, @@ -730,7 +720,7 @@ impl Erc721 { /// * operator can't be the address zero. /// /// # Events - /// + /// /// Emits an {ApprovalForAll} event. pub fn set_approval_for_all_inner( &mut self, @@ -754,10 +744,7 @@ impl Erc721 { /// # Arguments /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number - pub fn require_owned( - &self, - token_id: U256, - ) -> Result { + pub fn require_owned(&self, token_id: U256) -> Result { let owner = self.owner_of_inner(token_id)?; if owner == Address::ZERO { return Err(ERC721NonexistentToken { tokenId: token_id }.into()); @@ -787,6 +774,7 @@ impl Erc721 { token_id: U256, data: Bytes, ) -> Result<(), Error> { + // TODO: compute INTERFACE_ID at compile time const IERC721RECEIVER_INTERFACE_ID: u32 = 0x150b7a02; if to.has_code() { let call = Call::new_in(storage); @@ -805,7 +793,7 @@ impl Erc721 { Ok(()) } } - Err(err) => Err(Error(err.into())), + Err(_) => Err(ERC721InvalidReceiver{receiver: to}.into()), }; } Ok(()) @@ -832,34 +820,34 @@ impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { } } - - #[cfg(test)] mod tests { + use crate::erc721::{Erc721, Error}; + #[allow(unused_imports)] + use crate::test_utils; use alloy_primitives::{address, Address, U256}; use stylus_sdk::{ msg, storage::{StorageMap, StorageType, StorageU256}, }; - use crate::erc721::{Error, Erc721}; - #[allow(unused_imports)] - use crate::test_utils; impl Default for Erc721 { fn default() -> Self { let root = U256::ZERO; - let token = Erc721 { + + Erc721 { owners: unsafe { StorageMap::new(root, 0) }, balances: unsafe { StorageMap::new(root + U256::from(32), 0) }, - token_approvals: unsafe { StorageMap::new(root + U256::from(64), 0) }, - operator_approvals: unsafe { StorageMap::new(root + U256::from(96), 0) }, - }; - - token + token_approvals: unsafe { + StorageMap::new(root + U256::from(64), 0) + }, + operator_approvals: unsafe { + StorageMap::new(root + U256::from(96), 0) + }, + } } } - #[test] fn reads_balance() { test_utils::with_storage::(|token| { From 5bf9e6d46048181866a2bb4b364a3ac0aa834392 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 28 Mar 2024 14:41:12 +0400 Subject: [PATCH 08/68] add documentation for error's and event's fields --- contracts/src/erc721/mod.rs | 60 +++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 943ec8e2..ba85558a 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -13,47 +13,77 @@ use stylus_sdk::{ sol! { /// Emitted when `tokenId` token is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + /// + /// * `from` - Address from which token will be transferred. + /// * `to` - Address where token will be transferred. + /// * `token_id` - Token id as a number. + event Transfer(address indexed from, address indexed to, uint256 indexed token_id); /// Emitted when `owner` enables `approved` to manage the `tokenId` token. - event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + /// + /// * `owner` - Address of the owner of the token. + /// * `approved` - Address of the approver. + /// * `token_id` - Token id as a number. + event Approval(address indexed owner, address indexed approved, uint256 indexed token_id); /// Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + /// + /// * `owner` - Address of the owner of the token. + /// * `operator` - Address of an operator that will manage operations on the token. + /// * `token_id` - Token id as a number. event ApprovalForAll(address indexed owner, address indexed operator, bool approved); } sol! { /// Indicates that an address can't be an owner. /// For example, `address(0)` is a forbidden owner in ERC-20. Used in balance queries. + /// + /// * `owner` - Incorrect address of the owner. #[derive(Debug)] error ERC721InvalidOwner(address owner); /// Indicates a `tokenId` whose `owner` is the zero address. + /// + /// * `token_id` - Token id as a number. #[derive(Debug)] - error ERC721NonexistentToken(uint256 tokenId); + error ERC721NonexistentToken(uint256 token_id); /// Indicates an error related to the ownership over a particular token. Used in transfers. + /// + /// * `sender` - Address whose token being transferred. + /// * `token_id` - Token id as a number. + /// * `owner` - Address of the owner of the token. #[derive(Debug)] - error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + error ERC721IncorrectOwner(address sender, uint256 token_id, address owner); /// Indicates a failure with the token `sender`. Used in transfers. + /// + /// * `sender` - An address whose token being transferred. #[derive(Debug)] error ERC721InvalidSender(address sender); /// Indicates a failure with the token `receiver`. Used in transfers. + /// + /// * `receiver` - Address that receives token. #[derive(Debug)] error ERC721InvalidReceiver(address receiver); /// Indicates a failure with the `operator`’s approval. Used in transfers. + /// + /// * `address` - Address of an operator that wasn't approved. + /// * `token_id` - Token id as a number. #[derive(Debug)] - error ERC721InsufficientApproval(address operator, uint256 tokenId); + error ERC721InsufficientApproval(address operator, uint256 token_id); /// Indicates a failure with the `approver` of a token to be approved. Used in approvals. + /// + /// * `address` - Address of an approver that failed to approve. #[derive(Debug)] error ERC721InvalidApprover(address approver); /// Indicates a failure with the `operator` to be approved. Used in approvals. #[derive(Debug)] + /// * `operator` - Incorrect address of the operator. error ERC721InvalidOperator(address operator); } @@ -88,7 +118,7 @@ sol_interface! { function onERC721Received( address operator, address from, - uint256 tokenId, + uint256 token_id, bytes calldata data ) external returns (bytes4); } @@ -271,7 +301,7 @@ impl Erc721 { if previous_owner != from { return Err(ERC721IncorrectOwner { sender: from, - tokenId: token_id, + token_id: token_id, owner: previous_owner, } .into()); @@ -435,11 +465,11 @@ impl Erc721 { ) -> Result<(), Error> { if !self.is_authorized(owner, spender, token_id)? { return if owner == Address::ZERO { - Err(ERC721NonexistentToken { tokenId: token_id }.into()) + Err(ERC721NonexistentToken { token_id: token_id }.into()) } else { Err(ERC721InsufficientApproval { operator: spender, - tokenId: token_id, + token_id: token_id, } .into()) }; @@ -509,7 +539,7 @@ impl Erc721 { self.owners.setter(token_id).set(to); - evm::log(Transfer { from, to, tokenId: token_id }); + evm::log(Transfer { from, to, token_id: token_id }); Ok(from) } @@ -592,7 +622,7 @@ impl Erc721 { let previous_owner = self.update(Address::ZERO, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { - Err(ERC721NonexistentToken { tokenId: token_id }.into()) + Err(ERC721NonexistentToken { token_id: token_id }.into()) } else { Ok(()) } @@ -630,11 +660,11 @@ impl Erc721 { let previous_owner = self.update(to, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { - Err(ERC721NonexistentToken { tokenId: token_id }.into()) + Err(ERC721NonexistentToken { token_id: token_id }.into()) } else if previous_owner != from { Err(ERC721IncorrectOwner { sender: from, - tokenId: token_id, + token_id: token_id, owner: previous_owner, } .into()) @@ -700,7 +730,7 @@ impl Erc721 { } if emit_event { - evm::log(Approval { owner, approved: to, tokenId: token_id }); + evm::log(Approval { owner, approved: to, token_id: token_id }); } } @@ -747,7 +777,7 @@ impl Erc721 { pub fn require_owned(&self, token_id: U256) -> Result { let owner = self.owner_of_inner(token_id)?; if owner == Address::ZERO { - return Err(ERC721NonexistentToken { tokenId: token_id }.into()); + return Err(ERC721NonexistentToken { token_id: token_id }.into()); } Ok(owner) } From 987b2f8cc54d563a49c0d1ac9e3d52908743864d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 28 Mar 2024 19:53:49 +0400 Subject: [PATCH 09/68] add mint_nft_and_check_balance test --- Cargo.lock | 1 + contracts/Cargo.toml | 1 + contracts/src/erc721/mod.rs | 25 ++++++++++++++++--------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c58846c..7e17b7fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ dependencies = [ "alloy-sol-types", "derive_more", "mini-alloc", + "rand", "stylus-proc", "stylus-sdk", "wavm-shims", diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 2ead5a43..6e8049d6 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -19,6 +19,7 @@ derive_more = "0.99.17" [dev-dependencies] wavm-shims = { path = "../lib/wavm-shims" } +rand = "0.8.5" [features] default = [] diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index ba85558a..621e2c9d 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -861,6 +861,9 @@ mod tests { storage::{StorageMap, StorageType, StorageU256}, }; + const ALICE: Address = address!("01fA6bf4Ee48B6C95900BCcf9BEA172EF5DBd478"); + const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); + impl Default for Erc721 { fn default() -> Self { let root = U256::ZERO; @@ -879,18 +882,22 @@ mod tests { } #[test] - fn reads_balance() { + fn mint_nft_and_check_balance() { test_utils::with_storage::(|token| { - // TODO#q create random address - let address = address!("01fA6bf4Ee48B6C95900BCcf9BEA172EF5DBd478"); - let balance = token.balance_of(address); - assert_eq!(U256::ZERO, balance.unwrap()); + let token_id = random_token_id(); + token.mint(ALICE, token_id).expect("mint token"); + let owner = token.owner_of(token_id).expect("owner address"); + assert_eq!(owner, ALICE); - let owner = msg::sender(); + let balance = token.balance_of(ALICE).expect("balance of owner"); let one = U256::from(1); - token.balances.setter(owner).set(one); - let balance = token.balance_of(owner); - assert_eq!(one, balance.unwrap()); + assert!(balance >= one); }); } + + + pub fn random_token_id() -> U256 { + let num: u32 = rand::random(); + num.try_into().expect("conversion to U256") + } } From 6a7bcc03d0d4e0480a90796598daa8dd73e379d1 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 28 Mar 2024 20:29:11 +0400 Subject: [PATCH 10/68] add error_mint_second_nft test --- contracts/src/erc721/mod.rs | 69 +++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 621e2c9d..70c6391b 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,7 +1,7 @@ +use derive_more::From; use std::borrow::BorrowMut; use std::prelude::v1::{Box, String, ToString, Vec}; use std::vec; -use derive_more::From; use stylus_sdk::{ abi::Bytes, alloy_primitives::{Address, U256}, @@ -92,14 +92,14 @@ sol! { /// [ERC-6093]: https://eips.ethereum.org/EIPS/eip-6093 #[derive(SolidityError, Debug, From)] pub enum Error { - ERC721InvalidOwner(ERC721InvalidOwner), - ERC721NonexistentToken(ERC721NonexistentToken), - ERC721IncorrectOwner(ERC721IncorrectOwner), - ERC721InvalidSender(ERC721InvalidSender), - ERC721InvalidReceiver(ERC721InvalidReceiver), - ERC721InsufficientApproval(ERC721InsufficientApproval), - ERC721InvalidApprover(ERC721InvalidApprover), - ERC721InvalidOperator(ERC721InvalidOperator), + InvalidOwner(ERC721InvalidOwner), + NonexistentToken(ERC721NonexistentToken), + IncorrectOwner(ERC721IncorrectOwner), + InvalidSender(ERC721InvalidSender), + InvalidReceiver(ERC721InvalidReceiver), + InsufficientApproval(ERC721InsufficientApproval), + InvalidApprover(ERC721InvalidApprover), + InvalidOperator(ERC721InvalidOperator), } sol_interface! { @@ -301,7 +301,7 @@ impl Erc721 { if previous_owner != from { return Err(ERC721IncorrectOwner { sender: from, - token_id: token_id, + token_id, owner: previous_owner, } .into()); @@ -465,13 +465,10 @@ impl Erc721 { ) -> Result<(), Error> { if !self.is_authorized(owner, spender, token_id)? { return if owner == Address::ZERO { - Err(ERC721NonexistentToken { token_id: token_id }.into()) + Err(ERC721NonexistentToken { token_id }.into()) } else { - Err(ERC721InsufficientApproval { - operator: spender, - token_id: token_id, - } - .into()) + Err(ERC721InsufficientApproval { operator: spender, token_id } + .into()) }; } Ok(()) @@ -539,7 +536,7 @@ impl Erc721 { self.owners.setter(token_id).set(to); - evm::log(Transfer { from, to, token_id: token_id }); + evm::log(Transfer { from, to, token_id }); Ok(from) } @@ -622,7 +619,7 @@ impl Erc721 { let previous_owner = self.update(Address::ZERO, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { - Err(ERC721NonexistentToken { token_id: token_id }.into()) + Err(ERC721NonexistentToken { token_id }.into()) } else { Ok(()) } @@ -660,11 +657,11 @@ impl Erc721 { let previous_owner = self.update(to, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { - Err(ERC721NonexistentToken { token_id: token_id }.into()) + Err(ERC721NonexistentToken { token_id }.into()) } else if previous_owner != from { Err(ERC721IncorrectOwner { sender: from, - token_id: token_id, + token_id, owner: previous_owner, } .into()) @@ -730,7 +727,7 @@ impl Erc721 { } if emit_event { - evm::log(Approval { owner, approved: to, token_id: token_id }); + evm::log(Approval { owner, approved: to, token_id }); } } @@ -777,7 +774,7 @@ impl Erc721 { pub fn require_owned(&self, token_id: U256) -> Result { let owner = self.owner_of_inner(token_id)?; if owner == Address::ZERO { - return Err(ERC721NonexistentToken { token_id: token_id }.into()); + return Err(ERC721NonexistentToken { token_id }.into()); } Ok(owner) } @@ -823,7 +820,7 @@ impl Erc721 { Ok(()) } } - Err(_) => Err(ERC721InvalidReceiver{receiver: to}.into()), + Err(_) => Err(ERC721InvalidReceiver { receiver: to }.into()), }; } Ok(()) @@ -852,7 +849,7 @@ impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { #[cfg(test)] mod tests { - use crate::erc721::{Erc721, Error}; + use crate::erc721::{ERC721InvalidSender, Erc721, Error}; #[allow(unused_imports)] use crate::test_utils; use alloy_primitives::{address, Address, U256}; @@ -895,7 +892,29 @@ mod tests { }); } - + #[test] + fn error_mint_second_nft() { + test_utils::with_storage::(|token| { + let token_id = random_token_id(); + token.mint(ALICE, token_id).expect("mint token first time"); + match token.mint(ALICE, token_id) { + Ok(_) => { + panic!( + "Second mint of the same token should not be possible" + ) + } + Err(e) => match e { + Error::InvalidSender(ERC721InvalidSender { + sender: Address::ZERO, + }) => {} + e => { + panic!("Invalid error - {e:?}"); + } + }, + }; + }); + } + pub fn random_token_id() -> U256 { let num: u32 = rand::random(); num.try_into().expect("conversion to U256") From 903a9602e6c3dc6d55912c45184ce3f41c8c1a65 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 29 Mar 2024 14:10:19 +0400 Subject: [PATCH 11/68] add transfer_nft test --- contracts/src/erc721/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 70c6391b..637e11c2 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -914,7 +914,21 @@ mod tests { }; }); } - + + #[test] + fn transfer_nft() { + test_utils::with_storage::(|token| { + let alice = msg::sender(); + let token_id = random_token_id(); + token.mint(alice, token_id).expect("mint nft to alice"); + token + .transfer_from(alice, BOB, token_id) + .expect("transfer from alice to bob"); + let owner = token.owner_of(token_id).expect("new owner of nft"); + assert_eq!(owner, BOB); + }); + } + pub fn random_token_id() -> U256 { let num: u32 = rand::random(); num.try_into().expect("conversion to U256") From c45f61eca99bd99acba91c540b6100a9e71f4bd6 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 29 Mar 2024 14:44:10 +0400 Subject: [PATCH 12/68] add error_transfer_nonexistent_nft test --- Cargo.lock | 1 + contracts/Cargo.toml | 1 + contracts/src/erc721/mod.rs | 46 ++++++++++++++++++++++++++++--------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e17b7fa..a4080a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ dependencies = [ "alloy-sol-types", "derive_more", "mini-alloc", + "once_cell", "rand", "stylus-proc", "stylus-sdk", diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 6e8049d6..df39857a 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -20,6 +20,7 @@ derive_more = "0.99.17" [dev-dependencies] wavm-shims = { path = "../lib/wavm-shims" } rand = "0.8.5" +once_cell = "1.19.0" [features] default = [] diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 637e11c2..c60d383a 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -849,16 +849,19 @@ impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { #[cfg(test)] mod tests { - use crate::erc721::{ERC721InvalidSender, Erc721, Error}; + use crate::erc721::*; #[allow(unused_imports)] use crate::test_utils; use alloy_primitives::{address, Address, U256}; + use once_cell::sync::Lazy; use stylus_sdk::{ msg, storage::{StorageMap, StorageType, StorageU256}, }; - const ALICE: Address = address!("01fA6bf4Ee48B6C95900BCcf9BEA172EF5DBd478"); + // NOTE: Alice is always the sender of the message + static ALICE: Lazy
= Lazy::new(msg::sender); + const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); impl Default for Erc721 { @@ -882,11 +885,11 @@ mod tests { fn mint_nft_and_check_balance() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(ALICE, token_id).expect("mint token"); + token.mint(*ALICE, token_id).expect("mint token"); let owner = token.owner_of(token_id).expect("owner address"); - assert_eq!(owner, ALICE); + assert_eq!(owner, *ALICE); - let balance = token.balance_of(ALICE).expect("balance of owner"); + let balance = token.balance_of(*ALICE).expect("balance of owner"); let one = U256::from(1); assert!(balance >= one); }); @@ -896,8 +899,8 @@ mod tests { fn error_mint_second_nft() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(ALICE, token_id).expect("mint token first time"); - match token.mint(ALICE, token_id) { + token.mint(*ALICE, token_id).expect("mint token first time"); + match token.mint(*ALICE, token_id) { Ok(_) => { panic!( "Second mint of the same token should not be possible" @@ -918,17 +921,38 @@ mod tests { #[test] fn transfer_nft() { test_utils::with_storage::(|token| { - let alice = msg::sender(); let token_id = random_token_id(); - token.mint(alice, token_id).expect("mint nft to alice"); + token.mint(*ALICE, token_id).expect("mint nft to alice"); token - .transfer_from(alice, BOB, token_id) + .transfer_from(*ALICE, BOB, token_id) .expect("transfer from alice to bob"); let owner = token.owner_of(token_id).expect("new owner of nft"); assert_eq!(owner, BOB); }); } - + + #[test] + fn error_transfer_nonexistent_nft() { + test_utils::with_storage::(|token| { + let token_id = random_token_id(); + match token.transfer_from(*ALICE, BOB, token_id) { + Ok(_) => { + panic!( + "Transfer of a non existent nft should not be possible" + ) + } + Err(e) => match e { + Error::NonexistentToken(ERC721NonexistentToken { + token_id: t_id, + }) if t_id == token_id => {} + e => { + panic!("Invalid error - {e:?}"); + } + }, + } + }); + } + pub fn random_token_id() -> U256 { let num: u32 = rand::random(); num.try_into().expect("conversion to U256") From 9bf2d933226f9e01e84aa53b3ef657d2716b79b8 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 29 Mar 2024 16:09:51 +0400 Subject: [PATCH 13/68] add approve_nft_transfer and transfer_approved_nft tests --- contracts/src/erc721/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index c60d383a..8e3262a5 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -953,6 +953,32 @@ mod tests { }); } + #[test] + fn approve_nft_transfer() { + test_utils::with_storage::(|token| { + let token_id = random_token_id(); + token.mint(*ALICE, token_id).expect("mint token"); + token + .approve(BOB, token_id) + .expect("approve bob for operations on token"); + assert_eq!(token.token_approvals.get(token_id), BOB); + }); + } + + #[test] + fn transfer_approved_nft() { + test_utils::with_storage::(|token| { + let token_id = random_token_id(); + token.mint(BOB, token_id).expect("mint token"); + token.token_approvals.setter(token_id).set(*ALICE); + token + .transfer_from(BOB, *ALICE, token_id) + .expect("transfer Bob's token to Alice"); + let owner = token.owner_of(token_id).expect("owner of token"); + assert_eq!(owner, *ALICE); + }); + } + pub fn random_token_id() -> U256 { let num: u32 = rand::random(); num.try_into().expect("conversion to U256") From 1a19400779d36d898126facb4b822440234ae962 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 29 Mar 2024 17:43:11 +0400 Subject: [PATCH 14/68] add error_not_approved_nft_transfer test --- contracts/src/erc721/mod.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 8e3262a5..d0906368 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -531,7 +531,7 @@ impl Erc721 { } if to != Address::ZERO { - self.balances.setter(to).add_assign_unchecked(U256::from(1)) + self.balances.setter(to).add_assign_unchecked(U256::from(1)); } self.owners.setter(token_id).set(to); @@ -979,7 +979,30 @@ mod tests { }); } - pub fn random_token_id() -> U256 { + #[test] + fn error_not_approved_nft_transfer() { + test_utils::with_storage::(|token| { + let token_id = random_token_id(); + token.mint(BOB, token_id).expect("mint token"); + match token.transfer_from(BOB, *ALICE, token_id) { + Ok(_) => { + panic!("Transfer of not approved token should not happen"); + } + Err(e) => match e { + Error::InsufficientApproval( + ERC721InsufficientApproval { operator, token_id: t_id }, + ) if operator == *ALICE && t_id == token_id => {} + e => { + panic!("Invalid error - {e:?}"); + } + }, + }; + }); + } + + // TODO: add mock for on_erc721_received + + fn random_token_id() -> U256 { let num: u32 = rand::random(); num.try_into().expect("conversion to U256") } From 55c65a75d6caf573e611ed4076f22fafb6990c1b Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 1 Apr 2024 13:32:33 +0400 Subject: [PATCH 15/68] prefix internal functions with underscore --- contracts/src/erc721/mod.rs | 110 +++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index d0906368..4ab7659b 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,7 +1,7 @@ use derive_more::From; use std::borrow::BorrowMut; -use std::prelude::v1::{Box, String, ToString, Vec}; use std::vec; +use stylus_sdk::storage::{StorageGuardMut, StorageUint}; use stylus_sdk::{ abi::Bytes, alloy_primitives::{Address, U256}, @@ -170,7 +170,7 @@ impl Erc721 { /// /// * `token_id` must exist. pub fn owner_of(&self, token_id: U256) -> Result { - self.require_owned(token_id) + self._require_owned(token_id) } /// Safely transfers `token_id` token from `from` to `to`, checking first that contract recipients @@ -250,7 +250,7 @@ impl Erc721 { data: Bytes, ) -> Result<(), Error> { storage.borrow_mut().transfer_from(from, to, token_id)?; - Self::check_on_erc721_received( + Self::_check_on_erc721_received( storage, msg::sender(), from, @@ -297,7 +297,7 @@ impl Erc721 { // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. - let previous_owner = self.update(to, token_id, msg::sender())?; + let previous_owner = self._update(to, token_id, msg::sender())?; if previous_owner != from { return Err(ERC721IncorrectOwner { sender: from, @@ -332,7 +332,7 @@ impl Erc721 { to: Address, token_id: U256, ) -> Result<(), Error> { - self.approve_inner(to, token_id, msg::sender(), true) + self._approve(to, token_id, msg::sender(), true) } /// Approve or remove `operator` as an operator for the caller. @@ -356,7 +356,7 @@ impl Erc721 { operator: Address, approved: bool, ) -> Result<(), Error> { - self.set_approval_for_all_inner(msg::sender(), operator, approved) + self._set_approval_for_all(msg::sender(), operator, approved) } /// Returns the account approved for `token_id` token. @@ -370,8 +370,8 @@ impl Erc721 { /// /// * `token_id` must exist. pub fn get_approved(&self, token_id: U256) -> Result { - self.require_owned(token_id)?; - self.get_approved_inner(token_id) + self._require_owned(token_id)?; + self._get_approved_inner(token_id) } /// Returns if the `operator` is allowed to manage all the assets of `owner`. @@ -406,7 +406,7 @@ impl Erc721 { /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number - pub fn owner_of_inner(&self, token_id: U256) -> Result { + pub fn _owner_of_inner(&self, token_id: U256) -> Result { Ok(self.owners.get(token_id)) } @@ -416,7 +416,10 @@ impl Erc721 { /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number - pub fn get_approved_inner(&self, token_id: U256) -> Result { + pub fn _get_approved_inner( + &self, + token_id: U256, + ) -> Result { Ok(self.token_approvals.get(token_id)) } @@ -431,7 +434,7 @@ impl Erc721 { /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. /// * `token_id` - Token id as a number - pub fn is_authorized( + pub fn _is_authorized( &self, owner: Address, spender: Address, @@ -440,7 +443,7 @@ impl Erc721 { let is_authorized = spender != Address::ZERO && (owner == spender || self.is_approved_for_all(owner, spender)? - || self.get_approved_inner(token_id)? == spender); + || self._get_approved_inner(token_id)? == spender); Ok(is_authorized) } @@ -457,13 +460,13 @@ impl Erc721 { /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. /// * `token_id` - Token id as a number - pub fn check_authorized( + pub fn _check_authorized( &self, owner: Address, spender: Address, token_id: U256, ) -> Result<(), Error> { - if !self.is_authorized(owner, spender, token_id)? { + if !self._is_authorized(owner, spender, token_id)? { return if owner == Address::ZERO { Err(ERC721NonexistentToken { token_id }.into()) } else { @@ -488,7 +491,7 @@ impl Erc721 { /// * `&mut self` - Write access to the contract's state. /// * `account` - Account to increase balance. /// * `value` - The number of tokens to increase balance. - pub fn increase_balance(&mut self, account: Address, value: U256) { + pub fn _increase_balance(&mut self, account: Address, value: U256) { self.balances.setter(account).add_assign_unchecked(value); } @@ -510,23 +513,23 @@ impl Erc721 { /// # Events /// /// Emits a [`Transfer`] event. - pub fn update( + pub fn _update( &mut self, to: Address, token_id: U256, auth: Address, ) -> Result { - let from = self.owner_of_inner(token_id)?; + let from = self._owner_of_inner(token_id)?; // Perform (optional) operator check if auth != Address::ZERO { - self.check_authorized(from, auth, token_id)?; + self._check_authorized(from, auth, token_id)?; } // Execute the update if from != Address::ZERO { // Clear approval. No need to re-authorize or emit the Approval event - self.approve_inner(Address::ZERO, token_id, Address::ZERO, false)?; + self._approve(Address::ZERO, token_id, Address::ZERO, false)?; self.balances.setter(from).sub_assign_unchecked(U256::from(1)); } @@ -559,14 +562,14 @@ impl Erc721 { /// # Events /// /// Emits a [`Transfer`] event. - pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { + pub fn _mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { if to == Address::ZERO { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() ); } - let previous_owner = self.update(to, token_id, Address::ZERO)?; + let previous_owner = self._update(to, token_id, Address::ZERO)?; if previous_owner != Address::ZERO { return Err(ERC721InvalidSender { sender: Address::ZERO }.into()); } @@ -582,14 +585,14 @@ impl Erc721 { /// * `to` - Account of the recipient /// * `token_id` - Token id as a number /// * `data` - Additional data with no specified format, sent in call to `to` - pub fn safe_mint( + pub fn _safe_mint( storage: &mut (impl TopLevelStorage + BorrowMut), to: Address, token_id: U256, data: Bytes, ) -> Result<(), Error> { - storage.borrow_mut().mint(to, token_id)?; - Self::check_on_erc721_received( + storage.borrow_mut()._mint(to, token_id)?; + Self::_check_on_erc721_received( storage, msg::sender(), Address::ZERO, @@ -615,9 +618,9 @@ impl Erc721 { /// # Events /// /// Emits a [`Transfer`] event. - pub fn burn(&mut self, token_id: U256) -> Result<(), Error> { + pub fn _burn(&mut self, token_id: U256) -> Result<(), Error> { let previous_owner = - self.update(Address::ZERO, token_id, Address::ZERO)?; + self._update(Address::ZERO, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { Err(ERC721NonexistentToken { token_id }.into()) } else { @@ -643,7 +646,7 @@ impl Erc721 { /// # Events /// /// Emits a [`Transfer`] event. - pub fn transfer( + pub fn _transfer( &mut self, from: Address, to: Address, @@ -655,7 +658,7 @@ impl Erc721 { ); } - let previous_owner = self.update(to, token_id, Address::ZERO)?; + let previous_owner = self._update(to, token_id, Address::ZERO)?; if previous_owner == Address::ZERO { Err(ERC721NonexistentToken { token_id }.into()) } else if previous_owner != from { @@ -680,15 +683,15 @@ impl Erc721 { /// * `to` - Account of the recipient /// * `token_id` - Token id as a number /// * `data` - Additional data with no specified format, sent in call to `to` - pub fn safe_transfer( + pub fn _safe_transfer( storage: &mut (impl TopLevelStorage + BorrowMut), from: Address, to: Address, token_id: U256, data: Bytes, ) -> Result<(), Error> { - storage.borrow_mut().transfer(from, to, token_id)?; - Self::check_on_erc721_received( + storage.borrow_mut()._transfer(from, to, token_id)?; + Self::_check_on_erc721_received( storage, msg::sender(), from, @@ -707,7 +710,7 @@ impl Erc721 { /// * `token_id` - Token id as a number /// * `auth` - Account used for authorization of the update. /// * `emit_event` - Emit ['Approval'] event flag. - pub fn approve_inner( + pub fn _approve( &mut self, to: Address, token_id: U256, @@ -716,7 +719,7 @@ impl Erc721 { ) -> Result<(), Error> { // Avoid reading the owner unless necessary if emit_event || auth != Address::ZERO { - let owner = self.require_owned(token_id)?; + let owner = self._require_owned(token_id)?; // We do not use _isAuthorized because single-token approvals should not be able to call approve if auth != Address::ZERO @@ -749,7 +752,7 @@ impl Erc721 { /// # Events /// /// Emits an {ApprovalForAll} event. - pub fn set_approval_for_all_inner( + pub fn _set_approval_for_all( &mut self, owner: Address, operator: Address, @@ -771,8 +774,8 @@ impl Erc721 { /// # Arguments /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number - pub fn require_owned(&self, token_id: U256) -> Result { - let owner = self.owner_of_inner(token_id)?; + pub fn _require_owned(&self, token_id: U256) -> Result { + let owner = self._owner_of_inner(token_id)?; if owner == Address::ZERO { return Err(ERC721NonexistentToken { token_id }.into()); } @@ -793,7 +796,7 @@ impl Erc721 { /// * `to` - Account of the recipient /// * `token_id` - Token id as a number /// * `data` - Additional data with no specified format, sent in call to `to` - pub fn check_on_erc721_received( + pub fn _check_on_erc721_received( storage: &mut impl TopLevelStorage, operator: Address, from: Address, @@ -801,7 +804,7 @@ impl Erc721 { token_id: U256, data: Bytes, ) -> Result<(), Error> { - // TODO: compute INTERFACE_ID at compile time + // TODO: think how we can retrieve INTERFACE_ID at compile time const IERC721RECEIVER_INTERFACE_ID: u32 = 0x150b7a02; if to.has_code() { let call = Call::new_in(storage); @@ -827,8 +830,6 @@ impl Erc721 { } } -use stylus_sdk::storage::{StorageGuardMut, StorageMap, StorageUint}; - pub trait IncrementalMath { fn add_assign_unchecked(&mut self, rhs: T); @@ -849,16 +850,17 @@ impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { #[cfg(test)] mod tests { - use crate::erc721::*; - #[allow(unused_imports)] - use crate::test_utils; use alloy_primitives::{address, Address, U256}; use once_cell::sync::Lazy; use stylus_sdk::{ msg, - storage::{StorageMap, StorageType, StorageU256}, + storage::{StorageMap, StorageType}, }; + use crate::erc721::*; + #[allow(unused_imports)] + use crate::test_utils; + // NOTE: Alice is always the sender of the message static ALICE: Lazy
= Lazy::new(msg::sender); @@ -885,7 +887,7 @@ mod tests { fn mint_nft_and_check_balance() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(*ALICE, token_id).expect("mint token"); + token._mint(*ALICE, token_id).expect("mint token"); let owner = token.owner_of(token_id).expect("owner address"); assert_eq!(owner, *ALICE); @@ -899,8 +901,8 @@ mod tests { fn error_mint_second_nft() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(*ALICE, token_id).expect("mint token first time"); - match token.mint(*ALICE, token_id) { + token._mint(*ALICE, token_id).expect("mint token first time"); + match token._mint(*ALICE, token_id) { Ok(_) => { panic!( "Second mint of the same token should not be possible" @@ -922,7 +924,7 @@ mod tests { fn transfer_nft() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(*ALICE, token_id).expect("mint nft to alice"); + token._mint(*ALICE, token_id).expect("mint nft to alice"); token .transfer_from(*ALICE, BOB, token_id) .expect("transfer from alice to bob"); @@ -957,7 +959,7 @@ mod tests { fn approve_nft_transfer() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(*ALICE, token_id).expect("mint token"); + token._mint(*ALICE, token_id).expect("mint token"); token .approve(BOB, token_id) .expect("approve bob for operations on token"); @@ -969,7 +971,7 @@ mod tests { fn transfer_approved_nft() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(BOB, token_id).expect("mint token"); + token._mint(BOB, token_id).expect("mint token"); token.token_approvals.setter(token_id).set(*ALICE); token .transfer_from(BOB, *ALICE, token_id) @@ -983,7 +985,7 @@ mod tests { fn error_not_approved_nft_transfer() { test_utils::with_storage::(|token| { let token_id = random_token_id(); - token.mint(BOB, token_id).expect("mint token"); + token._mint(BOB, token_id).expect("mint token"); match token.transfer_from(BOB, *ALICE, token_id) { Ok(_) => { panic!("Transfer of not approved token should not happen"); @@ -999,8 +1001,10 @@ mod tests { }; }); } - - // TODO: add mock for on_erc721_received + + // TODO: add set_approval_for_all test + + // TODO: add mock test for on_erc721_received fn random_token_id() -> U256 { let num: u32 = rand::random(); From ff6716ed25644ce13d737ae72acefa65dd5de767 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 1 Apr 2024 14:05:41 +0400 Subject: [PATCH 16/68] remove std deps --- contracts/src/erc721/mod.rs | 3 +-- contracts/src/lib.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 4ab7659b..c4b60801 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,6 +1,5 @@ +use core::borrow::BorrowMut; use derive_more::From; -use std::borrow::BorrowMut; -use std::vec; use stylus_sdk::storage::{StorageGuardMut, StorageUint}; use stylus_sdk::{ abi::Bytes, diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index b978eba9..d4389cb7 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -2,6 +2,7 @@ #![warn(missing_docs, unreachable_pub, rust_2021_compatibility)] #![warn(clippy::all, clippy::pedantic)] #![cfg_attr(not(test), no_std, no_main)] +#[macro_use] extern crate alloc; #[global_allocator] From 6cbaf5a11439976c30f20ed37f173e3edaaaf989 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 1 Apr 2024 18:27:58 +0400 Subject: [PATCH 17/68] fix function and events links in documentation --- contracts/src/erc721/mod.rs | 224 +++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 104 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index c4b60801..997538dc 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -29,7 +29,7 @@ sol! { /// /// * `owner` - Address of the owner of the token. /// * `operator` - Address of an operator that will manage operations on the token. - /// * `token_id` - Token id as a number. + /// * `approved` - Approved or not permission been granted. event ApprovalForAll(address indexed owner, address indexed operator, bool approved); } @@ -69,14 +69,14 @@ sol! { /// Indicates a failure with the `operator`’s approval. Used in transfers. /// - /// * `address` - Address of an operator that wasn't approved. + /// * `operator` - Address of an operator that wasn't approved. /// * `token_id` - Token id as a number. #[derive(Debug)] error ERC721InsufficientApproval(address operator, uint256 token_id); /// Indicates a failure with the `approver` of a token to be approved. Used in approvals. /// - /// * `address` - Address of an approver that failed to approve. + /// * `approver` - Address of an approver that failed to approve. #[derive(Debug)] error ERC721InvalidApprover(address approver); @@ -101,19 +101,18 @@ pub enum Error { InvalidOperator(ERC721InvalidOperator), } +// TODO: better to keep it at separate module sol_interface! { /// ERC-721 token receiver interface. /// Interface for any contract that wants to support safeTransfers /// from ERC-721 asset contracts. interface IERC721Receiver { - /// Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + /// Whenever an [`ERC721`] `tokenId` token is transferred to this contract via [`ERC721::safe_transfer_from`] /// by `operator` from `from`, this function is called. /// /// It must return its Solidity selector to confirm the token transfer. /// If any other value is returned or the interface is not implemented by the recipient, the transfer will be /// reverted. - /// - /// The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. function onERC721Received( address operator, address from, @@ -124,7 +123,7 @@ sol_interface! { } sol_storage! { - pub struct Erc721 { + pub struct ERC721 { mapping(uint256 => address) owners; mapping(address => uint256) balances; @@ -136,17 +135,13 @@ sol_storage! { } #[external] -impl Erc721 { - /// Returns the number of tokens in ``owner``'s account. +impl ERC721 { + /// Returns the number of tokens in `owner` 's account. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. - /// - /// # Returns - /// - /// The balance of the owner. pub fn balance_of(&self, owner: Address) -> Result { if owner == Address::ZERO { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); @@ -159,11 +154,7 @@ impl Erc721 { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `token_id` - Token id as a number - /// - /// # Returns - /// - /// The owner of the token. + /// * `token_id` - Token id as a number. /// /// # Requirements /// @@ -178,22 +169,18 @@ impl Erc721 { /// # Arguments /// /// * `storage` - Write access to the contract's state. - /// * `from` - Account of the sender - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number - /// - /// # Returns - /// - /// Result indicating success or failure. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. /// /// # Requirements /// /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must exist and be owned by `from`. - /// * If the caller is not `from`, it must have been allowed to move this token by either {approve} or - /// * {setApprovalForAll}. - /// * If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + /// * If the caller is not `from`, it must have been allowed to move this token by either [`Self::approve`] or + /// * [`Self::set_approval_for_all`]. + /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon /// * a safe transfer. /// /// # Events @@ -219,22 +206,18 @@ impl Erc721 { /// # Arguments /// /// * `storage` - Write access to the contract's state. - /// * `from` - Account of the sender - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number - /// * `data` - Additional data with no specified format, sent in call to `to` - /// - /// # Returns - /// - /// Result indicating success or failure. + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `data` - Additional data with no specified format, sent in call to `to`. /// /// # Requirements /// /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must exist and be owned by `from`. - /// * If the caller is not `from`, it must be approved to move this token by either {approve} or [`set_approval_for_all`]. - /// * If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + /// * If the caller is not `from`, it must be approved to move this token by either [`Self::_approve`] or [`Self::set_approval_for_all`]. + /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon /// * a safe transfer. /// /// # Events @@ -262,22 +245,22 @@ impl Erc721 { /// Transfers `token_id` token from `from` to `to`. /// /// WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721 - /// or else they may be permanently lost. Usage of {safe_transfer_from} prevents loss, though the caller must + /// or else they may be permanently lost. Usage of [`Self::safe_transfer_from`] prevents loss, though the caller must /// understand this adds an external call which potentially creates a reentrancy vulnerability. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. /// /// # Requirements: /// /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must be owned by `from`. - /// * If the caller is not `from`, it must be approved to move this token by either {approve} or [`set_approval_for_all`]. + /// * If the caller is not `from`, it must be approved to move this token by either [`Self::approve`] or [`Self::set_approval_for_all`]. /// /// # Events /// @@ -315,8 +298,8 @@ impl Erc721 { /// /// # Arguments /// * `&mut self` - Write access to the contract's state. - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. /// /// # Requirements: /// @@ -335,7 +318,7 @@ impl Erc721 { } /// Approve or remove `operator` as an operator for the caller. - /// Operators can call {transfer_from} or {safe_transfer_from} for any token owned by the caller. + /// Operators can call [`Self::transfer_from`] or [`Self::safe_transfer_from`] for any token owned by the caller. /// /// # Arguments /// @@ -363,7 +346,7 @@ impl Erc721 { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. /// /// # Requirements: /// @@ -383,7 +366,7 @@ impl Erc721 { /// /// # Events /// - /// Emits an [`set_approval_for_all`] event + /// Emits an [`set_approval_for_all`] event. pub fn is_approved_for_all( &self, owner: Address, @@ -393,18 +376,18 @@ impl Erc721 { } } -impl Erc721 { - /// Returns the owner of the `token_id`. Does NOT revert if token doesn't exist +impl ERC721 { + /// Returns the owner of the `token_id`. Does NOT revert if token doesn't exist. /// /// IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the - /// core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances + /// core ERC-721 logic MUST be matched with the use of [`Self::_increase_balance`] to keep balances /// consistent with ownership. The invariant to preserve is that for any address `a` the value returned by /// `balance_of(a)` must be equal to the number of tokens such that `owner_of_inner(token_id)` is `a`. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. pub fn _owner_of_inner(&self, token_id: U256) -> Result { Ok(self.owners.get(token_id)) } @@ -414,7 +397,7 @@ impl Erc721 { /// # Arguments /// /// * `&self` - Read access to the contract's state. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. pub fn _get_approved_inner( &self, token_id: U256, @@ -429,10 +412,11 @@ impl Erc721 { /// assumption. /// /// # Arguments + /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. pub fn _is_authorized( &self, owner: Address, @@ -458,7 +442,7 @@ impl Erc721 { /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. pub fn _check_authorized( &self, owner: Address, @@ -476,13 +460,13 @@ impl Erc721 { Ok(()) } - /// Unsafe write access to the balances, used by extensions that "mint" tokens using an {owner_of} override. + /// Unsafe write access to the balances, used by extensions that "mint" tokens using an [`Self::owner_of`] override. /// /// NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that /// a uint256 would ever overflow from increments when these increments are bounded to uint128 values. /// /// WARNING: Increasing an account's balance using this function tends to be paired with an override of the - /// {owner_of_inner} function to resolve the ownership of the corresponding tokens so that balances and ownership + /// [`Self::_owner_of_inner`] function to resolve the ownership of the corresponding tokens so that balances and ownership /// remain consistent with one another. /// /// # Arguments @@ -500,7 +484,7 @@ impl Erc721 { /// The `auth` argument is optional. If the value passed is non 0, then this function will check that /// `auth` is either the owner of the token, or approved to operate on the token (by the owner). /// - /// NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + /// NOTE: If overriding this function in a way that tracks balances, see also [`Self::_increase_balance`]. /// /// # Arguments /// @@ -545,13 +529,13 @@ impl Erc721 { /// Mints `token_id` and transfers it to `to`. /// - /// WARNING: Usage of this method is discouraged, use {safe_mint} whenever possible + /// WARNING: Usage of this method is discouraged, use [`Self::_safe_mint`] whenever possible. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. /// /// # Requirements: /// @@ -575,15 +559,25 @@ impl Erc721 { Ok(()) } - /// Same as {xref-ERC721-safe_mint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is - /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + /// Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + /// An additional `data` parameter is forwarded to [`IERC721Receiver::on_erc_721_received`] + /// to contract recipients. /// /// # Arguments /// /// * `storage` - Write access to the contract's state. - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number - /// * `data` - Additional data with no specified format, sent in call to `to` + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `data` - Additional data with no specified format, sent in call to `to`. + /// + /// # Requirements: + /// + /// * `tokenId` must not exist. + /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon a safe transfer. + /// + /// # Events + /// + /// Emits a [`Transfer`] event. pub fn _safe_mint( storage: &mut (impl TopLevelStorage + BorrowMut), to: Address, @@ -608,7 +602,7 @@ impl Erc721 { /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. /// /// # Requirements: /// @@ -628,14 +622,14 @@ impl Erc721 { } /// Transfers `token_id` from `from` to `to`. - /// As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + /// As opposed to [`transferFrom`], this imposes no restrictions on msg.sender. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `from` - Account of the sender - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. /// /// # Requirements: /// @@ -672,16 +666,30 @@ impl Erc721 { } } - /// Same as {xref-ERC721-safe_transfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is - /// forwarded in {IERC721Receiver-onERC721Received} to contract recipients. - /// + /// Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients + /// are aware of the ERC-721 standard to prevent tokens from being forever locked. + /// `data` is additional data, it has no specified format and it is sent in call to `to`. + /// This internal function is like [`Self::safe_transfer_from`] in the sense that it invokes + /// [`IERC721Receiver::on_erc_721_received`] on the receiver, and can be used to e.g. + /// implement alternative mechanisms to perform token transfer, such as signature-based. + /// /// # Arguments /// /// * `storage` - Write access to the contract's state. - /// * `from` - Account of the sender - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number - /// * `data` - Additional data with no specified format, sent in call to `to` + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `data` - Additional data with no specified format, sent in call to `to`. + /// + /// # Requirements: + /// + /// * `tokenId` token must exist and be owned by `from`. + /// * `to` cannot be the zero address. + /// * `from` cannot be the zero address. + /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon a safe transfer. + /// + /// # Events + /// Emits a [`Transfer`] event. pub fn _safe_transfer( storage: &mut (impl TopLevelStorage + BorrowMut), from: Address, @@ -700,15 +708,19 @@ impl Erc721 { ) } - /// Variant of `approve_inner` with an optional flag to enable or disable the {Approval} event. The event is not + /// Variant of `approve_inner` with an optional flag to enable or disable the [`Approval`] event. The event is not /// emitted in the context of transfers. /// /// # Arguments + /// /// * `&mut self` - Write access to the contract's state. - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. /// * `auth` - Account used for authorization of the update. - /// * `emit_event` - Emit ['Approval'] event flag. + /// * `emit_event` - Emit an [`Approval`] event flag. + /// + /// # Events + /// Emits an [`Approval`] event. pub fn _approve( &mut self, to: Address, @@ -737,20 +749,22 @@ impl Erc721 { Ok(()) } - /// Approve `operator` to operate on all of `owner` tokens + /// Approve `operator` to operate on all of `owner` tokens. /// /// # Arguments + /// /// * `&mut self` - Write access to the contract's state. /// * `owner` - Account the token's owner. /// * `operator` - Account to add to the set of authorized operators. - /// * `approved` - Flag that that set approval or disapproval for the operator. + /// * `approved` - Flag that set approval or disapproval for the operator. /// /// # Requirements: + /// /// * operator can't be the address zero. /// /// # Events /// - /// Emits an {ApprovalForAll} event. + /// Emits an [`ApprovalForAll`] event. pub fn _set_approval_for_all( &mut self, owner: Address, @@ -768,11 +782,12 @@ impl Erc721 { /// Reverts if the `token_id` doesn't have a current owner (it hasn't been minted, or it has been burned). /// Returns the owner. /// - /// Overrides to ownership logic should be done to {owner_of_inner}. + /// Overrides to ownership logic should be done to [`Self::_owner_of_inner`]. /// /// # Arguments + /// /// * `&self` - Read access to the contract's state. - /// * `token_id` - Token id as a number + /// * `token_id` - Token id as a number. pub fn _require_owned(&self, token_id: U256) -> Result { let owner = self._owner_of_inner(token_id)?; if owner == Address::ZERO { @@ -781,20 +796,21 @@ impl Erc721 { Ok(owner) } - /// Performs an acceptance check for the provided `operator` by calling {IERC721-onERC721Received} - /// on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). + /// Performs an acceptance check for the provided `operator` by calling [`IERC721Receiver::on_erc_721_received`] + /// on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg::sender()`). /// - /// The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA). - /// Otherwise, the recipient must implement {IERC721Receiver-onERC721Received} and return the acceptance magic value to accept + /// The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). + /// Otherwise, the recipient must implement [`IERC721Receiver::on_erc_721_received`] and return the acceptance magic value to accept /// the transfer. /// /// # Arguments + /// /// * `storage` - Write access to the contract's state. /// * `operator` - Account to add to the set of authorized operators. - /// * `from` - Account of the sender - /// * `to` - Account of the recipient - /// * `token_id` - Token id as a number - /// * `data` - Additional data with no specified format, sent in call to `to` + /// * `from` - Account of the sender. + /// * `to` - Account of the recipient. + /// * `token_id` - Token id as a number. + /// * `data` - Additional data with no specified format, sent in call to `to`. pub fn _check_on_erc721_received( storage: &mut impl TopLevelStorage, operator: Address, @@ -865,11 +881,11 @@ mod tests { const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); - impl Default for Erc721 { + impl Default for ERC721 { fn default() -> Self { let root = U256::ZERO; - Erc721 { + ERC721 { owners: unsafe { StorageMap::new(root, 0) }, balances: unsafe { StorageMap::new(root + U256::from(32), 0) }, token_approvals: unsafe { @@ -884,7 +900,7 @@ mod tests { #[test] fn mint_nft_and_check_balance() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(*ALICE, token_id).expect("mint token"); let owner = token.owner_of(token_id).expect("owner address"); @@ -898,7 +914,7 @@ mod tests { #[test] fn error_mint_second_nft() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(*ALICE, token_id).expect("mint token first time"); match token._mint(*ALICE, token_id) { @@ -921,7 +937,7 @@ mod tests { #[test] fn transfer_nft() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(*ALICE, token_id).expect("mint nft to alice"); token @@ -934,7 +950,7 @@ mod tests { #[test] fn error_transfer_nonexistent_nft() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); match token.transfer_from(*ALICE, BOB, token_id) { Ok(_) => { @@ -956,7 +972,7 @@ mod tests { #[test] fn approve_nft_transfer() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(*ALICE, token_id).expect("mint token"); token @@ -968,7 +984,7 @@ mod tests { #[test] fn transfer_approved_nft() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(BOB, token_id).expect("mint token"); token.token_approvals.setter(token_id).set(*ALICE); @@ -982,7 +998,7 @@ mod tests { #[test] fn error_not_approved_nft_transfer() { - test_utils::with_storage::(|token| { + test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(BOB, token_id).expect("mint token"); match token.transfer_from(BOB, *ALICE, token_id) { From 3a360b5daa899f75f9e7e9e2d8615841335fe36d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 1 Apr 2024 22:47:10 +0400 Subject: [PATCH 18/68] add errors description to documentation --- contracts/src/erc721/mod.rs | 103 +++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 997538dc..6ac86cbb 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -142,6 +142,10 @@ impl ERC721 { /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. + /// + /// # Errors + /// + /// * If owner address is `Address::ZERO`, then [`Error::InvalidOwner`] is returned. pub fn balance_of(&self, owner: Address) -> Result { if owner == Address::ZERO { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); @@ -156,6 +160,10 @@ impl ERC721 { /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// /// # Requirements /// /// * `token_id` must exist. @@ -173,6 +181,15 @@ impl ERC721 { /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. + /// * If caller does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its interface id or + /// returned with error then [`Error::InvalidReceiver`] is returned. + /// /// # Requirements /// /// * `from` cannot be the zero address. @@ -211,6 +228,15 @@ impl ERC721 { /// * `token_id` - Token id as a number. /// * `data` - Additional data with no specified format, sent in call to `to`. /// + /// # Errors + /// + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. + /// * If caller does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its interface id or + /// returned with error then [`Error::InvalidReceiver`] is returned. + /// /// # Requirements /// /// * `from` cannot be the zero address. @@ -255,6 +281,13 @@ impl ERC721 { /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. + /// * If caller does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// /// # Requirements: /// /// * `from` cannot be the zero address. @@ -301,6 +334,12 @@ impl ERC721 { /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If `auth` does not have a right to approve this token then [`Error::InvalidApprover`] + /// is returned + /// /// # Requirements: /// /// - The caller must own the token or be an approved operator. @@ -412,7 +451,7 @@ impl ERC721 { /// assumption. /// /// # Arguments - /// + /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. @@ -443,6 +482,11 @@ impl ERC721 { /// * `owner` - Account of the token's owner. /// * `spender` - Account that will spend token. /// * `token_id` - Token id as a number. + /// + /// # Errors + /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If spender does not have right to approve then [`Error::InsufficientApproval`] is returned. pub fn _check_authorized( &self, owner: Address, @@ -493,6 +537,13 @@ impl ERC721 { /// * `token_id` - Token id as a number. /// * `auth` - Account used for authorization of the update. /// + /// # Errors + /// + /// * If token does not exist and `auth` is not `Address::ZERO` then + /// [`Error::NonexistentToken`] is returned. + /// * If `auth` is not `Address::ZERO` and `auth` does not have a right to approve this token + /// then [`Error::InsufficientApproval`] ßis returned. + /// /// # Events /// /// Emits a [`Transfer`] event. @@ -537,6 +588,11 @@ impl ERC721 { /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If `token_id` already exist then [`Error::InvalidSender`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// /// # Requirements: /// /// * `token_id` must not exist. @@ -604,6 +660,10 @@ impl ERC721 { /// * `&mut self` - Write access to the contract's state. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// /// # Requirements: /// /// * `token_id` must exist. @@ -631,6 +691,12 @@ impl ERC721 { /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. + /// /// # Requirements: /// /// * `to` cannot be the zero address. @@ -680,6 +746,12 @@ impl ERC721 { /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// * `data` - Additional data with no specified format, sent in call to `to`. + /// + /// # Errors + /// + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. /// /// # Requirements: /// @@ -712,13 +784,19 @@ impl ERC721 { /// emitted in the context of transfers. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// * `auth` - Account used for authorization of the update. /// * `emit_event` - Emit an [`Approval`] event flag. /// + /// # Errors + /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If `auth` does not have a right to approve this token then [`Error::InvalidApprover`] + /// is returned + /// /// # Events /// Emits an [`Approval`] event. pub fn _approve( @@ -752,14 +830,18 @@ impl ERC721 { /// Approve `operator` to operate on all of `owner` tokens. /// /// # Arguments - /// + /// /// * `&mut self` - Write access to the contract's state. /// * `owner` - Account the token's owner. /// * `operator` - Account to add to the set of authorized operators. /// * `approved` - Flag that set approval or disapproval for the operator. /// - /// # Requirements: + /// # Errors /// + /// * If `operator` is `Address::ZERO` then [`Error::InvalidOperator`] is returned. + /// + /// # Requirements: + /// /// * operator can't be the address zero. /// /// # Events @@ -784,8 +866,12 @@ impl ERC721 { /// /// Overrides to ownership logic should be done to [`Self::_owner_of_inner`]. /// - /// # Arguments + /// # Errors /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// + /// # Arguments + /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. pub fn _require_owned(&self, token_id: U256) -> Result { @@ -804,13 +890,18 @@ impl ERC721 { /// the transfer. /// /// # Arguments - /// + /// /// * `storage` - Write access to the contract's state. /// * `operator` - Account to add to the set of authorized operators. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// * `data` - Additional data with no specified format, sent in call to `to`. + /// + /// # Errors + /// + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its interface id or + /// returned with error then [`Error::InvalidReceiver`] is returned. pub fn _check_on_erc721_received( storage: &mut impl TopLevelStorage, operator: Address, From 3ece16106831d3a45dd560fb74c334cb9f5e5309 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 2 Apr 2024 00:33:51 +0400 Subject: [PATCH 19/68] rust fmt --- contracts/src/erc721/mod.rs | 286 ++++++++++++++++++++++-------------- 1 file changed, 179 insertions(+), 107 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 6ac86cbb..6a94da81 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,6 +1,6 @@ use core::borrow::BorrowMut; + use derive_more::From; -use stylus_sdk::storage::{StorageGuardMut, StorageUint}; use stylus_sdk::{ abi::Bytes, alloy_primitives::{Address, U256}, @@ -8,6 +8,7 @@ use stylus_sdk::{ call::Call, evm, msg, prelude::*, + storage::{StorageGuardMut, StorageUint}, }; sol! { @@ -145,7 +146,8 @@ impl ERC721 { /// /// # Errors /// - /// * If owner address is `Address::ZERO`, then [`Error::InvalidOwner`] is returned. + /// * If owner address is `Address::ZERO`, then [`Error::InvalidOwner`] is + /// returned. pub fn balance_of(&self, owner: Address) -> Result { if owner == Address::ZERO { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); @@ -171,8 +173,9 @@ impl ERC721 { self._require_owned(token_id) } - /// Safely transfers `token_id` token from `from` to `to`, checking first that contract recipients - /// are aware of the ERC-721 protocol to prevent tokens from being forever locked. + /// Safely transfers `token_id` token from `from` to `to`, checking first + /// that contract recipients are aware of the ERC-721 protocol to + /// prevent tokens from being forever locked. /// /// # Arguments /// @@ -183,11 +186,15 @@ impl ERC721 { /// /// # Errors /// - /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. - /// * If caller does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// returned. + /// * If caller does not have right to approve then + /// [`Error::InsufficientApproval`] is returned. /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its interface id or + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its + /// interface id or /// returned with error then [`Error::InvalidReceiver`] is returned. /// /// # Requirements @@ -195,9 +202,11 @@ impl ERC721 { /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must exist and be owned by `from`. - /// * If the caller is not `from`, it must have been allowed to move this token by either [`Self::approve`] or + /// * If the caller is not `from`, it must have been allowed to move this + /// token by either [`Self::approve`] or /// * [`Self::set_approval_for_all`]. - /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon + /// * If `to` refers to a smart contract, it must implement + /// [`IERC721Receiver::on_erc_721_received`], which is called upon /// * a safe transfer. /// /// # Events @@ -226,15 +235,20 @@ impl ERC721 { /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to `to`. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. /// /// # Errors /// - /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. - /// * If caller does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// returned. + /// * If caller does not have right to approve then + /// [`Error::InsufficientApproval`] is returned. /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its interface id or + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its + /// interface id or /// returned with error then [`Error::InvalidReceiver`] is returned. /// /// # Requirements @@ -242,8 +256,10 @@ impl ERC721 { /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must exist and be owned by `from`. - /// * If the caller is not `from`, it must be approved to move this token by either [`Self::_approve`] or [`Self::set_approval_for_all`]. - /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon + /// * If the caller is not `from`, it must be approved to move this token by + /// either [`Self::_approve`] or [`Self::set_approval_for_all`]. + /// * If `to` refers to a smart contract, it must implement + /// [`IERC721Receiver::on_erc_721_received`], which is called upon /// * a safe transfer. /// /// # Events @@ -270,9 +286,11 @@ impl ERC721 { /// Transfers `token_id` token from `from` to `to`. /// - /// WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721 - /// or else they may be permanently lost. Usage of [`Self::safe_transfer_from`] prevents loss, though the caller must - /// understand this adds an external call which potentially creates a reentrancy vulnerability. + /// WARNING: Note that the caller is responsible to confirm that the + /// recipient is capable of receiving ERC-721 or else they may be + /// permanently lost. Usage of [`Self::safe_transfer_from`] prevents loss, + /// though the caller must understand this adds an external call which + /// potentially creates a reentrancy vulnerability. /// /// # Arguments /// @@ -283,9 +301,12 @@ impl ERC721 { /// /// # Errors /// - /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. - /// * If caller does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// returned. + /// * If caller does not have right to approve then + /// [`Error::InsufficientApproval`] is returned. /// * If token does not exist then [`Error::NonexistentToken`] is returned. /// /// # Requirements: @@ -293,7 +314,8 @@ impl ERC721 { /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. /// * `token_id` token must be owned by `from`. - /// * If the caller is not `from`, it must be approved to move this token by either [`Self::approve`] or [`Self::set_approval_for_all`]. + /// * If the caller is not `from`, it must be approved to move this token by + /// either [`Self::approve`] or [`Self::set_approval_for_all`]. /// /// # Events /// @@ -310,8 +332,9 @@ impl ERC721 { ); } - // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists - // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + // Setting an "auth" arguments enables the `_isAuthorized` check which + // verifies that the token exists (from != 0). Therefore, it is + // not needed to verify that the return value is not 0 here. let previous_owner = self._update(to, token_id, msg::sender())?; if previous_owner != from { return Err(ERC721IncorrectOwner { @@ -324,10 +347,11 @@ impl ERC721 { Ok(()) } - /// Gives permission to `to` to transfer `token_id` token to another account. - /// The approval is cleared when the token is transferred. + /// Gives permission to `to` to transfer `token_id` token to another + /// account. The approval is cleared when the token is transferred. /// - /// Only a single account can be approved at a time, so approving the zero address clears previous approvals. + /// Only a single account can be approved at a time, so approving the zero + /// address clears previous approvals. /// /// # Arguments /// * `&mut self` - Write access to the contract's state. @@ -337,7 +361,8 @@ impl ERC721 { /// # Errors /// /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If `auth` does not have a right to approve this token then [`Error::InvalidApprover`] + /// * If `auth` does not have a right to approve this token then + /// [`Error::InvalidApprover`] /// is returned /// /// # Requirements: @@ -357,13 +382,15 @@ impl ERC721 { } /// Approve or remove `operator` as an operator for the caller. - /// Operators can call [`Self::transfer_from`] or [`Self::safe_transfer_from`] for any token owned by the caller. + /// Operators can call [`Self::transfer_from`] or + /// [`Self::safe_transfer_from`] for any token owned by the caller. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. /// * `operator` - Account add to the set of authorized operators. - /// * `approved` - Flag that that set approval or disapproval for the operator. + /// * `approved` - Flag that that set approval or disapproval for the + /// operator. /// /// # Requirements: /// @@ -395,7 +422,8 @@ impl ERC721 { self._get_approved_inner(token_id) } - /// Returns if the `operator` is allowed to manage all the assets of `owner`. + /// Returns if the `operator` is allowed to manage all the assets of + /// `owner`. /// /// # Arguments /// @@ -416,12 +444,15 @@ impl ERC721 { } impl ERC721 { - /// Returns the owner of the `token_id`. Does NOT revert if token doesn't exist. + /// Returns the owner of the `token_id`. Does NOT revert if token doesn't + /// exist. /// - /// IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the - /// core ERC-721 logic MUST be matched with the use of [`Self::_increase_balance`] to keep balances - /// consistent with ownership. The invariant to preserve is that for any address `a` the value returned by - /// `balance_of(a)` must be equal to the number of tokens such that `owner_of_inner(token_id)` is `a`. + /// IMPORTANT: Any overrides to this function that add ownership of tokens + /// not tracked by the core ERC-721 logic MUST be matched with the use + /// of [`Self::_increase_balance`] to keep balances consistent with + /// ownership. The invariant to preserve is that for any address `a` the + /// value returned by `balance_of(a)` must be equal to the number of + /// tokens such that `owner_of_inner(token_id)` is `a`. /// /// # Arguments /// @@ -431,7 +462,8 @@ impl ERC721 { Ok(self.owners.get(token_id)) } - /// Returns the approved address for `token_id`. Returns 0 if `token_id` is not minted. + /// Returns the approved address for `token_id`. Returns 0 if `token_id` is + /// not minted. /// /// # Arguments /// @@ -444,11 +476,11 @@ impl ERC721 { Ok(self.token_approvals.get(token_id)) } - /// Returns whether `spender` is allowed to manage `owner`'s tokens, or `token_id` in - /// particular (ignoring whether it is owned by `owner`). + /// Returns whether `spender` is allowed to manage `owner`'s tokens, or + /// `token_id` in particular (ignoring whether it is owned by `owner`). /// - /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this - /// assumption. + /// WARNING: This function assumes that `owner` is the actual owner of + /// `token_id` and does not verify this assumption. /// /// # Arguments /// @@ -469,12 +501,13 @@ impl ERC721 { Ok(is_authorized) } - /// Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. - /// Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets - /// the `spender` for the specific `token_id`. + /// Checks if `spender` can operate on `token_id`, assuming the provided + /// `owner` is the actual owner. Reverts if `spender` does not have + /// approval from the provided `owner` for the given token or for all its + /// assets the `spender` for the specific `token_id`. /// - /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this - /// assumption. + /// WARNING: This function assumes that `owner` is the actual owner of + /// `token_id` and does not verify this assumption. /// /// # Arguments /// @@ -486,7 +519,8 @@ impl ERC721 { /// # Errors /// /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If spender does not have right to approve then [`Error::InsufficientApproval`] is returned. + /// * If spender does not have right to approve then + /// [`Error::InsufficientApproval`] is returned. pub fn _check_authorized( &self, owner: Address, @@ -504,14 +538,18 @@ impl ERC721 { Ok(()) } - /// Unsafe write access to the balances, used by extensions that "mint" tokens using an [`Self::owner_of`] override. + /// Unsafe write access to the balances, used by extensions that "mint" + /// tokens using an [`Self::owner_of`] override. /// - /// NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that - /// a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + /// NOTE: the value is limited to type(uint128).max. This protect against + /// _balance overflow. It is unrealistic that a uint256 would ever + /// overflow from increments when these increments are bounded to uint128 + /// values. /// - /// WARNING: Increasing an account's balance using this function tends to be paired with an override of the - /// [`Self::_owner_of_inner`] function to resolve the ownership of the corresponding tokens so that balances and ownership - /// remain consistent with one another. + /// WARNING: Increasing an account's balance using this function tends to be + /// paired with an override of the [`Self::_owner_of_inner`] function to + /// resolve the ownership of the corresponding tokens so that balances and + /// ownership remain consistent with one another. /// /// # Arguments /// @@ -522,13 +560,16 @@ impl ERC721 { self.balances.setter(account).add_assign_unchecked(value); } - /// Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner - /// (or `to`) is the zero address. Returns the owner of the `token_id` before the update. + /// Transfers `token_id` from its current owner to `to`, or alternatively + /// mints (or burns) if the current owner (or `to`) is the zero address. + /// Returns the owner of the `token_id` before the update. /// - /// The `auth` argument is optional. If the value passed is non 0, then this function will check that - /// `auth` is either the owner of the token, or approved to operate on the token (by the owner). + /// The `auth` argument is optional. If the value passed is non 0, then this + /// function will check that `auth` is either the owner of the token, or + /// approved to operate on the token (by the owner). /// - /// NOTE: If overriding this function in a way that tracks balances, see also [`Self::_increase_balance`]. + /// NOTE: If overriding this function in a way that tracks balances, see + /// also [`Self::_increase_balance`]. /// /// # Arguments /// @@ -541,7 +582,8 @@ impl ERC721 { /// /// * If token does not exist and `auth` is not `Address::ZERO` then /// [`Error::NonexistentToken`] is returned. - /// * If `auth` is not `Address::ZERO` and `auth` does not have a right to approve this token + /// * If `auth` is not `Address::ZERO` and `auth` does not have a right to + /// approve this token /// then [`Error::InsufficientApproval`] ßis returned. /// /// # Events @@ -562,7 +604,8 @@ impl ERC721 { // Execute the update if from != Address::ZERO { - // Clear approval. No need to re-authorize or emit the Approval event + // Clear approval. No need to re-authorize or emit the Approval + // event self._approve(Address::ZERO, token_id, Address::ZERO, false)?; self.balances.setter(from).sub_assign_unchecked(U256::from(1)); } @@ -580,7 +623,8 @@ impl ERC721 { /// Mints `token_id` and transfers it to `to`. /// - /// WARNING: Usage of this method is discouraged, use [`Self::_safe_mint`] whenever possible. + /// WARNING: Usage of this method is discouraged, use [`Self::_safe_mint`] + /// whenever possible. /// /// # Arguments /// @@ -591,7 +635,8 @@ impl ERC721 { /// # Errors /// /// * If `token_id` already exist then [`Error::InvalidSender`] is returned. - /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. /// /// # Requirements: /// @@ -616,20 +661,23 @@ impl ERC721 { } /// Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. - /// An additional `data` parameter is forwarded to [`IERC721Receiver::on_erc_721_received`] - /// to contract recipients. + /// An additional `data` parameter is forwarded to + /// [`IERC721Receiver::on_erc_721_received`] to contract recipients. /// /// # Arguments /// /// * `storage` - Write access to the contract's state. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to `to`. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. /// /// # Requirements: /// /// * `tokenId` must not exist. - /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon a safe transfer. + /// * If `to` refers to a smart contract, it must implement + /// [`IERC721Receiver::on_erc_721_received`], which is called upon a safe + /// transfer. /// /// # Events /// @@ -653,7 +701,8 @@ impl ERC721 { /// Destroys `token_id`. /// The approval is cleared when the token is burned. - /// This is an internal function that does not check if the sender is authorized to operate on the token. + /// This is an internal function that does not check if the sender is + /// authorized to operate on the token. /// /// # Arguments /// @@ -682,7 +731,8 @@ impl ERC721 { } /// Transfers `token_id` from `from` to `to`. - /// As opposed to [`transferFrom`], this imposes no restrictions on msg.sender. + /// As opposed to [`transferFrom`], this imposes no restrictions on + /// msg.sender. /// /// # Arguments /// @@ -693,9 +743,12 @@ impl ERC721 { /// /// # Errors /// - /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. - /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. + /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is + /// returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// returned. /// /// # Requirements: /// @@ -732,34 +785,42 @@ impl ERC721 { } } - /// Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients - /// are aware of the ERC-721 standard to prevent tokens from being forever locked. - /// `data` is additional data, it has no specified format and it is sent in call to `to`. - /// This internal function is like [`Self::safe_transfer_from`] in the sense that it invokes - /// [`IERC721Receiver::on_erc_721_received`] on the receiver, and can be used to e.g. - /// implement alternative mechanisms to perform token transfer, such as signature-based. - /// + /// Safely transfers `tokenId` token from `from` to `to`, checking that + /// contract recipients are aware of the ERC-721 standard to prevent + /// tokens from being forever locked. `data` is additional data, it has + /// no specified format and it is sent in call to `to`. This internal + /// function is like [`Self::safe_transfer_from`] in the sense that it + /// invokes [`IERC721Receiver::on_erc_721_received`] on the receiver, + /// and can be used to e.g. implement alternative mechanisms to perform + /// token transfer, such as signature-based. + /// /// # Arguments /// /// * `storage` - Write access to the contract's state. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to `to`. + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. /// /// # Errors /// - /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is returned. - /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is returned. - /// + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. + /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is + /// returned. + /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// returned. + /// /// # Requirements: - /// + /// /// * `tokenId` token must exist and be owned by `from`. /// * `to` cannot be the zero address. /// * `from` cannot be the zero address. - /// * If `to` refers to a smart contract, it must implement [`IERC721Receiver::on_erc_721_received`], which is called upon a safe transfer. - /// + /// * If `to` refers to a smart contract, it must implement + /// [`IERC721Receiver::on_erc_721_received`], which is called upon a safe + /// transfer. + /// /// # Events /// Emits a [`Transfer`] event. pub fn _safe_transfer( @@ -780,8 +841,9 @@ impl ERC721 { ) } - /// Variant of `approve_inner` with an optional flag to enable or disable the [`Approval`] event. The event is not - /// emitted in the context of transfers. + /// Variant of `approve_inner` with an optional flag to enable or disable + /// the [`Approval`] event. The event is not emitted in the context of + /// transfers. /// /// # Arguments /// @@ -794,7 +856,8 @@ impl ERC721 { /// # Errors /// /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If `auth` does not have a right to approve this token then [`Error::InvalidApprover`] + /// * If `auth` does not have a right to approve this token then + /// [`Error::InvalidApprover`] /// is returned /// /// # Events @@ -810,7 +873,8 @@ impl ERC721 { if emit_event || auth != Address::ZERO { let owner = self._require_owned(token_id)?; - // We do not use _isAuthorized because single-token approvals should not be able to call approve + // We do not use _isAuthorized because single-token approvals should + // not be able to call approve if auth != Address::ZERO && owner != auth && !self.is_approved_for_all(owner, auth)? @@ -837,8 +901,9 @@ impl ERC721 { /// * `approved` - Flag that set approval or disapproval for the operator. /// /// # Errors - /// - /// * If `operator` is `Address::ZERO` then [`Error::InvalidOperator`] is returned. + /// + /// * If `operator` is `Address::ZERO` then [`Error::InvalidOperator`] is + /// returned. /// /// # Requirements: /// @@ -861,13 +926,14 @@ impl ERC721 { Ok(()) } - /// Reverts if the `token_id` doesn't have a current owner (it hasn't been minted, or it has been burned). - /// Returns the owner. + /// Reverts if the `token_id` doesn't have a current owner (it hasn't been + /// minted, or it has been burned). Returns the owner. /// - /// Overrides to ownership logic should be done to [`Self::_owner_of_inner`]. + /// Overrides to ownership logic should be done to + /// [`Self::_owner_of_inner`]. /// /// # Errors - /// + /// /// * If token does not exist then [`Error::NonexistentToken`] is returned. /// /// # Arguments @@ -882,12 +948,15 @@ impl ERC721 { Ok(owner) } - /// Performs an acceptance check for the provided `operator` by calling [`IERC721Receiver::on_erc_721_received`] - /// on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg::sender()`). + /// Performs an acceptance check for the provided `operator` by calling + /// [`IERC721Receiver::on_erc_721_received`] on the `to` address. The + /// `operator` is generally the address that initiated the token transfer + /// (i.e. `msg::sender()`). /// - /// The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). - /// Otherwise, the recipient must implement [`IERC721Receiver::on_erc_721_received`] and return the acceptance magic value to accept - /// the transfer. + /// The acceptance call is not executed and treated as a no-op if the target + /// address doesn't contain code (i.e. an EOA). Otherwise, the recipient + /// must implement [`IERC721Receiver::on_erc_721_received`] and return the + /// acceptance magic value to accept the transfer. /// /// # Arguments /// @@ -896,11 +965,13 @@ impl ERC721 { /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to `to`. - /// + /// * `data` - Additional data with no specified format, sent in call to + /// `to`. + /// /// # Errors - /// - /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its interface id or + /// + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its + /// interface id or /// returned with error then [`Error::InvalidReceiver`] is returned. pub fn _check_on_erc721_received( storage: &mut impl TopLevelStorage, @@ -936,6 +1007,7 @@ impl ERC721 { } } +// TODO: make it common for all contracts or remove/inline pub trait IncrementalMath { fn add_assign_unchecked(&mut self, rhs: T); From 2b0e865677524aabf09f3ec26aad0b239c1bfdd9 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 2 Apr 2024 00:57:37 +0400 Subject: [PATCH 20/68] add more error documentation --- contracts/src/erc721/mod.rs | 65 ++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 6a94da81..b0dd94c6 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -4,7 +4,7 @@ use derive_more::From; use stylus_sdk::{ abi::Bytes, alloy_primitives::{Address, U256}, - alloy_sol_types::{sol, SolError}, + alloy_sol_types::sol, call::Call, evm, msg, prelude::*, @@ -392,6 +392,11 @@ impl ERC721 { /// * `approved` - Flag that that set approval or disapproval for the /// operator. /// + /// # Errors + /// + /// * If `operator` is `Address::ZERO` then [`Error::InvalidOperator`] is + /// returned. + /// /// # Requirements: /// /// * The `operator` cannot be the address zero. @@ -414,12 +419,16 @@ impl ERC721 { /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. /// + /// # Errors + /// + /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// /// # Requirements: /// /// * `token_id` must exist. pub fn get_approved(&self, token_id: U256) -> Result { self._require_owned(token_id)?; - self._get_approved_inner(token_id) + Ok(self._get_approved_inner(token_id)) } /// Returns if the `operator` is allowed to manage all the assets of @@ -438,8 +447,8 @@ impl ERC721 { &self, owner: Address, operator: Address, - ) -> Result { - Ok(self.operator_approvals.get(owner).get(operator)) + ) -> bool { + self.operator_approvals.get(owner).get(operator) } } @@ -458,8 +467,8 @@ impl ERC721 { /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. - pub fn _owner_of_inner(&self, token_id: U256) -> Result { - Ok(self.owners.get(token_id)) + pub fn _owner_of_inner(&self, token_id: U256) -> Address { + self.owners.get(token_id) } /// Returns the approved address for `token_id`. Returns 0 if `token_id` is @@ -469,11 +478,8 @@ impl ERC721 { /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. - pub fn _get_approved_inner( - &self, - token_id: U256, - ) -> Result { - Ok(self.token_approvals.get(token_id)) + pub fn _get_approved_inner(&self, token_id: U256) -> Address { + self.token_approvals.get(token_id) } /// Returns whether `spender` is allowed to manage `owner`'s tokens, or @@ -493,12 +499,11 @@ impl ERC721 { owner: Address, spender: Address, token_id: U256, - ) -> Result { - let is_authorized = spender != Address::ZERO + ) -> bool { + spender != Address::ZERO && (owner == spender - || self.is_approved_for_all(owner, spender)? - || self._get_approved_inner(token_id)? == spender); - Ok(is_authorized) + || self.is_approved_for_all(owner, spender) + || self._get_approved_inner(token_id) == spender) } /// Checks if `spender` can operate on `token_id`, assuming the provided @@ -527,7 +532,7 @@ impl ERC721 { spender: Address, token_id: U256, ) -> Result<(), Error> { - if !self._is_authorized(owner, spender, token_id)? { + if !self._is_authorized(owner, spender, token_id) { return if owner == Address::ZERO { Err(ERC721NonexistentToken { token_id }.into()) } else { @@ -595,7 +600,7 @@ impl ERC721 { token_id: U256, auth: Address, ) -> Result { - let from = self._owner_of_inner(token_id)?; + let from = self._owner_of_inner(token_id); // Perform (optional) operator check if auth != Address::ZERO { @@ -672,6 +677,15 @@ impl ERC721 { /// * `data` - Additional data with no specified format, sent in call to /// `to`. /// + /// # Errors + /// + /// * If `token_id` already exist then [`Error::InvalidSender`] is returned. + /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is + /// returned. + /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its + /// interface id or returned with error then [`Error::InvalidReceiver`] is + /// returned. + /// /// # Requirements: /// /// * `tokenId` must not exist. @@ -877,7 +891,7 @@ impl ERC721 { // not be able to call approve if auth != Address::ZERO && owner != auth - && !self.is_approved_for_all(owner, auth)? + && !self.is_approved_for_all(owner, auth) { return Err(ERC721InvalidApprover { approver: auth }.into()); } @@ -941,7 +955,7 @@ impl ERC721 { /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. pub fn _require_owned(&self, token_id: U256) -> Result { - let owner = self._owner_of_inner(token_id)?; + let owner = self._owner_of_inner(token_id); if owner == Address::ZERO { return Err(ERC721NonexistentToken { token_id }.into()); } @@ -971,8 +985,8 @@ impl ERC721 { /// # Errors /// /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its - /// interface id or - /// returned with error then [`Error::InvalidReceiver`] is returned. + /// interface id or returned with error then [`Error::InvalidReceiver`] is + /// returned. pub fn _check_on_erc721_received( storage: &mut impl TopLevelStorage, operator: Address, @@ -1028,12 +1042,9 @@ impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { #[cfg(test)] mod tests { - use alloy_primitives::{address, Address, U256}; + use alloy_primitives::address; use once_cell::sync::Lazy; - use stylus_sdk::{ - msg, - storage::{StorageMap, StorageType}, - }; + use stylus_sdk::storage::StorageMap; use crate::erc721::*; #[allow(unused_imports)] From d0e347319675c59e77d74479327c7dfb6f072cec Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:59:43 +0400 Subject: [PATCH 21/68] fix dbg attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index b0dd94c6..8ca7d2a6 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -82,8 +82,8 @@ sol! { error ERC721InvalidApprover(address approver); /// Indicates a failure with the `operator` to be approved. Used in approvals. - #[derive(Debug)] /// * `operator` - Incorrect address of the operator. + #[derive(Debug)] error ERC721InvalidOperator(address operator); } From 9bcaa21c02d5e6224addd983d08411c34fe1d3b7 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Tue, 2 Apr 2024 19:00:04 +0400 Subject: [PATCH 22/68] fix doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 8ca7d2a6..dad9dc67 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -354,6 +354,7 @@ impl ERC721 { /// address clears previous approvals. /// /// # Arguments + /// /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. From be49bd49f033b81b63f6a5d315acbae0bb566d12 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 2 Apr 2024 21:33:19 +0400 Subject: [PATCH 23/68] use is_zero() instead of comparing to Address::ZERO Co-authored-by: Eric Nordelo --- contracts/src/erc20/mod.rs | 8 ++++---- contracts/src/erc721/mod.rs | 32 ++++++++++++++++---------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/contracts/src/erc20/mod.rs b/contracts/src/erc20/mod.rs index f8784d24..6e6e9467 100644 --- a/contracts/src/erc20/mod.rs +++ b/contracts/src/erc20/mod.rs @@ -142,7 +142,7 @@ impl ERC20 { value: U256, ) -> Result { let from = msg::sender(); - if to == Address::ZERO { + if to.is_zero() { return Err(Error::InvalidReceiver(ERC20InvalidReceiver { receiver: Address::ZERO, })); @@ -200,7 +200,7 @@ impl ERC20 { value: U256, ) -> Result { let owner = msg::sender(); - if spender == Address::ZERO { + if spender.is_zero() { return Err(Error::InvalidSpender(ERC20InvalidSpender { spender: Address::ZERO, })); @@ -246,12 +246,12 @@ impl ERC20 { to: Address, value: U256, ) -> Result { - if from == Address::ZERO { + if from.is_zero() { return Err(Error::InvalidSender(ERC20InvalidSender { sender: Address::ZERO, })); } - if to == Address::ZERO { + if to.is_zero() { return Err(Error::InvalidReceiver(ERC20InvalidReceiver { receiver: Address::ZERO, })); diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index dad9dc67..ff4c571e 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -149,7 +149,7 @@ impl ERC721 { /// * If owner address is `Address::ZERO`, then [`Error::InvalidOwner`] is /// returned. pub fn balance_of(&self, owner: Address) -> Result { - if owner == Address::ZERO { + if owner.is_zero() { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); } Ok(self.balances.get(owner)) @@ -326,7 +326,7 @@ impl ERC721 { to: Address, token_id: U256, ) -> Result<(), Error> { - if to == Address::ZERO { + if to.is_zero() { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() ); @@ -501,7 +501,7 @@ impl ERC721 { spender: Address, token_id: U256, ) -> bool { - spender != Address::ZERO + !spender.is_zero() && (owner == spender || self.is_approved_for_all(owner, spender) || self._get_approved_inner(token_id) == spender) @@ -534,7 +534,7 @@ impl ERC721 { token_id: U256, ) -> Result<(), Error> { if !self._is_authorized(owner, spender, token_id) { - return if owner == Address::ZERO { + return if owner.is_zero() { Err(ERC721NonexistentToken { token_id }.into()) } else { Err(ERC721InsufficientApproval { operator: spender, token_id } @@ -604,19 +604,19 @@ impl ERC721 { let from = self._owner_of_inner(token_id); // Perform (optional) operator check - if auth != Address::ZERO { + if !auth.is_zero() { self._check_authorized(from, auth, token_id)?; } // Execute the update - if from != Address::ZERO { + if !from.is_zero() { // Clear approval. No need to re-authorize or emit the Approval // event self._approve(Address::ZERO, token_id, Address::ZERO, false)?; self.balances.setter(from).sub_assign_unchecked(U256::from(1)); } - if to != Address::ZERO { + if !to.is_zero() { self.balances.setter(to).add_assign_unchecked(U256::from(1)); } @@ -653,14 +653,14 @@ impl ERC721 { /// /// Emits a [`Transfer`] event. pub fn _mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { - if to == Address::ZERO { + if to.is_zero() { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() ); } let previous_owner = self._update(to, token_id, Address::ZERO)?; - if previous_owner != Address::ZERO { + if !previous_owner.is_zero() { return Err(ERC721InvalidSender { sender: Address::ZERO }.into()); } Ok(()) @@ -738,7 +738,7 @@ impl ERC721 { pub fn _burn(&mut self, token_id: U256) -> Result<(), Error> { let previous_owner = self._update(Address::ZERO, token_id, Address::ZERO)?; - if previous_owner == Address::ZERO { + if previous_owner.is_zero() { Err(ERC721NonexistentToken { token_id }.into()) } else { Ok(()) @@ -779,14 +779,14 @@ impl ERC721 { to: Address, token_id: U256, ) -> Result<(), Error> { - if to == Address::ZERO { + if to.is_zero() { return Err( ERC721InvalidReceiver { receiver: Address::ZERO }.into() ); } let previous_owner = self._update(to, token_id, Address::ZERO)?; - if previous_owner == Address::ZERO { + if previous_owner.is_zero() { Err(ERC721NonexistentToken { token_id }.into()) } else if previous_owner != from { Err(ERC721IncorrectOwner { @@ -885,12 +885,12 @@ impl ERC721 { emit_event: bool, ) -> Result<(), Error> { // Avoid reading the owner unless necessary - if emit_event || auth != Address::ZERO { + if emit_event || !auth.is_zero() { let owner = self._require_owned(token_id)?; // We do not use _isAuthorized because single-token approvals should // not be able to call approve - if auth != Address::ZERO + if !auth.is_zero() && owner != auth && !self.is_approved_for_all(owner, auth) { @@ -933,7 +933,7 @@ impl ERC721 { operator: Address, approved: bool, ) -> Result<(), Error> { - if operator == Address::ZERO { + if operator.is_zero() { return Err(ERC721InvalidOperator { operator }.into()); } self.operator_approvals.setter(owner).setter(operator).set(approved); @@ -957,7 +957,7 @@ impl ERC721 { /// * `token_id` - Token id as a number. pub fn _require_owned(&self, token_id: U256) -> Result { let owner = self._owner_of_inner(token_id); - if owner == Address::ZERO { + if owner.is_zero() { return Err(ERC721NonexistentToken { token_id }.into()); } Ok(owner) From 81028f8fc83b12b4e37ce3a6087fecd5710bce8d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 2 Apr 2024 21:40:47 +0400 Subject: [PATCH 24/68] add underscore for storage fields --- contracts/src/erc721/mod.rs | 40 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index ff4c571e..005fcbde 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -125,13 +125,13 @@ sol_interface! { sol_storage! { pub struct ERC721 { - mapping(uint256 => address) owners; + mapping(uint256 => address) _owners; - mapping(address => uint256) balances; + mapping(address => uint256) _balances; - mapping(uint256 => address) token_approvals; + mapping(uint256 => address) _token_approvals; - mapping(address => mapping(address => bool)) operator_approvals; + mapping(address => mapping(address => bool)) _operator_approvals; } } @@ -152,7 +152,7 @@ impl ERC721 { if owner.is_zero() { return Err(ERC721InvalidOwner { owner: Address::ZERO }.into()); } - Ok(self.balances.get(owner)) + Ok(self._balances.get(owner)) } /// Returns the owner of the `token_id` token. @@ -449,7 +449,7 @@ impl ERC721 { owner: Address, operator: Address, ) -> bool { - self.operator_approvals.get(owner).get(operator) + self._operator_approvals.get(owner).get(operator) } } @@ -469,7 +469,7 @@ impl ERC721 { /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. pub fn _owner_of_inner(&self, token_id: U256) -> Address { - self.owners.get(token_id) + self._owners.get(token_id) } /// Returns the approved address for `token_id`. Returns 0 if `token_id` is @@ -480,7 +480,7 @@ impl ERC721 { /// * `&self` - Read access to the contract's state. /// * `token_id` - Token id as a number. pub fn _get_approved_inner(&self, token_id: U256) -> Address { - self.token_approvals.get(token_id) + self._token_approvals.get(token_id) } /// Returns whether `spender` is allowed to manage `owner`'s tokens, or @@ -563,7 +563,7 @@ impl ERC721 { /// * `account` - Account to increase balance. /// * `value` - The number of tokens to increase balance. pub fn _increase_balance(&mut self, account: Address, value: U256) { - self.balances.setter(account).add_assign_unchecked(value); + self._balances.setter(account).add_assign_unchecked(value); } /// Transfers `token_id` from its current owner to `to`, or alternatively @@ -613,14 +613,14 @@ impl ERC721 { // Clear approval. No need to re-authorize or emit the Approval // event self._approve(Address::ZERO, token_id, Address::ZERO, false)?; - self.balances.setter(from).sub_assign_unchecked(U256::from(1)); + self._balances.setter(from).sub_assign_unchecked(U256::from(1)); } if !to.is_zero() { - self.balances.setter(to).add_assign_unchecked(U256::from(1)); + self._balances.setter(to).add_assign_unchecked(U256::from(1)); } - self.owners.setter(token_id).set(to); + self._owners.setter(token_id).set(to); evm::log(Transfer { from, to, token_id }); @@ -902,7 +902,7 @@ impl ERC721 { } } - self.token_approvals.setter(token_id).set(to); + self._token_approvals.setter(token_id).set(to); Ok(()) } @@ -936,7 +936,7 @@ impl ERC721 { if operator.is_zero() { return Err(ERC721InvalidOperator { operator }.into()); } - self.operator_approvals.setter(owner).setter(operator).set(approved); + self._operator_approvals.setter(owner).setter(operator).set(approved); evm::log(ApprovalForAll { owner, operator, approved }); Ok(()) } @@ -1061,12 +1061,12 @@ mod tests { let root = U256::ZERO; ERC721 { - owners: unsafe { StorageMap::new(root, 0) }, - balances: unsafe { StorageMap::new(root + U256::from(32), 0) }, - token_approvals: unsafe { + _owners: unsafe { StorageMap::new(root, 0) }, + _balances: unsafe { StorageMap::new(root + U256::from(32), 0) }, + _token_approvals: unsafe { StorageMap::new(root + U256::from(64), 0) }, - operator_approvals: unsafe { + _operator_approvals: unsafe { StorageMap::new(root + U256::from(96), 0) }, } @@ -1153,7 +1153,7 @@ mod tests { token .approve(BOB, token_id) .expect("approve bob for operations on token"); - assert_eq!(token.token_approvals.get(token_id), BOB); + assert_eq!(token._token_approvals.get(token_id), BOB); }); } @@ -1162,7 +1162,7 @@ mod tests { test_utils::with_storage::(|token| { let token_id = random_token_id(); token._mint(BOB, token_id).expect("mint token"); - token.token_approvals.setter(token_id).set(*ALICE); + token._token_approvals.setter(token_id).set(*ALICE); token .transfer_from(BOB, *ALICE, token_id) .expect("transfer Bob's token to Alice"); From 1767a07ae673a4d1a5b30d8e0dc9c932f5b20e23 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 3 Apr 2024 14:09:21 +0400 Subject: [PATCH 25/68] remove mention of erc-20 --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 005fcbde..b3ef732c 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -36,7 +36,7 @@ sol! { sol! { /// Indicates that an address can't be an owner. - /// For example, `address(0)` is a forbidden owner in ERC-20. Used in balance queries. + /// For example, `address(0)` is a forbidden owner in ERC-721. Used in balance queries. /// /// * `owner` - Incorrect address of the owner. #[derive(Debug)] From 2d248a4010af0b114f2e758cab17b08a7930ae12 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 3 Apr 2024 14:10:36 +0400 Subject: [PATCH 26/68] remove #[macro_use] --- contracts/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index d4389cb7..b978eba9 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -2,7 +2,6 @@ #![warn(missing_docs, unreachable_pub, rust_2021_compatibility)] #![warn(clippy::all, clippy::pedantic)] #![cfg_attr(not(test), no_std, no_main)] -#[macro_use] extern crate alloc; #[global_allocator] From f1262fb440af968e3752f04180edb4de2fa7f932 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 3 Apr 2024 21:13:24 +0400 Subject: [PATCH 27/68] add unsafe TopLevelStorage implementation --- contracts/src/erc721/mod.rs | 55 ++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index b3ef732c..4e78490d 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -135,6 +135,11 @@ sol_storage! { } } +// NOTE: implementation of `TopLevelStorage` to be able refer to &mut self when +// calling other contracts and not `&mut (impl TopLevelStorage + +// BorrowMut)`. Should be fixed in future by stylus team. +unsafe impl TopLevelStorage for ERC721 {} + #[external] impl ERC721 { /// Returns the number of tokens in `owner` 's account. @@ -179,7 +184,7 @@ impl ERC721 { /// /// # Arguments /// - /// * `storage` - Write access to the contract's state. + /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. @@ -213,25 +218,19 @@ impl ERC721 { /// /// Emits a [`Transfer`] event. pub fn safe_transfer_from( - storage: &mut (impl TopLevelStorage + BorrowMut), + &mut self, from: Address, to: Address, token_id: U256, ) -> Result<(), Error> { - Self::safe_transfer_from_with_data( - storage, - from, - to, - token_id, - vec![].into(), - ) + self.safe_transfer_from_with_data(from, to, token_id, vec![].into()) } /// Safely transfers `token_id` token from `from` to `to`. /// /// # Arguments /// - /// * `storage` - Write access to the contract's state. + /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. @@ -267,21 +266,14 @@ impl ERC721 { /// Emits a [`Transfer`] event. #[selector(name = "safeTransferFrom")] pub fn safe_transfer_from_with_data( - storage: &mut (impl TopLevelStorage + BorrowMut), + &mut self, from: Address, to: Address, token_id: U256, data: Bytes, ) -> Result<(), Error> { - storage.borrow_mut().transfer_from(from, to, token_id)?; - Self::_check_on_erc721_received( - storage, - msg::sender(), - from, - to, - token_id, - data, - ) + self.transfer_from(from, to, token_id)?; + self._check_on_erc721_received(msg::sender(), from, to, token_id, data) } /// Transfers `token_id` token from `from` to `to`. @@ -672,7 +664,7 @@ impl ERC721 { /// /// # Arguments /// - /// * `storage` - Write access to the contract's state. + /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. /// * `data` - Additional data with no specified format, sent in call to @@ -698,14 +690,13 @@ impl ERC721 { /// /// Emits a [`Transfer`] event. pub fn _safe_mint( - storage: &mut (impl TopLevelStorage + BorrowMut), + &mut self, to: Address, token_id: U256, data: Bytes, ) -> Result<(), Error> { - storage.borrow_mut()._mint(to, token_id)?; - Self::_check_on_erc721_received( - storage, + self._mint(to, token_id)?; + self._check_on_erc721_received( msg::sender(), Address::ZERO, to, @@ -811,7 +802,7 @@ impl ERC721 { /// /// # Arguments /// - /// * `storage` - Write access to the contract's state. + /// * `&mut self` - Write access to the contract's state. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. @@ -839,15 +830,15 @@ impl ERC721 { /// # Events /// Emits a [`Transfer`] event. pub fn _safe_transfer( - storage: &mut (impl TopLevelStorage + BorrowMut), + &mut self, from: Address, to: Address, token_id: U256, data: Bytes, ) -> Result<(), Error> { - storage.borrow_mut()._transfer(from, to, token_id)?; + self._transfer(from, to, token_id)?; Self::_check_on_erc721_received( - storage, + self, msg::sender(), from, to, @@ -975,7 +966,7 @@ impl ERC721 { /// /// # Arguments /// - /// * `storage` - Write access to the contract's state. + /// * `&mut self` - Write access to the contract's state. /// * `operator` - Account to add to the set of authorized operators. /// * `from` - Account of the sender. /// * `to` - Account of the recipient. @@ -989,7 +980,7 @@ impl ERC721 { /// interface id or returned with error then [`Error::InvalidReceiver`] is /// returned. pub fn _check_on_erc721_received( - storage: &mut impl TopLevelStorage, + &mut self, operator: Address, from: Address, to: Address, @@ -999,7 +990,7 @@ impl ERC721 { // TODO: think how we can retrieve INTERFACE_ID at compile time const IERC721RECEIVER_INTERFACE_ID: u32 = 0x150b7a02; if to.has_code() { - let call = Call::new_in(storage); + let call = Call::new_in(self); return match IERC721Receiver::new(to).on_erc_721_received( call, operator, From 2c31b1298a2fa8d8ef4e0bc06dbfa41257609bad Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 14:34:19 +0400 Subject: [PATCH 28/68] use grip::test for erc721 --- Cargo.lock | 20 ++-- contracts/src/erc721/mod.rs | 189 ++++++++++++++++-------------------- 2 files changed, 97 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c13c178..14203f89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,9 +71,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" @@ -127,7 +127,11 @@ version = "0.1.0" dependencies = [ "alloy-primitives 0.3.3", "alloy-sol-types", + "derive_more", + "grip", "mini-alloc", + "once_cell", + "rand", "stylus-proc", "stylus-sdk", ] @@ -281,9 +285,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "keccak" @@ -320,9 +324,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memory_units" @@ -460,9 +464,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "ruint" diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 4e78490d..543ca239 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1039,8 +1039,6 @@ mod tests { use stylus_sdk::storage::StorageMap; use crate::erc721::*; - #[allow(unused_imports)] - use crate::test_utils; // NOTE: Alice is always the sender of the message static ALICE: Lazy
= Lazy::new(msg::sender); @@ -1064,123 +1062,106 @@ mod tests { } } - #[test] - fn mint_nft_and_check_balance() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - token._mint(*ALICE, token_id).expect("mint token"); - let owner = token.owner_of(token_id).expect("owner address"); - assert_eq!(owner, *ALICE); - - let balance = token.balance_of(*ALICE).expect("balance of owner"); - let one = U256::from(1); - assert!(balance >= one); - }); + #[grip::test] + fn mint_nft_and_check_balance(contract: ERC721) { + let token_id = random_token_id(); + contract._mint(*ALICE, token_id).expect("mint token"); + let owner = contract.owner_of(token_id).expect("owner address"); + assert_eq!(owner, *ALICE); + + let balance = contract.balance_of(*ALICE).expect("balance of owner"); + let one = U256::from(1); + assert!(balance >= one); } - #[test] - fn error_mint_second_nft() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - token._mint(*ALICE, token_id).expect("mint token first time"); - match token._mint(*ALICE, token_id) { - Ok(_) => { - panic!( - "Second mint of the same token should not be possible" - ) + #[grip::test] + fn error_mint_second_nft(contract: ERC721) { + let token_id = random_token_id(); + contract._mint(*ALICE, token_id).expect("mint token first time"); + match contract._mint(*ALICE, token_id) { + Ok(_) => { + panic!("Second mint of the same token should not be possible") + } + Err(e) => match e { + Error::InvalidSender(ERC721InvalidSender { + sender: Address::ZERO, + }) => {} + e => { + panic!("Invalid error - {e:?}"); } - Err(e) => match e { - Error::InvalidSender(ERC721InvalidSender { - sender: Address::ZERO, - }) => {} - e => { - panic!("Invalid error - {e:?}"); - } - }, - }; - }); + }, + }; } - #[test] - fn transfer_nft() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - token._mint(*ALICE, token_id).expect("mint nft to alice"); - token - .transfer_from(*ALICE, BOB, token_id) - .expect("transfer from alice to bob"); - let owner = token.owner_of(token_id).expect("new owner of nft"); - assert_eq!(owner, BOB); - }); + #[grip::test] + fn transfer_nft(contract: ERC721) { + let token_id = random_token_id(); + contract._mint(*ALICE, token_id).expect("mint nft to alice"); + contract + .transfer_from(*ALICE, BOB, token_id) + .expect("transfer from alice to bob"); + let owner = contract.owner_of(token_id).expect("new owner of nft"); + assert_eq!(owner, BOB); } - #[test] - fn error_transfer_nonexistent_nft() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - match token.transfer_from(*ALICE, BOB, token_id) { - Ok(_) => { - panic!( - "Transfer of a non existent nft should not be possible" - ) - } - Err(e) => match e { - Error::NonexistentToken(ERC721NonexistentToken { - token_id: t_id, - }) if t_id == token_id => {} - e => { - panic!("Invalid error - {e:?}"); - } - }, + #[grip::test] + fn error_transfer_nonexistent_nft(contract: ERC721) { + let token_id = random_token_id(); + match contract.transfer_from(*ALICE, BOB, token_id) { + Ok(_) => { + panic!("Transfer of a non existent nft should not be possible") } - }); + Err(e) => match e { + Error::NonexistentToken(ERC721NonexistentToken { + token_id: t_id, + }) if t_id == token_id => {} + e => { + panic!("Invalid error - {e:?}"); + } + }, + } } - #[test] - fn approve_nft_transfer() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - token._mint(*ALICE, token_id).expect("mint token"); - token - .approve(BOB, token_id) - .expect("approve bob for operations on token"); - assert_eq!(token._token_approvals.get(token_id), BOB); - }); + #[grip::test] + fn approve_nft_transfer(contract: ERC721) { + let token_id = random_token_id(); + contract._mint(*ALICE, token_id).expect("mint token"); + contract + .approve(BOB, token_id) + .expect("approve bob for operations on token"); + assert_eq!(contract._token_approvals.get(token_id), BOB); } - #[test] - fn transfer_approved_nft() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - token._mint(BOB, token_id).expect("mint token"); - token._token_approvals.setter(token_id).set(*ALICE); - token - .transfer_from(BOB, *ALICE, token_id) - .expect("transfer Bob's token to Alice"); - let owner = token.owner_of(token_id).expect("owner of token"); - assert_eq!(owner, *ALICE); - }); + #[grip::test] + fn transfer_approved_nft(contract: ERC721) { + let token_id = random_token_id(); + contract._mint(BOB, token_id).expect("mint token"); + contract._token_approvals.setter(token_id).set(*ALICE); + contract + .transfer_from(BOB, *ALICE, token_id) + .expect("transfer Bob's token to Alice"); + let owner = contract.owner_of(token_id).expect("owner of token"); + assert_eq!(owner, *ALICE); } - #[test] - fn error_not_approved_nft_transfer() { - test_utils::with_storage::(|token| { - let token_id = random_token_id(); - token._mint(BOB, token_id).expect("mint token"); - match token.transfer_from(BOB, *ALICE, token_id) { - Ok(_) => { - panic!("Transfer of not approved token should not happen"); + #[grip::test] + fn error_not_approved_nft_transfer(contract: ERC721) { + let token_id = random_token_id(); + contract._mint(BOB, token_id).expect("mint token"); + match contract.transfer_from(BOB, *ALICE, token_id) { + Ok(_) => { + panic!("Transfer of not approved token should not happen"); + } + Err(e) => match e { + Error::InsufficientApproval(ERC721InsufficientApproval { + operator, + token_id: t_id, + }) if operator == *ALICE && t_id == token_id => {} + e => { + panic!("Invalid error - {e:?}"); } - Err(e) => match e { - Error::InsufficientApproval( - ERC721InsufficientApproval { operator, token_id: t_id }, - ) if operator == *ALICE && t_id == token_id => {} - e => { - panic!("Invalid error - {e:?}"); - } - }, - }; - }); + }, + }; } // TODO: add set_approval_for_all test From 33721da1755f2100f9768f86081347b3a38d09e4 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 15:01:24 +0400 Subject: [PATCH 29/68] fix U256 arg at _increase_balance --- contracts/src/erc721/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 543ca239..38457623 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -3,7 +3,7 @@ use core::borrow::BorrowMut; use derive_more::From; use stylus_sdk::{ abi::Bytes, - alloy_primitives::{Address, U256}, + alloy_primitives::{Address, U128, U256}, alloy_sol_types::sol, call::Call, evm, msg, @@ -554,8 +554,10 @@ impl ERC721 { /// * `&mut self` - Write access to the contract's state. /// * `account` - Account to increase balance. /// * `value` - The number of tokens to increase balance. - pub fn _increase_balance(&mut self, account: Address, value: U256) { - self._balances.setter(account).add_assign_unchecked(value); + // TODO: right now this function is pointless since it is not used. But once + // we will be able to override internal functions it will make a difference + pub fn _increase_balance(&mut self, account: Address, value: U128) { + self._balances.setter(account).add_assign_unchecked(U256::from(value)); } /// Transfers `token_id` from its current owner to `to`, or alternatively From cc1269dcd52b7f93907483b34287776454621a11 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 15:33:36 +0400 Subject: [PATCH 30/68] fix grammar --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 38457623..776764b0 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -584,7 +584,7 @@ impl ERC721 { /// [`Error::NonexistentToken`] is returned. /// * If `auth` is not `Address::ZERO` and `auth` does not have a right to /// approve this token - /// then [`Error::InsufficientApproval`] ßis returned. + /// then [`Error::InsufficientApproval`] is returned. /// /// # Events /// From 633ad3d0836ea8759900fed903814f175b0b0460 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:12:55 +0400 Subject: [PATCH 31/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 776764b0..64a653c8 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -915,7 +915,7 @@ impl ERC721 { /// /// # Requirements: /// - /// * operator can't be the address zero. + /// * `operator` can't be the address zero. /// /// # Events /// From 79456e3c1622dc23ac372c0d8561a5d6b159c763 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:15:18 +0400 Subject: [PATCH 32/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 64a653c8..dcf795e8 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -12,10 +12,10 @@ use stylus_sdk::{ }; sol! { - /// Emitted when `tokenId` token is transferred from `from` to `to`. + /// Emitted when the `tokenId` token is transferred from `from` to `to`. /// - /// * `from` - Address from which token will be transferred. - /// * `to` - Address where token will be transferred. + /// * `from` - Address from which the token will be transferred. + /// * `to` - Address where the token will be transferred to. /// * `token_id` - Token id as a number. event Transfer(address indexed from, address indexed to, uint256 indexed token_id); From 894be05a8612b5bf8910baf7d8e40755951ce4ab Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:15:42 +0400 Subject: [PATCH 33/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index dcf795e8..a8231e78 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -906,7 +906,7 @@ impl ERC721 { /// * `&mut self` - Write access to the contract's state. /// * `owner` - Account the token's owner. /// * `operator` - Account to add to the set of authorized operators. - /// * `approved` - Flag that set approval or disapproval for the operator. + /// * `approved` - Whether or not permission will be granted. If true, this means `operator` will be allowed to manage `owner`'s assets. /// /// # Errors /// From 72bbce6e50c2833ea2fdd21cda2331ab1bc5950a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:15:58 +0400 Subject: [PATCH 34/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index a8231e78..79f481f3 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -899,7 +899,7 @@ impl ERC721 { Ok(()) } - /// Approve `operator` to operate on all of `owner` tokens. + /// Approve `operator` to operate on all of `owner`'s tokens. /// /// # Arguments /// From d1006f461ab9d733d88420469a79d2fbaa7efd22 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:16:16 +0400 Subject: [PATCH 35/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 79f481f3..eea6c673 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -38,7 +38,7 @@ sol! { /// Indicates that an address can't be an owner. /// For example, `address(0)` is a forbidden owner in ERC-721. Used in balance queries. /// - /// * `owner` - Incorrect address of the owner. + /// * `owner` - The address deemed to be an invalid owner. #[derive(Debug)] error ERC721InvalidOwner(address owner); From 984cc1d1fcdf0103074916ed786dc0ee6e43f8d1 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:16:31 +0400 Subject: [PATCH 36/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index eea6c673..33e5525a 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -50,7 +50,7 @@ sol! { /// Indicates an error related to the ownership over a particular token. Used in transfers. /// - /// * `sender` - Address whose token being transferred. + /// * `sender` - Address whose tokens are being transferred. /// * `token_id` - Token id as a number. /// * `owner` - Address of the owner of the token. #[derive(Debug)] From 4cbecea1b5efde3e153937a2d4047d38aeb98382 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:17:15 +0400 Subject: [PATCH 37/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 33e5525a..9d331882 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -30,7 +30,7 @@ sol! { /// /// * `owner` - Address of the owner of the token. /// * `operator` - Address of an operator that will manage operations on the token. - /// * `approved` - Approved or not permission been granted. + /// * `approved` - Whether or not permission has been granted. If true, this means `operator` will be allowed to manage `owner`'s assets. event ApprovalForAll(address indexed owner, address indexed operator, bool approved); } From d41d34f6b4fd422d5c46c525691b60dc7bcd22c1 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 17:14:49 +0400 Subject: [PATCH 38/68] ++ --- contracts/src/erc721/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 9d331882..f6695acb 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,5 +1,3 @@ -use core::borrow::BorrowMut; - use derive_more::From; use stylus_sdk::{ abi::Bytes, From df94d1075a665f2e6db8649426c54bf2ef26813d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 17:45:02 +0400 Subject: [PATCH 39/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index f6695acb..1bce9905 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -904,7 +904,7 @@ impl ERC721 { /// * `&mut self` - Write access to the contract's state. /// * `owner` - Account the token's owner. /// * `operator` - Account to add to the set of authorized operators. - /// * `approved` - Whether or not permission will be granted. If true, this means `operator` will be allowed to manage `owner`'s assets. + /// * `approved` - Whether permission will be granted. If true, this means `operator` will be allowed to manage `owner`'s assets. /// /// # Errors /// From ee9dbe82d0dc5a31a48d2f5c7af249f763acb438 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 17:56:20 +0400 Subject: [PATCH 40/68] ++ --- contracts/src/erc721/mod.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 1bce9905..73925993 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,7 +1,7 @@ +use alloy_primitives::{fixed_bytes, Address, FixedBytes, U128, U256}; use derive_more::From; use stylus_sdk::{ abi::Bytes, - alloy_primitives::{Address, U128, U256}, alloy_sol_types::sol, call::Call, evm, msg, @@ -904,7 +904,8 @@ impl ERC721 { /// * `&mut self` - Write access to the contract's state. /// * `owner` - Account the token's owner. /// * `operator` - Account to add to the set of authorized operators. - /// * `approved` - Whether permission will be granted. If true, this means `operator` will be allowed to manage `owner`'s assets. + /// * `approved` - Whether permission will be granted. If true, this means + /// `operator` will be allowed to manage `owner`'s assets. /// /// # Errors /// @@ -987,8 +988,9 @@ impl ERC721 { token_id: U256, data: Bytes, ) -> Result<(), Error> { - // TODO: think how we can retrieve INTERFACE_ID at compile time - const IERC721RECEIVER_INTERFACE_ID: u32 = 0x150b7a02; + const IERC721RECEIVER_INTERFACE_ID: FixedBytes<4> = + fixed_bytes!("150b7a02"); + if to.has_code() { let call = Call::new_in(self); return match IERC721Receiver::new(to).on_erc_721_received( @@ -999,8 +1001,7 @@ impl ERC721 { data.to_vec(), ) { Ok(result) => { - let received_interface_id = u32::from_be_bytes(result.0); - if received_interface_id != IERC721RECEIVER_INTERFACE_ID { + if result != IERC721RECEIVER_INTERFACE_ID { Err(ERC721InvalidReceiver { receiver: to }.into()) } else { Ok(()) From eff68a984ad9ab4a7dccb19f908daa1d8cbd8ea3 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 4 Apr 2024 19:03:35 +0400 Subject: [PATCH 41/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 73925993..463258fa 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -100,7 +100,6 @@ pub enum Error { InvalidOperator(ERC721InvalidOperator), } -// TODO: better to keep it at separate module sol_interface! { /// ERC-721 token receiver interface. /// Interface for any contract that wants to support safeTransfers @@ -221,6 +220,7 @@ impl ERC721 { to: Address, token_id: U256, ) -> Result<(), Error> { + // TODO: use bytes! macro later self.safe_transfer_from_with_data(from, to, token_id, vec![].into()) } From d59e0e74499250db653663a84970116b537dbc5a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:55:53 +0400 Subject: [PATCH 42/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 463258fa..ee51ec7a 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1095,7 +1095,7 @@ mod tests { } #[grip::test] - fn transfer_nft(contract: ERC721) { + fn transfers(contract: ERC721) { let token_id = random_token_id(); contract._mint(*ALICE, token_id).expect("mint nft to alice"); contract From b9b5c588a5e2f1f257b8f1c4f53c14fd9ab7c68c Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:56:14 +0400 Subject: [PATCH 43/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index ee51ec7a..19f113ec 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1106,7 +1106,7 @@ mod tests { } #[grip::test] - fn error_transfer_nonexistent_nft(contract: ERC721) { + fn errors_when_transfers_nonexistent_nft(contract: ERC721) { let token_id = random_token_id(); match contract.transfer_from(*ALICE, BOB, token_id) { Ok(_) => { From bd53b37f2fd1e5ebaa2697bc4e323fab40613edb Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:21:35 +0400 Subject: [PATCH 44/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 19f113ec..67f82f42 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1064,7 +1064,7 @@ mod tests { } #[grip::test] - fn mint_nft_and_check_balance(contract: ERC721) { + fn mints(contract: ERC721) { let token_id = random_token_id(); contract._mint(*ALICE, token_id).expect("mint token"); let owner = contract.owner_of(token_id).expect("owner address"); From 3060c463aa4e305428bcdf6b35a7c314c3362fb5 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:27:17 +0400 Subject: [PATCH 45/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 67f82f42..7069cbb1 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1076,22 +1076,16 @@ mod tests { } #[grip::test] - fn error_mint_second_nft(contract: ERC721) { + fn errors_when_reusing_token_id(contract: ERC721) { let token_id = random_token_id(); - contract._mint(*ALICE, token_id).expect("mint token first time"); - match contract._mint(*ALICE, token_id) { - Ok(_) => { - panic!("Second mint of the same token should not be possible") - } - Err(e) => match e { - Error::InvalidSender(ERC721InvalidSender { - sender: Address::ZERO, - }) => {} - e => { - panic!("Invalid error - {e:?}"); - } - }, - }; + contract._mint(*ALICE, token_id).expect("should mint the token a first time"); + let actual = token + ._mint(*ALICE, token_id) + .expect_err("should not mint a token id twice"); + let expected = Error::InvalidSender(ERC721InvalidSender { + sender: Address::ZERO, + }); + assert!(matches!(actual, expected)); } #[grip::test] From 02575a543f45d405e1ce1f76dd9748af1e227d3d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:27:52 +0400 Subject: [PATCH 46/68] ++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander González --- contracts/src/erc721/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 7069cbb1..95c20a93 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1066,11 +1066,11 @@ mod tests { #[grip::test] fn mints(contract: ERC721) { let token_id = random_token_id(); - contract._mint(*ALICE, token_id).expect("mint token"); - let owner = contract.owner_of(token_id).expect("owner address"); + contract._mint(*ALICE, token_id).expect("should mint an nft for Alice"); + let owner = contract.owner_of(token_id).expect("should get the owner of the nft"); assert_eq!(owner, *ALICE); - let balance = contract.balance_of(*ALICE).expect("balance of owner"); + let balance = contract.balance_of(*ALICE).expect("should get the balance of Alice"); let one = U256::from(1); assert!(balance >= one); } From e921f4d3d4f36573089bfb3b61e2058ff05e3146 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 14:56:27 +0400 Subject: [PATCH 47/68] ++ --- contracts/src/erc721/mod.rs | 97 +++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 95c20a93..8a190ab2 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1063,14 +1063,25 @@ mod tests { } } + fn random_token_id() -> U256 { + let num: u32 = rand::random(); + num.try_into().expect("conversion to U256") + } + #[grip::test] fn mints(contract: ERC721) { let token_id = random_token_id(); - contract._mint(*ALICE, token_id).expect("should mint an nft for Alice"); - let owner = contract.owner_of(token_id).expect("should get the owner of the nft"); + contract + ._mint(*ALICE, token_id) + .expect("should mint a token for Alice"); + let owner = contract + .owner_of(token_id) + .expect("should get the owner of the token"); assert_eq!(owner, *ALICE); - let balance = contract.balance_of(*ALICE).expect("should get the balance of Alice"); + let balance = contract + .balance_of(*ALICE) + .expect("should get the balance of Alice"); let one = U256::from(1); assert!(balance >= one); } @@ -1078,47 +1089,46 @@ mod tests { #[grip::test] fn errors_when_reusing_token_id(contract: ERC721) { let token_id = random_token_id(); - contract._mint(*ALICE, token_id).expect("should mint the token a first time"); - let actual = token + contract + ._mint(*ALICE, token_id) + .expect("should mint the token a first time"); + let err = contract ._mint(*ALICE, token_id) .expect_err("should not mint a token id twice"); - let expected = Error::InvalidSender(ERC721InvalidSender { - sender: Address::ZERO, - }); - assert!(matches!(actual, expected)); + assert!(matches!( + err, + Error::InvalidSender(ERC721InvalidSender { sender: Address::ZERO }) + )); } #[grip::test] fn transfers(contract: ERC721) { let token_id = random_token_id(); - contract._mint(*ALICE, token_id).expect("mint nft to alice"); + contract._mint(*ALICE, token_id).expect("should mint a token to Alice"); contract .transfer_from(*ALICE, BOB, token_id) - .expect("transfer from alice to bob"); - let owner = contract.owner_of(token_id).expect("new owner of nft"); + .expect("transfer from Alice to Bob"); + let owner = + contract.owner_of(token_id).expect("new owner of the token"); assert_eq!(owner, BOB); } #[grip::test] - fn errors_when_transfers_nonexistent_nft(contract: ERC721) { + fn errors_when_transfers_nonexistent_token(contract: ERC721) { let token_id = random_token_id(); - match contract.transfer_from(*ALICE, BOB, token_id) { - Ok(_) => { - panic!("Transfer of a non existent nft should not be possible") - } - Err(e) => match e { - Error::NonexistentToken(ERC721NonexistentToken { + let err = contract.transfer_from(*ALICE, BOB, token_id).expect_err( + "transfer of a non existent token should not be possible", + ); + assert!(matches!( + err, + Error::NonexistentToken(ERC721NonexistentToken { token_id: t_id, - }) if t_id == token_id => {} - e => { - panic!("Invalid error - {e:?}"); - } - }, - } + }) if t_id == token_id + )); } #[grip::test] - fn approve_nft_transfer(contract: ERC721) { + fn approve_token_transfer(contract: ERC721) { let token_id = random_token_id(); contract._mint(*ALICE, token_id).expect("mint token"); contract @@ -1128,43 +1138,34 @@ mod tests { } #[grip::test] - fn transfer_approved_nft(contract: ERC721) { + fn transfer_approved_token(contract: ERC721) { let token_id = random_token_id(); - contract._mint(BOB, token_id).expect("mint token"); + contract._mint(BOB, token_id).expect("mint token to Bob"); contract._token_approvals.setter(token_id).set(*ALICE); contract .transfer_from(BOB, *ALICE, token_id) .expect("transfer Bob's token to Alice"); - let owner = contract.owner_of(token_id).expect("owner of token"); + let owner = contract.owner_of(token_id).expect("owner of the token"); assert_eq!(owner, *ALICE); } #[grip::test] - fn error_not_approved_nft_transfer(contract: ERC721) { + fn error_when_not_approved_token_transfer(contract: ERC721) { let token_id = random_token_id(); - contract._mint(BOB, token_id).expect("mint token"); - match contract.transfer_from(BOB, *ALICE, token_id) { - Ok(_) => { - panic!("Transfer of not approved token should not happen"); - } - Err(e) => match e { - Error::InsufficientApproval(ERC721InsufficientApproval { + contract._mint(BOB, token_id).expect("mint token to Bob"); + let err = contract + .transfer_from(BOB, *ALICE, token_id) + .expect_err("transfer of not approved token should not happen"); + assert!(matches!( + err, + Error::InsufficientApproval(ERC721InsufficientApproval { operator, token_id: t_id, - }) if operator == *ALICE && t_id == token_id => {} - e => { - panic!("Invalid error - {e:?}"); - } - }, - }; + }) if operator == *ALICE && t_id == token_id + )); } // TODO: add set_approval_for_all test // TODO: add mock test for on_erc721_received - - fn random_token_id() -> U256 { - let num: u32 = rand::random(); - num.try_into().expect("conversion to U256") - } } From 12cfdaa406b4ff2b1a0658f4320f411997763307 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 15:21:13 +0400 Subject: [PATCH 48/68] ++ --- contracts/src/erc721/mod.rs | 43 ++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 8a190ab2..006033f0 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1069,29 +1069,23 @@ mod tests { } #[grip::test] - fn mints(contract: ERC721) { + fn mint(contract: ERC721) { let token_id = random_token_id(); - contract - ._mint(*ALICE, token_id) - .expect("should mint a token for Alice"); - let owner = contract - .owner_of(token_id) - .expect("should get the owner of the token"); + contract._mint(*ALICE, token_id).expect("mint a token for Alice"); + let owner = + contract.owner_of(token_id).expect("get the owner of the token"); assert_eq!(owner, *ALICE); - let balance = contract - .balance_of(*ALICE) - .expect("should get the balance of Alice"); + let balance = + contract.balance_of(*ALICE).expect("get the balance of Alice"); let one = U256::from(1); assert!(balance >= one); } #[grip::test] - fn errors_when_reusing_token_id(contract: ERC721) { + fn error_when_reusing_token_id(contract: ERC721) { let token_id = random_token_id(); - contract - ._mint(*ALICE, token_id) - .expect("should mint the token a first time"); + contract._mint(*ALICE, token_id).expect("mint the token a first time"); let err = contract ._mint(*ALICE, token_id) .expect_err("should not mint a token id twice"); @@ -1102,23 +1096,23 @@ mod tests { } #[grip::test] - fn transfers(contract: ERC721) { + fn transfer(contract: ERC721) { let token_id = random_token_id(); - contract._mint(*ALICE, token_id).expect("should mint a token to Alice"); + contract._mint(*ALICE, token_id).expect("mint a token to Alice"); contract .transfer_from(*ALICE, BOB, token_id) .expect("transfer from Alice to Bob"); let owner = - contract.owner_of(token_id).expect("new owner of the token"); + contract.owner_of(token_id).expect("get the owner of the token"); assert_eq!(owner, BOB); } #[grip::test] - fn errors_when_transfers_nonexistent_token(contract: ERC721) { + fn error_when_transfers_nonexistent_token(contract: ERC721) { let token_id = random_token_id(); - let err = contract.transfer_from(*ALICE, BOB, token_id).expect_err( - "transfer of a non existent token should not be possible", - ); + let err = contract + .transfer_from(*ALICE, BOB, token_id) + .expect_err("should not transfer a non existent token"); assert!(matches!( err, Error::NonexistentToken(ERC721NonexistentToken { @@ -1145,17 +1139,18 @@ mod tests { contract .transfer_from(BOB, *ALICE, token_id) .expect("transfer Bob's token to Alice"); - let owner = contract.owner_of(token_id).expect("owner of the token"); + let owner = + contract.owner_of(token_id).expect("get the owner of the token"); assert_eq!(owner, *ALICE); } #[grip::test] - fn error_when_not_approved_token_transfer(contract: ERC721) { + fn error_when_transfer_unapproved_token(contract: ERC721) { let token_id = random_token_id(); contract._mint(BOB, token_id).expect("mint token to Bob"); let err = contract .transfer_from(BOB, *ALICE, token_id) - .expect_err("transfer of not approved token should not happen"); + .expect_err("should not transfer unapproved token"); assert!(matches!( err, Error::InsufficientApproval(ERC721InsufficientApproval { From fa433744a6ef2125e3d9182bd915a542849b43ce Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 16:31:13 +0400 Subject: [PATCH 49/68] ++ --- contracts/src/erc721/mod.rs | 28 +++------------------------- contracts/src/lib.rs | 1 + contracts/src/utils.rs | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 contracts/src/utils.rs diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 006033f0..9f845229 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1,14 +1,11 @@ use alloy_primitives::{fixed_bytes, Address, FixedBytes, U128, U256}; use derive_more::From; use stylus_sdk::{ - abi::Bytes, - alloy_sol_types::sol, - call::Call, - evm, msg, - prelude::*, - storage::{StorageGuardMut, StorageUint}, + abi::Bytes, alloy_sol_types::sol, call::Call, evm, msg, prelude::*, }; +use crate::utils::{AddAssignUnchecked, SubAssignUnchecked}; + sol! { /// Emitted when the `tokenId` token is transferred from `from` to `to`. /// @@ -1014,25 +1011,6 @@ impl ERC721 { } } -// TODO: make it common for all contracts or remove/inline -pub trait IncrementalMath { - fn add_assign_unchecked(&mut self, rhs: T); - - fn sub_assign_unchecked(&mut self, rhs: T); -} - -impl<'a> IncrementalMath for StorageGuardMut<'a, StorageUint<256, 4>> { - fn add_assign_unchecked(&mut self, rhs: U256) { - let new_balance = self.get() + rhs; - self.set(new_balance); - } - - fn sub_assign_unchecked(&mut self, rhs: U256) { - let new_balance = self.get() - rhs; - self.set(new_balance); - } -} - #[cfg(test)] mod tests { use alloy_primitives::address; diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index adaf2937..89efdf83 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -11,6 +11,7 @@ static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; pub mod erc20; #[cfg(any(test, erc721))] pub mod erc721; +mod utils; #[cfg(not(any(test, target_arch = "wasm32-unknown-unknown")))] #[panic_handler] diff --git a/contracts/src/utils.rs b/contracts/src/utils.rs new file mode 100644 index 00000000..20c93268 --- /dev/null +++ b/contracts/src/utils.rs @@ -0,0 +1,24 @@ +use alloy_primitives::U256; +use stylus_sdk::storage::{StorageGuardMut, StorageUint}; + +pub trait AddAssignUnchecked { + fn add_assign_unchecked(&mut self, rhs: T); +} + +impl<'a> AddAssignUnchecked for StorageGuardMut<'a, StorageUint<256, 4>> { + fn add_assign_unchecked(&mut self, rhs: U256) { + let new_balance = self.get() + rhs; + self.set(new_balance); + } +} + +pub trait SubAssignUnchecked { + fn sub_assign_unchecked(&mut self, rhs: T); +} + +impl<'a> SubAssignUnchecked for StorageGuardMut<'a, StorageUint<256, 4>> { + fn sub_assign_unchecked(&mut self, rhs: U256) { + let new_balance = self.get() - rhs; + self.set(new_balance); + } +} From ccb3e2a80080f535c2f8de11c94e016840cf75dd Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 16:56:49 +0400 Subject: [PATCH 50/68] ++ --- contracts/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/utils.rs b/contracts/src/utils.rs index 20c93268..e4cd10dc 100644 --- a/contracts/src/utils.rs +++ b/contracts/src/utils.rs @@ -1,7 +1,7 @@ use alloy_primitives::U256; use stylus_sdk::storage::{StorageGuardMut, StorageUint}; -pub trait AddAssignUnchecked { +pub(crate) trait AddAssignUnchecked { fn add_assign_unchecked(&mut self, rhs: T); } @@ -12,7 +12,7 @@ impl<'a> AddAssignUnchecked for StorageGuardMut<'a, StorageUint<256, 4>> { } } -pub trait SubAssignUnchecked { +pub(crate) trait SubAssignUnchecked { fn sub_assign_unchecked(&mut self, rhs: T); } From 7a870574c1c0344b7a48b0ad6e19f527b6370145 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 17:02:20 +0400 Subject: [PATCH 51/68] ++ --- contracts/src/{utils.rs => arithmetic.rs} | 0 contracts/src/erc721/mod.rs | 2 +- contracts/src/lib.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename contracts/src/{utils.rs => arithmetic.rs} (100%) diff --git a/contracts/src/utils.rs b/contracts/src/arithmetic.rs similarity index 100% rename from contracts/src/utils.rs rename to contracts/src/arithmetic.rs diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 9f845229..107097c5 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -4,7 +4,7 @@ use stylus_sdk::{ abi::Bytes, alloy_sol_types::sol, call::Call, evm, msg, prelude::*, }; -use crate::utils::{AddAssignUnchecked, SubAssignUnchecked}; +use crate::arithmetic::{AddAssignUnchecked, SubAssignUnchecked}; sol! { /// Emitted when the `tokenId` token is transferred from `from` to `to`. diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 89efdf83..37c02a64 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -11,7 +11,7 @@ static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; pub mod erc20; #[cfg(any(test, erc721))] pub mod erc721; -mod utils; +mod arithmetic; #[cfg(not(any(test, target_arch = "wasm32-unknown-unknown")))] #[panic_handler] From 500cf2873a66bd7d304fb3f1728f8c08158c525e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 17:11:44 +0400 Subject: [PATCH 52/68] ++ --- contracts/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 37c02a64..8289fa33 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -7,11 +7,11 @@ extern crate alloc; #[global_allocator] static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +mod arithmetic; #[cfg(any(test, erc20))] pub mod erc20; #[cfg(any(test, erc721))] pub mod erc721; -mod arithmetic; #[cfg(not(any(test, target_arch = "wasm32-unknown-unknown")))] #[panic_handler] From fe6ecf0a5ab2b02b647f7d7fab867434da23268d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 17:18:10 +0400 Subject: [PATCH 53/68] ++ --- contracts/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 8289fa33..deee119d 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; #[global_allocator] static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +#[cfg(any(test, erc20, erc721))] mod arithmetic; #[cfg(any(test, erc20))] pub mod erc20; From 6e9461a45c78e1c33bef05f075344218d7676c7c Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 17:29:56 +0400 Subject: [PATCH 54/68] ++ --- contracts/src/lib.rs | 1 - lib/grip/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index deee119d..8289fa33 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -7,7 +7,6 @@ extern crate alloc; #[global_allocator] static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; -#[cfg(any(test, erc20, erc721))] mod arithmetic; #[cfg(any(test, erc20))] pub mod erc20; diff --git a/lib/grip/Cargo.toml b/lib/grip/Cargo.toml index 5748e3c7..b160fdda 100644 --- a/lib/grip/Cargo.toml +++ b/lib/grip/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true version = "0.1.0" [dependencies] -const-hex = "1.11.1" +const-hex = { version = "1.11.1", default-features = false } once_cell = { version = "1.19.0" } tiny-keccak = { version = "2.0.2", features = ["keccak"] } grip-proc = { path = "../grip-proc" } From 82b41737dab9b27f223130b478ba18df1a315d68 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 17:32:31 +0400 Subject: [PATCH 55/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 107097c5..fa0ebaa2 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -1086,7 +1086,7 @@ mod tests { } #[grip::test] - fn error_when_transfers_nonexistent_token(contract: ERC721) { + fn error_when_transfer_nonexistent_token(contract: ERC721) { let token_id = random_token_id(); let err = contract .transfer_from(*ALICE, BOB, token_id) From 5f2ab139ac5356d26e4175a207e751508d3a06ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:51:04 +0200 Subject: [PATCH 56/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index fa0ebaa2..07894021 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -53,7 +53,7 @@ sol! { /// Indicates a failure with the token `sender`. Used in transfers. /// - /// * `sender` - An address whose token being transferred. + /// * `sender` - An address whose token is being transferred. #[derive(Debug)] error ERC721InvalidSender(address sender); From b8f1a8f9211b77630b5160184c854e78ed3a588b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:51:51 +0200 Subject: [PATCH 57/68] ++ --- contracts/src/erc721/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 07894021..4ef407ef 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -350,8 +350,7 @@ impl ERC721 { /// /// * If token does not exist then [`Error::NonexistentToken`] is returned. /// * If `auth` does not have a right to approve this token then - /// [`Error::InvalidApprover`] - /// is returned + /// [`Error::InvalidApprover`] is returned. /// /// # Requirements: /// From 19cbeb1927358ef8ee87c5acd92fb84fc9d0419b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:52:08 +0200 Subject: [PATCH 58/68] ++ --- contracts/src/erc721/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 4ef407ef..0f8b3f4e 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -789,7 +789,9 @@ impl ERC721 { /// Safely transfers `tokenId` token from `from` to `to`, checking that /// contract recipients are aware of the ERC-721 standard to prevent - /// tokens from being forever locked. `data` is additional data, it has + /// tokens from being forever locked. + /// + /// `data` is additional data, it has /// no specified format and it is sent in call to `to`. This internal /// function is like [`Self::safe_transfer_from`] in the sense that it /// invokes [`IERC721Receiver::on_erc_721_received`] on the receiver, From 97be1b1049029f54aa99eb3babadf470df1bb250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:52:34 +0200 Subject: [PATCH 59/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 0f8b3f4e..307347fd 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -59,7 +59,7 @@ sol! { /// Indicates a failure with the token `receiver`. Used in transfers. /// - /// * `receiver` - Address that receives token. + /// * `receiver` - Address that receives the token. #[derive(Debug)] error ERC721InvalidReceiver(address receiver); From a8577b1d500c34369edc4c0596cd8f71eed1193a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:53:17 +0200 Subject: [PATCH 60/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 307347fd..76025116 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -65,7 +65,7 @@ sol! { /// Indicates a failure with the `operator`’s approval. Used in transfers. /// - /// * `operator` - Address of an operator that wasn't approved. + /// * `operator` - Address that may be allowed to operate on tokens without being their owner. /// * `token_id` - Token id as a number. #[derive(Debug)] error ERC721InsufficientApproval(address operator, uint256 token_id); From 76aefdb4ab3c27ad4a5aae54c5f168db02a1869c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:54:11 +0200 Subject: [PATCH 61/68] ++ --- contracts/src/erc721/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 76025116..69c7ba8d 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -77,7 +77,8 @@ sol! { error ERC721InvalidApprover(address approver); /// Indicates a failure with the `operator` to be approved. Used in approvals. - /// * `operator` - Incorrect address of the operator. + /// + /// * `operator` - Address that may be allowed to operate on tokens without being their owner. #[derive(Debug)] error ERC721InvalidOperator(address operator); } From cba6ff457167390ee3ea2728c1a7e5baa85e5202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 15:55:42 +0200 Subject: [PATCH 62/68] ++ --- contracts/src/erc721/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 69c7ba8d..f46c9c76 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -72,7 +72,7 @@ sol! { /// Indicates a failure with the `approver` of a token to be approved. Used in approvals. /// - /// * `approver` - Address of an approver that failed to approve. + /// * `approver` - Address initiating an approval operation. #[derive(Debug)] error ERC721InvalidApprover(address approver); From 45e0b5304d83d418e29fede8ca480cc92510c6aa Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 5 Apr 2024 17:59:16 +0400 Subject: [PATCH 63/68] ++ --- contracts/src/erc721/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index f46c9c76..407421fb 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -355,8 +355,8 @@ impl ERC721 { /// /// # Requirements: /// - /// - The caller must own the token or be an approved operator. - /// - `token_id` must exist. + /// * The caller must own the token or be an approved operator. + /// * `token_id` must exist. /// /// # Events /// @@ -904,7 +904,6 @@ impl ERC721 { /// * `owner` - Account the token's owner. /// * `operator` - Account to add to the set of authorized operators. /// * `approved` - Whether permission will be granted. If true, this means - /// `operator` will be allowed to manage `owner`'s assets. /// /// # Errors /// From 4d631bd97f2b92baf9a5e8d680e8839be16de546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 16:01:02 +0200 Subject: [PATCH 64/68] ++ --- contracts/src/erc721/mod.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 407421fb..0cc1eecd 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -100,14 +100,15 @@ pub enum Error { sol_interface! { /// ERC-721 token receiver interface. - /// Interface for any contract that wants to support safeTransfers + /// + /// Interface for any contract that wants to support `safeTransfers` /// from ERC-721 asset contracts. interface IERC721Receiver { /// Whenever an [`ERC721`] `tokenId` token is transferred to this contract via [`ERC721::safe_transfer_from`] /// by `operator` from `from`, this function is called. /// - /// It must return its Solidity selector to confirm the token transfer. - /// If any other value is returned or the interface is not implemented by the recipient, the transfer will be + /// It must return its function selector to confirm the token transfer. If + /// any other value is returned or the interface is not implemented by the recipient, the transfer will be /// reverted. function onERC721Received( address operator, @@ -130,14 +131,14 @@ sol_storage! { } } -// NOTE: implementation of `TopLevelStorage` to be able refer to &mut self when -// calling other contracts and not `&mut (impl TopLevelStorage + -// BorrowMut)`. Should be fixed in future by stylus team. +/// 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 ERC721 {} #[external] impl ERC721 { - /// Returns the number of tokens in `owner` 's account. + /// Returns the number of tokens in `owner`'s account. /// /// # Arguments /// @@ -164,7 +165,7 @@ impl ERC721 { /// /// # Errors /// - /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. /// /// # Requirements /// @@ -188,20 +189,20 @@ impl ERC721 { /// /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// * If the previous owner is not `from` then [`Error::IncorrectOwner`] is /// returned. - /// * If caller does not have right to approve then + /// * If the caller does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. - /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its - /// interface id or - /// returned with error then [`Error::InvalidReceiver`] is returned. + /// interface id or returned with error then [`Error::InvalidReceiver`] + /// is returned. /// /// # Requirements /// /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. - /// * `token_id` token must exist and be owned by `from`. + /// * The `token_id` token must exist and be owned by `from`. /// * If the caller is not `from`, it must have been allowed to move this /// token by either [`Self::approve`] or /// * [`Self::set_approval_for_all`]. From 01d36600211434b8190f4a00f1b886ae13eb10b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 16:02:00 +0200 Subject: [PATCH 65/68] ++ --- contracts/src/erc721/mod.rs | 75 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 0cc1eecd..44f6baa5 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -204,11 +204,10 @@ impl ERC721 { /// * `to` cannot be the zero address. /// * The `token_id` token must exist and be owned by `from`. /// * If the caller is not `from`, it must have been allowed to move this - /// token by either [`Self::approve`] or - /// * [`Self::set_approval_for_all`]. + /// token by either [`Self::approve`] or [`Self::set_approval_for_all`]. /// * If `to` refers to a smart contract, it must implement /// [`IERC721Receiver::on_erc_721_received`], which is called upon - /// * a safe transfer. + /// a safe transfer. /// /// # Events /// @@ -231,32 +230,32 @@ impl ERC721 { /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to - /// `to`. + /// * `data` - Additional data with no specified format, sent in the call to + /// [`Self::_check_on_erc721_received`]. /// /// # Errors /// /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// * If the previous owner is not `from` then [`Error::IncorrectOwner`] is /// returned. - /// * If caller does not have right to approve then + /// * If the caller does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. - /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its - /// interface id or - /// returned with error then [`Error::InvalidReceiver`] is returned. + /// interface id or returned with error then [`Error::InvalidReceiver`] + /// is returned. /// /// # Requirements /// /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. - /// * `token_id` token must exist and be owned by `from`. + /// * The `token_id` token must exist and be owned by `from`. /// * If the caller is not `from`, it must be approved to move this token by /// either [`Self::_approve`] or [`Self::set_approval_for_all`]. /// * If `to` refers to a smart contract, it must implement /// [`IERC721Receiver::on_erc_721_received`], which is called upon - /// * a safe transfer. + /// a safe transfer. /// /// # Events /// @@ -279,7 +278,7 @@ impl ERC721 { /// recipient is capable of receiving ERC-721 or else they may be /// permanently lost. Usage of [`Self::safe_transfer_from`] prevents loss, /// though the caller must understand this adds an external call which - /// potentially creates a reentrancy vulnerability. + /// potentially creates a reentrancy vulnerability, unless it is disabled. /// /// # Arguments /// @@ -292,17 +291,17 @@ impl ERC721 { /// /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// * If the previous owner is not `from` then [`Error::IncorrectOwner`] is /// returned. - /// * If caller does not have right to approve then + /// * If the caller does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. - /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. /// /// # Requirements: /// /// * `from` cannot be the zero address. /// * `to` cannot be the zero address. - /// * `token_id` token must be owned by `from`. + /// * The `token_id` token must be owned by `from`. /// * If the caller is not `from`, it must be approved to move this token by /// either [`Self::approve`] or [`Self::set_approval_for_all`]. /// @@ -321,8 +320,8 @@ impl ERC721 { ); } - // Setting an "auth" arguments enables the `_isAuthorized` check which - // verifies that the token exists (from != 0). Therefore, it is + // Setting an "auth" argument enables the `_is_authorized` check which + // verifies that the token exists (`from != 0`). Therefore, it is // not needed to verify that the return value is not 0 here. let previous_owner = self._update(to, token_id, msg::sender())?; if previous_owner != from { @@ -350,8 +349,8 @@ impl ERC721 { /// /// # Errors /// - /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If `auth` does not have a right to approve this token then + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If `auth` (param of [`Self::_approve`]) does not have a right to approve this token then /// [`Error::InvalidApprover`] is returned. /// /// # Requirements: @@ -371,15 +370,17 @@ impl ERC721 { } /// Approve or remove `operator` as an operator for the caller. + /// /// Operators can call [`Self::transfer_from`] or /// [`Self::safe_transfer_from`] for any token owned by the caller. /// /// # Arguments /// /// * `&mut self` - Write access to the contract's state. - /// * `operator` - Account add to the set of authorized operators. - /// * `approved` - Flag that that set approval or disapproval for the - /// operator. + /// * `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 /// @@ -410,7 +411,7 @@ impl ERC721 { /// /// # Errors /// - /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. /// /// # Requirements: /// @@ -420,18 +421,14 @@ impl ERC721 { Ok(self._get_approved_inner(token_id)) } - /// Returns if the `operator` is allowed to manage all the assets of + /// Returns whether the `operator` is allowed to manage all the assets of /// `owner`. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. /// * `owner` - Account of the token's owner. - /// * `operator` - Account to add to the set of authorized operators. - /// - /// # Events - /// - /// Emits an [`set_approval_for_all`] event. + /// * `operator` - Account to be checked. pub fn is_approved_for_all( &self, owner: Address, @@ -442,7 +439,7 @@ impl ERC721 { } impl ERC721 { - /// Returns the owner of the `token_id`. Does NOT revert if token doesn't + /// Returns the owner of the `token_id`. Does NOT revert if the token doesn't /// exist. /// /// IMPORTANT: Any overrides to this function that add ownership of tokens @@ -512,8 +509,8 @@ impl ERC721 { /// /// # Errors /// - /// * If token does not exist then [`Error::NonexistentToken`] is returned. - /// * If spender does not have right to approve then + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If `spender` does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. pub fn _check_authorized( &self, @@ -535,7 +532,7 @@ impl ERC721 { /// Unsafe write access to the balances, used by extensions that "mint" /// tokens using an [`Self::owner_of`] override. /// - /// NOTE: the value is limited to type(uint128).max. This protect against + /// NOTE: the value is limited to type(uint128).max. This protects against /// _balance overflow. It is unrealistic that a uint256 would ever /// overflow from increments when these increments are bounded to uint128 /// values. @@ -560,7 +557,7 @@ impl ERC721 { /// mints (or burns) if the current owner (or `to`) is the zero address. /// Returns the owner of the `token_id` before the update. /// - /// The `auth` argument is optional. If the value passed is non 0, then this + /// The `auth` argument is optional. If the value passed is non-zero, then this /// function will check that `auth` is either the owner of the token, or /// approved to operate on the token (by the owner). /// @@ -593,15 +590,15 @@ impl ERC721 { ) -> Result { let from = self._owner_of_inner(token_id); - // Perform (optional) operator check + // Perform (optional) operator check. if !auth.is_zero() { self._check_authorized(from, auth, token_id)?; } - // Execute the update + // Execute the update. if !from.is_zero() { // Clear approval. No need to re-authorize or emit the Approval - // event + // event. self._approve(Address::ZERO, token_id, Address::ZERO, false)?; self._balances.setter(from).sub_assign_unchecked(U256::from(1)); } From 3a029e6a4f09d0439ff9a71e3b87bdafeddcb29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gonz=C3=A1lez?= Date: Fri, 5 Apr 2024 16:02:57 +0200 Subject: [PATCH 66/68] ++ --- contracts/src/erc721/mod.rs | 57 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 44f6baa5..6aaabfc8 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -627,7 +627,7 @@ impl ERC721 { /// /// # Errors /// - /// * If `token_id` already exist then [`Error::InvalidSender`] is returned. + /// * If `token_id` already exists then [`Error::InvalidSender`] is returned. /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. /// @@ -653,7 +653,8 @@ impl ERC721 { Ok(()) } - /// Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + /// Mints `tokenId`, transfers it to `to`, and checks for `to`'s acceptance. + /// /// An additional `data` parameter is forwarded to /// [`IERC721Receiver::on_erc_721_received`] to contract recipients. /// @@ -662,12 +663,12 @@ impl ERC721 { /// * `&mut self` - Write access to the contract's state. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to - /// `to`. + /// * `data` - Additional data with no specified format, sent in the call to + /// [`Self::_check_on_erc721_received`]. /// /// # Errors /// - /// * If `token_id` already exist then [`Error::InvalidSender`] is returned. + /// * If `token_id` already exists then [`Error::InvalidSender`] is returned. /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its @@ -701,9 +702,10 @@ impl ERC721 { } /// Destroys `token_id`. - /// The approval is cleared when the token is burned. - /// This is an internal function that does not check if the sender is - /// authorized to operate on the token. + /// + /// The approval is cleared when the token is burned. This is an + /// internal function that does not check if the sender is authorized + /// to operate on the token. /// /// # Arguments /// @@ -732,8 +734,9 @@ impl ERC721 { } /// Transfers `token_id` from `from` to `to`. - /// As opposed to [`transferFrom`], this imposes no restrictions on - /// msg.sender. + /// + /// As opposed to [`Self::transfer_from`], this imposes no restrictions on + /// `msg::sender`. /// /// # Arguments /// @@ -748,13 +751,13 @@ impl ERC721 { /// returned. /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is /// returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// * If the previous owner is not `from` then [`Error::IncorrectOwner`] is /// returned. /// /// # Requirements: /// /// * `to` cannot be the zero address. - /// * `token_id` token must be owned by `from`. + /// * The `token_id` token must be owned by `from`. /// /// # Events /// @@ -803,8 +806,8 @@ impl ERC721 { /// * `from` - Account of the sender. /// * `to` - Account of the recipient. /// * `token_id` - Token id as a number. - /// * `data` - Additional data with no specified format, sent in call to - /// `to`. + /// * `data` - Additional data with no specified format, sent in the call to + /// [`Self::_check_on_erc721_received`]. /// /// # Errors /// @@ -812,12 +815,12 @@ impl ERC721 { /// returned. /// * If `token_id` does not exist then [`Error::ERC721NonexistentToken`] is /// returned. - /// * If previous owner is not `from` then [`Error::IncorrectOwner`] is + /// * If the previous owner is not `from` then [`Error::IncorrectOwner`] is /// returned. /// /// # Requirements: /// - /// * `tokenId` token must exist and be owned by `from`. + /// * The `tokenId` token must exist and be owned by `from`. /// * `to` cannot be the zero address. /// * `from` cannot be the zero address. /// * If `to` refers to a smart contract, it must implement @@ -825,6 +828,7 @@ impl ERC721 { /// transfer. /// /// # Events + /// /// Emits a [`Transfer`] event. pub fn _safe_transfer( &mut self, @@ -834,8 +838,7 @@ impl ERC721 { data: Bytes, ) -> Result<(), Error> { self._transfer(from, to, token_id)?; - Self::_check_on_erc721_received( - self, + self._check_on_erc721_received( msg::sender(), from, to, @@ -858,12 +861,12 @@ impl ERC721 { /// /// # Errors /// - /// * If token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is returned. /// * If `auth` does not have a right to approve this token then - /// [`Error::InvalidApprover`] - /// is returned + /// [`Error::InvalidApprover`] is returned. /// /// # Events + /// /// Emits an [`Approval`] event. pub fn _approve( &mut self, @@ -872,15 +875,15 @@ impl ERC721 { auth: Address, emit_event: bool, ) -> Result<(), Error> { - // Avoid reading the owner unless necessary + // Avoid reading the owner unless necessary. if emit_event || !auth.is_zero() { let owner = self._require_owned(token_id)?; - // We do not use _isAuthorized because single-token approvals should - // not be able to call approve - if !auth.is_zero() - && owner != auth - && !self.is_approved_for_all(owner, auth) + // We do not use [`Self::_is_authorized`] because single-token approvals should + // not be able to call `approve`. + if auth.is_zero() + || owner == auth + || self.is_approved_for_all(owner, auth) { return Err(ERC721InvalidApprover { approver: auth }.into()); } From b80a0fad46b1738e7a2c9176a068c28c2d547eb3 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Fri, 5 Apr 2024 16:04:49 +0200 Subject: [PATCH 67/68] ++ --- contracts/src/erc721/mod.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index 6aaabfc8..d28321a3 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -702,7 +702,7 @@ impl ERC721 { } /// Destroys `token_id`. - /// + /// /// The approval is cleared when the token is burned. This is an /// internal function that does not check if the sender is authorized /// to operate on the token. @@ -838,13 +838,7 @@ impl ERC721 { data: Bytes, ) -> Result<(), Error> { self._transfer(from, to, token_id)?; - self._check_on_erc721_received( - msg::sender(), - from, - to, - token_id, - data, - ) + self._check_on_erc721_received(msg::sender(), from, to, token_id, data) } /// Variant of `approve_inner` with an optional flag to enable or disable @@ -881,9 +875,9 @@ impl ERC721 { // We do not use [`Self::_is_authorized`] because single-token approvals should // not be able to call `approve`. - if auth.is_zero() - || owner == auth - || self.is_approved_for_all(owner, auth) + if !auth.is_zero() + && owner != auth + && !self.is_approved_for_all(owner, auth) { return Err(ERC721InvalidApprover { approver: auth }.into()); } From b84eed2e0a84f42b106a418205c0f51f11f055a2 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Fri, 5 Apr 2024 16:05:15 +0200 Subject: [PATCH 68/68] lint(fmt): fix formatting --- contracts/src/erc721/mod.rs | 70 +++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/contracts/src/erc721/mod.rs b/contracts/src/erc721/mod.rs index d28321a3..025f79bd 100644 --- a/contracts/src/erc721/mod.rs +++ b/contracts/src/erc721/mod.rs @@ -165,7 +165,8 @@ impl ERC721 { /// /// # Errors /// - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// /// # Requirements /// @@ -193,10 +194,11 @@ impl ERC721 { /// returned. /// * If the caller does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its - /// interface id or returned with error then [`Error::InvalidReceiver`] - /// is returned. + /// interface id or returned with error then [`Error::InvalidReceiver`] is + /// returned. /// /// # Requirements /// @@ -206,8 +208,8 @@ impl ERC721 { /// * If the caller is not `from`, it must have been allowed to move this /// token by either [`Self::approve`] or [`Self::set_approval_for_all`]. /// * If `to` refers to a smart contract, it must implement - /// [`IERC721Receiver::on_erc_721_received`], which is called upon - /// a safe transfer. + /// [`IERC721Receiver::on_erc_721_received`], which is called upon a safe + /// transfer. /// /// # Events /// @@ -241,10 +243,11 @@ impl ERC721 { /// returned. /// * If the caller does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its - /// interface id or returned with error then [`Error::InvalidReceiver`] - /// is returned. + /// interface id or returned with error then [`Error::InvalidReceiver`] is + /// returned. /// /// # Requirements /// @@ -254,8 +257,8 @@ impl ERC721 { /// * If the caller is not `from`, it must be approved to move this token by /// either [`Self::_approve`] or [`Self::set_approval_for_all`]. /// * If `to` refers to a smart contract, it must implement - /// [`IERC721Receiver::on_erc_721_received`], which is called upon - /// a safe transfer. + /// [`IERC721Receiver::on_erc_721_received`], which is called upon a safe + /// transfer. /// /// # Events /// @@ -295,7 +298,8 @@ impl ERC721 { /// returned. /// * If the caller does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// /// # Requirements: /// @@ -349,9 +353,10 @@ impl ERC721 { /// /// # Errors /// - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. - /// * If `auth` (param of [`Self::_approve`]) does not have a right to approve this token then - /// [`Error::InvalidApprover`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. + /// * If `auth` (param of [`Self::_approve`]) does not have a right to + /// approve this token then [`Error::InvalidApprover`] is returned. /// /// # Requirements: /// @@ -378,9 +383,9 @@ impl ERC721 { /// /// * `&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. + /// * `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 /// @@ -411,7 +416,8 @@ impl ERC721 { /// /// # Errors /// - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// /// # Requirements: /// @@ -439,8 +445,8 @@ impl ERC721 { } impl ERC721 { - /// Returns the owner of the `token_id`. Does NOT revert if the token doesn't - /// exist. + /// Returns the owner of the `token_id`. Does NOT revert if the token + /// doesn't exist. /// /// IMPORTANT: Any overrides to this function that add ownership of tokens /// not tracked by the core ERC-721 logic MUST be matched with the use @@ -509,7 +515,8 @@ impl ERC721 { /// /// # Errors /// - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// * If `spender` does not have the right to approve then /// [`Error::InsufficientApproval`] is returned. pub fn _check_authorized( @@ -557,9 +564,9 @@ impl ERC721 { /// mints (or burns) if the current owner (or `to`) is the zero address. /// Returns the owner of the `token_id` before the update. /// - /// The `auth` argument is optional. If the value passed is non-zero, then this - /// function will check that `auth` is either the owner of the token, or - /// approved to operate on the token (by the owner). + /// The `auth` argument is optional. If the value passed is non-zero, then + /// this function will check that `auth` is either the owner of the + /// token, or approved to operate on the token (by the owner). /// /// NOTE: If overriding this function in a way that tracks balances, see /// also [`Self::_increase_balance`]. @@ -627,7 +634,8 @@ impl ERC721 { /// /// # Errors /// - /// * If `token_id` already exists then [`Error::InvalidSender`] is returned. + /// * If `token_id` already exists then [`Error::InvalidSender`] is + /// returned. /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. /// @@ -668,7 +676,8 @@ impl ERC721 { /// /// # Errors /// - /// * If `token_id` already exists then [`Error::InvalidSender`] is returned. + /// * If `token_id` already exists then [`Error::InvalidSender`] is + /// returned. /// * If `to` is `Address::ZERO` then [`Error::InvalidReceiver`] is /// returned. /// * If [`IERC721Receiver::on_erc_721_received`] hasn't returned its @@ -855,7 +864,8 @@ impl ERC721 { /// /// # Errors /// - /// * If the token does not exist then [`Error::NonexistentToken`] is returned. + /// * If the token does not exist then [`Error::NonexistentToken`] is + /// returned. /// * If `auth` does not have a right to approve this token then /// [`Error::InvalidApprover`] is returned. /// @@ -873,8 +883,8 @@ impl ERC721 { if emit_event || !auth.is_zero() { let owner = self._require_owned(token_id)?; - // We do not use [`Self::_is_authorized`] because single-token approvals should - // not be able to call `approve`. + // We do not use [`Self::_is_authorized`] because single-token + // approvals should not be able to call `approve`. if !auth.is_zero() && owner != auth && !self.is_approved_for_all(owner, auth)