Repository: pcaversaccio/snekmate (language: python)

Issue:
💥 ERC-1155 Token Implementation
Modern and gas-efficient ERC-1155 implementation.
♻️ Reorder `README` Sequence
The `README` sequence should look like this:

```ml
src
├─ auth
│  ├─ Ownable — "Owner-Based Access Control Functions"
│  ├─ Ownable2Step — "2-Step Ownership Transfer Functions"
│  ├─ AccessControl — "Multi-Role-Based Access Control Functions"
├─ extensions
│  ├─ ERC4626 — "ERC-4626 Tokenised Vault Implementation (TBD)"
├─ tokens
│  ├─ ERC20 — "Modern and Gas-Efficient ERC-20 + EIP-2612 Implementation"
│  ├─ ERC721 — "Modern and Gas-Efficient ERC-721 + EIP-4494 Implementation"
│  ├─ ERC1155 — "Modern and Gas-Efficient ERC-1155 Implementation (TBD)"
├─ utils
   ├─ Base64 — "Base64 Encoding and Decoding Functions"
   ├─ BatchDistributor — "Batch Sending Both Native and ERC-20 Tokens"
   ├─ CreateAddress — "`CREATE` EVM Opcode Utility Function for Address Calculation"
   ├─ Create2Address — "`CREATE2` EVM Opcode Utility Functions for Address Calculations"
   ├─ ECDSA — "Elliptic Curve Digital Signature Algorithm (ECDSA) Functions"
   ├─ SignatureChecker — "ECDSA and EIP-1271 Signature Verification Function"
   ├─ EIP712DomainSeparator — "EIP-712 Domain Separator"
   ├─ MerkleProofVerification — "Merkle Tree Proof Verification Functions"
   ├─ Multicall — "Multicall Functions"
```
💥 Add `ERC1155MetadataURI` Implementation Logic
#### PR Checklist

- [x] Because this PR includes a **new feature**, the change was previously discussed in #15.

---

#### Feature

This PR implements a basic `ERC1155MetadataURI` contract with an owner and minter authentication system. The contract is not feature-complete nor does this PR implement a full suite of unit tests. This will be done in an additional PR.



Discussion / hints:

Fix patch:
diff --git a/.gas-snapshot b/.gas-snapshot
index 4fc65813..bf1cb218 100644
--- a/.gas-snapshot
+++ b/.gas-snapshot
@@ -33,6 +33,103 @@ Ownable2StepTest:testRenounceOwnershipNonOwner() (gas: 10949)
 Ownable2StepTest:testRenounceOwnershipSuccess() (gas: 17815)
 Ownable2StepTest:testTransferOwnershipNonOwner() (gas: 11321)
 Ownable2StepTest:testTransferOwnershipSuccess() (gas: 41735)
+ERC1155Test:testBalanceOfBatchCase1() (gas: 267981)
+ERC1155Test:testBalanceOfBatchCase2() (gas: 222553)
+ERC1155Test:testBalanceOfBatchCase3() (gas: 35825)
+ERC1155Test:testBalanceOfBatchLengthsMismatch() (gas: 19627)
+ERC1155Test:testBalanceOfBatchZeroAddress() (gas: 14622)
+ERC1155Test:testBalanceOfCase1() (gas: 137780)
+ERC1155Test:testBalanceOfCase2() (gas: 17784)
+ERC1155Test:testBalanceOfZeroAddress() (gas: 9031)
+ERC1155Test:testBurnAmountExceedsBalance() (gas: 120811)
+ERC1155Test:testBurnBatch() (gas: 213026)
+ERC1155Test:testBurnBatchAmountExceedsBalance() (gas: 195973)
+ERC1155Test:testBurnBatchByApprovedOperator() (gas: 246458)
+ERC1155Test:testBurnBatchByNotApprovedOperator() (gas: 29697)
+ERC1155Test:testBurnBatchFromZeroAddress() (gas: 24166)
+ERC1155Test:testBurnBatchLengthsMismatch() (gas: 40932)
+ERC1155Test:testBurnBatchNonExistentTokenIds() (gas: 27409)
+ERC1155Test:testBurnByApprovedOperator() (gas: 185021)
+ERC1155Test:testBurnByNotApprovedOperator() (gas: 140695)
+ERC1155Test:testBurnFromZeroAddress() (gas: 15398)
+ERC1155Test:testBurnNonExistentTokenId() (gas: 19485)
+ERC1155Test:testBurnSuccess() (gas: 129748)
+ERC1155Test:testExistsAfterBatchBurn() (gas: 131679)
+ERC1155Test:testExistsAfterBatchMint() (gas: 142778)
+ERC1155Test:testExistsAfterSingleBurn() (gas: 127473)
+ERC1155Test:testExistsAfterSingleMint() (gas: 111557)
+ERC1155Test:testExistsBeforeMint() (gas: 7769)
+ERC1155Test:testHasOwner() (gas: 10097)
+ERC1155Test:testInitialSetup() (gas: 25537)
+ERC1155Test:testRenounceOwnershipNonOwner() (gas: 11238)
+ERC1155Test:testRenounceOwnershipSuccess() (gas: 21174)
+ERC1155Test:testSafeBatchTransferFromByApprovedOperator() (gas: 334852)
+ERC1155Test:testSafeBatchTransferFromByNotApprovedOperator() (gas: 257949)
+ERC1155Test:testSafeBatchTransferFromEOAReceiver() (gas: 294332)
+ERC1155Test:testSafeBatchTransferFromInsufficientBalance() (gas: 266018)
+ERC1155Test:testSafeBatchTransferFromLengthsMismatch() (gas: 61848)
+ERC1155Test:testSafeBatchTransferFromNoData() (gas: 678314)
+ERC1155Test:testSafeBatchTransferFromReceiverFunctionNotImplemented() (gas: 280330)
+ERC1155Test:testSafeBatchTransferFromReceiverInvalidReturnIdentifier() (gas: 656127)
+ERC1155Test:testSafeBatchTransferFromReceiverReverts() (gas: 649479)
+ERC1155Test:testSafeBatchTransferFromReceiverRevertsOnlySingle() (gas: 678334)
+ERC1155Test:testSafeBatchTransferFromToZeroAddress() (gas: 254942)
+ERC1155Test:testSafeBatchTransferFromWithData() (gas: 680128)
+ERC1155Test:testSafeMintBatchEOAReceiver() (gas: 245289)
+ERC1155Test:testSafeMintBatchLengthsMismatch() (gas: 66856)
+ERC1155Test:testSafeMintBatchNoData() (gas: 623169)
+ERC1155Test:testSafeMintBatchNonMinter() (gas: 37708)
+ERC1155Test:testSafeMintBatchOverflow() (gas: 262536)
+ERC1155Test:testSafeMintBatchReceiverFunctionNotImplemented() (gas: 227185)
+ERC1155Test:testSafeMintBatchReceiverInvalidReturnIdentifier() (gas: 607086)
+ERC1155Test:testSafeMintBatchReceiverReverts() (gas: 600504)
+ERC1155Test:testSafeMintBatchReceiverRevertsOnlySingle() (gas: 623190)
+ERC1155Test:testSafeMintBatchToZeroAddress() (gas: 39160)
+ERC1155Test:testSafeMintBatchWithData() (gas: 624127)
+ERC1155Test:testSafeMintEOAReceiver() (gas: 140095)
+ERC1155Test:testSafeMintNoData() (gas: 517080)
+ERC1155Test:testSafeMintNonMinter() (gas: 30079)
+ERC1155Test:testSafeMintOverflow() (gas: 84732)
+ERC1155Test:testSafeMintReceiverFunctionNotImplemented() (gas: 130520)
+ERC1155Test:testSafeMintReceiverInvalidReturnIdentifier() (gas: 510287)
+ERC1155Test:testSafeMintReceiverReverts() (gas: 504085)
+ERC1155Test:testSafeMintToZeroAddress() (gas: 31454)
+ERC1155Test:testSafeMintWithData() (gas: 518838)
+ERC1155Test:testSafeTransferFromByApprovedOperator() (gas: 192753)
+ERC1155Test:testSafeTransferFromByNotApprovedOperator() (gas: 141880)
+ERC1155Test:testSafeTransferFromEOAReceiver() (gas: 157736)
+ERC1155Test:testSafeTransferFromInsufficientBalance() (gas: 83295)
+ERC1155Test:testSafeTransferFromNoData() (gas: 597870)
+ERC1155Test:testSafeTransferFromReceiverFunctionNotImplemented() (gas: 192236)
+ERC1155Test:testSafeTransferFromReceiverInvalidReturnIdentifier() (gas: 571905)
+ERC1155Test:testSafeTransferFromReceiverReverts() (gas: 565615)
+ERC1155Test:testSafeTransferFromToZeroAddress() (gas: 81560)
+ERC1155Test:testSafeTransferFromWithData() (gas: 601369)
+ERC1155Test:testSetApprovalForAllRevoke() (gas: 32284)
+ERC1155Test:testSetApprovalForAllSuccess() (gas: 45584)
+ERC1155Test:testSetApprovalForAllToSelf() (gas: 9993)
+ERC1155Test:testSetMinterNonOwner() (gas: 11687)
+ERC1155Test:testSetMinterRemoveOwnerAddress() (gas: 14191)
+ERC1155Test:testSetMinterSuccess() (gas: 33278)
+ERC1155Test:testSetMinterToZeroAddress() (gas: 13956)
+ERC1155Test:testSetUri() (gas: 69644)
+ERC1155Test:testSetUriEmpty() (gas: 27790)
+ERC1155Test:testSetUriNonMinter() (gas: 12277)
+ERC1155Test:testSupportsInterfaceGasCost() (gas: 5571)
+ERC1155Test:testSupportsInterfaceInvalidInterfaceId() (gas: 5592)
+ERC1155Test:testSupportsInterfaceSuccess() (gas: 7062)
+ERC1155Test:testTotalSupplyAfterBatchBurn() (gas: 131961)
+ERC1155Test:testTotalSupplyAfterBatchMint() (gas: 143045)
+ERC1155Test:testTotalSupplyAfterSingleBurn() (gas: 124549)
+ERC1155Test:testTotalSupplyAfterSingleMint() (gas: 111775)
+ERC1155Test:testTotalSupplyBeforeMint() (gas: 7965)
+ERC1155Test:testTransferOwnershipNonOwner() (gas: 11623)
+ERC1155Test:testTransferOwnershipSuccess() (gas: 53199)
+ERC1155Test:testTransferOwnershipToZeroAddress() (gas: 13818)
+ERC1155Test:testUriBaseAndTokenUriNotSet() (gas: 2993588)
+ERC1155Test:testUriBaseAndTokenUriSet() (gas: 66090)
+ERC1155Test:testUriNoBaseURI() (gas: 3042360)
+ERC1155Test:testUriNoTokenUri() (gas: 16901)
 ERC20Test:testApproveExceedingBalanceCase1() (gas: 39384)
 ERC20Test:testApproveExceedingBalanceCase2() (gas: 46096)
 ERC20Test:testApproveFromZeroAddress() (gas: 9833)
@@ -80,10 +177,10 @@ ERC20Test:testPermitOtherSignature() (gas: 40311)
 ERC20Test:testPermitReplaySignature() (gas: 72425)
 ERC20Test:testPermitSuccess() (gas: 68616)
 ERC20Test:testRenounceOwnershipNonOwner() (gas: 11125)
-ERC20Test:testRenounceOwnershipSuccess() (gas: 17881)
+ERC20Test:testRenounceOwnershipSuccess() (gas: 21194)
 ERC20Test:testSetMinterNonOwner() (gas: 11536)
 ERC20Test:testSetMinterRemoveOwnerAddress() (gas: 13951)
-ERC20Test:testSetMinterSuccess() (gas: 33014)
+ERC20Test:testSetMinterSuccess() (gas: 33067)
 ERC20Test:testSetMinterToZeroAddress() (gas: 13762)
 ERC20Test:testTotalSupply() (gas: 9296)
 ERC20Test:testTransferFromExceedingBalance() (gas: 30884)
@@ -96,7 +193,7 @@ ERC20Test:testTransferFromUnlimitedAllowance() (gas: 69632)
 ERC20Test:testTransferFromZeroAddress() (gas: 16928)
 ERC20Test:testTransferInvalidAmount() (gas: 14201)
 ERC20Test:testTransferOwnershipNonOwner() (gas: 11642)
-ERC20Test:testTransferOwnershipSuccess() (gas: 21523)
+ERC20Test:testTransferOwnershipSuccess() (gas: 53265)
 ERC20Test:testTransferOwnershipToZeroAddress() (gas: 13705)
 ERC20Test:testTransferSuccess() (gas: 42042)
 ERC20Test:testTransferToZeroAddress() (gas: 14721)
@@ -133,7 +230,7 @@ ERC721Test:testPermitOtherSignature() (gas: 197089)
 ERC721Test:testPermitReplaySignature() (gas: 230556)
 ERC721Test:testPermitSuccess() (gas: 226099)
 ERC721Test:testRenounceOwnershipNonOwner() (gas: 11311)
-ERC721Test:testRenounceOwnershipSuccess() (gas: 18070)
+ERC721Test:testRenounceOwnershipSuccess() (gas: 21383)
 ERC721Test:testSafeMintNonMinter() (gas: 12264)
 ERC721Test:testSafeMintOverflow() (gas: 12487)
 ERC721Test:testSafeMintReceiverContract() (gas: 392750)
@@ -158,7 +255,7 @@ ERC721Test:testSetApprovalForAllSuccessCase2() (gas: 187509)
 ERC721Test:testSetApprovalForAllSuccessCase3() (gas: 200570)
 ERC721Test:testSetMinterNonOwner() (gas: 11759)
 ERC721Test:testSetMinterRemoveOwnerAddress() (gas: 14130)
-ERC721Test:testSetMinterSuccess() (gas: 33472)
+ERC721Test:testSetMinterSuccess() (gas: 33525)
 ERC721Test:testSetMinterToZeroAddress() (gas: 13917)
 ERC721Test:testSupportsInterfaceGasCost() (gas: 5754)
 ERC721Test:testSupportsInterfaceInvalidInterfaceId() (gas: 5641)
@@ -170,12 +267,12 @@ ERC721Test:testTokenOfOwnerByIndexReverts() (gas: 549957)
 ERC721Test:testTokenURIAfterBurning() (gas: 137982)
 ERC721Test:testTokenURIDefault() (gas: 167794)
 ERC721Test:testTokenURIInvalidTokenId() (gas: 11585)
-ERC721Test:testTokenURINoBaseURI() (gas: 2521716)
+ERC721Test:testTokenURINoBaseURI() (gas: 2558116)
 ERC721Test:testTokenURINoTokenUri() (gas: 124830)
 ERC721Test:testTotalSupply() (gas: 327164)
 ERC721Test:testTransferFrom() (gas: 554469)
 ERC721Test:testTransferOwnershipNonOwner() (gas: 11785)
-ERC721Test:testTransferOwnershipSuccess() (gas: 21752)
+ERC721Test:testTransferOwnershipSuccess() (gas: 53632)
 ERC721Test:testTransferOwnershipToZeroAddress() (gas: 13891)
 Base64Test:testDataLengthMismatch() (gas: 12503)
 Base64Test:testDecodeEmptyString() (gas: 7978)
diff --git a/README.md b/README.md
index cbfa52ba..9d92344c 100644
--- a/README.md
+++ b/README.md
@@ -20,17 +20,17 @@ src
 ├─ tokens
 │  ├─ ERC20 — "Modern and Gas-Efficient ERC-20 + EIP-2612 Implementation"
 │  ├─ ERC721 — "Modern and Gas-Efficient ERC-721 + EIP-4494 Implementation"
-│  ├─ ERC1155 — "Modern and Gas-Efficient ERC-1155 Implementation (TBD)"
+│  ├─ ERC1155 — "Modern and Gas-Efficient ERC-1155 Implementation"
 ├─ utils
-   ├─ ECDSA — "Elliptic Curve Digital Signature Algorithm (ECDSA) Functions"
+   ├─ Base64 — "Base64 Encoding and Decoding Functions"
+   ├─ BatchDistributor — "Batch Sending Both Native and ERC-20 Tokens"
    ├─ CreateAddress — "`CREATE` EVM Opcode Utility Function for Address Calculation"
    ├─ Create2Address — "`CREATE2` EVM Opcode Utility Functions for Address Calculations"
