From 589430303036f6047bd914a4b2a914f1f8b23deb Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 18 Feb 2024 18:30:05 -0500 Subject: [PATCH 01/35] add udc preset --- src/presets.cairo | 2 + src/presets/universal_deployer.cairo | 74 ++++++++++ src/tests/presets.cairo | 1 + .../presets/test_universal_deployer.cairo | 131 ++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 src/presets/universal_deployer.cairo create mode 100644 src/tests/presets/test_universal_deployer.cairo diff --git a/src/presets.cairo b/src/presets.cairo index 1a0804706..2285016b5 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -2,8 +2,10 @@ mod account; mod erc20; mod erc721; mod eth_account; +mod universal_deployer; use account::Account; use erc20::ERC20; use erc721::ERC721; use eth_account::EthAccountUpgradeable; +use universal_deployer::UniversalDeployer; diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo new file mode 100644 index 000000000..14003f3c3 --- /dev/null +++ b/src/presets/universal_deployer.cairo @@ -0,0 +1,74 @@ +use starknet::ClassHash; +use starknet::ContractAddress; + +#[starknet::interface] +trait IUniversalDeployer { + fn deploy_contract( + self: @TState, class_hash: ClassHash, salt: felt252, unique: bool, calldata: Span + ) -> ContractAddress; +} + +#[starknet::contract] +mod UniversalDeployer { + use core::pedersen::pedersen; + use starknet::ClassHash; + use starknet::ContractAddress; + use starknet::SyscallResultTrait; + use starknet::get_caller_address; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + ContractDeployed: ContractDeployed + } + + #[derive(Drop, starknet::Event)] + struct ContractDeployed { + address: ContractAddress, + deployer: ContractAddress, + unique: bool, + class_hash: ClassHash, + calldata: Span, + salt: felt252, + } + + #[external(v0)] + fn deploy_contract( + ref self: ContractState, + class_hash: ClassHash, + salt: felt252, + unique: bool, + calldata: Span + ) -> ContractAddress { + let deployer: ContractAddress = get_caller_address(); + + // Defaults for non-unique deployment + let mut _salt: felt252 = salt; + let mut from_zero: bool = true; + + if unique { + _salt = pedersen(deployer.into(), salt); + from_zero = false; + } + + let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) + .unwrap_syscall(); + + self + .emit( + ContractDeployed { + address: address, + deployer: deployer, + unique: unique, + class_hash: class_hash, + calldata: calldata, + salt: salt + } + ); + + return address; + } +} diff --git a/src/tests/presets.cairo b/src/tests/presets.cairo index 91046a8a8..b8b31ff53 100644 --- a/src/tests/presets.cairo +++ b/src/tests/presets.cairo @@ -2,3 +2,4 @@ mod test_account; mod test_erc20; mod test_erc721; mod test_eth_account; +mod test_universal_deployer; diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo new file mode 100644 index 000000000..18f71ce73 --- /dev/null +++ b/src/tests/presets/test_universal_deployer.cairo @@ -0,0 +1,131 @@ +use core::pedersen::pedersen; +use openzeppelin::presets::universal_deployer::UniversalDeployer::ContractDeployed; +use openzeppelin::presets::universal_deployer::UniversalDeployer; +use openzeppelin::presets::universal_deployer::{ + IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait +}; +use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; +use openzeppelin::tests::utils::constants::{NAME, SYMBOL, SUPPLY, SALT, CALLER, RECIPIENT}; +use openzeppelin::tests::utils; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ClassHash; +use starknet::ContractAddress; +use starknet::testing; + + +// 2**251 - 256 +const L2_ADDRESS_UPPER_BOUND: felt252 = + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00; +const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; + +fn UDC_CLASS_HASH() -> felt252 { + UniversalDeployer::TEST_CLASS_HASH +} + +fn ERC20_CLASS_HASH() -> ClassHash { + DualCaseERC20Mock::TEST_CLASS_HASH.try_into().unwrap() +} + +fn ERC20_CALLDATA() -> Span { + let mut calldata = array![]; + calldata.append_serde(NAME); + calldata.append_serde(SYMBOL); + calldata.append_serde(SUPPLY); + calldata.append_serde(RECIPIENT()); + calldata.span() +} + +fn deploy_udc() -> IUniversalDeployerDispatcher { + let calldata = array![]; + let address = utils::deploy(UDC_CLASS_HASH(), calldata); + + IUniversalDeployerDispatcher { contract_address: address } +} + +#[test] +fn test_deploy_not_unique() { + let udc = deploy_udc(); + let unique = false; + testing::set_contract_address(CALLER()); + + let expected_addr = calculate_contract_address_from_hash( + SALT, ERC20_CLASS_HASH(), ERC20_CALLDATA(), Zeroable::zero() + ); + let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA()); + assert_eq!(expected_addr, deployed_addr); + + let event = utils::pop_log::(udc.contract_address).unwrap(); + assert_eq!(event.address, deployed_addr); + assert_eq!(event.deployer, CALLER()); + assert_eq!(event.unique, unique); + assert_eq!(event.class_hash, ERC20_CLASS_HASH()); + assert_eq!(event.calldata, ERC20_CALLDATA()); + assert_eq!(event.salt, SALT); + utils::assert_no_events_left(udc.contract_address); +} + +#[test] +fn test_deploy_unique() { + let udc = deploy_udc(); + let unique = true; + testing::set_contract_address(CALLER()); + + let hashed_salt = pedersen(CALLER().into(), SALT); + let expected_addr = calculate_contract_address_from_hash( + hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address + ); + let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA()); + assert_eq!(expected_addr, deployed_addr); + + let event = utils::pop_log::(udc.contract_address).unwrap(); + assert_eq!(event.address, deployed_addr); + assert_eq!(event.deployer, CALLER()); + assert_eq!(event.unique, unique); + assert_eq!(event.class_hash, ERC20_CLASS_HASH()); + assert_eq!(event.calldata, ERC20_CALLDATA()); + assert_eq!(event.salt, SALT); + utils::assert_no_events_left(udc.contract_address); +} + +// +// Helpers +// + +/// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/cairo/common/hash_state.py +fn compute_hash_on_elements(mut data: Span::) -> felt252 { + let data_len: usize = data.len(); + let mut hash = 0; + loop { + match data.pop_front() { + Option::Some(elem) => { hash = pedersen(hash, *elem); }, + Option::None(_) => { + hash = pedersen(hash, data_len.into()); + break (); + }, + }; + }; + hash +} + +/// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/starknet/core/os/contract_address/contract_address.py +fn calculate_contract_address_from_hash( + salt: felt252, + class_hash: ClassHash, + constructor_calldata: Span::, + deployer_address: ContractAddress +) -> ContractAddress { + let constructor_calldata_hash = compute_hash_on_elements(constructor_calldata); + + let mut data = array![]; + data.append_serde(CONTRACT_ADDRESS_PREFIX); + data.append_serde(deployer_address); + data.append_serde(salt); + data.append_serde(class_hash); + data.append_serde(constructor_calldata_hash); + let raw_address = compute_hash_on_elements(data.span()); + + // Felt modulo is discouraged, hence the conversion to u256 + let u256_addr: u256 = raw_address.into() % L2_ADDRESS_UPPER_BOUND.into(); + let felt_addr = u256_addr.try_into().unwrap(); + starknet::contract_address_try_from_felt252(felt_addr).unwrap() +} From 48a72ac6bca99c769de68c6356aa82ae5ddb8d90 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 18 Feb 2024 18:30:32 -0500 Subject: [PATCH 02/35] add udc preset class hash --- docs/modules/ROOT/pages/presets.adoc | 4 ++++ docs/modules/ROOT/pages/utils/_class_hashes.adoc | 1 + 2 files changed, 5 insertions(+) diff --git a/docs/modules/ROOT/pages/presets.adoc b/docs/modules/ROOT/pages/presets.adoc index f5430b417..7f587d3be 100644 --- a/docs/modules/ROOT/pages/presets.adoc +++ b/docs/modules/ROOT/pages/presets.adoc @@ -2,6 +2,7 @@ :erc20: xref:/api/erc20.adoc#ERC20[ERC20] :erc721: xref:/api/erc721.adoc#ERC721[ERC721] :eth-account-upgradeable: xref:/api/account.adoc#EthAccountUpgradeable[EthAccountUpgradeable] +:udc: https://github.com/starknet-io/starknet-docs/blob/v0.1.479/components/Starknet/modules/architecture_and_concepts/pages/Smart_Contracts/universal-deployer.adoc[UniversalDeployer] :sierra-class-hashes: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/class-hash[Sierra class hashes] :starkli: https://book.starkli.rs/introduction[starkli] :wizard: https://wizard.openzeppelin.com[Wizard for Cairo] @@ -37,6 +38,9 @@ NOTE: Class hashes were computed using {class-hash-cairo-version}. | `{erc721}` | `{erc721-class-hash}` + +| `{udc}` +| `{udc-class-hash}` |=== TIP: {starkli} class-hash command can be used to compute the class hash from a Sierra artifact. diff --git a/docs/modules/ROOT/pages/utils/_class_hashes.adoc b/docs/modules/ROOT/pages/utils/_class_hashes.adoc index 76fb37a81..d167a889f 100644 --- a/docs/modules/ROOT/pages/utils/_class_hashes.adoc +++ b/docs/modules/ROOT/pages/utils/_class_hashes.adoc @@ -6,6 +6,7 @@ :eth-account-upgradeable-class-hash: 0x023e416842ca96b1f7067693892ed00881d97a4b0d9a4c793b75cb887944d98d :erc20-class-hash: 0x7d94f28156c0dc3bfd9a07ca79b15b8da2b5b32093db79000fcd0f6f625d213 :erc721-class-hash: 0x06b7c9efc5467c621f58d87995302d940a39b7217b5c5a7a55555c97cabf5cd8 +:udc-class-hash: 0x0548f35c7316b4dc5efdaa929fe83f1007c7e0ae24bec3045d47c94da00dd386 // Presets page :presets-page: xref:presets.adoc[Sierra class hash] From f094ff6350d34a0f4c0ac13119232ed13031521d Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Feb 2024 01:15:05 -0500 Subject: [PATCH 03/35] tidy up code --- src/tests/presets/test_universal_deployer.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 18f71ce73..b586509ae 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -92,7 +92,7 @@ fn test_deploy_unique() { // /// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/cairo/common/hash_state.py -fn compute_hash_on_elements(mut data: Span::) -> felt252 { +fn compute_hash_on_elements(mut data: Span) -> felt252 { let data_len: usize = data.len(); let mut hash = 0; loop { @@ -111,7 +111,7 @@ fn compute_hash_on_elements(mut data: Span::) -> felt252 { fn calculate_contract_address_from_hash( salt: felt252, class_hash: ClassHash, - constructor_calldata: Span::, + constructor_calldata: Span, deployer_address: ContractAddress ) -> ContractAddress { let constructor_calldata_hash = compute_hash_on_elements(constructor_calldata); From e135b1aa7ebbe43e4f7b8d90ee162908df1f94e6 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Feb 2024 01:15:15 -0500 Subject: [PATCH 04/35] add entry to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0efc9caf..d1dc63a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - EthAccount component and preset (#853) - Ownable two-step functionality (#809) +- UDC contract (#919) ### Changed From b078f70e874244f1a1ca43e0c62e4b4d63c774f3 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Feb 2024 01:50:59 -0500 Subject: [PATCH 05/35] tidy up code --- src/tests/presets/test_universal_deployer.cairo | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index b586509ae..4134b06db 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -18,10 +18,6 @@ const L2_ADDRESS_UPPER_BOUND: felt252 = 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00; const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; -fn UDC_CLASS_HASH() -> felt252 { - UniversalDeployer::TEST_CLASS_HASH -} - fn ERC20_CLASS_HASH() -> ClassHash { DualCaseERC20Mock::TEST_CLASS_HASH.try_into().unwrap() } @@ -37,7 +33,7 @@ fn ERC20_CALLDATA() -> Span { fn deploy_udc() -> IUniversalDeployerDispatcher { let calldata = array![]; - let address = utils::deploy(UDC_CLASS_HASH(), calldata); + let address = utils::deploy(UniversalDeployer::TEST_CLASS_HASH, calldata); IUniversalDeployerDispatcher { contract_address: address } } From 90b542f2e475934cad36c7eb0297426365c65fab Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Feb 2024 02:02:36 -0500 Subject: [PATCH 06/35] clean up code --- src/tests/presets/test_universal_deployer.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 4134b06db..afa6425e9 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -94,9 +94,9 @@ fn compute_hash_on_elements(mut data: Span) -> felt252 { loop { match data.pop_front() { Option::Some(elem) => { hash = pedersen(hash, *elem); }, - Option::None(_) => { + Option::None => { hash = pedersen(hash, data_len.into()); - break (); + break; }, }; }; From 476e0582a62aa34dbd8b27f426a124638c4ecd77 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Feb 2024 02:33:48 -0500 Subject: [PATCH 07/35] add udc impl --- src/presets/universal_deployer.cairo | 68 +++++++++++++++------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 14003f3c3..deedbd287 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -4,7 +4,11 @@ use starknet::ContractAddress; #[starknet::interface] trait IUniversalDeployer { fn deploy_contract( - self: @TState, class_hash: ClassHash, salt: felt252, unique: bool, calldata: Span + ref self: TState, + class_hash: ClassHash, + salt: felt252, + unique: bool, + calldata: Span ) -> ContractAddress; } @@ -35,40 +39,42 @@ mod UniversalDeployer { salt: felt252, } - #[external(v0)] - fn deploy_contract( - ref self: ContractState, - class_hash: ClassHash, - salt: felt252, - unique: bool, - calldata: Span - ) -> ContractAddress { - let deployer: ContractAddress = get_caller_address(); + #[abi(embed_v0)] + impl UniversalDeployerImpl of super::IUniversalDeployer { + fn deploy_contract( + ref self: ContractState, + class_hash: ClassHash, + salt: felt252, + unique: bool, + calldata: Span + ) -> ContractAddress { + let deployer: ContractAddress = get_caller_address(); - // Defaults for non-unique deployment - let mut _salt: felt252 = salt; - let mut from_zero: bool = true; + // Defaults for non-unique deployment + let mut _salt: felt252 = salt; + let mut from_zero: bool = true; - if unique { - _salt = pedersen(deployer.into(), salt); - from_zero = false; - } + if unique { + _salt = pedersen(deployer.into(), salt); + from_zero = false; + } - let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) - .unwrap_syscall(); + let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) + .unwrap_syscall(); - self - .emit( - ContractDeployed { - address: address, - deployer: deployer, - unique: unique, - class_hash: class_hash, - calldata: calldata, - salt: salt - } - ); + self + .emit( + ContractDeployed { + address: address, + deployer: deployer, + unique: unique, + class_hash: class_hash, + calldata: calldata, + salt: salt + } + ); - return address; + return address; + } } } From e78867c3194ae11cefc0a0419eba12fbe2212e5b Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Feb 2024 02:52:41 -0500 Subject: [PATCH 08/35] add deployment check and comments --- src/tests/presets/test_universal_deployer.cairo | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index afa6425e9..a85b83acf 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -7,6 +7,7 @@ use openzeppelin::presets::universal_deployer::{ use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; use openzeppelin::tests::utils::constants::{NAME, SYMBOL, SUPPLY, SALT, CALLER, RECIPIENT}; use openzeppelin::tests::utils; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use openzeppelin::utils::serde::SerializedAppend; use starknet::ClassHash; use starknet::ContractAddress; @@ -44,12 +45,14 @@ fn test_deploy_not_unique() { let unique = false; testing::set_contract_address(CALLER()); + // Check address let expected_addr = calculate_contract_address_from_hash( SALT, ERC20_CLASS_HASH(), ERC20_CALLDATA(), Zeroable::zero() ); let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); + // Check event let event = utils::pop_log::(udc.contract_address).unwrap(); assert_eq!(event.address, deployed_addr); assert_eq!(event.deployer, CALLER()); @@ -58,6 +61,11 @@ fn test_deploy_not_unique() { assert_eq!(event.calldata, ERC20_CALLDATA()); assert_eq!(event.salt, SALT); utils::assert_no_events_left(udc.contract_address); + + // Check deployment + let erc20 = IERC20Dispatcher { contract_address: deployed_addr }; + let total_supply = erc20.total_supply(); + assert_eq!(total_supply, SUPPLY); } #[test] @@ -66,6 +74,7 @@ fn test_deploy_unique() { let unique = true; testing::set_contract_address(CALLER()); + // Check address let hashed_salt = pedersen(CALLER().into(), SALT); let expected_addr = calculate_contract_address_from_hash( hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address @@ -73,6 +82,7 @@ fn test_deploy_unique() { let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); + // Check event let event = utils::pop_log::(udc.contract_address).unwrap(); assert_eq!(event.address, deployed_addr); assert_eq!(event.deployer, CALLER()); @@ -81,6 +91,11 @@ fn test_deploy_unique() { assert_eq!(event.calldata, ERC20_CALLDATA()); assert_eq!(event.salt, SALT); utils::assert_no_events_left(udc.contract_address); + + // Check deployment + let erc20 = IERC20Dispatcher { contract_address: deployed_addr }; + let total_supply = erc20.total_supply(); + assert_eq!(total_supply, SUPPLY); } // From b34339476ee81fc3bfe5057db146f81adaa26119 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Fri, 23 Feb 2024 18:08:20 -0500 Subject: [PATCH 09/35] Apply suggestions from code review Co-authored-by: Eric Nordelo --- docs/modules/ROOT/pages/presets.adoc | 2 +- docs/modules/ROOT/pages/utils/_class_hashes.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/presets.adoc b/docs/modules/ROOT/pages/presets.adoc index 7f587d3be..eef6386a9 100644 --- a/docs/modules/ROOT/pages/presets.adoc +++ b/docs/modules/ROOT/pages/presets.adoc @@ -40,7 +40,7 @@ NOTE: Class hashes were computed using {class-hash-cairo-version}. | `{erc721-class-hash}` | `{udc}` -| `{udc-class-hash}` +| `{UniversalDeployer-class-hash}` |=== TIP: {starkli} class-hash command can be used to compute the class hash from a Sierra artifact. diff --git a/docs/modules/ROOT/pages/utils/_class_hashes.adoc b/docs/modules/ROOT/pages/utils/_class_hashes.adoc index d167a889f..240d219c6 100644 --- a/docs/modules/ROOT/pages/utils/_class_hashes.adoc +++ b/docs/modules/ROOT/pages/utils/_class_hashes.adoc @@ -6,7 +6,7 @@ :eth-account-upgradeable-class-hash: 0x023e416842ca96b1f7067693892ed00881d97a4b0d9a4c793b75cb887944d98d :erc20-class-hash: 0x7d94f28156c0dc3bfd9a07ca79b15b8da2b5b32093db79000fcd0f6f625d213 :erc721-class-hash: 0x06b7c9efc5467c621f58d87995302d940a39b7217b5c5a7a55555c97cabf5cd8 -:udc-class-hash: 0x0548f35c7316b4dc5efdaa929fe83f1007c7e0ae24bec3045d47c94da00dd386 +:UniversalDeployer-class-hash: 0x0548f35c7316b4dc5efdaa929fe83f1007c7e0ae24bec3045d47c94da00dd386 // Presets page :presets-page: xref:presets.adoc[Sierra class hash] From 5215030a37aa40eac60fb1d13d7e5d31b4164283 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 23 Feb 2024 19:37:40 -0500 Subject: [PATCH 10/35] add comments --- src/presets/universal_deployer.cairo | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index deedbd287..20871c3e2 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -1,20 +1,18 @@ -use starknet::ClassHash; -use starknet::ContractAddress; - -#[starknet::interface] -trait IUniversalDeployer { - fn deploy_contract( - ref self: TState, - class_hash: ClassHash, - salt: felt252, - unique: bool, - calldata: Span - ) -> ContractAddress; -} +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (presets/universal_deployer.cairo) +/// # UniversalDeployer Preset +/// +/// The Universal Deployer Contract is a singleton smart contract that wraps `deploy_syscall` +/// to expose it to any contract that doesn't implement it, such as account contracts. +/// +/// This contract is already deployed at 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf +/// on mainnet, testnets, and starknet-devnet. +/// This address may change in the future. #[starknet::contract] mod UniversalDeployer { use core::pedersen::pedersen; + use openzeppelin::utils::universal_deployer::interface; use starknet::ClassHash; use starknet::ContractAddress; use starknet::SyscallResultTrait; @@ -40,7 +38,7 @@ mod UniversalDeployer { } #[abi(embed_v0)] - impl UniversalDeployerImpl of super::IUniversalDeployer { + impl UniversalDeployerImpl of interface::IUniversalDeployer { fn deploy_contract( ref self: ContractState, class_hash: ClassHash, From d81c544fbd2f519655668178e0b44ec9ec1af508 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 23 Feb 2024 19:38:54 -0500 Subject: [PATCH 11/35] move IUniversalDeployer to interface mod in udc dir --- src/tests/presets/test_universal_deployer.cairo | 6 +++--- src/utils.cairo | 1 + src/utils/universal_deployer.cairo | 2 ++ src/utils/universal_deployer/interface.cairo | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/utils/universal_deployer.cairo create mode 100644 src/utils/universal_deployer/interface.cairo diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index a85b83acf..9a08f90b3 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -1,14 +1,14 @@ use core::pedersen::pedersen; use openzeppelin::presets::universal_deployer::UniversalDeployer::ContractDeployed; use openzeppelin::presets::universal_deployer::UniversalDeployer; -use openzeppelin::presets::universal_deployer::{ - IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait -}; use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; use openzeppelin::tests::utils::constants::{NAME, SYMBOL, SUPPLY, SALT, CALLER, RECIPIENT}; use openzeppelin::tests::utils; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::utils::universal_deployer::interface::{ + IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait +}; use starknet::ClassHash; use starknet::ContractAddress; use starknet::testing; diff --git a/src/utils.cairo b/src/utils.cairo index 26e82e26c..0562fe7e1 100644 --- a/src/utils.cairo +++ b/src/utils.cairo @@ -3,6 +3,7 @@ mod selectors; mod serde; +mod universal_deployer; mod unwrap_and_cast; use starknet::ContractAddress; diff --git a/src/utils/universal_deployer.cairo b/src/utils/universal_deployer.cairo new file mode 100644 index 000000000..4c745c56a --- /dev/null +++ b/src/utils/universal_deployer.cairo @@ -0,0 +1,2 @@ +mod interface; +use interface::IUniversalDeployer; diff --git a/src/utils/universal_deployer/interface.cairo b/src/utils/universal_deployer/interface.cairo new file mode 100644 index 000000000..9cd6a2e20 --- /dev/null +++ b/src/utils/universal_deployer/interface.cairo @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (utils/universal_deployer/interface.cairo) + +use starknet::ClassHash; +use starknet::ContractAddress; + +#[starknet::interface] +trait IUniversalDeployer { + fn deploy_contract( + ref self: TState, + class_hash: ClassHash, + salt: felt252, + unique: bool, + calldata: Span + ) -> ContractAddress; +} From 40b293acf7d55b6bc37df5214dd707d63fa5bd98 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 6 Mar 2024 16:09:09 -0500 Subject: [PATCH 12/35] fix conflicts, add PartialEq to udc event --- .github/workflows/prepare-release.yml | 25 +- CHANGELOG.md | 20 + DEVELOPMENT.md | 42 + README.md | 4 +- docs/antora.yml | 2 +- docs/modules/ROOT/nav.adoc | 3 +- docs/modules/ROOT/pages/access.adoc | 12 +- docs/modules/ROOT/pages/api/access.adoc | 71 +- docs/modules/ROOT/pages/api/account.adoc | 104 +- docs/modules/ROOT/pages/api/erc1155.adoc | 670 ++++++++ docs/modules/ROOT/pages/api/erc20.adoc | 44 +- docs/modules/ROOT/pages/api/erc721.adoc | 255 +-- .../modules/ROOT/pages/api/introspection.adoc | 5 +- docs/modules/ROOT/pages/api/security.adoc | 10 +- docs/modules/ROOT/pages/components.adoc | 102 +- docs/modules/ROOT/pages/erc1155.adoc | 737 +++------ docs/modules/ROOT/pages/erc20.adoc | 28 +- docs/modules/ROOT/pages/erc721.adoc | 70 +- .../ROOT/pages/guides/erc20-supply.adoc | 8 +- docs/modules/ROOT/pages/index.adoc | 4 +- docs/modules/ROOT/pages/interfaces.adoc | 20 +- docs/modules/ROOT/pages/presets.adoc | 16 +- docs/modules/ROOT/pages/security.adoc | 64 +- docs/modules/ROOT/pages/utilities.adoc | 6 +- .../ROOT/pages/utils/_class_hashes.adoc | 9 +- docs/modules/ROOT/pages/utils/_common.adoc | 5 + docs/modules/ROOT/pages/wizard.adoc | 2 +- scripts/get_hashes_page.py | 37 + src/access/accesscontrol/accesscontrol.cairo | 105 +- src/access/accesscontrol/interface.cairo | 20 + src/access/ownable/interface.cairo | 28 + src/access/ownable/ownable.cairo | 81 +- src/account/account.cairo | 86 +- src/account/eth_account.cairo | 86 +- src/account/interface.cairo | 6 - src/presets.cairo | 2 + src/presets/account.cairo | 18 +- src/presets/erc1155.cairo | 72 + src/presets/erc20.cairo | 11 +- src/presets/erc721.cairo | 48 +- src/presets/eth_account.cairo | 20 +- src/presets/universal_deployer.cairo | 4 +- src/security/pausable.cairo | 6 +- src/tests/access/test_accesscontrol.cairo | 29 +- src/tests/access/test_ownable.cairo | 9 +- src/tests/access/test_ownable_twostep.cairo | 102 +- src/tests/account/test_account.cairo | 27 +- src/tests/account/test_eth_account.cairo | 28 +- src/tests/account/test_secp256k1.cairo | 1 + src/tests/mocks.cairo | 2 + src/tests/mocks/accesscontrol_mocks.cairo | 146 +- src/tests/mocks/account_mocks.cairo | 89 +- src/tests/mocks/erc1155_mocks.cairo | 308 ++++ src/tests/mocks/erc1155_receiver_mocks.cairo | 204 +++ src/tests/mocks/erc20_mocks.cairo | 195 +-- src/tests/mocks/erc721_mocks.cairo | 51 +- src/tests/mocks/erc721_receiver_mocks.cairo | 153 +- src/tests/mocks/eth_account_mocks.cairo | 88 +- src/tests/mocks/ownable_mocks.cairo | 85 +- src/tests/presets.cairo | 1 + src/tests/presets/test_account.cairo | 10 +- src/tests/presets/test_erc1155.cairo | 726 +++++++++ src/tests/presets/test_erc20.cairo | 28 +- src/tests/presets/test_erc721.cairo | 85 +- src/tests/presets/test_eth_account.cairo | 81 +- .../presets/test_universal_deployer.cairo | 42 +- src/tests/security/test_pausable.cairo | 10 +- src/tests/token.cairo | 4 + src/tests/token/test_dual1155.cairo | 386 +++++ src/tests/token/test_dual1155_receiver.cairo | 157 ++ src/tests/token/test_dual20.cairo | 16 +- src/tests/token/test_dual721.cairo | 31 +- src/tests/token/test_erc1155.cairo | 1406 +++++++++++++++++ src/tests/token/test_erc1155_receiver.cairo | 80 + src/tests/token/test_erc20.cairo | 24 +- src/tests/token/test_erc721.cairo | 94 +- src/tests/token/test_erc721_receiver.cairo | 6 +- src/tests/upgrades/test_upgradeable.cairo | 6 +- src/tests/utils.cairo | 27 +- src/tests/utils/constants.cairo | 26 +- src/token.cairo | 1 + src/token/erc1155.cairo | 8 + src/token/erc1155/dual1155.cairo | 166 ++ src/token/erc1155/dual1155_receiver.cairo | 83 + src/token/erc1155/erc1155.cairo | 547 +++++++ src/token/erc1155/erc1155_receiver.cairo | 102 ++ src/token/erc1155/interface.cairo | 176 +++ src/token/erc20/dual20.cairo | 8 +- src/token/erc20/erc20.cairo | 90 +- src/token/erc20/interface.cairo | 8 +- src/token/erc721/dual721.cairo | 12 +- src/token/erc721/erc721.cairo | 216 ++- src/token/erc721/erc721_receiver.cairo | 40 + src/token/erc721/interface.cairo | 45 +- src/upgrades/upgradeable.cairo | 4 +- src/utils/selectors.cairo | 22 + 96 files changed, 7522 insertions(+), 1737 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 docs/modules/ROOT/pages/api/erc1155.adoc create mode 100644 docs/modules/ROOT/pages/utils/_common.adoc create mode 100644 scripts/get_hashes_page.py create mode 100644 src/presets/erc1155.cairo create mode 100644 src/tests/mocks/erc1155_mocks.cairo create mode 100644 src/tests/mocks/erc1155_receiver_mocks.cairo create mode 100644 src/tests/presets/test_erc1155.cairo create mode 100644 src/tests/token/test_dual1155.cairo create mode 100644 src/tests/token/test_dual1155_receiver.cairo create mode 100644 src/tests/token/test_erc1155.cairo create mode 100644 src/tests/token/test_erc1155_receiver.cairo create mode 100644 src/token/erc1155.cairo create mode 100644 src/token/erc1155/dual1155.cairo create mode 100644 src/token/erc1155/dual1155_receiver.cairo create mode 100644 src/token/erc1155/erc1155.cairo create mode 100644 src/token/erc1155/erc1155_receiver.cairo create mode 100644 src/token/erc1155/interface.cairo diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 7ab2c115f..37451e21e 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -1,4 +1,4 @@ -name: Update version on new release branch +name: Update version and presets page on new release branch on: create: @@ -12,10 +12,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Extract current version + - name: Extract current versions run: | CURRENT_VERSION=$(grep '^version = ' Scarb.toml | sed 's/version = "\(.*\)"/\1/') + SCARB_VERSION=$(grep 'cairo-version = ' Scarb.toml | sed 's/cairo-version = "\(.*\)"/\1/') echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV + echo "SCARB_VERSION=$SCARB_VERSION" >> $GITHUB_ENV - name: Extract new version number run: echo "NEW_VERSION=${GITHUB_REF#refs/heads/release-v}" >> $GITHUB_ENV @@ -25,7 +27,24 @@ jobs: echo "Current version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" ESCAPED_CURRENT_VERSION=$(echo $CURRENT_VERSION | sed 's/\./\\./g') - find . -type f -not -path '*/\.*' -not -path './CHANGELOG.md' -not -path './docs/package-lock.json' -not -path './RELEASING.md' -exec sed -i "s/$ESCAPED_CURRENT_VERSION/$NEW_VERSION/g" {} + + find . -type f -not -path '*/\.*' -not -path './CHANGELOG.md' -not -path './docs/package-lock.json' \ + -not -path './RELEASING.md' -exec sed -i "s/$ESCAPED_CURRENT_VERSION/$NEW_VERSION/g" {} + + + - name: Setup scarb + uses: software-mansion/setup-scarb@v1 + id: setup_scarb + with: + scarb-version: ${{ env.SCARB_VERSION }} + + - name: Setup class_hash + uses: ericnordelo/setup-class-hash@c14dd33506c3eb8e1acfe2ade9f82585f5acf28c + with: + version: "0.1.0" + + - name: Update presets page + run: | + class_hash get --json | sed -e '1,4d' | python3 scripts/get_hashes_page.py $SCARB_VERSION \ + > ./docs/modules/ROOT/pages/utils/_class_hashes.adoc - name: Auto-commit changes uses: stefanzweifel/git-auto-commit-action@3ea6ae190baf489ba007f7c92608f33ce20ef04a #v4.16.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index d1dc63a99..860ce66cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- ERC1155 component and preset (#896) +- Mixin implementations in components (#863) +- ERC721Component functions and Storage member + - `InternalTrait::_set_base_uri` and `InternalTrait::_base_uri` to handle ByteArrays (#857) + - `ERC721_base_uri` Storage member to store the base URI (#857) + ### Changed - Change unwrap to unwrap_syscall (#901) +- ERC20Component + - `IERC20::name` and `IERC20::symbol` return ByteArrays instead of felts (#857) +- ERC721Component + - `IERC721::name`, `IERC721::symbol`, and `IERC721Metadata::token_uri` return ByteArrays instead of felts (#857) + - `InternalTrait::initializer` accepts an additional `base_uri` ByteArray parameter (#857) + - IERC721Metadata SRC5 interface ID. This is changed because of the ByteArray integration (#857) + +### Removed + +- ERC721Component function and Storage member + - `InternalTrait::_set_token_uri` because individual token URIs are no longer stored (#857) + - `ERC721_token_uri` Storage member because individual token URIs are no longer stored (#857) ## 0.9.0 (2024-02-08) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 000000000..a1ba3091b --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,42 @@ +# Development cycle + +To keep up with the fast development of the Cairo language and Starknet network while maintaining a secure +and healthy development process, **we organize our work in 3-weeks cycles**. +These cycles consist of **milestones** and **sprints.** + +## 📍 Milestones + +A milestone is a set of [issues](https://github.com/OpenZeppelin/cairo-contracts/issues) intended to be addressed. +We usually aim to have at least 4 milestones planned ahead (~12 weeks of work), +enough visibility for users to set realistic expectations on when a feature will be available, +and give us space to gauge demand. +We can also make better decisions and prioritization when we’re mindful of the bigger picture. + +We organize issues in our [Github project](https://github.com/orgs/OpenZeppelin/projects/29) in the following views: + +- [Current milestone](https://github.com/orgs/OpenZeppelin/projects/29/views/2) (now) +- [Next milestone](https://github.com/orgs/OpenZeppelin/projects/29/views/3): ~3 weeks from now +- [After milestone](https://github.com/orgs/OpenZeppelin/projects/29/views/4): ~6 weeks from now +- [Later milestone](https://github.com/orgs/OpenZeppelin/projects/29/views/5): ~9 weeks from now +- [Backlog](https://github.com/orgs/OpenZeppelin/projects/29/views/7): issues not assigned to any milestone +- [Good to tackle](https://github.com/orgs/OpenZeppelin/projects/29/views/10): issues not planned soon nor in progress, for contributors to take + +## 🏁 Sprints + +A sprint is a 3-week period of time in which we intend to complete a milestone. +Some milestones might extend or shorten a little bit if issues are finished ahead or after schedule, +while the end of a sprint marks the time to release whatever work has been finished to date, +i.e. “[the release train departs](https://github.com/OpenZeppelin/cairo-contracts/blob/main/RELEASING.md)”. + +- We design milestones to take ~25 working days so we can tackle them in a single sprint + 1. 5 working days times 3 weeks/cycle per 3 devs + - ⇒ 45 workdays/sprint + 2. we estimate we spend roughly ~1/3 or our time doing reviews + - ⇒ 30 workdays/sprint + 3. we apply a 1/6 reduction to account for vacations, sickness, distractions, support, etc. + - **⇒ 25 workdays/sprint** +- To do so, we categorize issues by size based on a rough estimate of how many working days we expect it to take + - 🦔 tiny: ~0.5 days + - 🐇 small: ~2.5 days + - 🐂 medium: ~5 days + - 🦑 large: ~10 days diff --git a/README.md b/README.md index feafcbc56..50fa29bba 100644 --- a/README.md +++ b/README.md @@ -95,8 +95,8 @@ mod MyToken { initial_supply: u256, recipient: ContractAddress ) { - let name = 'MyToken'; - let symbol = 'MTK'; + let name = "MyToken"; + let symbol = "MTK"; self.erc20.initializer(name, symbol); self.erc20._mint(recipient, initial_supply); diff --git a/docs/antora.yml b/docs/antora.yml index 3c4ffb147..f13903da8 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -5,4 +5,4 @@ nav: - modules/ROOT/nav.adoc asciidoc: attributes: - page-sidebar-collapse-default: 'Access,Accounts,Introspection,Security,ERC20,ERC721,Upgrades' + page-sidebar-collapse-default: 'Access,Accounts,Introspection,Security,ERC20,ERC721,ERC1155,Upgrades' diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 880d9c88b..dce0c8550 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -30,7 +30,8 @@ **** xref:/api/erc20.adoc[API Reference] *** xref:erc721.adoc[ERC721] **** xref:/api/erc721.adoc[API Reference] -// *** xref:erc1155.adoc[ERC1155] +*** xref:erc1155.adoc[ERC1155] +**** xref:/api/erc1155.adoc[API Reference] ** xref:upgrades.adoc[Upgrades] *** xref:/api/upgrades.adoc[API Reference] diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index 1ed423638..71ed6b30a 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -230,8 +230,8 @@ mod MyContract { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, initial_supply: u256, recipient: ContractAddress, minter: ContractAddress @@ -321,8 +321,8 @@ mod MyContract { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, initial_supply: u256, recipient: ContractAddress, minter: ContractAddress, @@ -425,8 +425,8 @@ mod MyContract { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, initial_supply: u256, recipient: ContractAddress, admin: ContractAddress diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index f30918c5a..62f8d9196 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -7,6 +7,8 @@ = Access Control +include::../utils/_common.adoc[] + This directory provides ways to restrict who can access the functions of a contract or when they can do it. - {Ownable} is a simple mechanism with a single "owner" role that can be assigned to a single account. @@ -29,15 +31,32 @@ use openzeppelin::access::ownable::OwnableComponent; This module includes the internal `assert_only_owner` to restrict a function to be used only by the owner. -[.contract-index] +[.contract-index#OwnableComponent-Mixin-Impl] +.{mixin-impls} + +-- +.OwnableMixinImpl + +* xref:#OwnableComponent-Embeddable-Impls-OwnableImpl[`++OwnableImpl++`] +* xref:#OwnableComponent-Embeddable-Impls-OwnableCamelOnlyImpl[`++OwnableCamelOnlyImpl++`] + +.OwnableTwoStepMixinImpl + +* xref:#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl[`++OwnableTwoStepImpl++`] +* xref:#OwnableComponent-Embeddable-Impls-OwnableTwoStepCamelOnlyImpl[`++OwnableTwoStepCamelOnlyImpl++`] +-- + +[.contract-index#OwnableComponent-Embeddable-Impls] .Embeddable Implementations -- +[.sub-index#OwnableComponent-Embeddable-Impls-OwnableImpl] .OwnableImpl * xref:OwnableComponent-owner[`++owner(self)++`] * xref:OwnableComponent-transfer_ownership[`++transfer_ownership(self, new_owner)++`] * xref:OwnableComponent-renounce_ownership[`++renounce_ownership(self)++`] +[.sub-index#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl] .OwnableTwoStepImpl * xref:OwnableComponent-two-step-owner[`++owner(self)++`] @@ -45,16 +64,14 @@ This module includes the internal `assert_only_owner` to restrict a function to * xref:OwnableComponent-two-step-accept_ownership[`++accept_ownership(self)++`] * xref:OwnableComponent-two-step-transfer_ownership[`++transfer_ownership(self, new_owner)++`] * xref:OwnableComponent-two-step-renounce_ownership[`++renounce_ownership(self)++`] --- -[.contract-index] -.Embeddable Implementations (camelCase) --- +[.sub-index#OwnableComponent-Embeddable-Impls-OwnableCamelOnlyImpl] .OwnableCamelOnlyImpl * xref:OwnableComponent-transferOwnership[`++transferOwnership(self, newOwner)++`] * xref:OwnableComponent-renounceOwnership[`++renounceOwnership(self)++`] +[.sub-index#OwnableComponent-Embeddable-Impls-OwnableTwoStepCamelOnlyImpl] .OwnableTwoStepCamelOnlyImpl * xref:OwnableComponent-two-step-pendingOwner[`++pendingOwner(self)++`] @@ -83,7 +100,7 @@ This module includes the internal `assert_only_owner` to restrict a function to -- [#OwnableComponent-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[OwnableComponent-owner]] @@ -113,7 +130,7 @@ thereby removing any functionality that is only available to the owner. //end::renounce_ownership[] [#OwnableComponent-Embeddable-Functions-Two-Step] -==== Embeddable Functions (Two Step Transfer) +==== Embeddable functions (two step transfer) [.contract-item] [[OwnableComponent-two-step-owner]] @@ -150,9 +167,6 @@ Emits an xref:OwnableComponent-OwnershipTransferStarted[OwnershipTransferStarted ==== `[.contract-item-name]#++renounce_ownership++#++(ref self: ContractState)++` [.item-kind]#external# include::./access.adoc[tag=renounce_ownership] -[#OwnableComponent-camelCase-Support] -==== camelCase Support - [.contract-item] [[OwnableComponent-transferOwnership]] ==== `[.contract-item-name]#++transferOwnership++#++(ref self: ContractState, newOwner: ContractAddress)++` [.item-kind]#external# @@ -165,9 +179,6 @@ See xref:OwnableComponent-transfer_ownership[transfer_ownership]. See xref:OwnableComponent-renounce_ownership[renounce_ownership]. -[#OwnableComponent-camelCase-Support-Two-Step] -==== camelCase Support (Two Step Transfer) - [.contract-item] [[OwnableComponent-two-step-pendingOwner]] ==== `[.contract-item-name]#++pendingOwner++#++(self: @ContractState)++` [.item-kind]#external# @@ -193,7 +204,7 @@ See xref:OwnableComponent-two-step-transfer_ownership[transfer_ownership]. See xref:OwnableComponent-two-step-renounce_ownership[renounce_ownership]. [#OwnableComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[OwnableComponent-initializer]] @@ -434,9 +445,21 @@ WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role. Extra precautions should be taken to secure accounts that have been granted it. -[.contract-index] +[.contract-index#AccessControl-Mixin-Impl] +.{mixin-impls} + +-- +.AccessControlMixinImpl + +* xref:#AccessControlComponent-Embeddable-Impls-AccessControlImpl[`++AccessControlImpl++`] +* xref:#AccessControlComponent-Embeddable-Impls-AccessControlCamelImpl[`++AccessControlCamelImpl++`] +* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +-- + +[.contract-index#AccessControlComponent-Embeddable-Impls] .Embeddable Implementations -- +[.sub-index#AccessControlComponent-Embeddable-Impls-AccessControlImpl] .AccessControlImpl * xref:#AccessControlComponent-has_role[`++has_role(self, role, account)++`] @@ -445,13 +468,7 @@ accounts that have been granted it. * xref:#AccessControlComponent-revoke_role[`++revoke_role(self, role, account)++`] * xref:#AccessControlComponent-renounce_role[`++renounce_role(self, role, account)++`] -.SRC5Impl -* xref:#AccessControlComponent-supports_interface[`++supports_interface(self, interface_id: felt252)++`] --- - -[.contract-index] -.Embeddable Implementations (camelCase) --- +[.sub-index#AccessControlComponent-Embeddable-Impls-AccessControlCamelImpl] .AccessControlCamelImpl * xref:#AccessControlComponent-hasRole[`++hasRole(self, role, account)++`] @@ -459,6 +476,9 @@ accounts that have been granted it. * xref:#AccessControlComponent-grantRole[`++grantRole(self, role, account)++`] * xref:#AccessControlComponent-revokeRole[`++revokeRole(self, role, account)++`] * xref:#AccessControlComponent-renounceRole[`++renounceRole(self, role, account)++`] + +.SRC5Impl +* xref:api/introspection.adoc#ISRC5-supports_interface[`supports_interface(self, interface_id: felt252)`] -- [.contract-index] @@ -483,7 +503,7 @@ accounts that have been granted it. -- [#AccessControlComponent-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[AccessControlComponent-has_role]] @@ -554,9 +574,6 @@ May emit a {RoleRevoked} event. See xref:api/introspection.adoc#ISRC5-supports_interface[ISRC5::supports_interface]. -[#AccessControlComponent-camelCase-Support] -==== camelCase Support - [.contract-item] [[AccessControlComponent-hasRole]] ==== `[.contract-item-name]#++hasRole++#++(self: @ContractState, role: felt252, account: ContractAddress) → bool++` [.item-kind]#external# @@ -588,7 +605,7 @@ See xref:AccessControlComponent-revoke_role[revoke_role]. See xref:AccessControlComponent-renounce_role[renounce_role]. [#AccessControlComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[AccessControlComponent-initializer]] diff --git a/docs/modules/ROOT/pages/api/account.adoc b/docs/modules/ROOT/pages/api/account.adoc index 897a2bac5..138cd77fd 100644 --- a/docs/modules/ROOT/pages/api/account.adoc +++ b/docs/modules/ROOT/pages/api/account.adoc @@ -8,6 +8,8 @@ Reference of interfaces, presets, and utilities related to account contracts. == Core +include::../utils/_common.adoc[] + [.contract] [[ISRC6]] === `++ISRC6++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/account/interface.cairo[{github-icon},role=heading-link] @@ -74,42 +76,62 @@ use openzeppelin::account::AccountComponent; ``` Account component implementing xref:ISRC6[`ISRC6`] for signatures over the {starknet-curve}. -NOTE: Implementing xref:api/introspection.adoc#SRC5Component[SRC5Component] is a requirement for this component to be implemented. +NOTE: {src5-component-required-note} + +[.contract-index#AccountComponent-Embeddable-Mixin-Impl] +.{mixin-impls} + +-- +.AccountMixinImpl + +* xref:#AccountComponent-Embeddable-Impls-SRC6Impl[`++SRC6Impl++`] +* xref:#AccountComponent-Embeddable-Impls-DeclarerImpl[`++DeclarerImpl++`] +* xref:#AccountComponent-Embeddable-Impls-DeployableImpl[`++DeployableImpl++`] +* xref:#AccountComponent-Embeddable-Impls-PublicKeyImpl[`++PublicKeyImpl++`] +* xref:#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl[`++SRC6CamelOnlyImpl++`] +* xref:#AccountComponent-Embeddable-Impls-PublicKeyCamelImpl[`++PublicKeyCamelImpl++`] +* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +-- [.contract-index#AccountComponent-Embeddable-Impls] .Embeddable Implementations -- +[.sub-index#AccountComponent-Embeddable-Impls-SRC6Impl] .SRC6Impl * xref:#AccountComponent-\\__execute__[`++__execute__(self, calls)++`] * xref:#AccountComponent-\\__validate__[`++__validate__(self, calls)++`] * xref:#AccountComponent-is_valid_signature[`++is_valid_signature(self, hash, signature)++`] +[.sub-index#AccountComponent-Embeddable-Impls-DeclarerImpl] .DeclarerImpl * xref:#AccountComponent-\\__validate_declare__[`++__validate_declare__(self, class_hash)++`] +[.sub-index#AccountComponent-Embeddable-Impls-DeployableImpl] .DeployableImpl * xref:#AccountComponent-\\__validate_deploy__[`++__validate_deploy__(self, hash, signature)++`] +[.sub-index#AccountComponent-Embeddable-Impls-PublicKeyImpl] .PublicKeyImpl * xref:#AccountComponent-get_public_key[`++get_public_key(self)++`] * xref:#AccountComponent-set_public_key[`++set_public_key(self, new_public_key)++`] --- -[.contract-index#AccountComponent-Embeddable-Impls-camelCase] -.Embeddable Implementations (camelCase) --- +[.sub-index#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl] .SRC6CamelOnlyImpl * xref:#AccountComponent-isValidSignature[`++isValidSignature(self, hash, signature)++`] +[.sub-index#AccountComponent-Embeddable-Impls-PublicKeyCamelImpl] .PublicKeyCamelImpl * xref:#AccountComponent-getPublicKey[`++getPublicKey(self)++`] * xref:#AccountComponent-setPublicKey[`++setPublicKey(self, newPublicKey)++`] + +.SRC5Impl +* xref:api/introspection.adoc#ISRC5-supports_interface[`supports_interface(self, interface_id: felt252)`] -- [.contract-index] @@ -132,7 +154,7 @@ NOTE: Implementing xref:api/introspection.adoc#SRC5Component[SRC5Component] is a -- [#AccountComponent-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[AccountComponent-__execute__]] @@ -183,9 +205,6 @@ Sets a new public key for the account. Only accessible by the account calling it Emits both an {OwnerRemoved} and an {OwnerAdded} event. -[#AccountComponent-camelCase-Support] -==== camelCase Support - [.contract-item] [[AccountComponent-isValidSignature]] ==== `[.contract-item-name]#++isValidSignature++#++(self: @ContractState, hash: felt252, signature: Array) → felt252++` [.item-kind]#external# @@ -205,7 +224,7 @@ See xref:AccountComponent-get_public_key[get_public_key]. See xref:AccountComponent-set_public_key[set_public_key]. [#AccountComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[AccountComponent-initializer]] @@ -274,44 +293,64 @@ use openzeppelin::account::eth_account::EthAccountComponent; ``` Account component implementing xref:ISRC6[`ISRC6`] for signatures over the {secp256k1-curve}. -NOTE: Implementing xref:api/introspection.adoc#SRC5Component[SRC5Component] is a requirement for this component to be implemented. +NOTE: {src5-component-required-note} NOTE: The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Point`. +[.contract-index#EthAccountComponent-Embeddable-Mixin-Impl] +.{mixin-impls} + +-- +.EthAccountMixinImpl + +* xref:#EthAccountComponent-Embeddable-Impls-SRC6Impl[`++SRC6Impl++`] +* xref:#EthAccountComponent-Embeddable-Impls-DeclarerImpl[`++DeclarerImpl++`] +* xref:#EthAccountComponent-Embeddable-Impls-DeployableImpl[`++DeployableImpl++`] +* xref:#EthAccountComponent-Embeddable-Impls-PublicKeyImpl[`++PublicKeyImpl++`] +* xref:#EthAccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl[`++SRC6CamelOnlyImpl++`] +* xref:#EthAccountComponent-Embeddable-Impls-PublicKeyCamelImpl[`++PublicKeyCamelImpl++`] +* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +-- + [.contract-index#EthAccountComponent-Embeddable-Impls] .Embeddable Implementations -- +[.sub-index#EthAccountComponent-Embeddable-Impls-SRC6Impl] .SRC6Impl * xref:#EthAccountComponent-\\__execute__[`++__execute__(self, calls)++`] * xref:#EthAccountComponent-\\__validate__[`++__validate__(self, calls)++`] * xref:#EthAccountComponent-is_valid_signature[`++is_valid_signature(self, hash, signature)++`] +[.sub-index#EthAccountComponent-Embeddable-Impls-DeclarerImpl] .DeclarerImpl * xref:#EthAccountComponent-\\__validate_declare__[`++__validate_declare__(self, class_hash)++`] +[.sub-index#EthAccountComponent-Embeddable-Impls-DeployableImpl] .DeployableImpl * xref:#EthAccountComponent-\\__validate_deploy__[`++__validate_deploy__(self, hash, signature)++`] +[.sub-index#EthAccountComponent-Embeddable-Impls-PublicKeyImpl] .PublicKeyImpl * xref:#EthAccountComponent-get_public_key[`++get_public_key(self)++`] * xref:#EthAccountComponent-set_public_key[`++set_public_key(self, new_public_key)++`] --- -[.contract-index#EthAccountComponent-Embeddable-Impls-camelCase] -.Embeddable Implementations (camelCase) --- +[.sub-index#EthAccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl] .SRC6CamelOnlyImpl * xref:#EthAccountComponent-isValidSignature[`++isValidSignature(self, hash, signature)++`] +[.sub-index#EthAccountComponent-Embeddable-Impls-PublicKeyCamelImpl] .PublicKeyCamelImpl * xref:#EthAccountComponent-getPublicKey[`++getPublicKey(self)++`] * xref:#EthAccountComponent-setPublicKey[`++setPublicKey(self, newPublicKey)++`] + +.SRC5Impl +* xref:api/introspection.adoc#ISRC5-supports_interface[`supports_interface(self, interface_id: felt252)`] -- [.contract-index] @@ -334,7 +373,7 @@ NOTE: The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Poi -- [#EthAccountComponent-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[EthAccountComponent-__execute__]] @@ -385,9 +424,6 @@ Sets a new public key for the account. Only accesible by the account calling its Emits both an {OwnerRemoved} and an {OwnerAdded} event. -[#EthAccountComponent-camelCase-Support] -==== camelCase Support - [.contract-item] [[EthAccountComponent-isValidSignature]] ==== `[.contract-item-name]#++isValidSignature++#++(self: @ContractState, hash: felt252, signature: Array) → felt252++` [.item-kind]#external# @@ -407,7 +443,7 @@ See xref:EthAccountComponent-get_public_key[get_public_key]. See xref:EthAccountComponent-set_public_key[set_public_key]. [#EthAccountComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[EthAccountComponent-initializer]] @@ -482,7 +518,7 @@ include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{account-class-hash} +{Account-class-hash} -- [.contract-index] @@ -496,16 +532,7 @@ include::../utils/_class_hashes.adoc[] -- .AccountComponent -* xref:#AccountComponent-Embeddable-Impls[`++SRC6Impl++`] -* xref:#AccountComponent-Embeddable-Impls[`++PublicKeyImpl++`] -* xref:#AccountComponent-Embeddable-Impls[`++DeclarerImpl++`] -* xref:#AccountComponent-Embeddable-Impls[`++DeployableImpl++`] -* xref:#AccountComponent-Embeddable-Impls-camelCase[`++SRC6CamelOnlyImpl++`] -* xref:#AccountComponent-Embeddable-Impls-camelCase[`++PublicKeyCamelImpl++`] - -.SRC5Component - -* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +* xref:#AccountComponent-Embeddable-Mixin-Impl[`++AccountMixinImpl++`] -- [#Account-constructor-section] @@ -534,7 +561,7 @@ include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{eth-account-upgradeable-class-hash} +{EthAccountUpgradeable-class-hash} -- [.contract-index] @@ -548,16 +575,7 @@ include::../utils/_class_hashes.adoc[] -- .EthAccountComponent -* xref:#EthAccountComponent-Embeddable-Impls[`++SRC6Impl++`] -* xref:#EthAccountComponent-Embeddable-Impls[`++PublicKeyImpl++`] -* xref:#EthAccountComponent-Embeddable-Impls[`++DeclarerImpl++`] -* xref:#EthAccountComponent-Embeddable-Impls[`++DeployableImpl++`] -* xref:#EthAccountComponent-Embeddable-Impls-camelCase[`++SRC6CamelOnlyImpl++`] -* xref:#EthAccountComponent-Embeddable-Impls-camelCase[`++PublicKeyCamelImpl++`] - -.SRC5Component - -* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +* xref:#EthAccountComponent-Embeddable-Mixin-Impl[`++EthAccountMixinImpl++`] -- [.contract-index] @@ -576,7 +594,7 @@ include::../utils/_class_hashes.adoc[] Sets the account `public_key` and registers the interfaces the contract supports. [#EthAccountUpgradeable-external-functions] -==== External Functions +==== External functions [.contract-item] [[EthAccountUpgradeable-upgrade]] diff --git a/docs/modules/ROOT/pages/api/erc1155.adoc b/docs/modules/ROOT/pages/api/erc1155.adoc new file mode 100644 index 000000000..bd4afdda1 --- /dev/null +++ b/docs/modules/ROOT/pages/api/erc1155.adoc @@ -0,0 +1,670 @@ +:github-icon: pass:[] +:eip1155: https://eips.ethereum.org/EIPS/eip-1155[EIP1155] +:eip1155-metadata: https://eips.ethereum.org/EIPS/eip-1155#metadata +:receiving-tokens: xref:/erc1155.adoc#receiving_tokens[Receiving tokens] +:inner-src5: xref:api/introspection.adoc#ISRC5[SRC5 ID] + += ERC1155 + +include::../utils/_common.adoc[] + +Reference of interfaces, presets, and utilities related to ERC1155 contracts. + +TIP: For an overview of ERC1155, read our xref:erc1155.adoc[ERC1155 guide]. + +== Core + +[.contract] +[[IERC1155]] +=== `++IERC1155++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc1155::interface::IERC1155; +``` +Interface of the IERC1155 standard as defined in {eip1155}. + +[.contract-index] +.{inner-src5} +-- +0x6114a8f75559e1b39fcba08ce02961a1aa082d9256a158dd3e64964e4b1b52 +-- + +[.contract-index] +.Functions +-- +* xref:#IERC1155-balance_of[`++balance_of(account, token_id)++`] +* xref:#IERC1155-balance_of_batch[`++balance_of_batch(accounts, token_ids)++`] +* xref:#IERC1155-safe_transfer_from[`++safe_transfer_from(from, to, token_id, value, data)++`] +* xref:#IERC1155-safe_batch_transfer_from[`++safe_batch_transfer_from(from, to, token_ids, values, data)++`] +* xref:#IERC1155-set_approval_for_all[`++set_approval_for_all(operator, approved)++`] +* xref:#IERC1155-is_approved_for_all[`++is_approved_for_all(owner, operator)++`] +-- + +[.contract-index] +.Events +-- +* xref:#IERC1155-TransferSingle[`++TransferSingle(operator, from, to, id, value)++`] +* xref:#IERC1155-TransferBatch[`++TransferBatch(operator, from, to, ids, values)++`] +* xref:#IERC1155-ApprovalForAll[`++ApprovalForAll(owner, operator, approved)++`] +* xref:#IERC1155-URI[`++URI(value, id)++`] +-- + +==== Functions + +[.contract-item] +[[IERC1155-balance_of]] +==== `[.contract-item-name]#++balance_of++#++(account: ContractAddress, token_id: u256) → u256++` [.item-kind]#external# + +Returns the amount of `token_id` tokens owned by `account`. + +[.contract-item] +[[IERC1155-balance_of_batch]] +==== `[.contract-item-name]#++balance_of_batch++#++(accounts: Span, token_ids: Span) → Span++` [.item-kind]#external# + +Returns a list of balances derived from the `accounts` and `token_ids` pairs. + +[.contract-item] +[[IERC1155-safe_transfer_from]] +==== `[.contract-item-name]#++safe_transfer_from++#++(from: ContractAddress, to: ContractAddress, token_id: u256, value: u256, data: Span)++` [.item-kind]#external# + +Transfers ownership of `value` amount of `token_id` from `from` if `to` is either `IERC1155Receiver` or an account. + +`data` is additional data, it has no specified format and it is passed to `to`. + +Emits a <> event. + +[.contract-item] +[[IERC1155-safe_batch_transfer_from]] +==== `[.contract-item-name]#++safe_batch_transfer_from++#++(from: ContractAddress, to: ContractAddress, token_ids: Span, values: Span, data: Span)++` [.item-kind]#external# + +Transfers ownership of `token_ids` and `values` pairs from `from` if `to` is either `IERC1155Receiver` or an account. + +`data` is additional data, it has no specified format and it is passed to `to`. + +Emits a <> event. + +[.contract-item] +[[IERC1155-set_approval_for_all]] +==== `[.contract-item-name]#++set_approval_for_all++#++(operator: ContractAddress, approved: bool)++` [.item-kind]#external# + +Enables or disables approval for `operator` to manage all of the caller's assets. + +Emits an <> event. + +[.contract-item] +[[IERC1155-is_approved_for_all]] +==== `[.contract-item-name]#++is_approved_for_all++#++(owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# + +Queries if `operator` is an authorized operator for `owner`. + +==== Events + +[.contract-item] +[[IERC1155-TransferSingle]] +==== `[.contract-item-name]#++TransferSingle++#++(operator: ContractAddress, from: ContractAddress, to: ContractAddress, id: u256, value: u256)++` [.item-kind]#event# + +Emitted when `value` amount of `id` token is transferred from `from` to `to` through `operator`. + +[.contract-item] +[[IERC1155-TransferBatch]] +==== `[.contract-item-name]#++TransferBatch++#++(operator: ContractAddress, from: ContractAddress, to: ContractAddress, ids: Span, values: Span)++` [.item-kind]#event# + +Emitted when a batch of `values` amount of `ids` tokens are transferred from `from` to `to` through `operator`. + +[.contract-item] +[[IERC1155-ApprovalForAll]] +==== `[.contract-item-name]#++ApprovalForAll++#++(owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#event# + +Emitted when `owner` enables or disables `operator` to manage all of the owner's assets. + +[.contract-item] +[[IERC1155-URI]] +==== `[.contract-item-name]#++URI++#++(value: ByteArray, id: u256)++` [.item-kind]#event# + +Emitted when the token URI is updated to `value` for the `id` token. + +[.contract] +[[IERC1155MetadataURI]] +=== `++IERC1155MetadataURI++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc1155::interface::IERC1155MetadataURI; +``` +Interface for the optional metadata function in {eip1155-metadata}[EIP1155]. + +[.contract-index] +.{inner-src5} +-- +0xcabe2400d5fe509e1735ba9bad205ba5f3ca6e062da406f72f113feb889ef7 +-- + +[.contract-index] +.Functions +-- +* xref:#IERC1155MetadataURI-uri[`++uri(token_id)++`] +-- + +==== Functions + +[.contract-item] +[[IERC1155MetadataURI-uri]] +==== `[.contract-item-name]#++uri++#++(token_id: u256) -> ByteArray++` [.item-kind]#external# + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. + +[.contract] +[[ERC1155Component]] +=== `++ERC1155Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc1155/erc1155.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc1155::ERC1155Component; +``` + +ERC1155 component implementing <> and <>. + +NOTE: {src5-component-required-note} + +[.contract-index#ERC1155Component-Embeddable-Impls] +.Embeddable Implementations +-- +[.sub-index#ERC1155Component-Embeddable-Impls-ERC1155Impl] +.ERC1155Impl +* xref:#ERC1155Component-balance_of[`++balance_of(self, account, token_id)++`] +* xref:#ERC1155Component-balance_of_batch[`++balance_of_batch(self, accounts, token_ids)++`] +* xref:#ERC1155Component-safe_transfer_from[`++safe_transfer_from(self, from, to, token_id, value, data)++`] +* xref:#ERC1155Component-safe_batch_transfer_from[`++safe_batch_transfer_from(self, from, to, token_ids, values, data)++`] +* xref:#ERC1155Component-set_approval_for_all[`++set_approval_for_all(self, operator, approved)++`] +* xref:#ERC1155Component-is_approved_for_all[`++is_approved_for_all(self, owner, operator)++`] + +[.sub-index#ERC1155Component-Embeddable-Impls-ERC1155MetadataURIImpl] +.ERC1155MetadataURIImpl +* xref:#ERC1155Component-uri[`++uri(self, token_id)++`] +-- + +[.contract-index#ERC1155Component-Embeddable-Impls-camelCase] +.Embeddable implementations (camelCase) +-- +[.sub-index#ERC1155Component-Embeddable-Impls-ER1155CamelImpl] +.ER1155CamelImpl +* xref:#ERC1155Component-balanceOf[`++balanceOf(self, account, tokenId)++`] +* xref:#ERC1155Component-balanceOfBatch[`++balanceOfBatch(self, accounts, tokenIds)++`] +* xref:#ERC1155Component-safeTransferFrom[`++safeTransferFrom(self, from, to, tokenId, value, data)++`] +* xref:#ERC1155Component-safeBatchTransferFrom[`++safeBatchTransferFrom(self, from, to, tokenIds, values, data)++`] +* xref:#ERC1155Component-setApprovalForAll[`++setApprovalForAll(self, operator, approved)++`] +* xref:#ERC1155Component-isApprovedForAll[`++isApprovedForAll(self, owner, operator)++`] +-- + +[.contract-index] +.Internal Functions +-- +.InternalImpl +* xref:#ERC1155Component-initializer[`++initializer(self, base_uri)++`] +* xref:#ERC1155Component-update[`++update(self, from, to, token_ids, values)++`] +* xref:#ERC1155Component-update_with_acceptance_check[`++update_with_acceptance_check(self, from, to, token_ids, values, data)++`] +* xref:#ERC1155Component-mint_with_acceptance_check[`++mint_with_acceptance_check(self, to, token_id, value, data)++`] +* xref:#ERC1155Component-batch_mint_with_acceptance_check[`++batch_mint_with_acceptance_check(self, to, token_ids, values, data)++`] +* xref:#ERC1155Component-burn[`++burn(self, from, token_id, value)++`] +* xref:#ERC1155Component-batch_burn[`++batch_burn(self, from, token_ids, values)++`] +* xref:#ERC1155Component-set_base_uri[`++set_base_uri(self, base_uri)++`] +-- + +[.contract-index] +.Events +-- +.IERC1155 +* xref:#ERC1155Component-TransferSingle[`++TransferSingle(operator, from, to, id, value)++`] +* xref:#ERC1155Component-TransferBatch[`++TransferBatch(operator, from, to, ids, values)++`] +* xref:#ERC1155Component-ApprovalForAll[`++ApprovalForAll(owner, operator, approved)++`] +* xref:#ERC1155Component-URI[`++URI(value, id)++`] +-- + +==== Embeddable functions + +[.contract-item] +[[ERC1155Component-balance_of]] +==== `[.contract-item-name]#++balance_of++#++(self: @ContractState, account: ContractAddress, token_id: u256) → u256++` [.item-kind]#external# + +Returns the amount of `token_id` tokens owned by `account`. + +[.contract-item] +[[ERC1155Component-balance_of_batch]] +==== `[.contract-item-name]#++balance_of_batch++#++(self: @ContractState, accounts: Span, token_ids: Span) → Span++` [.item-kind]#external# + +Returns a list of balances derived from the `accounts` and `token_ids` pairs. + +Requirements: + +- `token_ids` and `accounts` must have the same length. + +[.contract-item] +[[ERC1155Component-safe_transfer_from]] +==== `[.contract-item-name]#++safe_transfer_from++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, value: u256, data: Span)++` [.item-kind]#external# + +Transfers ownership of `value` amount of `token_id` from `from` if `to` is either an account or `IERC1155Receiver`. + +`data` is additional data, it has no specified format and it is passed to `to`. + +WARNING: This function can potentially allow a reentrancy attack when transferring tokens +to an untrusted contract, when invoking `on_ERC1155_received` on the receiver. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `from` is not the zero address. +- `to` is not the zero address. +- If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_received` + and return the required magic value. + +Emits a <> event. + +[.contract-item] +[[ERC1155Component-safe_batch_transfer_from]] +==== `[.contract-item-name]#++safe_batch_transfer_from++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_ids: Span, values: Span, data: Span)++` [.item-kind]#external# + +Transfers ownership of `values` and `token_ids` pairs from `from` if `to` is either an account or `IERC1155Receiver`. + +`data` is additional data, it has no specified format and it is passed to `to`. + +WARNING: This function can potentially allow a reentrancy attack when transferring tokens +to an untrusted contract, when invoking `on_ERC1155_batch_received` on the receiver. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `from` is not the zero address. +- `to` is not the zero address. +- `token_ids` and `values` must have the same length. +- If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` + and return the acceptance magic value. + +Emits a <> event if the arrays contain one element, +and <> otherwise. + +[.contract-item] +[[ERC1155Component-set_approval_for_all]] +==== `[.contract-item-name]#++set_approval_for_all++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# + +Enables or disables approval for `operator` to manage all of the callers assets. + +Requirements: + +- `operator` cannot be the caller. + +Emits an <> event. + +[.contract-item] +[[ERC1155Component-is_approved_for_all]] +==== `[.contract-item-name]#++is_approved_for_all++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# + +Queries if `operator` is an authorized operator for `owner`. + +[.contract-item] +[[ERC1155Component-uri]] +==== `[.contract-item-name]#++uri++#++(self: @ContractState, token_id: u256) -> ByteArray++` [.item-kind]#external# + +This implementation returns the same URI for *all* token types. It relies +on the token type ID substitution mechanism +{eip1155-metadata}[specified in the EIP]. + +Clients calling this function must replace the `\{id\}` substring with the +actual token type ID. + +==== camelCase Support + +[.contract-item] +[[ERC1155Component-balanceOf]] +==== `[.contract-item-name]#++balanceOf++#++(self: @ContractState, account: ContractAddress, tokenId: u256) → u256++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC1155Component-balanceOfBatch]] +==== `[.contract-item-name]#++balanceOfBatch++#++(self: @ContractState, accounts: Span, tokenIds: Span) → Span++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC1155Component-safeTransferFrom]] +==== `[.contract-item-name]#++safeTransferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256, value: u256, data: Span)++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC1155Component-safeBatchTransferFrom]] +==== `[.contract-item-name]#++safeBatchTransferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenIds: Span, values: Span, data: Span)++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC1155Component-setApprovalForAll]] +==== `[.contract-item-name]#++setApprovalForAll++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC1155Component-isApprovedForAll]] +==== `[.contract-item-name]#++isApprovedForAll++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# + +See <>. + +==== Internal functions + +[.contract-item] +[[ERC1155Component-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, base_uri: ByteArray)++` [.item-kind]#internal# + +Initializes the contract by setting the token's base URI as `base_uri`, and registering the supported interfaces. +This should only be used inside the contract's constructor. + +[.contract-item] +[[ERC1155Component-update]] +==== `[.contract-item-name]#++update++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_ids: Span, values: Span)++` [.item-kind]#internal# + +Transfers a `value` amount of tokens of type `id` from `from` to `to`. +Will mint (or burn) if `from` (or `to`) is the zero address. + +Requirements: + +- `token_ids` and `values` must have the same length. + +Emits a <> event if the arrays contain one element, +and <> otherwise. + +NOTE: The ERC1155 acceptance check is not performed in this function. +See <> instead. + +[.contract-item] +[[ERC1155Component-update_with_acceptance_check]] +==== `[.contract-item-name]#++update_with_acceptance_check++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_ids: Span, values: Span, data: Span)++` [.item-kind]#internal# + +Version of `update` that performs the token acceptance check by calling +`onERC1155Received` or `onERC1155BatchReceived` in the receiver if +it implements `IERC1155Receiver`, otherwise by checking if it is an account. + +Requirements: + +- `to` is either an account contract or supports the `IERC1155Receiver` interface. +- `token_ids` and `values` must have the same length. + +Emits a <> event if the arrays contain one element, +and <> otherwise. + +[.contract-item] +[[ERC1155Component-mint_with_acceptance_check]] +==== `[.contract-item-name]#++mint_with_acceptance_check++#++(ref self: ContractState, to: ContractAddress, token_id: u256, value: u256, data: Span)++` [.item-kind]#internal# + +Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`. + +Requirements: + +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_received` +and return the acceptance magic value. + +Emits a <> event. + +[.contract-item] +[[ERC1155Component-batch_mint_with_acceptance_check]] +==== `[.contract-item-name]#++batch_mint_with_acceptance_check++#++(ref self: ContractState, to: ContractAddress, token_ids: Span, values: Span, data: Span)++` [.item-kind]#internal# + +Batched version of <>. + +Requirements: + +- `to` cannot be the zero address. +- `token_ids` and `values` must have the same length. +- If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` +and return the acceptance magic value. + +Emits a <> event. + +[.contract-item] +[[ERC1155Component-burn]] +==== `[.contract-item-name]#++burn++#++(ref self: ContractState, from: ContractAddress, token_id: u256, value: u256)++` [.item-kind]#internal# + +Destroys a `value` amount of tokens of type `token_id` from `from`. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `token_id`. + +Emits a <> event. + +[.contract-item] +[[ERC1155Component-batch_burn]] +==== `[.contract-item-name]#++batch_burn++#++(ref self: ContractState, from: ContractAddress, token_ids: Span, values: Span)++` [.item-kind]#internal# + +Batched version of <>. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `token_id`. +- `token_ids` and `values` must have the same length. + +Emits a <> event. + +[.contract-item] +[[ERC1155Component-set_base_uri]] +==== `[.contract-item-name]#++set_base_uri++#++(ref self: ContractState, base_uri: ByteArray)++` [.item-kind]#internal# + +Sets a new URI for all token types, by relying on the token type ID +substitution mechanism +{eip1155-metadata}[specified in the EIP]. + +By this mechanism, any occurrence of the `\{id\}` substring in either the +URI or any of the values in the JSON file at said URI will be replaced by +clients with the token type ID. + +For example, the pass:[https://token-cdn-domain/\{id\}.json] URI would be +interpreted by clients as +pass:[https://token-cdn-domain/000000000000...000000000000004cce0.json] +for token type ID `0x4cce0`. + +Because these URIs cannot be meaningfully represented by the `URI` event, +this function emits no events. + +==== Events + +[.contract-item] +[[ERC1155Component-TransferSingle]] +==== `[.contract-item-name]#++TransferSingle++#++(operator: ContractAddress, from: ContractAddress, to: ContractAddress, id: u256, value: u256)++` [.item-kind]#event# + +See <>. + +[.contract-item] +[[ERC1155Component-TransferBatch]] +==== `[.contract-item-name]#++TransferBatch++#++(operator: ContractAddress, from: ContractAddress, to: ContractAddress, ids: Span, values: Span)++` [.item-kind]#event# + +See <>. + +[.contract-item] +[[ERC1155Component-ApprovalForAll]] +==== `[.contract-item-name]#++ApprovalForAll++#++(owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#event# + +See <>. + +[.contract-item] +[[ERC1155Component-URI]] +==== `[.contract-item-name]#++URI++#++(value: ByteArray, id: u256)++` [.item-kind]#event# + +See <>. + +== Receiver + +[.contract] +[[IERC1155Receiver]] +=== `++IERC1155Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc1155::interface::IERC1155Receiver; +``` + +Interface for contracts that support receiving token transfers from `ERC1155` contracts. + +[.contract-index] +.{inner-src5} +-- +0x15e8665b5af20040c3af1670509df02eb916375cdf7d8cbaf7bd553a257515e +-- + +[.contract-index] +.Functions +-- +* xref:#IERC1155Receiver-on_erc1155_received[`++on_erc1155_received(operator, from, token_id, value, data)++`] +* xref:#IERC1155Receiver-on_erc1155_batch_received[`++on_erc1155_batch_received(operator, from, token_ids, values, data)++`] +-- + +==== Functions + +[.contract-item] +[[IERC1155Receiver-on_erc1155_received]] +==== `[.contract-item-name]#++on_erc1155_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, value: u256, data Span) -> felt252++` [.item-kind]#external# + +This function is called whenever an ERC1155 `token_id` token is transferred to this `IERC1155Receiver` implementer +via <> by `operator` from `from`. + +[.contract-item] +[[IERC1155Receiver-on_erc1155_batch_received]] +==== `[.contract-item-name]#++on_erc1155_batch_received++#++(operator: ContractAddress, from: ContractAddress, token_ids: Span, values: Span, data Span) -> felt252++` [.item-kind]#external# + +This function is called whenever multiple ERC1155 `token_ids` tokens are transferred to this `IERC1155Receiver` implementer +via <> by `operator` from `from`. + +[.contract] +[[ERC1155ReceiverComponent]] +=== `++ERC1155ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc1155/erc1155_receiver.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc1155::ERC1155ReceiverComponent; +``` + +ERC1155Receiver component implementing <>. + +NOTE: {src5-component-required-note} + +[.contract-index#ERC1155ReceiverComponent-Embeddable-Impls] +.Embeddable Implementations +-- +.ERC1155ReceiverImpl +* xref:#ERC1155ReceiverComponent-on_erc1155_received[`++on_erc1155_received(self, operator, from, token_id, value, data)++`] +* xref:#ERC1155ReceiverComponent-on_erc1155_batch_received[`++on_erc1155_batch_received(self, operator, from, token_ids, values, data)++`] +-- + +[.contract-index#ERC1155ReceiverComponent-Embeddable-Impls-camelCase] +.Embeddable implementations (camelCase) +-- +.ERC1155ReceiverCamelImpl +* xref:#ERC1155ReceiverComponent-onERC1155Received[`++onERC1155Received(self, operator, from, tokenId, value, data)++`] +* xref:#ERC1155ReceiverComponent-onERC1155BatchReceived[`++onERC1155BatchReceived(self, operator, from, tokenIds, values, data)++`] +-- + +[.contract-index] +.Internal Functions +-- +.InternalImpl +* xref:#ERC1155ReceiverComponent-initializer[`++initializer(self)++`] +-- + +==== Embeddable functions + +[.contract-item] +[[ERC1155ReceiverComponent-on_erc1155_received]] +==== `[.contract-item-name]#++on_erc1155_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, value: u256, data Span) -> felt252++` [.item-kind]#external# + +Returns the `IERC1155Receiver` interface ID. + +[.contract-item] +[[ERC1155ReceiverComponent-on_erc1155_batch_received]] +==== `[.contract-item-name]#++on_erc1155_batch_received++#++(operator: ContractAddress, from: ContractAddress, token_ids: Span, values: Span, data Span) -> felt252++` [.item-kind]#external# + +Returns the `IERC1155Receiver` interface ID. + +==== camelCase Support + + +[.contract-item] +[[ERC1155ReceiverComponent-onERC1155Received]] +==== `[.contract-item-name]#++onERC1155Received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, value: u256, data Span) -> felt252++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC1155ReceiverComponent-onERC1155BatchReceived]] +==== `[.contract-item-name]#++onERC1155BatchReceived++#++(operator: ContractAddress, from: ContractAddress, token_ids: Span, values: Span, data Span) -> felt252++` [.item-kind]#external# + +See <>. + +==== Internal functions + +[.contract-item] +[[ERC1155ReceiverComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState)++` [.item-kind]#internal# + +Registers the `IERC1155Receiver` interface ID as supported through introspection. + +== Presets + +[.contract] +[[ERC1155]] +=== `++ERC1155++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/presets/erc1155.cairo[{github-icon},role=heading-link] + +```javascript +use openzeppelin::presets::ERC1155; +``` + +Basic ERC1155 contract leveraging xref:#ERC1155Component[ERC1155Component]. + +include::../utils/_class_hashes.adoc[] + +[.contract-index] +.{presets-page} +-- +{ERC1155-class-hash} +-- + +[.contract-index] +.Constructor +-- +* xref:#ERC1155-constructor[`++constructor(self, base_uri, recipient, token_ids, values)++`] +-- + +[.contract-index] +.Embedded Implementations +-- +.ERC1155Component + +* xref:#ERC1155Component-Embeddable-Impls-ERC1155Impl[`++ERC1155Impl++`] +* xref:#ERC1155Component-Embeddable-Impls-ERC1155MetadataURIImpl[`++ERC1155MetadataURIImpl++`] +* xref:#ERC1155Component-Embeddable-Impls-ER1155CamelImpl[`++ERC1155CamelImpl++`] + +.SRC5Component + +* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls-SRC5Impl[`++SRC5Impl++`] +-- + +[#ERC1155-constructor-section] +==== Constructor + +[.contract-item] +[[ERC1155-constructor]] +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, base_uri: ByteArray, recipient: ContractAddress, token_ids: Span, values: Span)++` [.item-kind]#constructor# + +Sets the `base_uri` for all tokens and registers the supported interfaces. +Mints the `values` for `token_ids` tokens to `recipient`. + +Requirements: + +- `to` is either an account contract (supporting ISRC6) or + supports the `IERC1155Receiver` interface. +- `token_ids` and `values` must have the same length. diff --git a/docs/modules/ROOT/pages/api/erc20.adoc b/docs/modules/ROOT/pages/api/erc20.adoc index 66c84fca3..9f18c1d16 100644 --- a/docs/modules/ROOT/pages/api/erc20.adoc +++ b/docs/modules/ROOT/pages/api/erc20.adoc @@ -6,6 +6,8 @@ = ERC20 +include::../utils/_common.adoc[] + Reference of interfaces and utilities related to ERC20 contracts. TIP: For an overview of ERC20, read our {erc20-guide}. @@ -134,13 +136,13 @@ Interface for the optional metadata functions in {eip20}. [.contract-item] [[IERC20Metadata-name]] -==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++name++#++() → ByteArray++` [.item-kind]#external# Returns the name of the token. [.contract-item] [[IERC20Metadata-symbol]] -==== `[.contract-item-name]#++symbol++#++() → felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++symbol++#++() → ByteArray++` [.item-kind]#external# Returns the ticker symbol of the token. @@ -168,9 +170,20 @@ use openzeppelin::token::erc20::ERC20Component; ``` ERC20 component extending <> and <>. +[.contract-index#ERC20Component-Embeddable-Mixin-Impl] +.{mixin-impls} + +-- +.ERC20MixinImpl +* xref:#ERC20Component-Embeddable-Impls-ERC20Impl[`++ERC20Impl++`] +* xref:#ERC20Component-Embeddable-Impls-ERC20MetadataImpl[`++ERC20MetadataImpl++`] +* xref:#ERC20Component-Embeddable-Impls-ERC20CamelOnlyImpl[`++ERC20CamelOnlyImpl++`] +-- + [.contract-index#ERC20Component-Embeddable-Impls] -.Embeddable implementations +.Embeddable Implementations -- +[.sub-index#ERC20Component-Embeddable-Impls-ERC20Impl] .ERC20Impl * xref:#ERC20Component-total_supply[`++total_supply(self)++`] * xref:#ERC20Component-balance_of[`++balance_of(self, account)++`] @@ -179,15 +192,13 @@ ERC20 component extending <> and < * xref:#ERC20Component-transfer_from[`++transfer_from(self, sender, recipient, amount)++`] * xref:#ERC20Component-approve[`++approve(self, spender, amount)++`] +[.sub-index#ERC20Component-Embeddable-Impls-ERC20MetadataImpl] .ERC20MetadataImpl * xref:#ERC20Component-name[`++name(self)++`] * xref:#ERC20Component-symbol[`++symbol(self)++`] * xref:#ERC20Component-decimals[`++decimals(self)++`] --- -[.contract-index#ERC20Component-Embeddable-Impls-camelCase] -.Embeddable implementations (camelCase) --- +[.sub-index#ERC20Component-Embeddable-Impls-ERC20CamelOnlyImpl] .ERC20CamelOnlyImpl * xref:#ERC20Component-totalSupply[`++totalSupply(self)++`] * xref:#ERC20Component-balanceOf[`++balanceOf(self, account)++`] @@ -270,13 +281,13 @@ Requirements: [.contract-item] [[ERC20Component-name]] -==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++name++#++() → ByteArray++` [.item-kind]#external# See <>. [.contract-item] [[ERC20Component-symbol]] -==== `[.contract-item-name]#++symbol++#++() → felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++symbol++#++() → ByteArray++` [.item-kind]#external# See <>. @@ -286,9 +297,6 @@ See <>. See <>. -[#ERC20Component-camelCase-support] -==== camelCase Support - [.contract-item] [[ERC20Component-totalSupply]] ==== `[.contract-item-name]#++totalSupply++#++(self: @ContractState) → u256++` [.item-kind]#external# @@ -318,7 +326,7 @@ Supports the Cairo v0 convention of writing external methods in camelCase as dis [.contract-item] [[ERC20Component-initializer]] -==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, name: felt252, symbol: felt252)++` [.item-kind]#internal# +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray)++` [.item-kind]#internal# Initializes the contract by setting the token name and symbol. This should be used inside of the contract's constructor. @@ -420,7 +428,7 @@ include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{erc20-class-hash} +{ERC20-class-hash} -- [.contract-index] @@ -432,11 +440,9 @@ include::../utils/_class_hashes.adoc[] [.contract-index] .Embedded Implementations -- -.ERC20Component +.ERC20MixinImpl -* xref:#ERC20Component-Embeddable-Impls[`++ERC20Impl++`] -* xref:#ERC20Component-Embeddable-Impls[`++ERC20MetadataImpl++`] -* xref:#ERC20Component-Embeddable-Impls-camelCase[`++ERC20CamelOnlyImpl++`] +* xref:#ERC20Component-Embeddable-Mixin-Impl[`++ERC20MixinImpl++`] -- [#ERC20-constructor-section] @@ -444,6 +450,6 @@ include::../utils/_class_hashes.adoc[] [.contract-item] [[ERC20-constructor]] -==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: felt252, symbol: felt252, fixed_supply: u256, recipient: ContractAddress)++` [.item-kind]#constructor# +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress)++` [.item-kind]#constructor# Sets the `name` and `symbol` and mints `fixed_supply` tokens to `recipient`. diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index b85597ee2..321e1634d 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -6,6 +6,8 @@ = ERC721 +include::../utils/_common.adoc[] + Reference of interfaces, presets, and utilities related to ERC721 contracts. TIP: For an overview of ERC721, read our xref:erc721.adoc[ERC721 guide]. @@ -14,7 +16,7 @@ TIP: For an overview of ERC721, read our xref:erc721.adoc[ERC721 guide]. [.contract] [[IERC721]] -=== `++IERC721++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/interface.cairo#L13-L31[{github-icon},role=heading-link] +=== `++IERC721++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc721/interface.cairo#L13-L31[{github-icon},role=heading-link] [.hljs-theme-dark] ```javascript @@ -97,7 +99,7 @@ Emits an <> event. Enable or disable approval for `operator` to manage all of the caller's assets. -Emits an <> event. +Emits an <> event. [.contract-item] [[IERC721-get_approved]] @@ -123,7 +125,7 @@ Emitted when `owner` enables `approved` to manage the `token_id` token. [[IERC721-ApprovalForAll]] ==== `[.contract-item-name]#++ApprovalForAll++#++(owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#event# -Emitted when `owner` enables `approved` to manage the `token_id` token. +Emitted when `owner` enables or disables `operator` to manage the `token_id` token. [.contract-item] [[IERC721-Transfer]] @@ -133,7 +135,7 @@ Emitted when `token_id` token is transferred from `from` to `to`. [.contract] [[IERC721Metadata]] -=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/interface.cairo#L54-L59[{github-icon},role=heading-link] +=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc721/interface.cairo#L54-L59[{github-icon},role=heading-link] [.hljs-theme-dark] ```javascript @@ -145,7 +147,7 @@ Interface for the optional metadata functions in {eip721}. [.contract-index] .{inner-src5} -- -0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd +0xabbcd595a567dce909050a1038e055daccb3c42af06f0add544fa90ee91f25 -- [.contract-index] @@ -160,26 +162,26 @@ Interface for the optional metadata functions in {eip721}. [.contract-item] [[IERC721Metadata-name]] -==== `[.contract-item-name]#++name++#++() -> felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++name++#++() -> ByteArray++` [.item-kind]#external# Returns the NFT name. [.contract-item] [[IERC721Metadata-symbol]] -==== `[.contract-item-name]#++symbol++#++() -> felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++symbol++#++() -> ByteArray++` [.item-kind]#external# Returns the NFT ticker symbol. [.contract-item] [[IERC721Metadata-token_uri]] -==== `[.contract-item-name]#++token_uri++#++(token_id: u256) -> felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++token_uri++#++(token_id: u256) -> ByteArray++` [.item-kind]#external# -Returns the Uniform Resource Identifier (URI) as a short string for the `token_id` token. -If the URI is not set for `token_id`, the return value will be `0`. +Returns the Uniform Resource Identifier (URI) for the `token_id` token. +If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. [.contract] [[ERC721Component]] -=== `++ERC721Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/erc721.cairo#L7[{github-icon},role=heading-link] +=== `++ERC721Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc721/erc721.cairo#L7[{github-icon},role=heading-link] [.hljs-theme-dark] ```javascript @@ -188,80 +190,96 @@ use openzeppelin::token::erc721::ERC721Component; ERC721 component implementing <> and <>. -NOTE: Implementing xref:api/introspection.adoc#SRC5Component[SRC5Component] is a requirement for this component to be implemented. +NOTE: {src5-component-required-note} + +[.contract-index#ERC721Component-Embeddable-Mixin-Impl] +.{mixin-impls} -[.contract-index#ERC721Component-Embeddable-Impls] -.Embeddable Implementations -- -.IERC721Impl -* xref:#IERC721-balance_of[`++balance_of(self, account)++`] -* xref:#IERC721-owner_of[`++owner_of(self, token_id)++`] -* xref:#IERC721-safe_transfer_from[`++safe_transfer_from(self, from, to, token_id, data)++`] -* xref:#IERC721-transfer_from[`++transfer_from(self, from, to, token_id)++`] -* xref:#IERC721-approve[`++approve(self, to, token_id)++`] -* xref:#IERC721-set_approval_for_all[`++set_approval_for_all(self, operator, approved)++`] -* xref:#IERC721-get_approved[`++get_approved(self, token_id)++`] -* xref:#IERC721-is_approved_for_all[`++is_approved_for_all(self, owner, operator)++`] - -.IERC721MetadataImpl -* xref:#IERC721Metadata-name[`++name(self)++`] -* xref:#IERC721Metadata-symbol[`++symbol(self)++`] -* xref:#IERC721Metadata-token_uri[`++token_uri(self, token_id)++`] +.ERC721MixinImpl +* xref:#ERC721Component-Embeddable-Impls-ERC721Impl[`++ERC721Impl++`] +* xref:#ERC721Component-Embeddable-Impls-ERC721MetadataImpl[`++ERC721MetadataImpl++`] +* xref:#ERC721Component-Embeddable-Impls-ERC721CamelOnlyImpl[`++ERC721CamelOnlyImpl++`] +* xref:#ERC721Component-Embeddable-Impls-ERC721MetadataCamelOnlyImpl[`++ERC721MetadataCamelOnlyImpl++`] +* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] -- -[.contract-index#ERC721Component-Embeddable-Impls-camelCase] -.Embeddable implementations (camelCase) +[.contract-index#ERC721Component-Embeddable-Impls] +.Embeddable Implementations -- -.ER721CamelOnlyImpl -* xref:#ERC721-balanceOf[`++balanceOf(self, account)++`] -* xref:#ERC721-ownerOf[`++ownerOf(self, tokenId)++`] -* xref:#ERC721-safeTransferFrom[`++safeTransferFrom(self, from, to, tokenId, data)++`] -* xref:#ERC721-transferFrom[`++transferFrom(self, from, to, tokenId)++`] -* xref:#ERC721-setApprovalForAll[`++setApprovalForAll(self, operator, approved)++`] -* xref:#ERC721-getApproved[`++getApproved(self, tokenId)++`] -* xref:#ERC721-isApprovedForAll[`++isApprovedForAll(self, owner, operator)++`] - +[.sub-index#ERC721Component-Embeddable-Impls-ERC721Impl] +.ERC721Impl +* xref:#ERC721Component-balance_of[`++balance_of(self, account)++`] +* xref:#ERC721Component-owner_of[`++owner_of(self, token_id)++`] +* xref:#ERC721Component-safe_transfer_from[`++safe_transfer_from(self, from, to, token_id, data)++`] +* xref:#ERC721Component-transfer_from[`++transfer_from(self, from, to, token_id)++`] +* xref:#ERC721Component-approve[`++approve(self, to, token_id)++`] +* xref:#ERC721Component-set_approval_for_all[`++set_approval_for_all(self, operator, approved)++`] +* xref:#ERC721Component-get_approved[`++get_approved(self, token_id)++`] +* xref:#ERC721Component-is_approved_for_all[`++is_approved_for_all(self, owner, operator)++`] + +[.sub-index#ERC721Component-Embeddable-Impls-ERC721MetadataImpl] +.ERC721MetadataImpl +* xref:#ERC721Component-name[`++name(self)++`] +* xref:#ERC721Component-symbol[`++symbol(self)++`] +* xref:#ERC721Component-token_uri[`++token_uri(self, token_id)++`] + +[.sub-index#ERC721Component-Embeddable-Impls-ERC721CamelOnlyImpl] +.ERC721CamelOnlyImpl +* xref:#ERC721Component-balanceOf[`++balanceOf(self, account)++`] +* xref:#ERC721Component-ownerOf[`++ownerOf(self, tokenId)++`] +* xref:#ERC721Component-safeTransferFrom[`++safeTransferFrom(self, from, to, tokenId, data)++`] +* xref:#ERC721Component-transferFrom[`++transferFrom(self, from, to, tokenId)++`] +* xref:#ERC721Component-setApprovalForAll[`++setApprovalForAll(self, operator, approved)++`] +* xref:#ERC721Component-getApproved[`++getApproved(self, tokenId)++`] +* xref:#ERC721Component-isApprovedForAll[`++isApprovedForAll(self, owner, operator)++`] + +[.sub-index#ERC721Component-Embeddable-Impls-ERC721MetadataCamelOnlyImpl] .ERC721MetadataCamelOnlyImpl -* xref:#ERC721-tokenURI[`++tokenURI(self, tokenId)++`] +* xref:#ERC721Component-tokenURI[`++tokenURI(self, tokenId)++`] + +.SRC5Impl +* xref:api/introspection.adoc#ISRC5-supports_interface[`supports_interface(self, interface_id: felt252)`] -- [.contract-index] -.Internal Functions +.Internal functions -- .InternalImpl -* xref:#ERC721-initializer[`++initializer(self, name_, symbol_)++`] -* xref:#ERC721-_owner_of[`++_owner_of(self, token_id)++`] -* xref:#ERC721-_exists[`++_exists(self, token_id)++`] -* xref:#ERC721-_is_approved_or_owner[`++_is_approved_or_owner(self, spender, token_id)++`] -* xref:#ERC721-_approve[`++_approve(self, to, token_id)++`] -* xref:#ERC721-_set_approval_for_all[`++_set_approval_for_all(self, owner, operator, approved)++`] -* xref:#ERC721-_mint[`++_mint(self, to, token_id)++`] -* xref:#ERC721-_transfer[`++_transfer(self, from, to, token_id)++`] -* xref:#ERC721-_burn[`++_burn(self, token_id)++`] -* xref:#ERC721-_safe_mint[`++_safe_mint(self, to, token_id, data)++`] -* xref:#ERC721-_safe_transfer[`++_safe_transfer(self, from, to, token_id, data)++`] -* xref:#ERC721-_set_token_uri[`++_set_token_uri(self, token_id, token_uri)++`] +* xref:#ERC721Component-initializer[`++initializer(self, name, symbol, base_uri)++`] +* xref:#ERC721Component-_owner_of[`++_owner_of(self, token_id)++`] +* xref:#ERC721Component-_exists[`++_exists(self, token_id)++`] +* xref:#ERC721Component-_is_approved_or_owner[`++_is_approved_or_owner(self, spender, token_id)++`] +* xref:#ERC721Component-_approve[`++_approve(self, to, token_id)++`] +* xref:#ERC721Component-_set_approval_for_all[`++_set_approval_for_all(self, owner, operator, approved)++`] +* xref:#ERC721Component-_mint[`++_mint(self, to, token_id)++`] +* xref:#ERC721Component-_transfer[`++_transfer(self, from, to, token_id)++`] +* xref:#ERC721Component-_burn[`++_burn(self, token_id)++`] +* xref:#ERC721Component-_safe_mint[`++_safe_mint(self, to, token_id, data)++`] +* xref:#ERC721Component-_safe_transfer[`++_safe_transfer(self, from, to, token_id, data)++`] +* xref:#ERC721Component-_set_base_uri[`++_set_base_uri(self, base_uri)++`] +* xref:#ERC721Component-_base_uri[`++_base_uri(self)++`] -- [.contract-index] .Events -- .IERC721 -* xref:#IERC721-Approval[`++Approval(owner, approved, token_id)++`] -* xref:#IERC721-ApprovalForAll[`++ApprovalForAll(owner, operator, approved)++`] -* xref:#IERC721-Transfer[`++Transfer(from, to, token_id)++`] +* xref:#ERC721Component-Approval[`++Approval(owner, approved, token_id)++`] +* xref:#ERC721Component-ApprovalForAll[`++ApprovalForAll(owner, operator, approved)++`] +* xref:#ERC721Component-Transfer[`++Transfer(from, to, token_id)++`] -- ==== Embeddable functions [.contract-item] -[[ERC721-balance_of]] +[[ERC721Component-balance_of]] ==== `[.contract-item-name]#++balance_of++#++(self: @ContractState, account: ContractAddress) → u256++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-owner_of]] +[[ERC721Component-owner_of]] ==== `[.contract-item-name]#++owner_of++#++(self: @ContractState, token_id: u256) → ContractAddress++` [.item-kind]#external# See <>. @@ -271,7 +289,7 @@ Requirements: - `token_id` exists. [.contract-item] -[[ERC721-safe_transfer_from]] +[[ERC721Component-safe_transfer_from]] ==== `[.contract-item-name]#++safe_transfer_from++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#external# See <>. @@ -285,7 +303,7 @@ Requirements: - `to` is either an account contract or supports the <> interface. [.contract-item] -[[ERC721-transfer_from]] +[[ERC721Component-transfer_from]] ==== `[.contract-item-name]#++transfer_from++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#external# See <>. @@ -298,7 +316,7 @@ Requirements: - `token_id` exists. [.contract-item] -[[ERC721-approve]] +[[ERC721Component-approve]] ==== `[.contract-item-name]#++approve++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#external# See <>. @@ -310,7 +328,7 @@ Requirements: - `token_id` exists. [.contract-item] -[[ERC721-set_approval_for_all]] +[[ERC721Component-set_approval_for_all]] ==== `[.contract-item-name]#++set_approval_for_all++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# See <>. @@ -320,7 +338,7 @@ Requirements: - `operator` cannot be the caller. [.contract-item] -[[ERC721-get_approved]] +[[ERC721Component-get_approved]] ==== `[.contract-item-name]#++get_approved++#++(self: @ContractState, token_id: u256) -> u256++` [.item-kind]#external# See <>. @@ -330,97 +348,99 @@ Requirements: - `token_id` exists. [.contract-item] -[[ERC721-is_approved_for_all]] +[[ERC721Component-is_approved_for_all]] ==== `[.contract-item-name]#++is_approved_for_all++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-name]] -==== `[.contract-item-name]#++name++#++(self: @ContractState) -> felt252++` [.item-kind]#external# +[[ERC721Component-name]] +==== `[.contract-item-name]#++name++#++(self: @ContractState) -> ByteArray++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-symbol]] -==== `[.contract-item-name]#++symbol++#++(self: @ContractState) -> felt252++` [.item-kind]#external# +[[ERC721Component-symbol]] +==== `[.contract-item-name]#++symbol++#++(self: @ContractState) -> ByteArray++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-token_uri]] -==== `[.contract-item-name]#++token_uri++#++(self: @ContractState, token_id: u256) -> felt252++` [.item-kind]#external# +[[ERC721Component-token_uri]] +==== `[.contract-item-name]#++token_uri++#++(self: @ContractState, token_id: u256) -> ByteArray++` [.item-kind]#external# -See <>. +Returns the Uniform Resource Identifier (URI) for the `token_id` token. +If a base URI is set, the resulting URI for each token will be the concatenation of the base URI and the token ID. +For example, the base URI pass:[https://token-cdn-domain/] would be returned as pass:[https://token-cdn-domain/123] for token ID `123`. -==== camelCase Support +If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. [.contract-item] -[[ERC721-balanceOf]] +[[ERC721Component-balanceOf]] ==== `[.contract-item-name]#++balanceOf++#++(self: @ContractState, account: ContractAddress) -> u256++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-ownerOf]] +[[ERC721Component-ownerOf]] ==== `[.contract-item-name]#++ownerOf++#++(self: @ContractState, tokenId: u256) -> ContractAddress++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-safeTransferFrom]] +[[ERC721Component-safeTransferFrom]] ==== `[.contract-item-name]#++safeTransferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256, data: Span)++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-transferFrom]] +[[ERC721Component-transferFrom]] ==== `[.contract-item-name]#++transferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256)++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-setApprovalForAll]] +[[ERC721Component-setApprovalForAll]] ==== `[.contract-item-name]#++setApprovalForAll++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-getApproved]] +[[ERC721Component-getApproved]] ==== `[.contract-item-name]#++getApproved++#++(self: @ContractState, tokenId: u256) -> ContractAddress++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-isApprovedForAll]] +[[ERC721Component-isApprovedForAll]] ==== `[.contract-item-name]#++isApprovedForAll++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# See <>. [.contract-item] -[[ERC721-tokenURI]] -==== `[.contract-item-name]#++tokenURI++#++(self: @ContractState, tokenId: u256) -> felt252++` [.item-kind]#external# +[[ERC721Component-tokenURI]] +==== `[.contract-item-name]#++tokenURI++#++(self: @ContractState, tokenId: u256) -> ByteArray++` [.item-kind]#external# See <>. ==== Internal functions [.contract-item] -[[ERC721-initializer]] -==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, name_: felt252, symbol_: felt252)++` [.item-kind]#internal# +[[ERC721Component-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, base_uri: ByteArray)++` [.item-kind]#internal# Initializes the contract by setting the token name and symbol. This should be used inside the contract's constructor. [.contract-item] -[[ERC721-_owner_of]] +[[ERC721Component-_owner_of]] ==== `[.contract-item-name]#++_owner_of++#++(self: @ContractState, token_id: felt252) -> ContractAddress++` [.item-kind]#internal# Internal function that returns the owner address of `token_id`. This function will panic if the token does not exist. [.contract-item] -[[ERC721-_exists]] +[[ERC721Component-_exists]] ==== `[.contract-item-name]#++_exists++#++(self: @ContractState, token_id: u256) -> bool++` [.item-kind]#internal# Internal function that returns whether `token_id` exists. @@ -428,7 +448,7 @@ Internal function that returns whether `token_id` exists. Tokens start existing when they are minted (<>), and stop existing when they are burned (<>). [.contract-item] -[[ERC721-_is_approved_or_owner]] +[[ERC721Component-_is_approved_or_owner]] ==== `[.contract-item-name]#++_is_approved_or_owner++#++(ref self: ContractState, spender: ContractAddress, token_id: u256) -> bool++` [.item-kind]#internal# Internal function that returns whether `spender` is allowed to manage `token_id`. @@ -438,7 +458,7 @@ Requirements: - `token_id` exists. [.contract-item] -[[ERC721-_approve]] +[[ERC721Component-_approve]] ==== `[.contract-item-name]#++_approve++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# Internal function that changes or reaffirms the approved address for an NFT. @@ -451,7 +471,7 @@ Requirements: - `to` is not the current token owner. [.contract-item] -[[ERC721-_set_approval_for_all]] +[[ERC721Component-_set_approval_for_all]] ==== `[.contract-item-name]#++_set_approval_for_all++#++(ref self: ContractState, owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#internal# Internal function that enables or disables approval for `operator` to manage all of the @@ -464,7 +484,7 @@ Requirements: - `operator` cannot be the caller. [.contract-item] -[[ERC721-_mint]] +[[ERC721Component-_mint]] ==== `[.contract-item-name]#++_mint++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# Internal function that mints `token_id` and transfers it to `to`. @@ -477,7 +497,7 @@ Requirements: - `token_id` does not already exist. [.contract-item] -[[ERC721-_transfer]] +[[ERC721Component-_transfer]] ==== `[.contract-item-name]#++_transfer++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# Internal function that transfers `token_id` from `from` to `to`. @@ -491,7 +511,7 @@ Requirements: - `token_id` exists. [.contract-item] -[[ERC721-_burn]] +[[ERC721Component-_burn]] ==== `[.contract-item-name]#++_burn++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#internal# Internal function that destroys `token_id`. @@ -505,7 +525,7 @@ Requirements: `token_id` exists. [.contract-item] -[[ERC721-_safe_mint]] +[[ERC721Component-_safe_mint]] ==== `[.contract-item-name]#++_safe_mint++#++(ref self: ContractState, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#internal# Internal function that mints `token_id` and transfers it to `to`. @@ -519,7 +539,7 @@ Requirements: - `to` is either an account contract or supports the <> interface. [.contract-item] -[[ERC721-_safe_transfer]] +[[ERC721Component-_safe_transfer]] ==== `[.contract-item-name]#++_safe_transfer++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#internal# Internal function that transfers `token_id` token from `from` to `to`, checking first that contract recipients are aware of the ERC721 protocol to prevent tokens from being forever locked. @@ -538,38 +558,40 @@ Requirements: - `to` either is an account contract or supports the <> interface. [.contract-item] -[[ERC721-_set_token_uri]] -==== `[.contract-item-name]#++_set_token_uri++#++(ref self: ContractState, token_id: u256, token_uri: felt252)++` [.item-kind]#internal# +[[ERC721Component-_set_base_uri]] +==== `[.contract-item-name]#++_set_base_uri++#++(ref self: ContractState, base_uri: ByteArray)++` [.item-kind]#internal# -Internal function that sets the `token_uri` of `token_id`. +Internal function that sets the `base_uri`. -Requirements: +[.contract-item] +[[ERC721Component-_base_uri]] +==== `[.contract-item-name]#++_base_uri++#++(self: @ContractState) -> ByteArray++` [.item-kind]#internal# -- `token_id` exists. +Base URI for computing <>. ==== Events [.contract-item] -[[ERC721-Approval]] +[[ERC721Component-Approval]] ==== `[.contract-item-name]#++Approval++#++(owner: ContractAddress, approved: ContractAddress, token_id: u256)++` [.item-kind]#event# -See <>. +See <>. [.contract-item] -[[ERC721-ApprovalForAll]] +[[ERC721Component-ApprovalForAll]] ==== `[.contract-item-name]#++ApprovalForAll++#++(owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#event# -See <>. +See <>. [.contract-item] -[[ERC721-Transfer]] +[[ERC721Component-Transfer]] ==== `[.contract-item-name]#++Transfer++#++(from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#event# -See <>. +See <>. [.contract] [[IERC721Receiver]] -=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] +=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.9.0/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] [.hljs-theme-dark] ```javascript @@ -594,7 +616,7 @@ Interface for contracts that support receiving `safe_transfer_from` transfers. [.contract-item] [[IERC721Receiver-on_erc721_received]] -==== `[.contract-item-name]#++on_erc721_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, data Span) -> felt252++` [.item-kind]#external# +==== `[.contract-item-name]#++on_erc721_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, data: Span) -> felt252++` [.item-kind]#external# Whenever an IERC721 `token_id` token is transferred to this non-account contract via <> by `operator` from `from`, this function is called. @@ -615,37 +637,30 @@ include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{erc721-class-hash} +{ERC721-class-hash} -- [.contract-index] .Constructor -- -* xref:#ERC721-constructor[`++constructor(self, name, symbol, recipient, token_ids, token_uris)++`] +* xref:#ERC721-constructor[`++constructor(self, name, symbol, recipient, token_ids, base_uri)++`] -- [.contract-index] .Embedded Implementations -- -.ERC721Component - -* xref:#ERC721Component-Embeddable-Impls[`++ERC721Impl++`] -* xref:#ERC721Component-Embeddable-Impls[`++ERC721MetadataImpl++`] -* xref:#ERC721Component-Embeddable-Impls-camelCase[`++ERC721CamelOnly++`] -* xref:#ERC721Component-Embeddable-Impls-camelCase[`++ERC721MetadataCamelOnly++`] +.ERC721MixinImpl -.SRC5Component - -* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +* xref:#ERC721Component-Embeddable-Mixin-Impl[`++ERC721MixinImpl++`] -- [#ERC721-constructor-section] ==== Constructor [.contract-item] -[[ERC721-constructor]] -==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: felt252, symbol: felt252, recipient: ContractAddress, token_ids: Span, token_uris: Span)++` [.item-kind]#constructor# +[[ERC721Component-constructor]] +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, recipient: ContractAddress, token_ids: Span, base_uri: ByteArray)++` [.item-kind]#constructor# Sets the `name` and `symbol`. -Mints `token_ids` tokens with each corresponding URI from `token_uris` to `recipient`. +Mints `token_ids` tokens to `recipient` and sets the `base_uri`. diff --git a/docs/modules/ROOT/pages/api/introspection.adoc b/docs/modules/ROOT/pages/api/introspection.adoc index 589630f4a..16dbdc193 100644 --- a/docs/modules/ROOT/pages/api/introspection.adoc +++ b/docs/modules/ROOT/pages/api/introspection.adoc @@ -55,6 +55,7 @@ SRC5 component extending xref:ISRC5[`ISRC5`]. [.contract-index#SRC5Component-Embeddable-Impls] .Embeddable Implementations -- +[.sub-index#SRC5Component-Embeddable-Impls-SRC5Impl] .SRC5Impl * xref:#SRC5Component-supports_interface[`++supports_interface(self, interface_id)++`] @@ -70,7 +71,7 @@ SRC5 component extending xref:ISRC5[`ISRC5`]. -- [#SRC5Component-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[SRC5Component-supports_interface]] @@ -79,7 +80,7 @@ SRC5 component extending xref:ISRC5[`ISRC5`]. See xref:ISRC5-supports_interface[`ISRC5::supports_interface`]. [#SRC5Component-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[SRC5Component-register_interface]] diff --git a/docs/modules/ROOT/pages/api/security.adoc b/docs/modules/ROOT/pages/api/security.adoc index 44a26cec0..c71cbd9a9 100644 --- a/docs/modules/ROOT/pages/api/security.adoc +++ b/docs/modules/ROOT/pages/api/security.adoc @@ -33,7 +33,7 @@ Component enabling one-time initialization for contracts. -- [#InitializableComponent-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[InitializableComponent-is_initialized]] @@ -42,7 +42,7 @@ Component enabling one-time initialization for contracts. Returns whether the contract has been initialized. [#InitializableComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[InitializableComponent-initialize]] @@ -96,7 +96,7 @@ Component to implement an emergency stop mechanism. -- [#PausableComponent-Embeddable-Functions] -==== Embeddable Functions +==== Embeddable functions [.contract-item] [[PausableComponent-is_paused]] @@ -105,7 +105,7 @@ Component to implement an emergency stop mechanism. Returns whether the contract is currently paused. [#PausableComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[PausableComponent-assert_not_paused]] @@ -181,7 +181,7 @@ Component to help prevent reentrant calls. -- [#ReentrancyGuardComponent-Internal-Functions] -==== Internal Functions +==== Internal functions [.contract-item] [[ReentrancyGuardComponent-start]] diff --git a/docs/modules/ROOT/pages/components.adoc b/docs/modules/ROOT/pages/components.adoc index 7f47f2be1..2ea18eb57 100644 --- a/docs/modules/ROOT/pages/components.adoc +++ b/docs/modules/ROOT/pages/components.adoc @@ -70,6 +70,7 @@ Flattening the component event removes it, leaving the event ID as the first key === Implementations :erc20-component: xref:/api/erc20.adoc#ERC20Component[ERC20Component] +:mixin: xref:/components.adoc#mixins[mixin] Components come with granular implementations of different interfaces. This allows contracts to integrate only the implementations that they'll use and avoid unnecessary bloat. @@ -137,15 +138,106 @@ By adding the embed attribute, `is_initialized` becomes a contract entrypoint fo [TIP] ==== Embeddable implementations, when available in this library's components, are segregated from the internal component implementation which makes it easier to safely expose. -Components also separate standard implementations (`snake_case`) from `camelCase`. -This trichotomy structures the API documentation design. +Components also separate granular implementations from {mixin} implementations. +The API documentation design reflects these groupings. See {erc20-component} as an example which includes: -- *Embeddable implementations* -- *Embeddable implementations (camelCase)* -- *Internal implementations* +- *Embeddable Mixin Implementation* +- *Embeddable Implementations* +- *Internal Implementations* +- *Events* ==== +=== Mixins + +Mixins are impls made of a combination of smaller, more specific impls. +While separating components into granular implementations offers flexibility, +integrating components with many implementations can appear crowded especially if the contract uses all of them. +Mixins simplify this by allowing contracts to embed groups of implementations with a single directive. + +Compare the following code blocks to see the benefit of using a mixin when creating an account contract. + +==== Account without mixin + +[,javascript] +---- +component!(path: AccountComponent, storage: account, event: AccountEvent); +component!(path: SRC5Component, storage: src5, event: SRC5Event); + +#[abi(embed_v0)] +impl SRC6Impl = AccountComponent::SRC6Impl; +#[abi(embed_v0)] +impl DeclarerImpl = AccountComponent::DeclarerImpl; +#[abi(embed_v0)] +impl DeployableImpl = AccountComponent::DeployableImpl; +#[abi(embed_v0)] +impl PublicKeyImpl = AccountComponent::PublicKeyImpl; +#[abi(embed_v0)] +impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; +#[abi(embed_v0)] +impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; +impl AccountInternalImpl = AccountComponent::InternalImpl; + +#[abi(embed_v0)] +impl SRC5Impl = SRC5Component::SRC5Impl; +---- + +==== Account with mixin + +[,javascript] +---- +component!(path: AccountComponent, storage: account, event: AccountEvent); +component!(path: SRC5Component, storage: src5, event: SRC5Event); + +#[abi(embed_v0)] +impl AccountMixinImpl = AccountComponent::AccountMixinImpl; +impl AccountInternalImpl = AccountComponent::InternalImpl; +---- + +The rest of the setup for the contract, however, does not change. +This means that component dependencies must still be included in the `Storage` struct and `Event` enum. +Here's a full example of an account contract that embeds the `AccountMixinImpl`: + +[,javascript] +---- +#[starknet::contract] +mod Account { + use openzeppelin::account::AccountComponent; + use openzeppelin::introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // This embeds all of the methods from the many AccountComponent implementations + // and also includes `supports_interface` from `SRC5Impl` + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +---- + === Initializers :ownable-component: xref:/api/access.adoc#OwnableComponent[OwnableComponent] diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 19e0b5566..3889c042f 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -1,548 +1,277 @@ -= ERC1155 - -The ERC1155 multi token standard is a specification for https://docs.openzeppelin.com/contracts/4.x/tokens#different-kinds-of-tokens[fungibility-agnostic] token contracts. -The ERC1155 library implements an approximation of https://eips.ethereum.org/EIPS/eip-1155[EIP-1155] in Cairo for StarkNet. - -== Table of Contents - -* <> -* <> -* <> - ** <> - ** <> - ** <> -* <> -* <> - ** <> -* <> - ** <> - ** <> - ** <> - ** <> - -== IERC1155 - -[,cairo] ----- -@contract_interface -namespace IERC1155 { - func balanceOf(account: felt, id: Uint256) -> (balance: Uint256) { - } - - func balanceOfBatch( - accounts_len: felt, - accounts: felt*, - ids_len: felt, - ids: Uint256* - ) -> ( - balances_len: felt, - balances: Uint256* - ) { - } - - func isApprovedForAll( - account: felt, - operator: felt - ) -> (approved: felt) { - } - - func setApprovalForAll(operator: felt, approved: felt) { - } +:eip-1155: https://eips.ethereum.org/EIPS/eip-1155[EIP-1155] +:fungibility-agnostic: https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens[fungibility-agnostic] - func safeTransferFrom( - from_: felt, - to: felt, - id: Uint256, - value: Uint256, - data_len: felt, - data: felt* - ) { - } - - func safeBatchTransferFrom( - from_: felt, - to: felt, - ids_len: felt, - ids: Uint256*, - values_len: felt, - values: Uint256*, - data_len: felt, - data: felt*, - ) { - } - - --------------- IERC165 --------------- += ERC1155 - func supportsInterface(interfaceId: felt) -> (success: felt) { - } +The ERC1155 multi token standard is a specification for {fungibility-agnostic} token contracts. +The ERC1155 library implements an approximation of {eip-1155} in Cairo for StarkNet. + +== Multi Token Standard + +:balance_of-api: xref:api/erc1155.adoc#IERC1155-balance_of[balance_of] +:erc721-balance_of-api: xref:api/erc721.adoc#IERC721-balance_of[balance_of] + +The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This +is why its {balance_of-api} function differs from ERC20’s and ERC777’s: it has an additional ID argument for the +identifier of the token that you want to query the balance of. + +This is similar to how ERC721 does things, but in that standard a token ID has no concept of balance: each token is +non-fungible and exists or doesn’t. The ERC721 {erc721-balance_of-api} function refers to how many different tokens an account +has, not how many of each. On the other hand, in ERC1155 accounts have a distinct balance for each token ID, and +non-fungible tokens are implemented by simply minting a single one of them. + +This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new +contract for each token type, a single ERC1155 token contract can hold the entire system state, reducing deployment +costs and complexity. + +== Interface + +:compatibility: xref:/erc1155.adoc#erc1155_compatibility[ERC1155 Compatibility] +:isrc5-interface: xref:/api/introspection.adoc#ISRC5[ISRC5] +:ierc1155-interface: xref:/api/erc1155.adoc#IERC1155[IERC1155] +:ierc1155metadata-interface: xref:/api/erc1155.adoc#IERC1155MetadataURI[IERC1155MetadataURI] +:erc1155-component: xref:/api/erc1155.adoc#ERC1155Component[ERC1155Component] +:dual-interfaces: xref:interfaces.adoc#dual_interfaces[Dual interfaces] + +The following interface represents the full ABI of the Contracts for Cairo {erc1155-component}. +The interface includes the {ierc1155-interface} standard interface and the optional {ierc1155metadata-interface} interface together with {isrc5-interface}. + +To support older token deployments, as mentioned in {dual-interfaces}, the component also includes implementations of the interface written in camelCase. + +[,javascript] +---- +trait ERC1155ABI { + // IERC1155 + fn balance_of(account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(operator: ContractAddress, approved: bool); + + // IERC1155MetadataURI + fn uri(token_id: u256) -> ByteArray; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IERC1155Camel + fn balanceOf(account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch( + accounts: Span, tokenIds: Span + ) -> Span; + fn safeTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ); + fn safeBatchTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(operator: ContractAddress, approved: bool); } ---- === ERC1155 Compatibility -Although StarkNet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard in the following ways: - -* It uses Cairo's `uint256` instead of `felt`. -* It returns `TRUE` as success. - -But some differences can still be found, such as: +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard but some differences can still be found, such as: -* It implements an `uri()` function that returns the same URI for *all* token types. It relies on the token type ID substitution mechanism https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. Clients calling this function must replace the `\{id\}` substring with the actual token type ID. -* `safeTransferFrom` and `safeBatchTransferFrom` are specified such that the optional `data` argument should be of type bytes. -In Solidity, this means a dynamically-sized array. -To be as close as possible to the standard, the `data` argument accepts a dynamic array of felts. -In Cairo, arrays are expressed with the array length preceding the actual array; -hence, the method accepts `data_len` and `data` respectively as types `felt` and `felt*`. -* `IERC1155Receiver` compliant contracts (`ERC1155Holder`) return a hardcoded selector id according to EVM selectors, since selectors are calculated differently in Cairo. -This is in line with the ERC165 interfaces design choice towards EVM compatibility. -See the xref:introspection.adoc[Introspection docs] for more info. -* `IERC1155Receiver` compliant contracts (`ERC1155Holder`) must support ERC165 by registering the `IERC1155Receiver` selector id in its constructor and exposing the `supportsInterface` method. -In doing so, recipient contracts (both accounts and non-accounts) can be verified that they support ERC1155 transfers. +* The optional `data` argument in both `safe_transfer_from` and `safe_batch_transfer_from` is implemented as `Span`. +* `IERC1155Receiver` compliant contracts must implement SRC5 and register the `IERC1155Receiver` interface ID. +* `IERC1155Receiver::on_erc1155_received` must return that interface ID on success. == Usage -To show a standard use case, we'll use the `ERC1155MintableBurnable` preset which allows for only the owner to `mint` tokens. -To create a token, you need to first deploy both Account and ERC1155 contracts respectively. +Using Contracts for Cairo, constructing an ERC1155 contract requires integrating both `ERC1155Component` and `SRC5Component`. +The contract should also set up the constructor to initialize the token's URI and interface support. +Here's an example of a basic contract: + +[,javascript] +---- +#[starknet::contract] +mod MyERC1155 { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155Component; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 + #[abi(embed_v0)] + impl ERC1155Impl = ERC1155Component::ERC1155Impl; + #[abi(embed_v0)] + impl ERC1155MetadataURIImpl = + ERC1155Component::ERC1155MetadataURIImpl; + #[abi(embed_v0)] + impl ERC1155Camel = ERC1155Component::ERC1155CamelImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } -Considering that the ERC1155 constructor method looks like this: + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } -[,cairo] ----- -func constructor( - uri: felt, // Token URI as Cairo short string - owner: felt // Address designated as the contract owner -) { + #[constructor] + fn constructor( + ref self: ContractState, + token_uri: ByteArray, + recipient: ContractAddress, + token_ids: Span, + values: Span + ) { + self.erc1155.initializer(token_uri); + self + .erc1155 + .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); + } } ---- -Deployment of both contracts looks like this: +=== Batch operations -[,python] ----- -account = await starknet.deploy( - "contracts/Account.cairo", - constructor_calldata=[signer.public_key] -) - -erc1155 = await starknet.deploy( - "contracts/token/erc1155/presets/ERC1155MintableBurnable.cairo", - constructor_calldata=[ - str_to_felt("http://my.uri/{id}"), # uri - account.contract_address # owner - ] -) ----- - -To mint tokens, send a transaction like this: - -[,python] ----- -signer = MockSigner(PRIVATE_KEY) -token_id = uint(1) -mint_value = uint(1000) - -await signer.send_transaction( - account, erc1155.contract_address, 'mint', [ - recipient_address, - *token_id, - *mint_value, - 0 # data - ] -) ----- +:safe_transfer_from: xref:/api/erc1155.adoc#IERC1155-safe_transfer_from[safe_transfer_from] +:balance_of_batch: xref:/api/erc1155.adoc#IERC1155-balance_of_batch[balance_of_batch] +:safe_batch_transfer_from: xref:/api/erc1155.adoc#IERC1155-safe_batch_transfer_from[safe_batch_transfer_from] +:batch_mint_with_acceptance_check: xref:/api/erc1155.adoc#ERC1155Component-batch_mint_with_acceptance_check[batch_mint_with_acceptance_check] -=== Token Transfers +Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, {balance_of_batch} and {safe_batch_transfer_from}, that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. We also have {safe_transfer_from} for non-batch operations. -This library includes `safeTransferFrom` and `safeBatchTransferFrom` to transfer assets. These methods incorporate the following conditional logic: +In the spirit of the standard, we’ve also included batch operations in the non-standard functions, such as +{batch_mint_with_acceptance_check}. -. If the receiving address xref:accounts.adoc#account_introspection_with_erc165[is identified as an account], the tokens will be transferred. -. If the receiving address is not an account contract, the function will check that the receiver supports ERC1155 tokens before sending them. +WARNING: While {safe_transfer_from} and {safe_batch_transfer_from} prevent loss by checking the receiver can handle the +tokens, this yields execution to the receiver which can result in a xref:security.adoc#reentrancy_guard[reentrant call]. -The current implementation requires recipient contracts to support ERC165 and expose the `supportsInterface` method. -See <>. +=== Receiving tokens -=== Interpreting ERC1155 URIs +:src5: xref:introspection.adoc#src5[SRC5] +:on_erc1155_received: xref:/api/erc1155.adoc#IERC1155Receiver-on_erc1155_received[on_erc1155_received] +:on_erc1155_batch_received: xref:/api/erc1155.adoc#IERC1155Receiver-on_erc1155_batch_received[on_erc1155_batch_received] +:computing-interface-id: xref:introspection.adoc#computing_the_interface_id[Computing the interface ID] -Token URIs in Cairo are stored as single field elements. -Each field element equates to 252-bits (or 31.5 bytes) which means that a token's URI can be no longer than 31 characters. +In order to be sure a non-account contract can safely accept ERC1155 tokens, said contract must implement the `IERC1155Receiver` interface. +The recipient contract must also implement the {src5} interface which supports interface introspection. -NOTE: Storing the URI as an array of felts was considered to accommodate larger strings. -While this approach is more flexible regarding URIs, a returned array further deviates from the standard. Therefore, this library's ERC1155 implementation sets URIs as a single field element. +==== IERC1155Receiver -The `utils.py` module includes utility methods for converting to/from Cairo field elements. -To properly interpret a URI from ERC1155, simply trim the null bytes and decode the remaining bits as an ASCII string. -For example: +:receiver-id: xref:/api/erc1155.adoc#IERC1155Receiver[IERC1155Receiver interface ID] -[,python] +[,javascript] ---- -# HELPER METHODS -def str_to_felt(text): - b_text = bytes(text, 'ascii') - return int.from_bytes(b_text, "big") - -def felt_to_str(felt): - b_felt = felt.to_bytes(31, "big") - return b_felt.decode() - -token_id = uint(1) -sample_uri = str_to_felt('mock://mytoken') - -await signer.send_transaction( - account, erc1155.contract_address, 'setTokenURI', [ - *token_id, sample_uri] -) - -felt_uri = await erc1155.tokenURI(first_token_id).call() -string_uri = felt_to_str(felt_uri) ----- - -=== ERC1155Received - -In order to be sure a contract can safely accept ERC1155 tokens, said contract must implement the <> interface (as expressed in the EIP1155 specification). -Methods such as `safeTransferFrom` and `safeBatchTransferFrom` call the recipient contract's `onERC1155Received` or `onERC1155BatchReceived` method. -If the contract fails to return the correct magic value, the transaction fails. - -StarkNet contracts that support safe transfers, however, must also support xref:introspection.adoc#erc165[ERC165] and include `supportsInterface` as proposed in https://github.com/OpenZeppelin/cairo-contracts/discussions/100[#100]. -Transfer functions require a means of differentiating between account and non-account contracts. -Currently, StarkNet does not support error handling from the contract level; -therefore, the current ERC1155 implementation requires that all contracts that support safe ERC1155 transfers (both accounts and non-accounts) include the `supportsInterface` method. -Further, `supportsInterface` should return `TRUE` if the recipient contract supports the `IERC1155Receiver` magic value `00x4e2312e0` (which invokes `onERC1155Received` and `onERC1155BatchReceived`). -If the recipient contract supports the `IAccount` magic value, `supportsInterface` should return `TRUE`. -Otherwise, transfer functions should fail. - -== Presets - -=== ERC1155MintableBurnable - -The https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/token/erc1155/presets/ERC1155MintableBurnable.cairo[`ERC1155MintableBurnable`] preset offers a quick and easy setup for creating ERC1155 tokens. Its constructor takes three arguments: `name`, `symbol`, and an `owner` address which will be capable of minting tokens. - -== Utilities - -=== ERC1155Holder - -Implementation of the `IERC1155Receiver` interface. - -Accepts all token transfers. -Make sure the contract is able to use its token with `IERC1155.safeTransferFrom`, `IERC1155.approve` or `IERC1155.setApprovalForAll`. - -Also utilizes the ERC165 method `supportsInterface` to determine if the contract is an account. -See <>. - -== API Specification - -=== IERC1155 API - -[,cairo] ----- -func balanceOf(account: felt, id: Uint256) -> (balance: Uint256) { +trait IERC1155Receiver { + fn on_erc1155_received( + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252; + fn on_erc1155_batch_received( + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252; } - -func balanceOfBatch( - accounts_len: felt, - accounts: felt*, - ids_len: felt, - ids: Uint256* -) -> ( - balances_len: felt, - balances: Uint256* -) { -} - -func isApprovedForAll(account: felt, operator: felt) -> (approved: felt) { -} - -func setApprovalForAll(operator: felt, approved: felt) { -} - -func safeTransferFrom( - from_: felt, - to: felt, - id: Uint256, - value: Uint256, - data_len: felt, - data: felt* -) { -} - -func safeBatchTransferFrom( - from_: felt, - to: felt, - ids_len: felt, - ids: Uint256*, - values_len: felt, - values: Uint256*, - data_len: felt, - data: felt*, -) { -} ----- - -==== `balanceOf` - -Returns the number of `id` tokens of ``account``. - -Parameters: - -[,cairo] ----- -account: felt -id: Uint256 ----- - -Returns: - -[,cairo] ----- -balance: Uint256 ----- - -==== `balanceOfBatch` - -Get the balance of multiple account/token pairs. - -Parameters: - -[,cairo] ----- -accounts_len: felt -accounts: felt* -ids_len: felt -ids: Uint156* ----- - -Returns: - -[,cairo] ----- -balances_len: felt -balances: Uint256* ----- - -==== `isApprovedForAll` - -Get whether `operator` is approved by `account` for all tokens. - -Parameters: - -[,cairo] ----- -account: felt -operator: felt ----- - -Returns: - -[,cairo] ----- -approved: felt ----- - -==== `setApprovalForAll` - -Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. - -Parameters: - -[,cairo] ----- -operator: felt -approved: felt ----- - -Returns: None. - -==== `safeTransferFrom` - -Transfers `value` amount of token `id` from `from_` to `to`, checking first that contract recipients are aware and capable of handling ERC1155 tokens to prevent locking them forever. -For information regarding how contracts communicate their awareness of the ERC1155 protocol, see <>. - -Emits a <> event. - -Parameters: - -[,cairo] ----- -from_: felt -to: felt -id: Uint256 -value: Uint256 -data_len: felt -data: felt* ---- -Returns: None. - -==== `safeBatchTransferFrom` +Implementing the `IERC1155Receiver` interface exposes the {on_erc1155_received} and {on_erc1155_batch_received} methods. +When {safe_transfer_from} and {safe_batch_transfer_from} are called, they invoke the recipient contract's `on_erc1155_received` or `on_erc1155_batch_received` methods respectively which *must* return the {receiver-id}. +Otherwise, the transaction will fail. -Batch version of <>. Emits a <> event. +TIP: For information on how to calculate interface IDs, see {computing-interface-id}. -Parameters: - -[,cairo] ----- -from_: felt -to: felt -ids_len: felt -ids: Uint256* -values_len: felt -values: Uint256* -data_len: felt -data: felt* ----- +==== Creating a token receiver contract -Returns: None. +:ERC1155ReceiverComponent: xref:/api/erc1155.adoc#ERC1155ReceiverComponent[ERC1155ReceiverComponent] -''' +The Contracts for Cairo {ERC1155ReceiverComponent} already returns the correct interface ID for safe token transfers. +To integrate the `IERC1155Receiver` interface into a contract, simply include the ABI embed directive to the implementations and add the `initializer` in the contract's constructor. +Here's an example of a simple token receiver contract: -=== Events - -==== `TransferSingle (Event)` - -Emitted when `operator` sends `value` amount of `id` token from `from_` to `to`. - -Parameters: - -[,cairo] ----- -operator: felt -from_: felt -to: felt -id: Uint256 -value: Uint256 +[,javascript] ---- +#[starknet::contract] +mod MyTokenReceiver { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155ReceiverComponent; + use starknet::ContractAddress; -==== `TransferBatch (Event)` + component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); -Emitted when `operator` sends multiple `values` of multiple token `ids` from `from_` to `to`. - -Parameters: - -[,cairo] ----- -operator: felt -from_: felt -to: felt -ids_len: felt -ids: Uint256* -values_len: felt -values: Uint256* ----- - -==== `ApprovalForAll (Event)` - -Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. - -Parameters: - -[,cairo] ----- -account: felt -operator: felt -approved: felt ----- + // ERC1155Receiver + #[abi(embed_v0)] + impl ERC1155ReceiverImpl = ERC1155ReceiverComponent::ERC1155ReceiverImpl; + #[abi(embed_v0)] + impl ERC1155ReceiverCamelImpl = ERC1155ReceiverComponent::ERC1155ReceiverCamelImpl; + impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; -''' + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; -=== IERC1155Metadata API - -[,cairo] ----- -func uri(id: Uint256) -> (uri: felt) { -} ----- - -==== `uri` - -Returns the Uniform Resource Identifier (URI) for a token `id`, although this implementation returns the same URI for *all* token types. It relies -on the token type ID substitution mechanism -https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. - -Parameters: - -[,cairo] ----- -id: Uint256 ----- - -Returns: - -[,cairo] ----- -uri: felt ----- - -''' - -=== IERC1155Receiver API + #[storage] + struct Storage { + #[substorage(v0)] + erc1155_receiver: ERC1155ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } -[,cairo] ----- -func onERC1155Received( - operator: felt, - from_: felt, - tokenId: Uint256, - data_len: felt, - data: felt* -) -> (selector: felt) { -} + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } -func onERC1155BatchReceived( - operator: felt, - from_: felt, - ids_len: felt, - ids: Uint256*, - values_len: felt, - values: Uint256*, - data_len: felt, - data: felt*, -) -> (selector: felt) { + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721_receiver.initializer(); + } } ---- - -==== `onERC1155Received` - -Whenever any IERC1155 token is transferred or minted to this non-account contract by `operator` from `from_`, this function is called. - -Parameters: - -[,cairo] ----- -operator: felt -from_: felt -id: Uint256 -value: Uint156 -data_len: felt -data: felt* ----- - -Returns: - -[,cairo] ----- -selector: felt ----- - -==== `onERC1155BatchReceived` - -Whenever any IERC1155 token is transferred to this non-account contract via `safeBatchTransferFrom` by `operator` from `from_`, this function is called. - -Parameters: - -[,cairo] ----- -operator: felt -from_: felt -ids_len: felt -ids: Uint256* -values_len: felt -values: Uint156* -data_len: felt -data: felt* ----- - -Returns: - -[,cairo] ----- -selector: felt ----- diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index cf876740a..7d260e47b 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -15,11 +15,15 @@ See the {custom-decimals} guide. == Interface :dual-interfaces: xref:/interfaces.adoc#dual_interfaces[Dual interfaces] +:ierc20-interface: xref:/api/erc20.adoc#IERC20[IERC20] +:ierc20metadata-interface: xref:/api/erc20.adoc#IERC20Metadata[IERC20Metadata] +:erc20-component: xref:/api/erc20.adoc#ERC20Component[ERC20Component] :erc20-supply: xref:/guides/erc20-supply.adoc[Creating ERC20 Supply] -The following interface represents the full ABI of the Contracts for Cairo `ERC20` component. -The interface includes the `IERC20` standard interface as well as `IERC20Camel` for backwards compatibility. -To support older token deployments, as mentioned in {dual-interfaces}, the `ERC20` component also includes an implementation of the interface written in camelCase. +The following interface represents the full ABI of the Contracts for Cairo {erc20-component}. +The interface includes the {ierc20-interface} standard interface as well as the optional {ierc20metadata-interface}. + +To support older token deployments, as mentioned in {dual-interfaces}, the component also includes an implementation of the interface written in camelCase. [,javascript] ---- @@ -35,8 +39,8 @@ trait ERC20ABI { fn approve(spender: ContractAddress, amount: u256) -> bool; // IERC20Metadata - fn name() -> felt252; - fn symbol() -> felt252; + fn name() -> ByteArray; + fn symbol() -> ByteArray; fn decimals() -> u8; // IERC20Camel @@ -57,7 +61,7 @@ trait ERC20ABI { Although Starknet is not EVM compatible, this component aims to be as close as possible to the ERC20 token standard. Some notable differences, however, can still be found, such as: -* Cairo short strings are used to simulate `name` and `symbol` because Cairo does not yet include native string support. +* The `ByteArray` type is used to represent strings in Cairo. * The component offers a {dual-interface} which supports both snake_case and camelCase methods, as opposed to just camelCase in Solidity. * `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error. * Function selectors are calculated differently between {cairo-selectors} and {solidity-selectors}. @@ -105,8 +109,8 @@ mod MyToken { initial_supply: u256, recipient: ContractAddress ) { - let name = 'MyToken'; - let symbol = 'MTK'; + let name = "MyToken"; + let symbol = "MTK"; self.erc20.initializer(name, symbol); self.erc20._mint(recipient, initial_supply); @@ -203,8 +207,8 @@ mod MyToken { self._set_decimals(decimals); // Initialize ERC20 - let name = 'MyToken'; - let symbol = 'MTK'; + let name = "MyToken"; + let symbol = "MTK"; self.erc20.initializer(name, symbol); self.erc20._mint(recipient, initial_supply); @@ -212,11 +216,11 @@ mod MyToken { #[external(v0)] impl ERC20MetadataImpl of interface::IERC20Metadata { - fn name(self: @ContractState) -> felt252 { + fn name(self: @ContractState) -> ByteArray { self.erc20.name() } - fn symbol(self: @ContractState) -> felt252 { + fn symbol(self: @ContractState) -> ByteArray { self.erc20.symbol() } diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 7d88b558b..3b63ce67f 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -9,17 +9,19 @@ The ERC721 token standard is a specification for {token-types}, or more colloqui == Interface :compatibility: xref:/erc721.adoc#erc721_compatibility[ERC721 Compatibility] -:ierc721-interface: xref:/erc721.adoc#ierc721[IERC721] -:ierc721metadata-interface: xref:/erc721.adoc#ierc721metadata[IERC721Metadata] +:ierc721-interface: xref:/api/erc721.adoc#IERC721[IERC721] +:ierc721metadata-interface: xref:/api/erc721.adoc#IERC721Metadata[IERC721Metadata] +:erc721-component: xref:/api/erc721.adoc#ERC721Component[ERC721Component] :dual-interfaces: xref:interfaces.adoc#dual_interfaces[Dual interfaces] -The following interface represents the full ABI of the Contracts for Cairo `ERC721Component`. -The interface includes the <> standard interface and the optional <> interface. +The following interface represents the full ABI of the Contracts for Cairo {erc721-component}. +The interface includes the {ierc721-interface} standard interface and the optional {ierc721metadata-interface} interface. + To support older token deployments, as mentioned in {dual-interfaces}, the component also includes implementations of the interface written in camelCase. [,javascript] ---- -trait IERC721ABI { +trait ERC721ABI { // IERC721 fn balance_of(account: ContractAddress) -> u256; fn owner_of(token_id: u256) -> ContractAddress; @@ -36,9 +38,9 @@ trait IERC721ABI { fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; // IERC721Metadata - fn name() -> felt252; - fn symbol() -> felt252; - fn token_uri(token_id: u256) -> felt252; + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn token_uri(token_id: u256) -> ByteArray; // IERC721CamelOnly fn balanceOf(account: ContractAddress) -> u256; @@ -55,7 +57,7 @@ trait IERC721ABI { fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; // IERC721MetadataCamelOnly - fn tokenURI(tokenId: u256) -> felt252; + fn tokenURI(tokenId: u256) -> ByteArray; } ---- @@ -66,24 +68,16 @@ trait IERC721ABI { :introspection: xref:introspection.adoc[Introspection] :eip165: https://eips.ethereum.org/EIPS/eip-165[EIP165] -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard by utilizing Cairo's short strings to simulate `name` and `symbol`. +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard. This implementation does, however, include a few notable differences such as: -* `token_uri` returns a felt252 representation of the queried token's URI. -The EIP721 standard, however, states that the return value should be of type string. -If a token's URI is not set, the returned value is `0`. -Note that URIs cannot exceed 31 characters at this time. -See <>. * ``interface_id``s are hardcoded and initialized by the constructor. The hardcoded values derive from Starknet's selector calculations. See the {introspection} docs. * `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. The difference between both functions consists of accepting `data` as an argument. -`safe_transfer_from` by default accepts the `data` argument. +`safe_transfer_from` by default accepts the `data` argument which is interpreted as `Span`. If `data` is not used, simply pass an empty array. -* `safe_transfer_from` is implemented such that the optional `data` argument mimics `bytes`. -In Solidity, this means a dynamically-sized array. -To be as close as possible to the standard, it accepts a dynamic array of felts. * ERC721 utilizes {src5-api} to declare and query interface support on Starknet as opposed to Ethereum's {eip165}. The design for `SRC5` is similar to OpenZeppelin's {erc165-storage}. * `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). @@ -145,28 +139,13 @@ mod MyNFT { ref self: ContractState, recipient: ContractAddress ) { - let name = 'MyNFT'; - let symbol = 'NFT'; + let name = "MyNFT"; + let symbol = "NFT"; + let base_uri = "https://api.example.com/v1/"; let token_id = 1; - let token_uri = 'NFT_URI'; - - self.erc721.initializer(name, symbol); - self._mint_with_uri(recipient, token_id, token_uri); - } - #[generate_trait] - impl InternalImpl of InternalTrait { - fn _mint_with_uri( - ref self: ContractState, - recipient: ContractAddress, - token_id: u256, - token_uri: felt252 - ) { - // Initialize the ERC721 storage - self.erc721._mint(recipient, token_id); - // Mint the NFT to recipient and set the token's URI - self.erc721._set_token_uri(token_id, token_uri); - } + self.erc721.initializer(name, symbol, base_uri); + self._mint(recipient, token_id); } } ---- @@ -218,7 +197,7 @@ TIP: For information on how to calculate interface IDs, see {computing-interface ==== Creating a token receiver contract The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. -To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract's constructor . +To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract's constructor. Here's an example of a simple token receiver contract: [,javascript] @@ -269,10 +248,9 @@ mod MyTokenReceiver { === Storing ERC721 URIs -:string-roadmap: https://github.com/orgs/starkware-libs/projects/1/views/1?pane=issue&itemId=28823165[here] - -Token URIs in Cairo are stored as single field elements (`felt252`). -Each field element equates to 252-bits (or 31.5 bytes) which means that a token's URI can be no longer than 31 characters. +:solidity-impl: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/932fddf69a699a9a80fd2396fd1a2ab91cdda123/contracts/token/ERC721/ERC721.sol#L85-L93[Solidity implementation] +:token-uri: xref:/api/erc721.adoc#IERC721Metadata-token_uri[token_uri] -NOTE: Native string support in Cairo is currently in progress and tracked {string-roadmap}. -Once Cairo offers full string support, this will be revisited. +Token URIs were previously stored as single field elements prior to Cairo v0.2.5. +ERC721Component now stores only the base URI as a `ByteArray` and the full token URI is returned as the `ByteArray` concatenation of the base URI and the token ID through the {token-uri} method. +This design mirrors OpenZeppelin's default {solidity-impl} for ERC721. diff --git a/docs/modules/ROOT/pages/guides/erc20-supply.adoc b/docs/modules/ROOT/pages/guides/erc20-supply.adoc index 710977aeb..77e4e3f7a 100644 --- a/docs/modules/ROOT/pages/guides/erc20-supply.adoc +++ b/docs/modules/ROOT/pages/guides/erc20-supply.adoc @@ -47,8 +47,8 @@ mod MyToken { fixed_supply: u256, recipient: ContractAddress ) { - let name = 'MyToken'; - let symbol = 'MTK'; + let name = "MyToken"; + let symbol = "MTK"; self.erc20.initializer(name, symbol); self.erc20._mint(recipient, fixed_supply); @@ -100,8 +100,8 @@ mod MyToken { #[constructor] fn constructor(ref self: ContractState) { - let name = 'MyToken'; - let symbol = 'MTK'; + let name = "MyToken"; + let symbol = "MTK"; self.erc20.initializer(name, symbol); } diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 568edc5c4..70dc958c1 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -100,8 +100,8 @@ mod MyERC20Token { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress ) { diff --git a/docs/modules/ROOT/pages/interfaces.adoc b/docs/modules/ROOT/pages/interfaces.adoc index 9249db4cd..0ba356e25 100644 --- a/docs/modules/ROOT/pages/interfaces.adoc +++ b/docs/modules/ROOT/pages/interfaces.adoc @@ -29,8 +29,8 @@ This includes only the functions defined in the interface, and is the standard w ```javascript #[starknet::interface] trait IERC20 { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; fn decimals(self: @TState) -> u8; fn total_supply(self: @TState) -> u256; fn balance_of(self: @TState, account: ContractAddress) -> u256; @@ -50,8 +50,8 @@ They describe a contract's complete interface. This is useful to interface with ```javascript #[starknet::interface] trait ERC20ABI { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; fn decimals(self: @TState) -> u8; fn total_supply(self: @TState) -> u256; fn balance_of(self: @TState, account: ContractAddress) -> u256; @@ -74,8 +74,8 @@ struct DualCaseERC20 { } trait DualCaseERC20Trait { - fn name(self: @DualCaseERC20) -> felt252; - fn symbol(self: @DualCaseERC20) -> felt252; + fn name(self: @DualCaseERC20) -> ByteArray; + fn symbol(self: @DualCaseERC20) -> ByteArray; fn decimals(self: @DualCaseERC20) -> u8; fn total_supply(self: @DualCaseERC20) -> u256; fn balance_of(self: @DualCaseERC20, account: ContractAddress) -> u256; @@ -108,8 +108,8 @@ The default version of the ERC20 interface trait exposes `snake_case` functions: ```javascript #[starknet::interface] trait IERC20 { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; fn decimals(self: @TState) -> u8; fn total_supply(self: @TState) -> u256; fn balance_of(self: @TState, account: ContractAddress) -> u256; @@ -129,8 +129,8 @@ On top of that, we also offer a `camelCase` version of the same interface: ```javascript #[starknet::interface] trait IERC20Camel { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; fn decimals(self: @TState) -> u8; fn totalSupply(self: @TState) -> u256; fn balanceOf(self: @TState, account: ContractAddress) -> u256; diff --git a/docs/modules/ROOT/pages/presets.adoc b/docs/modules/ROOT/pages/presets.adoc index eef6386a9..977d29b2f 100644 --- a/docs/modules/ROOT/pages/presets.adoc +++ b/docs/modules/ROOT/pages/presets.adoc @@ -1,6 +1,7 @@ :account: xref:/api/account.adoc#Account[Account] :erc20: xref:/api/erc20.adoc#ERC20[ERC20] :erc721: xref:/api/erc721.adoc#ERC721[ERC721] +:erc1155: xref:/api/erc1155.adoc#ERC1155[ERC1155] :eth-account-upgradeable: xref:/api/account.adoc#EthAccountUpgradeable[EthAccountUpgradeable] :udc: https://github.com/starknet-io/starknet-docs/blob/v0.1.479/components/Starknet/modules/architecture_and_concepts/pages/Smart_Contracts/universal-deployer.adoc[UniversalDeployer] :sierra-class-hashes: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/class-hash[Sierra class hashes] @@ -28,16 +29,19 @@ NOTE: Class hashes were computed using {class-hash-cairo-version}. | Name | Sierra Class Hash | `{account}` -| `{account-class-hash}` - -| `{eth-account-upgradeable}` -| `{eth-account-upgradeable-class-hash}` +| `{Account-class-hash}` | `{erc20}` -| `{erc20-class-hash}` +| `{ERC20-class-hash}` | `{erc721}` -| `{erc721-class-hash}` +| `{ERC721-class-hash}` + +| `{erc1155}` +| `{ERC1155-class-hash}` + +| `{eth-account-upgradeable}` +| `{EthAccountUpgradeable-class-hash}` | `{udc}` | `{UniversalDeployer-class-hash}` diff --git a/docs/modules/ROOT/pages/security.adoc b/docs/modules/ROOT/pages/security.adoc index e739ef892..289f1d197 100644 --- a/docs/modules/ROOT/pages/security.adoc +++ b/docs/modules/ROOT/pages/security.adoc @@ -105,28 +105,28 @@ mod MyPausableContract { self.ownable.initializer(owner); } - #[generate_trait] #[external(v0)] - impl ExternalImpl of ExternalTrait { - fn pause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable._pause(); - } - - fn unpause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable._unpause(); - } - - fn when_not_paused(ref self: ContractState) { - self.pausable.assert_not_paused(); - // Do something - } - - fn when_paused(ref self: ContractState) { - self.pausable.assert_paused(); - // Do something - } + fn pause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable._pause(); + } + + #[external(v0)] + fn unpause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable._unpause(); + } + + #[external(v0)] + fn when_not_paused(ref self: ContractState) { + self.pausable.assert_not_paused(); + // Do something + } + + #[external(v0)] + fn when_paused(ref self: ContractState) { + self.pausable.assert_paused(); + // Do something } } ---- @@ -167,24 +167,22 @@ mod MyReentrancyContract { ReentrancyGuardEvent: ReentrancyGuardComponent::Event } - #[generate_trait] #[external(v0)] - impl ExternalImpl of ExternalTrait { - fn protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); + fn protected_function(ref self: ContractState) { + self.reentrancy_guard.start(); - // Do something + // Do something - self.reentrancy_guard.end(); - } + self.reentrancy_guard.end(); + } - fn another_protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); + #[external(v0)] + fn another_protected_function(ref self: ContractState) { + self.reentrancy_guard.start(); - // Do something + // Do something - self.reentrancy_guard.end(); - } + self.reentrancy_guard.end(); } } ---- diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 5ece94326..814678c58 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -224,9 +224,6 @@ fn deploy_test_contract() -> ContractAddress { Pops the earliest unpopped logged event for the contract as the requested type and checks that there's no more keys or data left on the event, preventing unaccounted params. -This function also removes the first key from the event, to match the event structure key params without -the event ID. - Required traits for `T`: - `Drop` @@ -248,6 +245,9 @@ Asserts that `expected_keys` exactly matches the indexed keys from `event`. `expected_keys` must include all indexed event keys for `event` in the order that they're defined. +NOTE: If the event is not flattened, the first key will be the event member name +e.g. selector!("EnumMemberName"). + Required traits for `T`: - `Drop` diff --git a/docs/modules/ROOT/pages/utils/_class_hashes.adoc b/docs/modules/ROOT/pages/utils/_class_hashes.adoc index 240d219c6..cb64c2703 100644 --- a/docs/modules/ROOT/pages/utils/_class_hashes.adoc +++ b/docs/modules/ROOT/pages/utils/_class_hashes.adoc @@ -2,10 +2,11 @@ :class-hash-cairo-version: https://crates.io/crates/cairo-lang-compiler/2.5.3[cairo 2.5.3] // Class Hashes -:account-class-hash: 0x01148c31dfa5c4708a4e9cf1eb0fd3d4d8ad9ccf09d0232cd6b56bee64a7de9d -:eth-account-upgradeable-class-hash: 0x023e416842ca96b1f7067693892ed00881d97a4b0d9a4c793b75cb887944d98d -:erc20-class-hash: 0x7d94f28156c0dc3bfd9a07ca79b15b8da2b5b32093db79000fcd0f6f625d213 -:erc721-class-hash: 0x06b7c9efc5467c621f58d87995302d940a39b7217b5c5a7a55555c97cabf5cd8 +:Account-class-hash: 0x01148c31dfa5c4708a4e9cf1eb0fd3d4d8ad9ccf09d0232cd6b56bee64a7de9d +:ERC20-class-hash: 0x07d94f28156c0dc3bfd9a07ca79b15b8da2b5b32093db79000fcd0f6f625d213 +:ERC721-class-hash: 0x06b7c9efc5467c621f58d87995302d940a39b7217b5c5a7a55555c97cabf5cd8 +:ERC1155-class-hash: 0x518be7d9fa527c78d6929bf9e638e9c98b6077722e27e9546cc4342e830386e +:EthAccountUpgradeable-class-hash: 0x0580fe510cf07255540abda6f9d29aa07cb8944db444bca59e1573904c269844 :UniversalDeployer-class-hash: 0x0548f35c7316b4dc5efdaa929fe83f1007c7e0ae24bec3045d47c94da00dd386 // Presets page diff --git a/docs/modules/ROOT/pages/utils/_common.adoc b/docs/modules/ROOT/pages/utils/_common.adoc new file mode 100644 index 000000000..b69272b96 --- /dev/null +++ b/docs/modules/ROOT/pages/utils/_common.adoc @@ -0,0 +1,5 @@ +// Notes +:src5-component-required-note: Implementing xref:api/introspection.adoc#SRC5Component[SRC5Component] is a requirement for this component to be implemented. + +// Links +:mixin-impls: xref:components.adoc#mixins[Embeddable Mixin Implementations] diff --git a/docs/modules/ROOT/pages/wizard.adoc b/docs/modules/ROOT/pages/wizard.adoc index a60ee2dae..5c5cc8439 100644 --- a/docs/modules/ROOT/pages/wizard.adoc +++ b/docs/modules/ROOT/pages/wizard.adoc @@ -10,5 +10,5 @@ NOTE: We strongly recommend checking the xref:components.adoc[Components] sectio ++++ - + ++++ diff --git a/scripts/get_hashes_page.py b/scripts/get_hashes_page.py new file mode 100644 index 000000000..2cc1b0d96 --- /dev/null +++ b/scripts/get_hashes_page.py @@ -0,0 +1,37 @@ +import sys +import json + + +def main(): + # Required compiler version argument + cmp_version = sys.argv[1] + + # Read class hashes from stdin + contracts = json.load(sys.stdin) + + print(generate_doc_file(cmp_version, contracts)) + + +def generate_doc_file(cmp_version, contracts): + header = f"""// Version +:class-hash-cairo-version: \ +https://crates.io/crates/cairo-lang-compiler/{cmp_version}[cairo {cmp_version}] +""" + hashes = "// Class Hashes\n" + contracts['contracts'].sort(key=lambda x: x['name']) + for contract in contracts['contracts']: + # The [13:] is to remove the "openzeppelin_" prefix from the contract name + hashes += f":{contract['name'][13:]}-class-hash: {normalize_len(contract['sierra'])}\n" + + footer = """// Presets page +:presets-page: xref:presets.adoc[Sierra class hash]""" + + return f"{header}\n{hashes}\n{footer}\n" + + +def normalize_len(sierra_hash): + return "0x" + "0" * (66 - len(sierra_hash)) + sierra_hash[2:] + + +if __name__ == '__main__': + main() diff --git a/src/access/accesscontrol/accesscontrol.cairo b/src/access/accesscontrol/accesscontrol.cairo index 44b61ba92..2ce327086 100644 --- a/src/access/accesscontrol/accesscontrol.cairo +++ b/src/access/accesscontrol/accesscontrol.cairo @@ -9,6 +9,7 @@ mod AccessControlComponent { use openzeppelin::access::accesscontrol::interface; use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection::src5::SRC5Component; use starknet::ContractAddress; use starknet::get_caller_address; @@ -20,7 +21,7 @@ mod AccessControlComponent { } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { RoleGranted: RoleGranted, RoleRevoked: RoleRevoked, @@ -31,7 +32,7 @@ mod AccessControlComponent { /// /// `sender` is the account that originated the contract call, an admin role /// bearer (except if `_grant_role` is called during initialization from the constructor). - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct RoleGranted { role: felt252, account: ContractAddress, @@ -43,7 +44,7 @@ mod AccessControlComponent { /// `sender` is the account that originated the contract call: /// - If using `revoke_role`, it is the admin role bearer. /// - If using `renounce_role`, it is the role bearer (i.e. `account`). - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct RoleRevoked { role: felt252, account: ContractAddress, @@ -54,7 +55,7 @@ mod AccessControlComponent { /// /// `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite /// {RoleAdminChanged} not being emitted signaling this. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct RoleAdminChanged { role: felt252, previous_admin_role: felt252, @@ -95,7 +96,7 @@ mod AccessControlComponent { fn grant_role( ref self: ComponentState, role: felt252, account: ContractAddress ) { - let admin = self.get_role_admin(role); + let admin = AccessControl::get_role_admin(@self, role); self.assert_only_role(admin); self._grant_role(role, account); } @@ -110,7 +111,7 @@ mod AccessControlComponent { fn revoke_role( ref self: ComponentState, role: felt252, account: ContractAddress ) { - let admin = self.get_role_admin(role); + let admin = AccessControl::get_role_admin(@self, role); self.assert_only_role(admin); self._revoke_role(role, account); } @@ -147,29 +148,29 @@ mod AccessControlComponent { fn hasRole( self: @ComponentState, role: felt252, account: ContractAddress ) -> bool { - self.has_role(role, account) + AccessControl::has_role(self, role, account) } fn getRoleAdmin(self: @ComponentState, role: felt252) -> felt252 { - self.get_role_admin(role) + AccessControl::get_role_admin(self, role) } fn grantRole( ref self: ComponentState, role: felt252, account: ContractAddress ) { - self.grant_role(role, account); + AccessControl::grant_role(ref self, role, account); } fn revokeRole( ref self: ComponentState, role: felt252, account: ContractAddress ) { - self.revoke_role(role, account); + AccessControl::revoke_role(ref self, role, account); } fn renounceRole( ref self: ComponentState, role: felt252, account: ContractAddress ) { - self.renounce_role(role, account); + AccessControl::renounce_role(ref self, role, account); } } @@ -189,7 +190,7 @@ mod AccessControlComponent { /// Validates that the caller has the given role. Otherwise it panics. fn assert_only_role(self: @ComponentState, role: felt252) { let caller: ContractAddress = get_caller_address(); - let authorized: bool = self.has_role(role, caller); + let authorized = AccessControl::has_role(self, role, caller); assert(authorized, Errors::MISSING_ROLE); } @@ -201,7 +202,7 @@ mod AccessControlComponent { fn _grant_role( ref self: ComponentState, role: felt252, account: ContractAddress ) { - if !self.has_role(role, account) { + if !AccessControl::has_role(@self, role, account) { let caller: ContractAddress = get_caller_address(); self.AccessControl_role_member.write((role, account), true); self.emit(RoleGranted { role, account, sender: caller }); @@ -216,7 +217,7 @@ mod AccessControlComponent { fn _revoke_role( ref self: ComponentState, role: felt252, account: ContractAddress ) { - if self.has_role(role, account) { + if AccessControl::has_role(@self, role, account) { let caller: ContractAddress = get_caller_address(); self.AccessControl_role_member.write((role, account), false); self.emit(RoleRevoked { role, account, sender: caller }); @@ -229,9 +230,83 @@ mod AccessControlComponent { fn _set_role_admin( ref self: ComponentState, role: felt252, admin_role: felt252 ) { - let previous_admin_role: felt252 = self.get_role_admin(role); + let previous_admin_role: felt252 = AccessControl::get_role_admin(@self, role); self.AccessControl_role_admin.write(role, admin_role); self.emit(RoleAdminChanged { role, previous_admin_role, new_admin_role: admin_role }); } } + + #[embeddable_as(AccessControlMixinImpl)] + impl AccessControlMixin< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of interface::AccessControlABI> { + // IAccessControl + fn has_role( + self: @ComponentState, role: felt252, account: ContractAddress + ) -> bool { + AccessControl::has_role(self, role, account) + } + + fn get_role_admin(self: @ComponentState, role: felt252) -> felt252 { + AccessControl::get_role_admin(self, role) + } + + fn grant_role( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + AccessControl::grant_role(ref self, role, account); + } + + fn revoke_role( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + AccessControl::revoke_role(ref self, role, account); + } + + fn renounce_role( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + AccessControl::renounce_role(ref self, role, account); + } + + // IAccessControlCamel + fn hasRole( + self: @ComponentState, role: felt252, account: ContractAddress + ) -> bool { + AccessControlCamel::hasRole(self, role, account) + } + + fn getRoleAdmin(self: @ComponentState, role: felt252) -> felt252 { + AccessControlCamel::getRoleAdmin(self, role) + } + + fn grantRole( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + AccessControlCamel::grantRole(ref self, role, account); + } + + fn revokeRole( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + AccessControlCamel::revokeRole(ref self, role, account); + } + + fn renounceRole( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + AccessControlCamel::renounceRole(ref self, role, account); + } + + // ISRC5 + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { + let src5 = get_dep_component!(self, SRC5); + src5.supports_interface(interface_id) + } + } } diff --git a/src/access/accesscontrol/interface.cairo b/src/access/accesscontrol/interface.cairo index fdf093c9e..e121e0121 100644 --- a/src/access/accesscontrol/interface.cairo +++ b/src/access/accesscontrol/interface.cairo @@ -23,3 +23,23 @@ trait IAccessControlCamel { fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); } + +#[starknet::interface] +trait AccessControlABI { + // IAccessControl + fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(self: @TState, role: felt252) -> felt252; + fn grant_role(ref self: TState, role: felt252, account: ContractAddress); + fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); + fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(self: @TState, role: felt252) -> felt252; + fn grantRole(ref self: TState, role: felt252, account: ContractAddress); + fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); + fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; +} diff --git a/src/access/ownable/interface.cairo b/src/access/ownable/interface.cairo index d4299d336..ca42d75d9 100644 --- a/src/access/ownable/interface.cairo +++ b/src/access/ownable/interface.cairo @@ -16,6 +16,18 @@ trait IOwnableCamelOnly { fn renounceOwnership(ref self: TState); } +#[starknet::interface] +trait OwnableABI { + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); +} + #[starknet::interface] trait IOwnableTwoStep { fn owner(self: @TState) -> ContractAddress; @@ -32,3 +44,19 @@ trait IOwnableTwoStepCamelOnly { fn transferOwnership(ref self: TState, newOwner: ContractAddress); fn renounceOwnership(ref self: TState); } + +#[starknet::interface] +trait OwnableTwoStepABI { + // IOwnableTwoStep + fn owner(self: @TState) -> ContractAddress; + fn pending_owner(self: @TState) -> ContractAddress; + fn accept_ownership(ref self: TState); + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableTwoStepCamelOnly + fn pendingOwner(self: @TState) -> ContractAddress; + fn acceptOwnership(ref self: TState); + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); +} diff --git a/src/access/ownable/ownable.cairo b/src/access/ownable/ownable.cairo index 851cf5505..6dae1b203 100644 --- a/src/access/ownable/ownable.cairo +++ b/src/access/ownable/ownable.cairo @@ -15,6 +15,7 @@ /// finalize the transfer. #[starknet::component] mod OwnableComponent { + use openzeppelin::access::ownable::interface::IOwnableTwoStep; use openzeppelin::access::ownable::interface; use starknet::ContractAddress; use starknet::get_caller_address; @@ -26,13 +27,13 @@ mod OwnableComponent { } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { OwnershipTransferred: OwnershipTransferred, OwnershipTransferStarted: OwnershipTransferStarted } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct OwnershipTransferred { #[key] previous_owner: ContractAddress, @@ -40,7 +41,7 @@ mod OwnableComponent { new_owner: ContractAddress, } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct OwnershipTransferStarted { #[key] previous_owner: ContractAddress, @@ -228,4 +229,78 @@ mod OwnableComponent { ); } } + + #[embeddable_as(OwnableMixinImpl)] + impl OwnableMixin< + TContractState, +HasComponent, +Drop + > of interface::OwnableABI> { + // IOwnable + fn owner(self: @ComponentState) -> ContractAddress { + Ownable::owner(self) + } + + fn transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress + ) { + Ownable::transfer_ownership(ref self, new_owner); + } + + fn renounce_ownership(ref self: ComponentState) { + Ownable::renounce_ownership(ref self); + } + + // IOwnableCamelOnly + fn transferOwnership(ref self: ComponentState, newOwner: ContractAddress) { + OwnableCamelOnly::transferOwnership(ref self, newOwner); + } + + fn renounceOwnership(ref self: ComponentState) { + OwnableCamelOnly::renounceOwnership(ref self); + } + } + + #[embeddable_as(OwnableTwoStepMixinImpl)] + impl OwnableTwoStepMixin< + TContractState, +HasComponent, +Drop + > of interface::OwnableTwoStepABI> { + // IOwnableTwoStep + fn owner(self: @ComponentState) -> ContractAddress { + OwnableTwoStep::owner(self) + } + + fn pending_owner(self: @ComponentState) -> ContractAddress { + OwnableTwoStep::pending_owner(self) + } + + fn accept_ownership(ref self: ComponentState) { + OwnableTwoStep::accept_ownership(ref self); + } + + fn transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress + ) { + OwnableTwoStep::transfer_ownership(ref self, new_owner); + } + + fn renounce_ownership(ref self: ComponentState) { + OwnableTwoStep::renounce_ownership(ref self); + } + + // IOwnableTwoStepCamelOnly + fn pendingOwner(self: @ComponentState) -> ContractAddress { + OwnableTwoStepCamelOnly::pendingOwner(self) + } + + fn acceptOwnership(ref self: ComponentState) { + OwnableTwoStepCamelOnly::acceptOwnership(ref self); + } + + fn transferOwnership(ref self: ComponentState, newOwner: ContractAddress) { + OwnableTwoStepCamelOnly::transferOwnership(ref self, newOwner); + } + + fn renounceOwnership(ref self: ComponentState) { + OwnableTwoStepCamelOnly::renounceOwnership(ref self); + } + } } diff --git a/src/account/account.cairo b/src/account/account.cairo index 2f720b8a4..15f73db61 100644 --- a/src/account/account.cairo +++ b/src/account/account.cairo @@ -10,6 +10,7 @@ mod AccountComponent { use openzeppelin::account::utils::{MIN_TRANSACTION_VERSION, QUERY_VERSION, QUERY_OFFSET}; use openzeppelin::account::utils::{execute_calls, is_valid_stark_signature}; use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component::{SRC5, SRC5Camel}; use openzeppelin::introspection::src5::SRC5Component; use starknet::account::Call; use starknet::get_caller_address; @@ -22,19 +23,19 @@ mod AccountComponent { } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { OwnerAdded: OwnerAdded, OwnerRemoved: OwnerRemoved } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct OwnerAdded { #[key] new_owner_guid: felt252 } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct OwnerRemoved { #[key] removed_owner_guid: felt252 @@ -174,7 +175,7 @@ mod AccountComponent { fn isValidSignature( self: @ComponentState, hash: felt252, signature: Array ) -> felt252 { - self.is_valid_signature(hash, signature) + SRC6::is_valid_signature(self, hash, signature) } } @@ -191,7 +192,7 @@ mod AccountComponent { } fn setPublicKey(ref self: ComponentState, newPublicKey: felt252) { - self.set_public_key(newPublicKey); + PublicKey::set_public_key(ref self, newPublicKey); } } @@ -245,4 +246,79 @@ mod AccountComponent { is_valid_stark_signature(hash, public_key, signature) } } + + #[embeddable_as(AccountMixinImpl)] + impl AccountMixin< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of interface::AccountABI> { + // ISRC6 + fn __execute__( + self: @ComponentState, calls: Array + ) -> Array> { + SRC6::__execute__(self, calls) + } + + fn __validate__(self: @ComponentState, calls: Array) -> felt252 { + SRC6::__validate__(self, calls) + } + + fn is_valid_signature( + self: @ComponentState, hash: felt252, signature: Array + ) -> felt252 { + SRC6::is_valid_signature(self, hash, signature) + } + + // ISRC6CamelOnly + fn isValidSignature( + self: @ComponentState, hash: felt252, signature: Array + ) -> felt252 { + SRC6CamelOnly::isValidSignature(self, hash, signature) + } + + // IDeclarer + fn __validate_declare__( + self: @ComponentState, class_hash: felt252 + ) -> felt252 { + Declarer::__validate_declare__(self, class_hash) + } + + // IDeployable + fn __validate_deploy__( + self: @ComponentState, + class_hash: felt252, + contract_address_salt: felt252, + public_key: felt252 + ) -> felt252 { + Deployable::__validate_deploy__(self, class_hash, contract_address_salt, public_key) + } + + // IPublicKey + fn get_public_key(self: @ComponentState) -> felt252 { + PublicKey::get_public_key(self) + } + + fn set_public_key(ref self: ComponentState, new_public_key: felt252) { + PublicKey::set_public_key(ref self, new_public_key); + } + + // IPublicKeyCamel + fn getPublicKey(self: @ComponentState) -> felt252 { + PublicKeyCamel::getPublicKey(self) + } + + fn setPublicKey(ref self: ComponentState, newPublicKey: felt252) { + PublicKeyCamel::setPublicKey(ref self, newPublicKey); + } + + // ISRC5 + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { + let src5 = get_dep_component!(self, SRC5); + src5.supports_interface(interface_id) + } + } } diff --git a/src/account/eth_account.cairo b/src/account/eth_account.cairo index 112e0f4f5..c3e21f922 100644 --- a/src/account/eth_account.cairo +++ b/src/account/eth_account.cairo @@ -13,6 +13,7 @@ mod EthAccountComponent { use openzeppelin::account::utils::{MIN_TRANSACTION_VERSION, QUERY_VERSION, QUERY_OFFSET}; use openzeppelin::account::utils::{execute_calls, is_valid_eth_signature}; use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component::{SRC5, SRC5Camel}; use openzeppelin::introspection::src5::SRC5Component; use poseidon::poseidon_hash_span; use starknet::SyscallResultTrait; @@ -27,19 +28,19 @@ mod EthAccountComponent { } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { OwnerAdded: OwnerAdded, OwnerRemoved: OwnerRemoved } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct OwnerAdded { #[key] new_owner_guid: felt252 } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct OwnerRemoved { #[key] removed_owner_guid: felt252 @@ -183,7 +184,7 @@ mod EthAccountComponent { fn isValidSignature( self: @ComponentState, hash: felt252, signature: Array ) -> felt252 { - self.is_valid_signature(hash, signature) + SRC6::is_valid_signature(self, hash, signature) } } @@ -200,7 +201,7 @@ mod EthAccountComponent { } fn setPublicKey(ref self: ComponentState, newPublicKey: EthPublicKey) { - self.set_public_key(newPublicKey); + PublicKey::set_public_key(ref self, newPublicKey); } } @@ -260,4 +261,79 @@ mod EthAccountComponent { let (x, y) = public_key.get_coordinates().unwrap_syscall(); poseidon_hash_span(array![x.low.into(), x.high.into(), y.low.into(), y.high.into()].span()) } + + #[embeddable_as(EthAccountMixinImpl)] + impl EthAccountMixin< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of interface::EthAccountABI> { + // ISRC6 + fn __execute__( + self: @ComponentState, calls: Array + ) -> Array> { + SRC6::__execute__(self, calls) + } + + fn __validate__(self: @ComponentState, calls: Array) -> felt252 { + SRC6::__validate__(self, calls) + } + + fn is_valid_signature( + self: @ComponentState, hash: felt252, signature: Array + ) -> felt252 { + SRC6::is_valid_signature(self, hash, signature) + } + + // ISRC6CamelOnly + fn isValidSignature( + self: @ComponentState, hash: felt252, signature: Array + ) -> felt252 { + SRC6CamelOnly::isValidSignature(self, hash, signature) + } + + // IDeclarer + fn __validate_declare__( + self: @ComponentState, class_hash: felt252 + ) -> felt252 { + Declarer::__validate_declare__(self, class_hash) + } + + // IDeployable + fn __validate_deploy__( + self: @ComponentState, + class_hash: felt252, + contract_address_salt: felt252, + public_key: EthPublicKey + ) -> felt252 { + Deployable::__validate_deploy__(self, class_hash, contract_address_salt, public_key) + } + + // IPublicKey + fn get_public_key(self: @ComponentState) -> EthPublicKey { + PublicKey::get_public_key(self) + } + + fn set_public_key(ref self: ComponentState, new_public_key: EthPublicKey) { + PublicKey::set_public_key(ref self, new_public_key); + } + + // IPublicKeyCamel + fn getPublicKey(self: @ComponentState) -> EthPublicKey { + PublicKeyCamel::getPublicKey(self) + } + + fn setPublicKey(ref self: ComponentState, newPublicKey: EthPublicKey) { + PublicKeyCamel::setPublicKey(ref self, newPublicKey); + } + + // ISRC5 + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { + let src5 = get_dep_component!(self, SRC5); + src5.supports_interface(interface_id) + } + } } diff --git a/src/account/interface.cairo b/src/account/interface.cairo index 7ba173a8d..abed1104c 100644 --- a/src/account/interface.cairo +++ b/src/account/interface.cairo @@ -78,9 +78,6 @@ trait AccountABI { // ISRC6CamelOnly fn isValidSignature(self: @TState, hash: felt252, signature: Array) -> felt252; - // ISRC5Camel - fn supportsInterface(self: @TState, interfaceId: felt252) -> bool; - // IPublicKeyCamel fn getPublicKey(self: @TState) -> felt252; fn setPublicKey(ref self: TState, newPublicKey: felt252); @@ -139,9 +136,6 @@ trait EthAccountABI { // ISRC6CamelOnly fn isValidSignature(self: @TState, hash: felt252, signature: Array) -> felt252; - // ISRC5Camel - fn supportsInterface(self: @TState, interfaceId: felt252) -> bool; - // IEthPublicKeyCamel fn getPublicKey(self: @TState) -> EthPublicKey; fn setPublicKey(ref self: TState, newPublicKey: EthPublicKey); diff --git a/src/presets.cairo b/src/presets.cairo index 2285016b5..932bc9b35 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -1,10 +1,12 @@ mod account; +mod erc1155; mod erc20; mod erc721; mod eth_account; mod universal_deployer; use account::Account; +use erc1155::ERC1155; use erc20::ERC20; use erc721::ERC721; use eth_account::EthAccountUpgradeable; diff --git a/src/presets/account.cairo b/src/presets/account.cairo index 5c78e5f34..7193852c8 100644 --- a/src/presets/account.cairo +++ b/src/presets/account.cairo @@ -12,25 +12,11 @@ mod Account { component!(path: AccountComponent, storage: account, event: AccountEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); - // Account + // AccountMixin #[abi(embed_v0)] - impl SRC6Impl = AccountComponent::SRC6Impl; - #[abi(embed_v0)] - impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; - #[abi(embed_v0)] - impl PublicKeyImpl = AccountComponent::PublicKeyImpl; - #[abi(embed_v0)] - impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; - #[abi(embed_v0)] - impl DeclarerImpl = AccountComponent::DeclarerImpl; - #[abi(embed_v0)] - impl DeployableImpl = AccountComponent::DeployableImpl; + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; impl AccountInternalImpl = AccountComponent::InternalImpl; - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - #[storage] struct Storage { #[substorage(v0)] diff --git a/src/presets/erc1155.cairo b/src/presets/erc1155.cairo new file mode 100644 index 000000000..b1c111e5f --- /dev/null +++ b/src/presets/erc1155.cairo @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (presets/erc1155.cairo) + +/// # ERC1155 Preset +/// +/// The ERC1155 contract offers a batch-mint mechanism that +/// can only be executed once upon contract construction. +/// +/// For more complex or custom contracts, use Wizard for Cairo +/// https://wizard.openzeppelin.com/cairo +#[starknet::contract] +mod ERC1155 { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155Component; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 + #[abi(embed_v0)] + impl ERC1155Impl = ERC1155Component::ERC1155Impl; + #[abi(embed_v0)] + impl ERC1155MetadataURIImpl = + ERC1155Component::ERC1155MetadataURIImpl; + #[abi(embed_v0)] + impl ERC1155Camel = ERC1155Component::ERC1155CamelImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + /// Sets the `base_uri` for all tokens. + /// Mints the `values` for `token_ids` tokens to `recipient`. + /// + /// Requirements: + /// + /// - `to` is either an account contract (supporting ISRC6) or + /// supports the `IERC1155Receiver` interface. + /// - `token_ids` and `values` must have the same length. + #[constructor] + fn constructor( + ref self: ContractState, + base_uri: ByteArray, + recipient: ContractAddress, + token_ids: Span, + values: Span + ) { + self.erc1155.initializer(base_uri); + self + .erc1155 + .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); + } +} diff --git a/src/presets/erc20.cairo b/src/presets/erc20.cairo index 725959165..28ed49114 100644 --- a/src/presets/erc20.cairo +++ b/src/presets/erc20.cairo @@ -13,12 +13,9 @@ mod ERC20 { component!(path: ERC20Component, storage: erc20, event: ERC20Event); + // ERC20Mixin #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl InternalImpl = ERC20Component::InternalImpl; #[storage] @@ -39,8 +36,8 @@ mod ERC20 { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress ) { diff --git a/src/presets/erc721.cairo b/src/presets/erc721.cairo index 1ac53d6e3..dcd1d40e9 100644 --- a/src/presets/erc721.cairo +++ b/src/presets/erc721.cairo @@ -14,22 +14,11 @@ mod ERC721 { component!(path: ERC721Component, storage: erc721, event: ERC721Event); component!(path: SRC5Component, storage: src5, event: SRC5Event); - // ERC721 + // ERC721Mixin #[abi(embed_v0)] - impl ERC721Impl = ERC721Component::ERC721Impl; - #[abi(embed_v0)] - impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl; - #[abi(embed_v0)] - impl ERC721CamelOnly = ERC721Component::ERC721CamelOnlyImpl; - #[abi(embed_v0)] - impl ERC721MetadataCamelOnly = - ERC721Component::ERC721MetadataCamelOnlyImpl; + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; impl ERC721InternalImpl = ERC721Component::InternalImpl; - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - #[storage] struct Storage { #[substorage(v0)] @@ -47,52 +36,35 @@ mod ERC721 { SRC5Event: SRC5Component::Event } - mod Errors { - const UNEQUAL_ARRAYS: felt252 = 'Array lengths do not match'; - } - /// Sets the token `name` and `symbol`. /// Mints the `token_ids` tokens to `recipient` and sets - /// each token's URI. + /// the base URI. #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, recipient: ContractAddress, token_ids: Span, - token_uris: Span ) { - self.erc721.initializer(name, symbol); - self._mint_assets(recipient, token_ids, token_uris); + self.erc721.initializer(name, symbol, base_uri); + self._mint_assets(recipient, token_ids); } - /// Mints `token_ids` to `recipient`. - /// Sets the token URI from `token_uris` to the corresponding - /// token ID of `token_ids`. - /// - /// Requirements: - /// - /// - `token_ids` must be equal in length to `token_uris`. #[generate_trait] impl InternalImpl of InternalTrait { + /// Mints `token_ids` to `recipient`. fn _mint_assets( - ref self: ContractState, - recipient: ContractAddress, - mut token_ids: Span, - mut token_uris: Span + ref self: ContractState, recipient: ContractAddress, mut token_ids: Span ) { - assert(token_ids.len() == token_uris.len(), Errors::UNEQUAL_ARRAYS); - loop { if token_ids.len() == 0 { break; } let id = *token_ids.pop_front().unwrap(); - let uri = *token_uris.pop_front().unwrap(); self.erc721._mint(recipient, id); - self.erc721._set_token_uri(id, uri); } } } diff --git a/src/presets/eth_account.cairo b/src/presets/eth_account.cairo index 345ace630..0676ebad1 100644 --- a/src/presets/eth_account.cairo +++ b/src/presets/eth_account.cairo @@ -19,26 +19,12 @@ mod EthAccountUpgradeable { component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - // EthAccount + // EthAccountMixin #[abi(embed_v0)] - impl SRC6Impl = EthAccountComponent::SRC6Impl; - #[abi(embed_v0)] - impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl; - #[abi(embed_v0)] - impl PublicKeyImpl = EthAccountComponent::PublicKeyImpl; - #[abi(embed_v0)] - impl PublicKeyCamelImpl = - EthAccountComponent::PublicKeyCamelImpl; - #[abi(embed_v0)] - impl DeclarerImpl = EthAccountComponent::DeclarerImpl; - #[abi(embed_v0)] - impl DeployableImpl = EthAccountComponent::DeployableImpl; + impl EthAccountMixinImpl = + EthAccountComponent::EthAccountMixinImpl; impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - // Upgradeable impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 20871c3e2..81ce7ccea 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -22,12 +22,12 @@ mod UniversalDeployer { struct Storage {} #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { ContractDeployed: ContractDeployed } - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct ContractDeployed { address: ContractAddress, deployer: ContractAddress, diff --git a/src/security/pausable.cairo b/src/security/pausable.cairo index 74ae9b2a2..fb477c11c 100644 --- a/src/security/pausable.cairo +++ b/src/security/pausable.cairo @@ -19,20 +19,20 @@ mod PausableComponent { } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { Paused: Paused, Unpaused: Unpaused, } /// Emitted when the pause is triggered by `account`. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Paused { account: ContractAddress } /// Emitted when the pause is lifted by `account`. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Unpaused { account: ContractAddress } diff --git a/src/tests/access/test_accesscontrol.cairo b/src/tests/access/test_accesscontrol.cairo index df2a2b9fe..71ec36030 100644 --- a/src/tests/access/test_accesscontrol.cairo +++ b/src/tests/access/test_accesscontrol.cairo @@ -45,7 +45,7 @@ fn setup() -> ComponentState { fn test_initializer() { let mut state = COMPONENT_STATE(); state.initializer(); - let supports_iaccesscontrol = CONTRACT_STATE().supports_interface(IACCESSCONTROL_ID); + let supports_iaccesscontrol = CONTRACT_STATE().src5.supports_interface(IACCESSCONTROL_ID); assert!(supports_iaccesscontrol); } @@ -448,27 +448,30 @@ fn test_default_admin_role_is_its_own_admin() { // fn assert_event_role_revoked(role: felt252, account: ContractAddress, sender: ContractAddress) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.role, role); - assert_eq!(event.account, account); - assert_eq!(event.sender, sender); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = AccessControlComponent::Event::RoleRevoked( + RoleRevoked { role, account, sender } + ); + assert!(event == expected); utils::assert_no_events_left(ZERO()); } fn assert_event_role_granted(role: felt252, account: ContractAddress, sender: ContractAddress) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.role, role); - assert_eq!(event.account, account); - assert_eq!(event.sender, sender); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = AccessControlComponent::Event::RoleGranted( + RoleGranted { role, account, sender } + ); + assert!(event == expected); utils::assert_no_events_left(ZERO()); } fn assert_event_role_admin_changed( role: felt252, previous_admin_role: felt252, new_admin_role: felt252 ) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.role, role); - assert_eq!(event.previous_admin_role, previous_admin_role); - assert_eq!(event.new_admin_role, new_admin_role); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = AccessControlComponent::Event::RoleAdminChanged( + RoleAdminChanged { role, previous_admin_role, new_admin_role } + ); + assert!(event == expected); utils::assert_no_events_left(ZERO()); } diff --git a/src/tests/access/test_ownable.cairo b/src/tests/access/test_ownable.cairo index 1f723901e..7d7abb968 100644 --- a/src/tests/access/test_ownable.cairo +++ b/src/tests/access/test_ownable.cairo @@ -216,12 +216,15 @@ fn test_renounceOwnership_from_nonowner() { // fn assert_event_ownership_transferred(previous_owner: ContractAddress, new_owner: ContractAddress) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.previous_owner, previous_owner); - assert_eq!(event.new_owner, new_owner); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = OwnableComponent::Event::OwnershipTransferred( + OwnershipTransferred { previous_owner, new_owner } + ); + assert!(event == expected); utils::assert_no_events_left(ZERO()); let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("OwnershipTransferred")); indexed_keys.append_serde(previous_owner); indexed_keys.append_serde(new_owner); utils::assert_indexed_keys(event, indexed_keys.span()); diff --git a/src/tests/access/test_ownable_twostep.cairo b/src/tests/access/test_ownable_twostep.cairo index 9708fc033..2ce7af905 100644 --- a/src/tests/access/test_ownable_twostep.cairo +++ b/src/tests/access/test_ownable_twostep.cairo @@ -34,17 +34,16 @@ fn setup() -> ComponentState { // #[test] -#[available_gas(2000000)] fn test_initializer_owner_pending_owner() { let mut state = COMPONENT_STATE(); - assert(state.Ownable_owner.read() == ZERO(), 'Owner should be ZERO'); - assert(state.Ownable_pending_owner.read() == ZERO(), 'Pending owner should be ZERO'); + assert!(state.Ownable_owner.read().is_zero()); + assert!(state.Ownable_pending_owner.read().is_zero()); state.initializer(OWNER()); assert_event_ownership_transferred(ZERO(), OWNER()); - assert(state.Ownable_owner.read() == OWNER(), 'Owner should be set'); - assert(state.Ownable_pending_owner.read() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.Ownable_owner.read(), OWNER()); + assert!(state.Ownable_pending_owner.read().is_zero()); } // @@ -52,7 +51,6 @@ fn test_initializer_owner_pending_owner() { // #[test] -#[available_gas(2000000)] fn test__accept_ownership() { let mut state = setup(); state.Ownable_pending_owner.write(OTHER()); @@ -60,8 +58,8 @@ fn test__accept_ownership() { state._accept_ownership(); assert_event_ownership_transferred(OWNER(), OTHER()); - assert(state.owner() == OTHER(), 'Owner should be OTHER'); - assert(state.pending_owner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OTHER()); + assert!(state.pending_owner().is_zero()); } // @@ -69,52 +67,48 @@ fn test__accept_ownership() { // #[test] -#[available_gas(2000000)] fn test__propose_owner() { let mut state = setup(); state._propose_owner(OTHER()); assert_event_ownership_transfer_started(OWNER(), OTHER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pending_owner() == OTHER(), 'Pending owner should be OTHER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pending_owner(), OTHER()); } // transfer_ownership & transferOwnership #[test] -#[available_gas(2000000)] fn test_transfer_ownership() { let mut state = setup(); testing::set_caller_address(OWNER()); state.transfer_ownership(OTHER()); assert_event_ownership_transfer_started(OWNER(), OTHER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pending_owner() == OTHER(), 'Pending owner should be OTHER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pending_owner(), OTHER()); // Transferring to yet another owner while pending is set should work state.transfer_ownership(NEW_OWNER()); assert_event_ownership_transfer_started(OWNER(), NEW_OWNER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pending_owner() == NEW_OWNER(), 'Pending should be NEW_OWNER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pending_owner(), NEW_OWNER()); } #[test] -#[available_gas(2000000)] fn test_transfer_ownership_to_zero() { let mut state = setup(); testing::set_caller_address(OWNER()); state.transfer_ownership(ZERO()); assert_event_ownership_transfer_started(OWNER(), ZERO()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pending_owner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pending_owner(), ZERO()); } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is the zero address',))] fn test_transfer_ownership_from_zero() { let mut state = setup(); @@ -122,7 +116,6 @@ fn test_transfer_ownership_from_zero() { } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is not the owner',))] fn test_transfer_ownership_from_nonowner() { let mut state = setup(); @@ -131,38 +124,35 @@ fn test_transfer_ownership_from_nonowner() { } #[test] -#[available_gas(2000000)] fn test_transferOwnership() { let mut state = setup(); testing::set_caller_address(OWNER()); state.transferOwnership(OTHER()); assert_event_ownership_transfer_started(OWNER(), OTHER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pendingOwner() == OTHER(), 'Pending owner should be OTHER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pendingOwner(), OTHER()); // Transferring to yet another owner while pending is set should work state.transferOwnership(NEW_OWNER()); assert_event_ownership_transfer_started(OWNER(), NEW_OWNER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pendingOwner() == NEW_OWNER(), 'Pending should be NEW_OWNER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pendingOwner(), NEW_OWNER()); } #[test] -#[available_gas(2000000)] fn test_transferOwnership_to_zero() { let mut state = setup(); testing::set_caller_address(OWNER()); state.transferOwnership(ZERO()); assert_event_ownership_transfer_started(OWNER(), ZERO()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pendingOwner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OWNER()); + assert!(state.pendingOwner().is_zero()); } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is the zero address',))] fn test_transferOwnership_from_zero() { let mut state = setup(); @@ -170,7 +160,6 @@ fn test_transferOwnership_from_zero() { } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is not the owner',))] fn test_transferOwnership_from_nonowner() { let mut state = setup(); @@ -183,7 +172,6 @@ fn test_transferOwnership_from_nonowner() { // #[test] -#[available_gas(2000000)] fn test_accept_ownership() { let mut state = setup(); state.Ownable_pending_owner.write(OTHER()); @@ -192,12 +180,11 @@ fn test_accept_ownership() { state.accept_ownership(); assert_event_ownership_transferred(OWNER(), OTHER()); - assert(state.owner() == OTHER(), 'Owner should be OTHER'); - assert(state.pending_owner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OTHER()); + assert!(state.pending_owner().is_zero()); } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is not the pending owner',))] fn test_accept_ownership_from_nonpending() { let mut state = setup(); @@ -207,7 +194,6 @@ fn test_accept_ownership_from_nonpending() { } #[test] -#[available_gas(2000000)] fn test_acceptOwnership() { let mut state = setup(); state.Ownable_pending_owner.write(OTHER()); @@ -216,12 +202,11 @@ fn test_acceptOwnership() { state.acceptOwnership(); assert_event_ownership_transferred(OWNER(), OTHER()); - assert(state.owner() == OTHER(), 'Owner should be OTHER'); - assert(state.pendingOwner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OTHER()); + assert!(state.pendingOwner().is_zero()); } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is not the pending owner',))] fn test_acceptOwnership_from_nonpending() { let mut state = setup(); @@ -235,7 +220,6 @@ fn test_acceptOwnership_from_nonpending() { // #[test] -#[available_gas(2000000)] fn test_renounce_ownership() { let mut state = setup(); testing::set_caller_address(OWNER()); @@ -243,11 +227,10 @@ fn test_renounce_ownership() { assert_event_ownership_transferred(OWNER(), ZERO()); - assert(state.owner() == ZERO(), 'Should renounce ownership'); + assert!(state.owner().is_zero()); } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is the zero address',))] fn test_renounce_ownership_from_zero_address() { let mut state = setup(); @@ -255,7 +238,6 @@ fn test_renounce_ownership_from_zero_address() { } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is not the owner',))] fn test_renounce_ownership_from_nonowner() { let mut state = setup(); @@ -264,7 +246,6 @@ fn test_renounce_ownership_from_nonowner() { } #[test] -#[available_gas(2000000)] fn test_renounceOwnership() { let mut state = setup(); testing::set_caller_address(OWNER()); @@ -272,11 +253,10 @@ fn test_renounceOwnership() { assert_event_ownership_transferred(OWNER(), ZERO()); - assert(state.owner() == ZERO(), 'Should renounce ownership'); + assert!(state.owner().is_zero()); } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is the zero address',))] fn test_renounceOwnership_from_zero_address() { let mut state = setup(); @@ -284,7 +264,6 @@ fn test_renounceOwnership_from_zero_address() { } #[test] -#[available_gas(2000000)] #[should_panic(expected: ('Caller is not the owner',))] fn test_renounceOwnership_from_nonowner() { let mut state = setup(); @@ -293,46 +272,44 @@ fn test_renounceOwnership_from_nonowner() { } #[test] -#[available_gas(2000000)] fn test_full_two_step_transfer() { let mut state = setup(); testing::set_caller_address(OWNER()); state.transfer_ownership(OTHER()); assert_event_ownership_transfer_started(OWNER(), OTHER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pending_owner() == OTHER(), 'Pending owner should be OTHER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pending_owner(), OTHER()); testing::set_caller_address(OTHER()); state.accept_ownership(); assert_event_ownership_transferred(OWNER(), OTHER()); - assert(state.owner() == OTHER(), 'Owner should be OTHER'); - assert(state.pending_owner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OTHER()); + assert!(state.pending_owner().is_zero()); } #[test] -#[available_gas(2000000)] fn test_pending_accept_after_owner_renounce() { let mut state = setup(); testing::set_caller_address(OWNER()); state.transfer_ownership(OTHER()); assert_event_ownership_transfer_started(OWNER(), OTHER()); - assert(state.owner() == OWNER(), 'Owner should be OWNER'); - assert(state.pending_owner() == OTHER(), 'Pending owner should be OTHER'); + assert_eq!(state.owner(), OWNER()); + assert_eq!(state.pending_owner(), OTHER()); state.renounce_ownership(); assert_event_ownership_transferred(OWNER(), ZERO()); - assert(state.owner() == ZERO(), 'Should renounce ownership'); + assert!(state.owner().is_zero()); testing::set_caller_address(OTHER()); state.accept_ownership(); assert_event_ownership_transferred(ZERO(), OTHER()); - assert(state.owner() == OTHER(), 'Owner should be OTHER'); - assert(state.pending_owner() == ZERO(), 'Pending owner should be ZERO'); + assert_eq!(state.owner(), OTHER()); + assert!(state.pending_owner().is_zero()); } // @@ -342,12 +319,15 @@ fn test_pending_accept_after_owner_renounce() { fn assert_event_ownership_transfer_started( previous_owner: ContractAddress, new_owner: ContractAddress ) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert(event.previous_owner == previous_owner, 'Invalid `previous_owner`'); - assert(event.new_owner == new_owner, 'Invalid `new_owner`'); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = OwnableComponent::Event::OwnershipTransferStarted( + OwnershipTransferStarted { previous_owner, new_owner } + ); + assert!(event == expected); utils::assert_no_events_left(ZERO()); let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("OwnershipTransferStarted")); indexed_keys.append_serde(previous_owner); indexed_keys.append_serde(new_owner); utils::assert_indexed_keys(event, indexed_keys.span()); diff --git a/src/tests/account/test_account.cairo b/src/tests/account/test_account.cairo index 0ffb000dc..ec149353c 100644 --- a/src/tests/account/test_account.cairo +++ b/src/tests/account/test_account.cairo @@ -8,7 +8,8 @@ use openzeppelin::introspection::interface::{ISRC5, ISRC5_ID}; use openzeppelin::tests::mocks::account_mocks::DualCaseAccountMock; use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; use openzeppelin::tests::utils::constants::{ - PUBKEY, NEW_PUBKEY, SALT, ZERO, QUERY_OFFSET, QUERY_VERSION, MIN_TRANSACTION_VERSION + NAME, SYMBOL, PUBKEY, NEW_PUBKEY, SALT, ZERO, QUERY_OFFSET, QUERY_VERSION, + MIN_TRANSACTION_VERSION }; use openzeppelin::tests::utils; use openzeppelin::token::erc20::interface::{IERC20DispatcherTrait, IERC20Dispatcher}; @@ -89,12 +90,10 @@ fn setup_dispatcher(data: Option<@SignedTransactionData>) -> AccountABIDispatche } fn deploy_erc20(recipient: ContractAddress, initial_supply: u256) -> IERC20Dispatcher { - let name = 0; - let symbol = 0; let mut calldata = array![]; - calldata.append_serde(name); - calldata.append_serde(symbol); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); calldata.append_serde(initial_supply); calldata.append_serde(recipient); @@ -539,20 +538,26 @@ fn test__set_public_key() { // fn assert_event_owner_removed(contract: ContractAddress, removed_owner_guid: felt252) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.removed_owner_guid, removed_owner_guid); + let event = utils::pop_log::(contract).unwrap(); + let expected = AccountComponent::Event::OwnerRemoved(OwnerRemoved { removed_owner_guid }); + assert!(event == expected); // Check indexed keys - let indexed_keys = array![removed_owner_guid]; + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("OwnerRemoved")); + indexed_keys.append_serde(removed_owner_guid); utils::assert_indexed_keys(event, indexed_keys.span()); } fn assert_event_owner_added(contract: ContractAddress, new_owner_guid: felt252) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.new_owner_guid, new_owner_guid); + let event = utils::pop_log::(contract).unwrap(); + let expected = AccountComponent::Event::OwnerAdded(OwnerAdded { new_owner_guid }); + assert!(event == expected); // Check indexed keys - let indexed_keys = array![new_owner_guid]; + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("OwnerAdded")); + indexed_keys.append_serde(new_owner_guid); utils::assert_indexed_keys(event, indexed_keys.span()); } diff --git a/src/tests/account/test_eth_account.cairo b/src/tests/account/test_eth_account.cairo index 44faba392..309d03259 100644 --- a/src/tests/account/test_eth_account.cairo +++ b/src/tests/account/test_eth_account.cairo @@ -13,7 +13,7 @@ use openzeppelin::introspection::interface::{ISRC5, ISRC5_ID}; use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; use openzeppelin::tests::mocks::eth_account_mocks::DualCaseEthAccountMock; use openzeppelin::tests::utils::constants::{ - ETH_PUBKEY, NEW_ETH_PUBKEY, SALT, ZERO, OTHER, RECIPIENT, CALLER, QUERY_VERSION, + ETH_PUBKEY, NEW_ETH_PUBKEY, NAME, SYMBOL, SALT, ZERO, OTHER, RECIPIENT, CALLER, QUERY_VERSION, MIN_TRANSACTION_VERSION }; use openzeppelin::tests::utils; @@ -109,8 +109,8 @@ fn setup_dispatcher(data: Option<@SignedTransactionData>) -> EthAccountABIDispat } fn deploy_erc20(recipient: ContractAddress, initial_supply: u256) -> IERC20Dispatcher { - let name = 0; - let symbol = 0; + let name = NAME(); + let symbol = SYMBOL(); let mut calldata = array![]; calldata.append_serde(name); @@ -585,12 +585,15 @@ fn test__set_public_key() { // fn assert_event_owner_added(contract: ContractAddress, public_key: EthPublicKey) { - let event = utils::pop_log::(contract).unwrap(); - let guid = get_guid_from_public_key(public_key); - assert_eq!(event.new_owner_guid, guid); + let event = utils::pop_log::(contract).unwrap(); + let new_owner_guid = get_guid_from_public_key(public_key); + let expected = EthAccountComponent::Event::OwnerAdded(OwnerAdded { new_owner_guid }); + assert!(event == expected); // Check indexed keys - let indexed_keys = array![guid]; + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("OwnerAdded")); + indexed_keys.append_serde(new_owner_guid); utils::assert_indexed_keys(event, indexed_keys.span()); } @@ -600,12 +603,15 @@ fn assert_only_event_owner_added(contract: ContractAddress, public_key: EthPubli } fn assert_event_owner_removed(contract: ContractAddress, public_key: EthPublicKey) { - let event = utils::pop_log::(contract).unwrap(); - let guid = get_guid_from_public_key(public_key); - assert_eq!(event.removed_owner_guid, guid); + let event = utils::pop_log::(contract).unwrap(); + let removed_owner_guid = get_guid_from_public_key(public_key); + let expected = EthAccountComponent::Event::OwnerRemoved(OwnerRemoved { removed_owner_guid }); + assert!(event == expected); // Check indexed keys - let indexed_keys = array![guid]; + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("OwnerRemoved")); + indexed_keys.append_serde(removed_owner_guid); utils::assert_indexed_keys(event, indexed_keys.span()); } diff --git a/src/tests/account/test_secp256k1.cairo b/src/tests/account/test_secp256k1.cairo index e0bb327f5..eb4c42c14 100644 --- a/src/tests/account/test_secp256k1.cairo +++ b/src/tests/account/test_secp256k1.cairo @@ -59,6 +59,7 @@ fn test_unpack_big_secp256k1_points() { let (x, _) = StorePacking::unpack((xlow, xhigh_and_parity)).get_coordinates().unwrap_syscall(); assert_eq!(x, expected_x); + assert_eq!(y, expected_y); } #[test] diff --git a/src/tests/mocks.cairo b/src/tests/mocks.cairo index 99da97b15..f09324fb7 100644 --- a/src/tests/mocks.cairo +++ b/src/tests/mocks.cairo @@ -1,5 +1,7 @@ mod accesscontrol_mocks; mod account_mocks; +mod erc1155_mocks; +mod erc1155_receiver_mocks; mod erc20_mocks; mod erc721_mocks; mod erc721_receiver_mocks; diff --git a/src/tests/mocks/accesscontrol_mocks.cairo b/src/tests/mocks/accesscontrol_mocks.cairo index a2f4290d0..cf5b5c8ee 100644 --- a/src/tests/mocks/accesscontrol_mocks.cairo +++ b/src/tests/mocks/accesscontrol_mocks.cairo @@ -8,20 +8,12 @@ mod DualCaseAccessControlMock { component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; + // AccessControlMixin #[abi(embed_v0)] - impl AccessControlCamelImpl = - AccessControlComponent::AccessControlCamelImpl; + impl AccessControlMixinImpl = + AccessControlComponent::AccessControlMixinImpl; impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] struct Storage { #[substorage(v0)] @@ -148,37 +140,41 @@ mod SnakeAccessControlPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn has_role(self: @ContractState, role: felt252, account: ContractAddress) -> bool { - panic!("Some error"); - false - } - - #[external(v0)] - fn get_role_admin(self: @ContractState, role: felt252) -> felt252 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn grant_role(ref self: ContractState, role: felt252, account: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn revoke_role(ref self: ContractState, role: felt252, account: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn renounce_role(ref self: ContractState, role: felt252, account: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - panic!("Some error"); - false + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn has_role(self: @ContractState, role: felt252, account: ContractAddress) -> bool { + panic!("Some error"); + false + } + + #[external(v0)] + fn get_role_admin(self: @ContractState, role: felt252) -> felt252 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn grant_role(ref self: ContractState, role: felt252, account: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn revoke_role(ref self: ContractState, role: felt252, account: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn renounce_role(ref self: ContractState, role: felt252, account: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + panic!("Some error"); + false + } } } @@ -189,36 +185,40 @@ mod CamelAccessControlPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn hasRole(self: @ContractState, role: felt252, account: ContractAddress) -> bool { - panic!("Some error"); - false - } - - #[external(v0)] - fn getRoleAdmin(self: @ContractState, role: felt252) -> felt252 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn grantRole(ref self: ContractState, role: felt252, account: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn revokeRole(ref self: ContractState, role: felt252, account: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn renounceRole(ref self: ContractState, role: felt252, account: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - panic!("Some error"); - false + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn hasRole(self: @ContractState, role: felt252, account: ContractAddress) -> bool { + panic!("Some error"); + false + } + + #[external(v0)] + fn getRoleAdmin(self: @ContractState, role: felt252) -> felt252 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn grantRole(ref self: ContractState, role: felt252, account: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn revokeRole(ref self: ContractState, role: felt252, account: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn renounceRole(ref self: ContractState, role: felt252, account: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + panic!("Some error"); + false + } } } diff --git a/src/tests/mocks/account_mocks.cairo b/src/tests/mocks/account_mocks.cairo index efa7eaced..8a3dae2af 100644 --- a/src/tests/mocks/account_mocks.cairo +++ b/src/tests/mocks/account_mocks.cairo @@ -21,7 +21,6 @@ mod DualCaseAccountMock { #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; - #[storage] struct Storage { #[substorage(v0)] @@ -64,7 +63,6 @@ mod SnakeAccountMock { #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; - #[storage] struct Storage { #[substorage(v0)] @@ -109,7 +107,6 @@ mod CamelAccountMock { #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; - #[storage] struct Storage { #[substorage(v0)] @@ -158,29 +155,33 @@ mod SnakeAccountPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn set_public_key(ref self: ContractState, new_public_key: felt252) { - panic!("Some error"); - } + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn set_public_key(ref self: ContractState, new_public_key: felt252) { + panic!("Some error"); + } - #[external(v0)] - fn get_public_key(self: @ContractState) -> felt252 { - panic!("Some error"); - 3 - } + #[external(v0)] + fn get_public_key(self: @ContractState) -> felt252 { + panic!("Some error"); + 3 + } - #[external(v0)] - fn is_valid_signature( - self: @ContractState, hash: felt252, signature: Array - ) -> felt252 { - panic!("Some error"); - 3 - } + #[external(v0)] + fn is_valid_signature( + self: @ContractState, hash: felt252, signature: Array + ) -> felt252 { + panic!("Some error"); + 3 + } - #[external(v0)] - fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - panic!("Some error"); - false + #[external(v0)] + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + panic!("Some error"); + false + } } } @@ -189,26 +190,32 @@ mod CamelAccountPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn setPublicKey(ref self: ContractState, newPublicKey: felt252) { - panic!("Some error"); - } + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn setPublicKey(ref self: ContractState, newPublicKey: felt252) { + panic!("Some error"); + } - #[external(v0)] - fn getPublicKey(self: @ContractState) -> felt252 { - panic!("Some error"); - 3 - } + #[external(v0)] + fn getPublicKey(self: @ContractState) -> felt252 { + panic!("Some error"); + 3 + } - #[external(v0)] - fn isValidSignature(self: @ContractState, hash: felt252, signature: Array) -> felt252 { - panic!("Some error"); - 3 - } + #[external(v0)] + fn isValidSignature( + self: @ContractState, hash: felt252, signature: Array + ) -> felt252 { + panic!("Some error"); + 3 + } - #[external(v0)] - fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - panic!("Some error"); - false + #[external(v0)] + fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + panic!("Some error"); + false + } } } diff --git a/src/tests/mocks/erc1155_mocks.cairo b/src/tests/mocks/erc1155_mocks.cairo new file mode 100644 index 000000000..976050426 --- /dev/null +++ b/src/tests/mocks/erc1155_mocks.cairo @@ -0,0 +1,308 @@ +#[starknet::contract] +mod DualCaseERC1155Mock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155Component; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 + #[abi(embed_v0)] + impl ERC1155Impl = ERC1155Component::ERC1155Impl; + #[abi(embed_v0)] + impl ERC1155MetadataURIImpl = + ERC1155Component::ERC1155MetadataURIImpl; + #[abi(embed_v0)] + impl ERC721Camel = ERC1155Component::ERC1155CamelImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256, + value: u256 + ) { + self.erc1155.initializer(base_uri); + self.erc1155.mint_with_acceptance_check(recipient, token_id, value, array![].span()); + } +} + +#[starknet::contract] +mod SnakeERC1155Mock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155Component; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 + #[abi(embed_v0)] + impl ERC1155Impl = ERC1155Component::ERC1155Impl; + #[abi(embed_v0)] + impl ERC1155MetadataURIImpl = + ERC1155Component::ERC1155MetadataURIImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256, + value: u256 + ) { + self.erc1155.initializer(base_uri); + self.erc1155.mint_with_acceptance_check(recipient, token_id, value, array![].span()); + } +} + +#[starknet::contract] +mod CamelERC1155Mock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155Component; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 + #[abi(embed_v0)] + impl ERC1155Camel = ERC1155Component::ERC1155CamelImpl; + #[abi(embed_v0)] + impl ERC1155MetadataURIImpl = + ERC1155Component::ERC1155MetadataURIImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256, + value: u256 + ) { + self.erc1155.initializer(base_uri); + self.erc1155.mint_with_acceptance_check(recipient, token_id, value, array![].span()); + } +} + +#[starknet::contract] +mod SnakeERC1155PanicMock { + use starknet::ContractAddress; + use zeroable::Zeroable; + + #[storage] + struct Storage {} + + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn uri(self: @ContractState, token_id: u256) -> ByteArray { + panic!("Some error"); + "3" + } + + #[external(v0)] + fn balance_of(self: @ContractState, account: ContractAddress, token_id: u256) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + + #[external(v0)] + fn balance_of_batch( + self: @ContractState, accounts: Span, token_ids: Span + ) -> Span { + panic!("Some error"); + array![u256 { low: 3, high: 3 }].span() + } + + #[external(v0)] + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) { + panic!("Some error"); + } + + #[external(v0)] + fn safe_batch_transfer_from( + ref self: ContractState, + from: starknet::ContractAddress, + to: starknet::ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) { + panic!("Some error"); + } + + #[external(v0)] + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + panic!("Some error"); + false + } + + #[external(v0)] + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + panic!("Some error"); + } + + #[external(v0)] + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + panic!("Some error"); + false + } + } +} + +#[starknet::contract] +mod CamelERC1155PanicMock { + use starknet::ContractAddress; + use zeroable::Zeroable; + + #[storage] + struct Storage {} + + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn uri(self: @ContractState, tokenId: u256) -> ByteArray { + panic!("Some error"); + "3" + } + + #[external(v0)] + fn balanceOf(self: @ContractState, account: ContractAddress, tokenId: u256) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + + #[external(v0)] + fn balanceOfBatch( + self: @ContractState, accounts: Span, token_ids: Span + ) -> Span { + panic!("Some error"); + array![u256 { low: 3, high: 3 }].span() + } + + #[external(v0)] + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ) { + panic!("Some error"); + } + + #[external(v0)] + fn safeBatchTransferFrom( + ref self: ContractState, + from: starknet::ContractAddress, + to: starknet::ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) { + panic!("Some error"); + } + + #[external(v0)] + fn isApprovedForAll( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + panic!("Some error"); + false + } + + #[external(v0)] + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + panic!("Some error"); + } + + #[external(v0)] + fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + panic!("Some error"); + false + } + } +} diff --git a/src/tests/mocks/erc1155_receiver_mocks.cairo b/src/tests/mocks/erc1155_receiver_mocks.cairo new file mode 100644 index 000000000..b4bad6446 --- /dev/null +++ b/src/tests/mocks/erc1155_receiver_mocks.cairo @@ -0,0 +1,204 @@ +use openzeppelin::tests::utils::constants::SUCCESS; + +#[starknet::contract] +mod DualCaseERC1155ReceiverMock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155ReceiverComponent; + use starknet::ContractAddress; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!( + path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent + ); + + // ERC1155Receiver + #[abi(embed_v0)] + impl ERC1155ReceiverImpl = + ERC1155ReceiverComponent::ERC1155ReceiverImpl; + #[abi(embed_v0)] + impl ERC1155ReceiverCamelImpl = + ERC1155ReceiverComponent::ERC1155ReceiverCamelImpl; + impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155_receiver: ERC1155ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc1155_receiver.initializer(); + } +} + +#[starknet::contract] +mod SnakeERC1155ReceiverMock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155ReceiverComponent; + use starknet::ContractAddress; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!( + path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent + ); + + // ERC1155Receiver + #[abi(embed_v0)] + impl ERC1155ReceiverImpl = + ERC1155ReceiverComponent::ERC1155ReceiverImpl; + impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155_receiver: ERC1155ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc1155_receiver.initializer(); + } +} + +#[starknet::contract] +mod CamelERC1155ReceiverMock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::ERC1155ReceiverComponent; + use starknet::ContractAddress; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!( + path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent + ); + + // ERC1155Receiver + #[abi(embed_v0)] + impl ERC1155ReceiverCamelImpl = + ERC1155ReceiverComponent::ERC1155ReceiverCamelImpl; + impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155_receiver: ERC1155ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc1155_receiver.initializer(); + } +} + +#[starknet::contract] +mod SnakeERC1155ReceiverPanicMock { + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[external(v0)] + fn on_erc1155_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn on_erc1155_batch_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ) -> felt252 { + panic!("Some error"); + 3 + } +} + +#[starknet::contract] +mod CamelERC1155ReceiverPanicMock { + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[external(v0)] + fn onERC1155Received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ) -> felt252 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn onERC1155BatchReceived( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ) -> felt252 { + panic!("Some error"); + 3 + } +} diff --git a/src/tests/mocks/erc20_mocks.cairo b/src/tests/mocks/erc20_mocks.cairo index d3d9694b7..96fdeeb3f 100644 --- a/src/tests/mocks/erc20_mocks.cairo +++ b/src/tests/mocks/erc20_mocks.cairo @@ -29,8 +29,8 @@ mod DualCaseERC20Mock { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, initial_supply: u256, recipient: ContractAddress ) { @@ -68,8 +68,8 @@ mod SnakeERC20Mock { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, initial_supply: u256, recipient: ContractAddress ) { @@ -111,8 +111,8 @@ mod CamelERC20Mock { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, initial_supply: u256, recipient: ContractAddress ) { @@ -120,19 +120,25 @@ mod CamelERC20Mock { self.erc20._mint(recipient, initial_supply); } - #[external(v0)] - fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 { - self.erc20.allowance(owner, spender) - } + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.erc20.allowance(owner, spender) + } - #[external(v0)] - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - self.erc20.transfer(recipient, amount) - } + #[external(v0)] + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + self.erc20.transfer(recipient, amount) + } - #[external(v0)] - fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { - self.erc20.approve(spender, amount) + #[external(v0)] + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + self.erc20.approve(spender, amount) + } } } @@ -149,60 +155,66 @@ mod SnakeERC20Panic { #[storage] struct Storage {} - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn symbol(self: @ContractState) -> felt252 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn decimals(self: @ContractState) -> u8 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - panic!("Some error"); - false - } - - #[external(v0)] - fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) -> bool { - panic!("Some error"); - false - } - - #[external(v0)] - fn total_supply(self: @ContractState) -> u256 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn transfer_from( - ref self: ContractState, from: ContractAddress, to: ContractAddress, amount: u256 - ) -> bool { - panic!("Some error"); - false + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn name(self: @ContractState) -> ByteArray { + panic!("Some error"); + "3" + } + + #[external(v0)] + fn symbol(self: @ContractState) -> ByteArray { + panic!("Some error"); + "3" + } + + #[external(v0)] + fn decimals(self: @ContractState) -> u8 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + panic!("Some error"); + false + } + + #[external(v0)] + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) -> bool { + panic!("Some error"); + false + } + + #[external(v0)] + fn total_supply(self: @ContractState) -> u256 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, amount: u256 + ) -> bool { + panic!("Some error"); + false + } } } @@ -213,22 +225,29 @@ mod CamelERC20Panic { #[storage] struct Storage {} - #[external(v0)] - fn totalSupply(self: @ContractState) -> u256 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { - panic!("Some error"); - 3 - } - - #[external(v0)] - fn transferFrom( - ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) { - panic!("Some error"); + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn totalSupply(self: @ContractState) -> u256 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + panic!("Some error"); + 3 + } + + #[external(v0)] + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + panic!("Some error"); + } } } diff --git a/src/tests/mocks/erc721_mocks.cairo b/src/tests/mocks/erc721_mocks.cairo index 241e7ddc0..0d33028bd 100644 --- a/src/tests/mocks/erc721_mocks.cairo +++ b/src/tests/mocks/erc721_mocks.cairo @@ -43,15 +43,14 @@ mod DualCaseERC721Mock { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, recipient: ContractAddress, - token_id: u256, - uri: felt252 + token_id: u256 ) { - self.erc721.initializer(name, symbol); + self.erc721.initializer(name, symbol, base_uri); self.erc721._mint(recipient, token_id); - self.erc721._set_token_uri(token_id, uri); } } @@ -95,15 +94,14 @@ mod SnakeERC721Mock { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, recipient: ContractAddress, - token_id: u256, - uri: felt252 + token_id: u256 ) { - self.erc721.initializer(name, symbol); + self.erc721.initializer(name, symbol, base_uri); self.erc721._mint(recipient, token_id); - self.erc721._set_token_uri(token_id, uri); } } @@ -149,21 +147,20 @@ mod CamelERC721Mock { #[constructor] fn constructor( ref self: ContractState, - name: felt252, - symbol: felt252, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, recipient: ContractAddress, - token_id: u256, - uri: felt252 + token_id: u256 ) { - self.erc721.initializer(name, symbol); + self.erc721.initializer(name, symbol, base_uri); self.erc721._mint(recipient, token_id); - self.erc721._set_token_uri(token_id, uri); } /// The following external methods are included because they are case-agnostic /// and this contract should not embed the snake_case impl. - #[generate_trait] #[abi(per_item)] + #[generate_trait] impl ExternalImpl of ExternalTrait { #[external(v0)] fn approve(ref self: ContractState, to: ContractAddress, tokenId: u256) { @@ -171,12 +168,12 @@ mod CamelERC721Mock { } #[external(v0)] - fn name(self: @ContractState) -> felt252 { + fn name(self: @ContractState) -> ByteArray { self.erc721.name() } #[external(v0)] - fn symbol(self: @ContractState) -> felt252 { + fn symbol(self: @ContractState) -> ByteArray { self.erc721.symbol() } } @@ -196,19 +193,19 @@ mod SnakeERC721PanicMock { #[storage] struct Storage {} - #[generate_trait] #[abi(per_item)] + #[generate_trait] impl ExternalImpl of ExternalTrait { #[external(v0)] - fn name(self: @ContractState) -> felt252 { + fn name(self: @ContractState) -> ByteArray { panic!("Some error"); - 3 + "3" } #[external(v0)] - fn symbol(self: @ContractState) -> felt252 { + fn symbol(self: @ContractState) -> ByteArray { panic!("Some error"); - 3 + "3" } #[external(v0)] @@ -289,8 +286,8 @@ mod CamelERC721PanicMock { #[storage] struct Storage {} - #[generate_trait] #[abi(per_item)] + #[generate_trait] impl ExternalImpl of ExternalTrait { #[external(v0)] fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { diff --git a/src/tests/mocks/erc721_receiver_mocks.cairo b/src/tests/mocks/erc721_receiver_mocks.cairo index ed54696e1..a940c01c3 100644 --- a/src/tests/mocks/erc721_receiver_mocks.cairo +++ b/src/tests/mocks/erc721_receiver_mocks.cairo @@ -39,30 +39,34 @@ mod DualCaseERC721ReceiverMock { self.erc721_receiver.initializer(); } - #[external(v0)] - fn on_erc721_received( - self: @ContractState, - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - data: Span - ) -> felt252 { - if *data.at(0) == super::SUCCESS { - self.erc721_receiver.on_erc721_received(operator, from, token_id, data) - } else { - 0 + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn on_erc721_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252 { + if *data.at(0) == super::SUCCESS { + self.erc721_receiver.on_erc721_received(operator, from, token_id, data) + } else { + 0 + } } - } - #[external(v0)] - fn onERC721Received( - self: @ContractState, - operator: ContractAddress, - from: ContractAddress, - tokenId: u256, - data: Span - ) -> felt252 { - self.on_erc721_received(operator, from, tokenId, data) + #[external(v0)] + fn onERC721Received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span + ) -> felt252 { + ExternalTrait::on_erc721_received(self, operator, from, tokenId, data) + } } } @@ -105,18 +109,22 @@ mod SnakeERC721ReceiverMock { self.erc721_receiver.initializer(); } - #[external(v0)] - fn on_erc721_received( - self: @ContractState, - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - data: Span - ) -> felt252 { - if *data.at(0) == super::SUCCESS { - self.erc721_receiver.on_erc721_received(operator, from, token_id, data) - } else { - 0 + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn on_erc721_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252 { + if *data.at(0) == super::SUCCESS { + self.erc721_receiver.on_erc721_received(operator, from, token_id, data) + } else { + 0 + } } } } @@ -160,18 +168,22 @@ mod CamelERC721ReceiverMock { self.erc721_receiver.initializer(); } - #[external(v0)] - fn onERC721Received( - self: @ContractState, - operator: ContractAddress, - from: ContractAddress, - tokenId: u256, - data: Span - ) -> felt252 { - if *data.at(0) == super::SUCCESS { - self.erc721_receiver.onERC721Received(operator, from, tokenId, data) - } else { - 0 + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn onERC721Received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span + ) -> felt252 { + if *data.at(0) == super::SUCCESS { + self.erc721_receiver.onERC721Received(operator, from, tokenId, data) + } else { + 0 + } } } } @@ -183,16 +195,20 @@ mod SnakeERC721ReceiverPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn on_erc721_received( - self: @ContractState, - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - data: Span - ) -> felt252 { - panic!("Some error"); - 3 + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn on_erc721_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252 { + panic!("Some error"); + 3 + } } } @@ -203,16 +219,19 @@ mod CamelERC721ReceiverPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn onERC721Received( - self: @ContractState, - operator: ContractAddress, - from: ContractAddress, - tokenId: u256, - data: Span - ) -> felt252 { - panic!("Some error"); - 3 + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn onERC721Received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span + ) -> felt252 { + panic!("Some error"); + 3 + } } } - diff --git a/src/tests/mocks/eth_account_mocks.cairo b/src/tests/mocks/eth_account_mocks.cairo index b1f35733c..91724eb41 100644 --- a/src/tests/mocks/eth_account_mocks.cairo +++ b/src/tests/mocks/eth_account_mocks.cairo @@ -127,8 +127,8 @@ mod CamelEthAccountMock { self.eth_account.initializer(publicKey); } - #[generate_trait] #[abi(per_item)] + #[generate_trait] impl ExternalImpl of ExternalTrait { #[external(v0)] fn __execute__(self: @ContractState, mut calls: Array) -> Array> { @@ -158,29 +158,33 @@ mod SnakeEthAccountPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn set_public_key(ref self: ContractState, new_public_key: EthPublicKey) { - panic!("Some error"); - } + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn set_public_key(ref self: ContractState, new_public_key: EthPublicKey) { + panic!("Some error"); + } - #[external(v0)] - fn get_public_key(self: @ContractState) -> EthPublicKey { - panic!("Some error"); - secp256k1_new_syscall(3, 3).unwrap_syscall().unwrap() - } + #[external(v0)] + fn get_public_key(self: @ContractState) -> EthPublicKey { + panic!("Some error"); + secp256k1_new_syscall(3, 3).unwrap_syscall().unwrap() + } - #[external(v0)] - fn is_valid_signature( - self: @ContractState, hash: felt252, signature: Array - ) -> felt252 { - panic!("Some error"); - 3 - } + #[external(v0)] + fn is_valid_signature( + self: @ContractState, hash: felt252, signature: Array + ) -> felt252 { + panic!("Some error"); + 3 + } - #[external(v0)] - fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - panic!("Some error"); - false + #[external(v0)] + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + panic!("Some error"); + false + } } } @@ -194,26 +198,32 @@ mod CamelEthAccountPanicMock { #[storage] struct Storage {} - #[external(v0)] - fn setPublicKey(ref self: ContractState, newPublicKey: EthPublicKey) { - panic!("Some error"); - } + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn setPublicKey(ref self: ContractState, newPublicKey: EthPublicKey) { + panic!("Some error"); + } - #[external(v0)] - fn getPublicKey(self: @ContractState) -> EthPublicKey { - panic!("Some error"); - secp256k1_new_syscall(3, 3).unwrap_syscall().unwrap() - } + #[external(v0)] + fn getPublicKey(self: @ContractState) -> EthPublicKey { + panic!("Some error"); + secp256k1_new_syscall(3, 3).unwrap_syscall().unwrap() + } - #[external(v0)] - fn isValidSignature(self: @ContractState, hash: felt252, signature: Array) -> felt252 { - panic!("Some error"); - 3 - } + #[external(v0)] + fn isValidSignature( + self: @ContractState, hash: felt252, signature: Array + ) -> felt252 { + panic!("Some error"); + 3 + } - #[external(v0)] - fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - panic!("Some error"); - false + #[external(v0)] + fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + panic!("Some error"); + false + } } } diff --git a/src/tests/mocks/ownable_mocks.cairo b/src/tests/mocks/ownable_mocks.cairo index c0a8f5fe7..400e95178 100644 --- a/src/tests/mocks/ownable_mocks.cairo +++ b/src/tests/mocks/ownable_mocks.cairo @@ -6,10 +6,7 @@ mod DualCaseOwnableMock { component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] - impl OwnableImpl = OwnableComponent::OwnableImpl; - #[abi(embed_v0)] - impl OwnableCamelOnlyImpl = - OwnableComponent::OwnableCamelOnlyImpl; + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; impl InternalImpl = OwnableComponent::InternalImpl; #[storage] @@ -91,9 +88,13 @@ mod CamelOwnableMock { self.ownable.initializer(owner); } - #[external(v0)] - fn owner(self: @ContractState) -> ContractAddress { - self.ownable.Ownable_owner.read() + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn owner(self: @ContractState) -> ContractAddress { + self.ownable.Ownable_owner.read() + } } } @@ -105,20 +106,24 @@ mod SnakeOwnablePanicMock { #[storage] struct Storage {} - #[external(v0)] - fn owner(self: @ContractState) -> ContractAddress { - panic!("Some error"); - Zeroable::zero() - } - - #[external(v0)] - fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn renounce_ownership(ref self: ContractState) { - panic!("Some error"); + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn owner(self: @ContractState) -> ContractAddress { + panic!("Some error"); + Zeroable::zero() + } + + #[external(v0)] + fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn renounce_ownership(ref self: ContractState) { + panic!("Some error"); + } } } @@ -129,20 +134,24 @@ mod CamelOwnablePanicMock { #[storage] struct Storage {} - #[external(v0)] - fn owner(self: @ContractState) -> ContractAddress { - panic!("Some error"); - Zeroable::zero() - } - - #[external(v0)] - fn transferOwnership(ref self: ContractState, newOwner: ContractAddress) { - panic!("Some error"); - } - - #[external(v0)] - fn renounceOwnership(ref self: ContractState) { - panic!("Some error"); + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn owner(self: @ContractState) -> ContractAddress { + panic!("Some error"); + Zeroable::zero() + } + + #[external(v0)] + fn transferOwnership(ref self: ContractState, newOwner: ContractAddress) { + panic!("Some error"); + } + + #[external(v0)] + fn renounceOwnership(ref self: ContractState) { + panic!("Some error"); + } } } @@ -154,10 +163,8 @@ mod DualCaseTwoStepOwnableMock { component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] - impl OwnableTwoStepImpl = OwnableComponent::OwnableTwoStepImpl; - #[abi(embed_v0)] - impl OwnableTwoStepCamelOnlyImpl = - OwnableComponent::OwnableTwoStepCamelOnlyImpl; + impl OwnableTwoStepMixinImpl = + OwnableComponent::OwnableTwoStepMixinImpl; impl InternalImpl = OwnableComponent::InternalImpl; #[storage] diff --git a/src/tests/presets.cairo b/src/tests/presets.cairo index b8b31ff53..87e7cb2f4 100644 --- a/src/tests/presets.cairo +++ b/src/tests/presets.cairo @@ -1,4 +1,5 @@ mod test_account; +mod test_erc1155; mod test_erc20; mod test_erc721; mod test_eth_account; diff --git a/src/tests/presets/test_account.cairo b/src/tests/presets/test_account.cairo index 9b9cd3db7..a15075f48 100644 --- a/src/tests/presets/test_account.cairo +++ b/src/tests/presets/test_account.cairo @@ -65,13 +65,13 @@ fn test_constructor() { assert_only_event_owner_added(ZERO(), PUBKEY); - let public_key = Account::PublicKeyImpl::get_public_key(@state); + let public_key = Account::AccountMixinImpl::get_public_key(@state); assert_eq!(public_key, PUBKEY); - let supports_isrc5 = Account::SRC5Impl::supports_interface(@state, ISRC5_ID); + let supports_isrc5 = Account::AccountMixinImpl::supports_interface(@state, ISRC5_ID); assert!(supports_isrc5); - let supports_isrc6 = Account::SRC5Impl::supports_interface(@state, ISRC6_ID); + let supports_isrc6 = Account::AccountMixinImpl::supports_interface(@state, ISRC6_ID); assert!(supports_isrc6); } @@ -182,10 +182,10 @@ fn test_isValidSignature_bad_sig() { fn test_supports_interface() { let dispatcher = setup_dispatcher(); let supports_isrc5 = dispatcher.supports_interface(ISRC5_ID); - let supports_isrc6 = dispatcher.supports_interface(ISRC6_ID); - let doesnt_support_0x123 = !dispatcher.supports_interface(0x123); assert!(supports_isrc5); + let supports_isrc6 = dispatcher.supports_interface(ISRC6_ID); assert!(supports_isrc6); + let doesnt_support_0x123 = !dispatcher.supports_interface(0x123); assert!(doesnt_support_0x123); } diff --git a/src/tests/presets/test_erc1155.cairo b/src/tests/presets/test_erc1155.cairo new file mode 100644 index 000000000..1b2b5a587 --- /dev/null +++ b/src/tests/presets/test_erc1155.cairo @@ -0,0 +1,726 @@ +use openzeppelin::introspection; +use openzeppelin::presets::ERC1155; +use openzeppelin::tests::token::test_erc1155::{ + assert_only_event_transfer_single, assert_only_event_transfer_batch, + assert_only_event_approval_for_all +}; +use openzeppelin::tests::token::test_erc1155::{ + setup_account, setup_receiver, setup_camel_receiver, setup_account_with_salt, setup_src5 +}; +use openzeppelin::tests::token::test_erc1155::{get_ids_and_values, get_ids_and_split_values}; +use openzeppelin::tests::utils::constants::{ + EMPTY_DATA, ZERO, OWNER, OPERATOR, OTHER, TOKEN_ID, TOKEN_ID_2, TOKEN_VALUE, TOKEN_VALUE_2 +}; +use openzeppelin::tests::utils; +use openzeppelin::token::erc1155::interface::{ERC1155ABIDispatcher, ERC1155ABIDispatcherTrait}; +use openzeppelin::token::erc1155; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; +use starknet::testing; + +// +// Setup +// + +fn setup_dispatcher_with_event() -> (ERC1155ABIDispatcher, ContractAddress) { + let uri: ByteArray = "URI"; + let mut calldata = array![]; + let mut token_ids = array![TOKEN_ID, TOKEN_ID_2]; + let mut values = array![TOKEN_VALUE, TOKEN_VALUE_2]; + + let owner = setup_account(); + testing::set_contract_address(owner); + + calldata.append_serde(uri); + calldata.append_serde(owner); + calldata.append_serde(token_ids); + calldata.append_serde(values); + + let address = utils::deploy(ERC1155::TEST_CLASS_HASH, calldata); + (ERC1155ABIDispatcher { contract_address: address }, owner) +} + +fn setup_dispatcher() -> (ERC1155ABIDispatcher, ContractAddress) { + let (dispatcher, owner) = setup_dispatcher_with_event(); + utils::drop_event(dispatcher.contract_address); + (dispatcher, owner) +} + +// +// constructor +// + +#[test] +fn test_constructor() { + let (dispatcher, owner) = setup_dispatcher_with_event(); + + assert_eq!(dispatcher.uri(TOKEN_ID), "URI"); + assert_eq!(dispatcher.balance_of(owner, TOKEN_ID), TOKEN_VALUE); + assert_eq!(dispatcher.balance_of(owner, TOKEN_ID_2), TOKEN_VALUE_2); + + let supports_ierc1155 = dispatcher.supports_interface(erc1155::interface::IERC1155_ID); + assert!(supports_ierc1155); + + let supports_ierc1155_metadata_uri = dispatcher + .supports_interface(erc1155::interface::IERC1155_METADATA_URI_ID); + assert!(supports_ierc1155_metadata_uri); + + let supports_isrc5 = dispatcher.supports_interface(introspection::interface::ISRC5_ID); + assert!(supports_isrc5); +} + +// +// balance_of & balanceOf +// + +#[test] +fn test_balance_of() { + let (dispatcher, owner) = setup_dispatcher(); + + let balance = dispatcher.balance_of(owner, TOKEN_ID); + assert_eq!(balance, TOKEN_VALUE); +} + +#[test] +fn test_balanceOf() { + let (dispatcher, owner) = setup_dispatcher(); + + let balance = dispatcher.balanceOf(owner, TOKEN_ID); + assert_eq!(balance, TOKEN_VALUE); +} + +// +// balance_of_batch & balanceOfBatch +// + +#[test] +fn test_balance_of_batch() { + let (dispatcher, owner) = setup_dispatcher(); + + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID, TOKEN_ID].span(); + + let balances = dispatcher.balance_of_batch(accounts, token_ids); + assert_eq!(*balances.at(0), TOKEN_VALUE); + assert!((*balances.at(1)).is_zero()); +} + +#[test] +fn test_balanceOfBatch() { + let (dispatcher, owner) = setup_dispatcher(); + + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID, TOKEN_ID].span(); + + let balances = dispatcher.balanceOfBatch(accounts, token_ids); + assert_eq!(*balances.at(0), TOKEN_VALUE); + assert!((*balances.at(1)).is_zero()); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length', 'ENTRYPOINT_FAILED'))] +fn test_balance_of_batch_invalid_inputs() { + let (dispatcher, owner) = setup_dispatcher(); + + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID].span(); + + dispatcher.balance_of_batch(accounts, token_ids); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length', 'ENTRYPOINT_FAILED'))] +fn test_balanceOfBatch_invalid_inputs() { + let (dispatcher, owner) = setup_dispatcher(); + + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID].span(); + + dispatcher.balanceOfBatch(accounts, token_ids); +} + +// +// safe_transfer_from & safeTransferFrom +// + +#[test] +fn test_safe_transfer_from_to_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_receiver(); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + dispatcher.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safe_transfer_from_to_camel_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_camel_receiver(); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + dispatcher.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_to_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_receiver(); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + dispatcher.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_to_camel_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_camel_receiver(); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + dispatcher.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safe_transfer_from_to_account() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + dispatcher.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_to_account() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + dispatcher.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safe_transfer_from_approved_operator() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + + dispatcher.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(contract, owner, operator, true); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + testing::set_contract_address(operator); + dispatcher.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, operator, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_approved_operator() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + + dispatcher.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(contract, owner, operator, true); + + assert_state_before_transfer_single(dispatcher, owner, recipient, TOKEN_ID); + + testing::set_contract_address(operator); + dispatcher.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(contract, operator, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(dispatcher, owner, recipient, TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender', 'ENTRYPOINT_FAILED'))] +fn test_safe_transfer_from_from_zero() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safe_transfer_from(ZERO(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender', 'ENTRYPOINT_FAILED'))] +fn test_safeTransferFrom_from_zero() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safeTransferFrom(ZERO(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver', 'ENTRYPOINT_FAILED'))] +fn test_safe_transfer_from_to_zero() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safe_transfer_from(owner, ZERO(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver', 'ENTRYPOINT_FAILED'))] +fn test_safeTransferFrom_to_zero() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safeTransferFrom(owner, ZERO(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator', 'ENTRYPOINT_FAILED'))] +fn test_safe_transfer_from_unauthorized() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safe_transfer_from(OTHER(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator', 'ENTRYPOINT_FAILED'))] +fn test_safeTransferFrom_unauthorized() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safeTransferFrom(OTHER(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance', 'ENTRYPOINT_FAILED'))] +fn test_safe_transfer_from_insufficient_balance() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safe_transfer_from(owner, OTHER(), TOKEN_ID, TOKEN_VALUE + 1, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance', 'ENTRYPOINT_FAILED'))] +fn test_safeTransferFrom_insufficient_balance() { + let (dispatcher, owner) = setup_dispatcher(); + + dispatcher.safeTransferFrom(owner, OTHER(), TOKEN_ID, TOKEN_VALUE + 1, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed', 'ENTRYPOINT_FAILED'))] +fn test_safe_transfer_from_non_account_non_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let non_receiver = setup_src5(); + + dispatcher.safe_transfer_from(owner, non_receiver, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed', 'ENTRYPOINT_FAILED'))] +fn test_safeTransferFrom_non_account_non_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let non_receiver = setup_src5(); + + dispatcher.safeTransferFrom(owner, non_receiver, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +// +// safe_batch_transfer_from & safeBatchTransferFrom +// + +#[test] +fn test_safe_batch_transfer_from_to_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_receiver(); + let (token_ids, values) = get_ids_and_values(); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + dispatcher.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +fn test_safe_batch_transfer_from_to_camel_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_camel_receiver(); + let (token_ids, values) = get_ids_and_values(); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + dispatcher.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_to_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_receiver(); + let (token_ids, values) = get_ids_and_values(); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + dispatcher.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_to_camel_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_camel_receiver(); + let (token_ids, values) = get_ids_and_values(); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + dispatcher.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +fn test_safe_batch_transfer_from_to_account() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + let (token_ids, values) = get_ids_and_values(); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + dispatcher.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_to_account() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + let (token_ids, values) = get_ids_and_values(); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + dispatcher.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + + +#[test] +fn test_safe_batch_transfer_from_approved_operator() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(contract, owner, operator, true); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + + testing::set_contract_address(operator); + dispatcher.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, operator, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_approved_operator() { + let (dispatcher, owner) = setup_dispatcher(); + let contract = dispatcher.contract_address; + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(contract, owner, operator, true); + + assert_state_before_transfer_batch(dispatcher, owner, recipient, token_ids, values); + testing::set_contract_address(operator); + dispatcher.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(contract, operator, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(dispatcher, owner, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender', 'ENTRYPOINT_FAILED'))] +fn test_safe_batch_transfer_from_from_zero() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.safe_batch_transfer_from(ZERO(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender', 'ENTRYPOINT_FAILED'))] +fn test_safeBatchTransferFrom_from_zero() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.safeBatchTransferFrom(ZERO(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver', 'ENTRYPOINT_FAILED'))] +fn test_safe_batch_transfer_from_to_zero() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.safe_batch_transfer_from(owner, ZERO(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver', 'ENTRYPOINT_FAILED'))] +fn test_safeBatchTransferFrom_to_zero() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.safeBatchTransferFrom(owner, ZERO(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator', 'ENTRYPOINT_FAILED'))] +fn test_safe_batch_transfer_from_unauthorized() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.safe_batch_transfer_from(OTHER(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator', 'ENTRYPOINT_FAILED'))] +fn test_safeBatchTransferFrom_unauthorized() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_values(); + + dispatcher.safeBatchTransferFrom(OTHER(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance', 'ENTRYPOINT_FAILED'))] +fn test_safe_batch_transfer_from_insufficient_balance() { + let (dispatcher, owner) = setup_dispatcher(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE + 1, TOKEN_VALUE_2].span(); + + dispatcher.safe_batch_transfer_from(owner, OTHER(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance', 'ENTRYPOINT_FAILED'))] +fn test_safeBatchTransferFrom_insufficient_balance() { + let (dispatcher, owner) = setup_dispatcher(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE + 1, TOKEN_VALUE_2].span(); + + dispatcher.safeBatchTransferFrom(owner, OTHER(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed', 'ENTRYPOINT_FAILED'))] +fn test_safe_batch_transfer_from_non_account_non_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_split_values(5); + let non_receiver = setup_src5(); + + dispatcher.safe_batch_transfer_from(owner, non_receiver, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed', 'ENTRYPOINT_FAILED'))] +fn test_safeBatchTransferFrom_non_account_non_receiver() { + let (dispatcher, owner) = setup_dispatcher(); + let (token_ids, values) = get_ids_and_split_values(5); + let non_receiver = setup_src5(); + + dispatcher.safeBatchTransferFrom(owner, non_receiver, token_ids, values, EMPTY_DATA()); +} + +// +// set_approval_for_all & is_approved_for_all +// + +#[test] +fn test_set_approval_for_all_and_is_approved_for_all() { + let (dispatcher, _) = setup_dispatcher(); + let contract = dispatcher.contract_address; + testing::set_contract_address(OWNER()); + + let not_approved_for_all = !dispatcher.is_approved_for_all(OWNER(), OPERATOR()); + assert!(not_approved_for_all); + + dispatcher.set_approval_for_all(OPERATOR(), true); + assert_only_event_approval_for_all(contract, OWNER(), OPERATOR(), true); + + let is_approved_for_all = dispatcher.is_approved_for_all(OWNER(), OPERATOR()); + assert!(is_approved_for_all); + + dispatcher.set_approval_for_all(OPERATOR(), false); + assert_only_event_approval_for_all(contract, OWNER(), OPERATOR(), false); + + let not_approved_for_all = !dispatcher.is_approved_for_all(OWNER(), OPERATOR()); + assert!(not_approved_for_all); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval', 'ENTRYPOINT_FAILED'))] +fn test_set_approval_for_all_owner_equal_operator_true() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.set_approval_for_all(OWNER(), true); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval', 'ENTRYPOINT_FAILED'))] +fn test_set_approval_for_all_owner_equal_operator_false() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.set_approval_for_all(OWNER(), false); +} + +// +// setApprovalForAll & isApprovedForAll +// + +#[test] +fn test_setApprovalForAll_and_isApprovedForAll() { + let (dispatcher, _) = setup_dispatcher(); + let contract = dispatcher.contract_address; + testing::set_contract_address(OWNER()); + + let not_approved_for_all = !dispatcher.isApprovedForAll(OWNER(), OPERATOR()); + assert!(not_approved_for_all); + + dispatcher.setApprovalForAll(OPERATOR(), true); + assert_only_event_approval_for_all(contract, OWNER(), OPERATOR(), true); + + let is_approved_for_all = dispatcher.isApprovedForAll(OWNER(), OPERATOR()); + assert!(is_approved_for_all); + + dispatcher.setApprovalForAll(OPERATOR(), false); + assert_only_event_approval_for_all(contract, OWNER(), OPERATOR(), false); + + let not_approved_for_all = !dispatcher.isApprovedForAll(OWNER(), OPERATOR()); + assert!(not_approved_for_all); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval', 'ENTRYPOINT_FAILED'))] +fn test_setApprovalForAll_owner_equal_operator_true() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.set_approval_for_all(OWNER(), true); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval', 'ENTRYPOINT_FAILED'))] +fn test_setApprovalForAll_owner_equal_operator_false() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.setApprovalForAll(OWNER(), false); +} + +// +// Helpers +// + +fn assert_state_before_transfer_single( + dispatcher: ERC1155ABIDispatcher, + sender: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert_eq!(dispatcher.balance_of(sender, token_id), TOKEN_VALUE); + assert!(dispatcher.balance_of(recipient, token_id).is_zero()); +} + +fn assert_state_after_transfer_single( + dispatcher: ERC1155ABIDispatcher, + sender: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert!(dispatcher.balance_of(sender, token_id).is_zero()); + assert_eq!(dispatcher.balance_of(recipient, token_id), TOKEN_VALUE); +} + +fn assert_state_before_transfer_batch( + dispatcher: ERC1155ABIDispatcher, + sender: ContractAddress, + recipient: ContractAddress, + token_ids: Span, + values: Span +) { + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = dispatcher.balance_of(sender, *token_ids.at(index)); + assert_eq!(balance_of_sender, *values.at(index)); + let balance_of_recipient = dispatcher.balance_of(recipient, *token_ids.at(index)); + assert!(balance_of_recipient.is_zero()); + + index += 1; + } +} + +fn assert_state_after_transfer_batch( + dispatcher: ERC1155ABIDispatcher, + sender: ContractAddress, + recipient: ContractAddress, + token_ids: Span, + values: Span +) { + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = dispatcher.balance_of(sender, *token_ids.at(index)); + assert!(balance_of_sender.is_zero()); + let balance_of_recipient = dispatcher.balance_of(recipient, *token_ids.at(index)); + assert_eq!(balance_of_recipient, *values.at(index)); + + index += 1; + } +} diff --git a/src/tests/presets/test_erc20.cairo b/src/tests/presets/test_erc20.cairo index 6892ef2e7..80e1bd8cd 100644 --- a/src/tests/presets/test_erc20.cairo +++ b/src/tests/presets/test_erc20.cairo @@ -5,9 +5,7 @@ use openzeppelin::tests::utils::constants::{ }; use openzeppelin::tests::utils; use openzeppelin::token::erc20::ERC20Component::{Approval, Transfer}; -use openzeppelin::token::erc20::ERC20Component::{ERC20CamelOnlyImpl, ERC20Impl}; -use openzeppelin::token::erc20::ERC20Component::{ERC20MetadataImpl, InternalImpl}; -use openzeppelin::token::erc20::interface::ERC20ABI; +use openzeppelin::token::erc20::ERC20Component; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use openzeppelin::utils::serde::SerializedAppend; use starknet::ContractAddress; @@ -20,8 +18,8 @@ use starknet::testing; fn setup_dispatcher_with_event() -> ERC20ABIDispatcher { let mut calldata = array![]; - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); calldata.append_serde(SUPPLY); calldata.append_serde(OWNER()); @@ -43,8 +41,8 @@ fn setup_dispatcher() -> ERC20ABIDispatcher { fn test_constructor() { let mut dispatcher = setup_dispatcher_with_event(); - assert_eq!(dispatcher.name(), NAME); - assert_eq!(dispatcher.symbol(), SYMBOL); + assert_eq!(dispatcher.name(), NAME()); + assert_eq!(dispatcher.symbol(), SYMBOL()); assert_eq!(dispatcher.decimals(), DECIMALS); assert_eq!(dispatcher.total_supply(), SUPPLY); assert_eq!(dispatcher.balance_of(OWNER()), SUPPLY); @@ -297,13 +295,13 @@ fn test_transferFrom_from_zero_address() { fn assert_event_approval( contract: ContractAddress, owner: ContractAddress, spender: ContractAddress, value: u256 ) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.owner, owner); - assert_eq!(event.spender, spender); - assert_eq!(event.value, value); + let event = utils::pop_log::(contract).unwrap(); + let expected = ERC20Component::Event::Approval(Approval { owner, spender, value }); + assert!(event == expected); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Approval")); indexed_keys.append_serde(owner); indexed_keys.append_serde(spender); utils::assert_indexed_keys(event, indexed_keys.span()) @@ -319,13 +317,13 @@ fn assert_only_event_approval( fn assert_event_transfer( contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 ) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.from, from); - assert_eq!(event.to, to); - assert_eq!(event.value, value); + let event = utils::pop_log::(contract).unwrap(); + let expected = ERC20Component::Event::Transfer(Transfer { from, to, value }); + assert!(event == expected); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Transfer")); indexed_keys.append_serde(from); indexed_keys.append_serde(to); utils::assert_indexed_keys(event, indexed_keys.span()); diff --git a/src/tests/presets/test_erc721.cairo b/src/tests/presets/test_erc721.cairo index 3e2789512..d956ba05e 100644 --- a/src/tests/presets/test_erc721.cairo +++ b/src/tests/presets/test_erc721.cairo @@ -9,13 +9,11 @@ use openzeppelin::tests::mocks::erc721_receiver_mocks::{ }; use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; use openzeppelin::tests::utils::constants::{ - ZERO, DATA, OWNER, SPENDER, RECIPIENT, OTHER, OPERATOR, PUBKEY, NAME, SYMBOL + ZERO, DATA, OWNER, SPENDER, RECIPIENT, OTHER, OPERATOR, PUBKEY, NAME, SYMBOL, BASE_URI }; use openzeppelin::tests::utils; -use openzeppelin::token::erc721::ERC721Component::InternalImpl as ERC721ComponentInternalTrait; use openzeppelin::token::erc721::ERC721Component::{Approval, ApprovalForAll, Transfer}; -use openzeppelin::token::erc721::ERC721Component::{ERC721CamelOnlyImpl, ERC721Impl}; -use openzeppelin::token::erc721::ERC721Component::{ERC721MetadataImpl, ERC721MetadataCamelOnlyImpl}; +use openzeppelin::token::erc721::ERC721Component; use openzeppelin::token::erc721::interface::ERC721ABI; use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; use openzeppelin::token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID}; @@ -32,11 +30,6 @@ const NONEXISTENT: u256 = 9898; const TOKENS_LEN: u256 = 3; -// Token URIs -const URI_1: felt252 = 'URI_1'; -const URI_2: felt252 = 'URI_2'; -const URI_3: felt252 = 'URI_3'; - // // Setup // @@ -44,16 +37,15 @@ const URI_3: felt252 = 'URI_3'; fn setup_dispatcher_with_event() -> ERC721ABIDispatcher { let mut calldata = array![]; let mut token_ids = array![TOKEN_1, TOKEN_2, TOKEN_3]; - let mut token_uris = array![URI_1, URI_2, URI_3]; // Set caller as `OWNER` testing::set_contract_address(OWNER()); - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); + calldata.append_serde(BASE_URI()); calldata.append_serde(OWNER()); calldata.append_serde(token_ids); - calldata.append_serde(token_uris); let address = utils::deploy(ERC721::TEST_CLASS_HASH, calldata); ERC721ABIDispatcher { contract_address: address } @@ -91,45 +83,19 @@ fn setup_camel_account() -> ContractAddress { fn test__mint_assets() { let mut state = ERC721::contract_state_for_testing(); let mut token_ids = array![TOKEN_1, TOKEN_2, TOKEN_3].span(); - let mut token_uris = array![URI_1, URI_2, URI_3].span(); - - state._mint_assets(OWNER(), token_ids, token_uris); + state._mint_assets(OWNER(), token_ids); assert_eq!(state.erc721.balance_of(OWNER()), TOKENS_LEN); loop { if token_ids.len() == 0 { break; } - let id = *token_ids.pop_front().unwrap(); - let uri = *token_uris.pop_front().unwrap(); - assert_eq!(state.erc721.owner_of(id), OWNER()); - assert_eq!(state.erc721.token_uri(id), uri); }; } -#[test] -#[should_panic(expected: ('Array lengths do not match',))] -fn test__mint_assets_mismatched_arrays_1() { - let mut state = ERC721::contract_state_for_testing(); - - let token_ids = array![TOKEN_1, TOKEN_2, TOKEN_3].span(); - let short_uris = array![URI_1, URI_2].span(); - state._mint_assets(OWNER(), token_ids, short_uris); -} - -#[test] -#[should_panic(expected: ('Array lengths do not match',))] -fn test__mint_assets_mismatched_arrays_2() { - let mut state = ERC721::contract_state_for_testing(); - - let short_ids = array![TOKEN_1, TOKEN_2].span(); - let token_uris = array![URI_1, URI_2, URI_3].span(); - state._mint_assets(OWNER(), short_ids, token_uris); -} - // // constructor // @@ -145,8 +111,8 @@ fn test_constructor() { if interface_ids.len() == 0 { break; } - let supports_isrc5 = dispatcher.supports_interface(id); - assert!(supports_isrc5); + let supports_interface = dispatcher.supports_interface(id); + assert!(supports_interface); }; // Check token balance and owner @@ -216,6 +182,15 @@ fn test_token_uri_non_minted() { dispatcher.token_uri(7); } +#[test] +fn test_token_uri() { + let dispatcher = setup_dispatcher(); + + let uri = dispatcher.token_uri(TOKEN_1); + let expected = format!("{}{}", BASE_URI(), TOKEN_1); + assert_eq!(uri, expected); +} + #[test] fn test_get_approved() { let dispatcher = setup_dispatcher(); @@ -1015,14 +990,16 @@ fn assert_state_transfer_to_self( fn assert_event_approval_for_all( contract: ContractAddress, owner: ContractAddress, operator: ContractAddress, approved: bool ) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.owner, owner); - assert_eq!(event.operator, operator); - assert_eq!(event.approved, approved); + let event = utils::pop_log::(contract).unwrap(); + let expected = ERC721Component::Event::ApprovalForAll( + ApprovalForAll { owner, operator, approved, } + ); + assert!(event == expected); utils::assert_no_events_left(contract); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("ApprovalForAll")); indexed_keys.append_serde(owner); indexed_keys.append_serde(operator); utils::assert_indexed_keys(event, indexed_keys.span()); @@ -1031,14 +1008,14 @@ fn assert_event_approval_for_all( fn assert_event_approval( contract: ContractAddress, owner: ContractAddress, approved: ContractAddress, token_id: u256 ) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.owner, owner); - assert_eq!(event.approved, approved); - assert_eq!(event.token_id, token_id); + let event = utils::pop_log::(contract).unwrap(); + let expected = ERC721Component::Event::Approval(Approval { owner, approved, token_id }); + assert!(event == expected); utils::assert_no_events_left(contract); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Approval")); indexed_keys.append_serde(owner); indexed_keys.append_serde(approved); indexed_keys.append_serde(token_id); @@ -1048,13 +1025,13 @@ fn assert_event_approval( fn assert_event_transfer( contract: ContractAddress, from: ContractAddress, to: ContractAddress, token_id: u256 ) { - let event = utils::pop_log::(contract).unwrap(); - assert_eq!(event.from, from); - assert_eq!(event.to, to); - assert_eq!(event.token_id, token_id); + let event = utils::pop_log::(contract).unwrap(); + let expected = ERC721Component::Event::Transfer(Transfer { from, to, token_id }); + assert!(event == expected); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Transfer")); indexed_keys.append_serde(from); indexed_keys.append_serde(to); indexed_keys.append_serde(token_id); diff --git a/src/tests/presets/test_eth_account.cairo b/src/tests/presets/test_eth_account.cairo index 144f9739d..e4d71a23f 100644 --- a/src/tests/presets/test_eth_account.cairo +++ b/src/tests/presets/test_eth_account.cairo @@ -90,23 +90,23 @@ fn setup_upgradeable() -> IUpgradeableDispatcher { #[test] fn test_constructor() { let mut state = EthAccountUpgradeable::contract_state_for_testing(); - let public_key = ETH_PUBKEY(); - EthAccountUpgradeable::constructor(ref state, public_key); + EthAccountUpgradeable::constructor(ref state, ETH_PUBKEY()); - assert_only_event_owner_added(ZERO(), public_key); - assert( - EthAccountUpgradeable::PublicKeyImpl::get_public_key(@state) == public_key, - 'Should return public_key' - ); - assert( - EthAccountUpgradeable::SRC5Impl::supports_interface(@state, ISRC5_ID), - 'Should implement ISRC5' + assert_only_event_owner_added(ZERO(), ETH_PUBKEY()); + + let public_key = EthAccountUpgradeable::EthAccountMixinImpl::get_public_key(@state); + assert_eq!(public_key, ETH_PUBKEY()); + + let supports_isrc5 = EthAccountUpgradeable::EthAccountMixinImpl::supports_interface( + @state, ISRC5_ID ); - assert( - EthAccountUpgradeable::SRC5Impl::supports_interface(@state, ISRC6_ID), - 'Should implement ISRC6' + assert!(supports_isrc5); + + let supports_isrc6 = EthAccountUpgradeable::EthAccountMixinImpl::supports_interface( + @state, ISRC6_ID ); + assert!(supports_isrc6); } // @@ -121,7 +121,7 @@ fn test_public_key_setter_and_getter() { testing::set_contract_address(dispatcher.contract_address); dispatcher.set_public_key(new_public_key); - assert(dispatcher.get_public_key() == new_public_key, 'Should return new_public_key'); + assert_eq!(dispatcher.get_public_key(), new_public_key); assert_event_owner_removed(dispatcher.contract_address, ETH_PUBKEY()); assert_only_event_owner_added(dispatcher.contract_address, new_public_key); @@ -135,7 +135,7 @@ fn test_public_key_setter_and_getter_camel() { testing::set_contract_address(dispatcher.contract_address); dispatcher.setPublicKey(new_public_key); - assert(dispatcher.getPublicKey() == new_public_key, 'Should return new_public_key'); + assert_eq!(dispatcher.getPublicKey(), new_public_key); assert_event_owner_removed(dispatcher.contract_address, ETH_PUBKEY()); assert_only_event_owner_added(dispatcher.contract_address, new_public_key); @@ -178,7 +178,7 @@ fn test_is_valid_signature() { let (dispatcher, hash, signature) = is_valid_sig_dispatcher(); let is_valid = dispatcher.is_valid_signature(hash, signature); - assert(is_valid == starknet::VALIDATED, 'Should accept valid signature'); + assert_eq!(is_valid, starknet::VALIDATED); } #[test] @@ -186,7 +186,7 @@ fn test_is_valid_signature_bad_sig() { let (dispatcher, hash, signature) = is_valid_sig_dispatcher(); let is_valid = dispatcher.is_valid_signature(hash + 1, signature); - assert(is_valid == 0, 'Should reject invalid signature'); + assert!(is_valid.is_zero(), "Should reject invalid signature"); } #[test] @@ -194,7 +194,7 @@ fn test_isValidSignature() { let (dispatcher, hash, signature) = is_valid_sig_dispatcher(); let is_valid = dispatcher.isValidSignature(hash, signature); - assert(is_valid == starknet::VALIDATED, 'Should accept valid signature'); + assert_eq!(is_valid, starknet::VALIDATED); } #[test] @@ -202,7 +202,7 @@ fn test_isValidSignature_bad_sig() { let (dispatcher, hash, signature) = is_valid_sig_dispatcher(); let is_valid = dispatcher.isValidSignature(hash + 1, signature); - assert(is_valid == 0, 'Should reject invalid signature'); + assert!(is_valid.is_zero(), "Should reject invalid signature"); } // @@ -212,9 +212,13 @@ fn test_isValidSignature_bad_sig() { #[test] fn test_supports_interface() { let dispatcher = setup_dispatcher(); - assert(dispatcher.supports_interface(ISRC5_ID), 'Should implement ISRC5'); - assert(dispatcher.supports_interface(ISRC6_ID), 'Should implement ISRC6'); - assert(!dispatcher.supports_interface(0x123), 'Should not implement 0x123'); + + let supports_isrc5 = dispatcher.supports_interface(ISRC5_ID); + assert!(supports_isrc5); + let supports_isrc6 = dispatcher.supports_interface(ISRC6_ID); + assert!(supports_isrc6); + let doesnt_support_0x123 = !dispatcher.supports_interface(0x123); + assert!(doesnt_support_0x123); } // @@ -228,10 +232,8 @@ fn test_validate_deploy() { // `__validate_deploy__` does not directly use the passed arguments. Their // values are already integrated in the tx hash. The passed arguments in this // testing context are decoupled from the signature and have no effect on the test. - assert( - account.__validate_deploy__(CLASS_HASH(), SALT, ETH_PUBKEY()) == starknet::VALIDATED, - 'Should validate correctly' - ); + let is_valid = account.__validate_deploy__(CLASS_HASH(), SALT, ETH_PUBKEY()); + assert_eq!(is_valid, starknet::VALIDATED); } #[test] @@ -272,10 +274,8 @@ fn test_validate_declare() { // `__validate_declare__` does not directly use the class_hash argument. Its // value is already integrated in the tx hash. The class_hash argument in this // testing context is decoupled from the signature and has no effect on the test. - assert( - account.__validate_declare__(CLASS_HASH()) == starknet::VALIDATED, - 'Should validate correctly' - ); + let is_valid = account.__validate_declare__(CLASS_HASH()); + assert_eq!(is_valid, starknet::VALIDATED,); } #[test] @@ -333,12 +333,12 @@ fn test_execute_with_version(version: Option) { let ret = account.__execute__(calls); - assert(erc20.balance_of(account.contract_address) == 800, 'Should have remainder'); - assert(erc20.balance_of(RECIPIENT()) == amount, 'Should have transferred'); + assert_eq!(erc20.balance_of(account.contract_address), 800, "Should have remainder"); + assert_eq!(erc20.balance_of(RECIPIENT()), amount, "Should have transferred"); let mut call_serialized_retval = *ret.at(0); let call_retval = Serde::::deserialize(ref call_serialized_retval); - assert(call_retval.unwrap(), 'Should have succeeded'); + assert!(call_retval.unwrap()); } #[test] @@ -362,7 +362,8 @@ fn test_validate() { let calls = array![]; let account = setup_dispatcher_with_data(Option::Some(@SIGNED_TX_DATA())); - assert(account.__validate__(calls) == starknet::VALIDATED, 'Should validate correctly'); + let is_valid = account.__validate__(calls); + assert_eq!(is_valid, starknet::VALIDATED); } #[test] @@ -404,16 +405,16 @@ fn test_multicall() { calls.append(call2); let ret = account.__execute__(calls); - assert(erc20.balance_of(account.contract_address) == 200, 'Should have remainder'); - assert(erc20.balance_of(recipient1) == 300, 'Should have transferred'); - assert(erc20.balance_of(recipient2) == 500, 'Should have transferred'); + assert_eq!(erc20.balance_of(account.contract_address), 200, "Should have remainder"); + assert_eq!(erc20.balance_of(recipient1), 300, "Should have transferred"); + assert_eq!(erc20.balance_of(recipient2), 500, "Should have transferred"); let mut call1_serialized_retval = *ret.at(0); let mut call2_serialized_retval = *ret.at(1); let call1_retval = Serde::::deserialize(ref call1_serialized_retval); let call2_retval = Serde::::deserialize(ref call2_serialized_retval); - assert(call1_retval.unwrap(), 'Should have succeeded'); - assert(call2_retval.unwrap(), 'Should have succeeded'); + assert!(call1_retval.unwrap()); + assert!(call2_retval.unwrap()); } #[test] @@ -423,7 +424,7 @@ fn test_multicall_zero_calls() { let ret = account.__execute__(calls); - assert(ret.len() == 0, 'Should have an empty response'); + assert!(ret.len().is_zero(), "Should have an empty response"); } #[test] diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 9a08f90b3..499ab711a 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -25,8 +25,8 @@ fn ERC20_CLASS_HASH() -> ClassHash { fn ERC20_CALLDATA() -> Span { let mut calldata = array![]; - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); calldata.append_serde(SUPPLY); calldata.append_serde(RECIPIENT()); calldata.span() @@ -53,13 +53,18 @@ fn test_deploy_not_unique() { assert_eq!(expected_addr, deployed_addr); // Check event - let event = utils::pop_log::(udc.contract_address).unwrap(); - assert_eq!(event.address, deployed_addr); - assert_eq!(event.deployer, CALLER()); - assert_eq!(event.unique, unique); - assert_eq!(event.class_hash, ERC20_CLASS_HASH()); - assert_eq!(event.calldata, ERC20_CALLDATA()); - assert_eq!(event.salt, SALT); + let event = utils::pop_log::(udc.contract_address).unwrap(); + let expected = UniversalDeployer::Event::ContractDeployed( + ContractDeployed { + address: deployed_addr, + deployer: CALLER(), + unique: unique, + class_hash: ERC20_CLASS_HASH(), + calldata: ERC20_CALLDATA(), + salt: SALT + } + ); + assert!(event == expected); utils::assert_no_events_left(udc.contract_address); // Check deployment @@ -83,13 +88,18 @@ fn test_deploy_unique() { assert_eq!(expected_addr, deployed_addr); // Check event - let event = utils::pop_log::(udc.contract_address).unwrap(); - assert_eq!(event.address, deployed_addr); - assert_eq!(event.deployer, CALLER()); - assert_eq!(event.unique, unique); - assert_eq!(event.class_hash, ERC20_CLASS_HASH()); - assert_eq!(event.calldata, ERC20_CALLDATA()); - assert_eq!(event.salt, SALT); + let event = utils::pop_log::(udc.contract_address).unwrap(); + let expected = UniversalDeployer::Event::ContractDeployed( + ContractDeployed { + address: deployed_addr, + deployer: CALLER(), + unique: unique, + class_hash: ERC20_CLASS_HASH(), + calldata: ERC20_CALLDATA(), + salt: SALT + } + ); + assert!(event == expected); utils::assert_no_events_left(udc.contract_address); // Check deployment diff --git a/src/tests/security/test_pausable.cairo b/src/tests/security/test_pausable.cairo index 770c81374..922750646 100644 --- a/src/tests/security/test_pausable.cairo +++ b/src/tests/security/test_pausable.cairo @@ -120,13 +120,15 @@ fn test_unpause_when_unpaused() { // fn assert_event_paused(account: ContractAddress) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.account, account); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = PausableComponent::Event::Paused(Paused { account }); + assert!(event == expected); utils::assert_no_events_left(ZERO()); } fn assert_event_unpaused(account: ContractAddress) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.account, account); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = PausableComponent::Event::Unpaused(Unpaused { account }); + assert!(event == expected); utils::assert_no_events_left(ZERO()); } diff --git a/src/tests/token.cairo b/src/tests/token.cairo index 5b30d8743..01bfa76ce 100644 --- a/src/tests/token.cairo +++ b/src/tests/token.cairo @@ -1,6 +1,10 @@ +mod test_dual1155; +mod test_dual1155_receiver; mod test_dual20; mod test_dual721; mod test_dual721_receiver; +mod test_erc1155; +mod test_erc1155_receiver; mod test_erc20; mod test_erc721; mod test_erc721_receiver; diff --git a/src/tests/token/test_dual1155.cairo b/src/tests/token/test_dual1155.cairo new file mode 100644 index 000000000..32962595b --- /dev/null +++ b/src/tests/token/test_dual1155.cairo @@ -0,0 +1,386 @@ +use openzeppelin::tests::mocks::erc1155_mocks::{CamelERC1155Mock, SnakeERC1155Mock}; +use openzeppelin::tests::mocks::erc1155_mocks::{CamelERC1155PanicMock, SnakeERC1155PanicMock}; +use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; +use openzeppelin::tests::token::test_erc1155::{setup_account, setup_receiver}; +use openzeppelin::tests::utils::constants::{ + EMPTY_DATA, OWNER, RECIPIENT, OPERATOR, TOKEN_ID, TOKEN_ID_2, TOKEN_VALUE +}; +use openzeppelin::tests::utils; +use openzeppelin::token::erc1155::dual1155::{DualCaseERC1155, DualCaseERC1155Trait}; +use openzeppelin::token::erc1155::interface::IERC1155_ID; +use openzeppelin::token::erc1155::interface::{ + IERC1155CamelDispatcher, IERC1155CamelDispatcherTrait +}; +use openzeppelin::token::erc1155::interface::{IERC1155Dispatcher, IERC1155DispatcherTrait}; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; +use starknet::testing; + +// +// Setup +// + +fn setup_snake() -> (DualCaseERC1155, IERC1155Dispatcher, ContractAddress) { + let base_uri: ByteArray = "URI"; + let owner = setup_account(); + let mut calldata = array![]; + calldata.append_serde(base_uri); + calldata.append_serde(owner); + calldata.append_serde(TOKEN_ID); + calldata.append_serde(TOKEN_VALUE); + let target = utils::deploy(SnakeERC1155Mock::TEST_CLASS_HASH, calldata); + ( + DualCaseERC1155 { contract_address: target }, + IERC1155Dispatcher { contract_address: target }, + owner + ) +} + +fn setup_camel() -> (DualCaseERC1155, IERC1155CamelDispatcher, ContractAddress) { + let base_uri: ByteArray = "URI"; + let owner = setup_account(); + let mut calldata = array![]; + calldata.append_serde(base_uri); + calldata.append_serde(owner); + calldata.append_serde(TOKEN_ID); + calldata.append_serde(TOKEN_VALUE); + let target = utils::deploy(CamelERC1155Mock::TEST_CLASS_HASH, calldata); + ( + DualCaseERC1155 { contract_address: target }, + IERC1155CamelDispatcher { contract_address: target }, + owner + ) +} + +fn setup_non_erc1155() -> DualCaseERC1155 { + let calldata = array![]; + let target = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, calldata); + DualCaseERC1155 { contract_address: target } +} + +fn setup_erc1155_panic() -> (DualCaseERC1155, DualCaseERC1155) { + let snake_target = utils::deploy(SnakeERC1155PanicMock::TEST_CLASS_HASH, array![]); + let camel_target = utils::deploy(CamelERC1155PanicMock::TEST_CLASS_HASH, array![]); + ( + DualCaseERC1155 { contract_address: snake_target }, + DualCaseERC1155 { contract_address: camel_target } + ) +} + +// +// Case agnostic methods +// + +#[test] +fn test_dual_uri() { + let (snake_dispatcher, _, _) = setup_snake(); + let (camel_dispatcher, _, _) = setup_camel(); + assert_eq!(snake_dispatcher.uri(TOKEN_ID), "URI"); + assert_eq!(camel_dispatcher.uri(TOKEN_ID), "URI"); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_uri() { + let dispatcher = setup_non_erc1155(); + dispatcher.uri(TOKEN_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_uri_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + dispatcher.uri(TOKEN_ID); +} + +// +// snake_case target +// + +#[test] +fn test_dual_balance_of() { + let (dispatcher, _, owner) = setup_snake(); + assert_eq!(dispatcher.balance_of(owner, TOKEN_ID), TOKEN_VALUE); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_balance_of() { + let dispatcher = setup_non_erc1155(); + dispatcher.balance_of(OWNER(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_balance_of_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + dispatcher.balance_of(OWNER(), TOKEN_ID); +} + +#[test] +fn test_dual_balance_of_batch() { + let (dispatcher, _, owner) = setup_snake(); + let accounts = array![owner, RECIPIENT()].span(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + + let balances = dispatcher.balance_of_batch(accounts, token_ids); + assert_eq!(*balances.at(0), TOKEN_VALUE); + assert!((*balances.at(1)).is_zero()); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_balance_of_batch() { + let dispatcher = setup_non_erc1155(); + let (accounts, token_ids) = get_accounts_and_ids(); + dispatcher.balance_of_batch(accounts, token_ids); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_balance_of_batch_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + let (accounts, token_ids) = get_accounts_and_ids(); + dispatcher.balance_of_batch(accounts, token_ids); +} + +#[test] +fn test_dual_safe_transfer_from() { + let (dispatcher, target, owner) = setup_snake(); + let receiver = setup_receiver(); + testing::set_contract_address(owner); + dispatcher.safe_transfer_from(owner, receiver, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_eq!(target.balance_of(receiver, TOKEN_ID), TOKEN_VALUE); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_safe_transfer_from() { + let dispatcher = setup_non_erc1155(); + dispatcher.safe_transfer_from(OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_safe_transfer_from_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + dispatcher.safe_transfer_from(OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +fn test_dual_safe_batch_transfer_from() { + let (dispatcher, target, owner) = setup_snake(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, 0].span(); + let receiver = setup_receiver(); + testing::set_contract_address(owner); + + dispatcher.safe_batch_transfer_from(owner, receiver, token_ids, values, EMPTY_DATA()); + assert_eq!(target.balance_of(receiver, TOKEN_ID), TOKEN_VALUE); + assert!(target.balance_of(receiver, TOKEN_ID_2).is_zero()); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_safe_batch_transfer_from() { + let dispatcher = setup_non_erc1155(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, 0].span(); + dispatcher.safe_batch_transfer_from(OWNER(), RECIPIENT(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_safe_batch_transfer_from_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, 0].span(); + dispatcher.safe_batch_transfer_from(OWNER(), RECIPIENT(), token_ids, values, EMPTY_DATA()); +} + +#[test] +fn test_dual_is_approved_for_all() { + let (dispatcher, target, _) = setup_snake(); + testing::set_contract_address(OWNER()); + target.set_approval_for_all(OPERATOR(), true); + + let is_approved_for_all = dispatcher.is_approved_for_all(OWNER(), OPERATOR()); + assert!(is_approved_for_all); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_is_approved_for_all() { + let dispatcher = setup_non_erc1155(); + dispatcher.is_approved_for_all(OWNER(), OPERATOR()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_is_approved_for_all_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + dispatcher.is_approved_for_all(OWNER(), OPERATOR()); +} + +#[test] +fn test_dual_set_approval_for_all() { + let (dispatcher, target, _) = setup_snake(); + testing::set_contract_address(OWNER()); + dispatcher.set_approval_for_all(OPERATOR(), true); + + let is_approved_for_all = target.is_approved_for_all(OWNER(), OPERATOR()); + assert!(is_approved_for_all); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_set_approval_for_all() { + let dispatcher = setup_non_erc1155(); + dispatcher.set_approval_for_all(OPERATOR(), true); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_set_approval_for_all_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + dispatcher.set_approval_for_all(OPERATOR(), true); +} + +#[test] +fn test_dual_supports_interface() { + let (dispatcher, _, _) = setup_snake(); + let supports_ierc1155 = dispatcher.supports_interface(IERC1155_ID); + assert!(supports_ierc1155); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_supports_interface() { + let dispatcher = setup_non_erc1155(); + dispatcher.supports_interface(IERC1155_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_supports_interface_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_panic(); + dispatcher.supports_interface(IERC1155_ID); +} + +// +// camelCase target +// + +#[test] +fn test_dual_balanceOf() { + let (dispatcher, _, owner) = setup_camel(); + assert_eq!(dispatcher.balance_of(owner, TOKEN_ID), TOKEN_VALUE); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_balanceOf_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_panic(); + dispatcher.balance_of(OWNER(), TOKEN_ID); +} + +#[test] +fn test_dual_balanceOfBatch() { + let (dispatcher, _, owner) = setup_camel(); + let accounts = array![owner, RECIPIENT()].span(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + + let balances = dispatcher.balance_of_batch(accounts, token_ids); + assert_eq!(*balances.at(0), TOKEN_VALUE); + assert!((*balances.at(1)).is_zero()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_balanceOfBatch_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_panic(); + let (accounts, token_ids) = get_accounts_and_ids(); + dispatcher.balance_of_batch(accounts, token_ids); +} + +#[test] +fn test_dual_safeTransferFrom() { + let (dispatcher, target, owner) = setup_camel(); + let receiver = setup_receiver(); + testing::set_contract_address(owner); + dispatcher.safe_transfer_from(owner, receiver, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_eq!(target.balanceOf(receiver, TOKEN_ID), TOKEN_VALUE); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_safeTransferFrom_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_panic(); + dispatcher.safe_transfer_from(OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +fn test_dual_safeBatchTransferFrom() { + let (dispatcher, target, owner) = setup_camel(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, 0].span(); + let receiver = setup_receiver(); + testing::set_contract_address(owner); + + dispatcher.safe_batch_transfer_from(owner, receiver, token_ids, values, EMPTY_DATA()); + assert_eq!(target.balanceOf(receiver, TOKEN_ID), TOKEN_VALUE); + assert!(target.balanceOf(receiver, TOKEN_ID_2).is_zero()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_safeBatchTransferFrom_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_panic(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, 0].span(); + dispatcher.safe_batch_transfer_from(OWNER(), RECIPIENT(), token_ids, values, EMPTY_DATA()); +} + +#[test] +fn test_dual_isApprovedForAll() { + let (dispatcher, target, _) = setup_camel(); + testing::set_contract_address(OWNER()); + target.setApprovalForAll(OPERATOR(), true); + + let is_approved_for_all = dispatcher.is_approved_for_all(OWNER(), OPERATOR()); + assert!(is_approved_for_all); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_isApprovedForAll_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_panic(); + dispatcher.is_approved_for_all(OWNER(), OPERATOR()); +} + +#[test] +fn test_dual_setApprovalForAll() { + let (dispatcher, target, _) = setup_camel(); + testing::set_contract_address(OWNER()); + dispatcher.set_approval_for_all(OPERATOR(), true); + + let is_approved_for_all = target.isApprovedForAll(OWNER(), OPERATOR()); + assert!(is_approved_for_all); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_setApprovalForAll_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_panic(); + dispatcher.set_approval_for_all(OPERATOR(), true); +} + +// +// Helpers +// + +fn get_accounts_and_ids() -> (Span, Span) { + let accounts = array![OWNER(), RECIPIENT()].span(); + let ids = array![TOKEN_ID, TOKEN_ID_2].span(); + (accounts, ids) +} diff --git a/src/tests/token/test_dual1155_receiver.cairo b/src/tests/token/test_dual1155_receiver.cairo new file mode 100644 index 000000000..da0b87bba --- /dev/null +++ b/src/tests/token/test_dual1155_receiver.cairo @@ -0,0 +1,157 @@ +use openzeppelin::tests::mocks::erc1155_receiver_mocks::{ + CamelERC1155ReceiverMock, CamelERC1155ReceiverPanicMock, SnakeERC1155ReceiverMock, + SnakeERC1155ReceiverPanicMock +}; +use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; +use openzeppelin::tests::utils::constants::{EMPTY_DATA, OPERATOR, OWNER, TOKEN_ID, TOKEN_VALUE}; +use openzeppelin::tests::utils; +use openzeppelin::token::erc1155::dual1155_receiver::{ + DualCaseERC1155Receiver, DualCaseERC1155ReceiverTrait +}; +use openzeppelin::token::erc1155::interface::IERC1155_RECEIVER_ID; +use openzeppelin::token::erc1155::interface::{ + IERC1155ReceiverCamelDispatcher, IERC1155ReceiverCamelDispatcherTrait +}; +use openzeppelin::token::erc1155::interface::{ + IERC1155ReceiverDispatcher, IERC1155ReceiverDispatcherTrait +}; + +// +// Setup +// + +fn setup_snake() -> (DualCaseERC1155Receiver, IERC1155ReceiverDispatcher) { + let mut calldata = ArrayTrait::new(); + let target = utils::deploy(SnakeERC1155ReceiverMock::TEST_CLASS_HASH, calldata); + ( + DualCaseERC1155Receiver { contract_address: target }, + IERC1155ReceiverDispatcher { contract_address: target } + ) +} + +fn setup_camel() -> (DualCaseERC1155Receiver, IERC1155ReceiverCamelDispatcher) { + let mut calldata = ArrayTrait::new(); + let target = utils::deploy(CamelERC1155ReceiverMock::TEST_CLASS_HASH, calldata); + ( + DualCaseERC1155Receiver { contract_address: target }, + IERC1155ReceiverCamelDispatcher { contract_address: target } + ) +} + +fn setup_non_erc1155_receiver() -> DualCaseERC1155Receiver { + let calldata = ArrayTrait::new(); + let target = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, calldata); + DualCaseERC1155Receiver { contract_address: target } +} + +fn setup_erc1155_receiver_panic() -> (DualCaseERC1155Receiver, DualCaseERC1155Receiver) { + let snake_target = utils::deploy( + SnakeERC1155ReceiverPanicMock::TEST_CLASS_HASH, ArrayTrait::new() + ); + let camel_target = utils::deploy( + CamelERC1155ReceiverPanicMock::TEST_CLASS_HASH, ArrayTrait::new() + ); + ( + DualCaseERC1155Receiver { contract_address: snake_target }, + DualCaseERC1155Receiver { contract_address: camel_target } + ) +} + +// +// snake_case target +// + +#[test] +fn test_dual_on_erc1155_received() { + let (dispatcher, _) = setup_snake(); + let result = dispatcher + .on_erc1155_received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_eq!(result, IERC1155_RECEIVER_ID,); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_on_erc1155_received() { + let dispatcher = setup_non_erc1155_receiver(); + dispatcher.on_erc1155_received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_on_erc1155_received_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_receiver_panic(); + dispatcher.on_erc1155_received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +fn test_dual_on_erc1155_batch_received() { + let (dispatcher, _) = setup_snake(); + let (token_ids, values) = get_ids_and_values(); + + let result = dispatcher + .on_erc1155_batch_received(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); + assert_eq!(result, IERC1155_RECEIVER_ID); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_on_erc1155_batch_received() { + let dispatcher = setup_non_erc1155_receiver(); + let (token_ids, values) = get_ids_and_values(); + dispatcher.on_erc1155_batch_received(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_on_erc1155_batch_received_exists_and_panics() { + let (dispatcher, _) = setup_erc1155_receiver_panic(); + let (token_ids, values) = get_ids_and_values(); + dispatcher.on_erc1155_batch_received(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); +} + +// +// camelCase target +// + +#[test] +fn test_dual_onERC1155Received() { + let (dispatcher, _) = setup_camel(); + let result = dispatcher + .on_erc1155_received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_eq!(result, IERC1155_RECEIVER_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_onERC1155Received_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_receiver_panic(); + dispatcher.on_erc1155_received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +fn test_dual_onERC1155BatchReceived() { + let (dispatcher, _) = setup_camel(); + let (token_ids, values) = get_ids_and_values(); + + let result = dispatcher + .on_erc1155_batch_received(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); + assert_eq!(result, IERC1155_RECEIVER_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_onERC1155BatchReceived_exists_and_panics() { + let (_, dispatcher) = setup_erc1155_receiver_panic(); + let (token_ids, values) = get_ids_and_values(); + dispatcher.on_erc1155_batch_received(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); +} + +// +// Helpers +// + +fn get_ids_and_values() -> (Span, Span) { + let token_ids = array![TOKEN_ID, TOKEN_ID].span(); + let values = array![TOKEN_VALUE, TOKEN_VALUE].span(); + (token_ids, values) +} diff --git a/src/tests/token/test_dual20.cairo b/src/tests/token/test_dual20.cairo index 5e85fa1f2..ba9ba740d 100644 --- a/src/tests/token/test_dual20.cairo +++ b/src/tests/token/test_dual20.cairo @@ -17,8 +17,8 @@ use starknet::testing::set_contract_address; fn setup_snake() -> (DualCaseERC20, IERC20Dispatcher) { let mut calldata = array![]; - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); calldata.append_serde(SUPPLY); calldata.append_serde(OWNER()); let target = utils::deploy(SnakeERC20Mock::TEST_CLASS_HASH, calldata); @@ -27,8 +27,8 @@ fn setup_snake() -> (DualCaseERC20, IERC20Dispatcher) { fn setup_camel() -> (DualCaseERC20, IERC20CamelDispatcher) { let mut calldata = array![]; - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); calldata.append_serde(SUPPLY); calldata.append_serde(OWNER()); let target = utils::deploy(CamelERC20Mock::TEST_CLASS_HASH, calldata); @@ -57,10 +57,10 @@ fn setup_erc20_panic() -> (DualCaseERC20, DualCaseERC20) { #[test] fn test_dual_name() { let (snake_dispatcher, _) = setup_snake(); - assert_eq!(snake_dispatcher.name(), NAME); + assert_eq!(snake_dispatcher.name(), NAME()); let (camel_dispatcher, _) = setup_camel(); - assert_eq!(camel_dispatcher.name(), NAME); + assert_eq!(camel_dispatcher.name(), NAME()); } #[test] @@ -81,8 +81,8 @@ fn test_dual_name_exists_and_panics() { fn test_dual_symbol() { let (snake_dispatcher, _) = setup_snake(); let (camel_dispatcher, _) = setup_camel(); - assert_eq!(snake_dispatcher.symbol(), SYMBOL); - assert_eq!(camel_dispatcher.symbol(), SYMBOL); + assert_eq!(snake_dispatcher.symbol(), SYMBOL()); + assert_eq!(camel_dispatcher.symbol(), SYMBOL()); } #[test] diff --git a/src/tests/token/test_dual721.cairo b/src/tests/token/test_dual721.cairo index 22ae5c754..2e2bd7587 100644 --- a/src/tests/token/test_dual721.cairo +++ b/src/tests/token/test_dual721.cairo @@ -3,7 +3,7 @@ use openzeppelin::tests::mocks::erc721_mocks::{CamelERC721PanicMock, SnakeERC721 use openzeppelin::tests::mocks::erc721_receiver_mocks::DualCaseERC721ReceiverMock; use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; use openzeppelin::tests::utils::constants::{ - DATA, OWNER, RECIPIENT, SPENDER, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID + DATA, OWNER, RECIPIENT, SPENDER, OPERATOR, OTHER, NAME, SYMBOL, BASE_URI, TOKEN_ID }; use openzeppelin::tests::utils; use openzeppelin::token::erc721::dual721::{DualCaseERC721, DualCaseERC721Trait}; @@ -23,11 +23,11 @@ use starknet::testing::set_contract_address; fn setup_snake() -> (DualCaseERC721, IERC721Dispatcher) { let mut calldata = array![]; - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); + calldata.append_serde(BASE_URI()); calldata.append_serde(OWNER()); calldata.append_serde(TOKEN_ID); - calldata.append_serde(URI); set_contract_address(OWNER()); let target = utils::deploy(SnakeERC721Mock::TEST_CLASS_HASH, calldata); (DualCaseERC721 { contract_address: target }, IERC721Dispatcher { contract_address: target }) @@ -35,11 +35,11 @@ fn setup_snake() -> (DualCaseERC721, IERC721Dispatcher) { fn setup_camel() -> (DualCaseERC721, IERC721CamelOnlyDispatcher) { let mut calldata = array![]; - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); + calldata.append_serde(BASE_URI()); calldata.append_serde(OWNER()); calldata.append_serde(TOKEN_ID); - calldata.append_serde(URI); set_contract_address(OWNER()); let target = utils::deploy(CamelERC721Mock::TEST_CLASS_HASH, calldata); ( @@ -75,8 +75,8 @@ fn setup_receiver() -> ContractAddress { fn test_dual_name() { let (snake_dispatcher, _) = setup_snake(); let (camel_dispatcher, _) = setup_camel(); - assert_eq!(snake_dispatcher.name(), NAME); - assert_eq!(camel_dispatcher.name(), NAME); + assert_eq!(snake_dispatcher.name(), NAME()); + assert_eq!(camel_dispatcher.name(), NAME()); } #[test] @@ -97,8 +97,8 @@ fn test_dual_name_exists_and_panics() { fn test_dual_symbol() { let (snake_dispatcher, _) = setup_snake(); let (camel_dispatcher, _) = setup_camel(); - assert_eq!(snake_dispatcher.symbol(), SYMBOL); - assert_eq!(camel_dispatcher.symbol(), SYMBOL); + assert_eq!(snake_dispatcher.symbol(), SYMBOL()); + assert_eq!(camel_dispatcher.symbol(), SYMBOL()); } #[test] @@ -302,7 +302,9 @@ fn test_dual_is_approved_for_all_exists_and_panics() { #[test] fn test_dual_token_uri() { let (dispatcher, _) = setup_snake(); - assert_eq!(dispatcher.token_uri(TOKEN_ID), URI); + let uri = dispatcher.token_uri(TOKEN_ID); + let expected = format!("{}{}", BASE_URI(), TOKEN_ID); + assert_eq!(uri, expected); } #[test] @@ -459,8 +461,9 @@ fn test_dual_isApprovedForAll_exists_and_panics() { #[test] fn test_dual_tokenURI() { let (dispatcher, _) = setup_camel(); - let token_uri = dispatcher.token_uri(TOKEN_ID); - assert_eq!(token_uri, URI); + let uri = dispatcher.token_uri(TOKEN_ID); + let expected = format!("{}{}", BASE_URI(), TOKEN_ID); + assert_eq!(uri, expected); } #[test] diff --git a/src/tests/token/test_erc1155.cairo b/src/tests/token/test_erc1155.cairo new file mode 100644 index 000000000..d4dadd6f7 --- /dev/null +++ b/src/tests/token/test_erc1155.cairo @@ -0,0 +1,1406 @@ +use core::starknet::storage::StorageMemberAccessTrait; +use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; +use openzeppelin::introspection; +use openzeppelin::tests::mocks::account_mocks::SnakeAccountMock; +use openzeppelin::tests::mocks::erc1155_mocks::DualCaseERC1155Mock; +use openzeppelin::tests::mocks::erc1155_receiver_mocks::{ + CamelERC1155ReceiverMock, SnakeERC1155ReceiverMock +}; +use openzeppelin::tests::mocks::src5_mocks::DualCaseSRC5Mock; +use openzeppelin::tests::utils::constants::{ + EMPTY_DATA, ZERO, OWNER, RECIPIENT, OPERATOR, OTHER, TOKEN_ID, TOKEN_ID_2, TOKEN_VALUE, + TOKEN_VALUE_2, PUBKEY +}; +use openzeppelin::tests::utils; +use openzeppelin::token::erc1155::ERC1155Component::ERC1155CamelImpl; +use openzeppelin::token::erc1155::ERC1155Component::{ + ERC1155Impl, ERC1155MetadataURIImpl, InternalImpl +}; +use openzeppelin::token::erc1155::ERC1155Component::{TransferBatch, ApprovalForAll, TransferSingle}; +use openzeppelin::token::erc1155::ERC1155Component; +use openzeppelin::token::erc1155; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; +use starknet::testing; + +// +// Setup +// + +type ComponentState = ERC1155Component::ComponentState; + +fn CONTRACT_STATE() -> DualCaseERC1155Mock::ContractState { + DualCaseERC1155Mock::contract_state_for_testing() +} +fn COMPONENT_STATE() -> ComponentState { + ERC1155Component::component_state_for_testing() +} + +fn setup() -> (ComponentState, ContractAddress) { + let mut state = COMPONENT_STATE(); + state.initializer("URI"); + + let owner = setup_account(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, TOKEN_VALUE_2].span(); + + state.batch_mint_with_acceptance_check(owner, token_ids, values, array![].span()); + utils::drop_events(ZERO(), 2); + + (state, owner) +} + +fn setup_receiver() -> ContractAddress { + utils::deploy(SnakeERC1155ReceiverMock::TEST_CLASS_HASH, array![]) +} + +fn setup_camel_receiver() -> ContractAddress { + utils::deploy(CamelERC1155ReceiverMock::TEST_CLASS_HASH, array![]) +} + +fn setup_account() -> ContractAddress { + let mut calldata = array![PUBKEY]; + utils::deploy(SnakeAccountMock::TEST_CLASS_HASH, calldata) +} + +fn setup_account_with_salt(salt: felt252) -> ContractAddress { + let mut calldata = array![PUBKEY]; + utils::deploy_with_salt(SnakeAccountMock::TEST_CLASS_HASH, calldata, salt) +} + +fn setup_src5() -> ContractAddress { + utils::deploy(DualCaseSRC5Mock::TEST_CLASS_HASH, array![]) +} + +// +// Initializers +// + +#[test] +fn test_initialize() { + let mut state = COMPONENT_STATE(); + let mock_state = CONTRACT_STATE(); + + state.initializer("URI"); + + assert_eq!(state.ERC1155_uri.read(), "URI"); + assert!(state.balance_of(OWNER(), TOKEN_ID).is_zero()); + + let supports_ierc1155 = mock_state.supports_interface(erc1155::interface::IERC1155_ID); + assert!(supports_ierc1155); + + let supports_ierc1155_metadata_uri = mock_state + .supports_interface(erc1155::interface::IERC1155_METADATA_URI_ID); + assert!(supports_ierc1155_metadata_uri); + + let supports_isrc5 = mock_state.supports_interface(introspection::interface::ISRC5_ID); + assert!(supports_isrc5); +} + +// +// balance_of & balanceOf +// + +#[test] +fn test_balance_of() { + let (state, owner) = setup(); + let balance = state.balance_of(owner, TOKEN_ID); + assert_eq!(balance, TOKEN_VALUE); +} + +#[test] +fn test_balanceOf() { + let (state, owner) = setup(); + let balance = state.balanceOf(owner, TOKEN_ID); + assert_eq!(balance, TOKEN_VALUE); +} + +// +// balance_of_batch & balanceOfBatch +// + +#[test] +fn test_balance_of_batch() { + let (state, owner) = setup(); + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID, TOKEN_ID].span(); + + let balances = state.balance_of_batch(accounts, token_ids); + assert_eq!(*balances.at(0), TOKEN_VALUE); + assert!((*balances.at(1)).is_zero()); +} + +#[test] +fn test_balanceOfBatch() { + let (state, owner) = setup(); + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID, TOKEN_ID].span(); + + let balances = state.balanceOfBatch(accounts, token_ids); + assert_eq!(*balances.at(0), TOKEN_VALUE); + assert!((*balances.at(1)).is_zero()); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length',))] +fn test_balance_of_batch_invalid_inputs() { + let (state, owner) = setup(); + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID].span(); + + state.balance_of_batch(accounts, token_ids); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length',))] +fn test_balanceOfBatch_invalid_inputs() { + let (state, owner) = setup(); + let accounts = array![owner, OTHER()].span(); + let token_ids = array![TOKEN_ID].span(); + + state.balanceOfBatch(accounts, token_ids); +} + +// +// safe_transfer_from & safeTransferFrom +// + +#[test] +fn test_safe_transfer_from_owner_to_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safe_transfer_from_owner_to_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_owner_to_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_owner_to_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safe_transfer_from_owner_to_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_owner_to_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safe_transfer_from_approved_operator() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + + testing::set_caller_address(owner); + state.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(ZERO(), owner, operator, true); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + testing::set_caller_address(operator); + state.safe_transfer_from(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), operator, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_safeTransferFrom_approved_operator() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + + testing::set_caller_address(owner); + state.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(ZERO(), owner, operator, true); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + testing::set_caller_address(operator); + state.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), operator, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender',))] +fn test_safe_transfer_from_from_zero() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safe_transfer_from(ZERO(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender',))] +fn test_safeTransferFrom_from_zero() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safeTransferFrom(ZERO(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safe_transfer_from_to_zero() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safe_transfer_from(owner, ZERO(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safeTransferFrom_to_zero() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safeTransferFrom(owner, ZERO(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator',))] +fn test_safe_transfer_from_unauthorized() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safe_transfer_from(OTHER(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator',))] +fn test_safeTransferFrom_unauthorized() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safeTransferFrom(OTHER(), owner, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test_safe_transfer_from_insufficient_balance() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safe_transfer_from(owner, OTHER(), TOKEN_ID, TOKEN_VALUE + 1, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test_safeTransferFrom_insufficient_balance() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + state.safeTransferFrom(owner, OTHER(), TOKEN_ID, TOKEN_VALUE + 1, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_safe_transfer_from_non_account_non_receiver() { + let (mut state, owner) = setup(); + let non_receiver = setup_src5(); + testing::set_caller_address(owner); + + state.safe_transfer_from(owner, non_receiver, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_safeTransferFrom_non_account_non_receiver() { + let (mut state, owner) = setup(); + let non_receiver = setup_src5(); + testing::set_caller_address(owner); + + state.safeTransferFrom(owner, non_receiver, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +// +// safe_batch_transfer_from & safeBatchTransferFrom +// + +#[test] +fn test_safe_batch_transfer_from_owner_to_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_safe_batch_transfer_from_owner_to_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_owner_to_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_owner_to_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_safe_batch_transfer_from_owner_to_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_owner_to_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + + +#[test] +fn test_safe_batch_transfer_from_approved_operator() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + let (token_ids, values) = get_ids_and_values(); + + testing::set_caller_address(owner); + state.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(ZERO(), owner, operator, true); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + testing::set_caller_address(operator); + state.safe_batch_transfer_from(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), operator, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_safeBatchTransferFrom_approved_operator() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let operator = OPERATOR(); + let (token_ids, values) = get_ids_and_values(); + + testing::set_caller_address(owner); + state.set_approval_for_all(operator, true); + assert_only_event_approval_for_all(ZERO(), owner, operator, true); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + testing::set_caller_address(operator); + state.safeBatchTransferFrom(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), operator, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender',))] +fn test_safe_batch_transfer_from_from_zero() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.safe_batch_transfer_from(ZERO(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender',))] +fn test_safeBatchTransferFrom_from_zero() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.safeBatchTransferFrom(ZERO(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safe_batch_transfer_from_to_zero() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.safe_batch_transfer_from(owner, ZERO(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safeBatchTransferFrom_to_zero() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.safeBatchTransferFrom(owner, ZERO(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator',))] +fn test_safe_batch_transfer_from_unauthorized() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.safe_batch_transfer_from(OTHER(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: unauthorized operator',))] +fn test_safeBatchTransferFrom_unauthorized() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.safeBatchTransferFrom(OTHER(), owner, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test_safe_batch_transfer_from_insufficient_balance() { + let (mut state, owner) = setup(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE + 1, TOKEN_VALUE_2].span(); + testing::set_caller_address(owner); + + state.safe_batch_transfer_from(owner, OTHER(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test_safeBatchTransferFrom_insufficient_balance() { + let (mut state, owner) = setup(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE + 1, TOKEN_VALUE_2].span(); + testing::set_caller_address(owner); + + state.safeBatchTransferFrom(owner, OTHER(), token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_safe_batch_transfer_from_non_account_non_receiver() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_split_values(5); + let non_receiver = setup_src5(); + testing::set_caller_address(owner); + + state.safe_batch_transfer_from(owner, non_receiver, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_safeBatchTransferFrom_non_account_non_receiver() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_split_values(5); + let non_receiver = setup_src5(); + testing::set_caller_address(owner); + + state.safeBatchTransferFrom(owner, non_receiver, token_ids, values, EMPTY_DATA()); +} + +// +// set_approval_for_all & is_approved_for_all +// + +#[test] +fn test_set_approval_for_all_and_is_approved_for_all() { + let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); + + let not_approved_for_all = !state.is_approved_for_all(OWNER(), OPERATOR()); + assert!(not_approved_for_all); + + state.set_approval_for_all(OPERATOR(), true); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), true); + + let is_approved_for_all = state.is_approved_for_all(OWNER(), OPERATOR()); + assert!(is_approved_for_all); + + state.set_approval_for_all(OPERATOR(), false); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), false); + + let not_approved_for_all = !state.is_approved_for_all(OWNER(), OPERATOR()); + assert!(not_approved_for_all); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_true() { + let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); + state.set_approval_for_all(OWNER(), true); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_false() { + let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); + state.set_approval_for_all(OWNER(), false); +} + +// +// setApprovalForAll & isApprovedForAll +// + +#[test] +fn test_setApprovalForAll_and_isApprovedForAll() { + let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); + + let not_approved_for_all = !state.isApprovedForAll(OWNER(), OPERATOR()); + assert!(not_approved_for_all); + + state.setApprovalForAll(OPERATOR(), true); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), true); + + let is_approved_for_all = state.isApprovedForAll(OWNER(), OPERATOR()); + assert!(is_approved_for_all); + + state.setApprovalForAll(OPERATOR(), false); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), false); + + let not_approved_for_all = !state.isApprovedForAll(OWNER(), OPERATOR()); + assert!(not_approved_for_all); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_setApprovalForAll_owner_equal_operator_true() { + let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); + state.set_approval_for_all(OWNER(), true); +} + +#[test] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_setApprovalForAll_owner_equal_operator_false() { + let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); + state.setApprovalForAll(OWNER(), false); +} + +// +// update +// + +#[test] +fn test_update_single_from_non_zero_to_non_zero() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE].span(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.update(owner, recipient, token_ids, values); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_update_batch_from_non_zero_to_non_zero() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.update(owner, recipient, token_ids, values); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_update_from_non_zero_to_zero() { + let (mut state, owner) = setup(); + let recipient = ZERO(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.update(owner, recipient, token_ids, values); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_to_zero_batch(owner, recipient, token_ids); +} + +#[test] +fn test_update_from_zero_to_non_zero() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let sender = ZERO(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_from_zero_batch(sender, recipient, token_ids); + + state.update(sender, recipient, token_ids, values); + assert_only_event_transfer_batch(ZERO(), owner, sender, recipient, token_ids, values); + + assert_state_after_transfer_from_zero_batch(sender, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length',))] +fn test_update_token_ids_len_greater_than_values() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE].span(); + + state.update(owner, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length',))] +fn test_update_values_len_greater_than_token_ids() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE, TOKEN_VALUE_2].span(); + + state.update(owner, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test_update_insufficient_balance() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE + 1].span(); + + state.update(owner, recipient, token_ids, values); +} + + +// +// update_with_acceptance_check +// + +#[test] +fn test_update_wac_single_from_non_zero_to_non_zero() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE].span(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_update_wac_single_from_non_zero_to_non_zero_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE].span(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_update_wac_single_from_non_zero_to_non_zero_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE].span(); + testing::set_caller_address(owner); + + assert_state_before_transfer_single(owner, recipient, TOKEN_ID); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), owner, owner, recipient, TOKEN_ID, TOKEN_VALUE); + + assert_state_after_transfer_single(owner, recipient, TOKEN_ID); +} + +#[test] +fn test_update_wac_batch_from_non_zero_to_non_zero() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_update_wac_batch_from_non_zero_to_non_zero_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +fn test_update_wac_batch_from_non_zero_to_non_zero_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_batch(owner, recipient, token_ids, values); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, owner, recipient, token_ids, values); + + assert_state_after_transfer_batch(owner, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('CONTRACT_NOT_DEPLOYED',))] +fn test_update_wac_from_non_zero_to_zero() { + let (mut state, owner) = setup(); + let recipient = ZERO(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); +} + +#[test] +fn test_update_wac_from_zero_to_non_zero() { + let (mut state, owner) = setup(); + let recipient = setup_receiver(); + let sender = ZERO(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_from_zero_batch(sender, recipient, token_ids); + + state.update_with_acceptance_check(sender, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, sender, recipient, token_ids, values); + + assert_state_after_transfer_from_zero_batch(sender, recipient, token_ids, values); +} + +#[test] +fn test_update_wac_from_zero_to_non_zero_camel_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_camel_receiver(); + let sender = ZERO(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_from_zero_batch(sender, recipient, token_ids); + + state.update_with_acceptance_check(sender, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, sender, recipient, token_ids, values); + + assert_state_after_transfer_from_zero_batch(sender, recipient, token_ids, values); +} + +#[test] +fn test_update_wac_from_zero_to_non_zero_account() { + let (mut state, owner) = setup(); + let recipient = setup_account_with_salt(1); + let sender = ZERO(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + assert_state_before_transfer_from_zero_batch(sender, recipient, token_ids); + + state.update_with_acceptance_check(sender, recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), owner, sender, recipient, token_ids, values); + + assert_state_after_transfer_from_zero_batch(sender, recipient, token_ids, values); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length',))] +fn test_update_wac_token_ids_len_greater_than_values() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE].span(); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: no equal array length',))] +fn test_update_wac_values_len_greater_than_token_ids() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE, TOKEN_VALUE_2].span(); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test_update_wac_insufficient_balance() { + let (mut state, owner) = setup(); + let recipient = RECIPIENT(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE + 1].span(); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_update_wac_single_to_non_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_src5(); + let token_ids = array![TOKEN_ID].span(); + let values = array![TOKEN_VALUE].span(); + testing::set_caller_address(owner); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_update_wac_batch_to_non_receiver() { + let (mut state, owner) = setup(); + let recipient = setup_src5(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + state.update_with_acceptance_check(owner, recipient, token_ids, values, EMPTY_DATA()); +} + +// +// mint_with_acceptance_check +// + +#[test] +fn test_mint_wac_to_receiver() { + let mut state = COMPONENT_STATE(); + let recipient = setup_receiver(); + testing::set_caller_address(OTHER()); + + let balance_of_recipient = state.balance_of(recipient, TOKEN_ID); + assert!(balance_of_recipient.is_zero()); + + state.mint_with_acceptance_check(recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), OTHER(), ZERO(), recipient, TOKEN_ID, TOKEN_VALUE); + + let balance_of_recipient = state.balance_of(recipient, TOKEN_ID); + assert_eq!(balance_of_recipient, TOKEN_VALUE); +} + +#[test] +fn test_mint_wac_to_account() { + let mut state = COMPONENT_STATE(); + let recipient = setup_account_with_salt(1); + testing::set_caller_address(OTHER()); + + let balance_of_recipient = state.balance_of(recipient, TOKEN_ID); + assert!(balance_of_recipient.is_zero()); + + state.mint_with_acceptance_check(recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_only_event_transfer_single(ZERO(), OTHER(), ZERO(), recipient, TOKEN_ID, TOKEN_VALUE); + + let balance_of_recipient = state.balance_of(recipient, TOKEN_ID); + assert_eq!(balance_of_recipient, TOKEN_VALUE); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_mint_wac_to_zero() { + let mut state = COMPONENT_STATE(); + let recipient = ZERO(); + + state.mint_with_acceptance_check(recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_mint_wac_to_non_receiver() { + let mut state = COMPONENT_STATE(); + let recipient = setup_src5(); + + state.mint_with_acceptance_check(recipient, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); +} + +// +// batch_mint_with_acceptance_check +// + +#[test] +fn test_batch_mint_wac_to_receiver() { + let mut state = COMPONENT_STATE(); + let recipient = setup_receiver(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(OTHER()); + + let balance_of_recipient_token_1_before = state.balance_of(recipient, TOKEN_ID); + assert!(balance_of_recipient_token_1_before.is_zero()); + let balance_of_recipient_token_2_before = state.balance_of(recipient, TOKEN_ID_2); + assert!(balance_of_recipient_token_2_before.is_zero()); + + state.batch_mint_with_acceptance_check(recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), OTHER(), ZERO(), recipient, token_ids, values); + + let balance_of_recipient_token_1_after = state.balance_of(recipient, TOKEN_ID); + assert_eq!(balance_of_recipient_token_1_after, TOKEN_VALUE); + let balance_of_recipient_token_2_after = state.balance_of(recipient, TOKEN_ID_2); + assert_eq!(balance_of_recipient_token_2_after, TOKEN_VALUE_2); +} + +#[test] +fn test_batch_mint_wac_to_account() { + let mut state = COMPONENT_STATE(); + let recipient = setup_account_with_salt(1); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(OTHER()); + + let balance_of_recipient_token_1_before = state.balance_of(recipient, TOKEN_ID); + assert!(balance_of_recipient_token_1_before.is_zero()); + let balance_of_recipient_token_2_before = state.balance_of(recipient, TOKEN_ID_2); + assert!(balance_of_recipient_token_2_before.is_zero()); + + state.batch_mint_with_acceptance_check(recipient, token_ids, values, EMPTY_DATA()); + assert_only_event_transfer_batch(ZERO(), OTHER(), ZERO(), recipient, token_ids, values); + + let balance_of_recipient_token_1_after = state.balance_of(recipient, TOKEN_ID); + assert_eq!(balance_of_recipient_token_1_after, TOKEN_VALUE); + let balance_of_recipient_token_2_after = state.balance_of(recipient, TOKEN_ID_2); + assert_eq!(balance_of_recipient_token_2_after, TOKEN_VALUE_2); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_batch_mint_wac_to_zero() { + let mut state = COMPONENT_STATE(); + let recipient = ZERO(); + let (token_ids, values) = get_ids_and_values(); + + state.batch_mint_with_acceptance_check(recipient, token_ids, values, EMPTY_DATA()); +} + +#[test] +#[should_panic(expected: ('ERC1155: safe transfer failed',))] +fn test_batch_mint_wac_to_non_receiver() { + let mut state = COMPONENT_STATE(); + let recipient = setup_src5(); + let (token_ids, values) = get_ids_and_values(); + + state.batch_mint_with_acceptance_check(recipient, token_ids, values, EMPTY_DATA()); +} + +// +// burn & batch_burn +// + +#[test] +fn test_burn() { + let (mut state, owner) = setup(); + testing::set_caller_address(owner); + + let balance_of_owner = state.balance_of(owner, TOKEN_ID); + assert_eq!(balance_of_owner, TOKEN_VALUE); + + state.burn(owner, TOKEN_ID, TOKEN_VALUE); + assert_only_event_transfer_single(ZERO(), owner, owner, ZERO(), TOKEN_ID, TOKEN_VALUE); + + let balance_of_owner = state.balance_of(owner, TOKEN_ID); + assert!(balance_of_owner.is_zero()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender',))] +fn test_burn_from_zero() { + let mut state = COMPONENT_STATE(); + state.burn(ZERO(), TOKEN_ID, TOKEN_VALUE); +} + + +#[test] +fn test_batch_burn() { + let (mut state, owner) = setup(); + let (token_ids, values) = get_ids_and_values(); + testing::set_caller_address(owner); + + let balance_of_owner_token_1_before = state.balance_of(owner, TOKEN_ID); + assert_eq!(balance_of_owner_token_1_before, TOKEN_VALUE); + let balance_of_owner_token_2_before = state.balance_of(owner, TOKEN_ID_2); + assert_eq!(balance_of_owner_token_2_before, TOKEN_VALUE_2); + + state.batch_burn(owner, token_ids, values); + assert_only_event_transfer_batch(ZERO(), owner, owner, ZERO(), token_ids, values); + + let balance_of_owner_token_1_after = state.balance_of(owner, TOKEN_ID); + assert!(balance_of_owner_token_1_after.is_zero()); + let balance_of_owner_token_2_after = state.balance_of(owner, TOKEN_ID_2); + assert!(balance_of_owner_token_2_after.is_zero()); +} + +#[test] +#[should_panic(expected: ('ERC1155: invalid sender',))] +fn test_batch_burn_from_zero() { + let mut state = COMPONENT_STATE(); + let (token_ids, values) = get_ids_and_values(); + state.batch_burn(ZERO(), token_ids, values); +} + +// +// Helpers +// + +fn assert_state_before_transfer_single( + sender: ContractAddress, recipient: ContractAddress, token_id: u256 +) { + let state = COMPONENT_STATE(); + assert_eq!(state.balance_of(sender, token_id), TOKEN_VALUE); + assert!(state.balance_of(recipient, token_id).is_zero()); +} + +fn assert_state_after_transfer_single( + sender: ContractAddress, recipient: ContractAddress, token_id: u256 +) { + let state = COMPONENT_STATE(); + assert!(state.balance_of(sender, token_id).is_zero()); + assert_eq!(state.balance_of(recipient, token_id), TOKEN_VALUE); +} + +fn assert_state_before_transfer_batch( + sender: ContractAddress, recipient: ContractAddress, token_ids: Span, values: Span +) { + let state = COMPONENT_STATE(); + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = state.balance_of(sender, *token_ids.at(index)); + assert_eq!(balance_of_sender, *values.at(index)); + let balance_of_recipient = state.balance_of(recipient, *token_ids.at(index)); + assert!(balance_of_recipient.is_zero()); + + index += 1; + } +} + +fn assert_state_before_transfer_from_zero_batch( + sender: ContractAddress, recipient: ContractAddress, token_ids: Span +) { + let state = COMPONENT_STATE(); + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = state.balance_of(sender, *token_ids.at(index)); + assert!(balance_of_sender.is_zero()); + let balance_of_recipient = state.balance_of(recipient, *token_ids.at(index)); + assert!(balance_of_recipient.is_zero()); + + index += 1; + } +} + +fn assert_state_after_transfer_batch( + sender: ContractAddress, recipient: ContractAddress, token_ids: Span, values: Span +) { + let state = COMPONENT_STATE(); + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = state.balance_of(sender, *token_ids.at(index)); + assert!(balance_of_sender.is_zero()); + let balance_of_recipient = state.balance_of(recipient, *token_ids.at(index)); + assert_eq!(balance_of_recipient, *values.at(index)); + + index += 1; + } +} + +fn assert_state_after_transfer_to_zero_batch( + sender: ContractAddress, recipient: ContractAddress, token_ids: Span +) { + let state = COMPONENT_STATE(); + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = state.balance_of(sender, *token_ids.at(index)); + assert!(balance_of_sender.is_zero()); + let balance_of_recipient = state.balance_of(recipient, *token_ids.at(index)); + assert!(balance_of_recipient.is_zero()); + + index += 1; + } +} + +fn assert_state_after_transfer_from_zero_batch( + sender: ContractAddress, recipient: ContractAddress, token_ids: Span, values: Span +) { + let state = COMPONENT_STATE(); + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let balance_of_sender = state.balance_of(sender, *token_ids.at(index)); + assert!(balance_of_sender.is_zero()); + let balance_of_recipient = state.balance_of(recipient, *token_ids.at(index)); + assert_eq!(balance_of_recipient, *values.at(index)); + + index += 1; + } +} + +fn assert_event_approval_for_all( + contract: ContractAddress, owner: ContractAddress, operator: ContractAddress, approved: bool +) { + let event = utils::pop_log::(contract).unwrap(); + let expected = ERC1155Component::Event::ApprovalForAll( + ApprovalForAll { owner, operator, approved } + ); + assert!(event == expected); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("ApprovalForAll")); + indexed_keys.append_serde(owner); + indexed_keys.append_serde(operator); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_event_transfer_single( + contract: ContractAddress, + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256 +) { + let event = utils::pop_log::(contract).unwrap(); + let id = token_id; + let expected = ERC1155Component::Event::TransferSingle( + TransferSingle { operator, from, to, id, value } + ); + assert!(event == expected); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("TransferSingle")); + indexed_keys.append_serde(operator); + indexed_keys.append_serde(from); + indexed_keys.append_serde(to); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_event_transfer_batch( + contract: ContractAddress, + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span +) { + let event = utils::pop_log::(contract).unwrap(); + let ids = token_ids; + let expected = ERC1155Component::Event::TransferBatch( + TransferBatch { operator, from, to, ids, values } + ); + assert!(event == expected); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("TransferBatch")); + indexed_keys.append_serde(operator); + indexed_keys.append_serde(from); + indexed_keys.append_serde(to); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_only_event_transfer_single( + contract: ContractAddress, + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256 +) { + assert_event_transfer_single(contract, operator, from, to, token_id, value); + utils::assert_no_events_left(contract); +} + +fn assert_only_event_transfer_batch( + contract: ContractAddress, + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span +) { + assert_event_transfer_batch(contract, operator, from, to, token_ids, values); + utils::assert_no_events_left(contract); +} + +fn assert_only_event_approval_for_all( + contract: ContractAddress, owner: ContractAddress, operator: ContractAddress, approved: bool +) { + assert_event_approval_for_all(contract, owner, operator, approved); + utils::assert_no_events_left(contract); +} + +fn get_ids_and_values() -> (Span, Span) { + let ids = array![TOKEN_ID, TOKEN_ID_2].span(); + let values = array![TOKEN_VALUE, TOKEN_VALUE_2].span(); + (ids, values) +} + +fn get_ids_and_split_values(split: u256) -> (Span, Span) { + let ids = array![TOKEN_ID, TOKEN_ID].span(); + let values = array![TOKEN_VALUE - split, split].span(); + (ids, values) +} diff --git a/src/tests/token/test_erc1155_receiver.cairo b/src/tests/token/test_erc1155_receiver.cairo new file mode 100644 index 000000000..5d3456a52 --- /dev/null +++ b/src/tests/token/test_erc1155_receiver.cairo @@ -0,0 +1,80 @@ +use openzeppelin::introspection::interface::ISRC5_ID; +use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; +use openzeppelin::tests::mocks::erc1155_receiver_mocks::DualCaseERC1155ReceiverMock; +use openzeppelin::tests::utils::constants::{OWNER, OPERATOR, TOKEN_ID, TOKEN_VALUE, EMPTY_DATA}; +use openzeppelin::token::erc1155::ERC1155ReceiverComponent::{ + ERC1155ReceiverImpl, ERC1155ReceiverCamelImpl, InternalImpl +}; +use openzeppelin::token::erc1155::interface::IERC1155_RECEIVER_ID; + +fn STATE() -> DualCaseERC1155ReceiverMock::ContractState { + DualCaseERC1155ReceiverMock::contract_state_for_testing() +} + +#[test] +fn test_initializer() { + let mut state = STATE(); + state.erc1155_receiver.initializer(); + + let supports_ierc1155_receiver = state.src5.supports_interface(IERC1155_RECEIVER_ID); + assert!(supports_ierc1155_receiver); + + let supports_isrc5 = state.src5.supports_interface(ISRC5_ID); + assert!(supports_isrc5); +} + +// +// on_erc1155_received & onERC1155Received +// + +#[test] +fn test_on_erc1155_received() { + let mut state = STATE(); + let on_erc1155_received = state + .erc1155_receiver + .on_erc1155_received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_eq!(on_erc1155_received, IERC1155_RECEIVER_ID); +} + +#[test] +fn test_onERC1155Received() { + let mut state = STATE(); + let on_erc1155_received = state + .erc1155_receiver + .onERC1155Received(OPERATOR(), OWNER(), TOKEN_ID, TOKEN_VALUE, EMPTY_DATA()); + assert_eq!(on_erc1155_received, IERC1155_RECEIVER_ID); +} + +// +// on_erc1155_batch_received & onERC1155BatchReceived +// + +#[test] +fn test_on_erc1155_batch_received() { + let mut state = STATE(); + let (token_ids, values) = get_ids_and_values(); + let on_erc1155_received = state + .erc1155_receiver + .on_erc1155_batch_received(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); + assert_eq!(on_erc1155_received, IERC1155_RECEIVER_ID); +} + +#[test] +fn test_onERC1155BatchReceived() { + let mut state = STATE(); + let (token_ids, values) = get_ids_and_values(); + let on_erc1155_received = state + .erc1155_receiver + .onERC1155BatchReceived(OPERATOR(), OWNER(), token_ids, values, EMPTY_DATA()); + assert_eq!(on_erc1155_received, IERC1155_RECEIVER_ID); +} + +// +// Helpers +// + +fn get_ids_and_values() -> (Span, Span) { + let token_ids = array![TOKEN_ID, TOKEN_ID].span(); + let values = array![TOKEN_VALUE, TOKEN_VALUE].span(); + (token_ids, values) +} diff --git a/src/tests/token/test_erc20.cairo b/src/tests/token/test_erc20.cairo index f633db5aa..e761007a4 100644 --- a/src/tests/token/test_erc20.cairo +++ b/src/tests/token/test_erc20.cairo @@ -24,7 +24,7 @@ fn COMPONENT_STATE() -> ComponentState { fn setup() -> ComponentState { let mut state = COMPONENT_STATE(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL()); state._mint(OWNER(), SUPPLY); utils::drop_event(ZERO()); state @@ -37,10 +37,10 @@ fn setup() -> ComponentState { #[test] fn test_initializer() { let mut state = COMPONENT_STATE(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL()); - assert_eq!(state.name(), NAME); - assert_eq!(state.symbol(), SYMBOL); + assert_eq!(state.name(), NAME()); + assert_eq!(state.symbol(), SYMBOL()); assert_eq!(state.decimals(), DECIMALS); assert_eq!(state.total_supply(), 0); } @@ -433,13 +433,13 @@ fn test__burn_from_zero() { // fn assert_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.owner, owner); - assert_eq!(event.spender, spender); - assert_eq!(event.value, value); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = ERC20Component::Event::Approval(Approval { owner, spender, value }); + assert!(event == expected); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Approval")); indexed_keys.append_serde(owner); indexed_keys.append_serde(spender); utils::assert_indexed_keys(event, indexed_keys.span()) @@ -451,13 +451,13 @@ fn assert_only_event_approval(owner: ContractAddress, spender: ContractAddress, } fn assert_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.from, from); - assert_eq!(event.to, to); - assert_eq!(event.value, value); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = ERC20Component::Event::Transfer(Transfer { from, to, value }); + assert!(event == expected); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Transfer")); indexed_keys.append_serde(from); indexed_keys.append_serde(to); utils::assert_indexed_keys(event, indexed_keys.span()); diff --git a/src/tests/token/test_erc721.cairo b/src/tests/token/test_erc721.cairo index 32263f2c5..51f27349f 100644 --- a/src/tests/token/test_erc721.cairo +++ b/src/tests/token/test_erc721.cairo @@ -10,7 +10,8 @@ use openzeppelin::tests::mocks::erc721_receiver_mocks::{ }; use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; use openzeppelin::tests::utils::constants::{ - DATA, ZERO, OWNER, RECIPIENT, SPENDER, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID, PUBKEY, + DATA, ZERO, OWNER, RECIPIENT, SPENDER, OPERATOR, OTHER, NAME, SYMBOL, TOKEN_ID, PUBKEY, + BASE_URI, BASE_URI_2 }; use openzeppelin::tests::utils; use openzeppelin::token::erc721::ERC721Component::{ @@ -41,7 +42,7 @@ fn COMPONENT_STATE() -> ComponentState { fn setup() -> ComponentState { let mut state = COMPONENT_STATE(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL(), BASE_URI()); state._mint(OWNER(), TOKEN_ID); utils::drop_event(ZERO()); state @@ -74,10 +75,11 @@ fn test_initialize() { let mut state = COMPONENT_STATE(); let mock_state = CONTRACT_STATE(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL(), BASE_URI()); - assert_eq!(state.name(), NAME); - assert_eq!(state.symbol(), SYMBOL); + assert_eq!(state.name(), NAME()); + assert_eq!(state.symbol(), SYMBOL()); + assert_eq!(state._base_uri(), BASE_URI()); assert!(state.balance_of(OWNER()).is_zero()); let supports_ierc721 = mock_state.supports_interface(erc721::interface::IERC721_ID); @@ -121,6 +123,25 @@ fn test_owner_of_non_minted() { state.owner_of(u256_from_felt252(7)); } +#[test] +fn test_token_uri() { + let state = setup(); + + let uri = state.token_uri(TOKEN_ID); + let expected = format!("{}{}", BASE_URI(), TOKEN_ID); + assert_eq!(uri, expected); +} + +#[test] +fn test_token_uri_not_set() { + let mut state = COMPONENT_STATE(); + + state._mint(OWNER(), TOKEN_ID); + let uri = state.token_uri(TOKEN_ID); + let expected: ByteArray = ""; + assert_eq!(uri, expected); +} + #[test] #[should_panic(expected: ('ERC721: invalid token ID',))] fn test_token_uri_non_minted() { @@ -772,7 +793,7 @@ fn test_safe_transfer_from_to_owner() { let mut state = COMPONENT_STATE(); let token_id = TOKEN_ID; let owner = setup_receiver(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL(), BASE_URI()); state._mint(owner, token_id); utils::drop_event(ZERO()); @@ -792,7 +813,7 @@ fn test_safeTransferFrom_to_owner() { let mut state = COMPONENT_STATE(); let token_id = TOKEN_ID; let owner = setup_receiver(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL(), BASE_URI()); state._mint(owner, token_id); utils::drop_event(ZERO()); @@ -812,7 +833,7 @@ fn test_safe_transfer_from_to_owner_camel() { let mut state = COMPONENT_STATE(); let token_id = TOKEN_ID; let owner = setup_camel_receiver(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL(), BASE_URI()); state._mint(owner, token_id); utils::drop_event(ZERO()); @@ -832,7 +853,7 @@ fn test_safeTransferFrom_to_owner_camel() { let mut state = COMPONENT_STATE(); let token_id = TOKEN_ID; let owner = setup_camel_receiver(); - state.initializer(NAME, SYMBOL); + state.initializer(NAME(), SYMBOL(), BASE_URI()); state._mint(owner, token_id); utils::drop_event(ZERO()); @@ -1231,23 +1252,36 @@ fn test__burn_nonexistent() { } // -// _set_token_uri +// _set_base_uri & _base_uri // #[test] -fn test__set_token_uri() { +fn test__base_uri_not_set() { + let mut state = COMPONENT_STATE(); + + let base_uri = state._base_uri(); + assert_eq!(base_uri, ""); +} + +#[test] +fn test__base_uri() { let mut state = setup(); - assert!(state.token_uri(TOKEN_ID).is_zero()); - state._set_token_uri(TOKEN_ID, URI); - assert_eq!(state.token_uri(TOKEN_ID), URI); + let base_uri = state._base_uri(); + assert_eq!(base_uri, BASE_URI()); } #[test] -#[should_panic(expected: ('ERC721: invalid token ID',))] -fn test__set_token_uri_nonexistent() { +fn test__set_base_uri() { let mut state = COMPONENT_STATE(); - state._set_token_uri(TOKEN_ID, URI); + + state._set_base_uri(BASE_URI()); + let base_uri = state._base_uri(); + assert_eq!(base_uri, BASE_URI()); + + state._set_base_uri(BASE_URI_2()); + let base_uri_2 = state._base_uri(); + assert_eq!(base_uri_2, BASE_URI_2()); } // @@ -1286,28 +1320,30 @@ fn assert_state_after_mint(recipient: ContractAddress, token_id: u256) { fn assert_event_approval_for_all( owner: ContractAddress, operator: ContractAddress, approved: bool ) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.owner, owner); - assert_eq!(event.operator, operator); - assert_eq!(event.approved, approved); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = ERC721Component::Event::ApprovalForAll( + ApprovalForAll { owner, operator, approved } + ); + assert!(event == expected); utils::assert_no_events_left(ZERO()); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("ApprovalForAll")); indexed_keys.append_serde(owner); indexed_keys.append_serde(operator); utils::assert_indexed_keys(event, indexed_keys.span()); } fn assert_event_approval(owner: ContractAddress, approved: ContractAddress, token_id: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.owner, owner); - assert_eq!(event.approved, approved); - assert_eq!(event.token_id, token_id); + let event = utils::pop_log::(ZERO()).unwrap(); + let expected = ERC721Component::Event::Approval(Approval { owner, approved, token_id }); + assert!(event == expected); utils::assert_no_events_left(ZERO()); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Approval")); indexed_keys.append_serde(owner); indexed_keys.append_serde(approved); indexed_keys.append_serde(token_id); @@ -1315,14 +1351,14 @@ fn assert_event_approval(owner: ContractAddress, approved: ContractAddress, toke } fn assert_event_transfer(from: ContractAddress, to: ContractAddress, token_id: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); - assert_eq!(event.from, from); - assert_eq!(event.to, to); - assert_eq!(event.token_id, token_id); + let event = testing::pop_log::(ZERO()).unwrap(); + let expected = ERC721Component::Event::Transfer(Transfer { from, to, token_id }); + assert!(event == expected); utils::assert_no_events_left(ZERO()); // Check indexed keys let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("Transfer")); indexed_keys.append_serde(from); indexed_keys.append_serde(to); indexed_keys.append_serde(token_id); diff --git a/src/tests/token/test_erc721_receiver.cairo b/src/tests/token/test_erc721_receiver.cairo index c7c8c4f14..72f8272a2 100644 --- a/src/tests/token/test_erc721_receiver.cairo +++ b/src/tests/token/test_erc721_receiver.cairo @@ -1,7 +1,7 @@ use openzeppelin::introspection::interface::ISRC5_ID; use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::tests::mocks::erc721_receiver_mocks::DualCaseERC721ReceiverMock; -use openzeppelin::tests::utils::constants::{OWNER, OPERATOR, TOKEN_ID}; +use openzeppelin::tests::utils::constants::{OWNER, OPERATOR, TOKEN_ID, DATA}; use openzeppelin::token::erc721::ERC721ReceiverComponent::{ ERC721ReceiverImpl, ERC721ReceiverCamelImpl, InternalImpl }; @@ -17,9 +17,9 @@ fn test_initializer() { state.erc721_receiver.initializer(); let supports_ierc721_receiver = state.src5.supports_interface(IERC721_RECEIVER_ID); - let supports_isrc5 = state.src5.supports_interface(ISRC5_ID); - assert!(supports_ierc721_receiver); + + let supports_isrc5 = state.src5.supports_interface(ISRC5_ID); assert!(supports_isrc5); } diff --git a/src/tests/upgrades/test_upgradeable.cairo b/src/tests/upgrades/test_upgradeable.cairo index 97ea31097..fdad7c8e3 100644 --- a/src/tests/upgrades/test_upgradeable.cairo +++ b/src/tests/upgrades/test_upgradeable.cairo @@ -7,6 +7,7 @@ use openzeppelin::tests::mocks::upgrades_mocks::{ use openzeppelin::tests::utils::constants::{CLASS_HASH_ZERO, ZERO}; use openzeppelin::tests::utils; use openzeppelin::upgrades::UpgradeableComponent::Upgraded; +use openzeppelin::upgrades::UpgradeableComponent; use starknet::ClassHash; use starknet::ContractAddress; @@ -87,8 +88,9 @@ fn test_remove_selector_fails_in_v2() { // fn assert_event_upgraded(class_hash: ClassHash, contract: ContractAddress) { - let event = utils::pop_log::(contract).unwrap(); - assert!(event.class_hash == class_hash); + let event = utils::pop_log::(contract).unwrap(); + let expected = UpgradeableComponent::Event::Upgraded(Upgraded { class_hash }); + assert!(event == expected); } fn assert_only_event_upgraded(class_hash: ClassHash, contract: ContractAddress) { diff --git a/src/tests/utils.cairo b/src/tests/utils.cairo index ecb202c30..22aefd126 100644 --- a/src/tests/utils.cairo +++ b/src/tests/utils.cairo @@ -12,20 +12,24 @@ fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAdd address } -/// Pop the earliest unpopped logged event for the contract as the requested type -/// and checks there's no more keys or data left on the event, preventing unaccounted params. -/// -/// This function also removes the first key from the event, to match the event -/// structure key params without the event ID. +fn deploy_with_salt( + contract_class_hash: felt252, calldata: Array, salt: felt252 +) -> ContractAddress { + let (address, _) = starknet::deploy_syscall( + contract_class_hash.try_into().unwrap(), salt, calldata.span(), false + ) + .unwrap_syscall(); + address +} + +/// Pop the earliest unpopped logged event enum for the contract and checks +/// there's no more keys or data left on the event, preventing unaccounted params. /// -/// This method doesn't currently work for components events that are not flattened -/// because an extra key is added, pushing the event ID key to the second position. +/// CAUTION: If the event enum contains two `flat` events with the same structure (member types), +/// this function will always match the first event, even when the second one is emitted. fn pop_log, +starknet::Event>(address: ContractAddress) -> Option { let (mut keys, mut data) = testing::pop_log_raw(address)?; - // Remove the event ID from the keys - let _ = keys.pop_front(); - let ret = starknet::Event::deserialize(ref keys, ref data); assert!(data.is_empty(), "Event has extra data"); assert!(keys.is_empty(), "Event has extra keys"); @@ -36,6 +40,9 @@ fn pop_log, +starknet::Event>(address: ContractAddress) -> Option /// /// `expected_keys` must include all indexed event keys for `event` in the order /// that they're defined. +/// +/// If the event is not flattened, the first key will be the event member name +/// e.g. selector!("EnumMemberName"). fn assert_indexed_keys, +starknet::Event>(event: T, expected_keys: Span) { let mut keys = array![]; let mut data = array![]; diff --git a/src/tests/utils/constants.cairo b/src/tests/utils/constants.cairo index 740d40252..6810646b3 100644 --- a/src/tests/utils/constants.cairo +++ b/src/tests/utils/constants.cairo @@ -6,15 +6,15 @@ use starknet::class_hash_const; use starknet::contract_address_const; use starknet::secp256k1::secp256k1_get_point_from_x_syscall; -const NAME: felt252 = 'NAME'; -const SYMBOL: felt252 = 'SYMBOL'; const DECIMALS: u8 = 18_u8; const SUPPLY: u256 = 2000; const VALUE: u256 = 300; const ROLE: felt252 = 'ROLE'; const OTHER_ROLE: felt252 = 'OTHER_ROLE'; -const URI: felt252 = 'URI'; const TOKEN_ID: u256 = 21; +const TOKEN_ID_2: u256 = 121; +const TOKEN_VALUE: u256 = 42; +const TOKEN_VALUE_2: u256 = 142; const PUBKEY: felt252 = 'PUBKEY'; const NEW_PUBKEY: felt252 = 'NEW_PUBKEY'; const SALT: felt252 = 'SALT'; @@ -26,6 +26,22 @@ const QUERY_OFFSET: felt252 = 0x100000000000000000000000000000000; // QUERY_OFFSET + MIN_TRANSACTION_VERSION const QUERY_VERSION: felt252 = 0x100000000000000000000000000000001; +fn NAME() -> ByteArray { + "NAME" +} + +fn SYMBOL() -> ByteArray { + "SYMBOL" +} + +fn BASE_URI() -> ByteArray { + "https://api.example.com/v1/" +} + +fn BASE_URI_2() -> ByteArray { + "https://api.example.com/v2/" +} + fn ETH_PUBKEY() -> EthPublicKey { secp256k1_get_point_from_x_syscall(3, false).unwrap_syscall().unwrap() } @@ -91,3 +107,7 @@ fn DATA(success: bool) -> Span { } data.span() } + +fn EMPTY_DATA() -> Span { + array![].span() +} diff --git a/src/token.cairo b/src/token.cairo index f9a848d01..f5b6d2a37 100644 --- a/src/token.cairo +++ b/src/token.cairo @@ -1,2 +1,3 @@ +mod erc1155; mod erc20; mod erc721; diff --git a/src/token/erc1155.cairo b/src/token/erc1155.cairo new file mode 100644 index 000000000..43434d35d --- /dev/null +++ b/src/token/erc1155.cairo @@ -0,0 +1,8 @@ +mod dual1155; +mod dual1155_receiver; +mod erc1155; +mod erc1155_receiver; +mod interface; + +use erc1155::ERC1155Component; +use erc1155_receiver::ERC1155ReceiverComponent; diff --git a/src/token/erc1155/dual1155.cairo b/src/token/erc1155/dual1155.cairo new file mode 100644 index 000000000..3d27bbc73 --- /dev/null +++ b/src/token/erc1155/dual1155.cairo @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (token/erc1155/dual1155.cairo) + +use openzeppelin::utils::UnwrapAndCast; +use openzeppelin::utils::selectors; +use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::utils::try_selector_with_fallback; +use starknet::ContractAddress; +use starknet::SyscallResultTrait; +use starknet::call_contract_syscall; + +#[derive(Copy, Drop)] +struct DualCaseERC1155 { + contract_address: ContractAddress +} + +trait DualCaseERC1155Trait { + fn uri(self: @DualCaseERC1155, token_id: u256) -> ByteArray; + fn balance_of(self: @DualCaseERC1155, account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + self: @DualCaseERC1155, accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + self: @DualCaseERC1155, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + self: @DualCaseERC1155, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + self: @DualCaseERC1155, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(self: @DualCaseERC1155, operator: ContractAddress, approved: bool); + fn supports_interface(self: @DualCaseERC1155, interface_id: felt252) -> bool; +} + +impl DualCaseERC1155Impl of DualCaseERC1155Trait { + fn uri(self: @DualCaseERC1155, token_id: u256) -> ByteArray { + let mut args = array![]; + args.append_serde(token_id); + + call_contract_syscall(*self.contract_address, selectors::uri, args.span()).unwrap_and_cast() + } + + fn balance_of(self: @DualCaseERC1155, account: ContractAddress, token_id: u256) -> u256 { + let mut args = array![]; + args.append_serde(account); + args.append_serde(token_id); + + try_selector_with_fallback( + *self.contract_address, selectors::balance_of, selectors::balanceOf, args.span() + ) + .unwrap_and_cast() + } + + fn balance_of_batch( + self: @DualCaseERC1155, accounts: Span, token_ids: Span + ) -> Span { + let mut args = array![]; + args.append_serde(accounts); + args.append_serde(token_ids); + + try_selector_with_fallback( + *self.contract_address, + selectors::balance_of_batch, + selectors::balanceOfBatch, + args.span() + ) + .unwrap_and_cast() + } + + fn safe_transfer_from( + self: @DualCaseERC1155, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) { + let mut args = array![]; + args.append_serde(from); + args.append_serde(to); + args.append_serde(token_id); + args.append_serde(value); + args.append_serde(data); + + try_selector_with_fallback( + *self.contract_address, + selectors::safe_transfer_from, + selectors::safeTransferFrom, + args.span() + ) + .unwrap_syscall(); + } + + fn safe_batch_transfer_from( + self: @DualCaseERC1155, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) { + let mut args = array![]; + args.append_serde(from); + args.append_serde(to); + args.append_serde(token_ids); + args.append_serde(values); + args.append_serde(data); + + try_selector_with_fallback( + *self.contract_address, + selectors::safe_batch_transfer_from, + selectors::safeBatchTransferFrom, + args.span() + ) + .unwrap_syscall(); + } + + fn is_approved_for_all( + self: @DualCaseERC1155, owner: ContractAddress, operator: ContractAddress + ) -> bool { + let mut args = array![]; + args.append_serde(owner); + args.append_serde(operator); + + try_selector_with_fallback( + *self.contract_address, + selectors::is_approved_for_all, + selectors::isApprovedForAll, + args.span() + ) + .unwrap_and_cast() + } + + fn set_approval_for_all(self: @DualCaseERC1155, operator: ContractAddress, approved: bool) { + let mut args = array![]; + args.append_serde(operator); + args.append_serde(approved); + + try_selector_with_fallback( + *self.contract_address, + selectors::set_approval_for_all, + selectors::setApprovalForAll, + args.span() + ) + .unwrap_syscall(); + } + + fn supports_interface(self: @DualCaseERC1155, interface_id: felt252) -> bool { + let mut args = array![]; + args.append_serde(interface_id); + + call_contract_syscall(*self.contract_address, selectors::supports_interface, args.span()) + .unwrap_and_cast() + } +} diff --git a/src/token/erc1155/dual1155_receiver.cairo b/src/token/erc1155/dual1155_receiver.cairo new file mode 100644 index 000000000..9aee0cdcf --- /dev/null +++ b/src/token/erc1155/dual1155_receiver.cairo @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (token/erc1155/dual1155_receiver.cairo) + +use openzeppelin::utils::UnwrapAndCast; +use openzeppelin::utils::selectors; +use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::utils::try_selector_with_fallback; +use starknet::ContractAddress; + +#[derive(Copy, Drop)] +struct DualCaseERC1155Receiver { + contract_address: ContractAddress +} + +trait DualCaseERC1155ReceiverTrait { + fn on_erc1155_received( + self: @DualCaseERC1155Receiver, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252; + + fn on_erc1155_batch_received( + self: @DualCaseERC1155Receiver, + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252; +} + +impl DualCaseERC1155ReceiverImpl of DualCaseERC1155ReceiverTrait { + fn on_erc1155_received( + self: @DualCaseERC1155Receiver, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252 { + let mut args = array![]; + args.append_serde(operator); + args.append_serde(from); + args.append_serde(token_id); + args.append_serde(value); + args.append_serde(data); + + try_selector_with_fallback( + *self.contract_address, + selectors::on_erc1155_received, + selectors::onERC1155Received, + args.span() + ) + .unwrap_and_cast() + } + + fn on_erc1155_batch_received( + self: @DualCaseERC1155Receiver, + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252 { + let mut args = array![]; + args.append_serde(operator); + args.append_serde(from); + args.append_serde(token_ids); + args.append_serde(values); + args.append_serde(data); + + try_selector_with_fallback( + *self.contract_address, + selectors::on_erc1155_batch_received, + selectors::onERC1155BatchReceived, + args.span() + ) + .unwrap_and_cast() + } +} diff --git a/src/token/erc1155/erc1155.cairo b/src/token/erc1155/erc1155.cairo new file mode 100644 index 000000000..3a5199ccf --- /dev/null +++ b/src/token/erc1155/erc1155.cairo @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (token/erc1155/erc1155.cairo) + +/// # ERC1155 Component +/// +/// The ERC1155 component provides an implementation of the basic standard multi-token. +/// See https://eips.ethereum.org/EIPS/eip-1155. +#[starknet::component] +mod ERC1155Component { + use openzeppelin::account; + use openzeppelin::introspection::dual_src5::{DualCaseSRC5, DualCaseSRC5Trait}; + use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::dual1155_receiver::{ + DualCaseERC1155Receiver, DualCaseERC1155ReceiverTrait + }; + use openzeppelin::token::erc1155::interface; + use starknet::ContractAddress; + use starknet::get_caller_address; + + #[storage] + struct Storage { + ERC1155_balances: LegacyMap<(u256, ContractAddress), u256>, + ERC1155_operator_approvals: LegacyMap<(ContractAddress, ContractAddress), bool>, + ERC1155_uri: ByteArray, + } + + #[event] + #[derive(Drop, PartialEq, starknet::Event)] + enum Event { + TransferSingle: TransferSingle, + TransferBatch: TransferBatch, + ApprovalForAll: ApprovalForAll, + URI: URI + } + + /// Emitted when `value` token is transferred from `from` to `to` for `id`. + #[derive(Drop, PartialEq, starknet::Event)] + struct TransferSingle { + #[key] + operator: ContractAddress, + #[key] + from: ContractAddress, + #[key] + to: ContractAddress, + id: u256, + value: u256 + } + + /// Emitted when `values` are transferred from `from` to `to` for `ids`. + #[derive(Drop, PartialEq, starknet::Event)] + struct TransferBatch { + #[key] + operator: ContractAddress, + #[key] + from: ContractAddress, + #[key] + to: ContractAddress, + ids: Span, + values: Span, + } + + /// Emitted when `account` enables or disables (`approved`) `operator` to manage + /// all of its assets. + #[derive(Drop, PartialEq, starknet::Event)] + struct ApprovalForAll { + #[key] + owner: ContractAddress, + #[key] + operator: ContractAddress, + approved: bool + } + + /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + /// + /// If an `URI` event was emitted for `id`, the standard guarantees that `value` will equal the value + /// returned by `IERC1155MetadataURI::uri`. + /// https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions + #[derive(Drop, PartialEq, starknet::Event)] + struct URI { + value: ByteArray, + #[key] + id: u256 + } + + mod Errors { + const UNAUTHORIZED: felt252 = 'ERC1155: unauthorized operator'; + const SELF_APPROVAL: felt252 = 'ERC1155: self approval'; + const INVALID_RECEIVER: felt252 = 'ERC1155: invalid receiver'; + const INVALID_SENDER: felt252 = 'ERC1155: invalid sender'; + const INVALID_ARRAY_LENGTH: felt252 = 'ERC1155: no equal array length'; + const INSUFFICIENT_BALANCE: felt252 = 'ERC1155: insufficient balance'; + const SAFE_TRANSFER_FAILED: felt252 = 'ERC1155: safe transfer failed'; + } + + // + // External + // + + #[embeddable_as(ERC1155Impl)] + impl ERC1155< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop + > of interface::IERC1155> { + /// Returns the amount of `token_id` tokens owned by `account`. + fn balance_of( + self: @ComponentState, account: ContractAddress, token_id: u256 + ) -> u256 { + self.ERC1155_balances.read((token_id, account)) + } + + + /// Returns a list of balances derived from the `accounts` and `token_ids` pairs. + /// + /// Requirements: + /// + /// - `token_ids` and `accounts` must have the same length. + fn balance_of_batch( + self: @ComponentState, + accounts: Span, + token_ids: Span + ) -> Span { + assert(accounts.len() == token_ids.len(), Errors::INVALID_ARRAY_LENGTH); + + let mut batch_balances = array![]; + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + batch_balances.append(self.balance_of(*accounts.at(index), *token_ids.at(index))); + index += 1; + }; + + batch_balances.span() + } + + /// Transfers ownership of `value` amount of `token_id` from `from` if `to` is either an account or `IERC1155Receiver`. + /// + /// `data` is additional data, it has no specified format and it is passed to `to`. + /// + /// WARNING: This function can potentially allow a reentrancy attack when transferring tokens + /// to an untrusted contract, when invoking `on_ERC1155_received` on the receiver. + /// Ensure to follow the checks-effects-interactions pattern and consider employing + /// reentrancy guards when interacting with untrusted contracts. + /// + /// Requirements: + /// + /// - Caller is either approved or the `token_id` owner. + /// - `from` is not the zero address. + /// - `to` is not the zero address. + /// - If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_received` + /// and return the required magic value. + /// + /// Emits a `TransferSingle` event. + fn safe_transfer_from( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) { + let token_ids = array![token_id].span(); + let values = array![value].span(); + self.safe_batch_transfer_from(from, to, token_ids, values, data) + } + + /// Batched version of `safe_transfer_from`. + /// + /// WARNING: This function can potentially allow a reentrancy attack when transferring tokens + /// to an untrusted contract, when invoking `on_ERC1155_batch_received` on the receiver. + /// Ensure to follow the checks-effects-interactions pattern and consider employing + /// reentrancy guards when interacting with untrusted contracts. + /// + /// Requirements: + /// + /// - Caller is either approved or the `token_id` owner. + /// - `from` is not the zero address. + /// - `to` is not the zero address. + /// - `token_ids` and `values` must have the same length. + /// - If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` + /// and return the acceptance magic value. + /// + /// Emits either a `TransferSingle` or a `TransferBatch` event, depending on the length of the array arguments. + fn safe_batch_transfer_from( + ref self: ComponentState, + from: starknet::ContractAddress, + to: starknet::ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) { + assert(from.is_non_zero(), Errors::INVALID_SENDER); + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + + let operator = get_caller_address(); + if from != operator { + assert(self.is_approved_for_all(from, operator), Errors::UNAUTHORIZED); + } + + self.update_with_acceptance_check(from, to, token_ids, values, data); + } + + /// Enables or disables approval for `operator` to manage all of the + /// callers assets. + /// + /// Requirements: + /// + /// - `operator` cannot be the caller. + /// + /// Emits an `ApprovalForAll` event. + fn set_approval_for_all( + ref self: ComponentState, operator: ContractAddress, approved: bool + ) { + let owner = get_caller_address(); + assert(owner != operator, Errors::SELF_APPROVAL); + + self.ERC1155_operator_approvals.write((owner, operator), approved); + self.emit(ApprovalForAll { owner, operator, approved }); + } + + /// Queries if `operator` is an authorized operator for `owner`. + fn is_approved_for_all( + self: @ComponentState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + self.ERC1155_operator_approvals.read((owner, operator)) + } + } + + #[embeddable_as(ERC1155MetadataURIImpl)] + impl ERC1155MetadataURI< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop + > of interface::IERC1155MetadataURI> { + /// This implementation returns the same URI for *all* token types. It relies + /// on the token type ID substitution mechanism defined in the EIP: + /// https://eips.ethereum.org/EIPS/eip-1155#metadata. + /// + /// Clients calling this function must replace the `\{id\}` substring with the + /// actual token type ID. + fn uri(self: @ComponentState, token_id: u256) -> ByteArray { + self.ERC1155_uri.read() + } + } + + /// Adds camelCase support for `IERC1155`. + #[embeddable_as(ERC1155CamelImpl)] + impl ERC1155Camel< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop + > of interface::IERC1155Camel> { + fn balanceOf( + self: @ComponentState, account: ContractAddress, tokenId: u256 + ) -> u256 { + self.balance_of(account, tokenId) + } + + fn balanceOfBatch( + self: @ComponentState, + accounts: Span, + tokenIds: Span + ) -> Span { + self.balance_of_batch(accounts, tokenIds) + } + + fn safeTransferFrom( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ) { + self.safe_transfer_from(from, to, tokenId, value, data) + } + + fn safeBatchTransferFrom( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ) { + self.safe_batch_transfer_from(from, to, tokenIds, values, data) + } + + fn setApprovalForAll( + ref self: ComponentState, operator: ContractAddress, approved: bool + ) { + self.set_approval_for_all(operator, approved) + } + + fn isApprovedForAll( + self: @ComponentState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + self.is_approved_for_all(owner, operator) + } + } + + // + // Internal + // + + #[generate_trait] + impl InternalImpl< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of InternalTrait { + /// Initializes the contract by setting the `base_uri` for all tokens, + /// and registering the supported interfaces. + /// This should only be used inside the contract's constructor. + fn initializer(ref self: ComponentState, base_uri: ByteArray) { + self.set_base_uri(base_uri); + + let mut src5_component = get_dep_component_mut!(ref self, SRC5); + src5_component.register_interface(interface::IERC1155_ID); + src5_component.register_interface(interface::IERC1155_METADATA_URI_ID); + } + + /// Transfers a `value` amount of tokens of type `id` from `from` to `to`. + /// Will mint (or burn) if `from` (or `to`) is the zero address. + /// + /// Requirements: + /// + /// - `token_ids` and `values` must have the same length. + /// + /// Emits a `TransferSingle` event if the arrays contain one element, and `TransferBatch` otherwise. + /// + /// NOTE: The ERC1155 acceptance check is not performed in this function. + /// See `update_with_acceptance_check` instead. + fn update( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span + ) { + assert(token_ids.len() == values.len(), Errors::INVALID_ARRAY_LENGTH); + + let mut index = 0; + loop { + if index == token_ids.len() { + break; + } + let token_id = *token_ids.at(index); + let value = *values.at(index); + if from.is_non_zero() { + let from_balance = self.ERC1155_balances.read((token_id, from)); + assert(from_balance >= value, Errors::INSUFFICIENT_BALANCE); + self.ERC1155_balances.write((token_id, from), from_balance - value); + } + if to.is_non_zero() { + let to_balance = self.ERC1155_balances.read((token_id, to)); + self.ERC1155_balances.write((token_id, to), to_balance + value); + } + index += 1; + }; + let operator = get_caller_address(); + if token_ids.len() == 1 { + self + .emit( + TransferSingle { + operator, from, to, id: *token_ids.at(0), value: *values.at(0) + } + ); + } else { + self.emit(TransferBatch { operator, from, to, ids: token_ids, values }); + } + } + + /// Version of `update` that performs the token acceptance check by calling + /// `IERC1155Receiver::onERC1155Received` or `IERC1155Receiver::onERC1155BatchReceived` if + /// the receiver is not recognized as an account. + /// + /// Requirements: + /// + /// - `to` is either an account contract or supports the `IERC1155Receiver` interface. + /// - `token_ids` and `values` must have the same length. + /// + /// Emits a `TransferSingle` event if the arrays contain one element, and `TransferBatch` otherwise. + fn update_with_acceptance_check( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) { + self.update(from, to, token_ids, values); + let accepted = if token_ids.len() == 1 { + _check_on_ERC1155_received(from, to, *token_ids.at(0), *values.at(0), data) + } else { + _check_on_ERC1155_batch_received(from, to, token_ids, values, data) + }; + assert(accepted, Errors::SAFE_TRANSFER_FAILED); + } + + /// Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`. + /// + /// Requirements: + /// + /// - `to` cannot be the zero address. + /// - If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_received` + /// and return the acceptance magic value. + /// + /// Emits a `TransferSingle` event. + fn mint_with_acceptance_check( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + + let token_ids = array![token_id].span(); + let values = array![value].span(); + self.update_with_acceptance_check(Zeroable::zero(), to, token_ids, values, data); + } + + /// Batched version of `mint_with_acceptance_check`. + /// + /// Requirements: + /// + /// - `to` cannot be the zero address. + /// - `token_ids` and `values` must have the same length. + /// - If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` + /// and return the acceptance magic value. + /// + /// Emits a `TransferBatch` event. + fn batch_mint_with_acceptance_check( + ref self: ComponentState, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + self.update_with_acceptance_check(Zeroable::zero(), to, token_ids, values, data); + } + + /// Destroys a `value` amount of tokens of type `token_id` from `from`. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `from` must have at least `value` amount of tokens of type `token_id`. + /// + /// Emits a `TransferSingle` event. + fn burn( + ref self: ComponentState, + from: ContractAddress, + token_id: u256, + value: u256 + ) { + assert(from.is_non_zero(), Errors::INVALID_SENDER); + + let token_ids = array![token_id].span(); + let values = array![value].span(); + self.update(from, Zeroable::zero(), token_ids, values); + } + + /// Batched version of `burn`. + /// + /// Requirements: + /// + /// - `from` cannot be the zero address. + /// - `from` must have at least `value` amount of tokens of type `token_id`. + /// - `token_ids` and `values` must have the same length. + /// + /// Emits a `TransferBatch` event. + fn batch_burn( + ref self: ComponentState, + from: ContractAddress, + token_ids: Span, + values: Span + ) { + assert(from.is_non_zero(), Errors::INVALID_SENDER); + self.update(from, Zeroable::zero(), token_ids, values); + } + + /// Sets a new URI for all token types, by relying on the token type ID + /// substitution mechanism defined in the ERC1155 standard. + /// See https://eips.ethereum.org/EIPS/eip-1155#metadata. + /// + /// By this mechanism, any occurrence of the `\{id\}` substring in either the + /// URI or any of the values in the JSON file at said URI will be replaced by + /// clients with the token type ID. + /// + /// For example, the `https://token-cdn-domain/\{id\}.json` URI would be + /// interpreted by clients as + /// `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + /// for token type ID 0x4cce0. + /// + /// Because these URIs cannot be meaningfully represented by the `URI` event, + /// this function emits no events. + fn set_base_uri(ref self: ComponentState, base_uri: ByteArray) { + self.ERC1155_uri.write(base_uri); + } + } + + /// Checks if `to` accepts the token by implementing `IERC1155Receiver` + /// or if it's an account contract (supporting ISRC6). + fn _check_on_ERC1155_received( + from: ContractAddress, to: ContractAddress, token_id: u256, value: u256, data: Span + ) -> bool { + if (DualCaseSRC5 { contract_address: to } + .supports_interface(interface::IERC1155_RECEIVER_ID)) { + DualCaseERC1155Receiver { contract_address: to } + .on_erc1155_received( + get_caller_address(), from, token_id, value, data + ) == interface::IERC1155_RECEIVER_ID + } else { + DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) + } + } + + /// Checks if `to` accepts the token by implementing `IERC1155Receiver` + /// or if it's an account contract (supporting ISRC6). + fn _check_on_ERC1155_batch_received( + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> bool { + if (DualCaseSRC5 { contract_address: to } + .supports_interface(interface::IERC1155_RECEIVER_ID)) { + DualCaseERC1155Receiver { contract_address: to } + .on_erc1155_batch_received( + get_caller_address(), from, token_ids, values, data + ) == interface::IERC1155_RECEIVER_ID + } else { + DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) + } + } +} diff --git a/src/token/erc1155/erc1155_receiver.cairo b/src/token/erc1155/erc1155_receiver.cairo new file mode 100644 index 000000000..e9cf9e7c5 --- /dev/null +++ b/src/token/erc1155/erc1155_receiver.cairo @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (token/erc1155/erc1155_receiver.cairo) + +/// # ERC1155Receiver Component +/// +/// The ERC1155Receiver component provides implementations for the IERC1155Receiver +/// interface. Integrating this component allows contracts to support ERC1155 +/// safe transfers. +#[starknet::component] +mod ERC1155ReceiverComponent { + use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc1155::interface::IERC1155_RECEIVER_ID; + use openzeppelin::token::erc1155::interface::{IERC1155Receiver, IERC1155ReceiverCamel}; + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event {} + + #[embeddable_as(ERC1155ReceiverImpl)] + impl ERC1155Receiver< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop + > of IERC1155Receiver> { + /// Called whenever the implementing contract receives `value` through + /// a safe transfer. This function must return `IERC1155_RECEIVER_ID` + /// to confirm the token transfer. + fn on_erc1155_received( + self: @ComponentState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252 { + IERC1155_RECEIVER_ID + } + + fn on_erc1155_batch_received( + self: @ComponentState, + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252 { + IERC1155_RECEIVER_ID + } + } + + /// Adds camelCase support for `IERC1155Receiver`. + #[embeddable_as(ERC1155ReceiverCamelImpl)] + impl ERC1155ReceiverCamel< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +Drop + > of IERC1155ReceiverCamel> { + fn onERC1155Received( + self: @ComponentState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ) -> felt252 { + IERC1155_RECEIVER_ID + } + + fn onERC1155BatchReceived( + self: @ComponentState, + operator: ContractAddress, + from: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ) -> felt252 { + IERC1155_RECEIVER_ID + } + } + + #[generate_trait] + impl InternalImpl< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of InternalTrait { + /// Initializes the contract by registering the IERC1155Receiver interface ID. + /// This should be used inside the contract's constructor. + fn initializer(ref self: ComponentState) { + let mut src5_component = get_dep_component_mut!(ref self, SRC5); + src5_component.register_interface(IERC1155_RECEIVER_ID); + } + } +} diff --git a/src/token/erc1155/interface.cairo b/src/token/erc1155/interface.cairo new file mode 100644 index 000000000..2398cf063 --- /dev/null +++ b/src/token/erc1155/interface.cairo @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.9.0 (token/erc1155/interface.cairo) + +use starknet::ContractAddress; + +const IERC1155_ID: felt252 = 0x6114a8f75559e1b39fcba08ce02961a1aa082d9256a158dd3e64964e4b1b52; +const IERC1155_METADATA_URI_ID: felt252 = + 0xcabe2400d5fe509e1735ba9bad205ba5f3ca6e062da406f72f113feb889ef7; +const IERC1155_RECEIVER_ID: felt252 = + 0x15e8665b5af20040c3af1670509df02eb916375cdf7d8cbaf7bd553a257515e; + +#[starknet::interface] +trait IERC1155 { + fn balance_of(self: @TState, account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + self: @TState, accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); +} + +#[starknet::interface] +trait IERC1155MetadataURI { + fn uri(self: @TState, token_id: u256) -> ByteArray; +} + +#[starknet::interface] +trait IERC1155Camel { + fn balanceOf(self: @TState, account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch( + self: @TState, accounts: Span, tokenIds: Span + ) -> Span; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ); + fn safeBatchTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); +} + +// +// ERC1155 ABI +// + +#[starknet::interface] +trait ERC1155ABI { + // IERC1155 + fn balance_of(self: @TState, account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + self: @TState, accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IERC1155MetadataURI + fn uri(self: @TState, token_id: u256) -> ByteArray; + + // IERC1155Camel + fn balanceOf(self: @TState, account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch( + self: @TState, accounts: Span, tokenIds: Span + ) -> Span; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ); + fn safeBatchTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); +} + +// +// IERC1155Receiver +// + +#[starknet::interface] +trait IERC1155Receiver { + fn on_erc1155_received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252; + fn on_erc1155_batch_received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252; +} + +#[starknet::interface] +trait IERC1155ReceiverCamel { + fn onERC1155Received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ) -> felt252; + fn onERC1155BatchReceived( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ) -> felt252; +} diff --git a/src/token/erc20/dual20.cairo b/src/token/erc20/dual20.cairo index e0fe9dd0b..a28883a9e 100644 --- a/src/token/erc20/dual20.cairo +++ b/src/token/erc20/dual20.cairo @@ -15,8 +15,8 @@ struct DualCaseERC20 { } trait DualCaseERC20Trait { - fn name(self: @DualCaseERC20) -> felt252; - fn symbol(self: @DualCaseERC20) -> felt252; + fn name(self: @DualCaseERC20) -> ByteArray; + fn symbol(self: @DualCaseERC20) -> ByteArray; fn decimals(self: @DualCaseERC20) -> u8; fn total_supply(self: @DualCaseERC20) -> u256; fn balance_of(self: @DualCaseERC20, account: ContractAddress) -> u256; @@ -29,13 +29,13 @@ trait DualCaseERC20Trait { } impl DualCaseERC20Impl of DualCaseERC20Trait { - fn name(self: @DualCaseERC20) -> felt252 { + fn name(self: @DualCaseERC20) -> ByteArray { let args = array![]; call_contract_syscall(*self.contract_address, selectors::name, args.span()) .unwrap_and_cast() } - fn symbol(self: @DualCaseERC20) -> felt252 { + fn symbol(self: @DualCaseERC20) -> ByteArray { let args = array![]; call_contract_syscall(*self.contract_address, selectors::symbol, args.span()) .unwrap_and_cast() diff --git a/src/token/erc20/erc20.cairo b/src/token/erc20/erc20.cairo index 105f11c32..67d27d825 100644 --- a/src/token/erc20/erc20.cairo +++ b/src/token/erc20/erc20.cairo @@ -18,22 +18,22 @@ mod ERC20Component { #[storage] struct Storage { - ERC20_name: felt252, - ERC20_symbol: felt252, + ERC20_name: ByteArray, + ERC20_symbol: ByteArray, ERC20_total_supply: u256, ERC20_balances: LegacyMap, ERC20_allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { Transfer: Transfer, Approval: Approval, } /// Emitted when tokens are moved from address `from` to address `to`. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Transfer { #[key] from: ContractAddress, @@ -44,7 +44,7 @@ mod ERC20Component { /// Emitted when the allowance of a `spender` for an `owner` is set by a call /// to `approve`. `value` is the new allowance. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Approval { #[key] owner: ContractAddress, @@ -150,12 +150,12 @@ mod ERC20Component { TContractState, +HasComponent > of interface::IERC20Metadata> { /// Returns the name of the token. - fn name(self: @ComponentState) -> felt252 { + fn name(self: @ComponentState) -> ByteArray { self.ERC20_name.read() } /// Returns the ticker symbol of the token, usually a shorter version of the name. - fn symbol(self: @ComponentState) -> felt252 { + fn symbol(self: @ComponentState) -> ByteArray { self.ERC20_symbol.read() } @@ -198,7 +198,9 @@ mod ERC20Component { > of InternalTrait { /// Initializes the contract by setting the token name and symbol. /// To prevent reinitialization, this should only be used inside of a contract's constructor. - fn initializer(ref self: ComponentState, name: felt252, symbol: felt252) { + fn initializer( + ref self: ComponentState, name: ByteArray, symbol: ByteArray + ) { self.ERC20_name.write(name); self.ERC20_symbol.write(symbol); } @@ -297,4 +299,76 @@ mod ERC20Component { } } } + + #[embeddable_as(ERC20MixinImpl)] + impl ERC20Mixin< + TContractState, +HasComponent, +Drop + > of interface::ERC20ABI> { + // IERC20 + fn total_supply(self: @ComponentState) -> u256 { + ERC20::total_supply(self) + } + + fn balance_of(self: @ComponentState, account: ContractAddress) -> u256 { + ERC20::balance_of(self, account) + } + + fn allowance( + self: @ComponentState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + ERC20::allowance(self, owner, spender) + } + + fn transfer( + ref self: ComponentState, recipient: ContractAddress, amount: u256 + ) -> bool { + ERC20::transfer(ref self, recipient, amount) + } + + fn transfer_from( + ref self: ComponentState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20::transfer_from(ref self, sender, recipient, amount) + } + + fn approve( + ref self: ComponentState, spender: ContractAddress, amount: u256 + ) -> bool { + ERC20::approve(ref self, spender, amount) + } + + // IERC20Metadata + fn name(self: @ComponentState) -> ByteArray { + ERC20Metadata::name(self) + } + + fn symbol(self: @ComponentState) -> ByteArray { + ERC20Metadata::symbol(self) + } + + fn decimals(self: @ComponentState) -> u8 { + ERC20Metadata::decimals(self) + } + + // IERC20CamelOnly + fn totalSupply(self: @ComponentState) -> u256 { + ERC20CamelOnly::totalSupply(self) + } + + fn balanceOf(self: @ComponentState, account: ContractAddress) -> u256 { + ERC20CamelOnly::balanceOf(self, account) + } + + fn transferFrom( + ref self: ComponentState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20CamelOnly::transferFrom(ref self, sender, recipient, amount) + } + } } diff --git a/src/token/erc20/interface.cairo b/src/token/erc20/interface.cairo index 829ebd40e..c519cb534 100644 --- a/src/token/erc20/interface.cairo +++ b/src/token/erc20/interface.cairo @@ -17,8 +17,8 @@ trait IERC20 { #[starknet::interface] trait IERC20Metadata { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; fn decimals(self: @TState) -> u8; } @@ -56,8 +56,8 @@ trait ERC20ABI { fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; // IERC20Metadata - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; fn decimals(self: @TState) -> u8; // IERC20CamelOnly diff --git a/src/token/erc721/dual721.cairo b/src/token/erc721/dual721.cairo index 0feb9e879..1eeeb7ba8 100644 --- a/src/token/erc721/dual721.cairo +++ b/src/token/erc721/dual721.cairo @@ -15,9 +15,9 @@ struct DualCaseERC721 { } trait DualCaseERC721Trait { - fn name(self: @DualCaseERC721) -> felt252; - fn symbol(self: @DualCaseERC721) -> felt252; - fn token_uri(self: @DualCaseERC721, token_id: u256) -> felt252; + fn name(self: @DualCaseERC721) -> ByteArray; + fn symbol(self: @DualCaseERC721) -> ByteArray; + fn token_uri(self: @DualCaseERC721, token_id: u256) -> ByteArray; fn balance_of(self: @DualCaseERC721, account: ContractAddress) -> u256; fn owner_of(self: @DualCaseERC721, token_id: u256) -> ContractAddress; fn get_approved(self: @DualCaseERC721, token_id: u256) -> ContractAddress; @@ -40,17 +40,17 @@ trait DualCaseERC721Trait { } impl DualCaseERC721Impl of DualCaseERC721Trait { - fn name(self: @DualCaseERC721) -> felt252 { + fn name(self: @DualCaseERC721) -> ByteArray { call_contract_syscall(*self.contract_address, selectors::name, array![].span()) .unwrap_and_cast() } - fn symbol(self: @DualCaseERC721) -> felt252 { + fn symbol(self: @DualCaseERC721) -> ByteArray { call_contract_syscall(*self.contract_address, selectors::symbol, array![].span()) .unwrap_and_cast() } - fn token_uri(self: @DualCaseERC721, token_id: u256) -> felt252 { + fn token_uri(self: @DualCaseERC721, token_id: u256) -> ByteArray { let mut args = array![]; args.append_serde(token_id); diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 9a9a3e44b..fdcedf040 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -10,6 +10,7 @@ mod ERC721Component { use openzeppelin::account; use openzeppelin::introspection::dual_src5::{DualCaseSRC5, DualCaseSRC5Trait}; use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component::{SRC5, SRC5Camel}; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc721::dual721_receiver::{ DualCaseERC721Receiver, DualCaseERC721ReceiverTrait @@ -20,17 +21,17 @@ mod ERC721Component { #[storage] struct Storage { - ERC721_name: felt252, - ERC721_symbol: felt252, + ERC721_name: ByteArray, + ERC721_symbol: ByteArray, ERC721_owners: LegacyMap, ERC721_balances: LegacyMap, ERC721_token_approvals: LegacyMap, ERC721_operator_approvals: LegacyMap<(ContractAddress, ContractAddress), bool>, - ERC721_token_uri: LegacyMap, + ERC721_base_uri: ByteArray } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { Transfer: Transfer, Approval: Approval, @@ -38,7 +39,7 @@ mod ERC721Component { } /// Emitted when `token_id` token is transferred from `from` to `to`. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Transfer { #[key] from: ContractAddress, @@ -49,7 +50,7 @@ mod ERC721Component { } /// Emitted when `owner` enables `approved` to manage the `token_id` token. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Approval { #[key] owner: ContractAddress, @@ -61,7 +62,7 @@ mod ERC721Component { /// Emitted when `owner` enables or disables (`approved`) `operator` to manage /// all of its assets. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct ApprovalForAll { #[key] owner: ContractAddress, @@ -171,7 +172,8 @@ mod ERC721Component { let caller = get_caller_address(); assert( - owner == caller || self.is_approved_for_all(owner, caller), Errors::UNAUTHORIZED + owner == caller || ERC721::is_approved_for_all(@self, owner, caller), + Errors::UNAUTHORIZED ); self._approve(to, token_id); } @@ -216,24 +218,29 @@ mod ERC721Component { +Drop > of interface::IERC721Metadata> { /// Returns the NFT name. - fn name(self: @ComponentState) -> felt252 { + fn name(self: @ComponentState) -> ByteArray { self.ERC721_name.read() } /// Returns the NFT symbol. - fn symbol(self: @ComponentState) -> felt252 { + fn symbol(self: @ComponentState) -> ByteArray { self.ERC721_symbol.read() } /// Returns the Uniform Resource Identifier (URI) for the `token_id` token. - /// If the URI is not set for the `token_id`, the return value will be `0`. + /// If the URI is not set, the return value will be an empty ByteArray. /// /// Requirements: /// /// - `token_id` exists. - fn token_uri(self: @ComponentState, token_id: u256) -> felt252 { + fn token_uri(self: @ComponentState, token_id: u256) -> ByteArray { assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - self.ERC721_token_uri.read(token_id) + let base_uri = self._base_uri(); + if base_uri.len() == 0 { + return ""; + } else { + return format!("{}{}", base_uri, token_id); + } } } @@ -246,11 +253,11 @@ mod ERC721Component { +Drop > of interface::IERC721CamelOnly> { fn balanceOf(self: @ComponentState, account: ContractAddress) -> u256 { - self.balance_of(account) + ERC721::balance_of(self, account) } fn ownerOf(self: @ComponentState, tokenId: u256) -> ContractAddress { - self.owner_of(tokenId) + ERC721::owner_of(self, tokenId) } fn safeTransferFrom( @@ -260,7 +267,7 @@ mod ERC721Component { tokenId: u256, data: Span ) { - self.safe_transfer_from(from, to, tokenId, data) + ERC721::safe_transfer_from(ref self, from, to, tokenId, data) } fn transferFrom( @@ -269,23 +276,23 @@ mod ERC721Component { to: ContractAddress, tokenId: u256 ) { - self.transfer_from(from, to, tokenId) + ERC721::transfer_from(ref self, from, to, tokenId) } fn setApprovalForAll( ref self: ComponentState, operator: ContractAddress, approved: bool ) { - self.set_approval_for_all(operator, approved) + ERC721::set_approval_for_all(ref self, operator, approved) } fn getApproved(self: @ComponentState, tokenId: u256) -> ContractAddress { - self.get_approved(tokenId) + ERC721::get_approved(self, tokenId) } fn isApprovedForAll( self: @ComponentState, owner: ContractAddress, operator: ContractAddress ) -> bool { - self.is_approved_for_all(owner, operator) + ERC721::is_approved_for_all(self, owner, operator) } } @@ -297,8 +304,8 @@ mod ERC721Component { +SRC5Component::HasComponent, +Drop > of interface::IERC721MetadataCamelOnly> { - fn tokenURI(self: @ComponentState, tokenId: u256) -> felt252 { - self.token_uri(tokenId) + fn tokenURI(self: @ComponentState, tokenId: u256) -> ByteArray { + ERC721Metadata::token_uri(self, tokenId) } } @@ -313,11 +320,17 @@ mod ERC721Component { impl SRC5: SRC5Component::HasComponent, +Drop > of InternalTrait { - /// Initializes the contract by setting the token name and symbol. + /// Initializes the contract by setting the token name, symbol, and base URI. /// This should only be used inside the contract's constructor. - fn initializer(ref self: ComponentState, name: felt252, symbol: felt252) { + fn initializer( + ref self: ComponentState, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray + ) { self.ERC721_name.write(name); self.ERC721_symbol.write(symbol); + self._set_base_uri(base_uri); let mut src5_component = get_dep_component_mut!(ref self, SRC5); src5_component.register_interface(interface::IERC721_ID); @@ -351,8 +364,10 @@ mod ERC721Component { self: @ComponentState, spender: ContractAddress, token_id: u256 ) -> bool { let owner = self._owner_of(token_id); - let is_approved_for_all = self.is_approved_for_all(owner, spender); - owner == spender || is_approved_for_all || spender == self.get_approved(token_id) + let is_approved_for_all = ERC721::is_approved_for_all(self, owner, spender); + owner == spender + || is_approved_for_all + || spender == ERC721::get_approved(self, token_id) } /// Changes or reaffirms the approved address for an NFT. @@ -512,22 +527,22 @@ mod ERC721Component { ); } - /// Sets the `token_uri` of `token_id`. - /// - /// Requirements: + /// Sets the base URI. + fn _set_base_uri(ref self: ComponentState, base_uri: ByteArray) { + self.ERC721_base_uri.write(base_uri); + } + + /// Base URI for computing `token_uri`. /// - /// - `token_id` exists. - fn _set_token_uri( - ref self: ComponentState, token_id: u256, token_uri: felt252 - ) { - assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - self.ERC721_token_uri.write(token_id, token_uri) + /// If set, the resulting URI for each token will be the concatenation of the base URI and the token ID. + /// Returns an empty `ByteArray` if not set. + fn _base_uri(self: @ComponentState) -> ByteArray { + self.ERC721_base_uri.read() } } /// Checks if `to` either is an account contract or has registered support - /// for the `IERC721Receiver` interface through SRC5. The transaction will - /// fail if both cases are false. + /// for the `IERC721Receiver` interface through SRC5. fn _check_on_erc721_received( from: ContractAddress, to: ContractAddress, token_id: u256, data: Span ) -> bool { @@ -541,4 +556,131 @@ mod ERC721Component { DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) } } + + #[embeddable_as(ERC721MixinImpl)] + impl ERC721Mixin< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of interface::ERC721ABI> { + // IERC721 + fn balance_of(self: @ComponentState, account: ContractAddress) -> u256 { + ERC721::balance_of(self, account) + } + + fn owner_of(self: @ComponentState, token_id: u256) -> ContractAddress { + ERC721::owner_of(self, token_id) + } + + fn safe_transfer_from( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + ERC721::safe_transfer_from(ref self, from, to, token_id, data); + } + + + fn transfer_from( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + token_id: u256 + ) { + ERC721::transfer_from(ref self, from, to, token_id); + } + + fn approve(ref self: ComponentState, to: ContractAddress, token_id: u256) { + ERC721::approve(ref self, to, token_id); + } + + fn set_approval_for_all( + ref self: ComponentState, operator: ContractAddress, approved: bool + ) { + ERC721::set_approval_for_all(ref self, operator, approved); + } + + fn get_approved(self: @ComponentState, token_id: u256) -> ContractAddress { + ERC721::get_approved(self, token_id) + } + + fn is_approved_for_all( + self: @ComponentState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + ERC721::is_approved_for_all(self, owner, operator) + } + + // IERC721Metadata + fn name(self: @ComponentState) -> ByteArray { + ERC721Metadata::name(self) + } + + fn symbol(self: @ComponentState) -> ByteArray { + ERC721Metadata::symbol(self) + } + + fn token_uri(self: @ComponentState, token_id: u256) -> ByteArray { + ERC721Metadata::token_uri(self, token_id) + } + + // IERC721CamelOnly + fn balanceOf(self: @ComponentState, account: ContractAddress) -> u256 { + ERC721CamelOnly::balanceOf(self, account) + } + + fn ownerOf(self: @ComponentState, tokenId: u256) -> ContractAddress { + ERC721CamelOnly::ownerOf(self, tokenId) + } + + fn safeTransferFrom( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ) { + ERC721CamelOnly::safeTransferFrom(ref self, from, to, tokenId, data); + } + + fn transferFrom( + ref self: ComponentState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256 + ) { + ERC721CamelOnly::transferFrom(ref self, from, to, tokenId); + } + + fn setApprovalForAll( + ref self: ComponentState, operator: ContractAddress, approved: bool + ) { + ERC721CamelOnly::setApprovalForAll(ref self, operator, approved); + } + + fn getApproved(self: @ComponentState, tokenId: u256) -> ContractAddress { + ERC721CamelOnly::getApproved(self, tokenId) + } + + fn isApprovedForAll( + self: @ComponentState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + ERC721CamelOnly::isApprovedForAll(self, owner, operator) + } + + // IERC721MetadataCamelOnly + fn tokenURI(self: @ComponentState, tokenId: u256) -> ByteArray { + ERC721MetadataCamelOnly::tokenURI(self, tokenId) + } + + // ISRC5 + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { + let src5 = get_dep_component!(self, SRC5); + src5.supports_interface(interface_id) + } + } } diff --git a/src/token/erc721/erc721_receiver.cairo b/src/token/erc721/erc721_receiver.cairo index 6435c3da5..7b19fe84b 100644 --- a/src/token/erc721/erc721_receiver.cairo +++ b/src/token/erc721/erc721_receiver.cairo @@ -9,9 +9,11 @@ #[starknet::component] mod ERC721ReceiverComponent { use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc721::interface::IERC721_RECEIVER_ID; use openzeppelin::token::erc721::interface::{IERC721Receiver, IERC721ReceiverCamel}; + use openzeppelin::token::erc721::interface; use starknet::ContractAddress; #[storage] @@ -75,4 +77,42 @@ mod ERC721ReceiverComponent { src5_component.register_interface(IERC721_RECEIVER_ID); } } + + #[embeddable_as(ERC721ReceiverAMixinmpl)] + impl ERC721ReceiverMixin< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of interface::ERC721ReceiverMixin> { + // IERC721Receiver + fn on_erc721_received( + self: @ComponentState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252 { + ERC721Receiver::on_erc721_received(self, operator, from, token_id, data) + } + + // IERC721ReceiverCamel + fn onERC721Received( + self: @ComponentState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span + ) -> felt252 { + ERC721ReceiverCamel::onERC721Received(self, operator, from, tokenId, data) + } + + // ISRC5 + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { + let src5 = get_dep_component!(self, SRC5); + src5.supports_interface(interface_id) + } + } } diff --git a/src/token/erc721/interface.cairo b/src/token/erc721/interface.cairo index b2aa22ec4..972e91190 100644 --- a/src/token/erc721/interface.cairo +++ b/src/token/erc721/interface.cairo @@ -5,7 +5,7 @@ use starknet::ContractAddress; const IERC721_ID: felt252 = 0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943; const IERC721_METADATA_ID: felt252 = - 0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd; + 0xabbcd595a567dce909050a1038e055daccb3c42af06f0add544fa90ee91f25; const IERC721_RECEIVER_ID: felt252 = 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; @@ -31,9 +31,9 @@ trait IERC721 { #[starknet::interface] trait IERC721Metadata { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn token_uri(self: @TState, token_id: u256) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; } #[starknet::interface] @@ -55,7 +55,7 @@ trait IERC721CamelOnly { #[starknet::interface] trait IERC721MetadataCamelOnly { - fn tokenURI(self: @TState, tokenId: u256) -> felt252; + fn tokenURI(self: @TState, tokenId: u256) -> ByteArray; } // @@ -86,9 +86,9 @@ trait ERC721ABI { fn supports_interface(self: @TState, interface_id: felt252) -> bool; // IERC721Metadata - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn token_uri(self: @TState, token_id: u256) -> felt252; + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; // IERC721CamelOnly fn balanceOf(self: @TState, account: ContractAddress) -> u256; @@ -105,11 +105,8 @@ trait ERC721ABI { fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; - // ISRC5Camel - fn supportsInterface(self: @TState, interfaceId: felt252) -> bool; - // IERC721MetadataCamelOnly - fn tokenURI(self: @TState, tokenId: u256) -> felt252; + fn tokenURI(self: @TState, tokenId: u256) -> ByteArray; } // @@ -137,3 +134,27 @@ trait IERC721ReceiverCamel { data: Span ) -> felt252; } + +#[starknet::interface] +trait ERC721ReceiverMixin { + // IERC721Receiver + fn on_erc721_received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252; + + // IERC721ReceiverCamel + fn onERC721Received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span + ) -> felt252; + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; +} diff --git a/src/upgrades/upgradeable.cairo b/src/upgrades/upgradeable.cairo index a628153c2..eb2aad9cc 100644 --- a/src/upgrades/upgradeable.cairo +++ b/src/upgrades/upgradeable.cairo @@ -13,13 +13,13 @@ mod UpgradeableComponent { struct Storage {} #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] enum Event { Upgraded: Upgraded } /// Emitted when the contract is upgraded. - #[derive(Drop, starknet::Event)] + #[derive(Drop, PartialEq, starknet::Event)] struct Upgraded { class_hash: ClassHash } diff --git a/src/utils/selectors.cairo b/src/utils/selectors.cairo index 3ec5e25a8..acf3cb9b7 100644 --- a/src/utils/selectors.cairo +++ b/src/utils/selectors.cairo @@ -57,6 +57,28 @@ const safeTransferFrom: felt252 = selector!("safeTransferFrom"); const on_erc721_received: felt252 = selector!("on_erc721_received"); const onERC721Received: felt252 = selector!("onERC721Received"); +// +// ERC1155 +// + +// The following ERC1155 selectors are already defined in ERC721 above: +// balance_of, balanceOf, set_approval_for_all, +// setApprovalForAll, safe_transfer_from, safeTransferFrom +const uri: felt252 = selector!("uri"); +const balance_of_batch: felt252 = selector!("balance_of_batch"); +const balanceOfBatch: felt252 = selector!("balanceOfBatch"); +const safe_batch_transfer_from: felt252 = selector!("safe_batch_transfer_from"); +const safeBatchTransferFrom: felt252 = selector!("safeBatchTransferFrom"); + +// +// ERC1155Receiver +// + +const on_erc1155_received: felt252 = selector!("on_erc1155_received"); +const onERC1155Received: felt252 = selector!("onERC1155Received"); +const on_erc1155_batch_received: felt252 = selector!("on_erc1155_batch_received"); +const onERC1155BatchReceived: felt252 = selector!("onERC1155BatchReceived"); + // // ERC20 // From 620d22be13c4e7240e3900e1e48920759a88c78b Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 6 Mar 2024 17:02:15 -0500 Subject: [PATCH 13/35] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 860ce66cd..c8be79e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ERC721Component functions and Storage member - `InternalTrait::_set_base_uri` and `InternalTrait::_base_uri` to handle ByteArrays (#857) - `ERC721_base_uri` Storage member to store the base URI (#857) +- UDC preset contract (#919) ### Changed From 5324daa2ddc0231b1c5eea6695cd96cbaa8465b5 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 6 Mar 2024 17:52:18 -0500 Subject: [PATCH 14/35] remove duplicate entry --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8be79e40..3616fde83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - EthAccount component and preset (#853) - Ownable two-step functionality (#809) -- UDC contract (#919) ### Changed From 2bbb1948c86d39990d2cfdc78bf770e600f4e6c6 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Mon, 11 Mar 2024 15:17:49 -0400 Subject: [PATCH 15/35] Apply suggestions from code review Co-authored-by: Eric Nordelo --- src/presets/universal_deployer.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 81ce7ccea..6b4348f8b 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -50,7 +50,7 @@ mod UniversalDeployer { // Defaults for non-unique deployment let mut _salt: felt252 = salt; - let mut from_zero: bool = true; + let from_zero: bool = !unique; if unique { _salt = pedersen(deployer.into(), salt); From 8b0bf4279eda214462a7e42488c2425532d13532 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Mar 2024 15:19:47 -0400 Subject: [PATCH 16/35] fix from_zero logic --- src/presets/universal_deployer.cairo | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 6b4348f8b..e3bc0ca1e 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -47,14 +47,10 @@ mod UniversalDeployer { calldata: Span ) -> ContractAddress { let deployer: ContractAddress = get_caller_address(); - - // Defaults for non-unique deployment - let mut _salt: felt252 = salt; let from_zero: bool = !unique; - + let mut _salt: felt252 = salt; if unique { _salt = pedersen(deployer.into(), salt); - from_zero = false; } let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) From fb741dc09f2b964b94ecb139e2009cff3343851c Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Mar 2024 15:21:02 -0400 Subject: [PATCH 17/35] remove member names from struct --- src/presets/universal_deployer.cairo | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index e3bc0ca1e..058220412 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -59,12 +59,12 @@ mod UniversalDeployer { self .emit( ContractDeployed { - address: address, - deployer: deployer, - unique: unique, - class_hash: class_hash, - calldata: calldata, - salt: salt + address, + deployer, + unique, + class_hash, + calldata, + salt } ); From 8ee02975e89af1ecad24adefdfcfedf558fd33af Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Mar 2024 15:32:33 -0400 Subject: [PATCH 18/35] abstract event assertions into fn --- .../presets/test_universal_deployer.cairo | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 499ab711a..314742ca9 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -53,19 +53,15 @@ fn test_deploy_not_unique() { assert_eq!(expected_addr, deployed_addr); // Check event - let event = utils::pop_log::(udc.contract_address).unwrap(); - let expected = UniversalDeployer::Event::ContractDeployed( - ContractDeployed { - address: deployed_addr, - deployer: CALLER(), - unique: unique, - class_hash: ERC20_CLASS_HASH(), - calldata: ERC20_CALLDATA(), - salt: SALT - } + assert_event_contract_deployed( + udc.contract_address, + deployed_addr, + CALLER(), + unique, + ERC20_CLASS_HASH(), + ERC20_CALLDATA(), + SALT ); - assert!(event == expected); - utils::assert_no_events_left(udc.contract_address); // Check deployment let erc20 = IERC20Dispatcher { contract_address: deployed_addr }; @@ -88,19 +84,15 @@ fn test_deploy_unique() { assert_eq!(expected_addr, deployed_addr); // Check event - let event = utils::pop_log::(udc.contract_address).unwrap(); - let expected = UniversalDeployer::Event::ContractDeployed( - ContractDeployed { - address: deployed_addr, - deployer: CALLER(), - unique: unique, - class_hash: ERC20_CLASS_HASH(), - calldata: ERC20_CALLDATA(), - salt: SALT - } + assert_event_contract_deployed( + udc.contract_address, + deployed_addr, + CALLER(), + unique, + ERC20_CLASS_HASH(), + ERC20_CALLDATA(), + SALT ); - assert!(event == expected); - utils::assert_no_events_left(udc.contract_address); // Check deployment let erc20 = IERC20Dispatcher { contract_address: deployed_addr }; @@ -150,3 +142,27 @@ fn calculate_contract_address_from_hash( let felt_addr = u256_addr.try_into().unwrap(); starknet::contract_address_try_from_felt252(felt_addr).unwrap() } + +fn assert_event_contract_deployed( + contract: ContractAddress, + address: ContractAddress, + deployer: ContractAddress, + unique: bool, + class_hash: ClassHash, + calldata: Span, + salt: felt252 +) { + let event = utils::pop_log::(contract).unwrap(); + let expected = UniversalDeployer::Event::ContractDeployed( + ContractDeployed { + address, + deployer, + unique, + class_hash, + calldata, + salt + } + ); + assert!(event == expected); + utils::assert_no_events_left(contract); +} From b6d1e3f450f0b9df2ad55d6c7fe4d33d9955f063 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Mar 2024 15:33:19 -0400 Subject: [PATCH 19/35] fix formatting --- src/presets/universal_deployer.cairo | 13 +------------ src/tests/presets/test_universal_deployer.cairo | 9 +-------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 058220412..299f3711a 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -56,18 +56,7 @@ mod UniversalDeployer { let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) .unwrap_syscall(); - self - .emit( - ContractDeployed { - address, - deployer, - unique, - class_hash, - calldata, - salt - } - ); - + self.emit(ContractDeployed { address, deployer, unique, class_hash, calldata, salt }); return address; } } diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 314742ca9..ffb3fefe0 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -154,14 +154,7 @@ fn assert_event_contract_deployed( ) { let event = utils::pop_log::(contract).unwrap(); let expected = UniversalDeployer::Event::ContractDeployed( - ContractDeployed { - address, - deployer, - unique, - class_hash, - calldata, - salt - } + ContractDeployed { address, deployer, unique, class_hash, calldata, salt } ); assert!(event == expected); utils::assert_no_events_left(contract); From fbe27e09b958836d003e9003b32f1662487e8955 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 12 Mar 2024 14:40:17 -0400 Subject: [PATCH 20/35] Update src/tests/presets/test_universal_deployer.cairo Co-authored-by: Eric Nordelo --- src/tests/presets/test_universal_deployer.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index ffb3fefe0..b8c299c89 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -143,7 +143,7 @@ fn calculate_contract_address_from_hash( starknet::contract_address_try_from_felt252(felt_addr).unwrap() } -fn assert_event_contract_deployed( +fn assert_only_event_contract_deployed( contract: ContractAddress, address: ContractAddress, deployer: ContractAddress, From 22e4779e0d35430be27a797e8afcea8dc47b79c6 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Mar 2024 14:53:13 -0400 Subject: [PATCH 21/35] update assertion fn --- src/tests/presets/test_universal_deployer.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index b8c299c89..ffed0e180 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -53,7 +53,7 @@ fn test_deploy_not_unique() { assert_eq!(expected_addr, deployed_addr); // Check event - assert_event_contract_deployed( + assert_only_event_contract_deployed( udc.contract_address, deployed_addr, CALLER(), @@ -84,7 +84,7 @@ fn test_deploy_unique() { assert_eq!(expected_addr, deployed_addr); // Check event - assert_event_contract_deployed( + assert_only_event_contract_deployed( udc.contract_address, deployed_addr, CALLER(), From 15699547b737b6791e13f5f1b6ad1c7d3a3c4e48 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Fri, 15 Mar 2024 11:05:04 -0400 Subject: [PATCH 22/35] Update src/presets/universal_deployer.cairo Co-authored-by: Eric Nordelo --- src/presets/universal_deployer.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 299f3711a..ff4893cce 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts for Cairo v0.9.0 (presets/universal_deployer.cairo) -/// # UniversalDeployer Preset +/// # UniversalDeployerContract Preset /// /// The Universal Deployer Contract is a singleton smart contract that wraps `deploy_syscall` /// to expose it to any contract that doesn't implement it, such as account contracts. From db5f13dd3f2d8e060c8d2f54eadccf92f0eab9d2 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 19 Mar 2024 00:56:52 -0400 Subject: [PATCH 23/35] use poseidon in udc --- src/presets/universal_deployer.cairo | 5 +++-- src/tests/presets/test_universal_deployer.cairo | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 299f3711a..fd9701ce0 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -11,7 +11,7 @@ /// This address may change in the future. #[starknet::contract] mod UniversalDeployer { - use core::pedersen::pedersen; + use core::poseidon; use openzeppelin::utils::universal_deployer::interface; use starknet::ClassHash; use starknet::ContractAddress; @@ -50,7 +50,8 @@ mod UniversalDeployer { let from_zero: bool = !unique; let mut _salt: felt252 = salt; if unique { - _salt = pedersen(deployer.into(), salt); + _salt = poseidon::poseidon_hash_span(array![deployer.into(), salt].span()); + } let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index ffed0e180..55f5d34bc 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -1,4 +1,5 @@ use core::pedersen::pedersen; +use core::poseidon; use openzeppelin::presets::universal_deployer::UniversalDeployer::ContractDeployed; use openzeppelin::presets::universal_deployer::UniversalDeployer; use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; @@ -76,7 +77,7 @@ fn test_deploy_unique() { testing::set_contract_address(CALLER()); // Check address - let hashed_salt = pedersen(CALLER().into(), SALT); + let hashed_salt = poseidon::poseidon_hash_span(array![CALLER().into(), SALT].span()); let expected_addr = calculate_contract_address_from_hash( hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address ); From c1de6e0b2a01e9e41ac8b7cc40262bc7b323d256 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 19 Mar 2024 00:57:20 -0400 Subject: [PATCH 24/35] fix formatting --- src/presets/universal_deployer.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index fd9701ce0..e829f3af7 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -51,7 +51,6 @@ mod UniversalDeployer { let mut _salt: felt252 = salt; if unique { _salt = poseidon::poseidon_hash_span(array![deployer.into(), salt].span()); - } let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) From 05a6fa266390953fd00542e8da1488c111c76e0b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 19 Mar 2024 01:09:02 -0400 Subject: [PATCH 25/35] fix spdx --- src/presets/universal_deployer.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index e829f3af7..4494caf7f 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.9.0 (presets/universal_deployer.cairo) +// OpenZeppelin Contracts for Cairo v0.10.0 (presets/universal_deployer.cairo) /// # UniversalDeployer Preset /// From f25a3c18deb9afc418b9f51fdd9840ae2cf922a5 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 19 Mar 2024 15:29:53 -0400 Subject: [PATCH 26/35] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martín Triay --- src/presets/universal_deployer.cairo | 12 +++++------- src/utils/universal_deployer/interface.cairo | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 6cad82d6c..1a19d86d4 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -3,8 +3,7 @@ /// # UniversalDeployerContract Preset /// -/// The Universal Deployer Contract is a singleton smart contract that wraps `deploy_syscall` -/// to expose it to any contract that doesn't implement it, such as account contracts. +/// The Universal Deployer Contract is a standardized generic factory of Starknet contracts. /// /// This contract is already deployed at 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf /// on mainnet, testnets, and starknet-devnet. @@ -31,7 +30,7 @@ mod UniversalDeployer { struct ContractDeployed { address: ContractAddress, deployer: ContractAddress, - unique: bool, + from_zero: bool, class_hash: ClassHash, calldata: Span, salt: felt252, @@ -43,20 +42,19 @@ mod UniversalDeployer { ref self: ContractState, class_hash: ClassHash, salt: felt252, - unique: bool, + from_zero: bool, calldata: Span ) -> ContractAddress { let deployer: ContractAddress = get_caller_address(); - let from_zero: bool = !unique; let mut _salt: felt252 = salt; - if unique { + if !from_zero { _salt = poseidon::poseidon_hash_span(array![deployer.into(), salt].span()); } let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) .unwrap_syscall(); - self.emit(ContractDeployed { address, deployer, unique, class_hash, calldata, salt }); + self.emit(ContractDeployed { address, deployer, from_zero, class_hash, calldata, salt }); return address; } } diff --git a/src/utils/universal_deployer/interface.cairo b/src/utils/universal_deployer/interface.cairo index 9cd6a2e20..d94c98a65 100644 --- a/src/utils/universal_deployer/interface.cairo +++ b/src/utils/universal_deployer/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.9.0 (utils/universal_deployer/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.10.0 (utils/universal_deployer/interface.cairo) use starknet::ClassHash; use starknet::ContractAddress; From f38a5a645cdb1ba707aa8d0779ae2ea910d71a4c Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 19 Mar 2024 16:25:24 -0400 Subject: [PATCH 27/35] fix from_zero conflicts, change poseidon use, update tests --- src/presets/universal_deployer.cairo | 8 +++-- .../presets/test_universal_deployer.cairo | 30 +++++++++++-------- src/utils/universal_deployer/interface.cairo | 2 +- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 1a19d86d4..776fbfd59 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -10,7 +10,8 @@ /// This address may change in the future. #[starknet::contract] mod UniversalDeployer { - use core::poseidon; + use hash::{HashStateTrait, HashStateExTrait}; + use poseidon::PoseidonTrait; use openzeppelin::utils::universal_deployer::interface; use starknet::ClassHash; use starknet::ContractAddress; @@ -48,7 +49,10 @@ mod UniversalDeployer { let deployer: ContractAddress = get_caller_address(); let mut _salt: felt252 = salt; if !from_zero { - _salt = poseidon::poseidon_hash_span(array![deployer.into(), salt].span()); + let mut state = PoseidonTrait::new(); + state = state.update_with(deployer); + state = state.update_with(salt); + _salt = state.finalize(); } let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 55f5d34bc..af5daec5d 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -1,5 +1,6 @@ use core::pedersen::pedersen; -use core::poseidon; +use hash::{HashStateTrait, HashStateExTrait}; +use poseidon::PoseidonTrait; use openzeppelin::presets::universal_deployer::UniversalDeployer::ContractDeployed; use openzeppelin::presets::universal_deployer::UniversalDeployer; use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; @@ -41,16 +42,16 @@ fn deploy_udc() -> IUniversalDeployerDispatcher { } #[test] -fn test_deploy_not_unique() { +fn test_deploy_from_zero() { let udc = deploy_udc(); - let unique = false; + let from_zero = true; testing::set_contract_address(CALLER()); // Check address let expected_addr = calculate_contract_address_from_hash( SALT, ERC20_CLASS_HASH(), ERC20_CALLDATA(), Zeroable::zero() ); - let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA()); + let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, from_zero, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); // Check event @@ -58,7 +59,7 @@ fn test_deploy_not_unique() { udc.contract_address, deployed_addr, CALLER(), - unique, + from_zero, ERC20_CLASS_HASH(), ERC20_CALLDATA(), SALT @@ -71,17 +72,22 @@ fn test_deploy_not_unique() { } #[test] -fn test_deploy_unique() { +fn test_deploy_not_from_zero() { let udc = deploy_udc(); - let unique = true; + let not_from_zero = false; testing::set_contract_address(CALLER()); + // Hash salt + let mut state = PoseidonTrait::new(); + state = state.update_with(CALLER()); + state = state.update_with(SALT); + let hashed_salt = state.finalize(); + // Check address - let hashed_salt = poseidon::poseidon_hash_span(array![CALLER().into(), SALT].span()); let expected_addr = calculate_contract_address_from_hash( hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address ); - let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA()); + let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, not_from_zero, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); // Check event @@ -89,7 +95,7 @@ fn test_deploy_unique() { udc.contract_address, deployed_addr, CALLER(), - unique, + not_from_zero, ERC20_CLASS_HASH(), ERC20_CALLDATA(), SALT @@ -148,14 +154,14 @@ fn assert_only_event_contract_deployed( contract: ContractAddress, address: ContractAddress, deployer: ContractAddress, - unique: bool, + from_zero: bool, class_hash: ClassHash, calldata: Span, salt: felt252 ) { let event = utils::pop_log::(contract).unwrap(); let expected = UniversalDeployer::Event::ContractDeployed( - ContractDeployed { address, deployer, unique, class_hash, calldata, salt } + ContractDeployed { address, deployer, from_zero, class_hash, calldata, salt } ); assert!(event == expected); utils::assert_no_events_left(contract); diff --git a/src/utils/universal_deployer/interface.cairo b/src/utils/universal_deployer/interface.cairo index d94c98a65..0b7fcfa45 100644 --- a/src/utils/universal_deployer/interface.cairo +++ b/src/utils/universal_deployer/interface.cairo @@ -10,7 +10,7 @@ trait IUniversalDeployer { ref self: TState, class_hash: ClassHash, salt: felt252, - unique: bool, + from_zero: bool, calldata: Span ) -> ContractAddress; } From 14fc5bed5d6f145d64630322a84cf8c09aec1b3c Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 19 Mar 2024 16:25:55 -0400 Subject: [PATCH 28/35] fix formatting --- src/presets/universal_deployer.cairo | 7 +++++-- src/tests/presets/test_universal_deployer.cairo | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 776fbfd59..3786492b0 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -11,8 +11,8 @@ #[starknet::contract] mod UniversalDeployer { use hash::{HashStateTrait, HashStateExTrait}; - use poseidon::PoseidonTrait; use openzeppelin::utils::universal_deployer::interface; + use poseidon::PoseidonTrait; use starknet::ClassHash; use starknet::ContractAddress; use starknet::SyscallResultTrait; @@ -58,7 +58,10 @@ mod UniversalDeployer { let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) .unwrap_syscall(); - self.emit(ContractDeployed { address, deployer, from_zero, class_hash, calldata, salt }); + self + .emit( + ContractDeployed { address, deployer, from_zero, class_hash, calldata, salt } + ); return address; } } diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index af5daec5d..6a2c72711 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -1,6 +1,5 @@ use core::pedersen::pedersen; use hash::{HashStateTrait, HashStateExTrait}; -use poseidon::PoseidonTrait; use openzeppelin::presets::universal_deployer::UniversalDeployer::ContractDeployed; use openzeppelin::presets::universal_deployer::UniversalDeployer; use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock; @@ -11,6 +10,7 @@ use openzeppelin::utils::serde::SerializedAppend; use openzeppelin::utils::universal_deployer::interface::{ IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait }; +use poseidon::PoseidonTrait; use starknet::ClassHash; use starknet::ContractAddress; use starknet::testing; @@ -87,7 +87,8 @@ fn test_deploy_not_from_zero() { let expected_addr = calculate_contract_address_from_hash( hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address ); - let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, not_from_zero, ERC20_CALLDATA()); + let deployed_addr = udc + .deploy_contract(ERC20_CLASS_HASH(), SALT, not_from_zero, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); // Check event From 8e474a1387ed09b172efe7c6bc2485265bb6bd28 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 19 Mar 2024 16:29:14 -0400 Subject: [PATCH 29/35] remove deployment info from comment --- src/presets/universal_deployer.cairo | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 3786492b0..a28e462bd 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -4,10 +4,6 @@ /// # UniversalDeployerContract Preset /// /// The Universal Deployer Contract is a standardized generic factory of Starknet contracts. -/// -/// This contract is already deployed at 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf -/// on mainnet, testnets, and starknet-devnet. -/// This address may change in the future. #[starknet::contract] mod UniversalDeployer { use hash::{HashStateTrait, HashStateExTrait}; From f8da0111ac6272ad75e3e3d9cdabe9451f73d2b9 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 20 Mar 2024 16:07:40 -0400 Subject: [PATCH 30/35] change code reference in comment --- src/tests/presets/test_universal_deployer.cairo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 6a2c72711..735086cb4 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -112,7 +112,6 @@ fn test_deploy_not_from_zero() { // Helpers // -/// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/cairo/common/hash_state.py fn compute_hash_on_elements(mut data: Span) -> felt252 { let data_len: usize = data.len(); let mut hash = 0; @@ -128,7 +127,7 @@ fn compute_hash_on_elements(mut data: Span) -> felt252 { hash } -/// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/starknet/core/os/contract_address/contract_address.py +/// See https://github.com/starkware-libs/cairo/blob/v2.6.3/crates/cairo-lang-runner/src/casm_run/contract_address.rs#L38-L57 fn calculate_contract_address_from_hash( salt: felt252, class_hash: ClassHash, From 036500ebe6763491738a9c08dbb8b521a6dbe690 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Fri, 22 Mar 2024 00:56:17 -0400 Subject: [PATCH 31/35] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martín Triay --- src/tests/presets/test_universal_deployer.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 735086cb4..d091de3b4 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -74,7 +74,7 @@ fn test_deploy_from_zero() { #[test] fn test_deploy_not_from_zero() { let udc = deploy_udc(); - let not_from_zero = false; + let from_zero = false; testing::set_contract_address(CALLER()); // Hash salt @@ -88,7 +88,7 @@ fn test_deploy_not_from_zero() { hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address ); let deployed_addr = udc - .deploy_contract(ERC20_CLASS_HASH(), SALT, not_from_zero, ERC20_CALLDATA()); + .deploy_contract(ERC20_CLASS_HASH(), SALT, from_zero, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); // Check event From b1c86c5c19f6e6173785479b78c8e75e4df20ad0 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 22 Mar 2024 04:14:58 -0400 Subject: [PATCH 32/35] fix formatting --- src/tests/presets/test_universal_deployer.cairo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index d091de3b4..a086811fc 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -87,8 +87,7 @@ fn test_deploy_not_from_zero() { let expected_addr = calculate_contract_address_from_hash( hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address ); - let deployed_addr = udc - .deploy_contract(ERC20_CLASS_HASH(), SALT, from_zero, ERC20_CALLDATA()); + let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, from_zero, ERC20_CALLDATA()); assert_eq!(expected_addr, deployed_addr); // Check event From 1aa212d636f36bcda880b48eec1b6e86845bf3fa Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 22 Mar 2024 04:18:11 -0400 Subject: [PATCH 33/35] fix from_zero var in event test --- src/tests/presets/test_universal_deployer.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index a086811fc..43ecc0ed5 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -95,7 +95,7 @@ fn test_deploy_not_from_zero() { udc.contract_address, deployed_addr, CALLER(), - not_from_zero, + from_zero, ERC20_CLASS_HASH(), ERC20_CALLDATA(), SALT From 564f0c80c800bbb88b4ad55eab2b62c6dae2f855 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Fri, 22 Mar 2024 12:02:56 -0400 Subject: [PATCH 34/35] Apply suggestions from code review Co-authored-by: Eric Nordelo --- src/presets/universal_deployer.cairo | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index a28e462bd..9c5b81919 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -45,10 +45,8 @@ mod UniversalDeployer { let deployer: ContractAddress = get_caller_address(); let mut _salt: felt252 = salt; if !from_zero { - let mut state = PoseidonTrait::new(); - state = state.update_with(deployer); - state = state.update_with(salt); - _salt = state.finalize(); + let mut hash_state = PoseidonTrait::new(); + _salt = hash_state.update_with(deployer).update_with(salt).finalize(); } let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero) From bca330199e1b380b2b462eddb5c555d31244fa04 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 22 Mar 2024 12:24:16 -0400 Subject: [PATCH 35/35] update HashTrait in test --- src/tests/presets/test_universal_deployer.cairo | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tests/presets/test_universal_deployer.cairo b/src/tests/presets/test_universal_deployer.cairo index 43ecc0ed5..a66d8d03a 100644 --- a/src/tests/presets/test_universal_deployer.cairo +++ b/src/tests/presets/test_universal_deployer.cairo @@ -79,9 +79,7 @@ fn test_deploy_not_from_zero() { // Hash salt let mut state = PoseidonTrait::new(); - state = state.update_with(CALLER()); - state = state.update_with(SALT); - let hashed_salt = state.finalize(); + let hashed_salt = state.update_with(CALLER()).update_with(SALT).finalize(); // Check address let expected_addr = calculate_contract_address_from_hash(