From 8360af87542c215f5b3b932834eb6e07073f6047 Mon Sep 17 00:00:00 2001 From: notV4l <122404722+notV4l@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:59:56 +0200 Subject: [PATCH] Erc1155 dojotized (#925) * erc1155 impl, untested * erc1155 fix & tests * Component -> Model * update to models --- crates/dojo-erc/src/lib.cairo | 2 + crates/dojo-erc/src/tests/constants.cairo | 3 + crates/dojo-erc/src/tests/erc1155_tests.cairo | 836 ++++++++++++++++++ crates/dojo-erc/src/tests/erc721_tests.cairo | 1 - crates/dojo-erc/src/tests/utils.cairo | 6 - crates/dojo-erc/src/token/erc1155.cairo | 5 + .../dojo-erc/src/token/erc1155/erc1155.cairo | 439 +++++++++ .../src/token/erc1155/interface.cairo | 62 ++ .../dojo-erc/src/token/erc1155/models.cairo | 33 + 9 files changed, 1380 insertions(+), 7 deletions(-) create mode 100644 crates/dojo-erc/src/tests/erc1155_tests.cairo create mode 100644 crates/dojo-erc/src/token/erc1155.cairo create mode 100644 crates/dojo-erc/src/token/erc1155/erc1155.cairo create mode 100644 crates/dojo-erc/src/token/erc1155/interface.cairo create mode 100644 crates/dojo-erc/src/token/erc1155/models.cairo diff --git a/crates/dojo-erc/src/lib.cairo b/crates/dojo-erc/src/lib.cairo index d72ce1149b..537938a7ba 100644 --- a/crates/dojo-erc/src/lib.cairo +++ b/crates/dojo-erc/src/lib.cairo @@ -2,6 +2,7 @@ mod token { mod erc20; mod erc20_models; mod erc721; + mod erc1155; } @@ -12,4 +13,5 @@ mod tests { mod erc20_tests; mod erc721_tests; + mod erc1155_tests; } diff --git a/crates/dojo-erc/src/tests/constants.cairo b/crates/dojo-erc/src/tests/constants.cairo index a66b6e7218..6487fc1836 100644 --- a/crates/dojo-erc/src/tests/constants.cairo +++ b/crates/dojo-erc/src/tests/constants.cairo @@ -10,6 +10,9 @@ const ROLE: felt252 = 'ROLE'; const OTHER_ROLE: felt252 = 'OTHER_ROLE'; const URI: felt252 = 'URI'; const TOKEN_ID: u256 = 21; +const TOKEN_AMOUNT: u256 = 42; +const TOKEN_ID_2: u256 = 2; +const TOKEN_AMOUNT_2: u256 = 69; const PUBKEY: felt252 = 'PUBKEY'; fn ADMIN() -> ContractAddress { diff --git a/crates/dojo-erc/src/tests/erc1155_tests.cairo b/crates/dojo-erc/src/tests/erc1155_tests.cairo new file mode 100644 index 0000000000..6951695b51 --- /dev/null +++ b/crates/dojo-erc/src/tests/erc1155_tests.cairo @@ -0,0 +1,836 @@ +use dojo_erc::tests::utils; +use dojo_erc::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID, TOKEN_AMOUNT, + TOKEN_ID_2, TOKEN_AMOUNT_2 +}; + +use dojo_erc::token::erc1155::ERC1155::ERC1155Impl; +use dojo_erc::token::erc1155::ERC1155::ERC1155CamelOnlyImpl; +use dojo_erc::token::erc1155::ERC1155::ERC1155MetadataImpl; +use dojo_erc::token::erc1155::ERC1155::InternalImpl; +use dojo_erc::token::erc1155::ERC1155::WorldInteractionsImpl; +use dojo_erc::token::erc1155::ERC1155::{TransferSingle, TransferBatch, ApprovalForAll}; +use dojo_erc::token::erc1155::ERC1155; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; +use zeroable::Zeroable; +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::token::erc1155::models::{ + ERC1155Meta, erc_1155_meta, ERC1155OperatorApproval, erc_1155_operator_approval, ERC1155Balance, + erc_1155_balance +}; +use dojo_erc::token::erc1155::ERC1155::_worldContractMemberStateTrait; +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC1155::ContractState) { + let world = spawn_test_world( + array![ + erc_1155_meta::TEST_CLASS_HASH, + erc_1155_operator_approval::TEST_CLASS_HASH, + erc_1155_balance::TEST_CLASS_HASH, + ] + ); + let mut state = ERC1155::contract_state_for_testing(); + state._world.write(world.contract_address); + + InternalImpl::_mint(ref state, OWNER(), TOKEN_ID, TOKEN_AMOUNT); + utils::drop_event(ZERO()); + + InternalImpl::_mint(ref state, OWNER(), TOKEN_ID_2, TOKEN_AMOUNT_2); + utils::drop_event(ZERO()); + + (world, state) +} + +fn setup() -> ERC1155::ContractState { + let (world, mut state) = STATE(); + ERC1155::constructor(ref state, world.contract_address, NAME, SYMBOL, URI); + utils::drop_event(ZERO()); + state +} + +// +// initializer & constructor +// + +#[test] +#[available_gas(20000000)] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC1155::constructor(ref state, world.contract_address, NAME, SYMBOL, URI); + + assert(ERC1155MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC1155MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC1155MetadataImpl::uri(@state, 0) == URI, 'Uri should be URI'); +// assert( +// SRC5Impl::supports_interface(@state, erc1155::interface::IERC1155_ID), 'Missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc1155::interface::IERC1155_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + +#[test] +#[available_gas(20000000)] +fn test_initializer() { + let (world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL, URI); + + assert(ERC1155MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC1155MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + + assert(ERC1155Impl::balance_of(@state, OWNER(), 0) == 0, 'Balance should be zero'); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'should be TOKEN_AMOUNT' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'should be TOKEN_AMOUNT_2' + ); +} + + +// +// Getters +// + +#[test] +#[available_gas(20000000)] +fn test_balance_of() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Should return balance' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid account',))] +fn test_balance_of_zero() { + let state = setup(); + ERC1155Impl::balance_of(@state, ZERO(), TOKEN_ID); +} + + +#[test] +#[available_gas(20000000)] +fn test_balance_of_batch() { + let mut state = setup(); + + InternalImpl::_mint(ref state, OTHER(), TOKEN_ID_2, TOKEN_AMOUNT_2); + + let balances = ERC1155Impl::balance_of_batch( + @state, array![OWNER(), OTHER()], array![TOKEN_ID, TOKEN_ID_2] + ); + + assert(*balances.at(0) == TOKEN_AMOUNT, 'Should return TOKEN_AMOUNT'); + assert(*balances.at(1) == TOKEN_AMOUNT_2, 'Should return TOKEN_AMOUNT_2'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid account',))] +fn test_balance_of_batch_zero() { + let state = setup(); + ERC1155Impl::balance_of_batch(@state, array![OTHER(), ZERO()], array![TOKEN_ID_2, TOKEN_ID]); +} + + +// +// set_approval_for_all & _set_approval_for_all +// + +#[test] +#[available_gas(20000000)] +fn test_set_approval_for_all() { + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + + assert(!ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Approval not revoked correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_true() { + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC1155Impl::set_approval_for_all(ref state, OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_false() { + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC1155Impl::set_approval_for_all(ref state, OWNER(), false); +} + +#[test] +#[available_gas(20000000)] +fn test__set_approval_for_all() { + let (world, mut state) = STATE(); + assert(!ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_true() { + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_false() { + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); +} + + +// +// safe_transfer_from & safeTransferFrom +// + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_from_owner() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155Impl::safe_transfer_from(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_owner() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safe_transfer_from_zero() { + let (world, mut state) = STATE(); + ERC1155Impl::safe_transfer_from( + ref state, ZERO(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safeTransferFrom_zero() { + let (world, mut state) = STATE(); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, ZERO(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safe_transfer_from_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC1155Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, TOKEN_AMOUNT, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safeTransferFrom_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, OWNER(), ZERO(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_from_to_owner() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + + testing::set_caller_address(OWNER()); + ERC1155Impl::safe_transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT, array![]); + assert_event_transfer_single(OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner after' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_safeTransferFrom_to_owner() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + + testing::set_caller_address(OWNER()); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); + assert_event_transfer_single(OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner after' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_approved_for_all() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155Impl::safe_transfer_from(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(50000000)] +fn test_safeTransferFrom_approved_for_all() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::setApprovalForAll(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safe_transfer_from_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC1155Impl::safe_transfer_from( + ref state, OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safeTransferFrom_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + + +// +// safe_batch_transfer_from & safeBatchTransferFrom +// + +#[test] +#[available_gas(50000000)] +fn test_safe_batch_transfer_from_owner() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155Impl::safe_batch_transfer_from( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(50000000)] +fn test_safeBatchTransferFrom_owner() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safe_batch_transfer_from_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155Impl::safe_batch_transfer_from(ref state, ZERO(), RECIPIENT(), ids, amounts, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safeBatchTransferFrom_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, ZERO(), RECIPIENT(), ids, amounts, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safe_batch_transfer_from_to_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), ZERO(), ids, amounts, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safeBatchTransferFrom_to_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), ZERO(), ids, amounts, array![]); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_batch_transfer_from_to_owner() { + let mut state = setup(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner before1' + ); + + testing::set_caller_address(OWNER()); + ERC1155Impl::safe_batch_transfer_from( + ref state, OWNER(), OWNER(), ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(OWNER(), OWNER(), ids, amounts); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner after1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner after2' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_safeBatchTransferFrom_to_owner() { + let mut state = setup(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner before1' + ); + + testing::set_caller_address(OWNER()); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, OWNER(), OWNER(), ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(OWNER(), OWNER(), ids, amounts); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner after1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner after2' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_batch_transfer_from_approved_for_all() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155Impl::safe_batch_transfer_from( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(50000000)] +fn test_safeBatchTransferFrom_approved_for_all() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::setApprovalForAll(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safe_batch_transfer_from_unauthorized() { + let mut state = setup(); + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + testing::set_caller_address(OTHER()); + ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), RECIPIENT(), ids, amounts, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safeBatchTransferFrom_unauthorized() { + let mut state = setup(); + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + testing::set_caller_address(OTHER()); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, OWNER(), RECIPIENT(), ids, amounts, array![] + ); +} + +// +// _mint +// + +#[test] +#[available_gas(20000000)] +fn test__mint() { + let (world, mut state) = STATE(); + let recipient = RECIPIENT(); + + assert( + ERC1155Impl::balance_of(@state, recipient, TOKEN_ID_2) == 0, 'Balance of recipient before' + ); + + InternalImpl::_mint(ref state, recipient, TOKEN_ID_2, TOKEN_AMOUNT_2); + assert_event_transfer_single(ZERO(), recipient, TOKEN_ID_2, TOKEN_AMOUNT_2); + + assert( + ERC1155Impl::balance_of(@state, recipient, TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of recipient after' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test__mint_to_zero() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, ZERO(), TOKEN_ID, TOKEN_AMOUNT); +} + + +// +// _burn +// + +#[test] +#[available_gas(20000000)] +fn test__burn() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + + testing::set_caller_address(OWNER()); + InternalImpl::_burn(ref state, TOKEN_ID, 2); + assert_event_transfer_single(OWNER(), ZERO(), TOKEN_ID, 2); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT - 2, + 'Balance of owner after' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test__burn_more_than_balance() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + InternalImpl::_burn(ref state, TOKEN_ID, TOKEN_AMOUNT + 1); +} + + +// +// Helpers +// + +fn assert_state_before_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress, id: u256, +) { + assert(ERC1155Impl::balance_of(state, owner, id) == TOKEN_AMOUNT, 'Balance of owner before'); + assert(ERC1155Impl::balance_of(state, recipient, id) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress, id: u256 +) { + assert(ERC1155Impl::balance_of(state, owner, id) == 0, 'Balance of owner after'); + assert( + ERC1155Impl::balance_of(state, recipient, id) == TOKEN_AMOUNT, 'Balance of recipient after' + ); +} + +fn assert_state_before_batch_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress +) { + assert( + ERC1155Impl::balance_of(state, owner, TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner before1' + ); + assert( + ERC1155Impl::balance_of(state, owner, TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner before2' + ); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID) == 0, 'Balance of recipient before1' + ); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID_2) == 0, 'Balance of recipient before2' + ); +} + +fn assert_state_after_batch_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress +) { + assert(ERC1155Impl::balance_of(state, owner, TOKEN_ID) == 0, 'Balance of owner after1'); + assert(ERC1155Impl::balance_of(state, owner, TOKEN_ID_2) == 0, 'Balance of owner after2'); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of recipient after1' + ); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of recipient after2' + ); +} + + +// +// events +// + +fn assert_event_approval_for_all( + owner: ContractAddress, operator: ContractAddress, approved: bool +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.operator == operator, 'Invalid `operator`'); + assert(event.approved == approved, 'Invalid `approved`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer_single( + from: ContractAddress, to: ContractAddress, id: u256, amount: u256 +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.id == id, 'Invalid `id`'); + assert(event.value == amount, 'Invalid `amount`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer_batch( + from: ContractAddress, to: ContractAddress, ids: Array, amounts: Array +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.ids.len() == event.values.len(), 'Invalid array length'); + + let mut i = 0; + + loop { + if i == event.ids.len() { + break; + } + + assert(event.ids.at(i) == ids.at(i), 'Invalid `id`'); + assert(event.values.at(i) == amounts.at(i), 'Invalid `id`'); + + i += 1; + }; + + utils::assert_no_events_left(ZERO()); +} + diff --git a/crates/dojo-erc/src/tests/erc721_tests.cairo b/crates/dojo-erc/src/tests/erc721_tests.cairo index a4c194eddb..415db0ff16 100644 --- a/crates/dojo-erc/src/tests/erc721_tests.cairo +++ b/crates/dojo-erc/src/tests/erc721_tests.cairo @@ -1,4 +1,3 @@ -use integer::BoundedInt; use integer::u256; use integer::u256_from_felt252; use dojo_erc::tests::utils; diff --git a/crates/dojo-erc/src/tests/utils.cairo b/crates/dojo-erc/src/tests/utils.cairo index c42ce68c97..99e956694f 100644 --- a/crates/dojo-erc/src/tests/utils.cairo +++ b/crates/dojo-erc/src/tests/utils.cairo @@ -1,13 +1,7 @@ -// mod dojo_erc::tests::constants; - -use array::ArrayTrait; -use array::SpanTrait; use core::result::ResultTrait; -use option::OptionTrait; use starknet::class_hash::Felt252TryIntoClassHash; use starknet::ContractAddress; use starknet::testing; -use traits::TryInto; fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAddress { let (address, _) = starknet::deploy_syscall( diff --git a/crates/dojo-erc/src/token/erc1155.cairo b/crates/dojo-erc/src/token/erc1155.cairo new file mode 100644 index 0000000000..af50183ea3 --- /dev/null +++ b/crates/dojo-erc/src/token/erc1155.cairo @@ -0,0 +1,5 @@ +mod erc1155; +mod models; +mod interface; + +use erc1155::ERC1155; \ No newline at end of file diff --git a/crates/dojo-erc/src/token/erc1155/erc1155.cairo b/crates/dojo-erc/src/token/erc1155/erc1155.cairo new file mode 100644 index 0000000000..26ba7f23bb --- /dev/null +++ b/crates/dojo-erc/src/token/erc1155/erc1155.cairo @@ -0,0 +1,439 @@ +#[starknet::contract] +mod ERC1155 { + use dojo_erc::token::erc1155::models::{ + ERC1155Meta, ERC1155OperatorApproval, ERC1155Balance + }; + use dojo_erc::token::erc1155::interface; + use dojo_erc::token::erc1155::interface::{IERC1155, IERC1155CamelOnly}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use array::ArrayTCloneImpl; + use zeroable::Zeroable; + use debug::PrintTrait; + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Clone, Drop, starknet::Event)] + enum Event { + TransferSingle: TransferSingle, + TransferBatch: TransferBatch, + ApprovalForAll: ApprovalForAll + } + + #[derive(Clone, Drop, starknet::Event)] + struct TransferSingle { + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + id: u256, + value: u256 + } + + #[derive(Clone, Drop, starknet::Event)] + struct TransferBatch { + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + ids: Array, + values: Array + } + + #[derive(Clone, Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + mod Errors { + const INVALID_TOKEN_ID: felt252 = 'ERC1155: invalid token ID'; + const INVALID_ACCOUNT: felt252 = 'ERC1155: invalid account'; + const UNAUTHORIZED: felt252 = 'ERC1155: unauthorized caller'; + const SELF_APPROVAL: felt252 = 'ERC1155: self approval'; + const INVALID_RECEIVER: felt252 = 'ERC1155: invalid receiver'; + const WRONG_SENDER: felt252 = 'ERC1155: wrong sender'; + const SAFE_MINT_FAILED: felt252 = 'ERC1155: safe mint failed'; + const SAFE_TRANSFER_FAILED: felt252 = 'ERC1155: safe transfer failed'; + const INVALID_ARRAY_LENGTH: felt252 = 'ERC1155: invalid array length'; + const INSUFFICIENT_BALANCE: felt252 = 'ERC1155: insufficient balance'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, + ) { + self._world.write(world); + self.initializer(name, symbol, base_uri); + } + + // + // External + // + + // #[external(v0)] + // impl SRC5Impl of ISRC5 { + // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) + // } + // } + + // #[external(v0)] + // impl SRC5CamelImpl of ISRC5Camel { + // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) + // } + // } + + #[external(v0)] + impl ERC1155MetadataImpl of interface::IERC1155Metadata { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn uri(self: @ContractState, token_id: u256) -> felt252 { + //assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + // TODO : concat with id + self.get_uri(token_id) + } + } + + + #[external(v0)] + impl ERC1155Impl of interface::IERC1155 { + fn balance_of(self: @ContractState, account: ContractAddress, id: u256) -> u256 { + assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); + self.get_balance(account, id).amount + } + + fn balance_of_batch( + self: @ContractState, accounts: Array, ids: Array + ) -> Array { + assert(ids.len() == accounts.len(), Errors::INVALID_ARRAY_LENGTH); + + let mut batch_balances = array![]; + let mut index = 0; + loop { + if index == ids.len() { + break batch_balances.clone(); + } + batch_balances.append(self.balance_of(*accounts.at(index), *ids.at(index))); + index += 1; + } + } + + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + self._set_approval_for_all(get_caller_address(), operator, approved) + } + + fn is_approved_for_all( + self: @ContractState, account: ContractAddress, operator: ContractAddress + ) -> bool { + self.get_operator_approval(account, operator).approved + } + + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + assert(from.is_non_zero(), Errors::WRONG_SENDER); + assert( + self._is_approved_for_all_or_owner(from, get_caller_address()), Errors::UNAUTHORIZED + ); + + self._safe_transfer_from(from, to, id, amount, data); + } + + fn safe_batch_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + assert(from.is_non_zero(), Errors::WRONG_SENDER); + assert( + self._is_approved_for_all_or_owner(from, get_caller_address()), Errors::UNAUTHORIZED + ); + + self._safe_batch_transfer_from(from, to, ids, amounts, data); + } + } + + #[external(v0)] + impl ERC1155CamelOnlyImpl of interface::IERC1155CamelOnly { + fn balanceOf(self: @ContractState, account: ContractAddress, id: u256) -> u256 { + ERC1155Impl::balance_of(self, account, id) + } + + fn balanceOfBatch( + self: @ContractState, accounts: Array, ids: Array + ) -> Array { + ERC1155Impl::balance_of_batch(self, accounts, ids) + } + + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + ERC1155Impl::set_approval_for_all(ref self, operator, approved); + } + fn isApprovedForAll( + self: @ContractState, account: ContractAddress, operator: ContractAddress + ) -> bool { + ERC1155Impl::is_approved_for_all(self, account, operator) + } + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ) { + ERC1155Impl::safe_transfer_from(ref self, from, to, id, amount, data); + } + fn safeBatchTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ) { + ERC1155Impl::safe_batch_transfer_from(ref self, from, to, ids, amounts, data); + } + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC1155Meta { + get!(self.world(), get_contract_address(), ERC1155Meta) + } + + fn get_uri(self: @ContractState, token_id: u256) -> felt252 { + // TODO : concat with id when we have string type + self.get_meta().base_uri + } + + fn get_balance(self: @ContractState, account: ContractAddress, id: u256) -> ERC1155Balance { + get!(self.world(), (get_contract_address(), account, id), ERC1155Balance) + } + + fn get_operator_approval( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> ERC1155OperatorApproval { + get!(self.world(), (get_contract_address(), owner, operator), ERC1155OperatorApproval) + } + + fn set_operator_approval( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + set!( + self.world(), + ERC1155OperatorApproval { token: get_contract_address(), owner, operator, approved } + ); + self.emit_event(ApprovalForAll { owner, operator, approved }); + } + + fn set_balance(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) { + set!( + self.world(), ERC1155Balance { token: get_contract_address(), account, id, amount } + ); + } + + fn update_balances( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + ) { + self.set_balance(from, id, self.get_balance(from, id).amount - amount); + self.set_balance(to, id, self.get_balance(to, id).amount + amount); + } + + fn emit_event< + S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SClone: Clone + >( + ref self: ContractState, event: S + ) { + self.emit(event.clone()); + emit!(self.world(), event); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { + let meta = ERC1155Meta { token: get_contract_address(), name, symbol, base_uri }; + set!(self.world(), (meta)); + // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); + // src5::SRC5::InternalImpl::register_interface( + // ref unsafe_state, interface::IERC721_METADATA_ID + // ); + } + + fn _is_approved_for_all_or_owner( + self: @ContractState, from: ContractAddress, caller: ContractAddress + ) -> bool { + caller == from || self.is_approved_for_all(from, caller) + } + + fn _set_approval_for_all( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + assert(owner != operator, Errors::SELF_APPROVAL); + self.set_operator_approval(owner, operator, approved); + } + + fn _safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ) { + self.update_balances(from, to, id, amount); + // assert( + // _check_on_erc1155_received(from, to, id, data), Errors::SAFE_TRANSFER_FAILED + // ); + + self + .emit_event( + TransferSingle { operator: get_caller_address(), from, to, id, value: amount } + ); + } + + fn _safe_batch_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ) { + assert(ids.len() == amounts.len(), Errors::INVALID_ARRAY_LENGTH); + + let mut ids_span = ids.span(); + let mut amounts_span = amounts.span(); + + loop { + if ids_span.len() == 0 { + break (); + } + let id = *ids_span.pop_front().unwrap(); + let amount = *amounts_span.pop_front().unwrap(); + self.update_balances(from, to, id, amount); + // assert( + // _check_on_erc1155_received(from, to, id, data), Errors::SAFE_TRANSFER_FAILED + // ); + }; + + self + .emit_event( + TransferBatch { operator: get_caller_address(), from, to, ids, values: amounts } + ); + } + + fn _mint(ref self: ContractState, to: ContractAddress, id: u256, amount: u256) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + + self.set_balance(to, id, self.get_balance(to, id).amount + amount); + + self + .emit_event( + TransferSingle { + operator: get_caller_address(), + from: Zeroable::zero(), + to, + id, + value: amount + } + ); + } + + fn _burn(ref self: ContractState, id: u256, amount: u256) { + let caller = get_caller_address(); + assert(self.get_balance(caller, id).amount >= amount, Errors::INSUFFICIENT_BALANCE); + + self.set_balance(caller, id, self.get_balance(caller, id).amount - amount); + + self + .emit_event( + TransferSingle { + operator: get_caller_address(), + from: caller, + to: Zeroable::zero(), + id, + value: amount + } + ); + } + + fn _safe_mint( + ref self: ContractState, + to: ContractAddress, + id: u256, + amount: u256, + data: Span + ) { + self._mint(to, id, amount); + // assert( + // _check_on_erc1155_received(Zeroable::zero(), to, id, data), + // Errors::SAFE_MINT_FAILED + // ); + } + } +//#[internal] +// fn _check_on_erc1155_received( +// from: ContractAddress, to: ContractAddress, token_id: 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, data +// ) == interface::IERC1155_RECEIVER_ID +// } else { +// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) +// } +// } + +} diff --git a/crates/dojo-erc/src/token/erc1155/interface.cairo b/crates/dojo-erc/src/token/erc1155/interface.cairo new file mode 100644 index 0000000000..ecce8fb978 --- /dev/null +++ b/crates/dojo-erc/src/token/erc1155/interface.cairo @@ -0,0 +1,62 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC1155 { + fn balance_of(self: @TState, account: ContractAddress, id: u256) -> u256; + fn balance_of_batch( + self: @TState, accounts: Array, ids: Array + ) -> Array; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn is_approved_for_all( + self: @TState, account: ContractAddress, operator: ContractAddress + ) -> bool; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ); + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ); +} + +#[starknet::interface] +trait IERC1155CamelOnly { + fn balanceOf(self: @TState, account: ContractAddress, id: u256) -> u256; + fn balanceOfBatch( + self: @TState, accounts: Array, ids: Array + ) -> Array; + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn isApprovedForAll(self: @TState, account: ContractAddress, operator: ContractAddress) -> bool; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ); + fn safeBatchTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ); +} + +#[starknet::interface] +trait IERC1155Metadata { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn uri(self: @TState, token_id: u256) -> felt252; +} diff --git a/crates/dojo-erc/src/token/erc1155/models.cairo b/crates/dojo-erc/src/token/erc1155/models.cairo new file mode 100644 index 0000000000..fa021aa987 --- /dev/null +++ b/crates/dojo-erc/src/token/erc1155/models.cairo @@ -0,0 +1,33 @@ +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct ERC1155Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC1155OperatorApproval { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + operator: ContractAddress, + approved: bool +} + + +#[derive(Model, Copy, Drop, Serde)] +struct ERC1155Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + #[key] + id: u256, + amount: u256 +} \ No newline at end of file