From 5507e5d25b3b5b5d97447e5b1341b7aa80a5e91a Mon Sep 17 00:00:00 2001 From: gpylypchuk Date: Thu, 15 Feb 2024 16:02:38 -0300 Subject: [PATCH 1/3] feat: invalidate nonce --- src/NftReward.sol | 9 +++++++++ test/NftReward.t.sol | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/NftReward.sol b/src/NftReward.sol index c4c54cf..f1e6017 100644 --- a/src/NftReward.sol +++ b/src/NftReward.sol @@ -214,6 +214,15 @@ contract NftReward is Initializable, ERC721Upgradeable, OwnableUpgradeable, Paus function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + /** + * @notice Invalidates nonce value to prevent mint request reusage + * @param _nonceValue Nonce value to invalidate + */ + + function invalidateNonce(uint256 _nonceValue) external onlyOwner { + nonceRedeemed[_nonceValue] = true; + } + //==================== // Internal methods //==================== diff --git a/test/NftReward.t.sol b/test/NftReward.t.sol index a3a798b..a345250 100644 --- a/test/NftReward.t.sol +++ b/test/NftReward.t.sol @@ -527,4 +527,48 @@ contract NftRewardTest is Test { // after assertFalse(nftReward.paused()); } + + function testInvalidateNonce_ShouldInvalidateNonce() public { + // prepare arbitrary data keys + bytes32[] memory keys = new bytes32[](1); + keys[0] = keccak256("GITHUB_ORGANIZATION_NAME"); + // prepare arbitrary data values + string[] memory values = new string[](1); + values[0] = "ubiquity"; + // prepare mint request + NftReward.MintRequest memory mintRequest = NftReward.MintRequest({ + beneficiary: user1, + deadline: block.timestamp + 1, + keys: keys, + nonce: 1, + values: values + }); + // get mint request digest which should be signed + bytes32 digest = nftReward.getMintRequestDigest(mintRequest); + // minter signs mint request digest + (uint8 v, bytes32 r, bytes32 s) = vm.sign(minterPrivateKey, digest); + // get minter's signature + bytes memory signature = abi.encodePacked(r, s, v); + + uint tokenId = 0; + + // before + vm.expectRevert(); + nftReward.ownerOf(tokenId); + assertEq(nftReward.nonceRedeemed(1), false); + assertEq(nftReward.tokenDataKeyExists(keccak256("GITHUB_ORGANIZATION_NAME")), false); + + // owner invalidates + vm.prank(owner); + nftReward.invalidateNonce(1); + + // user try to mint + vm.prank(user1); + vm.expectRevert("Already minted"); + nftReward.safeMint(mintRequest, signature); + + // after + assertEq(nftReward.nonceRedeemed(1), true); + assertEq(nftReward.tokenIdCounter(), 0); + } } From 75c78bad13390de2858e26b998a2f57d224d24ec Mon Sep 17 00:00:00 2001 From: gpylypchuk Date: Thu, 15 Feb 2024 16:03:17 -0300 Subject: [PATCH 2/3] fix: typo --- src/NftReward.sol | 2 -- test/NftReward.t.sol | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/NftReward.sol b/src/NftReward.sol index f1e6017..8bb16ef 100644 --- a/src/NftReward.sol +++ b/src/NftReward.sol @@ -211,14 +211,12 @@ contract NftReward is Initializable, ERC721Upgradeable, OwnableUpgradeable, Paus * @notice Upgrades contract to new implementation * @param newImplementation New implementation address */ - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} /** * @notice Invalidates nonce value to prevent mint request reusage * @param _nonceValue Nonce value to invalidate */ - function invalidateNonce(uint256 _nonceValue) external onlyOwner { nonceRedeemed[_nonceValue] = true; } diff --git a/test/NftReward.t.sol b/test/NftReward.t.sol index a345250..79217ea 100644 --- a/test/NftReward.t.sol +++ b/test/NftReward.t.sol @@ -529,7 +529,7 @@ contract NftRewardTest is Test { } function testInvalidateNonce_ShouldInvalidateNonce() public { - // prepare arbitrary data keys + // prepare arbitrary data keys bytes32[] memory keys = new bytes32[](1); keys[0] = keccak256("GITHUB_ORGANIZATION_NAME"); // prepare arbitrary data values From 6ce539b027a8893b3691d57f6ea86985d740c1fc Mon Sep 17 00:00:00 2001 From: gpylypchuk Date: Fri, 16 Feb 2024 16:28:04 -0300 Subject: [PATCH 3/3] add require & test --- src/NftReward.sol | 1 + test/NftReward.t.sol | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/NftReward.sol b/src/NftReward.sol index 8bb16ef..9176a92 100644 --- a/src/NftReward.sol +++ b/src/NftReward.sol @@ -218,6 +218,7 @@ contract NftReward is Initializable, ERC721Upgradeable, OwnableUpgradeable, Paus * @param _nonceValue Nonce value to invalidate */ function invalidateNonce(uint256 _nonceValue) external onlyOwner { + require(!nonceRedeemed[_nonceValue], "Already minted"); nonceRedeemed[_nonceValue] = true; } diff --git a/test/NftReward.t.sol b/test/NftReward.t.sol index 79217ea..82b9042 100644 --- a/test/NftReward.t.sol +++ b/test/NftReward.t.sol @@ -571,4 +571,52 @@ contract NftRewardTest is Test { assertEq(nftReward.nonceRedeemed(1), true); assertEq(nftReward.tokenIdCounter(), 0); } + + function testRequireInvalidateNonce_ShouldRevert_IfNonceIsRedeemed() public { + // prepare arbitrary data keys + bytes32[] memory keys = new bytes32[](1); + keys[0] = keccak256("GITHUB_ORGANIZATION_NAME"); + // prepare arbitrary data values + string[] memory values = new string[](1); + values[0] = "ubiquity"; + // prepare mint request + NftReward.MintRequest memory mintRequest = NftReward.MintRequest({ + beneficiary: user1, + deadline: block.timestamp + 1, + keys: keys, + nonce: 1, + values: values + }); + // get mint request digest which should be signed + bytes32 digest = nftReward.getMintRequestDigest(mintRequest); + // minter signs mint request digest + (uint8 v, bytes32 r, bytes32 s) = vm.sign(minterPrivateKey, digest); + // get minter's signature + bytes memory signature = abi.encodePacked(r, s, v); + + uint tokenId = 0; + + // before + vm.expectRevert(); + nftReward.ownerOf(tokenId); + assertEq(nftReward.nonceRedeemed(1), false); + assertEq(nftReward.tokenDataKeyExists(keccak256("GITHUB_ORGANIZATION_NAME")), false); + + // user1 mints + vm.prank(user1); + nftReward.safeMint(mintRequest, signature); + + // owner try to invalidate + vm.prank(owner); + vm.expectRevert("Already minted"); + nftReward.invalidateNonce(1); + + // after + assertEq(nftReward.nonceRedeemed(1), true); + assertEq(nftReward.tokenDataKeys(0), keccak256("GITHUB_ORGANIZATION_NAME")); + assertEq(nftReward.tokenDataKeyExists(keccak256("GITHUB_ORGANIZATION_NAME")), true); + assertEq(nftReward.ownerOf(tokenId), user1); + assertEq(nftReward.tokenIdCounter(), 1); + assertEq(nftReward.tokenData(0, keccak256("GITHUB_ORGANIZATION_NAME")), "ubiquity"); + } }