From ca11b87d1bf3dc03618834c40a9427ba11ec7439 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Thu, 29 Feb 2024 14:58:15 +0100 Subject: [PATCH] Add ERC1155 (#896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add ERC1155 module, dual and interface * Implement ERC1155 and update token_id variable names * Add ERC1155 receiver mocks and test dual1155 receiver * apply linter * Update token_uri variable and remove IERC1155MetadataCamelOnly support * Add ERC1155 mocks and tests and add ERC1155 preset * update CHANGELOG * adding PR number (#852) * reset changlog * Update ERC1155Component Event and add batch_burn * Add batch minting functions for ERC1155 tokens * Refactor code for better readability Update error testing and remove tests failing from erc1155 * feat: finish first version with 2.5.3 * feat: apply review updates * feat: add tests * feat: bump version and add entry to CHANGELOG * feat: add more tests * fix: remove print * feat: add preset tests * fix: format * feat: uncomment test * refactor: shortening test names * feat: move assertion * Update src/token/erc1155/erc1155.cairo Co-authored-by: Martín Triay * fix: comments * featL apply review updates * feat: remove IAccount --------- Co-authored-by: Arn0d Co-authored-by: Martín Triay --- CHANGELOG.md | 4 + src/presets.cairo | 2 + src/presets/erc1155.cairo | 72 + src/tests/account/test_secp256k1.cairo | 1 + src/tests/mocks.cairo | 2 + src/tests/mocks/erc1155_mocks.cairo | 308 ++++ src/tests/mocks/erc1155_receiver_mocks.cairo | 204 +++ src/tests/mocks/erc721_mocks.cairo | 6 +- src/tests/mocks/eth_account_mocks.cairo | 2 +- src/tests/presets.cairo | 1 + src/tests/presets/test_erc1155.cairo | 726 +++++++++ src/tests/presets/test_erc721.cairo | 4 +- src/tests/token.cairo | 4 + src/tests/token/test_dual1155.cairo | 386 +++++ src/tests/token/test_dual1155_receiver.cairo | 157 ++ src/tests/token/test_erc1155.cairo | 1402 ++++++++++++++++++ src/tests/token/test_erc1155_receiver.cairo | 80 + src/tests/token/test_erc721_receiver.cairo | 4 +- src/tests/utils.cairo | 10 + src/tests/utils/constants.cairo | 7 + 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 | 545 +++++++ src/token/erc1155/erc1155_receiver.cairo | 102 ++ src/token/erc1155/interface.cairo | 176 +++ src/token/erc721/erc721.cairo | 3 +- src/utils/selectors.cairo | 22 + 29 files changed, 4478 insertions(+), 10 deletions(-) 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/CHANGELOG.md b/CHANGELOG.md index b0efc9caf..01c3b21e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- ERC1155 component and preset (#896) + ### Changed - Change unwrap to unwrap_syscall (#901) diff --git a/src/presets.cairo b/src/presets.cairo index 1a0804706..1c960eae0 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -1,9 +1,11 @@ mod account; +mod erc1155; mod erc20; mod erc721; mod eth_account; use account::Account; +use erc1155::ERC1155; use erc20::ERC20; use erc721::ERC721; use eth_account::EthAccountUpgradeable; diff --git a/src/presets/erc1155.cairo b/src/presets/erc1155.cairo new file mode 100644 index 000000000..cbefb3a3a --- /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 `token_uri`. + /// 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, + 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()); + } +} 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/erc1155_mocks.cairo b/src/tests/mocks/erc1155_mocks.cairo new file mode 100644 index 000000000..68da49228 --- /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, + recipient: ContractAddress, + token_id: u256, + value: u256, + uri: ByteArray + ) { + self.erc1155.initializer(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, + recipient: ContractAddress, + token_id: u256, + value: u256, + uri: ByteArray + ) { + self.erc1155.initializer(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, + recipient: ContractAddress, + token_id: u256, + value: u256, + uri: ByteArray + ) { + self.erc1155.initializer(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/erc721_mocks.cairo b/src/tests/mocks/erc721_mocks.cairo index 241e7ddc0..b12c6b3bc 100644 --- a/src/tests/mocks/erc721_mocks.cairo +++ b/src/tests/mocks/erc721_mocks.cairo @@ -162,8 +162,8 @@ mod CamelERC721Mock { /// 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) { @@ -196,8 +196,8 @@ mod SnakeERC721PanicMock { #[storage] struct Storage {} - #[generate_trait] #[abi(per_item)] + #[generate_trait] impl ExternalImpl of ExternalTrait { #[external(v0)] fn name(self: @ContractState) -> felt252 { @@ -289,8 +289,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/eth_account_mocks.cairo b/src/tests/mocks/eth_account_mocks.cairo index b1f35733c..ac2caff73 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> { diff --git a/src/tests/presets.cairo b/src/tests/presets.cairo index 91046a8a8..05b583e3e 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_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_erc721.cairo b/src/tests/presets/test_erc721.cairo index 3e2789512..f98734535 100644 --- a/src/tests/presets/test_erc721.cairo +++ b/src/tests/presets/test_erc721.cairo @@ -145,8 +145,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 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..272072fd7 --- /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 uri: ByteArray = "URI"; + let owner = setup_account(); + let mut calldata = array![]; + calldata.append_serde(owner); + calldata.append_serde(TOKEN_ID); + calldata.append_serde(TOKEN_VALUE); + calldata.append_serde(uri); + 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 uri: ByteArray = "URI"; + let owner = setup_account(); + let mut calldata = array![]; + calldata.append_serde(owner); + calldata.append_serde(TOKEN_ID); + calldata.append_serde(TOKEN_VALUE); + calldata.append_serde(uri); + 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_erc1155.cairo b/src/tests/token/test_erc1155.cairo new file mode 100644 index 000000000..1f28bb60a --- /dev/null +++ b/src/tests/token/test_erc1155.cairo @@ -0,0 +1,1402 @@ +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(); + assert_eq!(event.owner, owner); + assert_eq!(event.operator, operator); + assert_eq!(event.approved, approved); + + // Check indexed keys + let mut indexed_keys = array![]; + 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(); + assert_eq!(event.operator, operator); + assert_eq!(event.from, from); + assert_eq!(event.to, to); + assert_eq!(event.id, token_id); + assert_eq!(event.value, value); + + // Check indexed keys + let mut indexed_keys = array![]; + 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(); + assert_eq!(event.operator, operator); + assert_eq!(event.from, from); + assert_eq!(event.to, to); + assert_eq!(event.ids, token_ids); + assert_eq!(event.values, values); + + // Check indexed keys + let mut indexed_keys = array![]; + 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_erc721_receiver.cairo b/src/tests/token/test_erc721_receiver.cairo index c7c8c4f14..a84e1c434 100644 --- a/src/tests/token/test_erc721_receiver.cairo +++ b/src/tests/token/test_erc721_receiver.cairo @@ -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/utils.cairo b/src/tests/utils.cairo index ecb202c30..d96193b2d 100644 --- a/src/tests/utils.cairo +++ b/src/tests/utils.cairo @@ -12,6 +12,16 @@ fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAdd address } +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 for the contract as the requested type /// and checks there's no more keys or data left on the event, preventing unaccounted params. /// diff --git a/src/tests/utils/constants.cairo b/src/tests/utils/constants.cairo index 740d40252..39c6f8035 100644 --- a/src/tests/utils/constants.cairo +++ b/src/tests/utils/constants.cairo @@ -15,6 +15,9 @@ 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'; @@ -91,3 +94,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..b50079a20 --- /dev/null +++ b/src/token/erc1155/erc1155.cairo @@ -0,0 +1,545 @@ +// 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, 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, 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, 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, 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, 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 number of NFTs owned by `account` for a specific `token_id`. + fn balance_of( + self: @ComponentState, account: ContractAddress, token_id: u256 + ) -> u256 { + self.ERC1155_balances.read((token_id, account)) + } + + + /// Returns a span of u256 values representing the batch balances of the + /// `accounts` for the specified `token_ids`. + /// + /// 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 `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 `safeTransferFrom`. + /// + /// 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); + } + + /// Enable or disable 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 }); + } + + /// Query 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 token uri. + /// This should only be used inside the contract's constructor. + fn initializer(ref self: ComponentState, uri: ByteArray) { + self.set_uri(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. + 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_uri(ref self: ComponentState, uri: ByteArray) { + self.ERC1155_uri.write(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/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 9a9a3e44b..dbea882ba 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -526,8 +526,7 @@ mod ERC721Component { } /// 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 { 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 //