Skip to content

Commit d9883b3

Browse files
onurinancbidzyyysqalisander0xNeshi
authored
feat: add ERC-6909 Token and Supply extension (#777)
<!-- Thank you for your interest in contributing to OpenZeppelin! Consider opening an issue for discussion prior to submitting a PR. New features will be merged faster if they were first discussed and designed with the team. Describe the changes introduced in this pull request. Include any context necessary for understanding the PR's purpose. --> <!-- Fill in with issue number --> #### PR Checklist <!-- Before merging the pull request all of the following must be completed. Feel free to submit a PR or Draft PR even if some items are pending. Some of the items may not apply. --> - [x] Tests - [x] Documentation - [x] Changelog # ERC6909 Implementation in rust-contracts-stylus ## Summary This PR implements the [ERC-6909](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/token/ERC6909/draft-ERC6909.sol) token and [Supply extension](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/token/ERC6909/extensions/draft-ERC6909TokenSupply.sol) in Rust for Arbitrum Stylus. The following implementation is provided in this PR: - ERC-6909 + unit tests with motsu - Supply Extension + unit tests with motsu - Example usage of ERC-6909 and Supply Extension - Integration Tests for ERC-6909 and Supply Extension - Benchmark for both ERC6909 and Supply Extension ### Comments Integration tests and the benchmarks inside [rust-contracts-stylus](https://github.com/OpenZeppelin/rust-contracts-stylus), which use [nitro-testnode](https://github.com/OffchainLabs/nitro-testnode), didn't work with the M3 Pro Chip (would be an issue related to Docker). To solve the problem, firstly, [nitro-devnode] is tried to use. However, I observed that it is not suitable for the `e2e::test` workflow. So, an LTS Ubuntu machine is rented on AWS to run the integration tests and the benchmark. For the Supply Extension implementation, `Deref` traits are implemented. Using `Deref` allows direct access to Erc6909 methods, improving readability and maintainability. However, deferencing operations might increase the gas. Designing Supply Token Extension in this way increases the abstraction and makes it easy to use for users who would like to use the Supply Extension with ERC-6909 Token Contract. ### Future Improvements Currently, this PR does not include other ERC-6909 Extensions, specifically ContentURI and Metadata. Their example usage and the benchmarks should be added to the repository. The unit tests that differentiate the `id` should be added. ### Benchmark Benchmarks for ERC-6909 can be seen as follows: ``` | Contract::function | WASM Opt & Cached | Cached | Not Cached | | ------------------------------------------------------------------------- | ----------------- | ------ | ---------- | | Erc6909::mint(address,uint256,uint256) | 27925 | 29307 | 45089 | | Erc6909::balanceOf(address,uint256) | 5101 | 6448 | 22230 | | Erc6909::allowance(address,address,uint256) | 5875 | 7228 | 23010 | | Erc6909::isOperator(address,address) | 5322 | 6666 | 22448 | | Erc6909::setOperator(address,bool) | 27045 | 28433 | 44215 | | Erc6909::transfer(address,uint256,uint256) | 34364 | 35786 | 51568 | | Erc6909::approve(address,uint256,uint256) | 27939 | 29332 | 45114 | | Erc6909::transferFrom(address,address,uint256,uint256) | 20503 | 21943 | 37725 | | Erc6909::burn(address,uint256,uint256) | 11495 | 12893 | 28675 | ``` --------- Co-authored-by: Daniel Bigos <[email protected]> Co-authored-by: Alisander Qoshqosh <[email protected]> Co-authored-by: Daniel Bigos <[email protected]> Co-authored-by: Nenad <[email protected]>
1 parent b648011 commit d9883b3

File tree

25 files changed

+3885
-4
lines changed

25 files changed

+3885
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **Erc6909**: `Erc6909` contract and `Erc6909TokenSupply` extension. #777
13+
1014
### Changed (Breaking)
1115

1216
- `IErc721Wrapper` returns `Vec<u8>` instead of typed `Error`. #822

Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ members = [
2222
"examples/erc1155-metadata-uri",
2323
"examples/erc1155-supply",
2424
"examples/erc4626",
25+
"examples/erc6909",
26+
"examples/erc6909-supply",
2527
"examples/safe-erc20",
2628
"examples/merkle-proofs",
2729
"examples/ownable",
@@ -62,6 +64,8 @@ default-members = [
6264
"examples/erc1155-metadata-uri",
6365
"examples/erc1155-supply",
6466
"examples/erc4626",
67+
"examples/erc6909",
68+
"examples/erc6909-supply",
6569
"examples/safe-erc20",
6670
"examples/merkle-proofs",
6771
"examples/ownable",

benches/src/erc6909.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use alloy::{
2+
network::{AnyNetwork, EthereumWallet},
3+
primitives::Address,
4+
providers::ProviderBuilder,
5+
sol,
6+
sol_types::SolCall,
7+
uint,
8+
};
9+
use e2e::{receipt, Account};
10+
11+
use crate::{
12+
report::{ContractReport, FunctionReport},
13+
Opt,
14+
};
15+
16+
sol!(
17+
#[sol(rpc)]
18+
contract Erc6909 {
19+
function balanceOf(address owner, uint256 id) external view returns (uint256 balance);
20+
function allowance(address owner, address spender, uint256 id) external view returns (uint256 allowance);
21+
function isOperator(address owner, address spender) external view returns (bool approved);
22+
function approve(address spender, uint256 id, uint256 amount) external returns (bool);
23+
function setOperator(address spender, bool approved) external returns (bool);
24+
function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
25+
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
26+
function mint(address to, uint256 id, uint256 amount) external;
27+
function burn(address from, uint256 id, uint256 amount) external;
28+
}
29+
);
30+
31+
pub async fn bench() -> eyre::Result<ContractReport> {
32+
ContractReport::generate("Erc6909", run).await
33+
}
34+
35+
pub async fn run(cache_opt: Opt) -> eyre::Result<Vec<FunctionReport>> {
36+
let alice = Account::new().await?;
37+
let alice_addr = alice.address();
38+
let alice_wallet = ProviderBuilder::new()
39+
.network::<AnyNetwork>()
40+
.with_recommended_fillers()
41+
.wallet(EthereumWallet::from(alice.signer.clone()))
42+
.on_http(alice.url().parse()?);
43+
44+
let bob = Account::new().await?;
45+
let bob_addr = bob.address();
46+
let bob_wallet = ProviderBuilder::new()
47+
.network::<AnyNetwork>()
48+
.with_recommended_fillers()
49+
.wallet(EthereumWallet::from(bob.signer.clone()))
50+
.on_http(bob.url().parse()?);
51+
52+
let contract_addr = deploy(&alice, cache_opt).await?;
53+
54+
let contract = Erc6909::new(contract_addr, &alice_wallet);
55+
let contract_bob = Erc6909::new(contract_addr, &bob_wallet);
56+
57+
let token_id = uint!(1_U256);
58+
let amount = uint!(100_U256);
59+
let one = uint!(1_U256);
60+
61+
// IMPORTANT: Order matters!
62+
use Erc6909::*;
63+
#[rustfmt::skip]
64+
let receipts = vec![
65+
(mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_id, amount))?),
66+
(balanceOfCall::SIGNATURE, receipt!(contract.balanceOf(alice_addr, token_id))?),
67+
(allowanceCall::SIGNATURE, receipt!(contract.allowance(alice_addr, bob_addr, token_id))?),
68+
(isOperatorCall::SIGNATURE, receipt!(contract.isOperator(alice_addr, bob_addr))?),
69+
(setOperatorCall::SIGNATURE, receipt!(contract.setOperator(bob_addr, true))?),
70+
(transferCall::SIGNATURE, receipt!(contract.transfer(bob_addr, token_id, one))?),
71+
(approveCall::SIGNATURE, receipt!(contract.approve(bob_addr, token_id, one))?),
72+
(transferFromCall::SIGNATURE, receipt!(contract_bob.transferFrom(alice_addr, bob_addr, token_id, one))?),
73+
(burnCall::SIGNATURE, receipt!(contract.burn(alice_addr, token_id, one))?),
74+
];
75+
76+
receipts
77+
.into_iter()
78+
.map(FunctionReport::new)
79+
.collect::<eyre::Result<Vec<_>>>()
80+
}
81+
82+
async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result<Address> {
83+
crate::deploy(account, "erc6909", None, cache_opt).await
84+
}

benches/src/erc6909_supply.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use alloy::{
2+
network::{AnyNetwork, EthereumWallet},
3+
primitives::Address,
4+
providers::ProviderBuilder,
5+
sol,
6+
sol_types::SolCall,
7+
uint,
8+
};
9+
use e2e::{receipt, Account};
10+
11+
use crate::{
12+
report::{ContractReport, FunctionReport},
13+
Opt,
14+
};
15+
16+
sol!(
17+
#[sol(rpc)]
18+
contract Erc6909TokenSupply {
19+
function totalSupply(uint256 id) external view returns (uint256 totalSupply);
20+
function mint(address to, uint256 id, uint256 amount) external;
21+
function burn(address from, uint256 id, uint256 amount) external;
22+
}
23+
);
24+
25+
pub async fn bench() -> eyre::Result<ContractReport> {
26+
ContractReport::generate("Erc6909TokenSupply", run).await
27+
}
28+
29+
pub async fn run(cache_opt: Opt) -> eyre::Result<Vec<FunctionReport>> {
30+
let alice = Account::new().await?;
31+
let alice_addr = alice.address();
32+
let alice_wallet = ProviderBuilder::new()
33+
.network::<AnyNetwork>()
34+
.with_recommended_fillers()
35+
.wallet(EthereumWallet::from(alice.signer.clone()))
36+
.on_http(alice.url().parse()?);
37+
38+
let contract_addr = deploy(&alice, cache_opt).await?;
39+
40+
let contract = Erc6909TokenSupply::new(contract_addr, &alice_wallet);
41+
42+
let token_id = uint!(1_U256);
43+
let amount = uint!(100_U256);
44+
45+
// IMPORTANT: Order matters!
46+
use Erc6909TokenSupply::*;
47+
#[rustfmt::skip]
48+
let receipts = vec![
49+
(mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_id, amount))?),
50+
(totalSupplyCall::SIGNATURE, receipt!(contract.totalSupply(token_id))?),
51+
(burnCall::SIGNATURE, receipt!(contract.burn(alice_addr, token_id, amount))?),
52+
];
53+
54+
receipts
55+
.into_iter()
56+
.map(FunctionReport::new)
57+
.collect::<eyre::Result<Vec<_>>>()
58+
}
59+
60+
async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result<Address> {
61+
crate::deploy(account, "erc6909-supply", None, cache_opt).await
62+
}

