diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index a5e29581..17b354f1 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - name: Link Checker uses: lycheeverse/lychee-action@v2 with: @@ -30,9 +30,8 @@ jobs: if: startsWith(github.ref, 'refs/heads/v') || startsWith(github.base_ref, 'v') steps: - uses: actions/checkout@v4 - + - name: Run linkspector uses: umbrelladocs/action-linkspector@v1 with: fail_on_error: true - diff --git a/.github/workflows/check-publish.yml b/.github/workflows/check-publish.yml index 132eb00c..16956b9f 100644 --- a/.github/workflows/check-publish.yml +++ b/.github/workflows/check-publish.yml @@ -1,5 +1,5 @@ name: check-publish -# This workflow checks that the libraries can be published on crates.io. +# This workflow checks if the libraries can be published on crates.io. permissions: contents: read on: @@ -40,6 +40,5 @@ jobs: - name: check openzeppelin-stylus-proc run: cargo publish -p openzeppelin-stylus-proc --target wasm32-unknown-unknown --dry-run - # TODO: https://github.com/OpenZeppelin/rust-contracts-stylus/issues/291 - # - name: check openzeppelin-stylus - # run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --dry-run + - name: check openzeppelin-stylus + run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --dry-run diff --git a/Cargo.lock b/Cargo.lock index 0629f702..835134ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "access-control-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -793,7 +793,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy-primitives", "mini-alloc", @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "benches" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "cryptography-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1512,7 +1512,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erc20-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1526,7 +1526,7 @@ dependencies = [ [[package]] name = "erc20-permit-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1540,7 +1540,7 @@ dependencies = [ [[package]] name = "erc721-consecutive-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1556,7 +1556,7 @@ dependencies = [ [[package]] name = "erc721-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1571,7 +1571,7 @@ dependencies = [ [[package]] name = "erc721-metadata-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -2353,7 +2353,7 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "merkle-proofs-example" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -2565,7 +2565,7 @@ dependencies = [ [[package]] name = "openzeppelin-crypto" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "hex-literal", "mini-alloc", @@ -2575,9 +2575,12 @@ dependencies = [ [[package]] name = "openzeppelin-stylus" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", "alloy-sol-types", "keccak-const", "mini-alloc", @@ -2589,7 +2592,7 @@ dependencies = [ [[package]] name = "openzeppelin-stylus-proc" -version = "0.1.0-rc" +version = "0.1.0" dependencies = [ "convert_case 0.6.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 1886c295..244e4355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ authors = ["OpenZeppelin"] edition = "2021" license = "MIT" repository = "https://github.com/OpenZeppelin/rust-contracts-stylus" -version = "0.1.0-rc" +version = "0.1.0" [workspace.lints.rust] missing_docs = "warn" @@ -65,7 +65,7 @@ all = "warn" stylus-sdk = { version = "=0.6.0", default-features = false } mini-alloc = "0.4.2" -alloy = { version = "0.1.4", features = [ +alloy = { version = "=0.1.4", features = [ "contract", "network", "providers", @@ -78,8 +78,11 @@ alloy = { version = "0.1.4", features = [ # Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need # to keep both versions for compatibility with the Stylus SDK. Once they start # using `alloy` we can remove these. -alloy-primitives = { version = "0.7.6", default-features = false } -alloy-sol-types = { version = "0.7.6", default-features = false } +alloy-primitives = { version = "=0.7.6", default-features = false } +alloy-sol-types = { version = "=0.7.6", default-features = false } +alloy-sol-macro = { version = "=0.7.6", default-features = false } +alloy-sol-macro-expander = { version = "=0.7.6", default-features = false } +alloy-sol-macro-input = { version = "=0.7.6", default-features = false } const-hex = { version = "1.11.1", default-features = false } eyre = "0.6.8" @@ -99,7 +102,7 @@ quote = "1.0.35" # members openzeppelin-stylus = { path = "contracts" } -openzeppelin-stylus-proc = { path = "contracts-proc" } +openzeppelin-stylus-proc = { path = "contracts-proc", version = "0.1.0" } openzeppelin-crypto = { path = "lib/crypto" } motsu = { path = "lib/motsu"} motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } diff --git a/README.md b/README.md index dec4d415..eed2be55 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,6 @@ **A library for secure smart contract development** written in Rust for [Arbitrum Stylus](https://docs.arbitrum.io/stylus/stylus-gentle-introduction). -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - ## Features - Security-first smart contracts, ported from the [`openzeppelin-contracts`] @@ -31,7 +26,7 @@ line to your `Cargo.toml` (We recommend pinning to a specific version): ```toml [dependencies] -openzeppelin-stylus = "0.1.0-rc" +openzeppelin-stylus = "0.1.0" ``` Optionally, you can specify a git dependency if you want to have the latest @@ -56,7 +51,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20)] impl Erc20Example {} ``` @@ -71,8 +66,7 @@ For more information on what this library will include in the future, see our [roadmap]. [basic]: ./examples/basic - -[roadmap]: https://github.com/OpenZeppelin/rust-contracts-stylus/milestone/1 +[roadmap]: https://github.com/OpenZeppelin/rust-contracts-stylus/milestone/2 ## Contribute @@ -82,10 +76,7 @@ the [contribution guide](CONTRIBUTING.md)! ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. +Past audits can be found in [`audits/`](./audits). Refer to our [Security Policy](SECURITY.md) for more details. diff --git a/SECURITY.md b/SECURITY.md index a0acc639..97bbaa54 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,5 @@ # Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. +Past audits can be found in [`audits/`](./audits). Please report any security issues you find to security@openzeppelin.com. diff --git a/audits/2024-10-v0.1.0.pdf b/audits/2024-10-v0.1.0.pdf new file mode 100644 index 00000000..938278ba Binary files /dev/null and b/audits/2024-10-v0.1.0.pdf differ diff --git a/contracts-proc/Cargo.toml b/contracts-proc/Cargo.toml index 3372da2c..85b40c37 100644 --- a/contracts-proc/Cargo.toml +++ b/contracts-proc/Cargo.toml @@ -1,14 +1,13 @@ [package] name = "openzeppelin-stylus-proc" description = "Procedural macros for OpenZeppelin Stylus contracts" -version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true -categories = ["cryptography::cryptocurrencies", "no-std", "wasm"] -keywords = ["arbitrum", "ethereum", "stylus", "smart-contracts", "standards"] repository.workspace = true - +keywords = ["arbitrum", "ethereum", "stylus", "smart-contracts", "standards"] +categories = ["cryptography::cryptocurrencies", "no-std", "wasm"] +version = "0.1.0" [dependencies] proc-macro2.workspace = true diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index ba4f306a..269161b2 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -11,6 +11,9 @@ version.workspace = true [dependencies] alloy-primitives.workspace = true alloy-sol-types.workspace = true +alloy-sol-macro.workspace = true +alloy-sol-macro-expander.workspace = true +alloy-sol-macro-input.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true keccak-const.workspace = true diff --git a/contracts/README.md b/contracts/README.md index 4f958fa8..2f053b0c 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -9,9 +9,4 @@ Robust, reliable, and secure smart contracts for the Arbitrum Stylus upgrade. ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - Refer to our [Security Policy](../SECURITY.md) for more details. diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index dd4c8c73..dc71f9b9 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -6,10 +6,6 @@ A library for secure smart contract development written in Rust for This library offers common smart contract primitives and affordances that take advantage of the nature of Stylus. -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - ## Usage To start using it, add `openzeppelin-stylus` to your `Cargo.toml`, or simply run @@ -36,7 +32,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20)] impl MyContract { } ``` diff --git a/contracts/src/token/erc20/extensions/burnable.rs b/contracts/src/token/erc20/extensions/burnable.rs index d3e6e4df..fce7fe9a 100644 --- a/contracts/src/token/erc20/extensions/burnable.rs +++ b/contracts/src/token/erc20/extensions/burnable.rs @@ -158,7 +158,7 @@ mod tests { } #[motsu::test] - fn burns_from_errors_when_invalid_sender(contract: Erc20) { + fn burns_from_errors_when_invalid_approver(contract: Erc20) { let one = uint!(1_U256); contract @@ -168,7 +168,7 @@ mod tests { .set(one); let result = contract.burn_from(Address::ZERO, one); - assert!(matches!(result, Err(Error::InvalidSender(_)))); + assert!(matches!(result, Err(Error::InvalidApprover(_)))); } #[motsu::test] diff --git a/contracts/src/token/erc20/extensions/metadata.rs b/contracts/src/token/erc20/extensions/metadata.rs index 32697e07..6cc10141 100644 --- a/contracts/src/token/erc20/extensions/metadata.rs +++ b/contracts/src/token/erc20/extensions/metadata.rs @@ -61,9 +61,6 @@ pub trait IErc20Metadata { 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 { @@ -75,9 +72,6 @@ impl IErc20Metadata for Erc20Metadata { } 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 } } diff --git a/contracts/src/token/erc20/extensions/permit.rs b/contracts/src/token/erc20/extensions/permit.rs index 9283677d..1b58e948 100644 --- a/contracts/src/token/erc20/extensions/permit.rs +++ b/contracts/src/token/erc20/extensions/permit.rs @@ -177,7 +177,7 @@ impl Erc20Permit { return Err(ERC2612InvalidSigner { signer, owner }.into()); } - self.erc20._approve(owner, spender, value)?; + self.erc20._approve(owner, spender, value, true)?; Ok(()) } diff --git a/contracts/src/token/erc20/mod.rs b/contracts/src/token/erc20/mod.rs index 9a83384c..19fba1ea 100644 --- a/contracts/src/token/erc20/mod.rs +++ b/contracts/src/token/erc20/mod.rs @@ -72,6 +72,14 @@ sol! { #[allow(missing_docs)] error ERC20InvalidSpender(address spender); + /// Indicates a failure with the `approver` of a token to be approved. Used in approvals. + /// approver Address initiating an approval operation. + /// + /// * `approver` - Address initiating an approval operation. + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidApprover(address approver); + } /// An [`Erc20`] error defined as described in [ERC-6093]. @@ -92,6 +100,9 @@ pub enum Error { /// Indicates a failure with the `spender` to be approved. Used in /// approvals. InvalidSpender(ERC20InvalidSpender), + /// Indicates a failure with the `approver` of a token to be approved. Used + /// in approvals. approver Address initiating an approval operation. + InvalidApprover(ERC20InvalidApprover), } impl MethodError for Error { @@ -273,7 +284,7 @@ impl IErc20 for Erc20 { value: U256, ) -> Result { let owner = msg::sender(); - self._approve(owner, spender, value) + self._approve(owner, spender, value, true) } fn transfer_from( @@ -306,6 +317,7 @@ impl Erc20 { /// * `&mut self` - Write access to the contract's state. /// * `owner` - Account that owns the tokens. /// * `spender` - Account that will spend the tokens. + /// * `emit_event` - Emit an [`Approval`] event flag. /// /// # Errors /// @@ -320,7 +332,14 @@ impl Erc20 { owner: Address, spender: Address, value: U256, + emit_event: bool, ) -> Result { + if owner.is_zero() { + return Err(Error::InvalidApprover(ERC20InvalidApprover { + approver: Address::ZERO, + })); + } + if spender.is_zero() { return Err(Error::InvalidSpender(ERC20InvalidSpender { spender: Address::ZERO, @@ -328,7 +347,9 @@ impl Erc20 { } self._allowances.setter(owner).insert(spender, value); - evm::log(Approval { owner, spender, value }); + if emit_event { + evm::log(Approval { owner, spender, value }); + } Ok(true) } @@ -533,7 +554,7 @@ impl Erc20 { spender: Address, value: U256, ) -> Result<(), Error> { - let current_allowance = self._allowances.get(owner).get(spender); + let current_allowance = self.allowance(owner, spender); if current_allowance != U256::MAX { if current_allowance < value { return Err(Error::InsufficientAllowance( @@ -545,9 +566,7 @@ impl Erc20 { )); } - self._allowances - .setter(owner) - .insert(spender, current_allowance - value); + self._approve(owner, spender, current_allowance - value, false)?; } Ok(()) @@ -560,10 +579,7 @@ mod tests { use stylus_sdk::msg; use super::{Erc20, Error, IErc20}; - use crate::{ - token::erc721::{Erc721, IErc721}, - utils::introspection::erc165::IErc165, - }; + use crate::utils::introspection::erc165::IErc165; #[motsu::test] fn reads_balance(contract: Erc20) { @@ -830,7 +846,7 @@ mod tests { } #[motsu::test] - fn transfer_from_errors_when_invalid_sender(contract: Erc20) { + fn transfer_from_errors_when_invalid_approver(contract: Erc20) { let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d"); let one = uint!(1_U256); contract @@ -839,7 +855,7 @@ mod tests { .setter(msg::sender()) .set(one); let result = contract.transfer_from(Address::ZERO, alice, one); - assert!(matches!(result, Err(Error::InvalidSender(_)))); + assert!(matches!(result, Err(Error::InvalidApprover(_)))); } #[motsu::test] diff --git a/contracts/src/token/erc721/extensions/consecutive.rs b/contracts/src/token/erc721/extensions/consecutive.rs index 22a29b9c..302cb56f 100644 --- a/contracts/src/token/erc721/extensions/consecutive.rs +++ b/contracts/src/token/erc721/extensions/consecutive.rs @@ -47,18 +47,20 @@ use crate::{ structs::{ bitmap::BitMap, checkpoints, - checkpoints::{Trace160, U96}, + checkpoints::{Size, Trace, S160}, }, }, }; +type U96 = ::Key; + sol_storage! { /// State of an [`Erc721Consecutive`] token. pub struct Erc721Consecutive { /// Erc721 contract storage. Erc721 erc721; /// Checkpoint library contract for sequential ownership. - Trace160 _sequential_ownership; + Trace _sequential_ownership; /// BitMap library contract for sequential burn of tokens. BitMap _sequential_burn; /// Used to offset the first token id in @@ -797,20 +799,16 @@ mod tests { use alloy_primitives::{address, uint, Address, U256}; use stylus_sdk::msg; - use crate::{ - token::{ - erc721, - erc721::{ - extensions::consecutive::{ - ERC721ExceededMaxBatchMint, Erc721Consecutive, Error, - }, - tests::random_token_id, - ERC721IncorrectOwner, ERC721InvalidApprover, - ERC721InvalidReceiver, ERC721InvalidSender, - ERC721NonexistentToken, IErc721, + use crate::token::{ + erc721, + erc721::{ + extensions::consecutive::{ + ERC721ExceededMaxBatchMint, Erc721Consecutive, Error, U96, }, + tests::random_token_id, + ERC721IncorrectOwner, ERC721InvalidApprover, ERC721InvalidReceiver, + ERC721InvalidSender, ERC721NonexistentToken, IErc721, }, - utils::structs::checkpoints::U96, }; const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526"); diff --git a/contracts/src/token/erc721/extensions/enumerable.rs b/contracts/src/token/erc721/extensions/enumerable.rs index b82df270..3511489e 100644 --- a/contracts/src/token/erc721/extensions/enumerable.rs +++ b/contracts/src/token/erc721/extensions/enumerable.rs @@ -8,7 +8,6 @@ //! [`super::super::Erc721::balance_of`] logic, such as `Erc721Consecutive`, //! interfere with enumerability and should not be used together with //! [`Erc721Enumerable`]. -// TODO: Add link for `Erc721Consecutive` to module docs. use alloy_primitives::{uint, Address, FixedBytes, U256}; use alloy_sol_types::sol; @@ -73,8 +72,6 @@ pub trait IErc721Enumerable { /// implementation. type Error: Into>; - // TODO: fn supports_interface (#33) - /// Returns a token ID owned by `owner` at a given `index` of its token /// list. /// diff --git a/contracts/src/token/erc721/extensions/metadata.rs b/contracts/src/token/erc721/extensions/metadata.rs index 2fe273e2..c1e95e07 100644 --- a/contracts/src/token/erc721/extensions/metadata.rs +++ b/contracts/src/token/erc721/extensions/metadata.rs @@ -1,12 +1,15 @@ //! Optional Metadata of the ERC-721 standard. -use alloc::string::String; +use alloc::string::{String, ToString}; -use alloy_primitives::FixedBytes; +use alloy_primitives::{FixedBytes, U256}; use openzeppelin_stylus_proc::interface_id; use stylus_sdk::stylus_proc::{public, sol_storage}; -use crate::utils::{introspection::erc165::IErc165, Metadata}; +use crate::{ + token::erc721::{Error, IErc721}, + utils::{introspection::erc165::IErc165, Metadata}, +}; sol_storage! { /// Metadata of an [`crate::token::erc721::Erc721`] token. @@ -34,14 +37,6 @@ 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. - /// - /// # Arguments - /// - /// * `&self` - Read access to the contract's state. - fn base_uri(&self) -> String; } // FIXME: Apply multi-level inheritance to export Metadata's functions. @@ -56,16 +51,66 @@ impl IErc721Metadata for Erc721Metadata { fn symbol(&self) -> String { self._metadata.symbol() } - - fn base_uri(&self) -> String { - self._base_uri.get_string() - } } impl IErc165 for Erc721Metadata { fn supports_interface(interface_id: FixedBytes<4>) -> bool { - ::INTERFACE_ID - == u32::from_be_bytes(*interface_id) + // NOTE: interface id is calculated using additional selector + // [`Erc721Metadata::token_uri`] + 0x_5b5e139f == u32::from_be_bytes(*interface_id) + } +} + +impl Erc721Metadata { + /// Returns the base of Uniform Resource Identifier (URI) for tokens' + /// collection. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + pub fn base_uri(&self) -> String { + self._base_uri.get_string() + } + + /// Returns the Uniform Resource Identifier (URI) for `token_id` token. + /// + /// # Arguments + /// + /// * `&self` - Read access to the contract's state. + /// * `token_id` - Id of a token. + /// * `erc721` - Read access to a contract providing [`IErc721`] interface. + /// + /// # Errors + /// + /// If the token does not exist, then the error + /// [`Error::NonexistentToken`] is returned. + /// + /// NOTE: In order to have [`Erc721Metadata::token_uri`] exposed in ABI, + /// you need to do this manually. + /// + /// # Examples + /// + /// ```rust,ignore + /// #[selector(name = "tokenURI")] + /// pub fn token_uri(&self, token_id: U256) -> Result> { + /// Ok(self.metadata.token_uri(token_id, &self.erc721)?) + /// } + pub fn token_uri( + &self, + token_id: U256, + erc721: &impl IErc721, + ) -> Result { + erc721.owner_of(token_id)?; + + let base_uri = self.base_uri(); + + let token_uri = if base_uri.is_empty() { + String::new() + } else { + base_uri + &token_id.to_string() + }; + + Ok(token_uri) } } diff --git a/contracts/src/token/erc721/extensions/uri_storage.rs b/contracts/src/token/erc721/extensions/uri_storage.rs index 651de00e..644a3dc2 100644 --- a/contracts/src/token/erc721/extensions/uri_storage.rs +++ b/contracts/src/token/erc721/extensions/uri_storage.rs @@ -5,10 +5,9 @@ use alloc::string::String; use alloy_primitives::U256; use alloy_sol_types::sol; -use stylus_sdk::{ - evm, - stylus_proc::{public, sol_storage}, -}; +use stylus_sdk::{evm, stylus_proc::sol_storage}; + +use crate::token::erc721::{extensions::Erc721Metadata, Error, IErc721}; sol! { /// This event gets emitted when the metadata of a token is changed. @@ -48,53 +47,137 @@ impl Erc721UriStorage { self._token_uris.setter(token_id).set_str(token_uri); evm::log(MetadataUpdate { token_id }); } -} -#[public] -impl Erc721UriStorage { /// Returns the Uniform Resource Identifier (URI) for `token_id` token. /// /// # Arguments /// /// * `&self` - Read access to the contract's state. /// * `token_id` - Id of a token. - #[must_use] - pub fn token_uri(&self, token_id: U256) -> String { - self._token_uris.getter(token_id).get_string() + /// * `erc721` - Read access to a contract providing [`IErc721`] interface. + /// * `metadata` - Read access to a [`Erc721Metadata`] contract. + /// + /// # Errors + /// + /// If the token does not exist, then the error + /// [`Error::NonexistentToken`] is returned. + /// + /// NOTE: In order to have [`Erc721UriStorage::token_uri`] exposed in ABI, + /// you need to do this manually. + /// + /// # Examples + /// + /// ```rust,ignore + /// #[selector(name = "tokenURI")] + /// pub fn token_uri(&self, token_id: U256) -> Result> { + /// Ok(self.uri_storage.token_uri( + /// token_id, + /// &self.erc721, + /// &self.metadata, + /// )?) + /// } + pub fn token_uri( + &self, + token_id: U256, + erc721: &impl IErc721, + metadata: &Erc721Metadata, + ) -> Result { + let _owner = erc721.owner_of(token_id)?; + + let token_uri = self._token_uris.getter(token_id).get_string(); + let base = metadata.base_uri(); + + // 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`. + let uri = if !token_uri.is_empty() { + base + &token_uri + } else { + metadata.token_uri(token_id, erc721)? + }; + + Ok(uri) } } #[cfg(all(test, feature = "std"))] mod tests { use alloy_primitives::U256; + use stylus_sdk::{msg, stylus_proc::sol_storage}; use super::Erc721UriStorage; + use crate::token::erc721::{extensions::Erc721Metadata, Erc721}; fn random_token_id() -> U256 { let num: u32 = rand::random(); U256::from(num) } + sol_storage! { + struct Erc721MetadataExample { + Erc721 erc721; + Erc721Metadata metadata; + Erc721UriStorage uri_storage; + } + } + #[motsu::test] - fn get_token_uri_works(contract: Erc721UriStorage) { + fn get_token_uri_works(contract: Erc721MetadataExample) { + let alice = msg::sender(); + 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()); + contract + .erc721 + ._mint(alice, token_id) + .expect("should mint a token for Alice"); - assert_eq!(token_uri, contract.token_uri(token_id)); + let token_uri = String::from("https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#Erc721URIStorage"); + contract + .uri_storage + ._token_uris + .setter(token_id) + .set_str(token_uri.clone()); + + assert_eq!( + token_uri, + contract + .uri_storage + .token_uri(token_id, &contract.erc721, &contract.metadata) + .expect("should return token URI") + ); } #[motsu::test] - fn set_token_uri_works(contract: Erc721UriStorage) { + fn set_token_uri_works(contract: Erc721MetadataExample) { + let alice = msg::sender(); + let token_id = random_token_id(); + contract + .erc721 + ._mint(alice, token_id) + .expect("should mint a token for Alice"); + 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); + contract + .uri_storage + ._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)); + contract.uri_storage._set_token_uri(token_id, token_uri.clone()); + + assert_eq!( + token_uri, + contract + .uri_storage + .token_uri(token_id, &contract.erc721, &contract.metadata) + .expect("should return token URI") + ); } } diff --git a/contracts/src/token/erc721/mod.rs b/contracts/src/token/erc721/mod.rs index 84692aef..a3510568 100644 --- a/contracts/src/token/erc721/mod.rs +++ b/contracts/src/token/erc721/mod.rs @@ -489,8 +489,6 @@ impl IErc721 for Erc721 { to: Address, token_id: U256, ) -> Result<(), Error> { - // TODO: Once the SDK supports the conversion, - // use alloy_primitives::bytes!("") here. self.safe_transfer_from_with_data(from, to, token_id, vec![].into()) } @@ -675,9 +673,6 @@ impl Erc721 { /// * `&mut self` - Write access to the contract's state. /// * `account` - Account to increase balance. /// * `value` - The number of tokens to increase balance. - // 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)); } diff --git a/contracts/src/utils/cryptography/ecdsa.rs b/contracts/src/utils/cryptography/ecdsa.rs index d91f44f7..05aed2b5 100644 --- a/contracts/src/utils/cryptography/ecdsa.rs +++ b/contracts/src/utils/cryptography/ecdsa.rs @@ -30,13 +30,6 @@ sol! { #[allow(missing_docs)] error ECDSAInvalidSignature(); - /// The signature has an invalid length. - /// - /// * `length` - Length of the signature. - #[derive(Debug)] - #[allow(missing_docs)] - error ECDSAInvalidSignatureLength(uint256 length); - /// The signature has an `S` value that is in the upper half order. /// /// * `s` - Invalid `S` value. @@ -50,8 +43,6 @@ sol! { pub enum Error { /// The signature derives the `Address::ZERO`. InvalidSignature(ECDSAInvalidSignature), - /// The signature has an invalid length. - InvalidSignatureLength(ECDSAInvalidSignatureLength), /// The signature has an `S` value that is in the upper half order. InvalidSignatureS(ECDSAInvalidSignatureS), } @@ -186,7 +177,7 @@ fn encode_calldata(hash: B256, v: u8, r: B256, s: B256) -> Vec { /// and for v in (302): v ∈ {27, 28}. /// /// Most signatures from current libraries generate a unique signature -/// with an s-value in the lower half order.ECDSAInvalidSignatureLength +/// with an s-value in the lower half order. /// /// If your library generates malleable signatures, /// such as s-values in the upper range, calculate a new s-value diff --git a/contracts/src/utils/pausable.rs b/contracts/src/utils/pausable.rs index d128ce0d..1a2ed5ae 100644 --- a/contracts/src/utils/pausable.rs +++ b/contracts/src/utils/pausable.rs @@ -7,8 +7,12 @@ //! and [`Pausable::when_paused`], //! which can be added to the functions of your contract. //! -//! Note that they will not be pausable by simply including this module, -//! only once the modifiers are put in place. +//! Note that your contract will not be pausable by simply including this +//! module, only once and where you use [`Pausable::when_not_paused`]. +//! +//! Note that [`Pausable::pause`] and [`Pausable::unpause`] methods are not +//! exposed by default. +//! You should expose them manually in your contract's abi. use alloy_sol_types::sol; use stylus_sdk::{ @@ -69,7 +73,9 @@ impl Pausable { fn paused(&self) -> bool { self._paused.get() } +} +impl Pausable { /// Triggers `Paused` state. /// /// # Arguments @@ -104,7 +110,7 @@ impl Pausable { Ok(()) } - /// Modifier to make a function callable only when the contract is NOT + /// Helper to make a function callable only when the contract is NOT /// paused. /// /// # Arguments @@ -122,7 +128,7 @@ impl Pausable { Ok(()) } - /// Modifier to make a function callable + /// Helper to make a function callable /// only when the contract is paused. /// /// # Arguments diff --git a/contracts/src/utils/structs/checkpoints/generic_size.rs b/contracts/src/utils/structs/checkpoints/generic_size.rs new file mode 100644 index 00000000..033700c6 --- /dev/null +++ b/contracts/src/utils/structs/checkpoints/generic_size.rs @@ -0,0 +1,100 @@ +//! Contains generic size utilities for checkpoint storage contract. + +use core::ops::{Add, Div, Mul, Sub}; + +use stylus_sdk::{alloy_primitives::Uint, prelude::*}; + +/// Trait that associates types of specific size for checkpoints key and value. +pub trait Size { + /// Type of the key in abi. + type Key: Num; + + /// Type of the key in storage. + type KeyStorage: for<'a> StorageType = Self::Key> + + Accessor; + + /// Type of the value in abi. + type Value: Num; + + /// Type of the value in storage. + type ValueStorage: for<'a> StorageType = Self::Value> + + Accessor; +} + +/// Defines size of checkpoint storage contract with specific key and value +/// bits. +/// +/// # Arguments +/// +/// * `$name` - Identifier of the typed size. +/// * `$key_bits` - Number of bits in checkpoint's key. +/// * `$value_bits` - Number of bits in checkpoint's value. +macro_rules! define_checkpoint_size { + ($name:ident, $key_bits:expr, $value_bits:expr) => { + #[doc = "Size of checkpoint storage contract with"] + #[doc = stringify!($key_bits)] + #[doc = "bit key and "] + #[doc = stringify!($value_bits)] + #[doc = "bit value."] + pub struct $name; + + impl Size for $name { + type Key = stylus_sdk::alloy_primitives::Uint< + $key_bits, + { usize::div_ceil($key_bits, 64) }, + >; + type KeyStorage = stylus_sdk::storage::StorageUint< + $key_bits, + { usize::div_ceil($key_bits, 64) }, + >; + type Value = stylus_sdk::alloy_primitives::Uint< + $value_bits, + { usize::div_ceil($value_bits, 64) }, + >; + type ValueStorage = stylus_sdk::storage::StorageUint< + $value_bits, + { usize::div_ceil($value_bits, 64) }, + >; + } + }; +} + +define_checkpoint_size!(S160, 96, 160); +define_checkpoint_size!(S224, 32, 224); +define_checkpoint_size!(S208, 48, 208); + +/// Abstracts number inside the checkpoint contract. +pub trait Num: Add + Sub + Mul + Div + Ord + Sized + Copy { + /// Zero value of the number. + const ZERO: Self; +} + +impl Num for Uint { + const ZERO: Self = Self::ZERO; +} + +/// Abstracts accessor inside the checkpoint contract. +pub trait Accessor { + /// Type of the number associated with the storage type. + type Wraps: Num; + + /// Gets underlying element [`Self::Wraps`] from persistent storage. + fn get(&self) -> Self::Wraps; + + /// Sets underlying element [`Self::Wraps`] in persistent storage. + fn set(&mut self, value: Self::Wraps); +} + +impl Accessor + for stylus_sdk::storage::StorageUint +{ + type Wraps = Uint; + + fn get(&self) -> Self::Wraps { + self.get() + } + + fn set(&mut self, value: Self::Wraps) { + self.set(value); + } +} diff --git a/contracts/src/utils/structs/checkpoints.rs b/contracts/src/utils/structs/checkpoints/mod.rs similarity index 86% rename from contracts/src/utils/structs/checkpoints.rs rename to contracts/src/utils/structs/checkpoints/mod.rs index 4080184c..88b1eb22 100644 --- a/contracts/src/utils/structs/checkpoints.rs +++ b/contracts/src/utils/structs/checkpoints/mod.rs @@ -1,26 +1,27 @@ //! Contract for checkpointing values as they change at different points in //! time, and later looking up and later looking up past values by block number. //! -//! To create a history of checkpoints, define a variable type [`Trace160`] -//! in your contract, and store a new checkpoint for the current transaction -//! block using the [`Trace160::push`] function. -use alloy_primitives::{uint, Uint, U256, U32}; +//! To create a history of checkpoints, define a variable type [`Trace`] +//! in your contract. +//! Types [`S160`], [`S160`] and [`S160`] can be used to +//! define sizes for key and value. +//! Then store a new checkpoint for the current +//! transaction block using the [`Trace::push`] function. +pub mod generic_size; + +use alloy_primitives::{uint, U256, U32}; use alloy_sol_types::sol; +pub use generic_size::{Size, S160, S208, S224}; use stylus_sdk::{ call::MethodError, - storage::{StorageGuard, StorageGuardMut}, - stylus_proc::{sol_storage, SolidityError}, + prelude::*, + storage::{StorageGuard, StorageGuardMut, StorageVec}, }; -use crate::utils::math::alloy::Math; - -// TODO: add generics for other pairs (uint32, uint224) and (uint48, uint208). -// Logic should be the same. -/// [`Uint`] for 96 bits. -pub type U96 = Uint<96, 2>; - -/// [`Uint`] for 160 bits. -pub type U160 = Uint<160, 3>; +use crate::utils::{ + math::alloy::Math, + structs::checkpoints::generic_size::{Accessor, Num}, +}; sol! { /// A value was attempted to be inserted into a past checkpoint. @@ -28,7 +29,7 @@ sol! { error CheckpointUnorderedInsertion(); } -/// An error that occurred while calling the [`Trace160`] checkpoint contract. +/// An error that occurred while calling the [`Trace`] checkpoint contract. #[derive(SolidityError, Debug)] pub enum Error { /// A value was attempted to be inserted into a past checkpoint. @@ -43,22 +44,22 @@ impl MethodError for Error { sol_storage! { /// State of the checkpoint library contract. - pub struct Trace160 { + pub struct Trace { /// Stores checkpoints in a dynamic array sorted by key. - Checkpoint160[] _checkpoints; + StorageVec> _checkpoints; } /// State of a single checkpoint. - pub struct Checkpoint160 { + pub struct Checkpoint { /// The key of the checkpoint. Used as a sorting key. - uint96 _key; + S::KeyStorage _key; /// The value corresponding to the key. - uint160 _value; + S::ValueStorage _value; } } -impl Trace160 { - /// Pushes a (`key`, `value`) pair into a `Trace160` so that it is +impl Trace { + /// Pushes a (`key`, `value`) pair into a `Trace` so that it is /// stored as the checkpoint. /// /// Returns the previous value and the new value as an ordered pair. @@ -79,48 +80,49 @@ impl Trace160 { /// maintain sorted order). pub fn push( &mut self, - key: U96, - value: U160, - ) -> Result<(U160, U160), Error> { + key: S::Key, + value: S::Value, + ) -> Result<(S::Value, S::Value), Error> { self._insert(key, value) } /// Returns the value in the first (oldest) checkpoint with key greater or - /// equal than the search key, or `U160::ZERO` if there is none. + /// equal than the search key, or `S::Value::ZERO` if there is none. /// /// # Arguments /// /// * `&self` - Read access to the checkpoint's state. /// * `key` - Checkpoint's key to lookup. - pub fn lower_lookup(&self, key: U96) -> U160 { + pub fn lower_lookup(&self, key: S::Key) -> S::Value { let len = self.length(); let pos = self._lower_binary_lookup(key, U256::ZERO, len); if pos == len { - U160::ZERO + S::Value::ZERO } else { self._index(pos)._value.get() } } /// Returns the value in the last (most recent) checkpoint with key - /// lower or equal than the search key, or `U160::ZERO` if there is none. + /// lower or equal than the search key, or `S::Value::ZERO` if there is + /// none. /// /// # Arguments /// /// * `&self` - Read access to the checkpoint's state. /// * `key` - Checkpoint's key to lookup. - pub fn upper_lookup(&self, key: U96) -> U160 { + pub fn upper_lookup(&self, key: S::Key) -> S::Value { let len = self.length(); let pos = self._upper_binary_lookup(key, U256::ZERO, len); if pos == U256::ZERO { - U160::ZERO + S::Value::ZERO } else { self._index(pos - uint!(1_U256))._value.get() } } /// Returns the value in the last (most recent) checkpoint with key lower or - /// equal than the search key, or `U160::ZERO` if there is none. + /// equal than the search key, or `S::Value::ZERO` if there is none. /// /// This is a variant of [`Self::upper_lookup`] that is optimized to find /// "recent" checkpoints (checkpoints with high keys). @@ -129,7 +131,7 @@ impl Trace160 { /// /// * `&self` - Read access to the checkpoint's state. /// * `key` - Checkpoint's key to query. - pub fn upper_lookup_recent(&self, key: U96) -> U160 { + pub fn upper_lookup_recent(&self, key: S::Key) -> S::Value { let len = self.length(); let mut low = U256::ZERO; @@ -147,22 +149,22 @@ impl Trace160 { let pos = self._upper_binary_lookup(key, low, high); if pos == U256::ZERO { - U160::ZERO + S::Value::ZERO } else { self._index(pos - uint!(1_U256))._value.get() } } - /// Returns the value in the most recent checkpoint, or `U160::ZERO` if + /// Returns the value in the most recent checkpoint, or `S::Value::ZERO` if /// there are no checkpoints. /// /// # Arguments /// /// * `&self` - Read access to the checkpoint's state. - pub fn latest(&self) -> U160 { + pub fn latest(&self) -> S::Value { let pos = self.length(); if pos == U256::ZERO { - U160::ZERO + S::Value::ZERO } else { self._index(pos - uint!(1_U256))._value.get() } @@ -175,7 +177,7 @@ impl Trace160 { /// # Arguments /// /// * `&self` - Read access to the checkpoint's state. - pub fn latest_checkpoint(&self) -> Option<(U96, U160)> { + pub fn latest_checkpoint(&self) -> Option<(S::Key, S::Value)> { let pos = self.length(); if pos == U256::ZERO { None @@ -204,7 +206,7 @@ impl Trace160 { /// /// * `&self` - Read access to the checkpoint's state. /// * `pos` - Index of the checkpoint. - pub fn at(&self, pos: U32) -> (U96, U160) { + pub fn at(&self, pos: U32) -> (S::Key, S::Value) { let guard = self._checkpoints.get(pos).unwrap_or_else(|| { panic!("should get checkpoint at index `{pos}`") }); @@ -228,9 +230,9 @@ impl Trace160 { /// returned. fn _insert( &mut self, - key: U96, - value: U160, - ) -> Result<(U160, U160), Error> { + key: S::Key, + value: S::Value, + ) -> Result<(S::Value, S::Value), Error> { let pos = self.length(); if pos > U256::ZERO { let last = self._index(pos - uint!(1_U256)); @@ -251,7 +253,7 @@ impl Trace160 { Ok((last_value, value)) } else { self._unchecked_push(key, value); - Ok((U160::ZERO, value)) + Ok((S::Value::ZERO, value)) } } @@ -271,7 +273,7 @@ impl Trace160 { /// * `high` - Exclusive index where search ends. fn _upper_binary_lookup( &self, - key: U96, + key: S::Key, mut low: U256, mut high: U256, ) -> U256 { @@ -302,7 +304,7 @@ impl Trace160 { /// * `high` - Exclusive index where search ends. fn _lower_binary_lookup( &self, - key: U96, + key: S::Key, mut low: U256, mut high: U256, ) -> U256 { @@ -328,7 +330,7 @@ impl Trace160 { /// /// * `&self` - Read access to the checkpoint's state. /// * `pos` - Index of the checkpoint. - fn _index(&self, pos: U256) -> StorageGuard { + fn _index(&self, pos: U256) -> StorageGuard> { self._checkpoints .get(pos) .unwrap_or_else(|| panic!("should get checkpoint at index `{pos}`")) @@ -345,7 +347,7 @@ impl Trace160 { /// /// * `&mut self` - Write access to the checkpoint's state. /// * `pos` - Index of the checkpoint. - fn _index_mut(&mut self, pos: U256) -> StorageGuardMut { + fn _index_mut(&mut self, pos: U256) -> StorageGuardMut> { self._checkpoints .setter(pos) .unwrap_or_else(|| panic!("should get checkpoint at index `{pos}`")) @@ -358,7 +360,7 @@ impl Trace160 { /// * `&mut self` - Write access to the checkpoint's state. /// * `key` - Checkpoint key to insert. /// * `value` - Checkpoint value corresponding to insertion `key`. - fn _unchecked_push(&mut self, key: U96, value: U160) { + fn _unchecked_push(&mut self, key: S::Key, value: S::Value) { let mut new_checkpoint = self._checkpoints.grow(); new_checkpoint._key.set(key); new_checkpoint._value.set(value); @@ -370,11 +372,11 @@ mod tests { use alloy_primitives::uint; use crate::utils::structs::checkpoints::{ - CheckpointUnorderedInsertion, Error, Trace160, + generic_size::S160, CheckpointUnorderedInsertion, Error, Trace, }; #[motsu::test] - fn push(checkpoint: Trace160) { + fn push(checkpoint: Trace) { let first_key = uint!(1_U96); let first_value = uint!(11_U160); @@ -396,7 +398,7 @@ mod tests { } #[motsu::test] - fn push_same_value(checkpoint: Trace160) { + fn push_same_value(checkpoint: Trace) { let first_key = uint!(1_U96); let first_value = uint!(11_U160); @@ -421,7 +423,7 @@ mod tests { } #[motsu::test] - fn lower_lookup(checkpoint: Trace160) { + fn lower_lookup(checkpoint: Trace) { checkpoint.push(uint!(1_U96), uint!(11_U160)).expect("push first"); checkpoint.push(uint!(3_U96), uint!(33_U160)).expect("push second"); checkpoint.push(uint!(5_U96), uint!(55_U160)).expect("push third"); @@ -433,7 +435,7 @@ mod tests { } #[motsu::test] - fn upper_lookup(checkpoint: Trace160) { + fn upper_lookup(checkpoint: Trace) { checkpoint.push(uint!(1_U96), uint!(11_U160)).expect("push first"); checkpoint.push(uint!(3_U96), uint!(33_U160)).expect("push second"); checkpoint.push(uint!(5_U96), uint!(55_U160)).expect("push third"); @@ -445,7 +447,7 @@ mod tests { } #[motsu::test] - fn upper_lookup_recent(checkpoint: Trace160) { + fn upper_lookup_recent(checkpoint: Trace) { // `upper_lookup_recent` has different optimizations for "short" (<=5) // and "long" (>5) checkpoint arrays. // @@ -489,7 +491,7 @@ mod tests { } #[motsu::test] - fn latest(checkpoint: Trace160) { + fn latest(checkpoint: Trace) { assert_eq!(checkpoint.latest(), uint!(0_U160)); checkpoint.push(uint!(1_U96), uint!(11_U160)).expect("push first"); checkpoint.push(uint!(3_U96), uint!(33_U160)).expect("push second"); @@ -498,7 +500,7 @@ mod tests { } #[motsu::test] - fn latest_checkpoint(checkpoint: Trace160) { + fn latest_checkpoint(checkpoint: Trace) { assert_eq!(checkpoint.latest_checkpoint(), None); checkpoint.push(uint!(1_U96), uint!(11_U160)).expect("push first"); checkpoint.push(uint!(3_U96), uint!(33_U160)).expect("push second"); @@ -510,7 +512,7 @@ mod tests { } #[motsu::test] - fn error_when_unordered_insertion(checkpoint: Trace160) { + fn error_when_unordered_insertion(checkpoint: Trace) { checkpoint.push(uint!(1_U96), uint!(11_U160)).expect("push first"); checkpoint.push(uint!(3_U96), uint!(33_U160)).expect("push second"); let err = checkpoint diff --git a/docs/antora.yml b/docs/antora.yml index 524da2ac..9c514982 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,6 +1,5 @@ name: contracts-stylus title: Contracts for Stylus version: 0.1.0 -prerelease: '-rc' #we can remove this with the official release nav: - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc index a690cfed..d4d66d35 100644 --- a/docs/modules/ROOT/pages/access-control.adoc +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -7,7 +7,7 @@ Access control—that is, "who is allowed to do this thing"—is incredibly impo The most common and basic form of access control is the concept of _ownership_: there's an account that is the `owner` of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user. -OpenZeppelin Contracts for Stylus provides https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/access/ownable/struct.Ownable.html[`Ownable`] for implementing ownership in your contracts. +OpenZeppelin Contracts for Stylus provides https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html[`Ownable`] for implementing ownership in your contracts. [source,rust] ---- @@ -21,7 +21,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Ownable)] impl MyContract { fn normal_thing(&self) { @@ -40,12 +40,12 @@ impl MyContract { } ---- -At deployment, the https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.owner[`owner`] of an `Ownable` contract is set to the provided `initial_owner` parameter. +At deployment, the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.owner[`owner`] of an `Ownable` contract is set to the provided `initial_owner` parameter. Ownable also lets you: -* https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.transfer_ownership[`transfer_ownership`] from the owner account to a new one, and -* https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.renounce_ownership[`renounce_ownership`] for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. +* https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.transfer_ownership[`transfer_ownership`] from the owner account to a new one, and +* https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.renounce_ownership[`renounce_ownership`] for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. WARNING: Removing the owner altogether will mean that administrative tasks that are protected by `only_owner` will no longer be callable! @@ -66,7 +66,7 @@ Most software uses access control systems that are role-based: some users are re [[using-access-control]] === Using `AccessControl` -OpenZeppelin Contracts provides https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/access/control/struct.AccessControl.html[`AccessControl`] for implementing role-based access control. Its usage is straightforward: for each role that you want to define, +OpenZeppelin Contracts provides https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/control/struct.AccessControl.html[`AccessControl`] for implementing role-based access control. Its usage is straightforward: for each role that you want to define, you will create a new _role identifier_ that is used to grant, revoke, and check if an account has that role. Here's a simple example of using `AccessControl` in an xref:erc20.adoc[ERC-20 token] to define a 'minter' role, which allows accounts that have it create new tokens. Note that the example is unassuming of the way you construct your contract. @@ -90,7 +90,7 @@ pub const MINTER_ROLE: [u8; 32] = [ 166, ]; -#[external] +#[public] #[inherit(Erc20, AccessControl)] impl Example { pub const MINTER_ROLE: [u8; 32] = MINTER_ROLE; @@ -105,7 +105,7 @@ impl Example { } ---- -NOTE: Make sure you fully understand how https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/access/control/struct.AccessControl.html[`AccessControl`] works before using it on your system, or copy-pasting the examples from this guide. +NOTE: Make sure you fully understand how https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/control/struct.AccessControl.html[`AccessControl`] works before using it on your system, or copy-pasting the examples from this guide. While clear and explicit, this isn't anything we wouldn't have been able to achieve with `Ownable`. Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles. @@ -136,7 +136,7 @@ pub const BURNER_ROLE: [u8; 32] = [ 224, 87, 73, 143, 95, 0, 36, 97, 144, 234, 84, 34, 5, 118, 168, 72, ]; -#[external] +#[public] #[inherit(Erc20, AccessControl)] impl Example { pub const MINTER_ROLE: [u8; 32] = MINTER_ROLE; diff --git a/docs/modules/ROOT/pages/crypto.adoc b/docs/modules/ROOT/pages/crypto.adoc index 72a32985..1fdb6db3 100644 --- a/docs/modules/ROOT/pages/crypto.adoc +++ b/docs/modules/ROOT/pages/crypto.adoc @@ -8,11 +8,11 @@ Developers can build a Merkle Tree off-chain, which allows for verifying that an TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs. -https://docs.rs/openzeppelin-crypto/0.1.0-rc/openzeppelin_crypto/merkle/struct.Verifier.html[`MerkleProof`] provides: +https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html[`MerkleProof`] provides: -* https://docs.rs/openzeppelin-crypto/0.1.0-rc/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree]. +* https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree]. -* https://docs.rs/openzeppelin-crypto/0.1.0-rc/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof[`verify_multi_proof`] - can prove multiple values are part of a Merkle tree. +* https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof[`verify_multi_proof`] - can prove multiple values are part of a Merkle tree. [source,rust] ---- @@ -22,6 +22,6 @@ pub fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { } ---- -Note that these functions use `keccak256` as the hashing algorithm, but our library also provides generic counterparts: https://docs.rs/openzeppelin-crypto/0.1.0-rc/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_with_builder[`verify_with_builder`] and https://docs.rs/openzeppelin-crypto/0.1.0-rc/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof_with_builder[`verify_multi_proof_with_builder`]. +Note that these functions use `keccak256` as the hashing algorithm, but our library also provides generic counterparts: https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_with_builder[`verify_with_builder`] and https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof_with_builder[`verify_multi_proof_with_builder`]. -We also provide an adapter https://docs.rs/openzeppelin-crypto/0.1.0-rc/openzeppelin_crypto/hash/index.html[`hash`] module to use your own hashers in conjunction with them that resembles Rust's standard library's API. +We also provide an adapter https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/hash/index.html[`hash`] module to use your own hashers in conjunction with them that resembles Rust's standard library's API. diff --git a/docs/modules/ROOT/pages/deploy.adoc b/docs/modules/ROOT/pages/deploy.adoc index 82ae3515..3cff7ea0 100644 --- a/docs/modules/ROOT/pages/deploy.adoc +++ b/docs/modules/ROOT/pages/deploy.adoc @@ -37,7 +37,7 @@ sol_storage! { } } -#[external] +#[public] impl Counter { pub fn number(&self) -> U256 { self.number.get() diff --git a/docs/modules/ROOT/pages/erc20-burnable.adoc b/docs/modules/ROOT/pages/erc20-burnable.adoc index 74311ced..5e14a975 100644 --- a/docs/modules/ROOT/pages/erc20-burnable.adoc +++ b/docs/modules/ROOT/pages/erc20-burnable.adoc @@ -5,7 +5,7 @@ Extension of xref:erc20.adoc[ERC-20] that allows token holders to destroy both t [[usage]] == Usage -In order to make https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/extensions/burnable/index.html[`ERC-20 Burnable`] methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: +In order to make https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/burnable/index.html[`ERC-20 Burnable`] methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: [source,rust] ---- @@ -24,7 +24,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20)] impl Erc20Example { pub fn burn(&mut self, value: U256) -> Result<(), Vec> { diff --git a/docs/modules/ROOT/pages/erc20-capped.adoc b/docs/modules/ROOT/pages/erc20-capped.adoc index 2b4a1c05..ec03971e 100644 --- a/docs/modules/ROOT/pages/erc20-capped.adoc +++ b/docs/modules/ROOT/pages/erc20-capped.adoc @@ -5,7 +5,7 @@ Extension of xref:erc20.adoc[ERC-20] that adds a cap to the supply of tokens. [[usage]] == Usage -In order to make https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/extensions/capped/index.html[`ERC-20 Capped`] methods supervising the supply of tokens, you need to add them by yourself for your final contract as follows: +In order to make https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/capped/index.html[`ERC-20 Capped`] methods supervising the supply of tokens, you need to add them by yourself for your final contract as follows: [source,rust] ---- @@ -26,7 +26,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20, Capped)] impl Erc20Example { // Add token minting feature. diff --git a/docs/modules/ROOT/pages/erc20-metadata.adoc b/docs/modules/ROOT/pages/erc20-metadata.adoc index c90019fc..89aeb89c 100644 --- a/docs/modules/ROOT/pages/erc20-metadata.adoc +++ b/docs/modules/ROOT/pages/erc20-metadata.adoc @@ -5,7 +5,7 @@ Extension of xref:erc20.adoc[ERC-20] that adds the optional metadata functions f [[usage]] == Usage -In order to make https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/extensions/metadata/index.html[`ERC-20 Metadata`] methods “external” so that other contracts can call them, you need to add the following code to your contract: +In order to make https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/metadata/index.html[`ERC-20 Metadata`] methods “external” so that other contracts can call them, you need to add the following code to your contract: [source,rust] ---- @@ -26,7 +26,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20, Erc20Metadata, Capped, Pausable)] impl Erc20Example { // ... diff --git a/docs/modules/ROOT/pages/erc20-pausable.adoc b/docs/modules/ROOT/pages/erc20-pausable.adoc index 2b8e95ec..019ad44d 100644 --- a/docs/modules/ROOT/pages/erc20-pausable.adoc +++ b/docs/modules/ROOT/pages/erc20-pausable.adoc @@ -7,7 +7,7 @@ Useful for scenarios such as preventing trades until the end of an evaluation pe [[usage]] == Usage -In order to make your ERC20 token `pausable`, you need to use the https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/pausable/index.html[`Pausable`] contract and apply its mechanisms to ERC20 token functions as follows: +In order to make your ERC20 token `pausable`, you need to use the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/pausable/index.html[`Pausable`] contract and apply its mechanisms to ERC20 token functions as follows: [source,rust] ---- @@ -26,7 +26,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20, Pausable)] impl Erc20Example { pub fn burn(&mut self, value: U256) -> Result<(), Vec> { diff --git a/docs/modules/ROOT/pages/erc20-permit.adoc b/docs/modules/ROOT/pages/erc20-permit.adoc index 11004e87..7be277ba 100644 --- a/docs/modules/ROOT/pages/erc20-permit.adoc +++ b/docs/modules/ROOT/pages/erc20-permit.adoc @@ -1,13 +1,13 @@ = ERC-20 Permit Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[`EIP-2612`]. -Adds the permit method, which can be used to change an account’s ERC20 allowance (see https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.allowance[`IErc20::allowance`]) by presenting a message signed by the account. By not relying on https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.approve[`IErc20::approve`], the token holder account doesn’t need to send a transaction, and thus is not required to hold Ether at all. +Adds the permit method, which can be used to change an account’s ERC20 allowance (see https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.allowance[`IErc20::allowance`]) by presenting a message signed by the account. By not relying on https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.approve[`IErc20::approve`], the token holder account doesn’t need to send a transaction, and thus is not required to hold Ether at all. [[usage]] == Usage -In order to have https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/extensions/permit/index.html[`ERC-20 Permit`] token, you need to use only this contract without xref:erc20.adoc[ERC-20] as follows: +In order to have https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/permit/index.html[`ERC-20 Permit`] token, you need to use only this contract without xref:erc20.adoc[ERC-20] as follows: [source,rust] ---- @@ -31,7 +31,7 @@ impl IEip712 for Eip712 { const VERSION: &'static str = "1"; } -#[external] +#[public] #[inherit(Erc20Permit)] impl Erc20PermitExample { // ... diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index 2c6a7cf1..b1c4940b 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -4,7 +4,7 @@ An ERC-20 token contract keeps track of xref:tokens.adoc#different-kinds-of-toke This makes ERC-20 tokens useful for things like a *medium of exchange currency*, *voting rights*, *staking*, and more. OpenZeppelin Contracts provide many ERC20-related contracts for Arbitrum Stylus. -On the https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/struct.Erc20.html[`API reference`] you'll find detailed information on their properties and usage. +On the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/struct.Erc20.html[`API reference`] you'll find detailed information on their properties and usage. [[constructing-an-erc20-token-contract]] == Constructing an ERC-20 Token Contract @@ -25,12 +25,12 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc20, Erc20Metadata)] impl GLDToken {} ---- -Our contracts are often used via stylus-sdk https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#inheritance-inherit-and-borrow[inheritance], and here we're reusing https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/struct.Erc20.html[`ERC20`] for both the basic standard implementation and with optional extensions. +Our contracts are often used via stylus-sdk https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#inheritance-inherit-and-borrow[inheritance], and here we're reusing https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/struct.Erc20.html[`ERC20`] for both the basic standard implementation and with optional extensions. [[a-note-on-decimals]] == A Note on `decimals` @@ -39,7 +39,7 @@ Often, you'll want to be able to divide your tokens into arbitrary amounts: say, Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send `1` or `2` tokens, but not `1.5`. -To work around this, ERC20 provides a https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc20/extensions/metadata/trait.IErc20Metadata.html#tymethod.decimals[`decimals`] field, which is used to specify how many decimal places a token has. +To work around this, ERC20 provides a https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/metadata/trait.IErc20Metadata.html#tymethod.decimals[`decimals`] field, which is used to specify how many decimal places a token has. To be able to transfer `1.5 GLD`, `decimals` must be at least `1`, since that number has a single decimal place. How can this be achieved? diff --git a/docs/modules/ROOT/pages/erc721-burnable.adoc b/docs/modules/ROOT/pages/erc721-burnable.adoc index c045f3aa..e72f45ec 100644 --- a/docs/modules/ROOT/pages/erc721-burnable.adoc +++ b/docs/modules/ROOT/pages/erc721-burnable.adoc @@ -5,7 +5,7 @@ xref:erc721.adoc[ERC-721] Token that can be burned (destroyed). [[usage]] == Usage -In order to make https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/burnable/index.html[`ERC-721 Burnable`] methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: +In order to make https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/burnable/index.html[`ERC-721 Burnable`] methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: [source,rust] ---- @@ -24,7 +24,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc721)] impl Erc721Example { pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { diff --git a/docs/modules/ROOT/pages/erc721-consecutive.adoc b/docs/modules/ROOT/pages/erc721-consecutive.adoc index 64c36400..302f133a 100644 --- a/docs/modules/ROOT/pages/erc721-consecutive.adoc +++ b/docs/modules/ROOT/pages/erc721-consecutive.adoc @@ -5,7 +5,7 @@ Consecutive extension for xref:erc721.adoc[ERC-721] is useful for efficiently mi [[usage]] == Usage -In order to make https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/consecutive/index.html[`ERC-721 Consecutive`] methods “external” so that other contracts can call them, you need to add the following code to your contract: +In order to make https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/consecutive/index.html[`ERC-721 Consecutive`] methods “external” so that other contracts can call them, you need to add the following code to your contract: [source,rust] ---- @@ -21,7 +21,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc721Consecutive)] impl Erc721ConsecutiveExample { pub fn burn(&mut self, token_id: U256) -> Result<(), Error> { diff --git a/docs/modules/ROOT/pages/erc721-enumerable.adoc b/docs/modules/ROOT/pages/erc721-enumerable.adoc index 189d4e2e..a60c1a27 100644 --- a/docs/modules/ROOT/pages/erc721-enumerable.adoc +++ b/docs/modules/ROOT/pages/erc721-enumerable.adoc @@ -5,7 +5,7 @@ The OpenZeppelin xref:erc721.adoc[ERC-721] Enumerable extension is used to provi [[usage]] == Usage -In order to make an xref:erc721.adoc[ERC-721] token with https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/enumerable/index.html[Enumerable] flavour, +In order to make an xref:erc721.adoc[ERC-721] token with https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/enumerable/index.html[Enumerable] flavour, you need to create a specified contract as follows: [source,rust] @@ -25,7 +25,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc721, Erc721Enumerable)] impl Erc721Example { pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { diff --git a/docs/modules/ROOT/pages/erc721-metadata.adoc b/docs/modules/ROOT/pages/erc721-metadata.adoc index 3b6f8702..1071de97 100644 --- a/docs/modules/ROOT/pages/erc721-metadata.adoc +++ b/docs/modules/ROOT/pages/erc721-metadata.adoc @@ -5,7 +5,7 @@ Extension of xref:erc721.adoc[ERC-721] that adds the optional metadata functions [[usage]] == Usage -In order to make https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/metadata/index.html[`ERC-721 Metadata`] methods “external” so that other contracts can call them, you need to add the following code to your contract: +In order to make https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html[`ERC-721 Metadata`] methods “external” so that other contracts can call them, you need to add the following code to your contract: [source,rust] ---- @@ -26,10 +26,15 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc721, Erc721Metadata)] impl Erc721Example { // ... + + #[selector(name = "tokenURI")] + pub fn token_uri(&self, token_id: U256) -> Result> { + Ok(self.metadata.token_uri(token_id, &self.erc721)?) + } } ---- diff --git a/docs/modules/ROOT/pages/erc721-pausable.adoc b/docs/modules/ROOT/pages/erc721-pausable.adoc index 2d2716cf..fa77b273 100644 --- a/docs/modules/ROOT/pages/erc721-pausable.adoc +++ b/docs/modules/ROOT/pages/erc721-pausable.adoc @@ -7,7 +7,7 @@ Useful for scenarios such as preventing trades until the end of an evaluation pe [[usage]] == Usage -In order to make your ERC721 token `pausable`, you need to use the https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/pausable/index.html[`Pausable`] contract and apply its mechanisms to ERC721 token functions as follows: +In order to make your ERC721 token `pausable`, you need to use the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/pausable/index.html[`Pausable`] contract and apply its mechanisms to ERC721 token functions as follows: [source,rust] ---- @@ -26,7 +26,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc721, Pausable)] impl Erc721Example { pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { diff --git a/docs/modules/ROOT/pages/erc721-uri-storage.adoc b/docs/modules/ROOT/pages/erc721-uri-storage.adoc index 10940502..274fe719 100644 --- a/docs/modules/ROOT/pages/erc721-uri-storage.adoc +++ b/docs/modules/ROOT/pages/erc721-uri-storage.adoc @@ -7,8 +7,8 @@ This is particularly useful for non-fungible tokens (NFTs) where each token is u [[usage]] == Usage -In order to make an xref:erc721.adoc[ERC-721] token with https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/uri_storage/index.html[URI Storage] flavour, -your token should also use https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/metadata/index.html[`ERC-721 Metadata`] extension to provide additional metadata for each token. +In order to make an xref:erc721.adoc[ERC-721] token with https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/uri_storage/index.html[URI Storage] flavour, +your token should also use https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html[`ERC-721 Metadata`] extension to provide additional metadata for each token. You need to create a specified contract as follows: [source,rust] @@ -28,12 +28,11 @@ sol_storage! { Erc721 erc721; #[borrow] Erc721Metadata metadata; - #[borrow] Erc721UriStorage uri_storage; } } -#[external] +#[public] #[inherit(Erc721, Erc721Metadata, Erc721UriStorage)] impl Erc721MetadataExample { pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Vec> { @@ -46,25 +45,11 @@ impl Erc721MetadataExample { #[selector(name = "tokenURI")] pub fn token_uri(&self, token_id: U256) -> Result> { - let _owner = self.erc721.owner_of(token_id)?; - - let base = self.metadata.base_uri(); - let token_uri = self.uri_storage.token_uri(token_id); - - // 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. - let uri = if !token_uri.is_empty() { - base + &token_uri - } else { - base + &token_id.to_string() - }; - - Ok(uri) + Ok(self.uri_storage.token_uri( + token_id, + &self.erc721, + &self.metadata, + )?) } #[selector(name = "setTokenURI")] diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 9cb7042a..c05e8107 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -6,7 +6,7 @@ ERC-721 is a standard for representing ownership of xref:tokens.adoc#different-k ERC-721 is a more complex standard than ERC-20, with multiple optional extensions, and is split across a number of contracts. The OpenZeppelin Contracts provide flexibility regarding how these are combined, along with custom useful extensions. -Check out the https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/struct.Erc721.html[`API reference`] to learn more about these. +Check out the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/struct.Erc721.html[`API reference`] to learn more about these. == Constructing an ERC-721 Token Contract @@ -32,7 +32,7 @@ sol_storage! { } } -#[external] +#[public] #[inherit(Erc721, Metadata)] impl GameItem { pub fn award_item( @@ -49,7 +49,7 @@ impl GameItem { } ---- -The https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/token/erc721/extensions/metadata/struct.Erc721Metadata.html[`Erc721Metadata`] contract is an extension contract of ERC-721. +The https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/metadata/struct.Erc721Metadata.html[`Erc721Metadata`] contract is an extension contract of ERC-721. It extends the contract itself with the name, symbol and base uri for the token. Also note that, unlike ERC-20, ERC-721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 01bf3f87..4db1e5b3 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -4,6 +4,4 @@ *A library for secure smart contract development* written in Rust for {stylus}. -WARNING: This repo contains highly experimental code. It has never been audited nor thoroughly reviewed for security vulnerabilities. *Use at your own risk.* - NOTE: You can track our roadmap in our https://github.com/orgs/OpenZeppelin/projects/35[Github Project]. diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 249752b7..0d6bc569 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -1,7 +1,7 @@ = Utilities The OpenZeppelin Stylus Contracts provides a ton of useful utilities that you can use in your project. -For a complete list, check out the https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/index.html[API Reference]. +For a complete list, check out the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/index.html[API Reference]. Here are some of the more popular ones. [[introspection]] @@ -11,7 +11,7 @@ It's frequently helpful to know whether a contract supports an interface you'd l https://eips.ethereum.org/EIPS/eip-165[`ERC-165`] is a standard that helps do runtime interface detection. Contracts for Stylus provides helpers for implementing ERC-165 in your contracts: -* https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html[`IERC165`] — this is the ERC-165 trait that defines https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`]. In order to implement ERC-165 interface detection, you should manually expose https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`] function in your contract. +* https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html[`IERC165`] — this is the ERC-165 trait that defines https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`]. In order to implement ERC-165 interface detection, you should manually expose https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`] function in your contract. [source,rust] ---- @@ -39,5 +39,5 @@ impl Erc721Example { Some use cases require more powerful data structures than arrays and mappings offered natively in alloy and the Stylus sdk. Contracts for Stylus provides these libraries for enhanced data structure management: -- https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. -- https://docs.rs/openzeppelin-stylus/0.1.0-rc/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. \ No newline at end of file +- https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. +- https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. diff --git a/examples/erc20/src/lib.rs b/examples/erc20/src/lib.rs index ace37b5f..4d8ab3e0 100644 --- a/examples/erc20/src/lib.rs +++ b/examples/erc20/src/lib.rs @@ -110,4 +110,12 @@ impl Erc20Example { Erc20::supports_interface(interface_id) || Erc20Metadata::supports_interface(interface_id) } + + pub fn pause(&mut self) -> Result<(), Vec> { + self.pausable.pause().map_err(|e| e.into()) + } + + pub fn unpause(&mut self) -> Result<(), Vec> { + self.pausable.unpause().map_err(|e| e.into()) + } } diff --git a/examples/erc20/tests/abi/mod.rs b/examples/erc20/tests/abi/mod.rs index f042e396..eb835350 100644 --- a/examples/erc20/tests/abi/mod.rs +++ b/examples/erc20/tests/abi/mod.rs @@ -24,10 +24,6 @@ sol!( function paused() external view returns (bool paused); function pause() external; function unpause() external; - #[derive(Debug)] - function whenPaused() external view; - #[derive(Debug)] - function whenNotPaused() external view; function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); diff --git a/examples/erc20/tests/erc20.rs b/examples/erc20/tests/erc20.rs index a1301939..a61393c0 100644 --- a/examples/erc20/tests/erc20.rs +++ b/examples/erc20/tests/erc20.rs @@ -1061,18 +1061,6 @@ async fn pauses(alice: Account) -> eyre::Result<()> { assert_eq!(true, paused); - let result = contract.whenPaused().call().await; - - assert!(result.is_ok()); - - let err = contract - .whenNotPaused() - .call() - .await - .expect_err("should return `EnforcedPause`"); - - assert!(err.reverted_with(Erc20::EnforcedPause {})); - Ok(()) } @@ -1096,18 +1084,6 @@ async fn unpauses(alice: Account) -> eyre::Result<()> { assert_eq!(false, paused); - let result = contract.whenNotPaused().call().await; - - assert!(result.is_ok()); - - let err = contract - .whenPaused() - .call() - .await - .expect_err("should return `ExpectedPause`"); - - assert!(err.reverted_with(Erc20::ExpectedPause {})); - Ok(()) } diff --git a/examples/erc721-metadata/src/lib.rs b/examples/erc721-metadata/src/lib.rs index 006efa56..34aa92f8 100644 --- a/examples/erc721-metadata/src/lib.rs +++ b/examples/erc721-metadata/src/lib.rs @@ -1,18 +1,15 @@ #![cfg_attr(not(test), no_main, no_std)] extern crate alloc; -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; use alloy_primitives::{Address, U256}; use openzeppelin_stylus::token::erc721::{ extensions::{ Erc721Metadata as Metadata, Erc721UriStorage as UriStorage, - IErc721Burnable, IErc721Metadata, + IErc721Burnable, }, - Erc721, IErc721, + Erc721, }; use stylus_sdk::prelude::{entrypoint, public, sol_storage}; @@ -23,13 +20,12 @@ sol_storage! { Erc721 erc721; #[borrow] Metadata metadata; - #[borrow] UriStorage uri_storage; } } #[public] -#[inherit(Erc721, Metadata, UriStorage)] +#[inherit(Erc721, Metadata)] impl Erc721MetadataExample { pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Vec> { Ok(self.erc721._mint(to, token_id)?) @@ -39,29 +35,13 @@ impl Erc721MetadataExample { Ok(self.erc721.burn(token_id)?) } - // Overrides [`Erc721UriStorage::token_uri`]. - // Returns the Uniform Resource Identifier (URI) for tokenId token. #[selector(name = "tokenURI")] pub fn token_uri(&self, token_id: U256) -> Result> { - let _owner = self.erc721.owner_of(token_id)?; - - let base = self.metadata.base_uri(); - let token_uri = self.uri_storage.token_uri(token_id); - - // 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. - let uri = if !token_uri.is_empty() { - base + &token_uri - } else { - base + &token_id.to_string() - }; - - Ok(uri) + Ok(self.uri_storage.token_uri( + token_id, + &self.erc721, + &self.metadata, + )?) } #[selector(name = "setTokenURI")] diff --git a/examples/erc721-metadata/tests/abi/mod.rs b/examples/erc721-metadata/tests/abi/mod.rs index 9fd31eae..cec57cc1 100644 --- a/examples/erc721-metadata/tests/abi/mod.rs +++ b/examples/erc721-metadata/tests/abi/mod.rs @@ -19,7 +19,6 @@ sol!( function transferFrom(address from, address to, uint256 tokenId) external; function mint(address to, uint256 tokenId) external; function burn(uint256 tokenId) external; - function baseUri() external view returns (string memory baseURI); function name() external view returns (string memory name); function symbol() external view returns (string memory symbol); #[derive(Debug)] diff --git a/examples/erc721-metadata/tests/erc721.rs b/examples/erc721-metadata/tests/erc721.rs index 9e7d7f73..fee25c9d 100644 --- a/examples/erc721-metadata/tests/erc721.rs +++ b/examples/erc721-metadata/tests/erc721.rs @@ -35,11 +35,11 @@ fn ctr(base_uri: &str) -> constructorCall { #[e2e::test] async fn constructs(alice: Account) -> eyre::Result<()> { - let base_uri = ""; - let contract_addr = alice .as_deployer() - .with_constructor(ctr(base_uri)) + .with_constructor(ctr( + "https://github.com/OpenZeppelin/rust-contracts-stylus", + )) .deploy() .await? .address()?; @@ -47,30 +47,9 @@ async fn constructs(alice: Account) -> eyre::Result<()> { let Erc721::nameReturn { name } = contract.name().call().await?; let Erc721::symbolReturn { symbol } = contract.symbol().call().await?; - let Erc721::baseUriReturn { baseURI } = contract.baseUri().call().await?; assert_eq!(TOKEN_NAME.to_owned(), name); assert_eq!(TOKEN_SYMBOL.to_owned(), symbol); - assert_eq!(base_uri.to_owned(), baseURI); - - Ok(()) -} - -#[e2e::test] -async fn constructs_with_base_uri(alice: Account) -> eyre::Result<()> { - let base_uri = "https://github.com/OpenZeppelin/rust-contracts-stylus"; - - let contract_addr = alice - .as_deployer() - .with_constructor(ctr(base_uri)) - .deploy() - .await? - .address()?; - let contract = Erc721::new(contract_addr, &alice.wallet); - - let Erc721::baseUriReturn { baseURI } = contract.baseUri().call().await?; - - assert_eq!(base_uri.to_owned(), baseURI); Ok(()) } diff --git a/examples/erc721/src/lib.rs b/examples/erc721/src/lib.rs index 3b1c22ef..d77d9913 100644 --- a/examples/erc721/src/lib.rs +++ b/examples/erc721/src/lib.rs @@ -156,4 +156,12 @@ impl Erc721Example { Erc721::supports_interface(interface_id) || Enumerable::supports_interface(interface_id) } + + pub fn pause(&mut self) -> Result<(), Vec> { + self.pausable.pause().map_err(|e| e.into()) + } + + pub fn unpause(&mut self) -> Result<(), Vec> { + self.pausable.unpause().map_err(|e| e.into()) + } } diff --git a/examples/erc721/tests/abi/mod.rs b/examples/erc721/tests/abi/mod.rs index 796f4174..5d080796 100644 --- a/examples/erc721/tests/abi/mod.rs +++ b/examples/erc721/tests/abi/mod.rs @@ -25,10 +25,6 @@ sol!( function paused() external view returns (bool paused); function pause() external; function unpause() external; - #[derive(Debug)] - function whenPaused() external view; - #[derive(Debug)] - function whenNotPaused() external view; #[derive(Debug)] function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); diff --git a/examples/erc721/tests/erc721.rs b/examples/erc721/tests/erc721.rs index f00504a9..8823e1bc 100644 --- a/examples/erc721/tests/erc721.rs +++ b/examples/erc721/tests/erc721.rs @@ -1371,18 +1371,6 @@ async fn pauses(alice: Account) -> eyre::Result<()> { assert!(paused); - let result = contract.whenPaused().call().await; - - assert!(result.is_ok()); - - let err = contract - .whenNotPaused() - .call() - .await - .expect_err("should return `EnforcedPause`"); - - assert!(err.reverted_with(Erc721::EnforcedPause {})); - Ok(()) } @@ -1401,18 +1389,6 @@ async fn unpauses(alice: Account) -> eyre::Result<()> { assert_eq!(false, paused); - let result = contract.whenNotPaused().call().await; - - assert!(result.is_ok()); - - let err = contract - .whenPaused() - .call() - .await - .expect_err("should return `ExpectedPause`"); - - assert!(err.reverted_with(Erc721::ExpectedPause {})); - Ok(()) } diff --git a/lib/crypto/README.md b/lib/crypto/README.md index 081bdb19..9b06a9a2 100644 --- a/lib/crypto/README.md +++ b/lib/crypto/README.md @@ -26,9 +26,4 @@ the [Cargo.toml](./Cargo.toml) file. ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/lib/crypto/src/hash.rs b/lib/crypto/src/hash.rs index 65a64110..b8f98fd5 100644 --- a/lib/crypto/src/hash.rs +++ b/lib/crypto/src/hash.rs @@ -142,16 +142,16 @@ where /// Sort the pair `(a, b)` and hash the result with `state`. Frequently used /// when working with merkle proofs. #[inline] -pub fn commutative_hash_pair(mut a: H, mut b: H, state: S) -> S::Output +pub fn commutative_hash_pair(a: &H, b: &H, state: S) -> S::Output where H: Hash + PartialOrd, S: Hasher, { if a > b { - core::mem::swap(&mut a, &mut b); + hash_pair(b, a, state) + } else { + hash_pair(a, b, state) } - - hash_pair(&a, &b, state) } #[cfg(all(test, feature = "std"))] @@ -185,8 +185,8 @@ mod tests { let a = [1u8].as_slice(); let b = [2u8].as_slice(); - let r1 = commutative_hash_pair(a, b, builder.build_hasher()); - let r2 = commutative_hash_pair(b, a, builder.build_hasher()); + let r1 = commutative_hash_pair(&a, &b, builder.build_hasher()); + let r2 = commutative_hash_pair(&b, &a, builder.build_hasher()); assert_eq!(r1, r2); } } diff --git a/lib/crypto/src/merkle.rs b/lib/crypto/src/merkle.rs index 74063f57..9e6ee91c 100644 --- a/lib/crypto/src/merkle.rs +++ b/lib/crypto/src/merkle.rs @@ -183,7 +183,7 @@ where builder: &B, ) -> bool { for &hash in proof { - leaf = commutative_hash_pair(leaf, hash, builder.build_hasher()); + leaf = commutative_hash_pair(&leaf, &hash, builder.build_hasher()); } leaf == root @@ -304,7 +304,7 @@ where proof_pos += 1; }; - let hash = commutative_hash_pair(a, *b, builder.build_hasher()); + let hash = commutative_hash_pair(&a, b, builder.build_hasher()); hashes.push(hash); } @@ -412,7 +412,7 @@ mod tests { assert!(verification); let builder = KeccakBuilder.build_hasher(); - let no_such_leaf = commutative_hash_pair(leaf_a, leaf_b, builder); + let no_such_leaf = commutative_hash_pair(&leaf_a, &leaf_b, builder); let proof = &proof[1..]; let verification = Verifier::verify(proof, root, no_such_leaf); assert!(verification); diff --git a/lib/e2e/README.md b/lib/e2e/README.md index f2c4ad1e..dbd5c850 100644 --- a/lib/e2e/README.md +++ b/lib/e2e/README.md @@ -145,9 +145,4 @@ our [code of conduct] and [contribution guidelines]. ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/lib/motsu/README.md b/lib/motsu/README.md index 69c15b4c..f7e8c074 100644 --- a/lib/motsu/README.md +++ b/lib/motsu/README.md @@ -59,9 +59,4 @@ our [code of conduct] and [contribution guidelines]. ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - Refer to our [Security Policy](../../SECURITY.md) for more details.