Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC721Permit #210

Merged
merged 47 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0c159e2
allow for nonsigners to call permit
saucepoint Jul 19, 2024
d13fbe7
forge fmt
saucepoint Jul 19, 2024
ff0b56e
test permit with multicall
saucepoint Jul 19, 2024
558b621
make DOMAIN_SEPARATOR immutable
saucepoint Jul 19, 2024
36ccc28
avoid chain fork replays
saucepoint Jul 19, 2024
86e520b
misc test cleanup
saucepoint Jul 19, 2024
e0ec23c
custom errors
saucepoint Jul 19, 2024
fc539e3
move magic hex to a constant
saucepoint Jul 19, 2024
095ad00
unpayable permit
saucepoint Jul 19, 2024
85390a6
use OZ EIP712
saucepoint Jul 20, 2024
b5d18dc
separate out UnorderedNonce into a reusable contract
saucepoint Jul 20, 2024
1521e70
move token URI to posm
saucepoint Jul 21, 2024
16939fb
add back in payable permit
saucepoint Jul 21, 2024
ed59b58
fix cherry picked commits
saucepoint Jul 26, 2024
b0559b8
remove public digest getter
saucepoint Jul 26, 2024
e458882
replace range with config naming
saucepoint Jul 26, 2024
88981c1
merge in main; regenerate gas
saucepoint Jul 30, 2024
9ba2556
deprecate old test: requiring permission to increase liq
saucepoint Jul 30, 2024
3cc8407
pr feedback
saucepoint Jul 30, 2024
a229cf8
borrow pertmi2 nonce tests for UnorderedNonce
saucepoint Jul 30, 2024
76a5e47
dedicated permit and approve testing for ERC721Permit
saucepoint Jul 30, 2024
cdde9e6
pr feedback: operator should be broadcaster of permit calls
saucepoint Jul 30, 2024
c0a26b8
reorganize permit hashing and verification
saucepoint Jul 30, 2024
834f331
merge in main; fix fuzz test for SelfPermit; regenerate gas
saucepoint Jul 30, 2024
019bad1
refactor ERC721Permit signature verification with generic signature c…
saucepoint Jul 30, 2024
0d445f2
remove deprecated library
saucepoint Jul 30, 2024
741e660
fix imports
saucepoint Jul 31, 2024
8dc0ca3
merge in main; regenerate gas
saucepoint Jul 31, 2024
85eae38
formatting
saucepoint Jul 31, 2024
ff5bb68
Merge branch 'main' into posm-erc721permit-signed
saucepoint Aug 1, 2024
d188aa2
merge main; regenerate gas
saucepoint Aug 1, 2024
600dd6d
pr feedback
saucepoint Aug 1, 2024
3cab51c
optimize nonce bit flipping
saucepoint Aug 1, 2024
b15da09
Merge branch 'main' into posm-erc721permit-signed
saucepoint Aug 1, 2024
750dcba
Merge branch 'main' into posm-erc721permit-signed
saucepoint Aug 2, 2024
5c566d6
merge in main; regenerate gas
saucepoint Aug 2, 2024
6eafa63
discard public PERMIT_TYPEHASH
saucepoint Aug 2, 2024
ce52a7f
merge in main; regenerate gas
saucepoint Aug 2, 2024
e58f516
Merge branch 'main' into posm-erc721permit-signed
saucepoint Aug 2, 2024
ff6e58c
renaming
saucepoint Aug 2, 2024
33832c0
library-ify bit flipping
saucepoint Aug 2, 2024
d59bd1d
merge in main; regenerate gas
saucepoint Aug 2, 2024
42c8268
merge in main; fix conflicts; regenerate gas
saucepoint Aug 2, 2024
57542bd
yall crazy for sending through the ringer
saucepoint Aug 2, 2024
778d7f3
merge in main; regenerate gas
saucepoint Aug 2, 2024
9021dd8
nits
saucepoint Aug 3, 2024
9732562
merge in main; regenerate gas
saucepoint Aug 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
46796
47094
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
46614
46912
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
129656
129887
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
122578
122808
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149762
150028
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140914
141180
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149762
150028
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decreaseLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
115305
115571
Original file line number Diff line number Diff line change
@@ -1 +1 @@
108206
108419
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_burnEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133522
133920
Original file line number Diff line number Diff line change
@@ -1 +1 @@
126261
126660
Original file line number Diff line number Diff line change
@@ -1 +1 @@
128021
128287
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151241
152144
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133041
133944
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133968
130109
Original file line number Diff line number Diff line change
@@ -1 +1 @@
170124
170803
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140581
140625
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
371276
372056
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
335976
336756
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_nativeWithSweep.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
344606
345288
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
313958
314738
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
314600
315380
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
240182
240962
Original file line number Diff line number Diff line change
@@ -1 +1 @@
369444
370062
Original file line number Diff line number Diff line change
@@ -1 +1 @@
319976
320756
Original file line number Diff line number Diff line change
@@ -1 +1 @@
415651
416476
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_permit.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
79585
79569
Original file line number Diff line number Diff line change
@@ -1 +1 @@
62497
62481
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_permit_twice.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
45397
45381
6 changes: 1 addition & 5 deletions src/base/ERC721Permit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ import {UnorderedNonce} from "./UnorderedNonce.sol";
abstract contract ERC721Permit is ERC721, IERC721Permit, EIP712, UnorderedNonce {
using SignatureVerification for bytes;

/// @inheritdoc IERC721Permit
/// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)");
bytes32 public constant override PERMIT_TYPEHASH = ERC721PermitHashLibrary.PERMIT_TYPEHASH;

/// @notice Computes the nameHash and versionHash
constructor(string memory name_, string memory symbol_, string memory version_)
ERC721(name_, symbol_)
EIP712(name_, version_)
{}

/// @inheritdoc IERC721Permit
function DOMAIN_SEPARATOR() public view returns (bytes32) {
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _domainSeparatorV4();
}

Expand Down
49 changes: 35 additions & 14 deletions src/base/UnorderedNonce.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,42 @@ contract UnorderedNonce {
/// @param owner address, the owner/signer of the nonce
/// @param nonce uint256, the nonce to consume
function _useUnorderedNonce(address owner, uint256 nonce) internal {
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
(uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);
uint256 bit = 1 << bitPos;
uint256 flipped = nonces[owner][wordPos] ^= bit;

if (flipped & bit == 0) revert NonceAlreadyUsed();
// consume the bit by flipping it in storage
// reverts if the bit was already spent
_flipBit(nonces[owner], nonce);
}

/// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces
/// @param nonce The nonce to get the associated word and bit positions
/// @return wordPos The word position or index into the nonceBitmap
/// @return bitPos The bit position
/// @dev The first 248 bits of the nonce value is the index of the desired bitmap
/// @dev The last 8 bits of the nonce value is the position of the bit in the bitmap
function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) {
wordPos = uint248(nonce >> 8);
bitPos = uint8(nonce);
function _flipBit(mapping(uint256 => uint256) storage bitmap, uint256 nonce) private {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this does more than flip the bit so we should maybe split out this code.

1 - use the nonce to extract the wordPos/the bitmap to operate on
2- flip bit

flipBit(nonce) makes it seem like the nonce thats being passed in is the bit thats being flipped which is not really how the data is used

// equivalent to:
saucepoint marked this conversation as resolved.
Show resolved Hide resolved
// uint256 wordPos = uint248(nonce >> 8);
// uint256 bitPos = uint8(nonce);
// uint256 bit = 1 << bitPos;
// uint256 flipped = nonces[owner][wordPos] ^= bit;
// if (flipped & bit == 0) revert NonceAlreadyUsed();

assembly ("memory-safe") {
// wordPos = uint248(nonce >> 8)
let wordPos := shr(8, nonce)

// bit = 1 << uint8(nonce)
let bit := shl(and(nonce, 0xFF), 1)

// slot of bitmap[wordPos] is keccak256(abi.encode(wordPos, bitmap.slot))
mstore(0, wordPos)
mstore(0x20, bitmap.slot)
let slot := keccak256(0, 0x40)

// uint256 previousBits = bitmap[wordPos]
let previousBits := sload(slot)

// revert if it's already been used
if eq(and(previousBits, bit), bit) {
mstore(0, 0x1fb09b80) // 4 bytes of NonceAlreadyUsed.selector
revert(0x1c, 0x04)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're going to use .revertWith lib from core this will need to be changed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we doing that? lol

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can put that in the error pr just a call out tho

}

// bitmap[wordPos] = (previousBits | bit)
sstore(slot, or(previousBits, bit))
}
}
}
5 changes: 0 additions & 5 deletions src/interfaces/IERC721Permit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ interface IERC721Permit {
error DeadlineExpired();
error NoSelfPermit();
error Unauthorized();
error InvalidSignature();

/// @notice The permit typehash used in the permit signature
/// @return The typehash for the permit
function PERMIT_TYPEHASH() external pure returns (bytes32);

/// @notice The domain separator used in the permit signature
/// @return The domain seperator used in encoding of permit signature
Expand Down
23 changes: 12 additions & 11 deletions test/ERC721Permit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol";

import {ERC721PermitHashLibrary} from "../src/libraries/ERC721PermitHash.sol";
import {MockERC721Permit} from "./mocks/MockERC721Permit.sol";
import {IERC721Permit} from "../src/interfaces/IERC721Permit.sol";
import {IERC721} from "forge-std/interfaces/IERC721.sol";
Expand All @@ -28,7 +29,7 @@ contract ERC721PermitTest is Test {
}

// --- Test the overriden approval ---
function test_approve(address spender) public {
function test_fuzz_approve(address spender) public {
uint256 tokenId = erc721Permit.mint();
assertEq(erc721Permit.getApproved(tokenId), address(0));
vm.expectEmit(true, true, true, true, address(erc721Permit));
Expand All @@ -37,7 +38,7 @@ contract ERC721PermitTest is Test {
assertEq(erc721Permit.getApproved(tokenId), spender);
}

function test_approvedOperator_reapproves(address operator, address spender) public {
function test_fuzz_approvedOperator_reapproves(address operator, address spender) public {
uint256 tokenId = erc721Permit.mint();
erc721Permit.setApprovalForAll(operator, true);
assertEq(erc721Permit.isApprovedForAll(address(this), operator), true);
Expand All @@ -51,7 +52,7 @@ contract ERC721PermitTest is Test {
assertEq(erc721Permit.getApproved(tokenId), spender);
}

function test_approve_unauthorizedRevert(address caller) public {
function test_fuzz_approve_unauthorizedRevert(address caller) public {
uint256 tokenId = erc721Permit.mint();
vm.prank(caller);
if (caller != address(this)) vm.expectRevert(IERC721Permit.Unauthorized.selector);
Expand All @@ -61,7 +62,7 @@ contract ERC721PermitTest is Test {
// --- Test the signature-based approvals (permit) ---
function test_permitTypeHash() public view {
assertEq(
IERC721Permit(address(erc721Permit)).PERMIT_TYPEHASH(),
ERC721PermitHashLibrary.PERMIT_TYPEHASH,
keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)")
);
}
Expand All @@ -82,7 +83,7 @@ contract ERC721PermitTest is Test {
}

/// @dev spender uses alice's signature to approve itself
function test_erc721permit_spender(address spender) public {
function test_fuzz_erc721permit_spender(address spender) public {
vm.assume(spender != alice);
vm.prank(alice);
uint256 tokenId = erc721Permit.mint();
Expand Down Expand Up @@ -116,7 +117,7 @@ contract ERC721PermitTest is Test {
}

/// @dev a third party caller uses alice's signature to give `spender` the approval
function test_erc721permit_caller(address caller, address spender) public {
function test_fuzz_erc721permit_caller(address caller, address spender) public {
vm.assume(spender != alice);
vm.prank(alice);
uint256 tokenId = erc721Permit.mint();
Expand Down Expand Up @@ -149,7 +150,7 @@ contract ERC721PermitTest is Test {
assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010
}

function test_erc721permit_nonceAlreadyUsed() public {
function test_fuzz_erc721permit_nonceAlreadyUsed() public {
vm.prank(alice);
uint256 tokenIdAlice = erc721Permit.mint();

Expand All @@ -169,7 +170,7 @@ contract ERC721PermitTest is Test {
vm.stopPrank();
}

function test_erc721permit_nonceAlreadyUsed_twoPositions() public {
function test_fuzz_erc721permit_nonceAlreadyUsed_twoPositions() public {
vm.prank(alice);
uint256 tokenIdAlice = erc721Permit.mint();

Expand All @@ -192,7 +193,7 @@ contract ERC721PermitTest is Test {
vm.stopPrank();
}

function test_erc721permit_unauthorized() public {
function test_fuzz_erc721permit_unauthorized() public {
vm.prank(alice);
uint256 tokenId = erc721Permit.mint();

Expand Down Expand Up @@ -224,7 +225,7 @@ contract ERC721PermitTest is Test {
assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0);
}

function test_erc721Permit_deadlineExpired(address spender) public {
function test_fuzz_erc721Permit_deadlineExpired(address spender) public {
vm.prank(alice);
uint256 tokenId = erc721Permit.mint();

Expand Down Expand Up @@ -279,7 +280,7 @@ contract ERC721PermitTest is Test {
abi.encodePacked(
"\x19\x01",
erc721Permit.DOMAIN_SEPARATOR(),
keccak256(abi.encode(erc721Permit.PERMIT_TYPEHASH(), spender, tokenId, nonce, deadline))
keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline))
)
);
}
Expand Down
Loading
Loading