diff --git a/src/models/erc20/erc20.cairo b/src/models/erc20/erc20.cairo new file mode 100644 index 0000000..62a8ea8 --- /dev/null +++ b/src/models/erc20/erc20.cairo @@ -0,0 +1,266 @@ +#[starknet::contract] +mod ERC20 { + use origami_token::erc20::models::{ERC20Allowance, ERC20Balance, ERC20Meta}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use origami_token::erc20::interface; + use core::num::traits::Bounded; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use zeroable::Zeroable; + + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Copy, Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + + #[derive(Copy, Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256 + } + + mod Errors { + const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; + const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; + const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) { + self._world.write(world); + self.initializer(name, symbol); + self._mint(recipient, initial_supply); + } + + // + // External + // + + #[abi(embed_v0)] + impl ERC20MetadataImpl of interface::IERC20Metadata { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn decimals(self: @ContractState) -> u8 { + 18 + } + } + + #[abi(embed_v0)] + impl ERC20Impl of interface::IERC20 { + fn total_supply(self: @ContractState) -> u256 { + self.get_meta().total_supply + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.get_balance(account).amount + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.get_allowance(owner, spender).amount + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + let caller = get_caller_address(); + self._spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); + true + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let owner = get_caller_address(); + self + .set_allowance( + ERC20Allowance { token: get_contract_address(), owner, spender, amount } + ); + true + } + } + + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl of interface::IERC20CamelOnly { + fn totalSupply(self: @ContractState) -> u256 { + ERC20Impl::total_supply(self) + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC20Impl::balance_of(self, account) + } + + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20Impl::transfer_from(ref self, sender, recipient, amount) + } + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC20Meta { + get!(self.world(), get_contract_address(), ERC20Meta) + } + + // Helper function to update total_supply model + fn update_total_supply(ref self: ContractState, subtract: u256, add: u256) { + let mut meta = self.get_meta(); + // adding and subtracting is fewer steps than if + meta.total_supply = meta.total_supply - subtract; + meta.total_supply = meta.total_supply + add; + set!(self.world(), (meta)); + } + + // Helper function for balance model + fn get_balance(self: @ContractState, account: ContractAddress) -> ERC20Balance { + get!(self.world(), (get_contract_address(), account), ERC20Balance) + } + + fn update_balance( + ref self: ContractState, account: ContractAddress, subtract: u256, add: u256 + ) { + let mut balance: ERC20Balance = self.get_balance(account); + // adding and subtracting is fewer steps than if + balance.amount = balance.amount - subtract; + balance.amount = balance.amount + add; + set!(self.world(), (balance)); + } + + // Helper function for allowance model + fn get_allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress, + ) -> ERC20Allowance { + get!(self.world(), (get_contract_address(), owner, spender), ERC20Allowance) + } + + fn set_allowance(ref self: ContractState, allowance: ERC20Allowance) { + assert(!allowance.owner.is_zero(), Errors::APPROVE_FROM_ZERO); + assert(!allowance.spender.is_zero(), Errors::APPROVE_TO_ZERO); + set!(self.world(), (allowance)); + + let approval_event = Approval { + owner: allowance.owner, spender: allowance.spender, value: allowance.amount + }; + + self.emit(approval_event.clone()); + emit!(self.world(), (Event::Approval(approval_event))); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { + let meta = ERC20Meta { token: get_contract_address(), name, symbol, total_supply: 0 }; + set!(self.world(), (meta)); + } + + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); + self.update_total_supply(0, amount); + self.update_balance(recipient, 0, amount); + + let transfer_event = Transfer { from: Zeroable::zero(), to: recipient, value: amount }; + + self.emit(transfer_event.clone()); + emit!(self.world(), (Event::Transfer(transfer_event))); + } + + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), Errors::BURN_FROM_ZERO); + self.update_total_supply(amount, 0); + self.update_balance(account, amount, 0); + + let transfer_event = Transfer { from: account, to: Zeroable::zero(), value: amount }; + + self.emit(transfer_event.clone()); + emit!(self.world(), (Event::Transfer(transfer_event))); + } + + fn _approve( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + self + .set_allowance( + ERC20Allowance { token: get_contract_address(), owner, spender, amount } + ); + } + + fn _transfer( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); + assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + self.update_balance(sender, amount, 0); + self.update_balance(recipient, 0, amount); + + let transfer_event = Transfer { from: sender, to: recipient, value: amount }; + + self.emit(transfer_event.clone()); + emit!(self.world(), (Event::Transfer(transfer_event))); + } + + fn _spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let mut allowance = self.get_allowance(owner, spender); + allowance.amount = allowance.amount - amount; + self.set_allowance(allowance); + } + } +} diff --git a/src/models/erc20/interface.cairo b/src/models/erc20/interface.cairo new file mode 100644 index 0000000..577ce7d --- /dev/null +++ b/src/models/erc20/interface.cairo @@ -0,0 +1,66 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC20 { + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IERC20Metadata { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; +} + +#[starknet::interface] +trait IERC20Camel { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IERC20CamelOnly { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} + +#[starknet::interface] +trait ERC20ABI { + // IERC20 + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + + // IERC20CamelOnly + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} diff --git a/src/models/erc20/models.cairo b/src/models/erc20/models.cairo new file mode 100644 index 0000000..3f6ec16 --- /dev/null +++ b/src/models/erc20/models.cairo @@ -0,0 +1,35 @@ +// Starknet imports + +use starknet::ContractAddress; + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC20Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC20Allowance { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + amount: u256, +} + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC20Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + total_supply: u256, +} diff --git a/src/models/erc20/tests.cairo b/src/models/erc20/tests.cairo new file mode 100644 index 0000000..d0cb4ea --- /dev/null +++ b/src/models/erc20/tests.cairo @@ -0,0 +1,361 @@ +use core::num::traits::Bounded; +use integer::u256; +use integer::u256_from_felt252; +use origami_token::tests::utils; +use origami_token::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE +}; +use origami_token::erc20::ERC20::Approval; +use origami_token::erc20::ERC20::ERC20Impl; +use origami_token::erc20::ERC20::ERC20MetadataImpl; +use origami_token::erc20::ERC20::InternalImpl; +use origami_token::erc20::ERC20::Transfer; +use origami_token::erc20::ERC20; +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 origami_token::erc20::models::{ + ERC20Allowance, erc_20_allowance, ERC20Balance, erc_20_balance, ERC20Meta, erc_20_meta +}; +use starknet::storage::{StorageMemberAccessTrait}; + +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC20::ContractState) { + let world = spawn_test_world( + [].span(), + [ + erc_20_allowance::TEST_CLASS_HASH, + erc_20_balance::TEST_CLASS_HASH, + erc_20_meta::TEST_CLASS_HASH, + ] + ); + let mut state = ERC20::contract_state_for_testing(); + state._world.write(world.contract_address); + (world, state) +} + +fn setup() -> ERC20::ContractState { + let (world, mut state) = STATE(); + ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); + utils::drop_event(ZERO()); + state +} + +// +// initializer & constructor +// + +#[test] +fn test_initializer() { + let (_world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL); + + assert(ERC20MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC20MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC20MetadataImpl::decimals(@state) == DECIMALS, 'Decimals should be 18'); + assert(ERC20Impl::total_supply(@state) == 0, 'Supply should eq 0'); +} + + +#[test] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); + + assert_only_event_transfer(ZERO(), OWNER(), SUPPLY); + + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq inital_supply'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq inital_supply'); + assert(ERC20MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC20MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC20MetadataImpl::decimals(@state) == DECIMALS, 'Decimals should be 18'); +} + +// +// Getters +// + +#[test] +fn test_total_supply() { + let (_world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), SUPPLY); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq SUPPLY'); +} + +#[test] +fn test_balance_of() { + let (_world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), SUPPLY); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq SUPPLY'); +} + + +#[test] +fn test_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Should eq VALUE'); +} + +// +// approve & _approve +// + +#[test] +fn test_approve() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + assert(ERC20Impl::approve(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' + ); +} + +#[test] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_approve_from_zero() { + let mut state = setup(); + ERC20Impl::approve(ref state, SPENDER(), VALUE); +} + +#[test] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_approve_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, Zeroable::zero(), VALUE); +} + +#[test] +fn test__approve() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + InternalImpl::_approve(ref state, OWNER(), SPENDER(), VALUE); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' + ); +} + +#[test] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test__approve_from_zero() { + let mut state = setup(); + InternalImpl::_approve(ref state, Zeroable::zero(), SPENDER(), VALUE); +} + +#[test] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test__approve_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + InternalImpl::_approve(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +// +// transfer & _transfer +// + +#[test] +fn test_transfer() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + assert(ERC20Impl::transfer(ref state, RECIPIENT(), VALUE), 'Should return true'); + + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq VALUE'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - VALUE'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +fn test__transfer() { + let mut state = setup(); + + InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), VALUE); + + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test__transfer_not_enough_balance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + + let balance_plus_one = SUPPLY + 1; + InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), balance_plus_one); +} + +#[test] +#[should_panic(expected: ('ERC20: transfer from 0',))] +fn test__transfer_from_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, Zeroable::zero(), RECIPIENT(), VALUE); +} + +#[test] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test__transfer_to_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +// +// transfer_from +// + +#[test] +fn test_transfer_from() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + testing::set_caller_address(SPENDER()); + assert(ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE), 'Should return true'); + + assert_event_approval(OWNER(), SPENDER(), 0); + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Should eq amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq suppy - amount'); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == 0, 'Should eq 0'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_from_greater_than_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + testing::set_caller_address(SPENDER()); + let allowance_plus_one = VALUE + 1; + ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), allowance_plus_one); +} + +#[test] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test_transfer_from_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + testing::set_caller_address(SPENDER()); + ERC20Impl::transfer_from(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +#[test] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_from_from_zero_address() { + let mut state = setup(); + ERC20Impl::transfer_from(ref state, Zeroable::zero(), RECIPIENT(), VALUE); +} + +// +// _spend_allowance +// + +#[test] +fn test__spend_allowance_not_unlimited() { + let mut state = setup(); + + InternalImpl::_approve(ref state, OWNER(), SPENDER(), SUPPLY); + utils::drop_event(ZERO()); + + InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), VALUE); + + assert_only_event_approval(OWNER(), SPENDER(), SUPPLY - VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == SUPPLY - VALUE, + 'Should eq supply - amount' + ); +} + +// +// _mint +// + +#[test] +fn test__mint() { + let (_world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), VALUE); + assert_only_event_transfer(ZERO(), OWNER(), VALUE); + assert(ERC20Impl::balance_of(@state, OWNER()) == VALUE, 'Should eq amount'); + assert(ERC20Impl::total_supply(@state) == VALUE, 'Should eq total supply'); +} + +#[test] +#[should_panic(expected: ('ERC20: mint to 0',))] +fn test__mint_to_zero() { + let (_world, mut state) = STATE(); + InternalImpl::_mint(ref state, Zeroable::zero(), VALUE); +} + +// +// _burn +// + +#[test] +fn test__burn() { + let mut state = setup(); + InternalImpl::_burn(ref state, OWNER(), VALUE); + + assert_only_event_transfer(OWNER(), ZERO(), VALUE); + assert(ERC20Impl::total_supply(@state) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); +} + +#[test] +#[should_panic(expected: ('ERC20: burn from 0',))] +fn test__burn_from_zero() { + let mut state = setup(); + InternalImpl::_burn(ref state, Zeroable::zero(), VALUE); +} + +// +// Helpers +// + +fn assert_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.spender == spender, 'Invalid `spender`'); + assert(event.value == value, 'Invalid `value`'); +} + +fn assert_only_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { + assert_event_approval(owner, spender, value); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.value == value, 'Invalid `value`'); +} + +fn assert_only_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { + assert_event_transfer(from, to, value); + utils::assert_no_events_left(ZERO()); +} diff --git a/src/models/erc721/erc721.cairo b/src/models/erc721/erc721.cairo new file mode 100644 index 0000000..6a1bc7a --- /dev/null +++ b/src/models/erc721/erc721.cairo @@ -0,0 +1,414 @@ +#[starknet::contract] +mod ERC721 { + use origami_token::erc721::models::{ + ERC721Meta, ERC721OperatorApproval, ERC721Owner, ERC721Balance, ERC721TokenApproval + }; + use origami_token::erc721::interface; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use core::num::traits::Bounded; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use zeroable::Zeroable; + + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Copy, Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + ApprovalForAll: ApprovalForAll + } + + #[derive(Copy, Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + approved: ContractAddress, + token_id: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + mod Errors { + const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; + const INVALID_ACCOUNT: felt252 = 'ERC721: invalid account'; + const UNAUTHORIZED: felt252 = 'ERC721: unauthorized caller'; + const APPROVAL_TO_OWNER: felt252 = 'ERC721: approval to owner'; + const SELF_APPROVAL: felt252 = 'ERC721: self approval'; + const INVALID_RECEIVER: felt252 = 'ERC721: invalid receiver'; + const ALREADY_MINTED: felt252 = 'ERC721: token already minted'; + const WRONG_SENDER: felt252 = 'ERC721: wrong sender'; + const SAFE_MINT_FAILED: felt252 = 'ERC721: safe mint failed'; + const SAFE_TRANSFER_FAILED: felt252 = 'ERC721: safe transfer failed'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, + recipient: ContractAddress, + token_id: u256 + ) { + self._world.write(world); + self.initializer(name, symbol, base_uri); + self._mint(recipient, token_id); + } + + #[abi(embed_v0)] + impl ERC721MetadataImpl of interface::IERC721Metadata { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn token_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) + } + } + + #[abi(embed_v0)] + impl ERC721MetadataCamelOnlyImpl of interface::IERC721MetadataCamelOnly { + fn tokenURI(self: @ContractState, tokenId: u256) -> felt252 { + assert(self._exists(tokenId), Errors::INVALID_TOKEN_ID); + self.get_uri(tokenId) + } + } + + #[abi(embed_v0)] + impl ERC721Impl of interface::IERC721 { + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); + self.get_balance(account).amount + } + + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + self._owner_of(token_id) + } + + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + self.get_token_approval(token_id).address + } + + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + self.get_operator_approval(owner, operator).approved + } + + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self._owner_of(token_id); + + let caller = get_caller_address(); + assert( + owner == caller || Self::is_approved_for_all(@self, owner, caller), + Errors::UNAUTHORIZED + ); + self._approve(to, token_id); + } + + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + self._set_approval_for_all(get_caller_address(), operator, approved) + } + + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + ); + self._transfer(from, to, token_id); + } + + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + ); + self._safe_transfer(from, to, token_id, data); + } + } + + #[abi(embed_v0)] + impl ERC721CamelOnlyImpl of interface::IERC721CamelOnly { + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC721Impl::balance_of(self, account) + } + + fn ownerOf(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721Impl::owner_of(self, tokenId) + } + + fn getApproved(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721Impl::get_approved(self, tokenId) + } + + fn isApprovedForAll( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + ERC721Impl::is_approved_for_all(self, owner, operator) + } + + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + ERC721Impl::set_approval_for_all(ref self, operator, approved) + } + + fn transferFrom( + ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256 + ) { + ERC721Impl::transfer_from(ref self, from, to, tokenId) + } + + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ) { + ERC721Impl::safe_transfer_from(ref self, from, to, tokenId, data) + } + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC721Meta { + get!(self.world(), get_contract_address(), ERC721Meta) + } + + 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) -> ERC721Balance { + get!(self.world(), (get_contract_address(), account), ERC721Balance) + } + + fn get_owner_of(self: @ContractState, token_id: u256) -> ERC721Owner { + get!( + self.world(), + (get_contract_address(), TryInto::::try_into(token_id).unwrap()), + ERC721Owner + ) + } + + fn get_token_approval(self: @ContractState, token_id: u256) -> ERC721TokenApproval { + get!( + self.world(), + (get_contract_address(), TryInto::::try_into(token_id).unwrap()), + ERC721TokenApproval + ) + } + + fn get_operator_approval( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> ERC721OperatorApproval { + get!(self.world(), (get_contract_address(), owner, operator), ERC721OperatorApproval) + } + + fn set_token_approval( + ref self: ContractState, + owner: ContractAddress, + to: ContractAddress, + token_id: u256, + emit: bool + ) { + set!( + self.world(), + ERC721TokenApproval { + token: get_contract_address(), + token_id: token_id.try_into().unwrap(), + address: to, + } + ); + if emit { + let approval_event = Approval { owner, approved: to, token_id: token_id }; + + self.emit(approval_event.clone()); + emit!(self.world(), (Event::Approval(approval_event))); + } + } + + fn set_operator_approval( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + set!( + self.world(), + ERC721OperatorApproval { token: get_contract_address(), owner, operator, approved } + ); + + let approval_for_all_event = ApprovalForAll { owner, operator, approved }; + + self.emit(approval_for_all_event.clone()); + emit!(self.world(), (Event::ApprovalForAll(approval_for_all_event))); + } + + fn set_balance(ref self: ContractState, account: ContractAddress, amount: u256) { + set!(self.world(), ERC721Balance { token: get_contract_address(), account, amount }); + } + + fn set_owner(ref self: ContractState, token_id: u256, address: ContractAddress) { + set!( + self.world(), + ERC721Owner { + token: get_contract_address(), token_id: token_id.try_into().unwrap(), address + } + ); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { + let meta = ERC721Meta { token: get_contract_address(), name, symbol, base_uri }; + set!(self.world(), (meta)); + } + + fn _owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + let owner = self.get_owner_of(token_id).address; + match owner.is_zero() { + bool::False(()) => owner, + bool::True(()) => panic_with_felt252(Errors::INVALID_TOKEN_ID) + } + } + + fn _exists(self: @ContractState, token_id: u256) -> bool { + let owner = self.get_owner_of(token_id).address; + owner.is_non_zero() + } + + fn _is_approved_or_owner( + self: @ContractState, spender: ContractAddress, token_id: u256 + ) -> bool { + let owner = self._owner_of(token_id); + let is_approved_for_all = ERC721Impl::is_approved_for_all(self, owner, spender); + owner == spender + || is_approved_for_all + || spender == ERC721Impl::get_approved(self, token_id) + } + + fn _approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self._owner_of(token_id); + assert(owner != to, Errors::APPROVAL_TO_OWNER); + + self.set_token_approval(owner, to, token_id, true); + } + + 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 _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + assert(!self._exists(token_id), Errors::ALREADY_MINTED); + + self.set_balance(to, self.get_balance(to).amount + 1); + self.set_owner(token_id, to); + + let transfer_event = Transfer { from: Zeroable::zero(), to, token_id }; + + self.emit(transfer_event.clone()); + emit!(self.world(), (Event::Transfer(transfer_event))); + } + + fn _transfer( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + let owner = self._owner_of(token_id); + assert(from == owner, Errors::WRONG_SENDER); + + // Implicit clear approvals, no need to emit an event + self.set_token_approval(owner, Zeroable::zero(), token_id, false); + + self.set_balance(from, self.get_balance(from).amount - 1); + self.set_balance(to, self.get_balance(to).amount + 1); + self.set_owner(token_id, to); + + let transfer_event = Transfer { from, to, token_id }; + + self.emit(transfer_event.clone()); + emit!(self.world(), (Event::Transfer(transfer_event))); + } + + fn _burn(ref self: ContractState, token_id: u256) { + let owner = self._owner_of(token_id); + + // Implicit clear approvals, no need to emit an event + self.set_token_approval(owner, Zeroable::zero(), token_id, false); + + self.set_balance(owner, self.get_balance(owner).amount - 1); + self.set_owner(token_id, Zeroable::zero()); + + let transfer_event = Transfer { from: owner, to: Zeroable::zero(), token_id }; + + self.emit(transfer_event.clone()); + emit!(self.world(), (Event::Transfer(transfer_event))); + } + + fn _safe_mint( + ref self: ContractState, to: ContractAddress, token_id: u256, data: Span + ) { + self._mint(to, token_id); + } + + fn _safe_transfer( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + self._transfer(from, to, token_id); + } + } +} diff --git a/src/models/erc721/interface.cairo b/src/models/erc721/interface.cairo new file mode 100644 index 0000000..d257cc9 --- /dev/null +++ b/src/models/erc721/interface.cairo @@ -0,0 +1,130 @@ +use starknet::ContractAddress; + +const IERC721_ID: felt252 = 0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943; +const IERC721_METADATA_ID: felt252 = + 0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd; +const IERC721_RECEIVER_ID: felt252 = + 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; + +#[starknet::interface] +trait IERC721 { + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn owner_of(self: @TState, token_id: u256) -> ContractAddress; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn approve(ref self: TState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn get_approved(self: @TState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; +} + +#[starknet::interface] +trait IERC721Metadata { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn token_uri(self: @TState, token_id: u256) -> felt252; +} + +#[starknet::interface] +trait IERC721CamelOnly { + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; +} + +#[starknet::interface] +trait IERC721MetadataCamelOnly { + fn tokenURI(self: @TState, tokenId: u256) -> felt252; +} + +// +// ERC721 ABI +// + +#[starknet::interface] +trait ERC721ABI { + // IERC721 + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn owner_of(self: @TState, token_id: u256) -> ContractAddress; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn approve(ref self: TState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn get_approved(self: @TState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + + // IERC721Metadata + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn token_uri(self: @TState, token_id: u256) -> felt252; + + // IERC721CamelOnly + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721MetadataCamelOnly + fn tokenURI(self: @TState, tokenId: u256) -> felt252; +} + +// +// ERC721Receiver +// + +#[starknet::interface] +trait IERC721Receiver { + fn on_erc721_received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252; +} + +#[starknet::interface] +trait IERC721ReceiverCamel { + fn onERC721Received( + self: @TState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span + ) -> felt252; +} diff --git a/src/models/erc721/models.cairo b/src/models/erc721/models.cairo new file mode 100644 index 0000000..aa44c64 --- /dev/null +++ b/src/models/erc721/models.cairo @@ -0,0 +1,55 @@ +// Starknet imports + +use starknet::ContractAddress; + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC721Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, +} + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC721OperatorApproval { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + operator: ContractAddress, + approved: bool +} + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC721Owner { + #[key] + token: ContractAddress, + #[key] + token_id: felt252, + address: ContractAddress +} + +#[dojo::model] +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +#[dojo::model] +#[derive(Copy, Drop, Serde)] +struct ERC721TokenApproval { + #[key] + token: ContractAddress, + #[key] + token_id: felt252, + address: ContractAddress, +} diff --git a/src/models/erc721/tests.cairo b/src/models/erc721/tests.cairo new file mode 100644 index 0000000..7d7d960 --- /dev/null +++ b/src/models/erc721/tests.cairo @@ -0,0 +1,1378 @@ +// Core imports + +use integer::u256; +use integer::u256_from_felt252; +use zeroable::Zeroable; +use debug::PrintTrait; + +// Starknet imports + +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; + +// Dojo imports + +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +// External imports + +// Internal imports + +use origami_token::tests::utils; +use origami_token::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID +}; +use origami_token::erc721::ERC721::ERC721Impl; +use origami_token::erc721::ERC721::ERC721CamelOnlyImpl; +use origami_token::erc721::ERC721::ERC721MetadataImpl; +use origami_token::erc721::ERC721::InternalImpl; +use origami_token::erc721::ERC721::WorldInteractionsImpl; +use origami_token::erc721::ERC721::{Approval, ApprovalForAll, Transfer}; +use origami_token::erc721::ERC721; +use origami_token::erc721::models::{ + ERC721Meta, erc_721_meta, ERC721OperatorApproval, erc_721_operator_approval, ERC721Owner, + erc_721_owner, ERC721Balance, erc_721_balance, ERC721TokenApproval, erc_721_token_approval +}; +use starknet::storage::{StorageMemberAccessTrait}; + + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC721::ContractState) { + let world = spawn_test_world( + [].span(), + [ + erc_721_meta::TEST_CLASS_HASH, + erc_721_operator_approval::TEST_CLASS_HASH, + erc_721_owner::TEST_CLASS_HASH, + erc_721_balance::TEST_CLASS_HASH, + erc_721_token_approval::TEST_CLASS_HASH, + ].span() + ); + let mut state = ERC721::contract_state_for_testing(); + state._world.write(world.contract_address); + (world, state) +} + +fn setup() -> ERC721::ContractState { + let (world, mut state) = STATE(); + ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); + utils::drop_event(ZERO()); + state +} + +// fn setup_receiver() -> ContractAddress { +// utils::deploy(ERC721Receiver::TEST_CLASS_HASH, array![]) +// } + +// fn setup_camel_receiver() -> ContractAddress { +// utils::deploy(CamelERC721ReceiverMock::TEST_CLASS_HASH, array![]) +// } + +// fn setup_account() -> ContractAddress { +// let mut calldata = array![PUBKEY]; +// utils::deploy(Account::TEST_CLASS_HASH, calldata) +// } + +// fn setup_camel_account() -> ContractAddress { +// let mut calldata = array![PUBKEY]; +// utils::deploy(CamelAccountMock::TEST_CLASS_HASH, calldata) +// } + +// +// initializer & constructor +// + +#[test] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); + + assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance should be one'); + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'OWNER should be owner'); + // assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface +// ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + +#[test] +fn test_initializer() { + let (_world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL, URI); + + assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + + assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance should be zero'); + // assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface +// ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + + +// +// Getters +// + +#[test] +fn test_balance_of() { + let state = setup(); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Should return balance'); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid account',))] +fn test_balance_of_zero() { + let state = setup(); + ERC721Impl::balance_of(@state, ZERO()); +} + +#[test] +fn test_owner_of() { + let state = setup(); + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Should return owner'); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_owner_of_non_minted() { + let state = setup(); + ERC721Impl::owner_of(@state, u256_from_felt252(7)); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_token_uri_non_minted() { + let state = setup(); + ERC721MetadataImpl::token_uri(@state, u256_from_felt252(7)); +} + +#[test] +fn test_get_approved() { + let mut state = setup(); + let spender = SPENDER(); + let token_id = TOKEN_ID; + + assert(ERC721Impl::get_approved(@state, token_id) == ZERO(), 'Should return non-approval'); + InternalImpl::_approve(ref state, spender, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == spender, 'Should return approval'); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_get_approved_nonexistent() { + let mut state = setup(); + ERC721Impl::get_approved(@state, u256_from_felt252(7)); +} + +#[test] +fn test__exists() { + let (_world, mut state) = STATE(); + let token_id = TOKEN_ID; + + assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' + ); + + InternalImpl::_mint(ref state, RECIPIENT(), token_id); + + assert(InternalImpl::_exists(@state, token_id), 'Token should exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == RECIPIENT(), + 'Invalid owner' + ); + + InternalImpl::_burn(ref state, token_id); + + assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' + ); +} + + +// +// approve & _approve +// + +#[test] +fn test_approve_from_owner() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +fn test_approve_from_operator() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_approve_from_unauthorized() { + let mut state = setup(); + + testing::set_caller_address(OTHER()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: approval to owner',))] +fn test_approve_to_owner() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::approve(ref state, OWNER(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_approve_nonexistent() { + // let mut state = STATE(); + let (_world, mut state) = STATE(); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); +} + +#[test] +fn test__approve() { + let mut state = setup(); + InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[should_panic(expected: ('ERC721: approval to owner',))] +fn test__approve_to_owner() { + let mut state = setup(); + InternalImpl::_approve(ref state, OWNER(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__approve_nonexistent() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); +} + +// +// set_approval_for_all & _set_approval_for_all +// + +#[test] +fn test_set_approval_for_all() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + + assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Approval not revoked correctly' + ); +} + +#[test] +#[should_panic(expected: ('ERC721: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_true() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OWNER(), true); +} + +#[test] +#[should_panic(expected: ('ERC721: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_false() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OWNER(), false); +} + +#[test] +fn test__set_approval_for_all() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + assert(!ERC721Impl::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( + ERC721Impl::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( + !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); +} + +#[test] +#[should_panic(expected: ('ERC721: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_true() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); +} + +#[test] +#[should_panic(expected: ('ERC721: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_false() { + // let mut state = STATE(); + let (_world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); +} + + +// +// transfer_from & transferFrom +// + +#[test] +fn test_transfer_from_owner() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + // set approval to check reset + InternalImpl::_approve(ref state, OTHER(), token_id); + utils::drop_event(ZERO()); + + assert_state_before_transfer(@state, owner, recipient, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); + + testing::set_caller_address(owner); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +fn test_transferFrom_owner() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + // set approval to check reset + InternalImpl::_approve(ref state, OTHER(), token_id); + utils::drop_event(ZERO()); + + assert_state_before_transfer(@state, owner, recipient, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); + + testing::set_caller_address(owner); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_transfer_from_nonexistent() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + ERC721Impl::transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_transferFrom_nonexistent() { + //let mut state = STATE(); + let (_world, mut state) = STATE(); + ERC721CamelOnlyImpl::transferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test_transfer_from_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC721Impl::transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test_transferFrom_to_zero() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +fn test_transfer_from_to_owner() { + let mut state = setup(); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + + testing::set_caller_address(OWNER()); + ERC721Impl::transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID); + assert_event_transfer(OWNER(), OWNER(), TOKEN_ID); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); +} + +#[test] +fn test_transferFrom_to_owner() { + let mut state = setup(); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + + testing::set_caller_address(OWNER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), OWNER(), TOKEN_ID); + assert_event_transfer(OWNER(), OWNER(), TOKEN_ID); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); +} + +#[test] +fn test_transfer_from_approved() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::approve(ref state, OPERATOR(), token_id); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +fn test_transferFrom_approved() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::approve(ref state, OPERATOR(), token_id); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +fn test_transfer_from_approved_for_all() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +fn test_transferFrom_approved_for_all() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_transfer_from_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC721Impl::transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_transferFrom_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID); +} + +// // +// // safe_transfer_from & safeTransferFrom +// // + +// #[test] +// fn test_safe_transfer_from_to_account() { +// let mut state = setup(); +// let account = setup_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_to_account() { +// let mut state = setup(); +// let account = setup_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// fn test_safe_transfer_from_to_account_camel() { +// let mut state = setup(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_to_account_camel() { +// let mut state = setup(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// fn test_safe_transfer_from_to_receiver() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_to_receiver() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safe_transfer_from_to_receiver_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_to_receiver_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safe_transfer_from_to_receiver_failure() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safeTransferFrom_to_receiver_failure() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safe_transfer_from_to_receiver_failure_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safeTransferFrom_to_receiver_failure_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test_safe_transfer_from_to_non_receiver() { +// let mut state = setup(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, recipient, token_id, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test_safeTransferFrom_to_non_receiver() { +// let mut state = setup(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, token_id, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test_safe_transfer_from_nonexistent() { +// let mut state = STATE(); +// ERC721Impl::safe_transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test_safeTransferFrom_nonexistent() { +// let mut state = STATE(); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test_safe_transfer_from_to_zero() { +// let mut state = setup(); +// testing::set_caller_address(OWNER()); +// ERC721Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test_safeTransferFrom_to_zero() { +// let mut state = setup(); +// testing::set_caller_address(OWNER()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// fn test_safe_transfer_from_to_owner() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// fn test_safeTransferFrom_to_owner() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// fn test_safe_transfer_from_to_owner_camel() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_camel_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// fn test_safeTransferFrom_to_owner_camel() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_camel_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// fn test_safe_transfer_from_approved() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_approved() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safe_transfer_from_approved_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_approved_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safe_transfer_from_approved_for_all() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_approved_for_all() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safe_transfer_from_approved_for_all_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// fn test_safeTransferFrom_approved_for_all_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: unauthorized caller',))] +// fn test_safe_transfer_from_unauthorized() { +// let mut state = setup(); +// testing::set_caller_address(OTHER()); +// ERC721Impl::safe_transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: unauthorized caller',))] +// fn test_safeTransferFrom_unauthorized() { +// let mut state = setup(); +// testing::set_caller_address(OTHER()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// +// _transfer +// + +#[test] +fn test__transfer() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + InternalImpl::_transfer(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__transfer_nonexistent() { + let (_world, mut state) = STATE(); + InternalImpl::_transfer(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test__transfer_to_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: wrong sender',))] +fn test__transfer_from_invalid_owner() { + let mut state = setup(); + InternalImpl::_transfer(ref state, RECIPIENT(), OWNER(), TOKEN_ID); +} + +// +// _mint +// + +#[test] +fn test__mint() { + let (_world, mut state) = STATE(); + let recipient = RECIPIENT(); + let token_id = TOKEN_ID; + + assert_state_before_mint(@state, recipient); + InternalImpl::_mint(ref state, recipient, TOKEN_ID); + assert_event_transfer(ZERO(), recipient, token_id); + + assert_state_after_mint(@state, recipient, token_id); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test__mint_to_zero() { + let (_world, mut state) = STATE(); + InternalImpl::_mint(ref state, ZERO(), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ERC721: token already minted',))] +fn test__mint_already_exist() { + let mut state = setup(); + InternalImpl::_mint(ref state, RECIPIENT(), TOKEN_ID); +} + +// // +// // _safe_mint +// // + +// #[test] +// fn test__safe_mint_to_receiver() { +// let mut state = STATE(); +// let recipient = setup_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_event_transfer(ZERO(), recipient, token_id); + +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// fn test__safe_mint_to_receiver_camel() { +// let mut state = STATE(); +// let recipient = setup_camel_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_event_transfer(ZERO(), recipient, token_id); + +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// fn test__safe_mint_to_account() { +// let mut state = STATE(); +// let account = setup_account(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,account); +// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); +// assert_event_transfer(ZERO(), account, token_id); + +// assert_state_after_mint(@state,account, token_id); +// } + +// #[test] +// fn test__safe_mint_to_account_camel() { +// let mut state = STATE(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,account); +// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); +// assert_event_transfer(ZERO(), account, token_id); + +// assert_state_after_mint(@state,account, token_id); +// } + +// #[test] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test__safe_mint_to_non_receiver() { +// let mut state = STATE(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: safe mint failed',))] +// fn test__safe_mint_to_receiver_failure() { +// let mut state = STATE(); +// let recipient = setup_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: safe mint failed',))] +// fn test__safe_mint_to_receiver_failure_camel() { +// let mut state = STATE(); +// let recipient = setup_camel_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test__safe_mint_to_zero() { +// let mut state = STATE(); +// InternalImpl::_safe_mint(ref state, ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: token already minted',))] +// fn test__safe_mint_already_exist() { +// let mut state = setup(); +// InternalImpl::_safe_mint(ref state, RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// +// _burn +// + +#[test] +fn test__burn() { + let mut state = setup(); + + InternalImpl::_approve(ref state, OTHER(), TOKEN_ID); + utils::drop_event(ZERO()); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + assert(ERC721Impl::get_approved(@state, TOKEN_ID) == OTHER(), 'Approval before'); + + InternalImpl::_burn(ref state, TOKEN_ID); + assert_event_transfer(OWNER(), ZERO(), TOKEN_ID); + + assert( + WorldInteractionsImpl::get_owner_of(@state, TOKEN_ID).address == ZERO(), 'Ownership after' + ); + assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance of owner after'); + assert( + WorldInteractionsImpl::get_token_approval(@state, TOKEN_ID).address == ZERO(), + 'Approval after' + ); +} + +#[test] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__burn_nonexistent() { + let (mut _world, mut state) = STATE(); + InternalImpl::_burn(ref state, TOKEN_ID); +} + +// +// _set_token_uri +// + +// #[test] +// fn test__set_token_uri() { +// let mut state = setup(); + +// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == 0, 'URI should be 0'); +// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); +// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == URI, 'URI should be set'); +// } + +// #[test] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test__set_token_uri_nonexistent() { +// let mut state = STATE(); +// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); +// } + +// +// Helpers +// + +fn assert_state_before_transfer( + state: @ERC721::ContractState, + owner: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == owner, 'Ownership before'); + assert(ERC721Impl::balance_of(state, owner) == 1, 'Balance of owner before'); + assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_transfer( + state: @ERC721::ContractState, + owner: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); + assert(ERC721Impl::balance_of(state, owner) == 0, 'Balance of owner after'); + assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); + assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval not implicitly reset'); +} + +fn assert_state_before_mint(state: @ERC721::ContractState, recipient: ContractAddress) { + assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_mint( + state: @ERC721::ContractState, recipient: ContractAddress, token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); + assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); + assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval implicitly set'); +} + +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_approval(owner: ContractAddress, approved: ContractAddress, token_id: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.approved == approved, 'Invalid `approved`'); + assert(event.token_id == token_id, 'Invalid `token_id`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer(from: ContractAddress, to: ContractAddress, token_id: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.token_id == token_id, 'Invalid `token_id`'); + utils::assert_no_events_left(ZERO()); +}