diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index e856a5c2..8a24ee62 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -421127 \ No newline at end of file +421104 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index 81715a67..e62c334b 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79484 \ No newline at end of file +79506 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index bd214aa7..a0c684a2 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62372 \ No newline at end of file +62394 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index c0e053f6..2439b179 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45260 \ No newline at end of file +45282 \ No newline at end of file diff --git a/src/base/UnorderedNonce.sol b/src/base/UnorderedNonce.sol index a1efe769..75a34fe4 100644 --- a/src/base/UnorderedNonce.sol +++ b/src/base/UnorderedNonce.sol @@ -21,4 +21,11 @@ contract UnorderedNonce { uint256 flipped = nonces[owner][wordPos] ^= bit; if (flipped & bit == 0) revert NonceAlreadyUsed(); } + + /// @notice Revoke a nonce by spending it, preventing it from being used again + /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce + /// @dev payable so it can be multicalled with native-token related actions + function revokeNonce(uint256 nonce) external payable { + _useUnorderedNonce(msg.sender, nonce); + } } diff --git a/test/UnorderedNonce.t.sol b/test/UnorderedNonce.t.sol index 9683c763..d3939fd2 100644 --- a/test/UnorderedNonce.t.sol +++ b/test/UnorderedNonce.t.sol @@ -107,4 +107,16 @@ contract UnorderedNonceTest is Test { unorderedNonce.spendNonce(address(this), second); } } + + function test_fuzz_revokeNonce(uint256 nonce) public { + unorderedNonce.revokeNonce(nonce); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.revokeNonce(nonce); + } + + function test_fuzz_revokeNonce_twoNonces(uint256 first, uint256 second) public { + unorderedNonce.revokeNonce(first); + if (first == second) vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.revokeNonce(second); + } } diff --git a/test/erc721Permit/ERC721Permit.permitForAll.t.sol b/test/erc721Permit/ERC721Permit.permitForAll.t.sol index 6d5f4be3..26c97a46 100644 --- a/test/erc721Permit/ERC721Permit.permitForAll.t.sol +++ b/test/erc721Permit/ERC721Permit.permitForAll.t.sol @@ -297,6 +297,25 @@ contract ERC721PermitForAllTest is Test { vm.stopPrank(); } + /// @notice revoking a nonce prevents it from being used in permitForAll() + function test_fuzz_erc721PermitForAll_revokedNonceUsed(uint256 nonce) public { + // alice revokes the nonce + vm.prank(alice); + erc721Permit.revokeNonce(nonce); + + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitForAllDigest(bob, true, nonce, deadline); + // alice signs a permit for bob + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // Nonce does not work with permitForAll + vm.startPrank(bob); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); + vm.stopPrank(); + } + // Helpers related to permitForAll function _permitForAll(uint256 privateKey, address owner, address operator, bool approved, uint256 nonce) internal diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 6cf03c3f..2219e3f0 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -217,6 +217,27 @@ contract PermitTest is Test, PosmTestSetup { vm.stopPrank(); } + /// @notice revoking a nonce prevents it from being used in permit() + function test_fuzz_noPermit_revokeRevert(uint256 nonce) public { + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // alice revokes the nonce + vm.prank(alice); + lpm.revokeNonce(nonce); + + // alice gives bob spender permissions + bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + } + // Bob can use alice's signature to permit & decrease liquidity function test_permit_operatorSelfPermit() public { uint256 liquidityAlice = 1e18;