Skip to content

Commit

Permalink
refactor metadata for erc721 and erc20
Browse files Browse the repository at this point in the history
  • Loading branch information
qalisander committed Oct 9, 2024
1 parent 15a789d commit e6851f4
Show file tree
Hide file tree
Showing 18 changed files with 214 additions and 179 deletions.
29 changes: 5 additions & 24 deletions contracts/src/token/erc20/extensions/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use alloc::string::String;

use alloy_primitives::FixedBytes;
use openzeppelin_stylus_proc::interface_id;
use stylus_proc::{public, sol_storage};
use stylus_proc::sol_storage;

use crate::utils::introspection::erc165::IErc165;
use crate::{token::erc20::Erc20, utils::introspection::erc165::IErc165};

/// Number of decimals used by default on implementors of [`Metadata`].
pub const DEFAULT_DECIMALS: u8 = 18;
Expand Down Expand Up @@ -58,44 +58,25 @@ pub trait IErc20Metadata {
/// no way it affects any of the arithmetic of the contract, including
/// [`super::super::IErc20::balance_of`] and
/// [`super::super::IErc20::transfer`].
fn decimals(&self) -> u8;
}

// FIXME: Apply multi-level inheritance to export Metadata's functions.
// With the current version of SDK it is not possible.
// See https://github.com/OffchainLabs/stylus-sdk-rs/pull/120
#[public]
impl IErc20Metadata for Erc20Metadata {
fn name(&self) -> String {
self._metadata.name()
}

fn symbol(&self) -> String {
self._metadata.symbol()
}

fn decimals(&self) -> u8 {
// TODO: Use `U8` an avoid the conversion once
// https://github.com/OffchainLabs/stylus-sdk-rs/issues/117
// gets resolved.
DEFAULT_DECIMALS
}
}

impl IErc165 for Erc20Metadata {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc20Metadata>::INTERFACE_ID
<Erc20 as IErc20Metadata>::INTERFACE_ID
== u32::from_be_bytes(*interface_id)
}
}