benches/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub mod erc1155;
1212
pub mod erc1155_metadata_uri;
1313
pub mod erc1155_supply;
1414
pub mod erc20;
15+
pub mod erc6909;
16+
pub mod erc6909_supply;
1517
pub mod erc721;
1618
pub mod merkle_proofs;
1719
pub mod ownable;

benches/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use benches::{
2-
access_control, eddsa, erc1155, erc1155_metadata_uri, erc20, erc721,
3-
merkle_proofs, ownable, pedersen, poseidon, poseidon_asm_sol, poseidon_sol,
4-
report::BenchmarkReport,
2+
access_control, eddsa, erc1155, erc1155_metadata_uri, erc20, erc6909,
3+
erc6909_supply, erc721, merkle_proofs, ownable, pedersen, poseidon,
4+
poseidon_asm_sol, poseidon_sol, report::BenchmarkReport,
55
};
66
use futures::FutureExt;
77
use itertools::Itertools;
@@ -16,6 +16,8 @@ async fn main() -> eyre::Result<()> {
1616
ownable::bench().boxed(),
1717
erc1155::bench().boxed(),
1818
erc1155_metadata_uri::bench().boxed(),
19+
erc6909::bench().boxed(),
20+
erc6909_supply::bench().boxed(),
1921
pedersen::bench().boxed(),
2022
poseidon_sol::bench().boxed(),
2123
poseidon_asm_sol::bench().boxed(),

contracts/src/token/erc1155/extensions/supply.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl Erc1155Supply {
237237
/// # Panics
238238
///
239239
/// * If updated balance and/or supply exceeds [`U256::MAX`], may happen
240-
/// during the `mint` operation.
240+
/// during the [`Self::_mint`] operation.
241241
fn _update(
242242
&mut self,
243243
from: Address,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//! Common extensions to the ERC-6909 standard.
2+
pub mod token_supply;
3+
4+
pub use token_supply::{Erc6909TokenSupply, IErc6909TokenSupply};

0 commit comments

Comments
 (0)