Repository: pcaversaccio/snekmate (language: python)

Issue:
💥 Add a `MetadataUpdate` Event to EIP-721
[ERC-4906](https://eips.ethereum.org/EIPS/eip-4906) is now final and should be implemented.


Discussion / hints:

Fix patch:
diff --git a/lib/create-util b/lib/create-util
index 4ef12149..805c664c 160000
--- a/lib/create-util
+++ b/lib/create-util
@@ -1,1 +1,1 @@
-Subproject commit 4ef1214910ab67011d2f715910057f30ae0bfd32
+Subproject commit 805c664c182fbf4b6588a008a67173d16a2ed53c
diff --git a/lib/forge-std b/lib/forge-std
index 662ae0d6..a2edd39d 160000
--- a/lib/forge-std
+++ b/lib/forge-std
@@ -1,1 +1,1 @@
-Subproject commit 662ae0d6936654c5d1fb79fc15f521de28edb60e
+Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df
diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts
index 5a00628e..96a2297e 160000
--- a/lib/openzeppelin-contracts
+++ b/lib/openzeppelin-contracts
@@ -1,1 +1,1 @@
-Subproject commit 5a00628ed3d6ce3154cee4d2cc93fad920e8ea30
+Subproject commit 96a2297e15f1a4bbcf470d2d0d6cb9c579c63893
diff --git a/src/tokens/ERC721.vy b/src/tokens/ERC721.vy
index f7980f95..ae5a8e59 100644
--- a/src/tokens/ERC721.vy
+++ b/src/tokens/ERC721.vy
@@ -71,6 +71,12 @@ import interfaces.IERC721Permit as IERC721Permit
 implements: IERC721Permit
 
 
+# @dev We import and implement the `IERC4906` interface,
+# which is written using standard Vyper syntax.
+import interfaces.IERC4906 as IERC4906
+implements: IERC4906
+
+
 # @dev We import the `IERC721Receiver` interface, which
 # is written using standard Vyper syntax.
 import interfaces.IERC721Receiver as IERC721Receiver
@@ -80,12 +86,13 @@ import interfaces.IERC721Receiver as IERC721Receiver
 # imported interface. The ERC-165 interface identifier
 # is defined as the XOR of all function selectors in the
 # interface.
-_SUPPORTED_INTERFACES: constant(bytes4[5]) = [
+_SUPPORTED_INTERFACES: constant(bytes4[6]) = [
     0x01FFC9A7, # The ERC-165 identifier for ERC-165.
     0x80AC58CD, # The ERC-165 identifier for ERC-721.
     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.
+    0x49064906, # The ERC-165 identifier for ERC-4906.
 ]
 
 
@@ -224,6 +231,19 @@ event ApprovalForAll:
     approved: bool
 
 
+# @dev Emitted when the metadata of a token is
+# changed.
+event MetadataUpdate:
+    token_id: uint256
+
+
+# @dev Emitted when the metadata of a range of
+# tokens is changed.
+event BatchMetadataUpdate:
+    from_token_id: uint256
+    token_id: uint256
+
+
 # @dev Emitted when the ownership is transferred
 # from `previous_owner` to `new_owner`.
 event OwnershipTransferred:
@@ -864,6 +884,7 @@ def _set_token_uri(token_id: uint256, token_uri: String[432]):
     """
     assert self._exists(token_id), "ERC721URIStorage: URI set of nonexistent token"
     self._token_uris[token_id] = token_uri
+    log MetadataUpdate(token_id)
 
 
 @internal
diff --git a/src/tokens/interfaces/IERC4906.vy b/src/tokens/interfaces/IERC4906.vy
new file mode 100644
index 00000000..a9d04e2f
--- /dev/null
+++ b/src/tokens/interfaces/IERC4906.vy
@@ -0,0 +1,53 @@
+# @version ^0.3.7
+"""
+@title EIP-4906 Interface Definition
+@license GNU Affero General Public License v3.0
+@author pcaversaccio
+@notice The ERC-4906 standard is an extension of EIP-721.
+        It adds a `MetadataUpdate` event to EIP-721 tokens.
+        The ERC-165 identifier for this interface is 0x49064906.
+        For more details, please refer to:
+        https://eips.ethereum.org/EIPS/eip-4906.
+
+        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 the metadata of a token is changed.
+# Thus, third-party platforms, such as NFT marketplaces,
+# can update the images and associated attributes of the
+# NFT in a timely manner.
+event MetadataUpdate:
+    _tokenId: uint256
+
+
+# @dev Emitted when the metadata of a range of tokens is
+# changed. Thus, third-party platforms, such as NFT marketplaces,
+# can update the images and associated attributes of the
+# NFTs in a timely manner.
+event BatchMetadataUpdate:
+    _fromTokenId: uint256
+    _toTokenId: 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)


Test patch:
diff --git a/lib/prb-test b/lib/prb-test
index 8201f478..2c767827 160000
--- a/lib/prb-test
+++ b/lib/prb-test
@@ -1,1 +1,1 @@
-Subproject commit 8201f4782761ebe1f4f4f8f1999e2f85865de345
+Subproject commit 2c7678271c450c88e76cf50d3b8a9314344a0475
diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol
index fed45792..c67151de 100644
--- a/test/tokens/ERC721.t.sol
+++ b/test/tokens/ERC721.t.sol
@@ -75,6 +75,8 @@ contract ERC721Test is Test {
         address indexed newOwner
     );
 
+    event MetadataUpdate(uint256 tokenId);
+
     event RoleMinterChanged(address indexed minter, bool status);
 
     /**
@@ -576,6 +578,7 @@ contract ERC721Test is Test {
         assertTrue(
             ERC721Extended.supportsInterface(type(IERC4494).interfaceId)
         );
+        assertTrue(ERC721Extended.supportsInterface(0x49064906));
     }
 
     function testSupportsInterfaceGasCost() public {
@@ -1425,12 +1428,18 @@ contract ERC721Test is Test {
         vm.startPrank(deployer);
         vm.expectEmit(true, true, true, false);
         emit Transfer(zeroAddress, owner, tokenId);
+        vm.expectEmit(false, false, false, true);
+        emit MetadataUpdate(tokenId);
         ERC721Extended.safe_mint(owner, uri1);
         vm.expectEmit(true, true, true, false);
         emit Transfer(zeroAddress, owner, tokenId + 1);
+        vm.expectEmit(false, false, false, true);
+        emit MetadataUpdate(tokenId + 1);
         ERC721Extended.safe_mint(owner, uri2);
         vm.expectEmit(true, true, true, false);
         emit Transfer(zeroAddress, owner, tokenId + 2);
+        vm.expectEmit(false, false, false, true);
+        emit MetadataUpdate(tokenId + 2);
         ERC721Extended.safe_mint(owner, uri3);
         vm.stopPrank();
 
@@ -1443,6 +1452,8 @@ contract ERC721Test is Test {
         vm.startPrank(deployer);
         vm.expectEmit(true, true, true, false);
         emit Transfer(zeroAddress, owner, tokenId + 3);
+        vm.expectEmit(false, false, false, true);
+        emit MetadataUpdate(tokenId + 3);
         ERC721Extended.safe_mint(owner, "");
         vm.stopPrank();
         assertEq(ERC721Extended.balanceOf(owner), 3);
@@ -1460,9 +1471,13 @@ contract ERC721Test is Test {
         vm.startPrank(deployer);
         vm.expectEmit(true, true, true, false);
         emit Transfer(zeroAddress, owner, tokenId);
+        vm.expectEmit(false, false, false, true);
+        emit MetadataUpdate(tokenId);
         ERC721Extended.safe_mint(owner, uri1);
         vm.expectEmit(true, true, true, false);
         emit Transfer(zeroAddress, owner, tokenId + 1);
+        vm.expectEmit(false, false, false, true);
+        emit MetadataUpdate(tokenId + 1);
         ERC721Extended.safe_mint(owner, uri2);
 
         /**
@@ -2267,6 +2282,8 @@ contract ERC721Test is Test {
         for (uint256 i; i < owners.length; ++i) {
             vm.expectEmit(true, true, true, false);
             emit Transfer(zeroAddress, owners[i], i);
+            vm.expectEmit(false, false, false, true);
+            emit MetadataUpdate(i);
             ERC721Extended.safe_mint(owners[i], uri);
             assertGe(ERC721Extended.balanceOf(owners[i]), 1);
         }