#[cfg(all(test, feature = "std"))]
mod tests {
use crate::token::erc20::extensions::{Erc20Metadata, IErc20Metadata};
use crate::token::erc20::{extensions::IErc20Metadata, Erc20};

#[motsu::test]
fn interface_id() {
let actual = <Erc20Metadata as IErc20Metadata>::INTERFACE_ID;
let actual = <Erc20 as IErc20Metadata>::INTERFACE_ID;
let expected = 0xa219a025;
assert_eq!(actual, expected);
}
Expand Down
23 changes: 20 additions & 3 deletions contracts/src/token/erc20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//! revert instead of returning `false` on failure. This behavior is
//! nonetheless conventional and does not conflict with the expectations of
//! [`Erc20`] applications.
use alloc::string::String;

use alloy_primitives::{Address, FixedBytes, U256};
use alloy_sol_types::sol;
use openzeppelin_stylus_proc::interface_id;
Expand All @@ -14,7 +16,10 @@ use stylus_sdk::{
stylus_proc::{public, sol_storage},
};

use crate::utils::introspection::erc165::{Erc165, IErc165};
use crate::{
token::erc20::extensions::{Erc20Metadata, IErc20Metadata},
utils::introspection::erc165::{Erc165, IErc165},
};

pub mod extensions;

Expand Down Expand Up @@ -104,6 +109,8 @@ impl MethodError for Error {
sol_storage! {
/// State of an `Erc20` token.
pub struct Erc20 {
/// Metadata fields associated with token.
Erc20Metadata _metadata;
/// Maps users to balances.
mapping(address => uint256) _balances;
/// Maps users to a mapping of each spender's allowance.
Expand Down Expand Up @@ -290,9 +297,20 @@ impl IErc20 for Erc20 {
}
}

impl IErc20Metadata for Erc20 {
fn name(&self) -> String {
self._metadata._metadata.name()
}

fn symbol(&self) -> String {
self._metadata._metadata.symbol()
}
}

impl IErc165 for Erc20 {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc20>::INTERFACE_ID == u32::from_be_bytes(*interface_id)
|| Erc20Metadata::supports_interface(interface_id)
|| Erc165::supports_interface(interface_id)
}
}
Expand Down Expand Up @@ -562,8 +580,7 @@ mod tests {

use super::{Erc20, Error, IErc20};
use crate::{
token::erc721::{Erc721, IErc721},
utils::introspection::erc165::IErc165,
token::erc721::IErc721, utils::introspection::erc165::IErc165,
};

#[motsu::test]
Expand Down
60 changes: 26 additions & 34 deletions contracts/src/token/erc721/extensions/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

use alloc::string::String;

use alloy_primitives::FixedBytes;
use alloy_primitives::{FixedBytes, U256};
use openzeppelin_stylus_proc::interface_id;
use stylus_proc::{public, sol_storage};
use stylus_proc::sol_storage;

use crate::utils::{introspection::erc165::IErc165, Metadata};
use crate::{
token::erc721::{Erc721, Error},
utils::{introspection::erc165::IErc165, Metadata},
};

sol_storage! {
/// Metadata of an [`crate::token::erc721::Erc721`] token.
Expand Down Expand Up @@ -35,50 +38,39 @@ pub trait IErc721Metadata {
/// * `&self` - Read access to the contract's state.
fn symbol(&self) -> String;

/// Returns the base of Uniform Resource Identifier (URI) for tokens'
/// collection.
/// Returns the token URI for `token_id`.
///
/// NOTE: Don't forget to add `#[selector(name = "tokenURI")]` while
/// reexporting, since actual solidity name is different.
///
/// # Arguments
///
/// * `&self` - Read access to the contract's state.
fn base_uri(&self) -> String;
}

// FIXME: Apply multi-level inheritance to export Metadata's functions.
// With the current version of SDK it is not possible.
// See https://github.com/OffchainLabs/stylus-sdk-rs/pull/120
#[public]
impl IErc721Metadata for Erc721Metadata {
fn name(&self) -> String {
self._metadata.name()
}

fn symbol(&self) -> String {
self._metadata.symbol()
}

fn base_uri(&self) -> String {
self._base_uri.get_string()
}
/// * `token_id` - Token id as a number.
///
/// # Errors
///
/// If token does not exist, then the error
/// [`Error::NonexistentToken`] is returned.
#[selector(name = "tokenURI")]
fn token_uri(&self, token_id: U256) -> Result<String, Error>;
}

impl IErc165 for Erc721Metadata {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc721Metadata>::INTERFACE_ID
<Erc721 as IErc721Metadata>::INTERFACE_ID
== u32::from_be_bytes(*interface_id)
}
}

#[cfg(all(test, feature = "std"))]
mod tests {
// use crate::token::erc721::extensions::{Erc721Metadata, IErc721Metadata};
use crate::token::erc721::{extensions::IErc721Metadata, Erc721};

// TODO: IErc721Metadata should be refactored to have same api as solidity
// has: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4764ea50750d8bda9096e833706beba86918b163/contracts/token/ERC721/extensions/IERC721Metadata.sol#L12
// [motsu::test]
// fn interface_id() {
// let actual = <Erc721Metadata as IErc721Metadata>::INTERFACE_ID;
// let expected = 0x5b5e139f;
// assert_eq!(actual, expected);
// }
#[motsu::test]
fn interface_id() {
let actual = <Erc721 as IErc721Metadata>::INTERFACE_ID;
let expected = 0x5b5e139f;
assert_eq!(actual, expected);
}
}
80 changes: 51 additions & 29 deletions contracts/src/token/erc721/extensions/uri_storage.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
//! ERC-721 token with storage-based token URI management.
//!
//! It also implements IERC4096, which is an ERC-721 Metadata Update Extension.
use alloc::string::String;
use alloc::string::{String, ToString};

Check warning on line 4 in contracts/src/token/erc721/extensions/uri_storage.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] contracts/src/token/erc721/extensions/uri_storage.rs#L4

warning: unused import: `ToString` --> contracts/src/token/erc721/extensions/uri_storage.rs:4:29 | 4 | use alloc::string::{String, ToString}; | ^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
Raw output
contracts/src/token/erc721/extensions/uri_storage.rs:4:29:w:warning: unused import: `ToString`
 --> contracts/src/token/erc721/extensions/uri_storage.rs:4:29
  |
4 | use alloc::string::{String, ToString};
  |                             ^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default


__END__

use alloy_primitives::U256;
use alloy_sol_types::sol;
use stylus_proc::{public, sol_storage};
use stylus_proc::sol_storage;
use stylus_sdk::evm;

use crate::token::erc721::{
extensions::metadata::IErc721Metadata, Erc721, Error,
};

sol! {
/// This event gets emitted when the metadata of a token is changed.
///
Expand Down Expand Up @@ -48,51 +52,69 @@ impl Erc721UriStorage {
}
}

#[public]
impl Erc721UriStorage {
/// Returns the Uniform Resource Identifier (URI) for `token_id` token.
///
/// # Arguments
///
/// * `&self` - Read access to the contract's state.
/// * `erc721` - Read access to the Erc721 contract's state.
/// * `token_id` - Id of a token.
#[must_use]
pub fn token_uri(&self, token_id: U256) -> String {
self._token_uris.getter(token_id).get_string()
pub fn token_uri(
&self,
erc721: &Erc721,
token_id: U256,
) -> Result<String, Error> {
erc721._require_owned(token_id)?;

let token_uri = self._token_uris.getter(token_id).get_string();
let base = erc721._metadata._base_uri.get_string();

// If there is no base URI, return the token URI.
if base.is_empty() {
return Ok(token_uri);
};

// If both are set, concatenate the base uri and token_uri.
if !token_uri.is_empty() {
return Ok(base + &token_uri);
};

erc721.token_uri(token_id)
}
}

#[cfg(all(test, feature = "std"))]
mod tests {
use alloy_primitives::U256;

use super::Erc721UriStorage;

fn random_token_id() -> U256 {
let num: u32 = rand::random();
U256::from(num)
}

#[motsu::test]
fn get_token_uri_works(contract: Erc721UriStorage) {
let token_id = random_token_id();

let token_uri = String::from("https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#Erc721URIStorage");
contract._token_uris.setter(token_id).set_str(token_uri.clone());

assert_eq!(token_uri, contract.token_uri(token_id));
}

#[motsu::test]
fn set_token_uri_works(contract: Erc721UriStorage) {
let token_id = random_token_id();

let initial_token_uri = String::from("https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#Erc721URIStorage");
contract._token_uris.setter(token_id).set_str(initial_token_uri);

let token_uri = String::from("Updated Token URI");
contract._set_token_uri(token_id, token_uri.clone());

assert_eq!(token_uri, contract.token_uri(token_id));
}
// TODO#q: fix tests
// #[motsu::test]
// fn get_token_uri_works(contract: Erc721UriStorage) {
// let token_id = random_token_id();
//
// let token_uri = String::from("https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#Erc721URIStorage");
// contract._token_uris.setter(token_id).set_str(token_uri.clone());
//
// assert_eq!(token_uri, contract.token_uri(token_id));
// }
//
// #[motsu::test]
// fn set_token_uri_works(contract: Erc721UriStorage) {
// let token_id = random_token_id();
//
// let initial_token_uri = String::from("https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#Erc721URIStorage");
// contract._token_uris.setter(token_id).set_str(initial_token_uri);
//
// let token_uri = String::from("Updated Token URI");
// contract._set_token_uri(token_id, token_uri.clone());
//
// assert_eq!(token_uri, contract.token_uri(token_id));
// }
}
38 changes: 34 additions & 4 deletions contracts/src/token/erc721/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Implementation of the [`Erc721`] token standard.
use alloc::vec;
use alloc::{
string::{String, ToString},
vec,
};

use alloy_primitives::{fixed_bytes, uint, Address, FixedBytes, U128, U256};
use openzeppelin_stylus_proc::interface_id;
Expand All @@ -11,9 +14,12 @@ use stylus_sdk::{
prelude::*,
};

use crate::utils::{
introspection::erc165::{Erc165, IErc165},
math::storage::{AddAssignUnchecked, SubAssignUnchecked},
use crate::{
token::erc721::extensions::{Erc721Metadata, IErc721Metadata},
utils::{
introspection::erc165::{Erc165, IErc165},
math::storage::{AddAssignUnchecked, SubAssignUnchecked},
},
};

pub mod extensions;
Expand Down Expand Up @@ -185,6 +191,8 @@ sol_interface! {
sol_storage! {
/// State of an [`Erc721`] token.
pub struct Erc721 {
/// Metadata fields associated with token.
Erc721Metadata _metadata;
/// Maps tokens to owners.
mapping(uint256 => address) _owners;
/// Maps users to balances.
Expand Down Expand Up @@ -555,9 +563,31 @@ impl IErc721 for Erc721 {
}
}

impl IErc721Metadata for Erc721 {
fn name(&self) -> String {
self._metadata._metadata.name()
}

fn symbol(&self) -> String {
self._metadata._metadata.symbol()
}

fn token_uri(&self, token_id: U256) -> Result<String, Error> {
self._require_owned(token_id)?;
let base_uri = self._metadata._base_uri.get_string();
let uri = if !base_uri.is_empty() {
base_uri + &token_id.to_string()
} else {
"".to_string()
};
Ok(uri)
}
}

impl IErc165 for Erc721 {
fn supports_interface(interface_id: FixedBytes<4>) -> bool {
<Self as IErc721>::INTERFACE_ID == u32::from_be_bytes(*interface_id)
|| Erc721Metadata::supports_interface(interface_id)
|| Erc165::supports_interface(interface_id)
}
}
Expand Down
Loading

0 comments on commit e6851f4

Please sign in to comment.