+   ├─ ECDSA — "Elliptic Curve Digital Signature Algorithm (ECDSA) Functions"
+   ├─ SignatureChecker — "ECDSA and EIP-1271 Signature Verification Function"
    ├─ EIP712DomainSeparator — "EIP-712 Domain Separator"
    ├─ MerkleProofVerification — "Merkle Tree Proof Verification Functions"
    ├─ Multicall — "Multicall Functions"
-   ├─ SignatureChecker — "ECDSA and EIP-1271 Signature Verification Function"
-   ├─ BatchDistributor — "Batch Sending Both Native and ERC-20 Tokens"
-   ├─ Base64 — "Base64 Encoding and Decoding Functions"
 ```
 
 ## 🙏🏼 Acknowledgements
diff --git a/src/auth/AccessControl.vy b/src/auth/AccessControl.vy
index abf39125..d65d6d2d 100644
--- a/src/auth/AccessControl.vy
+++ b/src/auth/AccessControl.vy
@@ -149,7 +149,7 @@ def __init__():
 
 
 @external
-@pure
+@view
 def supportsInterface(interface_id: bytes4) -> bool:
     """
     @dev Returns `True` if this contract implements the
diff --git a/src/tokens/ERC1155.vy b/src/tokens/ERC1155.vy
index 19592cdc..30488cfe 100644
--- a/src/tokens/ERC1155.vy
+++ b/src/tokens/ERC1155.vy
@@ -3,5 +3,943 @@
 @title Modern and Gas-Efficient ERC-1155 Implementation
 @license GNU Affero General Public License v3.0
 @author pcaversaccio
-@notice TBD
+@custom:coauthor jtriley.eth
+@notice These functions implement the ERC-1155
+        standard interface:
+        - https://eips.ethereum.org/EIPS/eip-1155.
+        In addition, the following functions have
+        been added for convenience:
+        - `uri` (`external` `view` function),
+        - `set_uri` (`external` function),
+        - `total_supply` (`external` `view` function),
+        - `exists` (`external` `view` function),
+        - `burn` (`external` function),
+        - `burn_batch` (`external` function),
+        - `is_minter` (`external` `view` function),
+        - `safe_mint` (`external` function),
+        - `safe_mint_batch` (`external` function),
+        - `set_minter` (`external` function),
+        - `owner` (`external` `view` function),
+        - `transfer_ownership` (`external` function),
+        - `renounce_ownership` (`external` function),
+        - `_check_on_erc1155_received` (`internal` function),
+        - `_check_on_erc1155_batch_received` (`internal` function),
+        - `_before_token_transfer` (`internal` function),
+        - `_after_token_transfer` (`internal` function).
+        The implementation is inspired by OpenZeppelin's implementation here:
+        https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC1155/ERC1155.sol.
 """
+
+
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+# @dev We import and implement the `IERC1155` interface,
+# which is written using standard Vyper syntax.
+import interfaces.IERC1155 as IERC1155
+implements: IERC1155
+
+
+# @dev We import and implement the `IERC1155MetadataURI`
+# interface, which is written using standard Vyper
+# syntax.
+import interfaces.IERC1155MetadataURI as IERC1155MetadataURI
+implements: IERC1155MetadataURI
+
+
+# @dev We import the `IERC1155Receiver` interface, which
+# is written using standard Vyper syntax.
+import interfaces.IERC1155Receiver as IERC1155Receiver
+
+
+# @dev Stores the ERC-165 interface identifier for each
+# imported interface. The ERC-165 interface identifier
+# is defined as the XOR of all function selectors in the
+# interface.
+_SUPPORTED_INTERFACES: constant(bytes4[3]) = [
+    0x01FFC9A7, # The ERC-165 identifier for ERC-165.
+    0xD9B67A26, # The ERC-165 identifier for ERC-1155.
+    0x0E89341C, # The ERC-165 identifier for the ERC-1155 metadata extension.
+]
+
+
+# @dev Stores the upper bound for batch calls.
+_BATCH_SIZE: constant(uint16) = max_value(uint8)
+
+
+# @dev Stores the base URI for computing `uri`.
+_BASE_URI: immutable(String[80])
+
+
+# @dev Mapping from owner to operator approvals.
+isApprovedForAll: public(HashMap[address, HashMap[address, bool]])
+
+
+# @dev Mapping from token ID to token supply.
+total_supply: public(HashMap[uint256, uint256])
+
+
+# @dev Returns `True` if an `address` has been
+# granted the minter role.
+is_minter: public(HashMap[address, bool])
+
+
+# @dev Returns address of the current owner.
+owner: public(address)
+
+
+# @dev Mapping from token ID to owner balance.
+_balances: HashMap[uint256, HashMap[address, uint256]]
+
+
+# @dev Mapping from token ID to token URI.
+# @notice Since the Vyper design requires
+# strings of fixed size, we arbitrarily set
+# the maximum length for `_token_uris` to 432
+# characters. Since we have set the maximum
+# length for `_BASE_URI` to 80 characters,
+# which implies a maximum character length
+# for `uri` of 512.
+_token_uris: HashMap[uint256, String[432]]
+
+
+# @dev Emitted when `amount` tokens of token type
+# `id` are transferred from `owner` to `to` by
+# `operator`.
+event TransferSingle:
+    operator: indexed(address)
+    owner: indexed(address)
+    to: indexed(address)
+    id: uint256
+    amount: uint256
+
+
+# @dev Equivalent to multiple `TransferSingle` events,
+# where `operator`, `owner`, and `to` are the same
+# for all transfers.
+event TransferBatch:
+    operator: indexed(address)
+    owner: indexed(address)
+    to: indexed(address)
+    ids: DynArray[uint256, _BATCH_SIZE]
+    amounts: DynArray[uint256, _BATCH_SIZE]
+
+
+# @dev Emitted when `owner` grants or revokes permission
+# to `operator` to transfer their tokens, according to
+# `approved`.
+event ApprovalForAll:
+    owner: indexed(address)
+    operator: indexed(address)
+    approved: bool
+
+
+# @dev Emitted when the Uniform Resource Identifier (URI)
+# for token type `id` changes to `value`, if it is a
+# non-programmatic URI. Note that if an `URI` event was
+# emitted for `id`, the EIP-1155 standard guarantees that
+# `value` will equal the value returned by `uri`.
+event URI:
+    value: String[512]
+    id: indexed(uint256)
+
+
+# @dev Emitted when the ownership is transferred
+# from `previous_owner` to `new_owner`.
+event OwnershipTransferred:
+    previous_owner: indexed(address)
+    new_owner: indexed(address)
+
+
+# @dev Emitted when the status of a `minter`
+# address is changed.
+event RoleMinterChanged:
+    minter: indexed(address)
+    status: bool
+
+
+@external
+@payable
+def __init__(base_uri_: String[80]):
+    """
+    @dev To omit the opcodes for checking the `msg.value`
+         in the creation-time EVM bytecode, the constructor
+         is declared as `payable`.
+    @param base_uri_ The maximum 80-character user-readable
+           string base URI for computing `uri`.
+    """
+    _BASE_URI = base_uri_
+
+    self._transfer_ownership(msg.sender)
+    self.is_minter[msg.sender] = True
+    log RoleMinterChanged(msg.sender, True)
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return interface_id in _SUPPORTED_INTERFACES
+
+
+@external
+def safeTransferFrom(owner: address, to: address, id: uint256, amount: uint256, data: Bytes[1024]):
+    """
+    @dev Transfers `amount` tokens of token type `id` from
+         `owner` to `to`.
+    @notice Note that `to` cannot be the zero address. Also,
+            if the caller is not `owner`, it must have been
+            approved to spend `owner`'s tokens via `setApprovalForAll`.
+            Furthermore, `owner` must have a balance of tokens
+            of type `id` of at least `amount`. Eventually, if
+            `to` refers to a smart contract, it must implement
+            {IERC1155Receiver-onERC1155Received} and return the
+            acceptance magic value.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount that is
+           being transferred.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert owner == msg.sender or self.isApprovedForAll[owner][msg.sender], "ERC1155: caller is not token owner or approved"
+    self._safe_transfer_from(owner, to, id, amount, data)
+
+
+@external
+def safeBatchTransferFrom(owner: address, to: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE],
+                          data: Bytes[1024]):
+    """
+    @dev Batched version of `safeTransferFrom`.
+    @notice Note that `ids` and `amounts` must have the
+            same length. Also, if `to` refers to a smart
+            contract, it must implement {IERC1155Receiver-onERC1155BatchReceived}
+            and return the acceptance magic value.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being transferred. Note that the order and length must
+           match the 32-byte `ids` array.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert owner == msg.sender or self.isApprovedForAll[owner][msg.sender], "ERC1155: caller is not token owner or approved"
+    self._safe_batch_transfer_from(owner, to, ids, amounts, data)
+
+
+@external
+@view
+def balanceOf(owner: address, id: uint256) -> uint256:
+    """
+    @dev Returns the amount of tokens of token type
+         `id` owned by `owner`.
+    @notice Note that `owner` cannot be the zero
+            address.
+    @param owner The 20-byte owner address.
+    @param id The 32-byte identifier of the token.
+    @return uint256 The 32-byte token amount owned
+            by `owner`.
+    """
+    assert owner != empty(address), "ERC1155: address zero is not a valid owner"
+    return self._balances[id][owner]
+
+
+@external
+@view
+def balanceOfBatch(owners: DynArray[address, _BATCH_SIZE], ids: DynArray[uint256, _BATCH_SIZE]) -> DynArray[uint256, _BATCH_SIZE]:
+    """
+    @dev Batched version of `balanceOf`.
+    @notice Note that `owners` and `ids` must have the
+            same length.
+    @param owners The 20-byte array of owner addresses.
+    @param ids The 32-byte array of token identifiers.
+    @return DynArray The 32-byte array of token amounts
+            owned by `owners`.
+    """
+    assert len(owners) == len(ids), "ERC1155: owners and ids length mismatch"
+    batch_balances: DynArray[uint256, _BATCH_SIZE] = []
+    idx: uint256 = 0
+    for owner in owners:
+        batch_balances.append(IERC1155(self).balanceOf(owner, ids[idx]))
+        # The following line cannot overflow because we have
+        # limited the dynamic array `owners` by the `constant`
+        # parameter `_BATCH_SIZE`, which is bounded by the
+        # maximum value of `uint16`.
+        idx = unsafe_add(idx, 1)
+    return batch_balances
+
+
+@external
+def setApprovalForAll(operator: address, approved: bool):
+    """
+    @dev Grants or revokes permission to `operator` to
+         transfer the caller's tokens, according to `approved`.
+    @notice Note that `operator` cannot be the caller.
+    @param operator The 20-byte operator address.
+    @param approved The Boolean variable that sets the
+           approval status.
+    """
+    self._set_approval_for_all(msg.sender, operator, approved)
+
+
+@external
+@view
+def uri(id: uint256) -> String[512]:
+    """
+    @dev Returns the Uniform Resource Identifier (URI)
+         for token type `id`.
+    @notice If the `id` substring is present in the URI,
+            it must be replaced by clients with the actual
+            token type ID. Note that the `uri` function must
+            not be used to check for the existence of a token
+            as it is possible for the implementation to return
+            a valid string even if the token does not exist.
+    @param id The 32-byte identifier of the token type `id`.
+    @return String The maximum 512-character user-readable
+            string token URI of the token type `id`.
+    """
+    token_uri: String[432] = self._token_uris[id]
+
+    base_uri_length: uint256 = len(_BASE_URI)
+    # If there is no base URI, return the token URI.
+    if (base_uri_length == empty(uint256)):
+        return token_uri
+
+    # If both are set, concatenate the base URI
+    # and token URI.
+    if (len(token_uri) != empty(uint256)):
+        return concat(_BASE_URI, token_uri)
+
+    # If there is no token URI but a base URI,
+    # concatenate the base URI and token ID.
+    if (base_uri_length != empty(uint256)):
+        return concat(_BASE_URI, uint2str(id))
+    else:
+        return ""
+
+
+@external
+def set_uri(id: uint256, token_uri: String[432]):
+    """
+    @dev Sets the Uniform Resource Identifier (URI)
+         for token type `id`.
+    @notice This function is decoupled from `safe_mint`
+            and `safe_mint_batch`, as multiple of the same
+            `id` can be minted. However, the permissions
+            are shared with `is_minter`. Only minters have
+            the authorisation to change token URIs.
+    @param id The 32-byte identifier of the token.
+    @param token_uri The maximum 432-character user-readable
+           string URI for computing `uri`.
+    """
+    assert self.is_minter[msg.sender], "AccessControl: access is denied"
+    self._set_uri(id, token_uri)
+
+
+@external
+@view
+def exists(id: uint256) -> bool:
+    """
+    @dev Indicates whether any token exist with a
+         given `id` or not.
+    @param id The 32-byte identifier of the token.
+    @return bool The verification whether `id` exists
+            or not.
+    """
+    return self.total_supply[id] != 0
+
+
+@external
+def burn(owner: address, id: uint256, amount: uint256):
+    """
+    @dev Destroys `amount` tokens of token type `id`
+         from `owner`.
+    @notice Note that `owner` cannot be the zero
+            address. Also, `owner` must have at least
+            `amount` tokens of token type `id`.
+    @param owner The 20-byte owner address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount to be destroyed.
+    """
+    assert owner == msg.sender or self.isApprovedForAll[owner][msg.sender], "ERC1155: caller is not token owner or approved"
+    self._burn(owner, id, amount)
+
+
+@external
+def burn_batch(owner: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE]):
+    """
+    @dev Batched version of `burn`.
+    @notice Note that `ids` and `amounts` must have the
+            same length.
+    @param owner The 20-byte owner address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being destroyed. Note that the order and length must
+           match the 32-byte `ids` array.
+    """
+    assert owner == msg.sender or self.isApprovedForAll[owner][msg.sender], "ERC1155: caller is not token owner or approved"
+    self._burn_batch(owner, ids, amounts)
+
+
+@external
+def safe_mint(owner: address, id: uint256, amount: uint256, data: Bytes[1024]):
+    """
+    @dev Safely mints `amount` tokens of token type `id` and
+         transfers them to `owner`.
+    @notice Only authorised minters can access this function.
+            Note that `owner` cannot be the zero address. Also,
+            if `owner` refers to a smart contract, it must implement
+            {IERC1155Receiver-onERC1155Received}, which is called
+            upon a safe transfer.
+    @param owner The 20-byte owner address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount to be created.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert self.is_minter[msg.sender], "AccessControl: access is denied"
+    self._safe_mint(owner, id, amount, data)
+
+
+@external
+def safe_mint_batch(owner: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE], data: Bytes[1024]):
+    """
+    @dev Batched version of `safe_mint`.
+    @notice Note that `ids` and `amounts` must have the
+            same length. Also, if `owner` refers to a smart contract,
+            it must implement {IERC1155Receiver-onERC1155BatchReceived},
+            which is called upon a safe transfer.
+    @param owner The 20-byte owner address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being created. Note that the order and length must
+           match the 32-byte `ids` array.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert self.is_minter[msg.sender], "AccessControl: access is denied"
+    self._safe_mint_batch(owner, ids, amounts, data)
+
+
+@external
+def set_minter(minter: address, status: bool):
+    """
+    @dev Adds or removes an address `minter` to/from the
+         list of allowed minters. Note that only the
+         `owner` can add or remove `minter` addresses.
+         Also, the `minter` cannot be the zero address.
+         Eventually, the `owner` cannot remove himself
+         from the list of allowed minters.
+    @param minter The 20-byte minter address.
+    @param status The Boolean variable that sets the status.
+    """
+    self._check_owner()
+    assert minter != empty(address), "AccessControl: minter is the zero address"
+    assert minter != self.owner, "AccessControl: minter is owner address"
+    self.is_minter[minter] = status
+    log RoleMinterChanged(minter, status)
+
+
+@external
+def transfer_ownership(new_owner: address):
+    """
+    @dev Transfers the ownership of the contract
+         to a new account `new_owner`.
+    @notice Note that this function can only be
+            called by the current `owner`. Also,
+            the `new_owner` cannot be the zero address.
+
+            WARNING: The ownership transfer also removes
+            the previous owner's minter role and assigns
+            the minter role to `new_owner` accordingly.
+    @param new_owner The 20-byte address of the new owner.
+    """
+    self._check_owner()
+    assert new_owner != empty(address), "Ownable: new owner is the zero address"
+
+    self.is_minter[msg.sender] = False
+    log RoleMinterChanged(msg.sender, False)
+
+    self._transfer_ownership(new_owner)
+    self.is_minter[new_owner] = True
+    log RoleMinterChanged(new_owner, True)
+
+
+@external
+def renounce_ownership():
+    """
+    @dev Leaves the contract without an owner.
+    @notice Renouncing ownership will leave the
+            contract without an owner, thereby
+            removing any functionality that is
+            only available to the owner. Note
+            that the `owner` is also removed from
+            the list of allowed minters.
+
+            WARNING: All other existing `minter`
+            addresses will still be able to create
+            new tokens. Consider removing all non-owner
+            minter addresses first via `set_minter`
+            before calling `renounce_ownership`.
+    """
+    self._check_owner()
+    self.is_minter[msg.sender] = False
+    log RoleMinterChanged(msg.sender, False)
+    self._transfer_ownership(empty(address))
+
+
+@internal
+def _set_approval_for_all(owner: address, operator: address, approved: bool):
+    """
+    @dev Grants or revokes permission to `operator` to
+         transfer the `owner`'s tokens, according to `approved`.
+    @notice Note that `operator` cannot be the `owner`.
+    @param operator The 20-byte operator address.
+    @param approved The Boolean variable that sets the
+           approval status.
+    """
+    assert owner != operator, "ERC1155: setting approval status for self"
+    self.isApprovedForAll[owner][operator] = approved
+    log ApprovalForAll(owner, operator, approved)
+
+
+@internal
+def _safe_transfer_from(owner: address, to: address, id: uint256, amount: uint256, data: Bytes[1024]):
+    """
+    @dev Transfers `amount` tokens of token type `id` from
+         `owner` to `to`.
+    @notice Note that `to` cannot be the zero address. Also,
+            `owner` must have a balance of tokens of type `id`
+            of at least `amount`. Furthermore, if `to` refers
+            to a smart contract, it must implement {IERC1155Receiver-onERC1155Received}
+            and return the acceptance magic value.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount that is
+           being transferred.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert to != empty(address), "ERC1155: transfer to the zero address"
+
+    self._before_token_transfer(owner, to, self._as_singleton_array(id), self._as_singleton_array(amount), data)
+
+    owner_balance: uint256 = self._balances[id][owner]
+    assert owner_balance >= amount, "ERC1155: insufficient balance for transfer"
+    self._balances[id][owner] = unsafe_sub(owner_balance, amount)
+    # In the next line, an overflow is not possible
+    # due to an arithmetic check of the entire token
+    # supply in the functions `_safe_mint` and `_safe_mint_batch`.
+    self._balances[id][to] = unsafe_add(self._balances[id][to], amount)
+    log TransferSingle(msg.sender, owner, to, id, amount)
+
+    self._after_token_transfer(owner, to, self._as_singleton_array(id), self._as_singleton_array(amount), data)
+
+    assert self._check_on_erc1155_received(owner, to, id, amount, data), "ERC1155: transfer to non-ERC1155Receiver implementer"
+
+
+@internal
+def _safe_batch_transfer_from(owner: address, to: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE],
+                              data: Bytes[1024]):
+    """
+    @dev Batched version of `_safe_transfer_from`.
+    @notice Note that `ids` and `amounts` must have the
+            same length. Also, if `to` refers to a smart
+            contract, it must implement {IERC1155Receiver-onERC1155BatchReceived}
+            and return the acceptance magic value.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being transferred. Note that the order and length must
+           match the 32-byte `ids` array.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch"
+    assert to != empty(address), "ERC1155: transfer to the zero address"
+
+    self._before_token_transfer(owner, to, ids, amounts, data)
+
+    idx: uint256 = 0
+    for id in ids:
+        amount: uint256 = amounts[idx]
+        owner_balance: uint256 = self._balances[id][owner]
+        assert owner_balance >= amount, "ERC1155: insufficient balance for transfer"
+        self._balances[id][owner] = unsafe_sub(owner_balance, amount)
+        # In the next line, an overflow is not possible
+        # due to an arithmetic check of the entire token
+        # supply in the functions `_safe_mint` and `_safe_mint_batch`.
+        self._balances[id][to] = unsafe_add(self._balances[id][to], amount)
+        # The following line cannot overflow because we have
+        # limited the dynamic array `ids` by the `constant`
+        # parameter `_BATCH_SIZE`, which is bounded by the
+        # maximum value of `uint16`.
+        idx = unsafe_add(idx, 1)
+
+    log TransferBatch(msg.sender, owner, to, ids, amounts)
+
+    self._after_token_transfer(owner, to, ids, amounts, data)
+
+    assert self._check_on_erc1155_batch_received(owner, to, ids, amounts, data), "ERC1155: transfer to non-ERC1155Receiver implementer"
+
+
+@internal
+def _safe_mint(owner: address, id: uint256, amount: uint256, data: Bytes[1024]):
+    """
+    @dev Safely mints `amount` tokens of token type `id` and
+         transfers them to `owner`.
+    @notice This is an `internal` function without access
+            restriction. Note that `owner` cannot be the zero address.
+            Also, if `owner` refers to a smart contract, it must implement
+            {IERC1155Receiver-onERC1155Received}, which is called
+            upon a safe transfer.
+    @param owner The 20-byte owner address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount to be created.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert owner != empty(address), "ERC1155: mint to the zero address"
+
+    self._before_token_transfer(empty(address), owner, self._as_singleton_array(id), self._as_singleton_array(amount), data)
+
+    # In the next line, an overflow is not possible
+    # due to an arithmetic check of the entire token
+    # supply in the function `_before_token_transfer`.
+    self._balances[id][owner] = unsafe_add(self._balances[id][owner], amount)
+    log TransferSingle(msg.sender, empty(address), owner, id, amount)
+
+    self._after_token_transfer(empty(address), owner, self._as_singleton_array(id), self._as_singleton_array(amount), data)
+
+    assert self._check_on_erc1155_received(empty(address), owner, id, amount, data), "ERC1155: mint to non-ERC1155Receiver implementer"
+
+
+@internal
+def _safe_mint_batch(owner: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE], data: Bytes[1024]):
+    """
+    @dev Batched version of `_safe_mint`.
+    @notice Note that `ids` and `amounts` must have the
+            same length. Also, if `owner` refers to a smart contract,
+            it must implement {IERC1155Receiver-onERC1155BatchReceived},
+            which is called upon a safe transfer.
+    @param owner The 20-byte owner address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being created. Note that the order and length must
+           match the 32-byte `ids` array.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch"
+    assert owner != empty(address), "ERC1155: mint to the zero address"
+
+    self._before_token_transfer(empty(address), owner, ids, amounts, data)
+
+    idx: uint256 = 0
+    for id in ids:
+        # In the next line, an overflow is not possible
+        # due to an arithmetic check of the entire token
+        # supply in the function `_before_token_transfer`.
+        self._balances[id][owner] = unsafe_add(self._balances[id][owner], amounts[idx])
+        # The following line cannot overflow because we have
+        # limited the dynamic array `ids` by the `constant`
+        # parameter `_BATCH_SIZE`, which is bounded by the
+        # maximum value of `uint16`.
+        idx = unsafe_add(idx, 1)
+
+    log TransferBatch(msg.sender, empty(address), owner, ids, amounts)
+
+    self._after_token_transfer(empty(address), owner, ids, amounts, data)
+
+    assert self._check_on_erc1155_batch_received(empty(address), owner, ids, amounts, data), "ERC1155: transfer to non-ERC1155Receiver implementer"
+
+
+@internal
+def _set_uri(id: uint256, token_uri: String[432]):
+    """
+    @dev Sets the Uniform Resource Identifier (URI)
+         for token type `id`.
+    @notice This is an `internal` function without access
+            restriction. This function is decoupled from
+            `_mint`, as multiple of the same `id` can be
+            minted.
+    @param id The 32-byte identifier of the token.
+    @param token_uri The maximum 432-character user-readable
+           string URI for computing `uri`.
+    """
+    self._token_uris[id] = token_uri
+    log URI(IERC1155MetadataURI(self).uri(id), id)
+
+
+@internal
+def _burn(owner: address, id: uint256, amount: uint256):
+    """
+    @dev Destroys `amount` tokens of token type `id`
+         from `owner`.
+    @notice Note that `owner` cannot be the zero
+            address. Also, `owner` must have at least
+            `amount` tokens of token type `id`.
+    @param owner The 20-byte owner address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount to be destroyed.
+    """
+    assert owner != empty(address), "ERC1155: burn from the zero address"
+
+    self._before_token_transfer(owner, empty(address), self._as_singleton_array(id), self._as_singleton_array(amount), b"")
+
+    owner_balance: uint256 = self._balances[id][owner]
+    assert owner_balance >= amount, "ERC1155: burn amount exceeds balance"
+    self._balances[id][owner] = unsafe_sub(owner_balance, amount)
+    log TransferSingle(msg.sender, owner, empty(address), id, amount)
+
+    self._after_token_transfer(owner, empty(address), self._as_singleton_array(id), self._as_singleton_array(amount), b"")
+
+
+@internal
+def _burn_batch(owner: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE]):
+    """
+    @dev Batched version of `_burn`.
+    @notice Note that `ids` and `amounts` must have the
+            same length.
+    @param owner The 20-byte owner address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being destroyed. Note that the order and length must
+           match the 32-byte `ids` array.
+    """
+    assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch"
+    assert owner != empty(address), "ERC1155: burn from the zero address"
+
+    self._before_token_transfer(owner, empty(address), ids, amounts, b"")
+
+    idx: uint256 = 0
+    for id in ids:
+        amount: uint256 = amounts[idx]
+        owner_balance: uint256 = self._balances[id][owner]
+        assert owner_balance >= amount, "ERC1155: burn amount exceeds balance"
+        self._balances[id][owner] = unsafe_sub(owner_balance, amount)
+        # The following line cannot overflow because we have
+        # limited the dynamic array `ids` by the `constant`
+        # parameter `_BATCH_SIZE`, which is bounded by the
+        # maximum value of `uint16`.
+        idx = unsafe_add(idx, 1)
+
+    log TransferBatch(msg.sender, owner, empty(address), ids, amounts)
+
+    self._after_token_transfer(owner, empty(address), ids, amounts, b"")
+
+
+@internal
+def _check_on_erc1155_received(owner: address, to: address, id: uint256, amount: uint256, data: Bytes[1024]) -> bool:
+    """
+    @dev An `internal` function that invokes {IERC1155Receiver-onERC1155Received}
+         on a target address. The call is not executed
+         if the target address is not a contract.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param id The 32-byte identifier of the token.
+    @param amount The 32-byte token amount that is
+           being transferred.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    @return bool The verification whether the call correctly
+            returned the expected magic value.
+    """
+    # Contract case.
+    if (to.is_contract):
+        return_value: bytes4 = IERC1155Receiver(to).onERC1155Received(msg.sender, owner, id, amount, data)
+        assert return_value == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", output_type=bytes4),\
+                                         "ERC1155: transfer to non-ERC1155Receiver implementer"
+        return True
+    # EOA case.
+    else:
+        return True
+
+
+@internal
+def _check_on_erc1155_batch_received(owner: address, to: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE],
+                                     data: Bytes[1024]) -> bool:
+    """
+    @dev An `internal` function that invokes {IERC1155Receiver-onERC1155BatchReceived}
+         on a target address. The call is not executed
+         if the target address is not a contract.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param ids The 32-byte array of token identifiers.
+    @param amounts The 32-byte array of token amounts that are
+           being transferred.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    @return bool The verification whether the call correctly
+            returned the expected magic value.
+    """
+    # Contract case.
+    if (to.is_contract):
+        return_value: bytes4 = IERC1155Receiver(to).onERC1155BatchReceived(msg.sender, owner, ids, amounts, data)
+        assert return_value == method_id("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)", output_type=bytes4),\
+                                         "ERC1155: transfer to non-ERC1155Receiver implementer"
+        return True
+    # EOA case.
+    else:
+        return True
+
+
+@internal
+def _before_token_transfer(owner: address, to: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE],
+                           data: Bytes[1024]):
+    """
+    @dev Hook that is called before any token transfer.
+         This includes minting and burning, as well as
+         batched variants.
+    @notice Note that the same hook is called on both
+            single and batched variants. For single transfers,
+            the length of the `ids` and `amounts` arrays will
+            be 1. The calling conditions for each `id` and
+            `amount` pair are:
+            - when `owner` and `to` are both non-zero, `amount` of
+              `owner`'s tokens of token type `id` will be transferred
+              to `to`,
+            - when `owner` is zero, `amount` tokens of token type `id`
+              will be minted for `to`,
+            - when `to` is zero, `amount` of `owner`'s tokens of token
+              type `id` will be burned,
+            - `from` and `to` are never both zero,
+            - `ids` and `amounts` have the same, non-zero length.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being transferred. Note that the order and length must
+           match the 32-byte `ids` array.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    if (owner == empty(address)):
+        idx: uint256 = 0
+        for id in ids:
+            # The following line uses intentionally checked arithmetic
+            # to ensure that the total supply for each token type `id`
+            # never overflows.
+            self.total_supply[id] += amounts[idx]
+            # The following line cannot overflow because we have
+            # limited the dynamic array `ids` by the `constant`
+            # parameter `_BATCH_SIZE`, which is bounded by the
+            # maximum value of `uint16`.
+            idx = unsafe_add(idx, 1)
+
+    if (to == empty(address)):
+        idx: uint256 = 0
+        for id in ids:
+            amount: uint256 = amounts[idx]
+            supply: uint256 = self.total_supply[id]
+            assert supply >= amount, "ERC1155: burn amount exceeds total_supply"
+            self.total_supply[id] = unsafe_sub(supply, amount)
+            # The following line cannot overflow because we have
+            # limited the dynamic array `ids` by the `constant`
+            # parameter `_BATCH_SIZE`, which is bounded by the
+            # maximum value of `uint16`.
+            idx = unsafe_add(idx, 1)
+
+
+@internal
+def _after_token_transfer(owner: address, to: address, ids: DynArray[uint256, _BATCH_SIZE], amounts: DynArray[uint256, _BATCH_SIZE],
+                          data: Bytes[1024]):
+    """
+    @dev Hook that is called after any token transfer.
+         This includes minting and burning, as well as
+         batched variants.
+    @notice Note that the same hook is called on both
+            single and batched variants. For single transfers,
+            the length of the `ids` and `amounts` arrays will
+            be 1. The calling conditions for each `id` and
+            `amount` pair are:
+            - when `owner` and `to` are both non-zero, `amount` of
+              `owner`'s tokens of token type `id` will be transferred
+              to `to`,
+            - when `owner` is zero, `amount` tokens of token type `id`
+              will be minted for `to`,
+            - when `to` is zero, `amount` of `owner`'s tokens of token
+              type `id` will be burned,
+            - `from` and `to` are never both zero,
+            - `ids` and `amounts` have the same, non-zero length.
+    @param owner The 20-byte address which previously
+           owned the token.
+    @param to The 20-byte receiver address.
+    @param ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `amounts` array.
+    @param amounts The 32-byte array of token amounts that are
+           being transferred. Note that the order and length must
+           match the 32-byte `ids` array.
+    @param data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    pass
+
+
+@internal
+@pure
+def _as_singleton_array(element: uint256) -> DynArray[uint256, 1]:
+    """
+    @dev An `internal` helper function that converts a 32-byte
+         element into an array of length 1.
+    @param element The 32-byte non-array element.
+    @return DynArray The array of length 1 containing `element`.
+    """
+    return [element]
+
+
+@internal
+def _check_owner():
+    """
+    @dev Sourced from {Ownable-_check_owner}.
+    @notice See {Ownable-_check_owner} for
+            the function docstring.
+    """
+    assert msg.sender == self.owner, "Ownable: caller is not the owner"
+
+
+@internal
+def _transfer_ownership(new_owner: address):
+    """
+    @dev Sourced from {Ownable-_transfer_ownership}.
+    @notice See {Ownable-_transfer_ownership} for
+            the function docstring.
+    """
+    old_owner: address = self.owner
+    self.owner = new_owner
+    log OwnershipTransferred(old_owner, new_owner)
diff --git a/src/tokens/ERC20.vy b/src/tokens/ERC20.vy
index 57b799b4..6ea3f6f2 100644
--- a/src/tokens/ERC20.vy
+++ b/src/tokens/ERC20.vy
@@ -8,20 +8,20 @@
         - https://eips.ethereum.org/EIPS/eip-20.
         In addition, the following functions have
         been added for convenience:
-        - `name` (`external` function),
-        - `symbol` (`external` function),
-        - `decimals` (`external` function),
+        - `name` (`external` `view` function),
+        - `symbol` (`external` `view` function),
+        - `decimals` (`external` `view` function),
         - `increase_allowance` (`external` function),
         - `decrease_allowance` (`external` function),
         - `burn` (`external` function),
         - `burn_from` (`external` function),
-        - `is_minter` (`external` function),
+        - `is_minter` (`external` `view` function),
         - `mint` (`external` function),
         - `set_minter` (`external` function),
         - `permit` (`external` function),
-        - `nonces` (`external` function),
-        - `DOMAIN_SEPARATOR` (`external` function),
-        - `owner` (`external` function),
+        - `nonces` (`external` `view` function),
+        - `DOMAIN_SEPARATOR` (`external` `view` function),
+        - `owner` (`external` `view` function),
         - `transfer_ownership` (`external` function),
         - `renounce_ownership` (`external` function),
         - `_before_token_transfer` (`internal` function),
@@ -197,9 +197,14 @@ def __init__(name_: String[25], symbol_: String[5], initial_supply_: uint256, na
 
     self._transfer_ownership(msg.sender)
     self.is_minter[msg.sender] = True
+    log RoleMinterChanged(msg.sender, True)
+
     if (initial_supply != empty(uint256)):
-        self.balanceOf[msg.sender] = initial_supply
+        self._before_token_transfer(empty(address), msg.sender, initial_supply)
         self.totalSupply = initial_supply
+        self.balanceOf[msg.sender] = initial_supply
+        log Transfer(empty(address), msg.sender, initial_supply)
+        self._after_token_transfer(empty(address), msg.sender, initial_supply)
 
     hashed_name: bytes32 = keccak256(convert(name_eip712_, Bytes[50]))
     hashed_version: bytes32 = keccak256(convert(version_eip712_, Bytes[20]))
@@ -437,13 +442,26 @@ def DOMAIN_SEPARATOR() -> bytes32:
 @external
 def transfer_ownership(new_owner: address):
     """
-    @dev Sourced from {Ownable-transfer_ownership}.
-    @notice See {Ownable-transfer_ownership} for
-            the function docstring.
+    @dev Transfers the ownership of the contract
+         to a new account `new_owner`.
+    @notice Note that this function can only be
+            called by the current `owner`. Also,
+            the `new_owner` cannot be the zero address.
+
+            WARNING: The ownership transfer also removes
+            the previous owner's minter role and assigns
+            the minter role to `new_owner` accordingly.
+    @param new_owner The 20-byte address of the new owner.
     """
     self._check_owner()
     assert new_owner != empty(address), "Ownable: new owner is the zero address"
+
+    self.is_minter[msg.sender] = False
+    log RoleMinterChanged(msg.sender, False)
+
     self._transfer_ownership(new_owner)
+    self.is_minter[new_owner] = True
+    log RoleMinterChanged(new_owner, True)
 
 
 @external
@@ -465,6 +483,7 @@ def renounce_ownership():
     """
     self._check_owner()
     self.is_minter[msg.sender] = False
+    log RoleMinterChanged(msg.sender, False)
     self._transfer_ownership(empty(address))
 
 
diff --git a/src/tokens/ERC721.vy b/src/tokens/ERC721.vy
index d4ae60fc..f3eae966 100644
--- a/src/tokens/ERC721.vy
+++ b/src/tokens/ERC721.vy
@@ -8,20 +8,20 @@
         - https://eips.ethereum.org/EIPS/eip-721.
         In addition, the following functions have
         been added for convenience:
-        - `name` (`external` function),
-        - `symbol` (`external` function),
-        - `tokenURI` (`external` function),
-        - `totalSupply` (`external` function),
-        - `tokenByIndex` (`external` function),
-        - `tokenOfOwnerByIndex` (`external` function),
+        - `name` (`external` `view` function),
+        - `symbol` (`external` `view` function),
+        - `tokenURI` (`external` `view` function),
+        - `totalSupply` (`external` `view` function),
+        - `tokenByIndex` (`external` `view` function),
+        - `tokenOfOwnerByIndex` (`external` `view` function),
         - `burn` (`external` function),
-        - `is_minter` (`external` function),
+        - `is_minter` (`external` `view` function),
         - `safe_mint` (`external` function),
         - `set_minter` (`external` function),
         - `permit` (`external` function),
-        - `nonces` (`external` function),
-        - `DOMAIN_SEPARATOR` (`external` function),
-        - `owner` (`external` function),
+        - `nonces` (`external` `view` function),
+        - `DOMAIN_SEPARATOR` (`external` `view` function),
+        - `owner` (`external` `view` function),
         - `transfer_ownership` (`external` function),
         - `renounce_ownership` (`external` function),
         - `_check_on_erc721_received` (`internal` function),
@@ -83,8 +83,8 @@ import interfaces.IERC721Receiver as IERC721Receiver
 _SUPPORTED_INTERFACES: constant(bytes4[5]) = [
     0x01FFC9A7, # The ERC-165 identifier for ERC-165.
     0x80AC58CD, # The ERC-165 identifier for ERC-721.
-    0x5B5E139F, # The ERC-165 identifier for ERC-721 metadata extension.
-    0x780E9D63, # The ERC-165 identifier for ERC-721 enumeration extension.
+    0x5B5E139F, # The ERC-165 identifier for the ERC-721 metadata extension.
+    0x780E9D63, # The ERC-165 identifier for the ERC-721 enumeration extension.
     0x589C5CE2, # The ERC-165 identifier for ERC-4494.
 ]
 
@@ -268,6 +268,7 @@ def __init__(name_: String[25], symbol_: String[5], base_uri_: String[80], name_
 
     self._transfer_ownership(msg.sender)
     self.is_minter[msg.sender] = True
+    log RoleMinterChanged(msg.sender, True)
 
     hashed_name: bytes32 = keccak256(convert(name_eip712_, Bytes[50]))
     hashed_version: bytes32 = keccak256(convert(version_eip712_, Bytes[20]))
@@ -281,7 +282,7 @@ def __init__(name_: String[25], symbol_: String[5], base_uri_: String[80], name_
 
 
 @external
-@pure
+@view
 def supportsInterface(interface_id: bytes4) -> bool:
     """
     @dev Returns `True` if this contract implements the
@@ -622,13 +623,26 @@ def DOMAIN_SEPARATOR() -> bytes32:
 @external
 def transfer_ownership(new_owner: address):
     """
-    @dev Sourced from {Ownable-transfer_ownership}.
-    @notice See {Ownable-transfer_ownership} for
-            the function docstring.
+    @dev Transfers the ownership of the contract
+         to a new account `new_owner`.
+    @notice Note that this function can only be
+            called by the current `owner`. Also,
+            the `new_owner` cannot be the zero address.
+
+            WARNING: The ownership transfer also removes
+            the previous owner's minter role and assigns
+            the minter role to `new_owner` accordingly.
+    @param new_owner The 20-byte address of the new owner.
     """
     self._check_owner()
     assert new_owner != empty(address), "Ownable: new owner is the zero address"
+
+    self.is_minter[msg.sender] = False
+    log RoleMinterChanged(msg.sender, False)
+
     self._transfer_ownership(new_owner)
+    self.is_minter[new_owner] = True
+    log RoleMinterChanged(new_owner, True)
 
 
 @external
@@ -650,6 +664,7 @@ def renounce_ownership():
     """
     self._check_owner()
     self.is_minter[msg.sender] = False
+    log RoleMinterChanged(msg.sender, False)
     self._transfer_ownership(empty(address))
 
 
@@ -718,39 +733,39 @@ def _is_approved_or_owner(spender: address, token_id: uint256) -> bool:
 
 
 @internal
-def _safe_mint(to: address, token_id: uint256, data: Bytes[1024]):
+def _safe_mint(owner: address, token_id: uint256, data: Bytes[1024]):
     """
-    @dev Safely mints `token_id` and transfers it to `to`.
-    @notice Note that `token_id` must not exist. Also, if `to`
+    @dev Safely mints `token_id` and transfers it to `owner`.
+    @notice Note that `token_id` must not exist. Also, if `owner`
             refers to a smart contract, it must implement
             {IERC721Receiver-onERC721Received}, which is called
             upon a safe transfer.
-    @param to The 20-byte receiver address.
+    @param owner The 20-byte owner address.
     @param token_id The 32-byte identifier of the token.
     @param data The maximum 1024-byte additional data
            with no specified format that is sent
-           to `to`.
+           to `owner`.
     """
-    self._mint(to, token_id)
-    assert self._check_on_erc721_received(empty(address), to, token_id, data), "ERC721: transfer to non-ERC721Receiver implementer"
+    self._mint(owner, token_id)
+    assert self._check_on_erc721_received(empty(address), owner, token_id, data), "ERC721: transfer to non-ERC721Receiver implementer"
 
 
 @internal
-def _mint(to: address, token_id: uint256):
+def _mint(owner: address, token_id: uint256):
     """
-    @dev Mints `token_id` and transfers it to `to`.
+    @dev Mints `token_id` and transfers it to `owner`.
     @notice Note that `token_id` must not exist and
-            `to` cannot be the zero address.
+            `owner` cannot be the zero address.
 
             WARNING: Usage of this method is discouraged,
             use `_safe_mint` whenever possible.
-    @param to The 20-byte receiver address.
+    @param owner The 20-byte owner address.
     @param token_id The 32-byte identifier of the token.
     """
-    assert to != empty(address), "ERC721: mint to the zero address"
+    assert owner != empty(address), "ERC721: mint to the zero address"
     assert not(self._exists(token_id)), "ERC721: token already minted"
 
-    self._before_token_transfer(empty(address), to, token_id)
+    self._before_token_transfer(empty(address), owner, token_id)
     # Checks that the `token_id` was not minted by the
     # `_before_token_transfer` hook.
     assert not(self._exists(token_id)), "ERC721: token already minted"
@@ -760,11 +775,11 @@ def _mint(to: address, token_id: uint256):
     # However, since we have bounded the dynamic array
     # `_all_tokens` by the maximum value of `uint64`,
     # this is no longer even theoretically possible.
-    self._balances[to] = unsafe_add(self._balances[to], 1)
-    self._owners[token_id] = to
-    log Transfer(empty(address), to, token_id)
+    self._balances[owner] = unsafe_add(self._balances[owner], 1)
+    self._owners[token_id] = owner
+    log Transfer(empty(address), owner, token_id)
 
-    self._after_token_transfer(empty(address), to, token_id)
+    self._after_token_transfer(empty(address), owner, token_id)
 
 
 @internal
diff --git a/src/tokens/interfaces/IERC1155.vy b/src/tokens/interfaces/IERC1155.vy
new file mode 100644
index 00000000..a211bfb3
--- /dev/null
+++ b/src/tokens/interfaces/IERC1155.vy
@@ -0,0 +1,193 @@
+# @version ^0.3.7
+"""
+@title EIP-1155 Interface Definition
+@license GNU Affero General Public License v3.0
+@author pcaversaccio
+@notice The required interface definition of an ERC-1155
+        compliant smart contract as defined in:
+        https://eips.ethereum.org/EIPS/eip-1155. Note that
+        smart contracts implementing the ERC-1155 standard
+        must implement all of the functions in the ERC-1155
+        interface. For more details, please refer to:
+        https://eips.ethereum.org/EIPS/eip-1155#specification.
+
+        Note that Vyper interfaces that implement functions
+        with return values that require an upper bound (e.g.
+        `Bytes`, `DynArray`, or `String`), the upper bound
+        defined in the interface represents the lower bound
+        of the implementation:
+        https://github.com/vyperlang/vyper/pull/3205.
+
+        On how to use interfaces in Vyper, please visit:
+        https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.
+"""
+
+
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+# @dev Emitted when `_value` tokens of token type
+# `_id` are transferred from `_from` to `_to` by
+# `_operator`.
+event TransferSingle:
+    _operator: indexed(address)
+    _from: indexed(address)
+    _to: indexed(address)
+    _id: uint256
+    _value: uint256
+
+
+# @dev Equivalent to multiple `TransferSingle` events,
+# where `_operator`, `_from`, and `_to` are the same
+# for all transfers.
+event TransferBatch:
+    _operator: indexed(address)
+    _from: indexed(address)
+    _to: indexed(address)
+    _ids: DynArray[uint256, 128]
+    _values: DynArray[uint256, 128]
+
+
+# @dev Emitted when `_owner` grants or revokes permission
+# to `_operator` to transfer their tokens, according to
+# `_approved`.
+event ApprovalForAll:
+    _owner: indexed(address)
+    _operator: indexed(address)
+    _approved: bool
+
+
+# @dev Emitted when the Uniform Resource Identifier (URI)
+# for token type `_id` changes to `_value`, if it is a
+# non-programmatic URI. Note that if an `URI` event was
+# emitted for `_id`, the EIP-1155 standard guarantees that
+# `_value` will equal the value returned by {IERC1155MetadataURI-uri}.
+event URI:
+    _value: String[512]
+    _id: indexed(uint256)
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @notice For more details on how these identifiers are
+            created, please refer to:
+            https://eips.ethereum.org/EIPS/eip-165.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return empty(bool)
+
+
+@external
+def safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: Bytes[1024]):
+    """
+    @dev Transfers `_value` tokens of token type `_id` from
+         `_from` to `_to`.
+    @notice Note that `_to` cannot be the zero address. Also,
+            if the caller is not `_from`, it must have been
+            approved to spend `_from`'s tokens via `setApprovalForAll`.
+            Furthermore, `_from` must have a balance of tokens
+            of type `_id` of at least `_value`. Eventually, if
+            `_to` refers to a smart contract, it must implement
+            {IERC1155Receiver-onERC1155Received} and return the
+            acceptance magic value.
+    @param _from The 20-byte address which previously
+           owned the token.
+    @param _to The 20-byte receiver address.
+    @param _id The 32-byte identifier of the token.
+    @param _value The 32-byte token amount that is
+           being transferred.
+    @param _data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    pass
+
+
+@external
+def safeBatchTransferFrom(_from: address, _to: address, _ids: DynArray[uint256, 128], _values: DynArray[uint256, 128], _data: Bytes[1024]):
+    """
+    @dev Batched version of `safeTransferFrom`.
+    @notice Note that `_ids` and `_values` must have the
+            same length. Also, if `_to` refers to a smart
+            contract, it must implement {IERC1155Receiver-onERC1155BatchReceived}
+            and return the acceptance magic value.
+    @param _from The 20-byte address which previously
+           owned the token.
+    @param _to The 20-byte receiver address.
+    @param _ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `_values` array.
+    @param _values The 32-byte array of token amounts that are being
+           transferred. Note that the order and length must match
+           the 32-byte `_ids` array.
+    @param _data The maximum 1024-byte additional data
+           with no specified format.
+    """
+    pass
+
+
+@external
+@view
+def balanceOf(_owner: address, _id: uint256) -> uint256:
+    """
+    @dev Returns the amount of tokens of token type
+         `_id` owned by `_owner`.
+    @notice Note that `_owner` cannot be the zero
+            address.
+    @param _owner The 20-byte owner address.
+    @param _id The 32-byte identifier of the token.
+    @return uint256 The 32-byte token amount owned
+            by `_owner`.
+    """
+    return empty(uint256)
+
+
+@external
+@view
+def balanceOfBatch(_owners: DynArray[address, 128], _ids: DynArray[uint256, 128]) -> DynArray[uint256, 128]:
+    """
+    @dev Batched version of `balanceOf`.
+    @notice Note that `_owners` and `_ids` must have the
+            same length.
+    @param _owners The 20-byte array of owner addresses.
+    @param _ids The 32-byte array of token identifiers.
+    @return DynArray The 32-byte array of token amounts
+            owned by `_owners`.
+    """
+    return empty(DynArray[uint256, 128])
+
+
+@external
+def setApprovalForAll(_operator: address, _approved: bool):
+    """
+    @dev Grants or revokes permission to `_operator` to
+         transfer the caller's tokens, according to `_approved`.
+    @notice Note that `_operator` cannot be the caller.
+    @param _operator The 20-byte operator address.
+    @param _approved The Boolean variable that sets the
+           approval status.
+    """
+    pass
+
+
+@external
+@view
+def isApprovedForAll(_owner: address, _operator: address) -> bool:
+    """
+    @dev Returns `True` if `_operator` is approved to transfer
+         `_owner`'s tokens.
+    @notice Note that `_operator` cannot be the caller.
+    @param _owner The 20-byte owner address.
+    @param _operator The 20-byte operator address.
+    @return bool The verification whether `_operator` is approved
+            or not.
+    """
+    return empty(bool)
diff --git a/src/tokens/interfaces/IERC1155MetadataURI.vy b/src/tokens/interfaces/IERC1155MetadataURI.vy
new file mode 100644
index 00000000..349e1fd1
--- /dev/null
+++ b/src/tokens/interfaces/IERC1155MetadataURI.vy
@@ -0,0 +1,63 @@
+# @version ^0.3.7
+"""
+@title EIP-1155 Optional Metadata Interface Definition
+@license GNU Affero General Public License v3.0
+@author pcaversaccio
+@notice The metadata extension is optional for an ERC-1155
+        smart contract. This allows a smart contract to
+        be interrogated for the Uniform Resource Identifier
+        (URI) details of a specific token type. For more
+        details, please refer to:
+        https://eips.ethereum.org/EIPS/eip-1155#metadata.
+
+        Note that Vyper interfaces that implement functions
+        with return values that require an upper bound (e.g.
+        `Bytes`, `DynArray`, or `String`), the upper bound
+        defined in the interface represents the lower bound
+        of the implementation:
+        https://github.com/vyperlang/vyper/pull/3205.
+
+        On how to use interfaces in Vyper, please visit:
+        https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.
+"""
+
+
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @notice For more details on how these identifiers are
+            created, please refer to:
+            https://eips.ethereum.org/EIPS/eip-165.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return empty(bool)
+
+
+@external
+@view
+def uri(_id: uint256) -> String[512]:
+    """
+    @dev Returns the Uniform Resource Identifier (URI)
+         for token type `_id`.
+    @notice If the `_id` substring is present in the URI,
+            it must be replaced by clients with the actual
+            token type ID. Note that the `uri` function must
+            not be used to check for the existence of a token
+            as it is possible for an implementation to return
+            a valid string even if the token does not exist.
+    @param _id The 32-byte identifier of the token type `_id`.
+    @return String The maximum 512-character user-readable
+            string token URI of the token type `_id`.
+    """
+    return empty(String[512])
diff --git a/src/tokens/interfaces/IERC1155Receiver.vy b/src/tokens/interfaces/IERC1155Receiver.vy
new file mode 100644
index 00000000..1188d98f
--- /dev/null
+++ b/src/tokens/interfaces/IERC1155Receiver.vy
@@ -0,0 +1,88 @@
+# @version ^0.3.7
+"""
+@title EIP-1155 Token Receiver Interface Definition
+@license GNU Affero General Public License v3.0
+@author pcaversaccio
+@notice The interface definition for any contract
+        that wants to support safe transfers from
+        ERC-1155 asset contracts. For more details,
+        please refer to:
+        https://eips.ethereum.org/EIPS/eip-1155#erc-1155-token-receiver.
+
+        On how to use interfaces in Vyper, please visit:
+        https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.
+"""
+
+
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @notice For more details on how these identifiers are
+            created, please refer to:
+            https://eips.ethereum.org/EIPS/eip-165.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return empty(bool)
+
+
+@external
+def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256, _data: Bytes[1024]) -> bytes4:
+    """
+    @dev Handles the receipt of a single ERC-1155 token type.
+         This function is called at the end of a `safeTransferFrom`
+         after the balance has been updated.
+    @notice It must return its function selector to
+            confirm the token transfer. If any other value
+            is returned or the interface is not implemented
+            by the recipient, the transfer will be reverted.
+    @param _operator The 20-byte address which called
+           the `safeTransferFrom` function.
+    @param _from The 20-byte address which previously
+           owned the token.
+    @param _id The 32-byte identifier of the token.
+    @param _value The 32-byte token amount that is
+           being transferred.
+    @param _data The maximum 1024-byte additional data
+           with no specified format.
+    @return bytes4 The 4-byte function selector of `onERC1155Received`.
+    """
+    return empty(bytes4)
+
+
+@external
+def onERC1155BatchReceived(_operator: address, _from: address, _ids: DynArray[uint256, 65535], _values: DynArray[uint256, 65535],
+                           _data: Bytes[1024]) -> bytes4:
+    """
+    @dev Handles the receipt of multiple ERC-1155 token types.
+         This function is called at the end of a `safeBatchTransferFrom`
+         after the balances have been updated.
+    @notice It must return its function selector to
+            confirm the token transfers. If any other value
+            is returned or the interface is not implemented
+            by the recipient, the transfers will be reverted.
+    @param _operator The 20-byte address which called
+           the `safeBatchTransferFrom` function.
+    @param _from The 20-byte address which previously
+           owned the tokens.
+    @param _ids The 32-byte array of token identifiers. Note
+           that the order and length must match the 32-byte
+           `_values` array.
+    @param _values The 32-byte array of token amounts that are
+           being transferred. Note that the order and length must
+           match the 32-byte `_ids` array.
+    @param _data The maximum 1024-byte additional data
+           with no specified format.
+    @return bytes4 The 4-byte function selector of `onERC1155BatchReceived`.
+    """
+    return empty(bytes4)
diff --git a/src/tokens/interfaces/IERC721Enumerable.vy b/src/tokens/interfaces/IERC721Enumerable.vy
index ed5e140f..3fd972b7 100644
--- a/src/tokens/interfaces/IERC721Enumerable.vy
+++ b/src/tokens/interfaces/IERC721Enumerable.vy
@@ -6,12 +6,36 @@
 @notice The enumeration extension is optional for an ERC-721
         smart contract. This allows a contract to publish its
         full list of ERC-721 tokens and make them discoverable.
+        For more details, please refer to:
+        https://eips.ethereum.org/EIPS/eip-721#specification.
 
         On how to use interfaces in Vyper, please visit:
         https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.
 """
 
 
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @notice For more details on how these identifiers are
+            created, please refer to:
+            https://eips.ethereum.org/EIPS/eip-165.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return empty(bool)
+
+
 @external
 @view
 def totalSupply() -> uint256:
diff --git a/src/tokens/interfaces/IERC721Metadata.vy b/src/tokens/interfaces/IERC721Metadata.vy
index a4a66f11..3b9daffb 100644
--- a/src/tokens/interfaces/IERC721Metadata.vy
+++ b/src/tokens/interfaces/IERC721Metadata.vy
@@ -8,13 +8,42 @@
         be interrogated for its name and for details about
         the asset(s) which a non-fungible token (NFT)
         represents. For more details, please refer to:
-        https://eips.ethereum.org/EIPS/eip-721.
+        https://eips.ethereum.org/EIPS/eip-721#specification.
+
+        Note that Vyper interfaces that implement functions
+        with return values that require an upper bound (e.g.
+        `Bytes`, `DynArray`, or `String`), the upper bound
+        defined in the interface represents the lower bound
+        of the implementation:
+        https://github.com/vyperlang/vyper/pull/3205.
 
         On how to use interfaces in Vyper, please visit:
         https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.
 """
 
 
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @notice For more details on how these identifiers are
+            created, please refer to:
+            https://eips.ethereum.org/EIPS/eip-165.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return empty(bool)
+
+
 @external
 @view
 def name() -> String[25]:
diff --git a/src/tokens/interfaces/IERC721Permit.vy b/src/tokens/interfaces/IERC721Permit.vy
index e1b05741..3e93d469 100644
--- a/src/tokens/interfaces/IERC721Permit.vy
+++ b/src/tokens/interfaces/IERC721Permit.vy
@@ -27,6 +27,28 @@
 """
 
 
+# @dev We import and implement the `ERC165` interface,
+# which is a built-in interface of the Vyper compiler.
+from vyper.interfaces import ERC165
+implements: ERC165
+
+
+@external
+@view
+def supportsInterface(interface_id: bytes4) -> bool:
+    """
+    @dev Returns `True` if this contract implements the
+         interface defined by `interface_id`.
+    @notice For more details on how these identifiers are
+            created, please refer to:
+            https://eips.ethereum.org/EIPS/eip-165.
+    @param interface_id The 4-byte interface identifier.
+    @return bool The verification whether the contract
+            implements the interface or not.
+    """
+    return empty(bool)
+
+
 @external
 def permit(spender: address, tokenId: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32):
     """
diff --git a/src/tokens/interfaces/IERC721Receiver.vy b/src/tokens/interfaces/IERC721Receiver.vy
index 8e95ef7c..766021e2 100644
--- a/src/tokens/interfaces/IERC721Receiver.vy
+++ b/src/tokens/interfaces/IERC721Receiver.vy
@@ -5,7 +5,9 @@
 @author pcaversaccio
 @notice The interface definition for any contract
         that wants to support safe transfers from
-        ERC-721 asset contracts.
+        ERC-721 asset contracts. For more details,
+        please refer to:
+        https://eips.ethereum.org/EIPS/eip-721#specification.
 
         On how to use interfaces in Vyper, please visit:
         https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.


Test patch:
diff --git a/test/tokens/ERC1155.t.sol b/test/tokens/ERC1155.t.sol
new file mode 100644
index 00000000..f5637d28
--- /dev/null
+++ b/test/tokens/ERC1155.t.sol
@@ -0,0 +1,2319 @@
+// SPDX-License-Identifier: WTFPL
+pragma solidity ^0.8.17;
+
+import {Test} from "forge-std/Test.sol";
+import {VyperDeployer} from "utils/VyperDeployer.sol";
+
+import {IERC165} from "openzeppelin/utils/introspection/IERC165.sol";
+import {IERC1155} from "openzeppelin/token/ERC1155/IERC1155.sol";
+import {IERC1155Receiver} from "openzeppelin/token/ERC1155/IERC1155Receiver.sol";
+import {IERC1155MetadataURI} from "openzeppelin/token/ERC1155/extensions/IERC1155MetadataURI.sol";
+
+import {Strings} from "openzeppelin/utils/Strings.sol";
+import {ERC1155ReceiverMock} from "./mocks/ERC1155ReceiverMock.sol";
+
+import {IERC1155Extended} from "./interfaces/IERC1155Extended.sol";
+
+contract ERC1155Test is Test {
+    string private constant _BASE_URI = "https://www.wagmi.xyz/";
+
+    VyperDeployer private vyperDeployer = new VyperDeployer();
+
+    // solhint-disable-next-line var-name-mixedcase
+    IERC1155Extended private ERC1155Extended;
+    // solhint-disable-next-line var-name-mixedcase
+    IERC1155Extended private ERC1155ExtendedNoBaseURI;
+
+    event TransferSingle(
+        address indexed operator,
+        address indexed from,
+        address indexed to,
+        uint256 id,
+        uint256 amount
+    );
+
+    event TransferBatch(
+        address indexed operator,
+        address indexed from,
+        address indexed to,
+        uint256[] ids,
+        uint256[] amounts
+    );
+
+    event ApprovalForAll(
+        address indexed owner,
+        address indexed operator,
+        bool approved
+    );
+
+    event URI(string value, uint256 indexed id);
+
+    event Received(
+        address indexed operator,
+        address indexed from,
+        uint256 id,
+        uint256 amount,
+        bytes data
+    );
+
+    event BatchReceived(
+        address indexed operator,
+        address indexed from,
+        uint256[] ids,
+        uint256[] amounts,
+        bytes data
+    );
+
+    event OwnershipTransferred(
+        address indexed previousOwner,
+        address indexed newOwner
+    );
+
+    event RoleMinterChanged(address indexed minter, bool status);
+
+    function setUp() public {
+        bytes memory args = abi.encode(_BASE_URI);
+        ERC1155Extended = IERC1155Extended(
+            vyperDeployer.deployContract("src/tokens/", "ERC1155", args)
+        );
+    }
+
+    function testInitialSetup() public {
+        address deployer = address(vyperDeployer);
+        assertTrue(ERC1155Extended.owner() == deployer);
+        assertTrue(ERC1155Extended.is_minter(deployer));
+        assertEq(
+            ERC1155Extended.uri(0),
+            string.concat(_BASE_URI, Strings.toString(uint256(0)))
+        );
+        assertEq(
+            ERC1155Extended.uri(1),
+            string.concat(_BASE_URI, Strings.toString(uint256(1)))
+        );
+    }
+
+    function testSupportsInterfaceSuccess() public {
+        assertTrue(
+            ERC1155Extended.supportsInterface(type(IERC165).interfaceId)
+        );
+        assertTrue(
+            ERC1155Extended.supportsInterface(type(IERC1155).interfaceId)
+        );
+        assertTrue(
+            ERC1155Extended.supportsInterface(
+                type(IERC1155MetadataURI).interfaceId
+            )
+        );
+    }
+
+    function testSupportsInterfaceGasCost() public {
+        uint256 startGas = gasleft();
+        ERC1155Extended.supportsInterface(type(IERC165).interfaceId);
+        uint256 gasUsed = startGas - gasleft();
+        assertTrue(gasUsed < 30_000);
+    }
+
+    function testSupportsInterfaceInvalidInterfaceId() public {
+        assertTrue(!ERC1155Extended.supportsInterface(0x0011bbff));
+    }
+
+    function testBalanceOfCase1() public {
+        address deployer = address(vyperDeployer);
+        address firstOwner = vm.addr(1);
+        address secondOwner = vm.addr(2);
+        uint256 id1 = 0;
+        uint256 amountFirstOwner = 1;
+        uint256 id2 = 1;
+        uint256 amountSecondOwner = 20;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(firstOwner, id1, amountFirstOwner, data);
+        ERC1155Extended.safe_mint(secondOwner, id2, amountSecondOwner, data);
+        assertEq(ERC1155Extended.balanceOf(firstOwner, id1), amountFirstOwner);
+        assertEq(
+            ERC1155Extended.balanceOf(secondOwner, id2),
+            amountSecondOwner
+        );
+        assertEq(ERC1155Extended.balanceOf(firstOwner, 2), 0);
+        vm.stopPrank();
+    }
+
+    function testBalanceOfCase2() public {
+        assertEq(ERC1155Extended.balanceOf(vm.addr(1), 0), 0);
+        assertEq(ERC1155Extended.balanceOf(vm.addr(2), 1), 0);
+        assertEq(ERC1155Extended.balanceOf(vm.addr(3), 2), 0);
+    }
+
+    function testBalanceOfZeroAddress() public {
+        vm.expectRevert(bytes("ERC1155: address zero is not a valid owner"));
+        ERC1155Extended.balanceOf(address(0), 0);
+    }
+
+    function testBalanceOfBatchCase1() public {
+        address deployer = address(vyperDeployer);
+        address firstOwner = vm.addr(1);
+        address secondOwner = vm.addr(2);
+        address[] memory owners = new address[](6);
+        uint256[] memory ids = new uint256[](6);
+        uint256[] memory amounts = new uint256[](6);
+        bytes memory data = new bytes(0);
+
+        owners[0] = firstOwner;
+        owners[1] = firstOwner;
+        owners[2] = secondOwner;
+        owners[3] = secondOwner;
+        owners[4] = firstOwner;
+        owners[5] = secondOwner;
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 2;
+        ids[3] = 3;
+        ids[4] = 4;
+        ids[5] = 5;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+        amounts[4] = 0;
+        amounts[5] = 0;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owners[0], ids[0], amounts[0], data);
+        ERC1155Extended.safe_mint(owners[1], ids[1], amounts[1], data);
+        ERC1155Extended.safe_mint(owners[2], ids[2], amounts[2], data);
+        ERC1155Extended.safe_mint(owners[3], ids[3], amounts[3], data);
+        uint256[] memory balances = ERC1155Extended.balanceOfBatch(owners, ids);
+        assertEq(balances.length, 6);
+        for (uint256 i; i < balances.length; ++i) {
+            assertEq(balances[i], amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testBalanceOfBatchCase2() public {
+        address deployer = address(vyperDeployer);
+        address firstOwner = vm.addr(1);
+        address secondOwner = vm.addr(2);
+        address[] memory owners = new address[](6);
+        uint256[] memory ids = new uint256[](6);
+        uint256[] memory amounts = new uint256[](6);
+        bytes memory data = new bytes(0);
+
+        owners[0] = firstOwner;
+        owners[1] = firstOwner;
+        owners[2] = owners[0];
+        owners[3] = secondOwner;
+        owners[4] = firstOwner;
+        owners[5] = owners[3];
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = ids[0];
+        ids[3] = 3;
+        ids[4] = 4;
+        ids[5] = ids[3];
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = amounts[0];
+        amounts[3] = 20;
+        amounts[4] = 0;
+        amounts[5] = amounts[3];
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owners[0], ids[0], amounts[0] - 1, data);
+        ERC1155Extended.safe_mint(owners[1], ids[1], amounts[1], data);
+        ERC1155Extended.safe_mint(owners[2], ids[2], amounts[2], data);
+        ERC1155Extended.safe_mint(owners[3], ids[3], amounts[3], data);
+        uint256[] memory balances = ERC1155Extended.balanceOfBatch(owners, ids);
+        assertEq(balances.length, 6);
+        for (uint256 i; i < balances.length; ++i) {
+            assertEq(balances[i], amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testBalanceOfBatchCase3() public {
+        address firstOwner = vm.addr(1);
+        address secondOwner = vm.addr(2);
+        address[] memory owners = new address[](6);
+        uint256[] memory ids = new uint256[](6);
+
+        owners[0] = firstOwner;
+        owners[1] = firstOwner;
+        owners[2] = secondOwner;
+        owners[3] = secondOwner;
+        owners[4] = firstOwner;
+        owners[5] = secondOwner;
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 2;
+        ids[3] = 3;
+        ids[4] = 4;
+        ids[5] = 5;
+
+        uint256[] memory balances = ERC1155Extended.balanceOfBatch(owners, ids);
+        assertEq(balances.length, 6);
+        for (uint256 i; i < balances.length; ++i) {
+            assertEq(balances[i], 0);
+        }
+    }
+
+    function testBalanceOfBatchLengthsMismatch() public {
+        address[] memory owners1 = new address[](2);
+        uint256[] memory ids1 = new uint256[](1);
+        owners1[0] = vm.addr(1);
+        owners1[1] = vm.addr(2);
+        ids1[0] = 0;
+        vm.expectRevert(bytes("ERC1155: owners and ids length mismatch"));
+        ERC1155Extended.balanceOfBatch(owners1, ids1);
+
+        address[] memory owners2 = new address[](1);
+        uint256[] memory ids2 = new uint256[](2);
+        owners2[0] = vm.addr(3);
+        ids2[0] = 0;
+        ids2[1] = 1;
+        vm.expectRevert(bytes("ERC1155: owners and ids length mismatch"));
+        ERC1155Extended.balanceOfBatch(owners2, ids2);
+    }
+
+    function testBalanceOfBatchZeroAddress() public {
+        address[] memory owners = new address[](1);
+        uint256[] memory ids = new uint256[](1);
+        owners[0] = address(0);
+        ids[0] = 0;
+        vm.expectRevert(bytes("ERC1155: address zero is not a valid owner"));
+        ERC1155Extended.balanceOfBatch(owners, ids);
+    }
+
+    function testSetApprovalForAllSuccess() public {
+        address owner = vm.addr(1);
+        address operator = vm.addr(2);
+        bool approved = true;
+        assertTrue(!ERC1155Extended.isApprovedForAll(owner, operator));
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        assertTrue(ERC1155Extended.isApprovedForAll(owner, operator));
+
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        assertTrue(ERC1155Extended.isApprovedForAll(owner, operator));
+        vm.stopPrank();
+    }
+
+    function testSetApprovalForAllRevoke() public {
+        address owner = vm.addr(1);
+        address operator = vm.addr(2);
+        bool approved = true;
+        assertTrue(!ERC1155Extended.isApprovedForAll(owner, operator));
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        assertTrue(ERC1155Extended.isApprovedForAll(owner, operator));
+
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, !approved);
+        ERC1155Extended.setApprovalForAll(operator, !approved);
+        assertTrue(!ERC1155Extended.isApprovedForAll(owner, operator));
+        vm.stopPrank();
+    }
+
+    function testSetApprovalForAllToSelf() public {
+        address owner = vm.addr(1);
+        vm.expectRevert(bytes("ERC1155: setting approval status for self"));
+        vm.prank(owner);
+        ERC1155Extended.setApprovalForAll(owner, true);
+    }
+
+    function testSafeTransferFromEOAReceiver() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(owner, owner, receiver, id1, amount1);
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), 0);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromByApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        address operator = vm.addr(3);
+        bool approved = true;
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(operator, owner, receiver, id1, amount1);
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), 0);
+        assertEq(ERC1155Extended.balanceOf(operator, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(operator, id2), 0);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromByNotApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        address operator = vm.addr(3);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(operator);
+        vm.expectRevert(
+            bytes("ERC1155: caller is not token owner or approved")
+        );
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromNoData() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(owner, owner, receiver, id1, amount1);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit Received(owner, owner, id1, amount1, data);
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), 0);
+        vm.stopPrank();
+
+        address operator = vm.addr(3);
+        bool approved = true;
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(operator, owner, receiver, id2, amount2);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit Received(operator, owner, id2, amount2, data);
+        ERC1155Extended.safeTransferFrom(owner, receiver, id2, amount2, data);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), 0);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(operator, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(operator, id2), 0);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromWithData() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(42);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(owner, owner, receiver, id1, amount1);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit Received(owner, owner, id1, amount1, data);
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), 0);
+        vm.stopPrank();
+
+        address operator = vm.addr(3);
+        bool approved = true;
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(operator, owner, receiver, id2, amount2);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit Received(operator, owner, id2, amount2, data);
+        ERC1155Extended.safeTransferFrom(owner, receiver, id2, amount2, data);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), 0);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(operator, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(operator, id2), 0);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromReceiverInvalidReturnIdentifier() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = 0x00bb8833;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(
+            bytes("ERC1155: transfer to non-ERC1155Receiver implementer")
+        );
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        vm.stopPrank();
+
+        address operator = vm.addr(3);
+        bool approved = true;
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectRevert(
+            bytes("ERC1155: transfer to non-ERC1155Receiver implementer")
+        );
+        ERC1155Extended.safeTransferFrom(owner, receiver, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromReceiverReverts() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            true,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(bytes("ERC1155ReceiverMock: reverting on receive"));
+        ERC1155Extended.safeTransferFrom(owner, receiver, id1, amount1, data);
+        vm.stopPrank();
+
+        address operator = vm.addr(3);
+        bool approved = true;
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectRevert(bytes("ERC1155ReceiverMock: reverting on receive"));
+        ERC1155Extended.safeTransferFrom(owner, receiver, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromReceiverFunctionNotImplemented() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert();
+        ERC1155Extended.safeTransferFrom(owner, deployer, id1, amount1, data);
+        vm.stopPrank();
+
+        address operator = vm.addr(3);
+        bool approved = true;
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectRevert();
+        ERC1155Extended.safeTransferFrom(owner, deployer, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromInsufficientBalance() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256 id = 2;
+        uint256 amount = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id, amount, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(bytes("ERC1155: insufficient balance for transfer"));
+        ERC1155Extended.safeTransferFrom(owner, vm.addr(2), id, ++amount, data);
+        vm.stopPrank();
+    }
+
+    function testSafeTransferFromToZeroAddress() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256 id = 2;
+        uint256 amount = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id, amount, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(bytes("ERC1155: transfer to the zero address"));
+        ERC1155Extended.safeTransferFrom(owner, address(0), id, amount, data);
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromEOAReceiver() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(owner, owner, receiver, ids, amounts);
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.balanceOf(owner, ids[i]), 0);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromByApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        address operator = vm.addr(3);
+        bool approved = true;
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(operator, owner, receiver, ids, amounts);
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.balanceOf(owner, ids[i]), 0);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+            assertEq(ERC1155Extended.balanceOf(operator, ids[i]), 0);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromByNotApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        address operator = vm.addr(3);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectRevert(
+            bytes("ERC1155: caller is not token owner or approved")
+        );
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromNoData() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(owner, owner, receiver, ids, amounts);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit BatchReceived(owner, owner, ids, amounts, data);
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.balanceOf(owner, ids[i]), 0);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromWithData() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(42);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(owner, owner, receiver, ids, amounts);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit BatchReceived(owner, owner, ids, amounts, data);
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.balanceOf(owner, ids[i]), 0);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromReceiverInvalidReturnIdentifier() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = 0x00bb8833;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(
+            bytes("ERC1155: transfer to non-ERC1155Receiver implementer")
+        );
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromReceiverReverts() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = 0x00bb8833;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            true
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(
+            bytes("ERC1155ReceiverMock: reverting on batch receive")
+        );
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromReceiverFunctionNotImplemented() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert();
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            deployer,
+            ids,
+            amounts,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromReceiverRevertsOnlySingle() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            true,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(owner, owner, receiver, ids, amounts);
+        vm.expectEmit(true, true, false, true, receiver);
+        emit BatchReceived(owner, owner, ids, amounts, data);
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids,
+            amounts,
+            data
+        );
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.balanceOf(owner, ids[i]), 0);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromInsufficientBalance() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        ++amounts[3];
+        vm.expectRevert(bytes("ERC1155: insufficient balance for transfer"));
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            vm.addr(2),
+            ids,
+            amounts,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromLengthsMismatch() public {
+        address owner = vm.addr(1);
+        address receiver = vm.addr(2);
+        uint256[] memory ids1 = new uint256[](4);
+        uint256[] memory ids2 = new uint256[](3);
+        uint256[] memory amounts1 = new uint256[](3);
+        uint256[] memory amounts2 = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids1[0] = 0;
+        ids1[1] = 1;
+        ids1[2] = 5;
+        ids1[3] = 8;
+        ids2[0] = 0;
+        ids2[1] = 1;
+        ids2[2] = 5;
+        amounts1[0] = 1;
+        amounts1[1] = 2;
+        amounts1[2] = 10;
+        amounts2[0] = 1;
+        amounts2[1] = 2;
+        amounts2[2] = 10;
+        amounts2[2] = 20;
+
+        vm.startPrank(owner);
+        vm.expectRevert(bytes("ERC1155: ids and amounts length mismatch"));
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids1,
+            amounts1,
+            data
+        );
+        vm.expectRevert(bytes("ERC1155: ids and amounts length mismatch"));
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            receiver,
+            ids2,
+            amounts2,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testSafeBatchTransferFromToZeroAddress() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        vm.expectRevert(bytes("ERC1155: transfer to the zero address"));
+        ERC1155Extended.safeBatchTransferFrom(
+            owner,
+            address(0),
+            ids,
+            amounts,
+            data
+        );
+        vm.stopPrank();
+    }
+
+    function testUriNoTokenUri() public {
+        assertEq(
+            ERC1155Extended.uri(0),
+            string.concat(_BASE_URI, Strings.toString(uint256(0)))
+        );
+        assertEq(
+            ERC1155Extended.uri(1),
+            string.concat(_BASE_URI, Strings.toString(uint256(1)))
+        );
+    }
+
+    function testUriNoBaseURI() public {
+        bytes memory args = abi.encode("");
+        ERC1155ExtendedNoBaseURI = IERC1155Extended(
+            vyperDeployer.deployContract("src/tokens/", "ERC1155", args)
+        );
+        string memory uri = "my_awesome_uri";
+        uint256 id = 1;
+        vm.prank(address(vyperDeployer));
+        ERC1155ExtendedNoBaseURI.set_uri(id, uri);
+        assertEq(ERC1155ExtendedNoBaseURI.uri(id), uri);
+    }
+
+    function testUriBaseAndTokenUriSet() public {
+        string memory uri = "my_awesome_uri";
+        uint256 id = 1;
+        vm.prank(address(vyperDeployer));
+        ERC1155Extended.set_uri(id, uri);
+        assertEq(ERC1155Extended.uri(id), string.concat(_BASE_URI, uri));
+    }
+
+    function testUriBaseAndTokenUriNotSet() public {
+        bytes memory args = abi.encode("");
+        ERC1155ExtendedNoBaseURI = IERC1155Extended(
+            vyperDeployer.deployContract("src/tokens/", "ERC1155", args)
+        );
+        uint256 id = 1;
+        assertEq(ERC1155ExtendedNoBaseURI.uri(id), "");
+    }
+
+    function testSetUri() public {
+        string memory uri = "my_awesome_uri";
+        uint256 id = 1;
+        vm.prank(address(vyperDeployer));
+        vm.expectEmit(true, false, false, true);
+        emit URI(string.concat(_BASE_URI, uri), id);
+        ERC1155Extended.set_uri(id, uri);
+        assertEq(ERC1155Extended.uri(id), string.concat(_BASE_URI, uri));
+    }
+
+    function testSetUriEmpty() public {
+        string memory uri = "";
+        uint256 id = 1;
+        vm.prank(address(vyperDeployer));
+        vm.expectEmit(true, false, false, true);
+        emit URI(string.concat(_BASE_URI, Strings.toString(uint256(id))), id);
+        ERC1155Extended.set_uri(id, uri);
+        assertEq(
+            ERC1155Extended.uri(id),
+            string.concat(_BASE_URI, Strings.toString(uint256(id)))
+        );
+    }
+
+    function testSetUriNonMinter() public {
+        vm.expectRevert(bytes("AccessControl: access is denied"));
+        vm.prank(vm.addr(1));
+        ERC1155Extended.set_uri(1, "my_awesome_uri");
+    }
+
+    function testTotalSupplyBeforeMint() public {
+        assertEq(ERC1155Extended.total_supply(0), 0);
+    }
+
+    function testTotalSupplyAfterSingleMint() public {
+        uint256 id = 0;
+        bytes memory data = new bytes(0);
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint(vm.addr(1), id, 1, data);
+        ERC1155Extended.safe_mint(vm.addr(2), id, 20, data);
+        assertEq(ERC1155Extended.total_supply(0), 21);
+        vm.stopPrank();
+    }
+
+    function testTotalSupplyAfterBatchMint() public {
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 0;
+        ids[3] = 1;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint_batch(vm.addr(1), ids, amounts, new bytes(0));
+        assertEq(ERC1155Extended.total_supply(0), 11);
+        assertEq(ERC1155Extended.total_supply(1), 22);
+        vm.stopPrank();
+    }
+
+    function testTotalSupplyAfterSingleBurn() public {
+        address owner = vm.addr(1);
+        uint256 id = 0;
+        bytes memory data = new bytes(0);
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint(owner, id, 15, data);
+        ERC1155Extended.safe_mint(vm.addr(2), id, 20, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        ERC1155Extended.burn(owner, id, 10);
+        assertEq(ERC1155Extended.total_supply(0), 25);
+        vm.stopPrank();
+    }
+
+    function testTotalSupplyAfterBatchBurn() public {
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 0;
+        ids[3] = 1;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, new bytes(0));
+        vm.stopPrank();
+        vm.startPrank(owner);
+        --amounts[2];
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+        assertEq(ERC1155Extended.total_supply(0), 1);
+        assertEq(ERC1155Extended.total_supply(1), 0);
+        vm.stopPrank();
+    }
+
+    function testExistsBeforeMint() public {
+        assertTrue(!ERC1155Extended.exists(0));
+    }
+
+    function testExistsAfterSingleMint() public {
+        uint256 id = 0;
+        bytes memory data = new bytes(0);
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint(vm.addr(1), id, 1, data);
+        ERC1155Extended.safe_mint(vm.addr(2), id, 20, data);
+        assertTrue(ERC1155Extended.exists(0));
+        vm.stopPrank();
+    }
+
+    function testExistsAfterBatchMint() public {
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 0;
+        ids[3] = 1;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint_batch(vm.addr(1), ids, amounts, new bytes(0));
+        assertTrue(ERC1155Extended.exists(0));
+        assertTrue(ERC1155Extended.exists(1));
+        vm.stopPrank();
+    }
+
+    function testExistsAfterSingleBurn() public {
+        address owner = vm.addr(1);
+        uint256 id = 0;
+        bytes memory data = new bytes(0);
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint(owner, id, 15, data);
+        ERC1155Extended.safe_mint(vm.addr(2), id, 20, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        ERC1155Extended.burn(owner, id, 10);
+        assertTrue(ERC1155Extended.exists(0));
+        assertTrue(!ERC1155Extended.exists(1));
+        vm.stopPrank();
+    }
+
+    function testExistsAfterBatchBurn() public {
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 0;
+        ids[3] = 1;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, new bytes(0));
+        vm.stopPrank();
+        vm.startPrank(owner);
+        --amounts[2];
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+        assertTrue(ERC1155Extended.exists(0));
+        assertTrue(!ERC1155Extended.exists(1));
+        vm.stopPrank();
+    }
+
+    function testBurnSuccess() public {
+        address firstOwner = vm.addr(1);
+        address secondOwner = vm.addr(2);
+        uint256 id = 0;
+        uint256 burnAmount = 10;
+        bytes memory data = new bytes(0);
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint(firstOwner, id, 15, data);
+        ERC1155Extended.safe_mint(secondOwner, id, 20, data);
+        vm.stopPrank();
+        vm.startPrank(firstOwner);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(firstOwner, firstOwner, address(0), id, burnAmount);
+        ERC1155Extended.burn(firstOwner, id, 10);
+        assertEq(ERC1155Extended.total_supply(0), 25);
+        assertEq(ERC1155Extended.balanceOf(firstOwner, id), 5);
+        assertEq(ERC1155Extended.balanceOf(secondOwner, id), 20);
+        vm.stopPrank();
+    }
+
+    function testBurnByApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address operator = vm.addr(2);
+        bool approved = true;
+        uint256 id1 = 1;
+        uint256 amount1 = 15;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        uint256 burnAmount = 10;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(operator, owner, address(0), id1, burnAmount);
+        ERC1155Extended.burn(owner, id1, burnAmount);
+        assertEq(ERC1155Extended.total_supply(id1), amount1 - burnAmount);
+        assertEq(ERC1155Extended.total_supply(id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(owner, id1), amount1 - burnAmount);
+        assertEq(ERC1155Extended.balanceOf(owner, id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(operator, id1), 0);
+        assertEq(ERC1155Extended.balanceOf(operator, id2), 0);
+        vm.stopPrank();
+    }
+
+    function testBurnByNotApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address operator = vm.addr(2);
+        uint256 id1 = 1;
+        uint256 amount1 = 15;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        uint256 burnAmount = 10;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint(owner, id1, amount1, data);
+        ERC1155Extended.safe_mint(owner, id2, amount2, data);
+        vm.stopPrank();
+        vm.startPrank(operator);
+        vm.expectRevert(
+            bytes("ERC1155: caller is not token owner or approved")
+        );
+        ERC1155Extended.burn(owner, id1, burnAmount);
+        vm.stopPrank();
+    }
+
+    function testBurnFromZeroAddress() public {
+        address owner = address(0);
+        vm.prank(owner);
+        vm.expectRevert(bytes("ERC1155: burn from the zero address"));
+        ERC1155Extended.burn(owner, 1, 1);
+    }
+
+    function testBurnAmountExceedsBalance() public {
+        address firstOwner = vm.addr(1);
+        address secondOwner = vm.addr(2);
+        uint256 id = 0;
+        bytes memory data = new bytes(0);
+        vm.startPrank(address(vyperDeployer));
+        ERC1155Extended.safe_mint(firstOwner, id, 15, data);
+        ERC1155Extended.safe_mint(secondOwner, id, 20, data);
+        vm.stopPrank();
+        vm.startPrank(firstOwner);
+        vm.expectRevert(bytes("ERC1155: burn amount exceeds balance"));
+        ERC1155Extended.burn(firstOwner, id, 16);
+        vm.stopPrank();
+    }
+
+    function testBurnNonExistentTokenId() public {
+        address firstOwner = vm.addr(1);
+        vm.prank(vm.addr(1));
+        vm.expectRevert(bytes("ERC1155: burn amount exceeds total_supply"));
+        ERC1155Extended.burn(firstOwner, 1, 1);
+    }
+
+    function testBurnBatch() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+        vm.startPrank(owner);
+        --amounts[2];
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(owner, owner, address(0), ids, amounts);
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+        assertEq(ERC1155Extended.total_supply(ids[0]), 0);
+        assertEq(ERC1155Extended.total_supply(ids[1]), 0);
+        assertEq(ERC1155Extended.total_supply(ids[2]), 1);
+        assertEq(ERC1155Extended.total_supply(ids[3]), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[0]), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[1]), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[2]), 1);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[3]), 0);
+        vm.stopPrank();
+    }
+
+    function testBurnBatchByApprovedOperator() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address operator = vm.addr(2);
+        bool approved = true;
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, data);
+        vm.stopPrank();
+
+        vm.startPrank(owner);
+        vm.expectEmit(true, true, false, true);
+        emit ApprovalForAll(owner, operator, approved);
+        ERC1155Extended.setApprovalForAll(operator, approved);
+        vm.stopPrank();
+
+        vm.startPrank(operator);
+        --amounts[2];
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(operator, owner, address(0), ids, amounts);
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+        assertEq(ERC1155Extended.total_supply(ids[0]), 0);
+        assertEq(ERC1155Extended.total_supply(ids[1]), 0);
+        assertEq(ERC1155Extended.total_supply(ids[2]), 1);
+        assertEq(ERC1155Extended.total_supply(ids[3]), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[0]), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[1]), 0);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[2]), 1);
+        assertEq(ERC1155Extended.balanceOf(owner, ids[3]), 0);
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.balanceOf(operator, ids[i]), 0);
+        }
+        vm.stopPrank();
+    }
+
+    function testBurnBatchByNotApprovedOperator() public {
+        address owner = vm.addr(1);
+        address operator = vm.addr(2);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(operator);
+        vm.expectRevert(
+            bytes("ERC1155: caller is not token owner or approved")
+        );
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+        vm.stopPrank();
+    }
+
+    function testBurnBatchLengthsMismatch() public {
+        address owner = vm.addr(1);
+        uint256[] memory ids1 = new uint256[](4);
+        uint256[] memory ids2 = new uint256[](3);
+        uint256[] memory amounts1 = new uint256[](3);
+        uint256[] memory amounts2 = new uint256[](4);
+
+        ids1[0] = 0;
+        ids1[1] = 1;
+        ids1[2] = 5;
+        ids1[3] = 8;
+        ids2[0] = 0;
+        ids2[1] = 1;
+        ids2[2] = 5;
+        amounts1[0] = 1;
+        amounts1[1] = 2;
+        amounts1[2] = 10;
+        amounts2[0] = 1;
+        amounts2[1] = 2;
+        amounts2[2] = 10;
+        amounts2[2] = 20;
+
+        vm.startPrank(owner);
+        vm.expectRevert(bytes("ERC1155: ids and amounts length mismatch"));
+        ERC1155Extended.burn_batch(owner, ids1, amounts1);
+        vm.expectRevert(bytes("ERC1155: ids and amounts length mismatch"));
+        ERC1155Extended.burn_batch(owner, ids2, amounts2);
+        vm.stopPrank();
+    }
+
+    function testBurnBatchFromZeroAddress() public {
+        address owner = address(0);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.prank(owner);
+        vm.expectRevert(bytes("ERC1155: burn from the zero address"));
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+    }
+
+    function testBurnBatchAmountExceedsBalance() public {
+        address deployer = address(vyperDeployer);
+        address owner = vm.addr(1);
+        address nonOwner = vm.addr(2);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        ERC1155Extended.safe_mint_batch(owner, ids, amounts, new bytes(0));
+        vm.stopPrank();
+        vm.startPrank(nonOwner);
+        vm.expectRevert(bytes("ERC1155: burn amount exceeds balance"));
+        ERC1155Extended.burn_batch(nonOwner, ids, amounts);
+        vm.stopPrank();
+    }
+
+    function testBurnBatchNonExistentTokenIds() public {
+        address owner = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.prank(owner);
+        vm.expectRevert(bytes("ERC1155: burn amount exceeds total_supply"));
+        ERC1155Extended.burn_batch(owner, ids, amounts);
+    }
+
+    function testSafeMintEOAReceiver() public {
+        address deployer = address(vyperDeployer);
+        address receiver = vm.addr(1);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id1, amount1);
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id2, amount2);
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        assertEq(ERC1155Extended.total_supply(id1), amount1);
+        assertEq(ERC1155Extended.total_supply(id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), amount2);
+        vm.stopPrank();
+    }
+
+    function testSafeMintNoData() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id1, amount1);
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id2, amount2);
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        assertEq(ERC1155Extended.total_supply(id1), amount1);
+        assertEq(ERC1155Extended.total_supply(id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), amount2);
+        vm.stopPrank();
+    }
+
+    function testSafeMintWithData() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(42);
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id1, amount1);
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id2, amount2);
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        assertEq(ERC1155Extended.total_supply(id1), amount1);
+        assertEq(ERC1155Extended.total_supply(id2), amount2);
+        assertEq(ERC1155Extended.balanceOf(receiver, id1), amount1);
+        assertEq(ERC1155Extended.balanceOf(receiver, id2), amount2);
+        vm.stopPrank();
+    }
+
+    function testSafeMintReceiverInvalidReturnIdentifier() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = 0x00bb8833;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectRevert(
+            bytes("ERC1155: transfer to non-ERC1155Receiver implementer")
+        );
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectRevert(
+            bytes("ERC1155: transfer to non-ERC1155Receiver implementer")
+        );
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintReceiverReverts() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            true,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectRevert(bytes("ERC1155ReceiverMock: reverting on receive"));
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectRevert(bytes("ERC1155ReceiverMock: reverting on receive"));
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintReceiverFunctionNotImplemented() public {
+        address deployer = address(vyperDeployer);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectRevert();
+        ERC1155Extended.safe_mint(deployer, id1, amount1, data);
+        vm.expectRevert();
+        ERC1155Extended.safe_mint(deployer, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintToZeroAddress() public {
+        address deployer = address(vyperDeployer);
+        address receiver = address(0);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectRevert(bytes("ERC1155: mint to the zero address"));
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectRevert(bytes("ERC1155: mint to the zero address"));
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintNonMinter() public {
+        address receiver = vm.addr(1);
+        uint256 id1 = 1;
+        uint256 amount1 = 1;
+        uint256 id2 = 4;
+        uint256 amount2 = 15;
+        bytes memory data = new bytes(0);
+        vm.startPrank(vm.addr(2));
+        vm.expectRevert(bytes("AccessControl: access is denied"));
+        ERC1155Extended.safe_mint(receiver, id1, amount1, data);
+        vm.expectRevert(bytes("AccessControl: access is denied"));
+        ERC1155Extended.safe_mint(receiver, id2, amount2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintOverflow() public {
+        address deployer = address(vyperDeployer);
+        address receiver = vm.addr(1);
+        uint256 id = 1;
+        uint256 amount = type(uint256).max;
+        bytes memory data = new bytes(0);
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferSingle(deployer, address(0), receiver, id, amount);
+        ERC1155Extended.safe_mint(receiver, id, amount, data);
+        vm.expectRevert();
+        ERC1155Extended.safe_mint(receiver, id, amount, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchEOAReceiver() public {
+        address deployer = address(vyperDeployer);
+        address receiver = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(deployer, address(0), receiver, ids, amounts);
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.total_supply(ids[i]), amounts[i]);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchNoData() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(deployer, address(0), receiver, ids, amounts);
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.total_supply(ids[i]), amounts[i]);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchWithData() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(42);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(deployer, address(0), receiver, ids, amounts);
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.total_supply(ids[i]), amounts[i]);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchReceiverInvalidReturnIdentifier() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = 0x00bb8833;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectRevert(
+            bytes("ERC1155: transfer to non-ERC1155Receiver implementer")
+        );
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchReceiverReverts() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = 0x00bb8833;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            false,
+            receiverBatchMagicValue,
+            true
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectRevert(
+            bytes("ERC1155ReceiverMock: reverting on batch receive")
+        );
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchReceiverFunctionNotImplemented() public {
+        address deployer = address(vyperDeployer);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectRevert();
+        ERC1155Extended.safe_mint_batch(deployer, ids, amounts, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchReceiverRevertsOnlySingle() public {
+        address deployer = address(vyperDeployer);
+        bytes4 receiverSingleMagicValue = IERC1155Receiver
+            .onERC1155Received
+            .selector;
+        bytes4 receiverBatchMagicValue = IERC1155Receiver
+            .onERC1155BatchReceived
+            .selector;
+        ERC1155ReceiverMock erc1155ReceiverMock = new ERC1155ReceiverMock(
+            receiverSingleMagicValue,
+            true,
+            receiverBatchMagicValue,
+            false
+        );
+        address receiver = address(erc1155ReceiverMock);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(deployer, address(0), receiver, ids, amounts);
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        for (uint256 i; i < ids.length; ++i) {
+            assertEq(ERC1155Extended.total_supply(ids[i]), amounts[i]);
+            assertEq(ERC1155Extended.balanceOf(receiver, ids[i]), amounts[i]);
+        }
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchLengthsMismatch() public {
+        address deployer = address(vyperDeployer);
+        uint256[] memory ids1 = new uint256[](3);
+        uint256[] memory ids2 = new uint256[](4);
+        uint256[] memory amounts1 = new uint256[](4);
+        uint256[] memory amounts2 = new uint256[](3);
+        bytes memory data = new bytes(0);
+
+        ids1[0] = 0;
+        ids1[1] = 1;
+        ids1[2] = 5;
+        ids2[0] = 0;
+        ids2[1] = 1;
+        ids2[2] = 5;
+        ids2[3] = 8;
+        amounts1[0] = 1;
+        amounts1[1] = 2;
+        amounts1[2] = 10;
+        amounts1[3] = 20;
+        amounts2[0] = 1;
+        amounts2[1] = 2;
+        amounts2[2] = 10;
+
+        vm.startPrank(deployer);
+        vm.expectRevert("ERC1155: ids and amounts length mismatch");
+        ERC1155Extended.safe_mint_batch(deployer, ids1, amounts1, data);
+        vm.expectRevert("ERC1155: ids and amounts length mismatch");
+        ERC1155Extended.safe_mint_batch(deployer, ids2, amounts2, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchToZeroAddress() public {
+        address deployer = address(vyperDeployer);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(deployer);
+        vm.expectRevert(bytes("ERC1155: mint to the zero address"));
+        ERC1155Extended.safe_mint_batch(address(0), ids, amounts, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchNonMinter() public {
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = 20;
+
+        vm.startPrank(vm.addr(1));
+        vm.expectRevert(bytes("AccessControl: access is denied"));
+        ERC1155Extended.safe_mint_batch(vm.addr(2), ids, amounts, data);
+        vm.stopPrank();
+    }
+
+    function testSafeMintBatchOverflow() public {
+        address deployer = address(vyperDeployer);
+        address receiver = vm.addr(1);
+        uint256[] memory ids = new uint256[](4);
+        uint256[] memory amounts = new uint256[](4);
+        bytes memory data = new bytes(0);
+
+        ids[0] = 0;
+        ids[1] = 1;
+        ids[2] = 5;
+        ids[3] = 8;
+        amounts[0] = 1;
+        amounts[1] = 2;
+        amounts[2] = 10;
+        amounts[3] = type(uint256).max;
+
+        vm.startPrank(deployer);
+        vm.expectEmit(true, true, true, true);
+        emit TransferBatch(deployer, address(0), receiver, ids, amounts);
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        vm.expectRevert();
+        ERC1155Extended.safe_mint_batch(receiver, ids, amounts, data);
+        vm.stopPrank();
+    }
+
+    function testSetMinterSuccess() public {
+        address owner = address(vyperDeployer);
+        address minter = vm.addr(1);
+        vm.startPrank(owner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(minter, true);
+        ERC1155Extended.set_minter(minter, true);
+        assertTrue(ERC1155Extended.is_minter(minter));
+
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(minter, false);
+        ERC1155Extended.set_minter(minter, false);
+        assertTrue(!ERC1155Extended.is_minter(minter));
+        vm.stopPrank();
+    }
+
+    function testSetMinterNonOwner() public {
+        vm.expectRevert(bytes("Ownable: caller is not the owner"));
+        ERC1155Extended.set_minter(vm.addr(1), true);
+    }
+
+    function testSetMinterToZeroAddress() public {
+        vm.prank(address(vyperDeployer));
+        vm.expectRevert(bytes("AccessControl: minter is the zero address"));
+        ERC1155Extended.set_minter(address(0), true);
+    }
+
+    function testSetMinterRemoveOwnerAddress() public {
+        vm.prank(address(vyperDeployer));
+        vm.expectRevert(bytes("AccessControl: minter is owner address"));
+        ERC1155Extended.set_minter(address(vyperDeployer), false);
+    }
+
+    function testHasOwner() public {
+        assertEq(ERC1155Extended.owner(), address(vyperDeployer));
+    }
+
+    function testTransferOwnershipSuccess() public {
+        address oldOwner = address(vyperDeployer);
+        address newOwner = vm.addr(1);
+        vm.startPrank(oldOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(oldOwner, false);
+        vm.expectEmit(true, true, false, false);
+        emit OwnershipTransferred(oldOwner, newOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(newOwner, true);
+        ERC1155Extended.transfer_ownership(newOwner);
+        assertEq(ERC1155Extended.owner(), newOwner);
+        assertTrue(!ERC1155Extended.is_minter(oldOwner));
+        assertTrue(ERC1155Extended.is_minter(newOwner));
+        vm.stopPrank();
+    }
+
+    function testTransferOwnershipNonOwner() public {
+        vm.expectRevert(bytes("Ownable: caller is not the owner"));
+        ERC1155Extended.transfer_ownership(vm.addr(1));
+    }
+
+    function testTransferOwnershipToZeroAddress() public {
+        vm.prank(address(vyperDeployer));
+        vm.expectRevert(bytes("Ownable: new owner is the zero address"));
+        ERC1155Extended.transfer_ownership(address(0));
+    }
+
+    function testRenounceOwnershipSuccess() public {
+        address oldOwner = address(vyperDeployer);
+        address newOwner = address(0);
+        vm.startPrank(oldOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(oldOwner, false);
+        vm.expectEmit(true, true, false, false);
+        emit OwnershipTransferred(oldOwner, newOwner);
+        ERC1155Extended.renounce_ownership();
+        assertEq(ERC1155Extended.owner(), newOwner);
+        assertTrue(!ERC1155Extended.is_minter(oldOwner));
+        vm.stopPrank();
+    }
+
+    function testRenounceOwnershipNonOwner() public {
+        vm.expectRevert(bytes("Ownable: caller is not the owner"));
+        ERC1155Extended.renounce_ownership();
+    }
+}
diff --git a/test/tokens/ERC20.t.sol b/test/tokens/ERC20.t.sol
index f988e031..e104f159 100644
--- a/test/tokens/ERC20.t.sol
+++ b/test/tokens/ERC20.t.sol
@@ -991,10 +991,16 @@ contract ERC20Test is Test {
         address oldOwner = address(vyperDeployer);
         address newOwner = vm.addr(1);
         vm.startPrank(oldOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(oldOwner, false);
         vm.expectEmit(true, true, false, false);
         emit OwnershipTransferred(oldOwner, newOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(newOwner, true);
         ERC20Extended.transfer_ownership(newOwner);
         assertEq(ERC20Extended.owner(), newOwner);
+        assertTrue(!ERC20Extended.is_minter(oldOwner));
+        assertTrue(ERC20Extended.is_minter(newOwner));
         vm.stopPrank();
     }
 
@@ -1013,11 +1019,13 @@ contract ERC20Test is Test {
         address oldOwner = address(vyperDeployer);
         address newOwner = address(0);
         vm.startPrank(oldOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(oldOwner, false);
         vm.expectEmit(true, true, false, false);
         emit OwnershipTransferred(oldOwner, newOwner);
         ERC20Extended.renounce_ownership();
         assertEq(ERC20Extended.owner(), newOwner);
-        assertTrue(ERC20Extended.is_minter(oldOwner) == false);
+        assertTrue(!ERC20Extended.is_minter(oldOwner));
         vm.stopPrank();
     }
 
diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol
index cb641e36..fd2f683f 100644
--- a/test/tokens/ERC721.t.sol
+++ b/test/tokens/ERC721.t.sol
@@ -1842,10 +1842,16 @@ contract ERC721Test is Test {
         address oldOwner = address(vyperDeployer);
         address newOwner = vm.addr(1);
         vm.startPrank(oldOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(oldOwner, false);
         vm.expectEmit(true, true, false, false);
         emit OwnershipTransferred(oldOwner, newOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(newOwner, true);
         ERC721Extended.transfer_ownership(newOwner);
         assertEq(ERC721Extended.owner(), newOwner);
+        assertTrue(!ERC721Extended.is_minter(oldOwner));
+        assertTrue(ERC721Extended.is_minter(newOwner));
         vm.stopPrank();
     }
 
@@ -1864,11 +1870,13 @@ contract ERC721Test is Test {
         address oldOwner = address(vyperDeployer);
         address newOwner = address(0);
         vm.startPrank(oldOwner);
+        vm.expectEmit(true, false, false, true);
+        emit RoleMinterChanged(oldOwner, false);
         vm.expectEmit(true, true, false, false);
         emit OwnershipTransferred(oldOwner, newOwner);
         ERC721Extended.renounce_ownership();
         assertEq(ERC721Extended.owner(), newOwner);
-        assertTrue(ERC721Extended.is_minter(oldOwner) == false);
+        assertTrue(!ERC721Extended.is_minter(oldOwner));
         vm.stopPrank();
     }
 
diff --git a/test/tokens/interfaces/IERC1155Extended.sol b/test/tokens/interfaces/IERC1155Extended.sol
new file mode 100644
index 00000000..cfd45001
--- /dev/null
+++ b/test/tokens/interfaces/IERC1155Extended.sol
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: WTFPL
+pragma solidity ^0.8.17;
+
+import {IERC1155MetadataURI} from "openzeppelin/token/ERC1155/extensions/IERC1155MetadataURI.sol";
+
+interface IERC1155Extended is IERC1155MetadataURI {
+    function set_uri(uint256 id, string memory tokenUri) external;
+
+    function exists(uint256 id) external view returns (bool);
+
+    function total_supply(uint256 id) external view returns (uint256);
+
+    function burn(address owner, uint256 id, uint256 amount) external;
+
+    function burn_batch(
+        address owner,
+        uint256[] memory ids,
+        uint256[] memory amounts
+    ) external;
+
+    function is_minter(address account) external view returns (bool);
+
+    function safe_mint(
+        address owner,
+        uint256 id,
+        uint256 amount,
+        bytes memory data
+    ) external;
+
+    function safe_mint_batch(
+        address owner,
+        uint256[] memory ids,
+        uint256[] memory amounts,
+        bytes memory data
+    ) external;
+
+    function set_minter(address minter, bool status) external;
+
+    function owner() external view returns (address);
+
+    function transfer_ownership(address newOwner) external;
+
+    function renounce_ownership() external;
+}
diff --git a/test/tokens/interfaces/IERC20Extended.sol b/test/tokens/interfaces/IERC20Extended.sol
index bf03fda3..19992674 100644
--- a/test/tokens/interfaces/IERC20Extended.sol
+++ b/test/tokens/interfaces/IERC20Extended.sol
@@ -19,14 +19,14 @@ interface IERC20Extended is IERC20Metadata, IERC20Permit {
 
     function burn_from(address owner, uint256 amount) external;
 
-    function owner() external view returns (address);
-
     function is_minter(address minter) external view returns (bool);
 
     function mint(address owner, uint256 amount) external;
 
     function set_minter(address minter, bool status) external;
 
+    function owner() external view returns (address);
+
     function transfer_ownership(address newOwner) external;
 
     function renounce_ownership() external;
diff --git a/test/tokens/interfaces/IERC721Extended.sol b/test/tokens/interfaces/IERC721Extended.sol
index 6f159ef1..6054522e 100644
--- a/test/tokens/interfaces/IERC721Extended.sol
+++ b/test/tokens/interfaces/IERC721Extended.sol
@@ -8,14 +8,14 @@ import {IERC4494} from "./IERC4494.sol";
 interface IERC721Extended is IERC721Metadata, IERC721Enumerable, IERC4494 {
     function burn(uint256 tokenId) external;
 
-    function owner() external view returns (address);
-
     function is_minter(address minter) external view returns (bool);
 
     function safe_mint(address owner, string calldata uri) external;
 
     function set_minter(address minter, bool status) external;
 
+    function owner() external view returns (address);
+
     function transfer_ownership(address newOwner) external;
 
     function renounce_ownership() external;
diff --git a/test/tokens/mocks/ERC1155ReceiverMock.sol b/test/tokens/mocks/ERC1155ReceiverMock.sol
new file mode 100644
index 00000000..7ce0aa9b
--- /dev/null
+++ b/test/tokens/mocks/ERC1155ReceiverMock.sol
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.17;
+
+import {ERC165} from "openzeppelin/utils/introspection/ERC165.sol";
+import {IERC1155Receiver} from "openzeppelin/token/ERC1155/IERC1155Receiver.sol";
+
+/**
+ * @title ERC1155ReceiverMock
+ * @author jtriley.eth
+ * @custom:coauthor pcaversaccio
+ * @notice Forked and adjusted accordingly from here:
+ * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/token/ERC1155ReceiverMock.sol.
+ * @notice Allows to test receiving ERC-1155 tokens as a smart contract.
+ */
+contract ERC1155ReceiverMock is ERC165, IERC1155Receiver {
+    bytes4 private _recRetval;
+    bool private _recReverts;
+    bytes4 private _batRetval;
+    bool private _batReverts;
+
+    event Received(
+        address indexed operator,
+        address indexed from,
+        uint256 id,
+        uint256 amount,
+        bytes data
+    );
+
+    event BatchReceived(
+        address indexed operator,
+        address indexed from,
+        uint256[] ids,
+        uint256[] amounts,
+        bytes data
+    );
+
+    constructor(
+        bytes4 recRetval_,
+        bool recReverts_,
+        bytes4 batRetval_,
+        bool batReverts_
+    ) {
+        _recRetval = recRetval_;
+        _recReverts = recReverts_;
+        _batRetval = batRetval_;
+        _batReverts = batReverts_;
+    }
+
+    /**
+     * @dev Handles the receipt of a single ERC-1155 token type.
+     * This function is called at the end of a `safeTransferFrom`
+     * after the balance has been updated.
+     * @param operator The 20-byte address which called the `safeTransferFrom`
+     * function.
+     * @param from The 20-byte address which previously owned the token.
+     * @param id The 32-byte identifier of the token.
+     * @param amount The 32-byte token amount that is being transferred.
+     * @param data The additional data with no specified format that is sent
+     * to this smart contract.
+     * @return bytes4 The 4-byte return identifier.
+     */
+    function onERC1155Received(
+        address operator,
+        address from,
+        uint256 id,
+        uint256 amount,
+        bytes memory data
+    ) external returns (bytes4) {
+        // solhint-disable-next-line reason-string
+        require(!_recReverts, "ERC1155ReceiverMock: reverting on receive");
+        emit Received(operator, from, id, amount, data);
+        return _recRetval;
+    }
+
+    /**
+     * @dev Handles the receipt of multiple ERC-1155 token types.
+     * This function is called at the end of a `safeBatchTransferFrom`
+     * after the balances have been updated.
+     * @param operator The 20-byte address which called the `safeBatchTransferFrom`
+     * function.
+     * @param from The 20-byte address which previously owned the tokens.
+     * @param ids The 32-byte array of token identifiers.
+     * @param amounts The 32-byte array of token amounts that are being transferred.
+     * @param data The additional data with no specified format that is sent
+     * to this smart contract.
+     * @return bytes4 The 4-byte return identifier.
+     */
+    function onERC1155BatchReceived(
+        address operator,
+        address from,
+        uint256[] memory ids,
+        uint256[] memory amounts,
+        bytes memory data
+    ) external returns (bytes4) {
+        // solhint-disable-next-line reason-string
+        require(
+            !_batReverts,
+            "ERC1155ReceiverMock: reverting on batch receive"
+        );
+        emit BatchReceived(operator, from, ids, amounts, data);
+        return _batRetval;
+    }
+}
diff --git a/test/tokens/mocks/ERC721ReceiverMock.sol b/test/tokens/mocks/ERC721ReceiverMock.sol
index 1f4b8375..98d8d8af 100644
--- a/test/tokens/mocks/ERC721ReceiverMock.sol
+++ b/test/tokens/mocks/ERC721ReceiverMock.sol
@@ -7,7 +7,7 @@ import {IERC721Receiver} from "openzeppelin/token/ERC721/IERC721Receiver.sol";
  * @title ERC721ReceiverMock
  * @author pcaversaccio
  * @notice Forked and adjusted accordingly from here:
- * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/ERC721ReceiverMock.sol.
+ * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/token/ERC721ReceiverMock.sol.
  * @dev Allows to test receiving ERC-721 tokens as a smart contract.
  */
 contract ERC721ReceiverMock is IERC721Receiver {
@@ -23,9 +23,9 @@ contract ERC721ReceiverMock is IERC721Receiver {
 
     event Received(address operator, address from, uint256 tokenId, bytes data);
 
-    constructor(bytes4 retval, Error error) {
-        _retval = retval;
-        _error = error;
+    constructor(bytes4 retval_, Error error_) {
+        _retval = retval_;
+        _error = error_;
     }
 
     /**
diff --git a/test/utils/mocks/Create2Impl.sol b/test/utils/mocks/Create2Impl.sol
index a439aaca..ec2321b0 100644
--- a/test/utils/mocks/Create2Impl.sol
+++ b/test/utils/mocks/Create2Impl.sol
@@ -8,7 +8,7 @@ import {ERC1820Implementer} from "openzeppelin/utils/introspection/ERC1820Implem
  * @title Create2Impl
  * @author pcaversaccio
  * @notice Forked and adjusted accordingly from here:
- * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/Create2Impl.sol.
+ * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/a81b0d0b2136a0cca6029048be25c4e2bb230d49/contracts/mocks/Create2Impl.sol.
  * @dev Allows to test `CREATE2` deployments and address computation.
  */
 contract Create2Impl {
diff --git a/test/utils/mocks/ERC1271WalletMock.sol b/test/utils/mocks/ERC1271WalletMock.sol
index a3108d54..b7bea84b 100644
--- a/test/utils/mocks/ERC1271WalletMock.sol
+++ b/test/utils/mocks/ERC1271WalletMock.sol
@@ -13,8 +13,8 @@ import {ECDSA} from "openzeppelin/utils/cryptography/ECDSA.sol";
  * @dev Allows to mock a correct ERC-1271 implementation.
  */
 contract ERC1271WalletMock is Ownable, IERC1271 {
-    constructor(address originalOwner) {
-        _transferOwnership(originalOwner);
+    constructor(address originalOwner_) {
+        _transferOwnership(originalOwner_);
     }
 
     /**
diff --git a/test/utils/mocks/ERC20Mock.sol b/test/utils/mocks/ERC20Mock.sol
index 471d88c8..62529839 100644
--- a/test/utils/mocks/ERC20Mock.sol
+++ b/test/utils/mocks/ERC20Mock.sol
@@ -12,12 +12,12 @@ import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol";
  */
 contract ERC20Mock is ERC20 {
     constructor(
-        string memory name,
-        string memory symbol,
-        address initialAccount,
-        uint256 initialBalance
-    ) payable ERC20(name, symbol) {
-        _mint(initialAccount, initialBalance);
+        string memory name_,
+        string memory symbol_,
+        address initialAccount_,
+        uint256 initialBalance_
+    ) payable ERC20(name_, symbol_) {
+        _mint(initialAccount_, initialBalance_);
     }
 
     /**