From 3574673cedee13cf73178835142569d9a718fb10 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 14 May 2022 01:51:59 +0800 Subject: [PATCH] feat: erc1155 example w/ ownable/pausable (#2807) * Created ERC1155 ownable example/template * Applied most of proposed changes * Applied more of the feedback. * ERC165 fix * Applied @view/@pure, updated test to accomodate * Moved tests into the correct folder * added interface ERC1155.py * refactor: clean up built-in interface * rewrote tests to use eth-tester * cleaned up brownie tests in favor of eth-tester * Update examples/tokens/ERC1155ownable.vy Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> * applied proposed changes in contract and tests * interface fix / callback bytes constant * Ownership test assertion to ZERO_ADDRESS * reworked uri part, simplified tests * Fixed all testscript issues * refactor: apply suggestions from code review * Update examples/tokens/ERC1155ownable.vy Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> * Update examples/tokens/ERC1155ownable.vy Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> * applied codebase shrink tips, adjusted tests * cleaning up commented out code * refactor: flip orientation of `balanceOf` * test: update tests for switch of args to `balanceOf` * pull, isort, black, push * Resolved linter issues * Linter fixes for ERC1155 test script * Fixed ownership check in safeTransferFrom * Fixed setURI permissions, updated to docstrings * Minor test update Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> --- examples/tokens/ERC1155ownable.vy | 367 ++++++++++++++++++++++++++ tests/examples/tokens/test_erc1155.py | 364 +++++++++++++++++++++++++ 2 files changed, 731 insertions(+) create mode 100644 examples/tokens/ERC1155ownable.vy create mode 100644 tests/examples/tokens/test_erc1155.py diff --git a/examples/tokens/ERC1155ownable.vy b/examples/tokens/ERC1155ownable.vy new file mode 100644 index 0000000000..f8e46176d6 --- /dev/null +++ b/examples/tokens/ERC1155ownable.vy @@ -0,0 +1,367 @@ +# @version >=0.3.3 +""" +@dev Implementation of ERC-1155 non-fungible token standard ownable, with approval, OPENSEA compatible (name, symbol) +@author Dr. Pixel (github: @Doc-Pixel) +""" +############### imports ############### +from vyper.interfaces import ERC165 + +############### variables ############### +# maximum items in a batch call. Set to 128, to be determined what the practical limits are. +BATCH_SIZE: constant(uint256) = 128 + +# callback number of bytes +CALLBACK_NUMBYTES: constant(uint256) = 4096 + +# URI length set to 1024. +MAX_URI_LENGTH: constant(uint256) = 1024 + +# the contract owner +# not part of the core spec but a common feature for NFT projects +owner: public(address) + +# pause status True / False +# not part of the core spec but a common feature for NFT projects +paused: public(bool) + +# the contracts URI to find the metadata +_uri: String[MAX_URI_LENGTH] + +# NFT marketplace compatibility +name: public(String[128]) +symbol: public(String[16]) + +# Interface IDs +ERC165_INTERFACE_ID: constant(bytes4) = 0x01ffc9a7 +ERC1155_INTERFACE_ID: constant(bytes4) = 0xd9b67a26 +ERC1155_INTERFACE_ID_METADATA: constant(bytes4) = 0x0e89341c + +# mappings + +# Mapping from token ID to account balances +balanceOf: public(HashMap[address, HashMap[uint256, uint256]]) + +# Mapping from account to operator approvals +isApprovedForAll: public( HashMap[address, HashMap[address, bool]]) + +############### events ############### +event Paused: + # Emits a pause event with the address that paused the contract + account: address + +event unPaused: + # Emits an unpause event with the address that paused the contract + account: address + +event OwnershipTransferred: + # Emits smart contract ownership transfer from current to new owner + previouwOwner: address + newOwner: address + +event TransferSingle: + # Emits on transfer of a single token + operator: indexed(address) + fromAddress: indexed(address) + to: indexed(address) + id: uint256 + value: uint256 + +event TransferBatch: + # Emits on batch transfer of tokens. the ids array correspond with the values array by their position + operator: indexed(address) # indexed + fromAddress: indexed(address) + to: indexed(address) + ids: DynArray[uint256, BATCH_SIZE] + values: DynArray[uint256, BATCH_SIZE] + +event ApprovalForAll: + # This emits when an operator is enabled or disabled for an owner. The operator manages all tokens for an owner + account: indexed(address) + operator: indexed(address) + approved: bool + +event URI: + # This emits when the URI gets changed + value: String[MAX_URI_LENGTH] + id: uint256 + + +############### interfaces ############### +implements: ERC165 + +interface IERC1155Receiver: + def onERC1155Received( + operator: address, + sender: address, + id: uint256, + amount: uint256, + data: Bytes[CALLBACK_NUMBYTES], + ) -> bytes32: payable + def onERC1155BatchReceived( + operator: address, + sender: address, + ids: DynArray[uint256, BATCH_SIZE], + amounts: DynArray[uint256, BATCH_SIZE], + data: Bytes[CALLBACK_NUMBYTES], + ) -> bytes4: payable + +interface IERC1155MetadataURI: + def uri(id: uint256) -> String[MAX_URI_LENGTH]: view + +############### functions ############### + +@external +def __init__(name: String[128], symbol: String[16], uri: String[1024]): + """ + @dev contract initialization on deployment + @dev will set name and symbol, interfaces, owner and URI + @dev self.paused will default to false + @param name the smart contract name + @param symbol the smart contract symbol + @param uri the new uri for the contract + """ + self.name = name + self.symbol = symbol + self.owner = msg.sender + self._uri = uri + +## contract status ## +@external +def pause(): + """ + @dev Pause the contract, checks if the caller is the owner and if the contract is paused already + @dev emits a pause event + @dev not part of the core spec but a common feature for NFT projects + """ + assert self.owner == msg.sender, "Ownable: caller is not the owner" + assert not self.paused, "the contract is already paused" + self.paused = True + log Paused(msg.sender) + +@external +def unpause(): + """ + @dev Unpause the contract, checks if the caller is the owner and if the contract is paused already + @dev emits an unpause event + @dev not part of the core spec but a common feature for NFT projects + """ + assert self.owner == msg.sender, "Ownable: caller is not the owner" + assert self.paused, "the contract is not paused" + self.paused = False + log unPaused(msg.sender) + +## ownership ## +@external +def transferOwnership(newOwner: address): + """ + @dev Transfer the ownership. Checks for contract pause status, current owner and prevent transferring to + @dev zero address + @dev emits an OwnershipTransferred event with the old and new owner addresses + @param newOwner The address of the new owner. + """ + assert not self.paused, "The contract has been paused" + assert self.owner == msg.sender, "Ownable: caller is not the owner" + assert newOwner != self.owner, "This account already owns the contract" + assert newOwner != ZERO_ADDRESS, "Transfer to ZERO_ADDRESS not allowed. Use renounceOwnership() instead." + oldOwner: address = self.owner + self.owner = newOwner + log OwnershipTransferred(oldOwner, newOwner) + +@external +def renounceOwnership(): + """ + @dev Transfer the ownership to ZERO_ADDRESS, this will lock the contract + @dev emits an OwnershipTransferred event with the old and new ZERO_ADDRESS owner addresses + """ + assert not self.paused, "The contract has been paused" + assert self.owner == msg.sender, "Ownable: caller is not the owner" + oldOwner: address = self.owner + self.owner = ZERO_ADDRESS + log OwnershipTransferred(oldOwner, ZERO_ADDRESS) + +@external +@view +def balanceOfBatch(accounts: DynArray[address, BATCH_SIZE], ids: DynArray[uint256, BATCH_SIZE]) -> DynArray[uint256,BATCH_SIZE]: # uint256[BATCH_SIZE]: + """ + @dev check the balance for an array of specific IDs and addresses + @dev will return an array of balances + @dev Can also be used to check ownership of an ID + @param accounts a dynamic array of the addresses to check the balance for + @param ids a dynamic array of the token IDs to check the balance + """ + assert len(accounts) == len(ids), "ERC1155: accounts and ids length mismatch" + batchBalances: DynArray[uint256, BATCH_SIZE] = [] + j: uint256 = 0 + for i in ids: + batchBalances.append(self.balanceOf[accounts[j]][i]) + j += 1 + return batchBalances + +## mint ## +@external +def mint(receiver: address, id: uint256, amount:uint256, data:bytes32): + """ + @dev mint one new token with a certain ID + @dev this can be a new token or "topping up" the balance of a non-fungible token ID + @param receiver the account that will receive the minted token + @param id the ID of the token + @param amount of tokens for this ID + @param data the data associated with this mint. Usually stays empty + """ + assert not self.paused, "The contract has been paused" + assert self.owner == msg.sender, "Only the contract owner can mint" + assert receiver != ZERO_ADDRESS, "Can not mint to ZERO ADDRESS" + operator: address = msg.sender + self.balanceOf[receiver][id] += amount + log TransferSingle(operator, ZERO_ADDRESS, receiver, id, amount) + + +@external +def mintBatch(receiver: address, ids: DynArray[uint256, BATCH_SIZE], amounts: DynArray[uint256, BATCH_SIZE], data: bytes32): + """ + @dev mint a batch of new tokens with the passed IDs + @dev this can be new tokens or "topping up" the balance of existing non-fungible token IDs in the contract + @param receiver the account that will receive the minted token + @param ids array of ids for the tokens + @param amounts amounts of tokens for each ID in the ids array + @param data the data associated with this mint. Usually stays empty + """ + assert not self.paused, "The contract has been paused" + assert self.owner == msg.sender, "Only the contract owner can mint" + assert receiver != ZERO_ADDRESS, "Can not mint to ZERO ADDRESS" + assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch" + operator: address = msg.sender + + for i in range(BATCH_SIZE): + if i >= len(ids): + break + self.balanceOf[receiver][ids[i]] += amounts[i] + + log TransferBatch(operator, ZERO_ADDRESS, receiver, ids, amounts) + +## burn ## +@external +def burn(id: uint256, amount: uint256): + """ + @dev burn one or more token with a certain ID + @dev the amount of tokens will be deducted from the holder's balance + @param receiver the account that will receive the minted token + @param id the ID of the token to burn + @param amount of tokens to burnfor this ID + """ + assert not self.paused, "The contract has been paused" + assert self.balanceOf[msg.sender][id] > 0 , "caller does not own this ID" + self.balanceOf[msg.sender][id] -= amount + log TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, id, amount) + +@external +def burnBatch(ids: DynArray[uint256, BATCH_SIZE], amounts: DynArray[uint256, BATCH_SIZE]): + """ + @dev burn a batch of tokens with the passed IDs + @dev this can be burning non fungible tokens or reducing the balance of existing non-fungible token IDs in the contract + @dev inside the loop ownership will be checked for each token. We can not burn tokens we do not own + @param ids array of ids for the tokens to burn + @param amounts array of amounts of tokens for each ID in the ids array + """ + assert not self.paused, "The contract has been paused" + assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch" + operator: address = msg.sender + + for i in range(BATCH_SIZE): + if i >= len(ids): + break + self.balanceOf[msg.sender][ids[i]] -= amounts[i] + + log TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, ids, amounts) + +## approval ## +@external +def setApprovalForAll(owner: address, operator: address, approved: bool): + """ + @dev set an operator for a certain NFT owner address + @param account the NFT owner address + @param operator the operator address + """ + assert owner == msg.sender, "You can only set operators for your own account" + assert not self.paused, "The contract has been paused" + assert owner != operator, "ERC1155: setting approval status for self" + self.isApprovedForAll[owner][operator] = approved + log ApprovalForAll(owner, operator, approved) + +@external +def safeTransferFrom(sender: address, receiver: address, id: uint256, amount: uint256, bytes: bytes32): + """ + @dev transfer token from one address to another. + @param sender the sending account (current owner) + @param receiver the receiving account + @param id the token id that will be sent + @param amount the amount of tokens for the specified id + """ + assert not self.paused, "The contract has been paused" + assert receiver != ZERO_ADDRESS, "ERC1155: transfer to the zero address" + assert sender != receiver + assert sender == msg.sender or self.isApprovedForAll[sender][msg.sender], "Caller is neither owner nor approved operator for this ID" + assert self.balanceOf[sender][id] > 0 , "caller does not own this ID or ZERO balance" + operator: address = msg.sender + self.balanceOf[sender][id] -= amount + self.balanceOf[receiver][id] += amount + log TransferSingle(operator, sender, receiver, id, amount) + +@external +def safeBatchTransferFrom(sender: address, receiver: address, ids: DynArray[uint256, BATCH_SIZE], amounts: DynArray[uint256, BATCH_SIZE], _bytes: bytes32): + """ + @dev transfer tokens from one address to another. + @param sender the sending account + @param receiver the receiving account + @param ids a dynamic array of the token ids that will be sent + @param amounts a dynamic array of the amounts for the specified list of ids. + """ + assert not self.paused, "The contract has been paused" + assert receiver != ZERO_ADDRESS, "ERC1155: transfer to the zero address" + assert sender != receiver + assert sender == msg.sender or self.isApprovedForAll[sender][msg.sender], "Caller is neither owner nor approved operator for this ID" + assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch" + operator: address = msg.sender + for i in range(BATCH_SIZE): + if i >= len(ids): + break + id: uint256 = ids[i] + amount: uint256 = amounts[i] + self.balanceOf[sender][id] -= amount + self.balanceOf[receiver][id] += amount + + log TransferBatch(operator, sender, receiver, ids, amounts) + +# URI # +@external +def setURI(uri: String[MAX_URI_LENGTH]): + """ + @dev set the URI for the contract + @param uri the new uri for the contract + """ + assert not self.paused, "The contract has been paused" + assert self._uri != uri, "new and current URI are identical" + assert msg.sender == self.owner, "Only the contract owner can update the URI" + self._uri = uri + log URI(uri, 0) + +@external +def uri(id: uint256) -> String[MAX_URI_LENGTH]: + """ + @dev retrieve the uri, this function can optionally be extended to return dynamic uris. out of scope. + @param id NFT ID to retrieve the uri for. + """ + return self._uri + +@pure +@external +def supportsInterface(interfaceId: bytes4) -> bool: + """ + @dev Returns True if the interface is supported + @param interfaceID bytes4 interface identifier + """ + return interfaceId in [ + ERC165_INTERFACE_ID, + ERC1155_INTERFACE_ID, + ERC1155_INTERFACE_ID_METADATA, + ] diff --git a/tests/examples/tokens/test_erc1155.py b/tests/examples/tokens/test_erc1155.py new file mode 100644 index 0000000000..4bca205bff --- /dev/null +++ b/tests/examples/tokens/test_erc1155.py @@ -0,0 +1,364 @@ +import pytest + +# ERC1155 ownable, opensea compatible tests +# @author Dr. Pixel (github: @Doc-Pixel) + +# constants - contract deployment +CONTRACT_NAME = "TEST 1155" +CONTRACT_SYMBOL = "T1155" +CONTRACT_URI = "https://mydomain.io/NFTdata/{id}" +NEW_CONTRACT_URI = "https://mynewdomain.io/NFTdata/{id}" +ERC165_INTERFACE_ID = "0x01ffc9a7" +ERC1155_INTERFACE_ID = "0xd9b67a26" +ERC1155_INTERFACE_ID_METADATA = "0x0e89341c" +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" + +# minting test lists +mintBatch = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +mintBatch2 = [11, 12, 13, 14, 15, 16, 17, 19, 19, 20] +minBatchSetOf10 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +mintConflictBatch = [1, 2, 3] + + +@pytest.fixture +def erc1155(get_contract, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + with open("examples/tokens/ERC1155ownable.vy") as f: + code = f.read() + c = get_contract(code, *[CONTRACT_NAME, CONTRACT_SYMBOL, CONTRACT_URI]) + + c.mintBatch(a1, mintBatch, minBatchSetOf10, "", transact={"from": owner}) + c.mintBatch(a3, mintBatch2, minBatchSetOf10, "", transact={"from": owner}) + + assert c.balanceOf(a1, 1) == 1 + assert c.balanceOf(a1, 2) == 1 + assert c.balanceOf(a1, 3) == 1 + assert_tx_failed( + lambda: c.mintBatch(ZERO_ADDRESS, mintBatch, minBatchSetOf10, "", transact={"from": owner}) + ) + assert_tx_failed(lambda: c.mintBatch(a1, [1, 2, 3], [1, 1], "", transact={"from": owner})) + + c.mint(a1, 21, 1, "", transact={"from": owner}) + c.mint(a1, 22, 1, "", transact={"from": owner}) + c.mint(a1, 23, 1, "", transact={"from": owner}) + c.mint(a1, 24, 1, "", transact={"from": owner}) + + assert_tx_failed(lambda: c.mint(a1, 24, 1, "", transact={"from": a3})) + assert_tx_failed(lambda: c.mint(ZERO_ADDRESS, 24, 1, "", transact={"from": owner})) + + assert c.balanceOf(a1, 21) == 1 + assert c.balanceOf(a1, 22) == 1 + assert c.balanceOf(a1, 23) == 1 + assert c.balanceOf(a1, 24) == 1 + + return c + + +# tests + + +def test_initial_state(erc1155): + # Check if the constructor of the contract is set up properly + # and the contract is deployed with the desired variables + + # variables set correctly? + assert erc1155.name() == CONTRACT_NAME + assert erc1155.symbol() == CONTRACT_SYMBOL + assert erc1155.uri(0) == CONTRACT_URI + + # interfaces set up correctly? + assert erc1155.supportsInterface(ERC165_INTERFACE_ID) + assert erc1155.supportsInterface(ERC1155_INTERFACE_ID) + assert erc1155.supportsInterface(ERC1155_INTERFACE_ID_METADATA) + + +def test_pause(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + # check the pause status, pause, check, unpause, check, with owner and non-owner w3.eth.accounts + # this test will check all the function that should not work when paused. + assert not erc1155.paused() + + # try to pause the contract from a non owner account + assert_tx_failed(lambda: erc1155.pause(transact={"from": a1})) + + # now pause the contract and check status + erc1155.pause(transact={"from": owner}) + assert erc1155.paused() + + # try pausing a paused contract + assert_tx_failed(lambda: erc1155.pause()) + + # try functions that should not work when paused + assert_tx_failed(lambda: erc1155.setURI(NEW_CONTRACT_URI)) + + # test burn and burnbatch + assert_tx_failed(lambda: erc1155.burn(21, 1)) + assert_tx_failed(lambda: erc1155.burnBatch([21, 22], [1, 1])) + + # check mint and mintbatch + assert_tx_failed(lambda: erc1155.mint(a1, 21, 1, "", transact={"from": owner})) + assert_tx_failed( + lambda: erc1155.mintBatch(a1, mintBatch, minBatchSetOf10, "", transact={"from": owner}) + ) + + # check safetransferfrom and safebatchtransferfrom + assert_tx_failed(lambda: erc1155.safeTransferFrom(a1, a2, 21, 1, "", transact={"from": a1})) + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a2, [21, 22, 23], [1, 1, 1], "", transact={"from": a1} + ) + ) + + # check ownership functions + assert_tx_failed(lambda: erc1155.transferOwnership(a1)) + assert_tx_failed(lambda: erc1155.renounceOwnership()) + + # check approval functions + assert_tx_failed(lambda: erc1155.setApprovalForAll(owner, a5, True)) + + # try and unpause as non-owner + assert_tx_failed(lambda: erc1155.unpause(transact={"from": a1})) + + erc1155.unpause(transact={"from": owner}) + assert not erc1155.paused() + + # try un pausing an unpaused contract + assert_tx_failed(lambda: erc1155.unpause()) + + +def test_URI(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + # change contract URI and restore. + assert erc1155.uri(0) == CONTRACT_URI + erc1155.setURI(NEW_CONTRACT_URI, transact={"from": owner}) + assert erc1155.uri(0) == NEW_CONTRACT_URI + assert erc1155.uri(0) != CONTRACT_URI + erc1155.setURI(CONTRACT_URI, transact={"from": owner}) + assert erc1155.uri(0) != NEW_CONTRACT_URI + assert erc1155.uri(0) == CONTRACT_URI + + assert_tx_failed(lambda: erc1155.setURI(CONTRACT_URI)) + + +def test_safeTransferFrom_balanceOf_single(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + assert erc1155.balanceOf(a1, 24) == 1 + # transfer by non-owner + assert_tx_failed(lambda: erc1155.safeTransferFrom(a1, a2, 24, 1, "", transact={"from": a2})) + + # transfer to zero address + assert_tx_failed( + lambda: erc1155.safeTransferFrom(a1, ZERO_ADDRESS, 24, 1, "", transact={"from": a1}) + ) + + # transfer to self + assert_tx_failed(lambda: erc1155.safeTransferFrom(a1, a1, 24, 1, "", transact={"from": a1})) + + # transfer more than owned + assert_tx_failed(lambda: erc1155.safeTransferFrom(a1, a2, 24, 500, "", transact={"from": a1})) + + # transfer item not owned / not existing + assert_tx_failed(lambda: erc1155.safeTransferFrom(a1, a2, 500, 1, "", transact={"from": a1})) + + erc1155.safeTransferFrom(a1, a2, 21, 1, "", transact={"from": a1}) + + assert erc1155.balanceOf(a2, 21) == 1 + + # try to transfer item again + assert_tx_failed(lambda: erc1155.safeTransferFrom(a1, a2, 21, 1, "", transact={"from": a1})) + assert erc1155.balanceOf(a1, 21) == 0 + + +# TODO: mint 20 NFTs [1:20] and check the balance for each +def test_mintBatch_balanceOf(erc1155, w3, assert_tx_failed): # test_mint_batch + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + # Use the mint three fixture to mint the tokens. + # this test checks the balances of this test + for i in range(1, 10): + assert erc1155.balanceOf(a1, i) == 1 + + +def test_safeBatchTransferFrom_balanceOf_batch(erc1155, w3, assert_tx_failed): # test_mint_batch + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + + # check a1 balances for NFTs 21-24 + assert erc1155.balanceOf(a1, 21) == 1 + assert erc1155.balanceOf(a1, 22) == 1 + assert erc1155.balanceOf(a1, 23) == 1 + assert erc1155.balanceOf(a1, 23) == 1 + + # try to transfer item from non item owner account + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a2, [21, 22, 23], [1, 1, 1], "", transact={"from": a2} + ) + ) + + # try to transfer item to zero address + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, ZERO_ADDRESS, [21, 22, 23], [1, 1, 1], "", transact={"from": a1} + ) + ) + + # try to transfer item to self + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a1, [21, 22, 23], [1, 1, 1], "", transact={"from": a1} + ) + ) + + # try to transfer more items than we own + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a2, [21, 22, 23], [1, 125, 1], "", transact={"from": a1} + ) + ) + + # mismatched item and amounts + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a2, [21, 22, 23], [1, 1], "", transact={"from": a1} + ) + ) + + # try to transfer nonexisting item + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a2, [21, 22, 500], [1, 1, 1], "", transact={"from": a1} + ) + ) + assert erc1155.safeBatchTransferFrom(a1, a2, [21, 22, 23], [1, 1, 1], "", transact={"from": a1}) + + # try to transfer again, our balances are zero now, should fail + assert_tx_failed( + lambda: erc1155.safeBatchTransferFrom( + a1, a2, [21, 22, 23], [1, 1, 1], "", transact={"from": a1} + ) + ) + assert_tx_failed( + lambda: erc1155.balanceOfBatch([a2, a2, a2], [21, 22], transact={"from": owner}) + == [1, 1, 1] + ) + + assert erc1155.balanceOfBatch([a2, a2, a2], [21, 22, 23]) == [1, 1, 1] + assert erc1155.balanceOf(a1, 21) == 0 + + +def test_mint_one_burn_one(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + + # check the balance from an owner and non-owner account + erc1155.mint(owner, 25, 1, "", transact={"from": owner}) + + assert erc1155.balanceOf(owner, 25) == 1 + assert erc1155.balanceOf(owner, 25) == 1 + + # try and burn an item we don't control + assert_tx_failed(lambda: erc1155.burn(25, 1, transact={"from": a3})) + + # burn an item that contains something we don't own + assert_tx_failed(lambda: erc1155.burn(595, 1, transact={"from": a1})) + + # burn ah item passing a higher amount than we own + assert_tx_failed(lambda: erc1155.burn(25, 500, transact={"from": a1})) + + erc1155.burn(25, 1, transact={"from": owner}) + + assert erc1155.balanceOf(owner, 25) == 0 + + +def test_mint_batch_burn_batch(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + # mint NFTs 11-20 + + # check the balance + assert erc1155.balanceOfBatch([a3, a3, a3], [11, 12, 13]) == [1, 1, 1] + + # try and burn a batch we don't control + assert_tx_failed(lambda: erc1155.burnBatch([11, 12], [1, 1])) + + # ids and amounts array length not matching + assert_tx_failed(lambda: erc1155.burnBatch([1, 2, 3], [1, 1], transact={"from": a1})) + + # burn a batch that contains something we don't own + assert_tx_failed(lambda: erc1155.burnBatch([2, 3, 595], [1, 1, 1], transact={"from": a1})) + + # burn a batch passing a higher amount than we own + assert_tx_failed(lambda: erc1155.burnBatch([1, 2, 3], [1, 500, 1], transact={"from": a1})) + + # burn existing + erc1155.burnBatch([11, 12], [1, 1], transact={"from": a3}) + + assert erc1155.balanceOfBatch([a3, a3, a3], [11, 12, 13]) == [0, 0, 1] + + # burn again, should revert + assert_tx_failed(lambda: erc1155.burnBatch([11, 12], [1, 1], transact={"from": a3})) + + assert lambda: erc1155.balanceOfBatch([a3, a3, a3], [1, 2, 3]) == [0, 0, 1] + + +def test_approval_functions(erc1155, w3, assert_tx_failed): # test_mint_batch + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + # self-approval by the owner + assert_tx_failed(lambda: erc1155.setApprovalForAll(a5, a5, True, transact={"from": a5})) + + # let's approve and operator for somebody else's account + assert_tx_failed(lambda: erc1155.setApprovalForAll(owner, a5, True, transact={"from": a3})) + + # set approval correctly + erc1155.setApprovalForAll(owner, a5, True) + + # check approval + erc1155.isApprovedForAll(owner, a5) + + # remove approval + erc1155.setApprovalForAll(owner, a5, False) + + +def test_max_batch_size_violation(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + TOTAL_BAD_BATCH = 200 + ids = [] + amounts = [] + for i in range(1, TOTAL_BAD_BATCH): + ids.append(i) + amounts.append(1) + + assert_tx_failed(lambda: erc1155.mintBatch(a1, ids, amounts, "", transact={"from": owner})) + + +# Transferring back and forth + + +def test_ownership_functions(erc1155, w3, assert_tx_failed, tester): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + print(owner, a1, a2) + print("___owner___", erc1155.owner()) + # change owner from account 0 to account 1 and back + + assert erc1155.owner() == owner + assert_tx_failed(lambda: erc1155.transferOwnership(a1, transact={"from": a2})) + + # try to transfer ownership to current owner + assert_tx_failed(lambda: erc1155.transferOwnership(owner)) + # try to transfer ownership to ZERO ADDRESS + assert_tx_failed( + lambda: erc1155.transferOwnership("0x0000000000000000000000000000000000000000") + ) + + # Transfer ownership to account 1 + erc1155.transferOwnership(a1, transact={"from": owner}) + # assert erc1155.owner() == a1 + assert erc1155.owner() == a1 + + +def test_renounce_ownership(erc1155, w3, assert_tx_failed): + owner, a1, a2, a3, a4, a5 = w3.eth.accounts[0:6] + assert erc1155.owner() == owner + # try to transfer ownership from non-owner account + assert_tx_failed(lambda: erc1155.renounceOwnership(transact={"from": a2})) + + erc1155.renounceOwnership(transact={"from": owner}) + + # assert erc1155.owner() == ZERO_ADDRESS