diff --git a/src/dynamic-traits/DynamicTraits.sol b/src/dynamic-traits/DynamicTraits.sol index bde89e8..88cb176 100644 --- a/src/dynamic-traits/DynamicTraits.sol +++ b/src/dynamic-traits/DynamicTraits.sol @@ -7,142 +7,85 @@ import {IERC7496} from "./interfaces/IERC7496.sol"; abstract contract DynamicTraits is IERC7496 { using EnumerableSet for EnumerableSet.Bytes32Set; - ///@notice Thrown when trying to delete a trait that has not been set - error TraitNotSet(uint256 tokenId, bytes32 traitKey); - ///@notice Thrown when trying to set a trait explicitly to the zero value hash - error TraitCannotBeZeroValueHash(); - ///@notice Thrown when a new trait value is not different from the existing value + /// @notice Thrown when a new trait value is not different from the existing value error TraitValueUnchanged(); - bytes32 constant ZERO_VALUE = keccak256("DYNAMIC_TRAITS_ZERO_VALUE"); - ///@notice An enumerable set of all trait keys that have been set + /// @notice An enumerable set of all trait keys that have been set EnumerableSet.Bytes32Set internal _traitKeys; - ///@notice A mapping of token ID to a mapping of trait key to trait value + + /// @notice A mapping of token ID to a mapping of trait key to trait value mapping(uint256 tokenId => mapping(bytes32 traitKey => bytes32 traitValue)) internal _traits; - ///@notice An offchain string URI that points to a JSON file containing trait labels - string internal _traitLabelsURI; - function setTrait(bytes32 traitKey, uint256 tokenId, bytes32 trait) external virtual; - function deleteTrait(bytes32 traitKey, uint256 tokenId) external virtual; + /// @notice An offchain string URI that points to a JSON file containing trait metadata + string internal _traitMetadataURI; /** - * @notice Get the value of a trait for a given token ID. Reverts if the trait is not set. - * @param traitKey The trait key to get the value of + * @notice Get the value of a trait for a given token ID. * @param tokenId The token ID to get the trait value for + * @param traitKey The trait key to get the value of */ - function getTraitValue(bytes32 traitKey, uint256 tokenId) public view virtual returns (bytes32) { - bytes32 value = _traits[tokenId][traitKey]; - // Revert if the trait is not set - if (value == bytes32(0)) { - revert TraitNotSet(tokenId, traitKey); - } else if (value == ZERO_VALUE) { - // check for zero value hash; return 0 if so - return bytes32(0); - } else { - // otherwise return normal value - return value; - } + function getTraitValue(uint256 tokenId, bytes32 traitKey) public view virtual returns (bytes32 traitValue) { + traitValue = _traits[tokenId][traitKey]; } /** - * @notice Get the values of a trait for a given list of token IDs. Reverts if the trait is not set on any single token. - * @param traitKey The trait key to get the value of - * @param tokenIds The token IDs to get the trait values for + * @notice Get the values of traits for a given token ID. + * @param tokenId The token ID to get the trait values for + * @param traitKeys The trait keys to get the values of */ - function getTraitValues(bytes32 traitKey, uint256[] calldata tokenIds) - external + function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) + public view virtual returns (bytes32[] memory traitValues) { - uint256 length = tokenIds.length; - bytes32[] memory result = new bytes32[](length); - for (uint256 i = 0; i < length; i++) { - uint256 tokenId = tokenIds[i]; - result[i] = getTraitValue(traitKey, tokenId); + uint256 length = traitKeys.length; + traitValues = new bytes32[](length); + for (uint256 i = 0; i < length;) { + bytes32 traitKey = traitKeys[i]; + traitValues[i] = getTraitValue(tokenId, traitKey); + unchecked { + ++i; + } } - return result; } /** - * @notice Get the total number of trait keys that have been set + * @notice Get the URI for the trait metadata */ - function getTotalTraitKeys() external view virtual returns (uint256) { - return _traitKeys.length(); - } - - /** - * @notice Get the trait key at a given index - * @param index The index of the trait key to get - */ - function getTraitKeyAt(uint256 index) external view virtual returns (bytes32 traitKey) { - return _traitKeys.at(index); - } - - /** - * @notice Get the trait keys that have been set. May revert if there are too many trait keys. - */ - function getTraitKeys() external view virtual returns (bytes32[] memory traitKeys) { - return _traitKeys._inner._values; - } - - /** - * @notice Get the URI for the trait labels - */ - function getTraitLabelsURI() external view virtual returns (string memory labelsURI) { - return _traitLabelsURI; + function getTraitMetadataURI() external view virtual returns (string memory labelsURI) { + return _traitMetadataURI; } /** * @notice Set the value of a trait for a given token ID. If newTrait is bytes32(0), sets the zero value hash. * Reverts if the trait value is the zero value hash. - * @param traitKey The trait key to set the value of * @param tokenId The token ID to set the trait value for - * @param newTrait The new trait value to set + * @param traitKey The trait key to set the value of + * @param newValue The new trait value to set */ - function _setTrait(bytes32 traitKey, uint256 tokenId, bytes32 newTrait) internal { + function _setTrait(uint256 tokenId, bytes32 traitKey, bytes32 newValue) internal { bytes32 existingValue = _traits[tokenId][traitKey]; - if (newTrait == bytes32(0)) { - newTrait = ZERO_VALUE; - } else if (newTrait == ZERO_VALUE) { - revert InvalidTraitValue(traitKey, newTrait); - } - - if (existingValue == newTrait) { + if (existingValue == newValue) { revert TraitValueUnchanged(); } // no-op if exists _traitKeys.add(traitKey); - _traits[tokenId][traitKey] = newTrait; - - emit TraitUpdated(traitKey, tokenId, newTrait); - } - - /** - * @notice Delete the value of a trait for a given token ID. - * @param traitKey The trait key to delete the value of - * @param tokenId The token ID to delete the trait value for - */ - function _deleteTrait(bytes32 traitKey, uint256 tokenId) internal { - bytes32 existingValue = _traits[tokenId][traitKey]; - if (existingValue == bytes32(0)) { - revert TraitValueUnchanged(); - } + _traits[tokenId][traitKey] = newValue; - _traits[tokenId][traitKey] = bytes32(0); - emit TraitUpdated(traitKey, tokenId, bytes32(0)); + emit TraitUpdated(traitKey, tokenId, newValue); } /** - * @notice Set the URI for the trait labels + * @notice Set the URI for the trait metadata * @param uri The new URI to set */ - function _setTraitLabelsURI(string calldata uri) internal virtual { - _traitLabelsURI = uri; - emit TraitLabelsURIUpdated(uri); + function _setTraitMetadataURI(string calldata uri) internal virtual { + _traitMetadataURI = uri; + emit TraitMetadataURIUpdated(); } function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { diff --git a/src/dynamic-traits/ERC721DynamicTraits.sol b/src/dynamic-traits/ERC721DynamicTraits.sol index bd8988b..49e3bdd 100644 --- a/src/dynamic-traits/ERC721DynamicTraits.sol +++ b/src/dynamic-traits/ERC721DynamicTraits.sol @@ -7,19 +7,44 @@ import {DynamicTraits} from "./DynamicTraits.sol"; contract ERC721DynamicTraits is DynamicTraits, Ownable, ERC721 { constructor() Ownable(msg.sender) ERC721("ERC721DynamicTraits", "ERC721DT") { - _traitLabelsURI = "https://example.com"; + _traitMetadataURI = "https://example.com"; } - function setTrait(bytes32 traitKey, uint256 tokenId, bytes32 value) external virtual override onlyOwner { - _setTrait(traitKey, tokenId, value); + function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) external virtual override onlyOwner { + // Revert if the token doesn't exist. + _requireOwned(tokenId); + + _setTrait(tokenId, traitKey, value); } - function deleteTrait(bytes32 traitKey, uint256 tokenId) external virtual override onlyOwner { - _deleteTrait(traitKey, tokenId); + function getTraitValue(uint256 tokenId, bytes32 traitKey) + public + view + virtual + override + returns (bytes32 traitValue) + { + // Revert if the token doesn't exist. + _requireOwned(tokenId); + + return DynamicTraits.getTraitValue(tokenId, traitKey); + } + + function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) + public + view + virtual + override + returns (bytes32[] memory traitValues) + { + // Revert if the token doesn't exist. + _requireOwned(tokenId); + + return DynamicTraits.getTraitValues(tokenId, traitKeys); } - function setTraitLabelsURI(string calldata uri) external onlyOwner { - _setTraitLabelsURI(uri); + function setTraitMetadataURI(string calldata uri) external onlyOwner { + _setTraitMetadataURI(uri); } function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, DynamicTraits) returns (bool) { diff --git a/src/dynamic-traits/ERC721OnchainTraits.sol b/src/dynamic-traits/ERC721OnchainTraits.sol index ed6e996..7744754 100644 --- a/src/dynamic-traits/ERC721OnchainTraits.sol +++ b/src/dynamic-traits/ERC721OnchainTraits.sol @@ -7,7 +7,40 @@ import {DynamicTraits} from "./DynamicTraits.sol"; contract ERC721OnchainTraits is OnchainTraits, ERC721 { constructor() ERC721("ERC721DynamicTraits", "ERC721DT") { - _traitLabelsURI = "https://example.com"; + _traitMetadataURI = "https://example.com"; + } + + function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) external virtual override onlyOwner { + // Revert if the token doesn't exist. + _requireOwned(tokenId); + + _setTrait(tokenId, traitKey, value); + } + + function getTraitValue(uint256 tokenId, bytes32 traitKey) + public + view + virtual + override + returns (bytes32 traitValue) + { + // Revert if the token doesn't exist. + _requireOwned(tokenId); + + return DynamicTraits.getTraitValue(tokenId, traitKey); + } + + function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) + public + view + virtual + override + returns (bytes32[] memory traitValues) + { + // Revert if the token doesn't exist. + _requireOwned(tokenId); + + return DynamicTraits.getTraitValues(tokenId, traitKeys); } function _isOwnerOrApproved(uint256 tokenId, address addr) internal view virtual override returns (bool) { diff --git a/src/dynamic-traits/OnchainTraits.sol b/src/dynamic-traits/OnchainTraits.sol index 1169d8d..e45c92e 100644 --- a/src/dynamic-traits/OnchainTraits.sol +++ b/src/dynamic-traits/OnchainTraits.sol @@ -26,8 +26,6 @@ abstract contract OnchainTraits is Ownable, DynamicTraits { error InsufficientPrivilege(); ///@notice Thrown when trying to set a trait that does not exist error TraitDoesNotExist(bytes32 traitKey); - ///@notice Thrown when trying to delete a trait that is required to have a value. - error TraitIsRequired(); ///@notice a mapping of traitKey to TraitLabelStorage metadata mapping(bytes32 traitKey => TraitLabelStorage traitLabelStorage) public traitLabelStorage; @@ -91,16 +89,16 @@ abstract contract OnchainTraits is Ownable, DynamicTraits { // LABELS URI /** - * @notice Get the onchain URI for the trait labels, encoded as a JSON data URI + * @notice Get the onchain URI for the trait metadata, encoded as a JSON data URI */ - function getTraitLabelsURI() external view virtual override returns (string memory) { - return Metadata.jsonDataURI(_getTraitLabelsJson()); + function getTraitMetadataURI() external view virtual override returns (string memory) { + return Metadata.jsonDataURI(_getTraitMetadataJson()); } /** - * @notice Get the raw JSON for the trait labels + * @notice Get the raw JSON for the trait metadata */ - function _getTraitLabelsJson() internal view returns (string memory) { + function _getTraitMetadataJson() internal view returns (string memory) { bytes32[] memory keys = _traitKeys.values(); return TraitLabelStorageLib.toLabelJson(traitLabelStorage, keys); } @@ -109,11 +107,11 @@ abstract contract OnchainTraits is Ownable, DynamicTraits { * @notice Set a trait for a given traitKey and tokenId. Checks that the caller has permission to set the trait, * and, if the TraitLabel specifies that the trait value must be validated, checks that the trait value * is valid. - * @param traitKey The trait key to get the value of * @param tokenId The token ID to get the trait value for - * @param trait The trait value + * @param traitKey The trait key to get the value of + * @param newValue The new trait value */ - function setTrait(bytes32 traitKey, uint256 tokenId, bytes32 trait) external virtual override { + function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 newValue) external virtual override { TraitLabelStorage memory labelStorage = traitLabelStorage[traitKey]; StoredTraitLabel storedTraitLabel = labelStorage.storedLabel; if (!StoredTraitLabelLib.exists(storedTraitLabel)) { @@ -122,28 +120,9 @@ abstract contract OnchainTraits is Ownable, DynamicTraits { _verifySetterPrivilege(labelStorage.allowedEditors, tokenId); if (labelStorage.valuesRequireValidation) { - TraitLabelLib.validateAcceptableValue(StoredTraitLabelLib.load(storedTraitLabel), traitKey, trait); - } - _setTrait(traitKey, tokenId, trait); - } - - /** - * @notice Delete a trait for a given traitKey and tokenId. Checks that the caller has permission to delete the trait, - * and that the trait is not required to have a value. - * @param traitKey The trait key to delete the value of - * @param tokenId The token ID to delete the trait value for - */ - function deleteTrait(bytes32 traitKey, uint256 tokenId) external virtual override { - TraitLabelStorage memory labelStorage = traitLabelStorage[traitKey]; - StoredTraitLabel storedTraitLabel = labelStorage.storedLabel; - if (!StoredTraitLabelLib.exists(storedTraitLabel)) { - revert TraitDoesNotExist(traitKey); + TraitLabelLib.validateAcceptableValue(StoredTraitLabelLib.load(storedTraitLabel), traitKey, newValue); } - _verifySetterPrivilege(labelStorage.allowedEditors, tokenId); - if (labelStorage.required) { - revert TraitIsRequired(); - } - _deleteTrait(traitKey, tokenId); + _setTrait(tokenId, traitKey, newValue); } /** @@ -227,9 +206,6 @@ abstract contract OnchainTraits is Ownable, DynamicTraits { bytes32 trait = _traits[tokenId][key]; // check that the trait is set, otherwise, skip it if (trait != bytes32(0)) { - if (trait == ZERO_VALUE) { - trait = bytes32(0); - } attributes[num] = TraitLabelStorageLib.toAttributeJson(traitLabelStorage, key, trait); unchecked { ++num; diff --git a/src/dynamic-traits/interfaces/IERC7496.sol b/src/dynamic-traits/interfaces/IERC7496.sol index 9d75aa7..be74b95 100644 --- a/src/dynamic-traits/interfaces/IERC7496.sol +++ b/src/dynamic-traits/interfaces/IERC7496.sol @@ -4,26 +4,20 @@ pragma solidity ^0.8.19; import {IERC165} from "forge-std/interfaces/IERC165.sol"; interface IERC7496 is IERC165 { - error InvalidTraitValue(bytes32 traitKey, bytes32 traitValue); - /* Events */ event TraitUpdated(bytes32 indexed traitKey, uint256 indexed tokenId, bytes32 trait); - event TraitUpdatedBulkConsecutive(bytes32 indexed traitKeyPattern, uint256 fromTokenId, uint256 toTokenId); + event TraitUpdatedBulkRange(bytes32 indexed traitKeyPattern, uint256 fromTokenId, uint256 toTokenId); event TraitUpdatedBulkList(bytes32 indexed traitKeyPattern, uint256[] tokenIds); - event TraitLabelsURIUpdated(string uri); + event TraitMetadataURIUpdated(); /* Getters */ - function getTraitValue(bytes32 traitKey, uint256 tokenId) external view returns (bytes32 traitValue); - function getTraitValues(bytes32 traitKey, uint256[] calldata tokenIds) + function getTraitValue(uint256 tokenId, bytes32 traitKey) external view returns (bytes32 traitValue); + function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) external view returns (bytes32[] memory traitValues); - function getTotalTraitKeys() external view returns (uint256); - function getTraitKeys() external view returns (bytes32[] memory traitKeys); - function getTraitKeyAt(uint256 index) external view returns (bytes32 traitKey); - function getTraitLabelsURI() external view returns (string memory labelsURI); + function getTraitMetadataURI() external view returns (string memory uri); /* Setters */ - function setTrait(bytes32 traitKey, uint256 tokenId, bytes32 value) external; - function deleteTrait(bytes32 traitKey, uint256 tokenId) external; + function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) external; } diff --git a/src/interfaces/IERCDynamicMetadata.sol b/src/interfaces/IERCDynamicMetadata.sol deleted file mode 100644 index 396a492..0000000 --- a/src/interfaces/IERCDynamicMetadata.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -interface IERC7496 { - event TraitUpdated(uint256 indexed tokenId, bytes32 indexed traitKey, bytes32 oldValue, bytes32 newValue); - event TraitBulkUpdated(uint256 indexed fromTokenId, uint256 indexed toTokenId, bytes32 indexed traitKeyPattern); - - function getTrait(uint256 tokenId, bytes32 traitKey) external view returns (bytes32); -} diff --git a/test/dynamic-traits/ERC721DynamicTraits.t.sol b/test/dynamic-traits/ERC721DynamicTraits.t.sol index 2fa576e..cb94565 100644 --- a/test/dynamic-traits/ERC721DynamicTraits.t.sol +++ b/test/dynamic-traits/ERC721DynamicTraits.t.sol @@ -2,22 +2,32 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; +import {IERC721Errors} from "openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol"; +import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; import {IERC7496} from "src/dynamic-traits/interfaces/IERC7496.sol"; import {ERC721DynamicTraits, DynamicTraits} from "src/dynamic-traits/ERC721DynamicTraits.sol"; import {Solarray} from "solarray/Solarray.sol"; +contract ERC721DynamicTraitsMintable is ERC721DynamicTraits { + constructor() ERC721DynamicTraits() {} + + function mint(address to, uint256 tokenId) public onlyOwner { + _mint(to, tokenId); + } +} + contract ERC721DynamicTraitsTest is Test { - ERC721DynamicTraits token; + ERC721DynamicTraitsMintable token; /* Events */ event TraitUpdated(bytes32 indexed traitKey, uint256 indexed tokenId, bytes32 value); event TraitUpdatedBulkConsecutive(bytes32 indexed traitKeyPattern, uint256 fromTokenId, uint256 toTokenId); event TraitUpdatedBulkList(bytes32 indexed traitKeyPattern, uint256[] tokenIds); - event TraitLabelsURIUpdated(string uri); + event TraitMetadataURIUpdated(); function setUp() public { - token = new ERC721DynamicTraits(); + token = new ERC721DynamicTraitsMintable(); } function testSupportsInterfaceId() public { @@ -28,140 +38,90 @@ contract ERC721DynamicTraitsTest is Test { bytes32 key = bytes32("test.key"); bytes32 value = bytes32("foo"); uint256 tokenId = 12345; + token.mint(address(this), tokenId); vm.expectEmit(true, true, true, true); emit TraitUpdated(key, tokenId, value); - token.setTrait(key, tokenId, value); + token.setTrait(tokenId, key, value); - assertEq(token.getTraitValue(key, tokenId), value); + assertEq(token.getTraitValue(tokenId, key), value); } function testOnlyOwnerCanSetValues() public { address alice = makeAddr("alice"); vm.prank(alice); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice)); - token.setTrait(bytes32("test"), 0, bytes32("test")); + token.setTrait(0, bytes32("test"), bytes32("test")); } function testSetTrait_Unchanged() public { bytes32 key = bytes32("test.key"); - bytes32 value1 = bytes32("foo"); - uint256 tokenId1 = 1; + bytes32 value = bytes32("foo"); + uint256 tokenId = 1; + token.mint(address(this), tokenId); - token.setTrait(key, tokenId1, value1); + token.setTrait(tokenId, key, value); vm.expectRevert(DynamicTraits.TraitValueUnchanged.selector); - token.setTrait(key, tokenId1, value1); + token.setTrait(tokenId, key, value); } function testGetTraitValues() public { - bytes32 key = bytes32("test.key"); + bytes32 key1 = bytes32("test.key.one"); + bytes32 key2 = bytes32("test.key.two"); bytes32 value1 = bytes32("foo"); bytes32 value2 = bytes32("bar"); - uint256 tokenId1 = 1; - uint256 tokenId2 = 2; + uint256 tokenId = 1; + token.mint(address(this), tokenId); - token.setTrait(key, tokenId1, value1); - token.setTrait(key, tokenId2, value2); + token.setTrait(tokenId, key1, value1); + token.setTrait(tokenId, key2, value2); - bytes32[] memory values = token.getTraitValues(key, Solarray.uint256s(tokenId1, tokenId2)); + bytes32[] memory values = token.getTraitValues(tokenId, Solarray.bytes32s(key1, key2)); assertEq(values[0], value1); assertEq(values[1], value2); } - function testGetTotalTraitKeys() public { - bytes32 key1 = bytes32("test.key"); - bytes32 key2 = bytes32("test.key2"); - bytes32 value1 = bytes32("foo"); - bytes32 value2 = bytes32("bar"); - uint256 tokenId1 = 1; - uint256 tokenId2 = 2; - - assertEq(token.getTotalTraitKeys(), 0); - - token.setTrait(key1, tokenId1, value1); - assertEq(token.getTotalTraitKeys(), 1); - - token.setTrait(key2, tokenId2, value2); - assertEq(token.getTotalTraitKeys(), 2); - } - - function testGetTraitKeyAt() public { - bytes32 key1 = bytes32("test.key"); - bytes32 key2 = bytes32("test.key2"); - bytes32 value1 = bytes32("foo"); - bytes32 value2 = bytes32("bar"); - uint256 tokenId1 = 1; - uint256 tokenId2 = 2; - - token.setTrait(key1, tokenId1, value1); - - token.setTrait(key2, tokenId2, value2); - - assertEq(token.getTraitKeyAt(0), key1); - assertEq(token.getTraitKeyAt(1), key2); - } - - function testGetTraitKeys() public { - bytes32 key1 = bytes32("test.key"); - bytes32 key2 = bytes32("test.key2"); - bytes32 value1 = bytes32("foo"); - bytes32 value2 = bytes32("bar"); - uint256 tokenId1 = 1; - uint256 tokenId2 = 2; - - token.setTrait(key1, tokenId1, value1); - token.setTrait(key2, tokenId2, value2); - - bytes32[] memory traitKeys = token.getTraitKeys(); - assertEq(traitKeys[0], key1); - assertEq(traitKeys[1], key2); - } - - function testGetAndSetTraitLabelsURI() public { + function testGetAndSetTraitMetadataURI() public { string memory uri = "https://example.com/labels.json"; - token.setTraitLabelsURI(uri); - assertEq(token.getTraitLabelsURI(), uri); + token.setTraitMetadataURI(uri); + assertEq(token.getTraitMetadataURI(), uri); vm.prank(address(0x1234)); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(0x1234))); - token.setTraitLabelsURI(uri); + token.setTraitMetadataURI(uri); } - function testGetTraitValue_TraitNotSet() public { + function testGetTraitValue_NonexistantToken() public { bytes32 key = bytes32("test.key"); + bytes32 value = bytes32(uint256(1)); uint256 tokenId = 1; - vm.expectRevert(abi.encodeWithSelector(DynamicTraits.TraitNotSet.selector, tokenId, key)); - token.getTraitValue(key, tokenId); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, tokenId)); + token.setTrait(tokenId, key, value); + + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, tokenId)); + token.getTraitValue(tokenId, key); + + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, tokenId)); + token.getTraitValues(tokenId, Solarray.bytes32s(key)); } function testGetTraitValue_ZeroValue() public { bytes32 key = bytes32("test.key"); uint256 tokenId = 1; + token.mint(address(this), tokenId); - token.setTrait(key, tokenId, bytes32(0)); - bytes32 result = token.getTraitValue(key, tokenId); + bytes32 result = token.getTraitValue(tokenId, key); assertEq(result, bytes32(0), "should return bytes32(0)"); } function testGetTraitValues_ZeroValue() public { bytes32 key = bytes32("test.key"); uint256 tokenId = 1; + token.mint(address(this), tokenId); - token.setTrait(key, tokenId, bytes32(0)); - bytes32[] memory result = token.getTraitValues(key, Solarray.uint256s(tokenId)); + bytes32[] memory result = token.getTraitValues(tokenId, Solarray.bytes32s(key)); assertEq(result[0], bytes32(0), "should return bytes32(0)"); } - - function testSetTrait_ZeroValueHash() public { - bytes32 key = bytes32("test.key"); - uint256 tokenId = 1; - bytes32 badValue = keccak256("DYNAMIC_TRAITS_ZERO_VALUE"); - - vm.expectRevert(abi.encodeWithSelector(IERC7496.InvalidTraitValue.selector, key, badValue)); - token.setTrait(key, tokenId, badValue); - } - - function testdeleteTrait() public {} } diff --git a/test/dynamic-traits/ERC721OnchainTraits.t.sol b/test/dynamic-traits/ERC721OnchainTraits.t.sol index 722c6cc..1f3c63e 100644 --- a/test/dynamic-traits/ERC721OnchainTraits.t.sol +++ b/test/dynamic-traits/ERC721OnchainTraits.t.sol @@ -82,27 +82,23 @@ contract ERC721OnchainTraitsTest is Test { function testGetTraitLabelsURI() public { _setLabel(); assertEq( - token.getTraitLabelsURI(), + token.getTraitMetadataURI(), 'data:application/json;[{"traitKey":"test.key","fullTraitKey":"test.key","traitLabel":"Trait Key","acceptableValues":[],"fullTraitValues":[],"displayType":"string","editors":[0]}]' ); } function testSetTrait() public { _setLabel(); - // TODO: should token have to exist? - token.setTrait(bytes32("test.key"), 1, bytes32("foo")); - assertEq(token.getTraitValue(bytes32("test.key"), 1), bytes32("foo")); - - token.deleteTrait(bytes32("test.key"), 1); - vm.expectRevert(abi.encodeWithSelector(DynamicTraits.TraitNotSet.selector, 1, bytes32("test.key"))); - token.getTraitValue(bytes32("test.key"), 1); + token.mint(address(this)); + token.setTrait(1, bytes32("test.key"), bytes32("foo")); + assertEq(token.getTraitValue(1, bytes32("test.key")), bytes32("foo")); } function testStringURI() public { _setLabel(); token.mint(address(this)); - token.setTrait(bytes32("test.key"), 1, bytes32("foo")); - assertEq(token.getTraitValue(bytes32("test.key"), 1), bytes32("foo")); + token.setTrait(1, bytes32("test.key"), bytes32("foo")); + assertEq(token.getTraitValue(1, bytes32("test.key")), bytes32("foo")); assertEq( token.getStringURI(1), '{"name":"Example NFT #1","description":"This is an example NFT","image":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIiA+PHJlY3Qgd2lkdGg9IjUwMCIgaGVpZ2h0PSI1MDAiIGZpbGw9ImxpZ2h0Z3JheSIgLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSI0OCIgZmlsbD0iYmxhY2siID4xPC90ZXh0Pjwvc3ZnPg==","attributes":[{"trait_type":"Example Attribute","value":"Example Value"},{"trait_type":"Number","value":"1","display_type":"number"},{"trait_type":"Parity","value":"Odd"},{"trait_type":"Trait Key","value":"foo","display_type":"string"}]}'