diff --git a/.forge-snapshots/permitBatchCleanWrite.snap b/.forge-snapshots/permitBatchCleanWrite.snap index ff36c94f..2f15d30a 100644 --- a/.forge-snapshots/permitBatchCleanWrite.snap +++ b/.forge-snapshots/permitBatchCleanWrite.snap @@ -1 +1 @@ -91924 \ No newline at end of file +94475 \ No newline at end of file diff --git a/.forge-snapshots/permitBatchDirtyWrite.snap b/.forge-snapshots/permitBatchDirtyWrite.snap index 2020125e..082cd07a 100644 --- a/.forge-snapshots/permitBatchDirtyWrite.snap +++ b/.forge-snapshots/permitBatchDirtyWrite.snap @@ -1 +1 @@ -57724 \ No newline at end of file +60336 \ No newline at end of file diff --git a/.forge-snapshots/permitCleanWrite.snap b/.forge-snapshots/permitCleanWrite.snap index c49bc2ab..e7954ab8 100644 --- a/.forge-snapshots/permitCleanWrite.snap +++ b/.forge-snapshots/permitCleanWrite.snap @@ -1 +1 @@ -63119 \ No newline at end of file +63299 \ No newline at end of file diff --git a/.forge-snapshots/permitCompactSig.snap b/.forge-snapshots/permitCompactSig.snap index eb882987..af02b0f3 100644 --- a/.forge-snapshots/permitCompactSig.snap +++ b/.forge-snapshots/permitCompactSig.snap @@ -1 +1 @@ -63094 \ No newline at end of file +63273 \ No newline at end of file diff --git a/.forge-snapshots/permitDirtyNonce.snap b/.forge-snapshots/permitDirtyNonce.snap index a909d364..8c2da0ca 100644 --- a/.forge-snapshots/permitDirtyNonce.snap +++ b/.forge-snapshots/permitDirtyNonce.snap @@ -1 +1 @@ -44014 \ No newline at end of file +44348 \ No newline at end of file diff --git a/.forge-snapshots/permitDirtyWrite.snap b/.forge-snapshots/permitDirtyWrite.snap index 451c9a87..7581d9ee 100644 --- a/.forge-snapshots/permitDirtyWrite.snap +++ b/.forge-snapshots/permitDirtyWrite.snap @@ -1 +1 @@ -46019 \ No newline at end of file +46349 \ No newline at end of file diff --git a/.forge-snapshots/permitInvalidSigner.snap b/.forge-snapshots/permitInvalidSigner.snap index 73ab357b..2a49a1b7 100644 --- a/.forge-snapshots/permitInvalidSigner.snap +++ b/.forge-snapshots/permitInvalidSigner.snap @@ -1 +1 @@ -40301 \ No newline at end of file +40554 \ No newline at end of file diff --git a/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap b/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap index 6e633733..4d339286 100644 --- a/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap +++ b/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap @@ -1 +1 @@ -61114 \ No newline at end of file +61448 \ No newline at end of file diff --git a/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap b/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap index a909d364..8c2da0ca 100644 --- a/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap +++ b/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap @@ -1 +1 @@ -44014 \ No newline at end of file +44348 \ No newline at end of file diff --git a/.forge-snapshots/permitSignatureExpired.snap b/.forge-snapshots/permitSignatureExpired.snap index 309c6d2b..232a1d35 100644 --- a/.forge-snapshots/permitSignatureExpired.snap +++ b/.forge-snapshots/permitSignatureExpired.snap @@ -1 +1 @@ -31700 \ No newline at end of file +31952 \ No newline at end of file diff --git a/.forge-snapshots/transferFrom.snap b/.forge-snapshots/transferFrom.snap index ebbbd605..5f17e434 100644 --- a/.forge-snapshots/transferFrom.snap +++ b/.forge-snapshots/transferFrom.snap @@ -1 +1 @@ -52197 \ No newline at end of file +52178 \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index 463ca7d9..f4391fbc 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,3 +1,54 @@ +<<<<<<< HEAD +AllowanceTransferInvariants:invariant_balanceEqualsSpent() (runs: 256, calls: 3840, reverts: 887) +AllowanceTransferInvariants:invariant_permit2NeverHoldsBalance() (runs: 256, calls: 3840, reverts: 887) +AllowanceTransferInvariants:invariant_spendNeverExceedsPermit() (runs: 256, calls: 3840, reverts: 887) +AllowanceTransferTest_ERC20:testApprove() (gas: 47610) +AllowanceTransferTest_ERC20:testMaxAllowance() (gas: 135412) +AllowanceTransferTest_ERC20:testMaxAllowanceDirtyWrite() (gas: 118045) +AllowanceTransferTest_ERC20:testPartialAllowance() (gas: 105603) +AllowanceTransferTest_ERC20:testReuseOrderedNonceInvalid() (gas: 69865) +AllowanceTransferTest_ERC20:testSetAllowance() (gas: 89936) +AllowanceTransferTest_ERC20:testSetAllowanceBatch() (gas: 144498) +AllowanceTransferTest_ERC20:testSetAllowanceBatchDifferentNonces() (gas: 129658) +AllowanceTransferTest_ERC20:testSetAllowanceBatchDirtyWrite() (gas: 110034) +AllowanceTransferTest_ERC20:testSetAllowanceBatchEvent() (gas: 124271) +AllowanceTransferTest_ERC20:testSetAllowanceCompactSig() (gas: 89950) +AllowanceTransferTest_ERC20:testSetAllowanceDeadlinePassed() (gas: 56816) +AllowanceTransferTest_ERC20:testSetAllowanceDirtyWrite() (gas: 72528) +AllowanceTransferTest_ERC20:testSetAllowanceIncorrectSigLength() (gas: 29552) +AllowanceTransferTest_ERC20:testSetAllowanceInvalidSignature() (gas: 64467) +AllowanceTransferTest_ERC20:testSetAllowanceTransfer() (gas: 103558) +AllowanceTransferTest_ERC20:testSetAllowanceTransferDirtyNonceDirtyTransfer() (gas: 97849) +AllowanceTransferTest_ERC20:testTransferFromWithGasSnapshot() (gas: 133453) +CompactSignature:testCompactSignature27() (gas: 275) +CompactSignature:testCompactSignature28() (gas: 141) +EIP712Test:testDomainSeparator() (gas: 5804) +EIP712Test:testDomainSeparatorAfterFork() (gas: 10787) +Permit2LibTest:testOZSafePermit() (gas: 24555) +Permit2LibTest:testOZSafePermitPlusOZSafeTransferFrom() (gas: 129243) +Permit2LibTest:testOZSafeTransferFrom() (gas: 38941) +Permit2LibTest:testPermit2() (gas: 22822) +Permit2LibTest:testPermit2DSLessToken() (gas: 7011) +Permit2LibTest:testPermit2DSMore32Token() (gas: 7098) +Permit2LibTest:testPermit2DSMoreToken() (gas: 6979) +Permit2LibTest:testPermit2Full() (gas: 42242) +Permit2LibTest:testPermit2InvalidAmount() (gas: 20665) +Permit2LibTest:testPermit2LargerDS() (gas: 51272) +Permit2LibTest:testPermit2LargerDSRevert() (gas: 32696) +Permit2LibTest:testPermit2NonPermitFallback() (gas: 37094) +Permit2LibTest:testPermit2NonPermitToken() (gas: 32057) +Permit2LibTest:testPermit2PlusTransferFrom2() (gas: 126939) +Permit2LibTest:testPermit2PlusTransferFrom2WithNonPermit() (gas: 148045) +Permit2LibTest:testPermit2PlusTransferFrom2WithNonPermitFallback() (gas: 174683) +Permit2LibTest:testPermit2PlusTransferFrom2WithWETH9Mainnet() (gas: 147739) +Permit2LibTest:testPermit2SmallerDS() (gas: 77643) +Permit2LibTest:testPermit2SmallerDSNoRevert() (gas: 59293) +Permit2LibTest:testPermit2WETH9Mainnet() (gas: 28736) +Permit2LibTest:testStandardPermit() (gas: 22386) +Permit2LibTest:testStandardTransferFrom() (gas: 38143) +Permit2LibTest:testTransferFrom2() (gas: 38602) +Permit2LibTest:testTransferFrom2Full() (gas: 53280) +======= AllowanceTransferInvariants:invariant_balanceEqualsSpent() (runs: 256, calls: 3840, reverts: 886) AllowanceTransferInvariants:invariant_permit2NeverHoldsBalance() (runs: 256, calls: 3840, reverts: 886) AllowanceTransferInvariants:invariant_spendNeverExceedsPermit() (runs: 256, calls: 3840, reverts: 886) @@ -29,22 +80,10 @@ AllowanceTransferTest:testSetAllowanceInvalidSignature() (gas: 64071) AllowanceTransferTest:testSetAllowanceTransfer() (gas: 103161) AllowanceTransferTest:testSetAllowanceTransferDirtyNonceDirtyTransfer() (gas: 97432) AllowanceTransferTest:testTransferFromWithGasSnapshot() (gas: 133004) -AllowanceUnitTest:testPackAndUnpack(uint160,uint48,uint48) (runs: 256, μ: 38998, ~: 39076) -AllowanceUnitTest:testUpdateAllRandomly(uint160,uint48,uint48) (runs: 256, μ: 40222, ~: 40223) -AllowanceUnitTest:testUpdateAmountExpirationRandomly(uint160,uint48) (runs: 256, μ: 39224, ~: 39225) CompactSignature:testCompactSignature27() (gas: 253) CompactSignature:testCompactSignature28() (gas: 141) EIP712Test:testDomainSeparator() (gas: 5804) EIP712Test:testDomainSeparatorAfterFork() (gas: 10787) -NonceBitmapTest:testHighNonces() (gas: 36186) -NonceBitmapTest:testInvalidateFullWord() (gas: 63125) -NonceBitmapTest:testInvalidateNoncesRandomly(uint248,uint256) (runs: 256, μ: 30308, ~: 31008) -NonceBitmapTest:testInvalidateNonzeroWord() (gas: 85665) -NonceBitmapTest:testInvalidateTwoNoncesRandomly(uint248,uint256,uint256) (runs: 256, μ: 39187, ~: 39187) -NonceBitmapTest:testLowNonces() (gas: 41004) -NonceBitmapTest:testNonceWordBoundary() (gas: 42203) -NonceBitmapTest:testUseTwoRandomNonces(uint256,uint256) (runs: 256, μ: 49205, ~: 51640) -NonceBitmapTest:testUsingNonceTwiceFails(uint256) (runs: 256, μ: 21866, ~: 21889) Permit2LibTest:testOZSafePermit() (gas: 24509) Permit2LibTest:testOZSafePermitPlusOZSafeTransferFrom() (gas: 129197) Permit2LibTest:testOZSafeTransferFrom() (gas: 38919) @@ -69,41 +108,93 @@ Permit2LibTest:testStandardPermit() (gas: 22340) Permit2LibTest:testStandardTransferFrom() (gas: 38121) Permit2LibTest:testTransferFrom2() (gas: 38580) Permit2LibTest:testTransferFrom2Full() (gas: 53258) +>>>>>>> cce91d844585ac3f5a1ab817efd61b721b4a527e Permit2LibTest:testTransferFrom2InvalidAmount() (gas: 12710) -Permit2LibTest:testTransferFrom2NonPermitToken() (gas: 53104) +Permit2LibTest:testTransferFrom2NonPermitToken() (gas: 53126) SignatureTransferTest:testCorrectWitnessTypehashes() (gas: 3075) -SignatureTransferTest:testGasMultiplePermitBatchTransferFrom() (gas: 270919) -SignatureTransferTest:testGasSinglePermitBatchTransferFrom() (gas: 186316) -SignatureTransferTest:testGasSinglePermitTransferFrom() (gas: 123850) -SignatureTransferTest:testInvalidateUnorderedNonces() (gas: 41268) -SignatureTransferTest:testPermitBatchMultiPermitSingleTransfer() (gas: 133644) +SignatureTransferTest:testGasMultiplePermitBatchTransferFrom() (gas: 270943) +SignatureTransferTest:testGasSinglePermitBatchTransferFrom() (gas: 186340) +SignatureTransferTest:testGasSinglePermitTransferFrom() (gas: 123852) +SignatureTransferTest:testInvalidateUnorderedNonces() (gas: 41233) +SignatureTransferTest:testPermitBatchMultiPermitSingleTransfer() (gas: 133666) SignatureTransferTest:testPermitBatchTransferFrom() (gas: 162010) -SignatureTransferTest:testPermitBatchTransferFromSingleRecipient() (gas: 190319) -SignatureTransferTest:testPermitBatchTransferFromTypedWitness() (gas: 239854) +SignatureTransferTest:testPermitBatchTransferFromSingleRecipient() (gas: 190277) +SignatureTransferTest:testPermitBatchTransferFromTypedWitness() (gas: 239856) SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidType() (gas: 84467) -SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidTypeHash() (gas: 85864) +SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidTypeHash() (gas: 85842) SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidWitness() (gas: 85688) SignatureTransferTest:testPermitBatchTransferInvalidAmountsLengthMismatch() (gas: 43967) -SignatureTransferTest:testPermitBatchTransferMultiAddr() (gas: 160406) -SignatureTransferTest:testPermitBatchTransferSingleRecipientManyTokens() (gas: 211834) +SignatureTransferTest:testPermitBatchTransferMultiAddr() (gas: 160384) +SignatureTransferTest:testPermitBatchTransferSingleRecipientManyTokens() (gas: 211836) SignatureTransferTest:testPermitTransferFrom() (gas: 93012) SignatureTransferTest:testPermitTransferFromCompactSig() (gas: 123927) -SignatureTransferTest:testPermitTransferFromIncorrectSigLength() (gas: 51327) -SignatureTransferTest:testPermitTransferFromInvalidNonce() (gas: 72799) +SignatureTransferTest:testPermitTransferFromIncorrectSigLength() (gas: 51349) +SignatureTransferTest:testPermitTransferFromInvalidNonce() (gas: 72755) SignatureTransferTest:testPermitTransferFromRandomNonceAndAmount(uint256,uint128) (runs: 256, μ: 95754, ~: 96730) -SignatureTransferTest:testPermitTransferFromToSpender() (gas: 93342) -SignatureTransferTest:testPermitTransferFromTypedWitness() (gas: 125271) -SignatureTransferTest:testPermitTransferFromTypedWitnessInvalidType() (gas: 55906) +SignatureTransferTest:testPermitTransferFromToSpender() (gas: 93364) +SignatureTransferTest:testPermitTransferFromTypedWitness() (gas: 125273) +SignatureTransferTest:testPermitTransferFromTypedWitnessInvalidType() (gas: 55928) SignatureTransferTest:testPermitTransferFromTypedWitnessInvalidTypehash() (gas: 56794) -SignatureTransferTest:testPermitTransferSpendLessThanFull(uint256,uint128) (runs: 256, μ: 97989, ~: 99707) -TypehashGeneration:testPermitBatch() (gas: 40493) +SignatureTransferTest:testPermitTransferSpendLessThanFull(uint256,uint128) (runs: 256, μ: 97967, ~: 99685) +TypehashGeneration:testPermitBatch() (gas: 40515) TypehashGeneration:testPermitBatchTransferFrom() (gas: 49854) -TypehashGeneration:testPermitBatchTransferFromWithWitness() (gas: 56587) +TypehashGeneration:testPermitBatchTransferFromWithWitness() (gas: 56609) TypehashGeneration:testPermitBatchTransferFromWithWitnessIncorrectPermitData() (gas: 56744) -TypehashGeneration:testPermitBatchTransferFromWithWitnessIncorrectTypehashStub() (gas: 57229) +TypehashGeneration:testPermitBatchTransferFromWithWitnessIncorrectTypehashStub() (gas: 57251) TypehashGeneration:testPermitSingle() (gas: 28117) TypehashGeneration:testPermitTransferFrom() (gas: 36520) -TypehashGeneration:testPermitTransferFromWithWitness() (gas: 43369) +TypehashGeneration:testPermitTransferFromWithWitness() (gas: 43391) TypehashGeneration:testPermitTransferFromWithWitnessIncorrectPermitData() (gas: 43430) -TypehashGeneration:testPermitTransferFromWithWitnessIncorrectTypehashStub() (gas: 43833) +TypehashGeneration:testPermitTransferFromWithWitnessIncorrectTypehashStub() (gas: 43855) MockPermit2Lib:testPermit2Code(address):(bool) (runs: 256, μ: 3025, ~: 3016) +<<<<<<< HEAD +AllowanceUnitTest_ERC20:testPackAndUnpack(uint160,uint48,uint48) (runs: 256, μ: 38727, ~: 38805) +AllowanceUnitTest_ERC20:testUpdateAllRandomly(uint160,uint48,uint48) (runs: 256, μ: 40210, ~: 40211) +AllowanceUnitTest_ERC20:testUpdateAmountExpirationRandomly(uint160,uint48) (runs: 256, μ: 39330, ~: 39331) +AllowanceUnitTest_ERC721:testPackAndUnpack(uint160,uint48,uint48) (runs: 256, μ: 38727, ~: 38805) +AllowanceUnitTest_ERC721:testUpdateAllRandomly(uint160,uint48,uint48) (runs: 256, μ: 40210, ~: 40211) +AllowanceUnitTest_ERC721:testUpdateAmountExpirationRandomly(uint160,uint48) (runs: 256, μ: 39330, ~: 39331) +NonceBitmapTest_ERC20:testHighNonces() (gas: 36142) +NonceBitmapTest_ERC20:testInvalidateFullWord() (gas: 63031) +NonceBitmapTest_ERC20:testInvalidateNoncesRandomly(uint248,uint256) (runs: 256, μ: 30335, ~: 31035) +NonceBitmapTest_ERC20:testInvalidateNonzeroWord() (gas: 85489) +NonceBitmapTest_ERC20:testInvalidateTwoNoncesRandomly(uint248,uint256,uint256) (runs: 256, μ: 39067, ~: 39067) +NonceBitmapTest_ERC20:testLowNonces() (gas: 40894) +NonceBitmapTest_ERC20:testNonceWordBoundary() (gas: 42168) +NonceBitmapTest_ERC20:testUseTwoRandomNonces(uint256,uint256) (runs: 256, μ: 49018, ~: 51624) +NonceBitmapTest_ERC20:testUsingNonceTwiceFails(uint256) (runs: 256, μ: 21866, ~: 21889) +NonceBitmapTest_ERC721:testHighNonces() (gas: 36142) +NonceBitmapTest_ERC721:testInvalidateFullWord() (gas: 63031) +NonceBitmapTest_ERC721:testInvalidateNoncesRandomly(uint248,uint256) (runs: 256, μ: 30335, ~: 31035) +NonceBitmapTest_ERC721:testInvalidateNonzeroWord() (gas: 85489) +NonceBitmapTest_ERC721:testInvalidateTwoNoncesRandomly(uint248,uint256,uint256) (runs: 256, μ: 39067, ~: 39067) +NonceBitmapTest_ERC721:testLowNonces() (gas: 40894) +NonceBitmapTest_ERC721:testNonceWordBoundary() (gas: 42168) +NonceBitmapTest_ERC721:testUseTwoRandomNonces(uint256,uint256) (runs: 256, μ: 49189, ~: 51624) +NonceBitmapTest_ERC721:testUsingNonceTwiceFails(uint256) (runs: 256, μ: 21866, ~: 21889) +======= +AllowanceUnitTestERC20:testPackAndUnpack(uint160,uint48,uint48) (runs: 256, μ: 42090, ~: 42168) +AllowanceUnitTestERC20:testUpdateAllRandomly(uint160,uint48,uint48) (runs: 256, μ: 42620, ~: 42621) +AllowanceUnitTestERC20:testUpdateSomeRandomly(uint160,uint48) (runs: 256, μ: 41234, ~: 41235) +AllowanceUnitTestERC721:testPackAndUnpack(uint160,uint48,uint48) (runs: 256, μ: 41651, ~: 41729) +AllowanceUnitTestERC721:testUpdateAllRandomly(uint160,uint48,uint48) (runs: 256, μ: 42456, ~: 42457) +AllowanceUnitTestERC721:testUpdateSomeRandomly(uint160,uint48) (runs: 256, μ: 40723, ~: 40724) +NonceBitmapTest_ERC20:testHighNonces() (gas: 36142) +NonceBitmapTest_ERC20:testInvalidateFullWord() (gas: 63031) +NonceBitmapTest_ERC20:testInvalidateNoncesRandomly(uint248,uint256) (runs: 256, μ: 30313, ~: 31013) +NonceBitmapTest_ERC20:testInvalidateNonzeroWord() (gas: 85489) +NonceBitmapTest_ERC20:testInvalidateTwoNoncesRandomly(uint248,uint256,uint256) (runs: 256, μ: 39001, ~: 39001) +NonceBitmapTest_ERC20:testLowNonces() (gas: 40894) +NonceBitmapTest_ERC20:testNonceWordBoundary() (gas: 42168) +NonceBitmapTest_ERC20:testUseTwoRandomNonces(uint256,uint256) (runs: 256, μ: 49104, ~: 51624) +NonceBitmapTest_ERC20:testUsingNonceTwiceFails(uint256) (runs: 256, μ: 21866, ~: 21889) +NonceBitmapTestERC721:testHighNonces() (gas: 36362) +NonceBitmapTestERC721:testInvalidateFullWord() (gas: 63251) +NonceBitmapTestERC721:testInvalidateNoncesRandomly(uint248,uint256) (runs: 256, μ: 30203, ~: 30903) +NonceBitmapTestERC721:testInvalidateNonzeroWord() (gas: 85764) +NonceBitmapTestERC721:testInvalidateTwoNoncesRandomly(uint248,uint256,uint256) (runs: 256, μ: 38671, ~: 38671) +NonceBitmapTestERC721:testLowNonces() (gas: 41279) +NonceBitmapTestERC721:testNonceWordBoundary() (gas: 42344) +NonceBitmapTestERC721:testUseTwoRandomNonces(uint256,uint256) (runs: 256, μ: 49299, ~: 51734) +NonceBitmapTestERC721:testUsingNonceTwiceFails(uint256) (runs: 256, μ: 21954, ~: 21977) +>>>>>>> cce91d844585ac3f5a1ab817efd61b721b4a527e diff --git a/src/ERC20/AllowanceTransfer.sol b/src/ERC20/AllowanceTransfer.sol index 56c4cce0..c1339957 100644 --- a/src/ERC20/AllowanceTransfer.sol +++ b/src/ERC20/AllowanceTransfer.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.17; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {PermitHash} from "./libraries/PermitHash.sol"; -import {SignatureVerification} from "./libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../shared/SignatureVerification.sol"; import {EIP712} from "./EIP712.sol"; import {IAllowanceTransfer} from "./interfaces/IAllowanceTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; +import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol"; import {Allowance} from "./libraries/Allowance.sol"; contract AllowanceTransfer is IAllowanceTransfer, EIP712 { diff --git a/src/ERC20/SignatureTransfer.sol b/src/ERC20/SignatureTransfer.sol index c026553a..14d69e86 100644 --- a/src/ERC20/SignatureTransfer.sol +++ b/src/ERC20/SignatureTransfer.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.17; import {ISignatureTransfer} from "./interfaces/ISignatureTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; +import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; -import {SignatureVerification} from "./libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../shared/SignatureVerification.sol"; import {PermitHash} from "./libraries/PermitHash.sol"; import {EIP712} from "./EIP712.sol"; diff --git a/src/ERC721/AllowanceTransfer.sol b/src/ERC721/AllowanceTransfer.sol deleted file mode 100644 index 56c4cce0..00000000 --- a/src/ERC721/AllowanceTransfer.sol +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; -import {PermitHash} from "./libraries/PermitHash.sol"; -import {SignatureVerification} from "./libraries/SignatureVerification.sol"; -import {EIP712} from "./EIP712.sol"; -import {IAllowanceTransfer} from "./interfaces/IAllowanceTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; -import {Allowance} from "./libraries/Allowance.sol"; - -contract AllowanceTransfer is IAllowanceTransfer, EIP712 { - using SignatureVerification for bytes; - using SafeTransferLib for ERC20; - using PermitHash for PermitSingle; - using PermitHash for PermitBatch; - using Allowance for PackedAllowance; - - /// @notice Maps users to tokens to spender addresses and information about the approval on the token - /// @dev Indexed in the order of token owner address, token address, spender address - /// @dev The stored word saves the allowed amount, expiration on the allowance, and nonce - mapping(address => mapping(address => mapping(address => PackedAllowance))) public allowance; - - /// @inheritdoc IAllowanceTransfer - function approve(address token, address spender, uint160 amount, uint48 expiration) external { - PackedAllowance storage allowed = allowance[msg.sender][token][spender]; - allowed.updateAmountAndExpiration(amount, expiration); - emit Approval(msg.sender, token, spender, amount, expiration); - } - - /// @inheritdoc IAllowanceTransfer - function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external { - if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline); - - // Verify the signer address from the signature. - signature.verify(_hashTypedData(permitSingle.hash()), owner); - - _updateApproval(permitSingle.details, owner, permitSingle.spender); - } - - /// @inheritdoc IAllowanceTransfer - function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external { - if (block.timestamp > permitBatch.sigDeadline) revert SignatureExpired(permitBatch.sigDeadline); - - // Verify the signer address from the signature. - signature.verify(_hashTypedData(permitBatch.hash()), owner); - - address spender = permitBatch.spender; - unchecked { - uint256 length = permitBatch.details.length; - for (uint256 i = 0; i < length; ++i) { - _updateApproval(permitBatch.details[i], owner, spender); - } - } - } - - /// @inheritdoc IAllowanceTransfer - function transferFrom(address from, address to, uint160 amount, address token) external { - _transfer(from, to, amount, token); - } - - /// @inheritdoc IAllowanceTransfer - function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external { - unchecked { - uint256 length = transferDetails.length; - for (uint256 i = 0; i < length; ++i) { - AllowanceTransferDetails memory transferDetail = transferDetails[i]; - _transfer(transferDetail.from, transferDetail.to, transferDetail.amount, transferDetail.token); - } - } - } - - /// @notice Internal function for transferring tokens using stored allowances - /// @dev Will fail if the allowed timeframe has passed - function _transfer(address from, address to, uint160 amount, address token) private { - PackedAllowance storage allowed = allowance[from][token][msg.sender]; - - if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration); - - uint256 maxAmount = allowed.amount; - if (maxAmount != type(uint160).max) { - if (amount > maxAmount) { - revert InsufficientAllowance(maxAmount); - } else { - unchecked { - allowed.amount = uint160(maxAmount) - amount; - } - } - } - - // Transfer the tokens from the from address to the recipient. - ERC20(token).safeTransferFrom(from, to, amount); - } - - /// @inheritdoc IAllowanceTransfer - function lockdown(TokenSpenderPair[] calldata approvals) external { - address owner = msg.sender; - // Revoke allowances for each pair of spenders and tokens. - unchecked { - uint256 length = approvals.length; - for (uint256 i = 0; i < length; ++i) { - address token = approvals[i].token; - address spender = approvals[i].spender; - - allowance[owner][token][spender].amount = 0; - emit Lockdown(owner, token, spender); - } - } - } - - /// @inheritdoc IAllowanceTransfer - function invalidateNonces(address token, address spender, uint48 newNonce) external { - uint48 oldNonce = allowance[msg.sender][token][spender].nonce; - - if (newNonce <= oldNonce) revert InvalidNonce(); - - // Limit the amount of nonces that can be invalidated in one transaction. - unchecked { - uint48 delta = newNonce - oldNonce; - if (delta > type(uint16).max) revert ExcessiveInvalidation(); - } - - allowance[msg.sender][token][spender].nonce = newNonce; - emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce); - } - - /// @notice Sets the new values for amount, expiration, and nonce. - /// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1. - /// @dev Emits a Permit event. - function _updateApproval(PermitDetails memory details, address owner, address spender) private { - uint48 nonce = details.nonce; - address token = details.token; - uint160 amount = details.amount; - uint48 expiration = details.expiration; - PackedAllowance storage allowed = allowance[owner][token][spender]; - - if (allowed.nonce != nonce) revert InvalidNonce(); - - allowed.updateAll(amount, expiration, nonce); - emit Permit(owner, token, spender, amount, expiration, nonce); - } -} diff --git a/src/ERC721/AllowanceTransferERC721.sol b/src/ERC721/AllowanceTransferERC721.sol new file mode 100644 index 00000000..24433678 --- /dev/null +++ b/src/ERC721/AllowanceTransferERC721.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ERC721} from "solmate/src/tokens/ERC721.sol"; +import {PermitHashERC721} from "./libraries/PermitHashERC721.sol"; +import {SignatureVerification} from "../shared/SignatureVerification.sol"; +import {EIP712ERC721} from "./EIP712ERC721.sol"; +import {IAllowanceTransferERC721} from "./interfaces/IAllowanceTransferERC721.sol"; +import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol"; +import {AllowanceERC721} from "./libraries/AllowanceERC721.sol"; +import "forge-std/console2.sol"; + +contract AllowanceTransferERC721 is IAllowanceTransferERC721, EIP712ERC721 { + using SignatureVerification for bytes; + using PermitHashERC721 for PermitSingle; + using PermitHashERC721 for PermitBatch; + using PermitHashERC721 for PermitAll; + using AllowanceERC721 for PackedAllowance; + + /// @notice Maps users to tokens to tokenId and information about the approval, including the approved spender, on the token + /// @dev Indexed in the order of token owner address, token address, and tokenId + /// @dev The stored word saves the allowed spender, expiration on the allowance, and nonce + mapping(address => mapping(address => mapping(uint256 => PackedAllowance))) public allowance; + + /// @notice Maps users to tokens to spender and sets whether or not the spender has operator status on an entire token collection. + /// @dev Indexed in the order of token owner address, token address, then spender address. + /// @dev Sets a timestamp at which the spender no longer has operator status. Max expiration is type(uint48).max + mapping(address => mapping(address => mapping(address => PackedOperatorAllowance))) public operators; + + /// @inheritdoc IAllowanceTransferERC721 + function approve(address token, address spender, uint256 tokenId, uint48 expiration) external { + PackedAllowance storage allowed = allowance[msg.sender][token][tokenId]; + allowed.updateSpenderAndExpiration(spender, expiration); + emit Approval(msg.sender, token, spender, tokenId, expiration); + } + + /// @inheritdoc IAllowanceTransferERC721 + function setApprovalForAll(address token, address spender, uint48 expiration) external { + operators[msg.sender][token][spender].expiration = expiration; + emit ApprovalForAll(msg.sender, token, spender, expiration); + } + + /// @inheritdoc IAllowanceTransferERC721 + function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external { + if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline); + + // Verify the signer address from the signature. + signature.verify(_hashTypedData(permitSingle.hash()), owner); + + _updateApproval(permitSingle.details, owner, permitSingle.spender); + } + + /// @inheritdoc IAllowanceTransferERC721 + function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external { + if (block.timestamp > permitBatch.sigDeadline) revert SignatureExpired(permitBatch.sigDeadline); + + // Verify the signer address from the signature. + signature.verify(_hashTypedData(permitBatch.hash()), owner); + + address spender = permitBatch.spender; + unchecked { + uint256 length = permitBatch.details.length; + for (uint256 i = 0; i < length; ++i) { + _updateApproval(permitBatch.details[i], owner, spender); + } + } + } + + /// @inheritdoc IAllowanceTransferERC721 + function permit(address owner, PermitAll memory permitAll, bytes calldata signature) external { + if (block.timestamp > permitAll.sigDeadline) revert SignatureExpired(permitAll.sigDeadline); + + // Verify the signer address from the signature. + signature.verify(_hashTypedData(permitAll.hash()), owner); + + PackedOperatorAllowance storage operator = operators[owner][permitAll.token][permitAll.spender]; + + if (permitAll.nonce != operator.nonce) revert InvalidNonce(); + + unchecked { + operator.nonce += 1; + } + operator.expiration = permitAll.expiration; + } + + /// @inheritdoc IAllowanceTransferERC721 + function transferFrom(address from, address to, uint256 tokenId, address token) external { + _transfer(from, to, tokenId, token); + } + + /// @inheritdoc IAllowanceTransferERC721 + function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external { + unchecked { + uint256 length = transferDetails.length; + for (uint256 i = 0; i < length; ++i) { + AllowanceTransferDetails memory transferDetail = transferDetails[i]; + _transfer(transferDetail.from, transferDetail.to, transferDetail.tokenId, transferDetail.token); + } + } + } + + /// @notice Internal function for transferring tokens using stored allowances + /// @dev msg.sender must have tokenId level permissions through the `allowance` mapping OR operator permissions through the `operators` mapping. + /// @dev Will fail if the allowed timeframe has passed + function _transfer(address from, address to, uint256 tokenId, address token) private { + PackedAllowance storage allowed = allowance[from][token][tokenId]; + uint48 operatorExpiration = operators[from][token][msg.sender].expiration; + bool operatorExpired = block.timestamp > operatorExpiration; + + // At least one of the approval methods must not be expired. + if (block.timestamp > allowed.expiration && operatorExpired) { + revert AllowanceExpired(allowed.expiration, operatorExpiration); + } + + if (allowed.spender == msg.sender) { + // Reset permissions before transfer. + allowed.spender = address(0); + } else if (operatorExpired) { + // If there is no tokenId permissions and no operator permissions on msg.sender + // then the msg.sender has insufficient allowance. + revert InsufficientAllowance(token, tokenId); + } + + // Transfer the token from the from address to the recipient. + ERC721(token).safeTransferFrom(from, to, tokenId); + } + + /// @inheritdoc IAllowanceTransferERC721 + function lockdown(TokenSpenderPair[] calldata operatorApprovals, TokenAndIdPair[] calldata tokenIdApprovals) + external + { + address owner = msg.sender; + // Revoke operator allowances for each pair of spenders and tokens. + unchecked { + uint256 length = operatorApprovals.length; + for (uint256 i = 0; i < length; ++i) { + address token = operatorApprovals[i].token; + address spender = operatorApprovals[i].spender; + + operators[owner][token][spender].expiration = 0; + emit Lockdown(owner, token, spender); + } + } + // Revoke tokenId allowances for each pair of token and tokenId. + unchecked { + uint256 length = tokenIdApprovals.length; + for (uint256 i = 0; i < length; ++i) { + address token = tokenIdApprovals[i].token; + uint256 tokenId = tokenIdApprovals[i].tokenId; + allowance[owner][token][tokenId].expiration = 0; + } + } + } + + /// @inheritdoc IAllowanceTransferERC721 + function invalidateNonces(address token, address spender, uint48 newNonce) external { + uint48 oldNonce = operators[msg.sender][token][spender].nonce; + + if (newNonce <= oldNonce) revert InvalidNonce(); + + // Limit the amount of nonces that can be invalidated in one transaction. + unchecked { + uint48 delta = newNonce - oldNonce; + if (delta > type(uint16).max) revert ExcessiveInvalidation(); + } + + operators[msg.sender][token][spender].nonce = newNonce; + emit NonceInvalidation(msg.sender, token, uint256(uint160(spender)), newNonce, oldNonce); + } + + /// @inheritdoc IAllowanceTransferERC721 + function invalidateNonces(address token, uint256 tokenId, uint48 newNonce) external { + uint48 oldNonce = allowance[msg.sender][token][tokenId].nonce; + + if (newNonce <= oldNonce) revert InvalidNonce(); + + // Limit the amount of nonces that can be invalidated in one transaction. + unchecked { + uint48 delta = newNonce - oldNonce; + if (delta > type(uint16).max) revert ExcessiveInvalidation(); + } + + allowance[msg.sender][token][tokenId].nonce = newNonce; + emit NonceInvalidation(msg.sender, token, tokenId, newNonce, oldNonce); + } + + /// @notice Sets the new values for tokenId, expiration, and nonce. + /// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1. + /// @dev Emits a Permit event. + function _updateApproval(PermitDetails memory details, address owner, address spender) private { + uint48 nonce = details.nonce; + address token = details.token; + uint256 tokenId = details.tokenId; + uint48 expiration = details.expiration; + PackedAllowance storage allowed = allowance[owner][token][tokenId]; + + if (allowed.nonce != nonce) revert InvalidNonce(); + + allowed.updateAll(spender, expiration, nonce); + emit Permit(owner, token, spender, tokenId, expiration, nonce); + } +} diff --git a/src/ERC721/EIP712.sol b/src/ERC721/EIP712ERC721.sol similarity index 92% rename from src/ERC721/EIP712.sol rename to src/ERC721/EIP712ERC721.sol index bbd77f07..ac008be0 100644 --- a/src/ERC721/EIP712.sol +++ b/src/ERC721/EIP712ERC721.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -/// @notice EIP712 helpers for permit2 +/// @notice EIP712 helpers for Permit2 for ERC721s /// @dev Maintains cross-chain replay protection in the event of a fork /// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol -contract EIP712 { +contract EIP712ERC721 { // Cache the domain separator as an immutable value, but also store the chain id that it // corresponds to, in order to invalidate the cached domain separator if the chain id changes. bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; uint256 private immutable _CACHED_CHAIN_ID; - bytes32 private constant _HASHED_NAME = keccak256("Permit2"); + bytes32 private constant _HASHED_NAME = keccak256("Permit2ERC721"); bytes32 private constant _TYPE_HASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); diff --git a/src/ERC721/Permit2.sol b/src/ERC721/Permit2ERC721.sol similarity index 50% rename from src/ERC721/Permit2.sol rename to src/ERC721/Permit2ERC721.sol index 7249e40a..5f75099e 100644 --- a/src/ERC721/Permit2.sol +++ b/src/ERC721/Permit2ERC721.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {SignatureTransfer} from "./SignatureTransfer.sol"; -import {AllowanceTransfer} from "./AllowanceTransfer.sol"; +import {SignatureTransferERC721} from "./SignatureTransferERC721.sol"; +import {AllowanceTransferERC721} from "./AllowanceTransferERC721.sol"; /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. /// @dev Users must approve Permit2 before calling any of the transfer functions. -contract Permit2 is SignatureTransfer, AllowanceTransfer { +/// @dev It is recommended that you set operator permissions on Permit2 by calling `setApprovalForAll` for any underlying ERC721 token. +contract Permit2ERC721 is SignatureTransferERC721, AllowanceTransferERC721 { // Permit2 unifies the two contracts so users have maximal flexibility with their approval. } diff --git a/src/ERC721/PermitErrors.sol b/src/ERC721/PermitErrors.sol deleted file mode 100644 index 2c42e2d1..00000000 --- a/src/ERC721/PermitErrors.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -/// @notice Shared errors between signature based transfers and allowance based transfers. - -/// @notice Thrown when validating an inputted signature that is stale -/// @param signatureDeadline The timestamp at which a signature is no longer valid -error SignatureExpired(uint256 signatureDeadline); - -/// @notice Thrown when validating that the inputted nonce has not been used -error InvalidNonce(); diff --git a/src/ERC721/SignatureTransfer.sol b/src/ERC721/SignatureTransferERC721.sol similarity index 76% rename from src/ERC721/SignatureTransfer.sol rename to src/ERC721/SignatureTransferERC721.sol index c026553a..e2b7ae00 100644 --- a/src/ERC721/SignatureTransfer.sol +++ b/src/ERC721/SignatureTransferERC721.sol @@ -1,24 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {ISignatureTransfer} from "./interfaces/ISignatureTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {ISignatureTransferERC721} from "./interfaces/ISignatureTransferERC721.sol"; +import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol"; +import {ERC721} from "solmate/src/tokens/ERC721.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; -import {SignatureVerification} from "./libraries/SignatureVerification.sol"; -import {PermitHash} from "./libraries/PermitHash.sol"; -import {EIP712} from "./EIP712.sol"; +import {SignatureVerification} from "../shared/SignatureVerification.sol"; +import {PermitHashERC721} from "./libraries/PermitHashERC721.sol"; +import {EIP712ERC721} from "./EIP712ERC721.sol"; -contract SignatureTransfer is ISignatureTransfer, EIP712 { +contract SignatureTransferERC721 is ISignatureTransferERC721, EIP712ERC721 { using SignatureVerification for bytes; - using SafeTransferLib for ERC20; - using PermitHash for PermitTransferFrom; - using PermitHash for PermitBatchTransferFrom; + using PermitHashERC721 for PermitTransferFrom; + using PermitHashERC721 for PermitBatchTransferFrom; - /// @inheritdoc ISignatureTransfer + /// @inheritdoc ISignatureTransferERC721 mapping(address => mapping(uint256 => uint256)) public nonceBitmap; - /// @inheritdoc ISignatureTransfer + /// @inheritdoc ISignatureTransferERC721 function permitTransferFrom( PermitTransferFrom memory permit, SignatureTransferDetails calldata transferDetails, @@ -28,7 +27,7 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { _permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature); } - /// @inheritdoc ISignatureTransfer + /// @inheritdoc ISignatureTransferERC721 function permitWitnessTransferFrom( PermitTransferFrom memory permit, SignatureTransferDetails calldata transferDetails, @@ -55,19 +54,21 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { bytes32 dataHash, bytes calldata signature ) private { - uint256 requestedAmount = transferDetails.requestedAmount; + uint256 requestedTokenId = transferDetails.requestedTokenId; if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline); - if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount); + if (permit.permitted.tokenId != type(uint160).max && requestedTokenId != permit.permitted.tokenId) { + revert InvalidTokenId(permit.permitted.tokenId); + } _useUnorderedNonce(owner, permit.nonce); signature.verify(_hashTypedData(dataHash), owner); - ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount); + ERC721(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedTokenId); } - /// @inheritdoc ISignatureTransfer + /// @inheritdoc ISignatureTransferERC721 function permitTransferFrom( PermitBatchTransferFrom memory permit, SignatureTransferDetails[] calldata transferDetails, @@ -77,7 +78,7 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { _permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature); } - /// @inheritdoc ISignatureTransfer + /// @inheritdoc ISignatureTransferERC721 function permitWitnessTransferFrom( PermitBatchTransferFrom memory permit, SignatureTransferDetails[] calldata transferDetails, @@ -114,19 +115,21 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { unchecked { for (uint256 i = 0; i < numPermitted; ++i) { TokenPermissions memory permitted = permit.permitted[i]; - uint256 requestedAmount = transferDetails[i].requestedAmount; + uint256 requestedTokenId = transferDetails[i].requestedTokenId; - if (requestedAmount > permitted.amount) revert InvalidAmount(permitted.amount); + if (permitted.tokenId != type(uint160).max && requestedTokenId != permitted.tokenId) { + revert InvalidTokenId(permitted.tokenId); + } - if (requestedAmount != 0) { + if (requestedTokenId != 0) { // allow spender to specify which of the permitted tokens should be transferred - ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount); + ERC721(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedTokenId); } } } } - /// @inheritdoc ISignatureTransfer + /// @inheritdoc ISignatureTransferERC721 function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external { nonceBitmap[msg.sender][wordPos] |= mask; diff --git a/src/ERC721/interfaces/IAllowanceTransfer.sol b/src/ERC721/interfaces/IAllowanceTransfer.sol deleted file mode 100644 index c3739b9d..00000000 --- a/src/ERC721/interfaces/IAllowanceTransfer.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -/// @title AllowanceTransfer -/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts -/// @dev Requires user's token approval on the Permit2 contract -interface IAllowanceTransfer { - /// @notice Thrown when an allowance on a token has expired. - /// @param deadline The timestamp at which the allowed amount is no longer valid - error AllowanceExpired(uint256 deadline); - - /// @notice Thrown when an allowance on a token has been depleted. - /// @param amount The maximum amount allowed - error InsufficientAllowance(uint256 amount); - - /// @notice Thrown when too many nonces are invalidated. - error ExcessiveInvalidation(); - - /// @notice Emits an event when the owner successfully invalidates an ordered nonce. - event NonceInvalidation( - address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce - ); - - /// @notice Emits an event when the owner successfully sets permissions on a token for the spender. - event Approval( - address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration - ); - - /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender. - event Permit( - address indexed owner, - address indexed token, - address indexed spender, - uint160 amount, - uint48 expiration, - uint48 nonce - ); - - /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function. - event Lockdown(address indexed owner, address token, address spender); - - /// @notice The permit data for a token - struct PermitDetails { - // ERC20 token address - address token; - // the maximum amount allowed to spend - uint160 amount; - // timestamp at which a spender's token allowances become invalid - uint48 expiration; - // an incrementing value indexed per owner,token,and spender for each signature - uint48 nonce; - } - - /// @notice The permit message signed for a single token allownce - struct PermitSingle { - // the permit data for a single token alownce - PermitDetails details; - // address permissioned on the allowed tokens - address spender; - // deadline on the permit signature - uint256 sigDeadline; - } - - /// @notice The permit message signed for multiple token allowances - struct PermitBatch { - // the permit data for multiple token allowances - PermitDetails[] details; - // address permissioned on the allowed tokens - address spender; - // deadline on the permit signature - uint256 sigDeadline; - } - - /// @notice The saved permissions - /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message - /// @dev Setting amount to type(uint160).max sets an unlimited approval - struct PackedAllowance { - // amount allowed - uint160 amount; - // permission expiry - uint48 expiration; - // an incrementing value indexed per owner,token,and spender for each signature - uint48 nonce; - } - - /// @notice A token spender pair. - struct TokenSpenderPair { - // the token the spender is approved - address token; - // the spender address - address spender; - } - - /// @notice Details for a token transfer. - struct AllowanceTransferDetails { - // the owner of the token - address from; - // the recipient of the token - address to; - // the amount of the token - uint160 amount; - // the token to be transferred - address token; - } - - /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval. - /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress] - /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals. - function allowance(address, address, address) external view returns (uint160, uint48, uint48); - - /// @notice Approves the spender to use up to amount of the specified token up until the expiration - /// @param token The token to approve - /// @param spender The spender address to approve - /// @param amount The approved amount of the token - /// @param expiration The timestamp at which the approval is no longer valid - /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve - /// @dev Setting amount to type(uint160).max sets an unlimited approval - function approve(address token, address spender, uint160 amount, uint48 expiration) external; - - /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature - /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce - /// @param owner The owner of the tokens being approved - /// @param permitSingle Data signed over by the owner specifying the terms of approval - /// @param signature The owner's signature over the permit data - function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external; - - /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature - /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce - /// @param owner The owner of the tokens being approved - /// @param permitBatch Data signed over by the owner specifying the terms of approval - /// @param signature The owner's signature over the permit data - function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external; - - /// @notice Transfer approved tokens from one address to another - /// @param from The address to transfer from - /// @param to The address of the recipient - /// @param amount The amount of the token to transfer - /// @param token The token address to transfer - /// @dev Requires the from address to have approved at least the desired amount - /// of tokens to msg.sender. - function transferFrom(address from, address to, uint160 amount, address token) external; - - /// @notice Transfer approved tokens in a batch - /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers - /// @dev Requires the from addresses to have approved at least the desired amount - /// of tokens to msg.sender. - function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external; - - /// @notice Enables performing a "lockdown" of the sender's Permit2 identity - /// by batch revoking approvals - /// @param approvals Array of approvals to revoke. - function lockdown(TokenSpenderPair[] calldata approvals) external; - - /// @notice Invalidate nonces for a given (token, spender) pair - /// @param token The token to invalidate nonces for - /// @param spender The spender to invalidate nonces for - /// @param newNonce The new nonce to set. Invalidates all nonces less than it. - /// @dev Can't invalidate more than 2**16 nonces per transaction. - function invalidateNonces(address token, address spender, uint48 newNonce) external; -} diff --git a/src/ERC721/interfaces/IAllowanceTransferERC721.sol b/src/ERC721/interfaces/IAllowanceTransferERC721.sol new file mode 100644 index 00000000..ea0a5ada --- /dev/null +++ b/src/ERC721/interfaces/IAllowanceTransferERC721.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +/// @title AllowanceTransfer +/// @notice Handles ERC721 token permissions through signature based allowance setting and ERC721 token transfers by checking stored permissions +/// @dev Requires user's token approval on the Permit2 contract +interface IAllowanceTransferERC721 { + /// @notice Thrown when an allowance on a token has expired. + /// @param allowanceDeadline The timestamp at which the permissions on the token for a specific tokenId are no longer valid + /// @param operatorDeadline The timestamp at which the permissions given to an operator of an entire collection are no longer valid. + error AllowanceExpired(uint256 allowanceDeadline, uint256 operatorDeadline); + + /// @notice Thrown when there is no allowance for a token. + /// @param token The address of the token and tokenId + error InsufficientAllowance(address token, uint256 tokenId); + + /// @notice Thrown when too many nonces are invalidated. + error ExcessiveInvalidation(); + + /// @notice Emits an event when the owner successfully invalidates an ordered nonce on the operator mapping. + event NonceInvalidation( + address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce + ); + + /// @notice Emits an event when the owner successfully invalidates an ordered nonce on the allowance mapping. + event NonceInvalidation( + address indexed owner, address indexed token, uint256 indexed tokenId, uint48 newNonce, uint48 oldNonce + ); + + /// @notice Emits an event when the owner successfully sets permissions on a token for the spender. + event Approval( + address indexed owner, address indexed token, address indexed spender, uint256 tokenId, uint48 expiration + ); + + /// @notice Emits an event when the owner successfully gives a spender operator permissions on a token. + event ApprovalForAll(address indexed owner, address indexed token, address indexed spender, uint48 expiration); + + /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender. + event Permit( + address indexed owner, + address indexed token, + address indexed spender, + uint256 tokenId, + uint48 expiration, + uint48 nonce + ); + + /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function. + event Lockdown(address indexed owner, address token, address spender); + + /// @notice The permit data for a token + struct PermitDetails { + // ERC20 token address + address token; + // the tokenId allowed to spend + uint256 tokenId; + // timestamp at which a spender's token allowances become invalid + uint48 expiration; + // an incrementing value indexed per owner,token,and tokenId for each signature + uint48 nonce; + } + + /// @notice The permit message signed for a single token allownce + struct PermitSingle { + // the permit data for a single token alownce + PermitDetails details; + // address permissioned on the allowed tokens + address spender; + // deadline on the permit signature + uint256 sigDeadline; + } + + /// @notice The permit message signed for multiple token allowances + struct PermitBatch { + // the permit data for multiple token allowances + PermitDetails[] details; + // address permissioned on the allowed tokens + address spender; + // deadline on the permit signature + uint256 sigDeadline; + } + + /// @notice The permit message signed to set an operator for the token. + struct PermitAll { + // address of the token collection + address token; + // address of the spender who will act as an operator on all tokenIds owned by the signer for the token collection + address spender; + // expiration of the operator permissions + uint48 expiration; + // an incrementing value indexed per owner, per token, per spender + uint48 nonce; + // deadline on the permit signature + uint256 sigDeadline; + } + + /// @notice The saved permissions on the allowance mapping + /// @dev This info is saved per owner, per token, per tokenId and all signed over in the permit message + struct PackedAllowance { + // spender allowed + address spender; + // permission expiry + uint48 expiration; + // an incrementing value indexed per owner,token,and spender for each signature + uint48 nonce; + } + + /// @notice The saved expiration on the operator. + /// @dev Holds a nonce value to provide replay protection. + struct PackedOperatorAllowance { + uint48 expiration; + uint48 nonce; + } + + /// @notice A token spender pair. + struct TokenSpenderPair { + // the token the spender is approved + address token; + // the spender address + address spender; + } + + /// @notice A token and tokenId pair. + struct TokenAndIdPair { + // the token collection address + address token; + // the tokenId + uint256 tokenId; + } + + /// @notice Details for a token transfer. + struct AllowanceTransferDetails { + // the owner of the token + address from; + // the recipient of the token + address to; + // the tokenId of the token + uint256 tokenId; + // the token to be transferred + address token; + } + + /// @notice A mapping from owner address to token address to tokenId to PackedAllowance struct, which contains details and conditions of the approval. + /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][tokenId] + /// @dev The packed slot holds the allowed spender, expiration at which the permissions on the tokenId is no longer valid, and current nonce thats updated on any signature based approvals. + /// @dev Setting the expiration to 0, sets the expiration to block.timestamp so the approval only lasts for the duration of the block. + function allowance(address, address, uint256) external view returns (address, uint48, uint48); + + /// @notice A mapping from owner address to token address to spender address to a PackedOperatorAllowance struct, which contains the expiration of the operator approval. + /// @notice The mapping is indexed in the above order see: operator[ownerAddress][tokenAddress][spenderAddress] + /// @dev Unlike the allowance mappings, setting the expiration to 0 just invalidates the operator allowance. It does NOT set the allowance to block.timestamp. + function operators(address, address, address) external view returns (uint48, uint48); + + /// @notice Approves the spender to transfer the tokenId of the specified token up until the expiration + /// @param token The token to approve + /// @param spender The spender address to approve + /// @param tokenId The approved tokenId of the token + /// @param expiration The timestamp at which the approval is no longer valid + /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve + /// @dev Passing in expiration as 0 sets the expiration to the block.timestamp + function approve(address token, address spender, uint256 tokenId, uint48 expiration) external; + + /// @notice Approves the spender to be an operator of the specified token up until the expiration + /// @param token The token to approve + /// @param spender The spender address to approve + /// @param expiration The timestamp at which the operator approval is no longer valid + /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve + /// @dev Passing in expiration as 0 DOES NOT set the expiration to the block.timestamp unlike `approve`. + function setApprovalForAll(address token, address spender, uint48 expiration) external; + + /// @notice Permit a spender to a given tokenId of the owners token via the owner's EIP-712 signature + /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce + /// @param owner The owner of the tokens being approved + /// @param permitSingle Data signed over by the owner specifying the terms of approval + /// @param signature The owner's signature over the permit data + function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external; + + /// @notice Permit a spender to the signed tokenIds of the owners tokens via the owner's EIP-712 signature + /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce + /// @param owner The owner of the tokens being approved + /// @param permitBatch Data signed over by the owner specifying the terms of approval + /// @param signature The owner's signature over the permit data + function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external; + + /// @notice Permit a spender to be an operator of the owners tokens via the owner's EIP-712 signature + /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce + /// @param owner The owner of the tokens being approved + /// @param permitAll Data signed over by the owner specifying the terms of approval + /// @param signature The owner's signature over the permit data + function permit(address owner, PermitAll memory permitAll, bytes calldata signature) external; + + /// @notice Transfer approved tokens from one address to another + /// @param from The address to transfer from + /// @param to The address of the recipient + /// @param tokenId The tokenId of the token to transfer + /// @param token The token address to transfer + /// @dev Requires the from address to have approved the desired tokenId or be an operator + /// of the token to msg.sender. + function transferFrom(address from, address to, uint256 tokenId, address token) external; + + /// @notice Transfer approved tokens in a batch + /// @param transferDetails Array of owners, recipients, tokenIds, and tokens for the transfers + /// @dev Requires the from addresses to have approved the desired tokenIds or be an operator + /// of the tokens to msg.sender. + function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external; + + /// @notice Enables performing a "lockdown" of the sender's Permit2 identity + /// by batch revoking approvals + /// @param operatorApprovals Array of operator approvals to revoke. + /// @param tokenIdApprovals Array of tokenId approvals to revoke. + /// @dev Expires the allowances on each of the approval mappings, the operator and allowance mappings respectively. + function lockdown(TokenSpenderPair[] calldata operatorApprovals, TokenAndIdPair[] calldata tokenIdApprovals) + external; + + /// @notice Invalidate nonces for a given (token, spender) pair + /// @param token The token to invalidate nonces for + /// @param spender The spender to invalidate nonces for + /// @param newNonce The new nonce to set. Invalidates all nonces less than it. + /// @dev Can't invalidate more than 2**16 nonces per transaction. + function invalidateNonces(address token, address spender, uint48 newNonce) external; + + /// @notice Invalidate nonces for a given (token, tokenId) pair + /// @param token The token to invalidate nonces for + /// @param tokenId The tokenId to invalidate nonces for + /// @param newNonce The new nonce to set. Invalidates all nonces less than it. + /// @dev Can't invalidate more than 2**16 nonces per transaction. + function invalidateNonces(address token, uint256 tokenId, uint48 newNonce) external; +} diff --git a/src/ERC721/interfaces/IDAIPermit.sol b/src/ERC721/interfaces/IDAIPermit.sol deleted file mode 100644 index 912b7817..00000000 --- a/src/ERC721/interfaces/IDAIPermit.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -interface IDAIPermit { - /// @param holder The address of the token owner. - /// @param spender The address of the token spender. - /// @param nonce The owner's nonce, increases at each call to permit. - /// @param expiry The timestamp at which the permit is no longer valid. - /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0. - /// @param v Must produce valid secp256k1 signature from the owner along with r and s. - /// @param r Must produce valid secp256k1 signature from the owner along with v and s. - /// @param s Must produce valid secp256k1 signature from the owner along with r and v. - function permit( - address holder, - address spender, - uint256 nonce, - uint256 expiry, - bool allowed, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} diff --git a/src/ERC721/interfaces/ISignatureTransfer.sol b/src/ERC721/interfaces/ISignatureTransferERC721.sol similarity index 81% rename from src/ERC721/interfaces/ISignatureTransfer.sol rename to src/ERC721/interfaces/ISignatureTransferERC721.sol index 8b76a3eb..a90db50d 100644 --- a/src/ERC721/interfaces/ISignatureTransfer.sol +++ b/src/ERC721/interfaces/ISignatureTransferERC721.sol @@ -2,26 +2,26 @@ pragma solidity ^0.8.17; /// @title SignatureTransfer -/// @notice Handles ERC20 token transfers through signature based actions +/// @notice Handles ERC721 token transfers through signature based actions /// @dev Requires user's token approval on the Permit2 contract -interface ISignatureTransfer { - /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount - /// @param maxAmount The maximum amount a spender can request to transfer - error InvalidAmount(uint256 maxAmount); +interface ISignatureTransferERC721 { + /// @notice Thrown when the requested tokenId for a transfer is not the permitted tokenId + /// @param tokenId The valid tokenId a spender can request to transfer + error InvalidTokenId(uint256 tokenId); /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred - /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred + /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request tokenId 0 to be transferred error LengthMismatch(); /// @notice Emits an event when the owner successfully invalidates an unordered nonce. event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask); - /// @notice The token and amount details for a transfer signed in the permit transfer signature + /// @notice The token and tokenId details for a transfer signed in the permit transfer signature struct TokenPermissions { // ERC20 token address address token; - // the maximum amount that can be spent - uint256 amount; + // the tokenId to transfer + uint256 tokenId; } /// @notice The signed permit message for a single token transfer @@ -34,20 +34,20 @@ interface ISignatureTransfer { } /// @notice Specifies the recipient address and amount for batched transfers. - /// @dev Recipients and amounts correspond to the index of the signed token permissions array. - /// @dev Reverts if the requested amount is greater than the permitted signed amount. + /// @dev Recipients and tokenIds correspond to the index of the signed token permissions array. + /// @dev Reverts if the requested tokenId is not the signed tokenId or if the user did not sign operator permissions. struct SignatureTransferDetails { // recipient address address to; - // spender requested amount - uint256 requestedAmount; + // spender requested tokenId + uint256 requestedTokenId; } /// @notice Used to reconstruct the signed permit message for multiple token transfers /// @dev Do not need to pass in spender address as it is required that it is msg.sender /// @dev Note that a user still signs over a spender address struct PermitBatchTransferFrom { - // the tokens and corresponding amounts permitted for a transfer + // the tokens and corresponding tokenIds permitted for a transfer TokenPermissions[] permitted; // a unique value for every token owner's signature to prevent signature replays uint256 nonce; @@ -63,7 +63,7 @@ interface ISignatureTransfer { function nonceBitmap(address, uint256) external view returns (uint256); /// @notice Transfers a token using a signed permit message - /// @dev Reverts if the requested amount is greater than the permitted signed amount + /// @dev Reverts if the requested tokenId is not the permitted signed tokenId or if the permitted signed tokenId is not the maximum /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails The spender's requested transfer details for the permitted token @@ -78,7 +78,7 @@ interface ISignatureTransfer { /// @notice Transfers a token using a signed permit message /// @notice Includes extra data provided by the caller to verify signature over /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition - /// @dev Reverts if the requested amount is greater than the permitted signed amount + /// @dev Reverts if the requested tokenId is not the permitted signed tokenId or if the permitted signed tokenId is not the maximum /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails The spender's requested transfer details for the permitted token @@ -97,7 +97,7 @@ interface ISignatureTransfer { /// @notice Transfers multiple tokens using a signed permit message /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer - /// @param transferDetails Specifies the recipient and requested amount for the token transfer + /// @param transferDetails Specifies the recipient and requested tokenId for the token transfer /// @param signature The signature to verify function permitTransferFrom( PermitBatchTransferFrom memory permit, @@ -111,7 +111,7 @@ interface ISignatureTransfer { /// @notice Includes extra data provided by the caller to verify signature over /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer - /// @param transferDetails Specifies the recipient and requested amount for the token transfer + /// @param transferDetails Specifies the recipient and requested tokenId for the token transfer /// @param witness Extra data to include when checking the user signature /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash /// @param signature The signature to verify diff --git a/src/ERC721/libraries/Allowance.sol b/src/ERC721/libraries/AllowanceERC721.sol similarity index 66% rename from src/ERC721/libraries/Allowance.sol rename to src/ERC721/libraries/AllowanceERC721.sol index 671c9724..fae16a02 100644 --- a/src/ERC721/libraries/Allowance.sol +++ b/src/ERC721/libraries/AllowanceERC721.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; +import {IAllowanceTransferERC721} from "../interfaces/IAllowanceTransferERC721.sol"; -library Allowance { +library AllowanceERC721 { // note if the expiration passed is 0, then it the approval set to the block.timestamp uint256 private constant BLOCK_TIMESTAMP_EXPIRATION = 0; @@ -11,8 +11,8 @@ library Allowance { /// @dev Nonce is incremented. /// @dev If the inputted expiration is 0, the stored expiration is set to block.timestamp function updateAll( - IAllowanceTransfer.PackedAllowance storage allowed, - uint160 amount, + IAllowanceTransferERC721.PackedAllowance storage allowed, + address spender, uint48 expiration, uint48 nonce ) internal { @@ -23,26 +23,26 @@ library Allowance { uint48 storedExpiration = expiration == BLOCK_TIMESTAMP_EXPIRATION ? uint48(block.timestamp) : expiration; - uint256 word = pack(amount, storedExpiration, storedNonce); + uint256 word = pack(spender, storedExpiration, storedNonce); assembly { sstore(allowed.slot, word) } } - /// @notice Sets the allowed amount and expiry of the spender's permissions on owner's token. + /// @notice Sets the expiry of the spender's permissions on owner's token. /// @dev Nonce does not need to be incremented. - function updateAmountAndExpiration( - IAllowanceTransfer.PackedAllowance storage allowed, - uint160 amount, + function updateSpenderAndExpiration( + IAllowanceTransferERC721.PackedAllowance storage allowed, + address spender, uint48 expiration ) internal { // If the inputted expiration is 0, the allowance only lasts the duration of the block. allowed.expiration = expiration == 0 ? uint48(block.timestamp) : expiration; - allowed.amount = amount; + allowed.spender = spender; } /// @notice Computes the packed slot of the amount, expiration, and nonce that make up PackedAllowance - function pack(uint160 amount, uint48 expiration, uint48 nonce) internal pure returns (uint256 word) { - word = (uint256(nonce) << 208) | uint256(expiration) << 160 | amount; + function pack(address spender, uint48 expiration, uint48 nonce) internal pure returns (uint256 word) { + word = (uint256(nonce) << 208) | uint256(expiration) << 160 | uint160(spender); } } diff --git a/src/ERC721/libraries/Permit2Lib.sol b/src/ERC721/libraries/Permit2Lib.sol deleted file mode 100644 index d3d9b9c7..00000000 --- a/src/ERC721/libraries/Permit2Lib.sol +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -import {Permit2} from "../Permit2.sol"; -import {IDAIPermit} from "../interfaces/IDAIPermit.sol"; -import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; -import {SafeCast160} from "./SafeCast160.sol"; - -/// @title Permit2Lib -/// @notice Enables efficient transfers and EIP-2612/DAI -/// permits for any token by falling back to Permit2. -library Permit2Lib { - using SafeCast160 for uint256; - /*////////////////////////////////////////////////////////////// - CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /// @dev The unique EIP-712 domain domain separator for the DAI token contract. - bytes32 internal constant DAI_DOMAIN_SEPARATOR = 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; - - /// @dev The address for the WETH9 contract on Ethereum mainnet, encoded as a bytes32. - bytes32 internal constant WETH9_ADDRESS = 0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2; - - /// @dev The address of the Permit2 contract the library will use. - Permit2 internal constant PERMIT2 = Permit2(address(0x000000000022D473030F116dDEE9F6B43aC78BA3)); - - /// @notice Transfer a given amount of tokens from one user to another. - /// @param token The token to transfer. - /// @param from The user to transfer from. - /// @param to The user to transfer to. - /// @param amount The amount to transfer. - function transferFrom2(ERC20 token, address from, address to, uint256 amount) internal { - // Generate calldata for a standard transferFrom call. - bytes memory inputData = abi.encodeCall(ERC20.transferFrom, (from, to, amount)); - - bool success; // Call the token contract as normal, capturing whether it succeeded. - assembly { - success := - and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(eq(mload(0), 1), iszero(returndatasize())), - // Counterintuitively, this call() must be positioned after the or() in the - // surrounding and() because and() evaluates its arguments from right to left. - // We use 0 and 32 to copy up to 32 bytes of return data into the first slot of scratch space. - call(gas(), token, 0, add(inputData, 32), mload(inputData), 0, 32) - ) - } - - // We'll fall back to using Permit2 if calling transferFrom on the token directly reverted. - if (!success) PERMIT2.transferFrom(from, to, amount.toUint160(), address(token)); - } - - /*////////////////////////////////////////////////////////////// - PERMIT LOGIC - //////////////////////////////////////////////////////////////*/ - - /// @notice Permit a user to spend a given amount of - /// another user's tokens via the owner's EIP-712 signature. - /// @param token The token to permit spending. - /// @param owner The user to permit spending from. - /// @param spender The user to permit spending to. - /// @param amount The amount to permit spending. - /// @param deadline The timestamp after which the signature is no longer valid. - /// @param v Must produce valid secp256k1 signature from the owner along with r and s. - /// @param r Must produce valid secp256k1 signature from the owner along with v and s. - /// @param s Must produce valid secp256k1 signature from the owner along with r and v. - function permit2( - ERC20 token, - address owner, - address spender, - uint256 amount, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - // Generate calldata for a call to DOMAIN_SEPARATOR on the token. - bytes memory inputData = abi.encodeWithSelector(ERC20.DOMAIN_SEPARATOR.selector); - - bool success; // Call the token contract as normal, capturing whether it succeeded. - bytes32 domainSeparator; // If the call succeeded, we'll capture the return value here. - - assembly { - // If the token is WETH9, we know it doesn't have a DOMAIN_SEPARATOR, and we'll skip this step. - // We make sure to mask the token address as its higher order bits aren't guaranteed to be clean. - if iszero(eq(and(token, 0xffffffffffffffffffffffffffffffffffffffff), WETH9_ADDRESS)) { - success := - and( - // Should resolve false if its not 32 bytes or its first word is 0. - and(iszero(iszero(mload(0))), eq(returndatasize(), 32)), - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the and() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - // We send a maximum of 5000 gas to prevent tokens with fallbacks from using a ton of gas. - // which should be plenty to allow tokens to fetch their DOMAIN_SEPARATOR from storage, etc. - staticcall(5000, token, add(inputData, 32), mload(inputData), 0, 32) - ) - - domainSeparator := mload(0) // Copy the return value into the domainSeparator variable. - } - } - - // If the call to DOMAIN_SEPARATOR succeeded, try using permit on the token. - if (success) { - // We'll use DAI's special permit if it's DOMAIN_SEPARATOR matches, - // otherwise we'll just encode a call to the standard permit function. - inputData = domainSeparator == DAI_DOMAIN_SEPARATOR - ? abi.encodeCall(IDAIPermit.permit, (owner, spender, token.nonces(owner), deadline, true, v, r, s)) - : abi.encodeCall(ERC20.permit, (owner, spender, amount, deadline, v, r, s)); - - assembly { - success := call(gas(), token, 0, add(inputData, 32), mload(inputData), 0, 0) - } - } - - if (!success) { - // If the initial DOMAIN_SEPARATOR call on the token failed or a - // subsequent call to permit failed, fall back to using Permit2. - - (,, uint48 nonce) = PERMIT2.allowance(owner, address(token), spender); - - PERMIT2.permit( - owner, - IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(token), - amount: amount.toUint160(), - // Use an unlimited expiration because it most - // closely mimics how a standard approval works. - expiration: type(uint48).max, - nonce: nonce - }), - spender: spender, - sigDeadline: deadline - }), - bytes.concat(r, s, bytes1(v)) - ); - } - } -} diff --git a/src/ERC721/libraries/PermitHash.sol b/src/ERC721/libraries/PermitHashERC721.sol similarity index 69% rename from src/ERC721/libraries/PermitHash.sol rename to src/ERC721/libraries/PermitHashERC721.sol index 32d4a83f..9fbba466 100644 --- a/src/ERC721/libraries/PermitHash.sol +++ b/src/ERC721/libraries/PermitHashERC721.sol @@ -1,32 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; -import {ISignatureTransfer} from "../interfaces/ISignatureTransfer.sol"; +import {IAllowanceTransferERC721} from "../interfaces/IAllowanceTransferERC721.sol"; +import {ISignatureTransferERC721} from "../interfaces/ISignatureTransferERC721.sol"; -library PermitHash { +library PermitHashERC721 { bytes32 public constant _PERMIT_DETAILS_TYPEHASH = - keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); + keccak256("PermitDetails(address token,uint256 tokenId,uint48 expiration,uint48 nonce)"); bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256( - "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint256 tokenId,uint48 expiration,uint48 nonce)" ); bytes32 public constant _PERMIT_BATCH_TYPEHASH = keccak256( - "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint256 tokenId,uint48 expiration,uint48 nonce)" ); - bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); + bytes32 public constant _PERMIT_ALL_TYPEHASH = + keccak256("PermitAll(address token,address spender,uint48 expiration,uint48 nonce,uint256 sigDeadline)"); + + bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 tokenId)"); bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256( - "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" + "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 tokenId)" ); bytes32 public constant _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = keccak256( - "PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" + "PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 tokenId)" ); - string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 amount)"; + string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 tokenId)"; string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB = "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,"; @@ -34,13 +37,13 @@ library PermitHash { string public constant _PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB = "PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,"; - function hash(IAllowanceTransfer.PermitSingle memory permitSingle) internal pure returns (bytes32) { + function hash(IAllowanceTransferERC721.PermitSingle memory permitSingle) internal pure returns (bytes32) { bytes32 permitHash = _hashPermitDetails(permitSingle.details); return keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)); } - function hash(IAllowanceTransfer.PermitBatch memory permitBatch) internal pure returns (bytes32) { + function hash(IAllowanceTransferERC721.PermitBatch memory permitBatch) internal pure returns (bytes32) { uint256 numPermits = permitBatch.details.length; bytes32[] memory permitHashes = new bytes32[](numPermits); for (uint256 i = 0; i < numPermits; ++i) { @@ -56,14 +59,27 @@ library PermitHash { ); } - function hash(ISignatureTransfer.PermitTransferFrom memory permit) internal view returns (bytes32) { + function hash(IAllowanceTransferERC721.PermitAll memory permitAll) internal pure returns (bytes32) { + return keccak256( + abi.encode( + _PERMIT_ALL_TYPEHASH, + permitAll.token, + permitAll.spender, + permitAll.expiration, + permitAll.nonce, + permitAll.sigDeadline + ) + ); + } + + function hash(ISignatureTransferERC721.PermitTransferFrom memory permit) internal view returns (bytes32) { bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); return keccak256( abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline) ); } - function hash(ISignatureTransfer.PermitBatchTransferFrom memory permit) internal view returns (bytes32) { + function hash(ISignatureTransferERC721.PermitBatchTransferFrom memory permit) internal view returns (bytes32) { uint256 numPermitted = permit.permitted.length; bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); @@ -83,7 +99,7 @@ library PermitHash { } function hashWithWitness( - ISignatureTransfer.PermitTransferFrom memory permit, + ISignatureTransferERC721.PermitTransferFrom memory permit, bytes32 witness, string calldata witnessTypeString ) internal view returns (bytes32) { @@ -94,7 +110,7 @@ library PermitHash { } function hashWithWitness( - ISignatureTransfer.PermitBatchTransferFrom memory permit, + ISignatureTransferERC721.PermitBatchTransferFrom memory permit, bytes32 witness, string calldata witnessTypeString ) internal view returns (bytes32) { @@ -120,11 +136,11 @@ library PermitHash { ); } - function _hashPermitDetails(IAllowanceTransfer.PermitDetails memory details) private pure returns (bytes32) { + function _hashPermitDetails(IAllowanceTransferERC721.PermitDetails memory details) private pure returns (bytes32) { return keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, details)); } - function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory permitted) + function _hashTokenPermissions(ISignatureTransferERC721.TokenPermissions memory permitted) private pure returns (bytes32) diff --git a/src/ERC721/libraries/SafeCast160.sol b/src/ERC721/libraries/SafeCast160.sol deleted file mode 100644 index 5926036a..00000000 --- a/src/ERC721/libraries/SafeCast160.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -library SafeCast160 { - /// @notice Thrown when a valude greater than type(uint160).max is cast to uint160 - error UnsafeCast(); - - /// @notice Safely casts uint256 to uint160 - /// @param value The uint256 to be cast - function toUint160(uint256 value) internal pure returns (uint160) { - if (value > type(uint160).max) revert UnsafeCast(); - return uint160(value); - } -} diff --git a/src/ERC721/libraries/SignatureVerification.sol b/src/ERC721/libraries/SignatureVerification.sol deleted file mode 100644 index 904dfcd2..00000000 --- a/src/ERC721/libraries/SignatureVerification.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {IERC1271} from "../interfaces/IERC1271.sol"; - -library SignatureVerification { - /// @notice Thrown when the passed in signature is not a valid length - error InvalidSignatureLength(); - - /// @notice Thrown when the recovered signer is equal to the zero address - error InvalidSignature(); - - /// @notice Thrown when the recovered signer does not equal the claimedSigner - error InvalidSigner(); - - /// @notice Thrown when the recovered contract signature is incorrect - error InvalidContractSignature(); - - bytes32 constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - - function verify(bytes calldata signature, bytes32 hash, address claimedSigner) internal view { - bytes32 r; - bytes32 s; - uint8 v; - - if (claimedSigner.code.length == 0) { - if (signature.length == 65) { - (r, s) = abi.decode(signature, (bytes32, bytes32)); - v = uint8(signature[64]); - } else if (signature.length == 64) { - // EIP-2098 - bytes32 vs; - (r, vs) = abi.decode(signature, (bytes32, bytes32)); - s = vs & UPPER_BIT_MASK; - v = uint8(uint256(vs >> 255)) + 27; - } else { - revert InvalidSignatureLength(); - } - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) revert InvalidSignature(); - if (signer != claimedSigner) revert InvalidSigner(); - } else { - bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature); - if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature(); - } - } -} diff --git a/src/ERC721/interfaces/IERC1271.sol b/src/shared/IERC1271.sol similarity index 100% rename from src/ERC721/interfaces/IERC1271.sol rename to src/shared/IERC1271.sol diff --git a/src/ERC20/PermitErrors.sol b/src/shared/PermitErrors.sol similarity index 100% rename from src/ERC20/PermitErrors.sol rename to src/shared/PermitErrors.sol diff --git a/src/ERC20/libraries/SignatureVerification.sol b/src/shared/SignatureVerification.sol similarity index 97% rename from src/ERC20/libraries/SignatureVerification.sol rename to src/shared/SignatureVerification.sol index 904dfcd2..12d0b542 100644 --- a/src/ERC20/libraries/SignatureVerification.sol +++ b/src/shared/SignatureVerification.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {IERC1271} from "../interfaces/IERC1271.sol"; +import {IERC1271} from "./IERC1271.sol"; library SignatureVerification { /// @notice Thrown when the passed in signature is not a valid length diff --git a/test/AllowanceTransferInvariants.t.sol b/test/AllowanceTransferInvariants.t.sol index dd6d111e..d44049ad 100644 --- a/test/AllowanceTransferInvariants.t.sol +++ b/test/AllowanceTransferInvariants.t.sol @@ -1,10 +1,10 @@ pragma solidity 0.8.17; import "forge-std/Test.sol"; -import {TokenProvider} from "./utils/TokenProvider.sol"; +import {TokenProviderERC20} from "./utils/TokenProviderERC20.sol"; import {Permit2} from "../src/ERC20/Permit2.sol"; import {IAllowanceTransfer} from "../src/ERC20/interfaces/IAllowanceTransfer.sol"; -import {SignatureVerification} from "../src/ERC20/libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../src/shared/SignatureVerification.sol"; import {PermitSignature} from "./utils/PermitSignature.sol"; import {InvariantTest} from "./utils/InvariantTest.sol"; import {MockERC20} from "./mocks/MockERC20.sol"; diff --git a/test/AllowanceTransferTest.t.sol b/test/AllowanceTransferTest.t.sol deleted file mode 100644 index df827197..00000000 --- a/test/AllowanceTransferTest.t.sol +++ /dev/null @@ -1,698 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {TokenProvider} from "./utils/TokenProvider.sol"; -import {Permit2} from "../src/ERC20/Permit2.sol"; -import {PermitSignature} from "./utils/PermitSignature.sol"; -import {SignatureVerification} from "../src/ERC20/libraries/SignatureVerification.sol"; -import {AddressBuilder} from "./utils/AddressBuilder.sol"; -import {StructBuilder} from "./utils/StructBuilder.sol"; -import {AmountBuilder} from "./utils/AmountBuilder.sol"; -import {AllowanceTransfer} from "../src/ERC20/AllowanceTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "../src/ERC20/PermitErrors.sol"; -import {IAllowanceTransfer} from "../src/ERC20/interfaces/IAllowanceTransfer.sol"; -import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; - -contract AllowanceTransferTest is Test, TokenProvider, PermitSignature, GasSnapshot { - using AddressBuilder for address[]; - using stdStorage for StdStorage; - - event NonceInvalidation( - address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce - ); - event Approval( - address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration - ); - event Permit( - address indexed owner, - address indexed token, - address indexed spender, - uint160 amount, - uint48 expiration, - uint48 nonce - ); - event Lockdown(address indexed owner, address token, address spender); - - Permit2 permit2; - - address from; - uint256 fromPrivateKey; - - address fromDirty; - uint256 fromPrivateKeyDirty; - - address address0 = address(0); - address address2 = address(2); - - uint160 defaultAmount = 10 ** 18; - uint48 defaultNonce = 0; - uint32 dirtyNonce = 1; - uint48 defaultExpiration = uint48(block.timestamp + 5); - - // has some balance of token0 - address address3 = address(3); - - bytes32 DOMAIN_SEPARATOR; - - function setUp() public { - permit2 = new Permit2(); - DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); - - fromPrivateKey = 0x12341234; - from = vm.addr(fromPrivateKey); - - // Use this address to gas test dirty writes later. - fromPrivateKeyDirty = 0x56785678; - fromDirty = vm.addr(fromPrivateKeyDirty); - - initializeERC20Tokens(); - - setERC20TestTokens(from); - setERC20TestTokenApprovals(vm, from, address(permit2)); - - setERC20TestTokens(fromDirty); - setERC20TestTokenApprovals(vm, fromDirty, address(permit2)); - - // dirty the nonce for fromDirty address on token0 and token1 - vm.startPrank(fromDirty); - permit2.invalidateNonces(address(token0), address(this), 1); - permit2.invalidateNonces(address(token1), address(this), 1); - vm.stopPrank(); - // ensure address3 has some balance of token0 and token1 for dirty sstore on transfer - token0.mint(address3, defaultAmount); - token1.mint(address3, defaultAmount); - } - - function testApprove() public { - vm.prank(from); - vm.expectEmit(true, true, true, true); - emit Approval(from, address(token0), address(this), defaultAmount, defaultExpiration); - permit2.approve(address(token0), address(this), defaultAmount, defaultExpiration); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 0); - } - - function testSetAllowance() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - snapStart("permitCleanWrite"); - permit2.permit(from, permit, sig); - snapEnd(); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - } - - function testSetAllowanceCompactSig() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getCompactPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - assertEq(sig.length, 64); - - snapStart("permitCompactSig"); - permit2.permit(from, permit, sig); - snapEnd(); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - } - - function testSetAllowanceIncorrectSigLength() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - bytes memory sigExtra = bytes.concat(sig, bytes1(uint8(1))); - assertEq(sigExtra.length, 66); - - vm.expectRevert(SignatureVerification.InvalidSignatureLength.selector); - permit2.permit(from, permit, sigExtra); - } - - function testSetAllowanceDirtyWrite() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, dirtyNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); - - snapStart("permitDirtyWrite"); - permit2.permit(fromDirty, permit, sig); - snapEnd(); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 2); - } - - function testSetAllowanceBatchDifferentNonces() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - permit2.permit(from, permit, sig); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - IAllowanceTransfer.PermitBatch memory permitBatch = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, 1); - // first token nonce is 1, second token nonce is 0 - permitBatch.details[1].nonce = 0; - bytes memory sig1 = getPermitBatchSignature(permitBatch, fromPrivateKey, DOMAIN_SEPARATOR); - - permit2.permit(from, permitBatch, sig1); - - (amount, expiration, nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 2); - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, defaultAmount); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - } - - function testSetAllowanceBatch() public { - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - IAllowanceTransfer.PermitBatch memory permit = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - snapStart("permitBatchCleanWrite"); - permit2.permit(from, permit, sig); - snapEnd(); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, defaultAmount); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - } - - function testSetAllowanceBatchEvent() public { - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - uint160[] memory amounts = AmountBuilder.fillUInt160(2, defaultAmount); - - IAllowanceTransfer.PermitBatch memory permit = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - vm.expectEmit(true, true, true, true); - emit Permit(from, tokens[0], address(this), amounts[0], defaultExpiration, defaultNonce); - vm.expectEmit(true, true, true, true); - emit Permit(from, tokens[1], address(this), amounts[1], defaultExpiration, defaultNonce); - permit2.permit(from, permit, sig); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, defaultAmount); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - } - - function testSetAllowanceBatchDirtyWrite() public { - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - IAllowanceTransfer.PermitBatch memory permit = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, dirtyNonce); - bytes memory sig = getPermitBatchSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); - - snapStart("permitBatchDirtyWrite"); - permit2.permit(fromDirty, permit, sig); - snapEnd(); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 2); - (uint160 amount1, uint48 expiration1, uint48 nonce1) = - permit2.allowance(fromDirty, address(token1), address(this)); - assertEq(amount1, defaultAmount); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 2); - } - - // test setting allowance with ordered nonce and transfer - function testSetAllowanceTransfer() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); - - permit2.permit(from, permit, sig); - - (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); - - assertEq(amount, defaultAmount); - - permit2.transferFrom(from, address0, defaultAmount, address(token0)); - - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address0), startBalanceTo + defaultAmount); - } - - function testTransferFromWithGasSnapshot() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); - - permit2.permit(from, permit, sig); - - (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); - - assertEq(amount, defaultAmount); - - snapStart("transferFrom"); - permit2.transferFrom(from, address0, defaultAmount, address(token0)); - - snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address0), startBalanceTo + defaultAmount); - } - - function testBatchTransferFromWithGasSnapshot() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); - - permit2.permit(from, permit, sig); - - (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - - // permit token0 for 1 ** 18 - address[] memory owners = AddressBuilder.fill(3, from); - IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = - StructBuilder.fillAllowanceTransferDetail(3, address(token0), 1 ** 18, address0, owners); - snapStart("batchTransferFrom"); - permit2.transferFrom(transferDetails); - snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom - 3 * 1 ** 18); - assertEq(token0.balanceOf(address0), startBalanceTo + 3 * 1 ** 18); - (amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount - 3 * 1 ** 18); - } - - // dirty sstore on nonce, dirty sstore on transfer - function testSetAllowanceTransferDirtyNonceDirtyTransfer() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, dirtyNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(fromDirty); - uint256 startBalanceTo = token0.balanceOf(address3); - // ensure its a dirty store for the recipient address - assertEq(startBalanceTo, defaultAmount); - - snapStart("permitDirtyNonce"); - permit2.permit(fromDirty, permit, sig); - snapEnd(); - - (uint160 amount,,) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(amount, defaultAmount); - - permit2.transferFrom(fromDirty, address3, defaultAmount, address(token0)); - - assertEq(token0.balanceOf(fromDirty), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address3), startBalanceTo + defaultAmount); - } - - function testSetAllowanceInvalidSignature() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - snapStart("permitInvalidSigner"); - vm.expectRevert(SignatureVerification.InvalidSigner.selector); - permit.spender = address0; - permit2.permit(from, permit, sig); - snapEnd(); - } - - function testSetAllowanceDeadlinePassed() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 sigDeadline = block.timestamp + 100; - - vm.warp(block.timestamp + 101); - snapStart("permitSignatureExpired"); - vm.expectRevert(abi.encodeWithSelector(SignatureExpired.selector, sigDeadline)); - permit2.permit(from, permit, sig); - snapEnd(); - } - - function testMaxAllowance() public { - uint160 maxAllowance = type(uint160).max; - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), maxAllowance, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); - - snapStart("permitSetMaxAllowanceCleanWrite"); - permit2.permit(from, permit, sig); - snapEnd(); - - (uint160 startAllowedAmount0,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(startAllowedAmount0, type(uint160).max); - - permit2.transferFrom(from, address0, defaultAmount, address(token0)); - - (uint160 endAllowedAmount0,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(endAllowedAmount0, type(uint160).max); - - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address0), startBalanceTo + defaultAmount); - } - - function testMaxAllowanceDirtyWrite() public { - uint160 maxAllowance = type(uint160).max; - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), maxAllowance, defaultExpiration, dirtyNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(fromDirty); - uint256 startBalanceTo = token0.balanceOf(address0); - - snapStart("permitSetMaxAllowanceDirtyWrite"); - permit2.permit(fromDirty, permit, sig); - snapEnd(); - - (uint160 startAllowedAmount0,,) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(startAllowedAmount0, type(uint160).max); - - permit2.transferFrom(fromDirty, address0, defaultAmount, address(token0)); - - (uint160 endAllowedAmount0,,) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(endAllowedAmount0, type(uint160).max); - - assertEq(token0.balanceOf(fromDirty), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address0), startBalanceTo + defaultAmount); - } - - function testPartialAllowance() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); - - permit2.permit(from, permit, sig); - - (uint160 startAllowedAmount0,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(startAllowedAmount0, defaultAmount); - - uint160 transferAmount = 5 ** 18; - permit2.transferFrom(from, address0, transferAmount, address(token0)); - (uint160 endAllowedAmount0,,) = permit2.allowance(from, address(token0), address(this)); - // ensure the allowance was deducted - assertEq(endAllowedAmount0, defaultAmount - transferAmount); - - assertEq(token0.balanceOf(from), startBalanceFrom - transferAmount); - assertEq(token0.balanceOf(address0), startBalanceTo + transferAmount); - } - - function testReuseOrderedNonceInvalid() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - permit2.permit(from, permit, sig); - (,, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(nonce, 1); - - (uint160 amount, uint48 expiration,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - - vm.expectRevert(InvalidNonce.selector); - permit2.permit(from, permit, sig); - } - - function testInvalidateNonces() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - // Invalidates the 0th nonce by setting the new nonce to 1. - vm.prank(from); - vm.expectEmit(true, true, true, true); - emit NonceInvalidation(from, address(token0), address(this), 1, defaultNonce); - permit2.invalidateNonces(address(token0), address(this), 1); - (,, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(nonce, 1); - - vm.expectRevert(InvalidNonce.selector); - permit2.permit(from, permit, sig); - } - - function testInvalidateMultipleNonces() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - // Valid permit, uses nonce 0. - permit2.permit(from, permit, sig); - (,, uint48 nonce1) = permit2.allowance(from, address(token0), address(this)); - assertEq(nonce1, 1); - - permit = defaultERC20PermitAllowance(address(token1), defaultAmount, defaultExpiration, nonce1); - sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - // Invalidates the 9 nonces by setting the new nonce to 33. - vm.prank(from); - vm.expectEmit(true, true, true, true); - - emit NonceInvalidation(from, address(token0), address(this), 33, nonce1); - permit2.invalidateNonces(address(token0), address(this), 33); - (,, uint48 nonce2) = permit2.allowance(from, address(token0), address(this)); - assertEq(nonce2, 33); - - vm.expectRevert(InvalidNonce.selector); - permit2.permit(from, permit, sig); - } - - function testInvalidateNoncesInvalid() public { - // fromDirty nonce is 1 - vm.prank(fromDirty); - vm.expectRevert(InvalidNonce.selector); - // setting nonce to 0 should revert - permit2.invalidateNonces(address(token0), address(this), 0); - } - - function testExcessiveInvalidation() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint32 numInvalidate = type(uint16).max; - vm.startPrank(from); - vm.expectRevert(IAllowanceTransfer.ExcessiveInvalidation.selector); - permit2.invalidateNonces(address(token0), address(this), numInvalidate + 1); - vm.stopPrank(); - - permit2.permit(from, permit, sig); - (,, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(nonce, 1); - } - - function testBatchTransferFrom() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); - - permit2.permit(from, permit, sig); - - (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - - // permit token0 for 1 ** 18 - address[] memory owners = AddressBuilder.fill(3, from); - IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = - StructBuilder.fillAllowanceTransferDetail(3, address(token0), 1 ** 18, address0, owners); - snapStart("batchTransferFrom"); - permit2.transferFrom(transferDetails); - snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom - 3 * 1 ** 18); - assertEq(token0.balanceOf(address0), startBalanceTo + 3 * 1 ** 18); - (amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount - 3 * 1 ** 18); - } - - function testBatchTransferFromMultiToken() public { - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - IAllowanceTransfer.PermitBatch memory permitBatch = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitBatchSignature(permitBatch, fromPrivateKey, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address0); - uint256 startBalanceTo1 = token1.balanceOf(address0); - - permit2.permit(from, permitBatch, sig); - - (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - (amount,,) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount, defaultAmount); - - // permit token0 for 1 ** 18 - address[] memory owners = AddressBuilder.fill(2, from); - IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = - StructBuilder.fillAllowanceTransferDetail(2, tokens, 1 ** 18, address0, owners); - snapStart("batchTransferFromMultiToken"); - permit2.transferFrom(transferDetails); - snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom0 - 1 ** 18); - assertEq(token1.balanceOf(from), startBalanceFrom1 - 1 ** 18); - assertEq(token0.balanceOf(address0), startBalanceTo0 + 1 ** 18); - assertEq(token1.balanceOf(address0), startBalanceTo1 + 1 ** 18); - (amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount - 1 ** 18); - (amount,,) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount, defaultAmount - 1 ** 18); - } - - function testBatchTransferFromDifferentOwners() public { - IAllowanceTransfer.PermitSingle memory permit = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - IAllowanceTransfer.PermitSingle memory permitDirty = - defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, dirtyNonce); - bytes memory sigDirty = getPermitSignature(permitDirty, fromPrivateKeyDirty, DOMAIN_SEPARATOR); - - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address(this)); - uint256 startBalanceFromDirty = token0.balanceOf(fromDirty); - - // from and fromDirty approve address(this) as spender - permit2.permit(from, permit, sig); - permit2.permit(fromDirty, permitDirty, sigDirty); - - (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - (uint160 amount1,,) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(amount1, defaultAmount); - - address[] memory owners = AddressBuilder.fill(1, from).push(fromDirty); - IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = - StructBuilder.fillAllowanceTransferDetail(2, address(token0), 1 ** 18, address(this), owners); - snapStart("transferFrom with different owners"); - permit2.transferFrom(transferDetails); - snapEnd(); - - assertEq(token0.balanceOf(from), startBalanceFrom - 1 ** 18); - assertEq(token0.balanceOf(fromDirty), startBalanceFromDirty - 1 ** 18); - assertEq(token0.balanceOf(address(this)), startBalanceTo + 2 * 1 ** 18); - (amount,,) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount - 1 ** 18); - (amount,,) = permit2.allowance(fromDirty, address(token0), address(this)); - assertEq(amount, defaultAmount - 1 ** 18); - } - - function testLockdown() public { - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - IAllowanceTransfer.PermitBatch memory permit = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - permit2.permit(from, permit, sig); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, defaultAmount); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - - IAllowanceTransfer.TokenSpenderPair[] memory approvals = new IAllowanceTransfer.TokenSpenderPair[](2); - approvals[0] = IAllowanceTransfer.TokenSpenderPair(address(token0), address(this)); - approvals[1] = IAllowanceTransfer.TokenSpenderPair(address(token1), address(this)); - - vm.prank(from); - snapStart("lockdown"); - permit2.lockdown(approvals); - snapEnd(); - - (amount, expiration, nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, 0); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - (amount1, expiration1, nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, 0); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - } - - function testLockdownEvent() public { - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); - IAllowanceTransfer.PermitBatch memory permit = - defaultERC20PermitBatchAllowance(tokens, defaultAmount, defaultExpiration, defaultNonce); - bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - - permit2.permit(from, permit, sig); - - (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, defaultAmount); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, defaultAmount); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - - IAllowanceTransfer.TokenSpenderPair[] memory approvals = new IAllowanceTransfer.TokenSpenderPair[](2); - approvals[0] = IAllowanceTransfer.TokenSpenderPair(address(token0), address(this)); - approvals[1] = IAllowanceTransfer.TokenSpenderPair(address(token1), address(this)); - - //TODO :fix expecting multiple events, can only check for 1 - vm.prank(from); - vm.expectEmit(true, false, false, false); - emit Lockdown(from, address(token0), address(this)); - permit2.lockdown(approvals); - - (amount, expiration, nonce) = permit2.allowance(from, address(token0), address(this)); - assertEq(amount, 0); - assertEq(expiration, defaultExpiration); - assertEq(nonce, 1); - (amount1, expiration1, nonce1) = permit2.allowance(from, address(token1), address(this)); - assertEq(amount1, 0); - assertEq(expiration1, defaultExpiration); - assertEq(nonce1, 1); - } -} diff --git a/test/AllowanceTransferTestERC20.t.sol b/test/AllowanceTransferTestERC20.t.sol new file mode 100644 index 00000000..812b38d8 --- /dev/null +++ b/test/AllowanceTransferTestERC20.t.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {BaseAllowanceTransferTest} from "./BaseAllowanceTransferTest.t.sol"; +import {Permit2} from "../src/ERC20/Permit2.sol"; +import {TokenProviderERC20} from "./utils/TokenProviderERC20.sol"; +import {PermitHash} from "../src/ERC20/libraries/PermitHash.sol"; +import {PermitAbstraction} from "./utils/PermitAbstraction.sol"; +import {PermitSignature} from "./utils/PermitSignature.sol"; +import {IAllowanceTransfer} from "../src/ERC20/interfaces/IAllowanceTransfer.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; + +contract AllowanceTransferTestERC20 is TokenProviderERC20, BaseAllowanceTransferTest { + function setUp() public override { + permit2 = address(new Permit2()); + DOMAIN_SEPARATOR = Permit2(permit2).DOMAIN_SEPARATOR(); + + // amount for ERC20s + defaultAmountOrId = 10 ** 18; + + fromPrivateKey = 0x12341234; + from = vm.addr(fromPrivateKey); + + // Use this address to gas test dirty writes later. + fromPrivateKeyDirty = 0x56785678; + fromDirty = vm.addr(fromPrivateKeyDirty); + + initializeTokens(); + + setTokens(from); + setTokenApprovals(vm, from, address(permit2)); + + setTokens(fromDirty); + setTokenApprovals(vm, fromDirty, address(permit2)); + + // dirty the nonce for fromDirty address on token0 and token1 + vm.startPrank(fromDirty); + Permit2(permit2).invalidateNonces(token0(), address(this), 1); + Permit2(permit2).invalidateNonces(token1(), address(this), 1); + vm.stopPrank(); + // ensure address3 has some balance of token0 and token1 for dirty sstore on transfer + MockERC20(token0()).mint(address3, defaultAmountOrId); + MockERC20(token1()).mint(address3, defaultAmountOrId); + } + + function getAmountOrId() public override returns (uint256) { + // default amount + return defaultAmountOrId; + } + + function getExpectedAmountOrSpender() public override returns (uint160) { + // expected amount is the defaultAmountOrId + return uint160(defaultAmountOrId); + } + + function permit2Approve(address token, address spender, uint256 amountOrId, uint48 expiration) public override { + Permit2(permit2).approve(token, spender, uint160(amountOrId), expiration); + } + + function permit2Allowance(address from, address token, uint256 tokenIdOrSpender) + public + override + returns (uint160, uint48, uint48) + { + return Permit2(permit2).allowance(from, token, address(uint160(tokenIdOrSpender))); + } + + function permit2Permit(address from, PermitAbstraction.IPermitSingle memory permit, bytes memory sig) + public + override + { + // convert IPermitSingle to AllowanceTransfer.PermitSingle + IAllowanceTransfer.PermitSingle memory parsedPermit = IAllowanceTransfer.PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: permit.token, + amount: permit.amountOrId, + expiration: permit.expiration, + nonce: permit.nonce + }), + spender: permit.spender, + sigDeadline: permit.sigDeadline + }); + + Permit2(permit2).permit(from, parsedPermit, sig); + } + + function permit2Permit(address from, PermitAbstraction.IPermitBatch memory permitBatch, bytes memory sig) + public + override + { + // convert IPermitBatch to IAllowanceTransfer.PermitBatch + + IAllowanceTransfer.PermitDetails[] memory details = + new IAllowanceTransfer.PermitDetails[](permitBatch.tokens.length); + for (uint256 i = 0; i < details.length; i++) { + details[i] = IAllowanceTransfer.PermitDetails({ + token: permitBatch.tokens[i], + amount: permitBatch.amountOrIds[i], + expiration: permitBatch.expirations[i], + nonce: permitBatch.nonces[i] + }); + } + IAllowanceTransfer.PermitBatch memory parsedPermitBatch = IAllowanceTransfer.PermitBatch({ + details: details, + spender: permitBatch.spender, + sigDeadline: permitBatch.sigDeadline + }); + + Permit2(permit2).permit(from, parsedPermitBatch, sig); + } + + function permit2TransferFrom(address from, address to, uint160 amountOrId, address token) public override { + Permit2(permit2).transferFrom(from, to, amountOrId, token); + } + + function token0() public view override returns (address) { + return address(_token0); + } + + function token1() public view override returns (address) { + return address(_token1); + } + + function balanceOf(address token, address from) public override returns (uint256) { + return MockERC20(token).balanceOf(from); + } + + function getPermitSignature(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + public + override + returns (bytes memory sig) + { + (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); + return bytes.concat(r, s, bytes1(v)); + } + + function getCompactPermitSignature(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + public + override + returns (bytes memory sig) + { + (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); + bytes32 vs; + (r, vs) = getCompactSignature(v, r, s); + return bytes.concat(r, vs); + } + + function getPermitSignatureRaw(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + internal + returns (uint8 v, bytes32 r, bytes32 s) + { + // convert IPermitSingle to permit details & hash + bytes32 permitHash = keccak256( + abi.encode( + PermitHash._PERMIT_DETAILS_TYPEHASH, permit.token, permit.amountOrId, permit.expiration, permit.nonce + ) + ); + + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode(PermitHash._PERMIT_SINGLE_TYPEHASH, permitHash, permit.spender, permit.sigDeadline) + ) + ) + ); + + (v, r, s) = vm.sign(privateKey, msgHash); + } + + function getPermitBatchSignature(IPermitBatch memory permit, uint256 privateKey, bytes32 domainSeparator) + public + override + returns (bytes memory) + { + bytes32[] memory permitHashes = new bytes32[](permit.tokens.length); + for (uint256 i = 0; i < permit.tokens.length; ++i) { + permitHashes[i] = keccak256( + abi.encode( + PermitHash._PERMIT_DETAILS_TYPEHASH, + permit.tokens[i], + permit.amountOrIds[i], + permit.expirations[i], + permit.nonces[i] + ) + ); + } + + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode( + PermitHash._PERMIT_BATCH_TYPEHASH, + keccak256(abi.encodePacked(permitHashes)), + permit.spender, + permit.sigDeadline + ) + ) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } +} diff --git a/test/AllowanceTransferTestERC721.t.sol b/test/AllowanceTransferTestERC721.t.sol new file mode 100644 index 00000000..eacc2229 --- /dev/null +++ b/test/AllowanceTransferTestERC721.t.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {BaseAllowanceTransferTest} from "./BaseAllowanceTransferTest.t.sol"; +import {Permit2ERC721} from "../src/ERC721/Permit2ERC721.sol"; +import {TokenProviderERC721} from "./utils/TokenProviderERC721.sol"; +import {PermitHashERC721} from "../src/ERC721/libraries/PermitHashERC721.sol"; +import {PermitAbstraction} from "./utils/PermitAbstraction.sol"; +import {PermitSignature} from "./utils/PermitSignature.sol"; +import {IAllowanceTransferERC721} from "../src/ERC721/interfaces/IAllowanceTransferERC721.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; + +contract AllowanceTransferTestERC721 is TokenProviderERC721, BaseAllowanceTransferTest { + uint256 currentId = 1; + + function setUp() public override { + permit2 = address(new Permit2ERC721()); + DOMAIN_SEPARATOR = Permit2ERC721(permit2).DOMAIN_SEPARATOR(); + + uint256 mintAmount = 10 ** 18; + // amount for ERC20s + defaultAmountOrId = 0; + + fromPrivateKey = 0x12341234; + from = vm.addr(fromPrivateKey); + + // Use this address to gas test dirty writes later. + fromPrivateKeyDirty = 0x56785678; + fromDirty = vm.addr(fromPrivateKeyDirty); + + initializeTokens(); + + setToken0(from); + setTokenApprovals0(vm, from, address(permit2)); + + setToken1(fromDirty); + setTokenApprovals1(vm, fromDirty, address(permit2)); + + // dirty the nonce for fromDirty address on token0 and token1 + // use token1 for fromDirty tests + vm.startPrank(fromDirty); + Permit2ERC721(permit2).invalidateNonces(token1(), address(this), 1); + vm.stopPrank(); + } + + function getExpectedAmountOrSpender() public override returns (uint160) { + // spender is this address + return uint160(address(this)); + } + + function setAmountOrId(uint256 id) public override { + currentId = id; + } + + function getAmountOrId() public override returns (uint256) { + // tokenId for token0 is 1 + return currentId; + } + + function permit2Approve(address token, address spender, uint256 amountOrId, uint48 expiration) public override { + Permit2ERC721(permit2).approve(token, spender, amountOrId, expiration); + } + + function permit2Allowance(address from, address token, uint256 tokenIdOrSpender) + public + override + returns (uint160, uint48, uint48) + { + (address spender1, uint48 expiration1, uint48 nonce1) = + Permit2ERC721(address(permit2)).allowance(from, token, getAmountOrId()); + return (uint160(spender1), expiration1, nonce1); + } + + function permit2Permit(address from, PermitAbstraction.IPermitSingle memory permit, bytes memory sig) + public + override + { + // convert IPermitSingle to AllowanceTransfer.PermitSingle + IAllowanceTransferERC721.PermitSingle memory parsedPermit = IAllowanceTransferERC721.PermitSingle({ + details: IAllowanceTransferERC721.PermitDetails({ + token: permit.token, + tokenId: permit.amountOrId, + expiration: permit.expiration, + nonce: permit.nonce + }), + spender: permit.spender, + sigDeadline: permit.sigDeadline + }); + + Permit2ERC721(permit2).permit(from, parsedPermit, sig); + } + + function permit2Permit(address from, PermitAbstraction.IPermitBatch memory permitBatch, bytes memory sig) + public + override + { + // convert IPermitBatch to IAllowanceTransferERC721.PermitBatch + + IAllowanceTransferERC721.PermitDetails[] memory details = + new IAllowanceTransferERC721.PermitDetails[](permitBatch.tokens.length); + for (uint256 i = 0; i < details.length; i++) { + details[i] = IAllowanceTransferERC721.PermitDetails({ + token: permitBatch.tokens[i], + tokenId: permitBatch.amountOrIds[i], + expiration: permitBatch.expirations[i], + nonce: permitBatch.nonces[i] + }); + } + IAllowanceTransferERC721.PermitBatch memory parsedPermitBatch = IAllowanceTransferERC721.PermitBatch({ + details: details, + spender: permitBatch.spender, + sigDeadline: permitBatch.sigDeadline + }); + + Permit2ERC721(permit2).permit(from, parsedPermitBatch, sig); + } + + function permit2TransferFrom(address from, address to, uint160 amountOrId, address token) public override { + Permit2ERC721(permit2).transferFrom(from, to, amountOrId, token); + } + + function token0() public view override returns (address) { + return address(_token0); + } + + function token1() public view override returns (address) { + return address(_token1); + } + + function balanceOf(address token, address from) public override returns (uint256) { + return MockERC20(token).balanceOf(from); + } + + function getPermitSignature(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + public + override + returns (bytes memory sig) + { + (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); + return bytes.concat(r, s, bytes1(v)); + } + + function getCompactPermitSignature(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + public + override + returns (bytes memory sig) + { + (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); + bytes32 vs; + (r, vs) = getCompactSignature(v, r, s); + return bytes.concat(r, vs); + } + + function getPermitSignatureRaw(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + internal + returns (uint8 v, bytes32 r, bytes32 s) + { + // convert IPermitSingle to permit details & hash + bytes32 permitHash = keccak256( + abi.encode( + PermitHashERC721._PERMIT_DETAILS_TYPEHASH, + permit.token, + permit.amountOrId, + permit.expiration, + permit.nonce + ) + ); + + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode(PermitHashERC721._PERMIT_SINGLE_TYPEHASH, permitHash, permit.spender, permit.sigDeadline) + ) + ) + ); + + (v, r, s) = vm.sign(privateKey, msgHash); + } + + function getPermitBatchSignature(IPermitBatch memory permit, uint256 privateKey, bytes32 domainSeparator) + public + override + returns (bytes memory) + { + bytes32[] memory permitHashes = new bytes32[](permit.tokens.length); + for (uint256 i = 0; i < permit.tokens.length; ++i) { + permitHashes[i] = keccak256( + abi.encode( + PermitHashERC721._PERMIT_DETAILS_TYPEHASH, + permit.tokens[i], + permit.amountOrIds[i], + permit.expirations[i], + permit.nonces[i] + ) + ); + } + + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode( + PermitHashERC721._PERMIT_BATCH_TYPEHASH, + keccak256(abi.encodePacked(permitHashes)), + permit.spender, + permit.sigDeadline + ) + ) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } +} diff --git a/test/AllowanceUnitTest.sol b/test/AllowanceUnitTest.sol deleted file mode 100644 index 2aa0d170..00000000 --- a/test/AllowanceUnitTest.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import "./mocks/MockPermit2.sol"; -import {TokenProvider} from "./utils/TokenProvider.sol"; - -contract AllowanceUnitTest is Test, TokenProvider { - MockPermit2 permit2; - - address from = address(0xBEEE); - address spender = address(0xBBBB); - - function setUp() public { - permit2 = new MockPermit2(); - initializeERC20Tokens(); - } - - function testUpdateAmountExpirationRandomly(uint160 amount, uint48 expiration) public { - address token = address(token1); - - (,, uint48 nonce) = permit2.allowance(from, token, spender); - - permit2.mockUpdateAmountAndExpiration(from, token, spender, amount, expiration); - - uint48 timestampAfterUpdate = expiration == 0 ? uint48(block.timestamp) : expiration; - - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, token, spender); - assertEq(amount, amount1); - assertEq(timestampAfterUpdate, expiration1); - /// nonce shouldnt change - assertEq(nonce, nonce1); - } - - function testUpdateAllRandomly(uint160 amount, uint48 expiration, uint48 nonce) public { - // there is overflow since we increment the nonce by 1 - // we assume we will never be able to reach 2**48 - vm.assume(nonce < type(uint48).max); - - address token = address(token1); - - permit2.mockUpdateAll(from, token, spender, amount, expiration, nonce); - - uint48 nonceAfterUpdate = nonce + 1; - uint48 timestampAfterUpdate = expiration == 0 ? uint48(block.timestamp) : expiration; - - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, token, spender); - - assertEq(amount, amount1); - assertEq(timestampAfterUpdate, expiration1); - assertEq(nonceAfterUpdate, nonce1); - } - - function testPackAndUnpack(uint160 amount, uint48 expiration, uint48 nonce) public { - // pack some numbers - uint256 word = Allowance.pack(amount, expiration, nonce); - - // store the raw word - permit2.doStore(from, address(token1), spender, word); - - // load it as a packed allowance - (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), spender); - assertEq(amount, amount1); - assertEq(expiration, expiration1); - assertEq(nonce, nonce1); - - // get the stored word - uint256 word1 = permit2.getStore(from, address(token1), spender); - assertEq(word, word1); - } -} diff --git a/test/BaseAllowanceTransferTest.t.sol b/test/BaseAllowanceTransferTest.t.sol new file mode 100644 index 00000000..00fe53ce --- /dev/null +++ b/test/BaseAllowanceTransferTest.t.sol @@ -0,0 +1,729 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {Permit2} from "../src/ERC20/Permit2.sol"; +import {PermitAbstraction} from "./utils/PermitAbstraction.sol"; +import {PermitSignature} from "./utils/PermitSignature.sol"; +import {SignatureVerification} from "../src/shared/SignatureVerification.sol"; +import {AddressBuilder} from "./utils/AddressBuilder.sol"; +import {StructBuilder} from "./utils/StructBuilder.sol"; +import {AmountBuilder} from "./utils/AmountBuilder.sol"; +import {AllowanceTransfer} from "../src/ERC20/AllowanceTransfer.sol"; +import {SignatureExpired, InvalidNonce} from "../src/shared/PermitErrors.sol"; +import {IAllowanceTransfer} from "../src/ERC20/interfaces/IAllowanceTransfer.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; + +abstract contract BaseAllowanceTransferTest is Test, PermitSignature, PermitAbstraction, GasSnapshot { + using AddressBuilder for address[]; + using stdStorage for StdStorage; + + event NonceInvalidation( + address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce + ); + event Approval( + address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration + ); + event Permit( + address indexed owner, + address indexed token, + address indexed spender, + uint160 amount, + uint48 expiration, + uint48 nonce + ); + event Lockdown(address indexed owner, address token, address spender); + + address permit2; + + address from; + uint256 fromPrivateKey; + + address fromDirty; + uint256 fromPrivateKeyDirty; + + address address0 = address(0); + address address2 = address(2); + + uint160 defaultAmountOrId = 10 ** 18; + uint48 defaultNonce = 0; + uint32 dirtyNonce = 1; + uint48 defaultExpiration = uint48(block.timestamp + 5); + + // has some balance of token0 + address address3 = address(3); + + address spender = address(this); + + bytes32 DOMAIN_SEPARATOR; + + function setUp() public virtual; + + function token0() public virtual returns (address); + function token1() public virtual returns (address); + + function balanceOf(address token, address from) public virtual returns (uint256); + + function getPermitSignature(IPermitSingle memory permit, uint256 privKey, bytes32 domainSeparator) + public + virtual + returns (bytes memory); + + function getCompactPermitSignature(IPermitSingle memory permit, uint256 privateKey, bytes32 domainSeparator) + public + virtual + returns (bytes memory sig); + + function getPermitBatchSignature(IPermitBatch memory permit, uint256 privKey, bytes32 domainSeparator) + public + virtual + returns (bytes memory); + + function permit2Approve(address token, address spender, uint256 amountOrId, uint48 expiration) public virtual; + + function permit2Allowance(address from, address token, uint256 tokenIdOrSpender) + public + virtual + returns (uint160, uint48, uint48); + + function permit2Permit(address from, PermitAbstraction.IPermitSingle memory permit, bytes memory sig) + public + virtual; + + function permit2Permit(address from, PermitAbstraction.IPermitBatch memory permit, bytes memory sig) + public + virtual; + + function permit2TransferFrom(address from, address to, uint160 amount, address token) public virtual; + + function getExpectedAmountOrSpender() public virtual returns (uint160); + function getAmountOrId() public virtual returns (uint256); + + function testApprove() public { + vm.prank(from); + // vm.expectEmit(true, true, true, true); + // emit Approval(from, token0(), address(this), getAmountOrId(), defaultExpiration); + permit2Approve(token0(), address(this), getAmountOrId(), defaultExpiration); + + (uint160 amountOrSpender, uint48 expiration, uint48 nonce) = + permit2Allowance(from, token0(), uint256(uint160(address(this)))); + + // for erc20s the amountOrSpender should be compared to defaultAmountOrId + // for erc721s the amountOrSpender should be compared to spender + assertEq(amountOrSpender, getExpectedAmountOrSpender()); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 0); + } + + function setAmountOrId(uint256 id) public virtual {} + + function testSetAllowance() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + snapStart("permitCleanWrite"); + permit2Permit(from, permit, sig); + snapEnd(); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 1); + } + + function testSetAllowanceCompactSig() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getCompactPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + assertEq(sig.length, 64); + + snapStart("permitCompactSig"); + permit2Permit(from, permit, sig); + snapEnd(); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 1); + } + + function testSetAllowanceIncorrectSigLength() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + bytes memory sigExtra = bytes.concat(sig, bytes1(uint8(1))); + assertEq(sigExtra.length, 66); + + vm.expectRevert(SignatureVerification.InvalidSignatureLength.selector); + permit2Permit(from, permit, sigExtra); + } + + function testSetAllowanceDirtyWrite() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, dirtyNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); + + snapStart("permitDirtyWrite"); + permit2Permit(fromDirty, permit, sig); + snapEnd(); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(fromDirty, token1(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 2); + } + + function testSetAllowanceBatchDifferentNonces() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + permit2Permit(from, permit, sig); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 1); + + address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + PermitAbstraction.IPermitBatch memory permitBatch = + defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, 1); + // first token nonce is 1, second token nonce is 0 + permitBatch.nonces[1] = 0; + bytes memory sig1 = getPermitBatchSignature(permitBatch, fromPrivateKey, DOMAIN_SEPARATOR); + + permit2Permit(from, permitBatch, sig1); + + (amount, expiration, nonce) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 2); + (uint160 amount1, uint48 expiration1, uint48 nonce1) = + permit2Allowance(from, token1(), uint256(uint160(address(this)))); + assertEq(amount1, defaultAmountOrId); + assertEq(expiration1, defaultExpiration); + assertEq(nonce1, 1); + } + + function testSetAllowanceBatch() public { + address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + PermitAbstraction.IPermitBatch memory permit = + defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + snapStart("permitBatchCleanWrite"); + permit2Permit(from, permit, sig); + snapEnd(); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 1); + (uint160 amount1, uint48 expiration1, uint48 nonce1) = + permit2Allowance(from, token1(), uint256(uint160(address(this)))); + assertEq(amount1, defaultAmountOrId); + assertEq(expiration1, defaultExpiration); + assertEq(nonce1, 1); + } + + function testSetAllowanceBatchEvent() public { + address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + uint160[] memory amounts = AmountBuilder.fillUInt160(2, defaultAmountOrId); + + PermitAbstraction.IPermitBatch memory permit = + defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + vm.expectEmit(true, true, true, true); + emit Permit(from, tokens[0], address(this), amounts[0], defaultExpiration, defaultNonce); + vm.expectEmit(true, true, true, true); + emit Permit(from, tokens[1], address(this), amounts[1], defaultExpiration, defaultNonce); + permit2Permit(from, permit, sig); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 1); + (uint160 amount1, uint48 expiration1, uint48 nonce1) = + permit2Allowance(from, token1(), uint256(uint160(address(this)))); + assertEq(amount1, defaultAmountOrId); + assertEq(expiration1, defaultExpiration); + assertEq(nonce1, 1); + } + + function testSetAllowanceBatchDirtyWrite() public { + address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + PermitAbstraction.IPermitBatch memory permit = + defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, dirtyNonce); + bytes memory sig = getPermitBatchSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); + + snapStart("permitBatchDirtyWrite"); + permit2Permit(fromDirty, permit, sig); + snapEnd(); + + (uint160 amount, uint48 expiration, uint48 nonce) = + permit2Allowance(fromDirty, token1(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + assertEq(nonce, 2); + (uint160 amount1, uint48 expiration1, uint48 nonce1) = + permit2Allowance(fromDirty, token1(), uint256(uint160(address(this)))); + assertEq(amount1, defaultAmountOrId); + assertEq(expiration1, defaultExpiration); + assertEq(nonce1, 2); + } + + // test setting allowance with ordered nonce and transfer + function testSetAllowanceTransfer() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + uint256 startBalanceFrom = balanceOf(token0(), from); + uint256 startBalanceTo = balanceOf(token0(), address0); + + permit2Permit(from, permit, sig); + + (uint160 amount,,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + + assertEq(amount, defaultAmountOrId); + + permit2TransferFrom(from, address0, defaultAmountOrId, token0()); + + assertEq(balanceOf(token0(), from), startBalanceFrom - defaultAmountOrId); + assertEq(balanceOf(token0(), address0), startBalanceTo + defaultAmountOrId); + } + + function testTransferFromWithGasSnapshot() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + uint256 startBalanceFrom = balanceOf(token0(), from); + uint256 startBalanceTo = balanceOf(token0(), address0); + + permit2Permit(from, permit, sig); + + (uint160 amount,,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + + assertEq(amount, defaultAmountOrId); + + snapStart("transferFrom"); + permit2TransferFrom(from, address0, defaultAmountOrId, token0()); + + snapEnd(); + assertEq(balanceOf(token0(), from), startBalanceFrom - defaultAmountOrId); + assertEq(balanceOf(token0(), address0), startBalanceTo + defaultAmountOrId); + } + + // function testBatchTransferFromWithGasSnapshot() public { + // PermitAbstraction.IPermitSingle memory permit = + // defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // uint256 startBalanceFrom = balanceOf(token0(), from); + // uint256 startBalanceTo = balanceOf(token0(), address0); + + // permit2Permit(from, permit, sig); + + // (uint160 amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId); + + // // permit token0 for 1 ** 18 + // address[] memory owners = AddressBuilder.fill(3, from); + // IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = + // StructBuilder.fillAllowanceTransferDetail(3, token0(), 1 ** 18, address0, owners); + // snapStart("batchTransferFrom"); + // permit2.transferFrom(transferDetails); + // snapEnd(); + // assertEq(balanceOf(token0(), from), startBalanceFrom - 3 * 1 ** 18); + // assertEq(balanceOf(token0(), address0), startBalanceTo + 3 * 1 ** 18); + // (amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId - 3 * 1 ** 18); + // } + + // dirty sstore on nonce, dirty sstore on transfer + function testSetAllowanceTransferDirtyNonceDirtyTransfer() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, dirtyNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); + + uint256 startBalanceFrom = balanceOf(token0(), fromDirty); + uint256 startBalanceTo = balanceOf(token0(), address3); + // ensure its a dirty store for the recipient address + assertEq(startBalanceTo, defaultAmountOrId); + + snapStart("permitDirtyNonce"); + permit2Permit(fromDirty, permit, sig); + snapEnd(); + + (uint160 amount,,) = permit2Allowance(fromDirty, token1(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + + permit2TransferFrom(fromDirty, address3, defaultAmountOrId, token0()); + + assertEq(balanceOf(token0(), fromDirty), startBalanceFrom - defaultAmountOrId); + assertEq(balanceOf(token0(), address3), startBalanceTo + defaultAmountOrId); + } + + function testSetAllowanceInvalidSignature() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + snapStart("permitInvalidSigner"); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + permit.spender = address0; + permit2Permit(from, permit, sig); + snapEnd(); + } + + function testSetAllowanceDeadlinePassed() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + uint256 sigDeadline = block.timestamp + 100; + + vm.warp(block.timestamp + 101); + snapStart("permitSignatureExpired"); + vm.expectRevert(abi.encodeWithSelector(SignatureExpired.selector, sigDeadline)); + permit2Permit(from, permit, sig); + snapEnd(); + } + + function testMaxAllowance() public { + uint160 maxAllowance = type(uint160).max; + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), maxAllowance, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + uint256 startBalanceFrom = balanceOf(token0(), from); + uint256 startBalanceTo = balanceOf(token0(), address0); + + snapStart("permitSetMaxAllowanceCleanWrite"); + permit2Permit(from, permit, sig); + snapEnd(); + + (uint160 startAllowedAmount0,,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(startAllowedAmount0, type(uint160).max); + + permit2TransferFrom(from, address0, defaultAmountOrId, token0()); + + (uint160 endAllowedAmount0,,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(endAllowedAmount0, type(uint160).max); + + assertEq(balanceOf(token0(), from), startBalanceFrom - defaultAmountOrId); + assertEq(balanceOf(token0(), address0), startBalanceTo + defaultAmountOrId); + } + + function testMaxAllowanceDirtyWrite() public { + uint160 maxAllowance = type(uint160).max; + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), maxAllowance, defaultExpiration, dirtyNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKeyDirty, DOMAIN_SEPARATOR); + + uint256 startBalanceFrom = balanceOf(token0(), fromDirty); + uint256 startBalanceTo = balanceOf(token0(), address0); + + snapStart("permitSetMaxAllowanceDirtyWrite"); + permit2Permit(fromDirty, permit, sig); + snapEnd(); + + (uint160 startAllowedAmount0,,) = permit2Allowance(fromDirty, token1(), uint256(uint160(address(this)))); + assertEq(startAllowedAmount0, type(uint160).max); + + permit2TransferFrom(fromDirty, address0, defaultAmountOrId, token0()); + + (uint160 endAllowedAmount0,,) = permit2Allowance(fromDirty, token1(), uint256(uint160(address(this)))); + assertEq(endAllowedAmount0, type(uint160).max); + + assertEq(balanceOf(token0(), fromDirty), startBalanceFrom - defaultAmountOrId); + assertEq(balanceOf(token0(), address0), startBalanceTo + defaultAmountOrId); + } + + function testPartialAllowance() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + uint256 startBalanceFrom = balanceOf(token0(), from); + uint256 startBalanceTo = balanceOf(token0(), address0); + + permit2Permit(from, permit, sig); + + (uint160 startAllowedAmount0,,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(startAllowedAmount0, defaultAmountOrId); + + uint160 transferAmount = 5 ** 18; + permit2TransferFrom(from, address0, transferAmount, token0()); + (uint160 endAllowedAmount0,,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + // ensure the allowance was deducted + assertEq(endAllowedAmount0, defaultAmountOrId - transferAmount); + + assertEq(balanceOf(token0(), from), startBalanceFrom - transferAmount); + assertEq(balanceOf(token0(), address0), startBalanceTo + transferAmount); + } + + function testReuseOrderedNonceInvalid() public { + PermitAbstraction.IPermitSingle memory permit = + defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + permit2Permit(from, permit, sig); + (,, uint48 nonce) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(nonce, 1); + + (uint160 amount, uint48 expiration,) = permit2Allowance(from, token0(), uint256(uint160(address(this)))); + assertEq(amount, defaultAmountOrId); + assertEq(expiration, defaultExpiration); + + vm.expectRevert(InvalidNonce.selector); + permit2Permit(from, permit, sig); + } + + // function testInvalidateNonces() public { + // PermitAbstraction.IPermitSingle memory permit = + // defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // // Invalidates the 0th nonce by setting the new nonce to 1. + // vm.prank(from); + // vm.expectEmit(true, true, true, true); + // emit NonceInvalidation(from, token0(), address(this), 1, defaultNonce); + // permit2.invalidateNonces(token0(), address(this), 1); + // (,, uint48 nonce) = permit2Allowance(from, token0(), address(this)); + // assertEq(nonce, 1); + + // vm.expectRevert(InvalidNonce.selector); + // permit2Permit(from, permit, sig); + // } + + // function testInvalidateMultipleNonces() public { + // PermitAbstraction.IPermitSingle memory permit = + // defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // // Valid permit, uses nonce 0. + // permit2Permit(from, permit, sig); + // (,, uint48 nonce1) = permit2Allowance(from, token0(), address(this)); + // assertEq(nonce1, 1); + + // permit = defaultPermitAllowance(token1(), defaultAmountOrId, defaultExpiration, nonce1); + // sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // // Invalidates the 9 nonces by setting the new nonce to 33. + // vm.prank(from); + // vm.expectEmit(true, true, true, true); + + // emit NonceInvalidation(from, token0(), address(this), 33, nonce1); + // permit2.invalidateNonces(token0(), address(this), 33); + // (,, uint48 nonce2) = permit2Allowance(from, token0(), address(this)); + // assertEq(nonce2, 33); + + // vm.expectRevert(InvalidNonce.selector); + // permit2Permit(from, permit, sig); + // } + + // function testInvalidateNoncesInvalid() public { + // // fromDirty nonce is 1 + // vm.prank(fromDirty); + // vm.expectRevert(InvalidNonce.selector); + // // setting nonce to 0 should revert + // permit2.invalidateNonces(token0(), address(this), 0); + // } + + // function testExcessiveInvalidation() public { + // PermitAbstraction.IPermitSingle memory permit = + // defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // uint32 numInvalidate = type(uint16).max; + // vm.startPrank(from); + // vm.expectRevert(IAllowanceTransfer.ExcessiveInvalidation.selector); + // permit2.invalidateNonces(token0(), address(this), numInvalidate + 1); + // vm.stopPrank(); + + // permit2Permit(from, permit, sig); + // (,, uint48 nonce) = permit2Allowance(from, token0(), address(this)); + // assertEq(nonce, 1); + // } + + // function testBatchTransferFrom() public { + // PermitAbstraction.IPermitSingle memory permit = + // defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // uint256 startBalanceFrom = balanceOf(token0(), from); + // uint256 startBalanceTo = balanceOf(token0(), address0); + + // permit2Permit(from, permit, sig); + + // (uint160 amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId); + + // // permit token0 for 1 ** 18 + // address[] memory owners = AddressBuilder.fill(3, from); + // IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = + // StructBuilder.fillAllowanceTransferDetail(3, token0(), 1 ** 18, address0, owners); + // snapStart("batchTransferFrom"); + // permit2.transferFrom(transferDetails); + // snapEnd(); + // assertEq(balanceOf(token0(), from), startBalanceFrom - 3 * 1 ** 18); + // assertEq(balanceOf(token0(), address0), startBalanceTo + 3 * 1 ** 18); + // (amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId - 3 * 1 ** 18); + // } + + // function testBatchTransferFromMultiToken() public { + // address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + // PermitAbstraction.IPermitBatch memory permitBatch = + // defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitBatchSignature(permitBatch, fromPrivateKey, DOMAIN_SEPARATOR); + + // uint256 startBalanceFrom0 = balanceOf(token0(), from); + // uint256 startBalanceFrom1 = balanceOf(token1(), from); + // uint256 startBalanceTo0 = balanceOf(token0(), address0); + // uint256 startBalanceTo1 = balanceOf(token1(), address0); + + // permit2Permit(from, permitBatch, sig); + + // (uint160 amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId); + // (amount,,) = permit2Allowance(from, token1(), address(this)); + // assertEq(amount, defaultAmountOrId); + + // // permit token0 for 1 ** 18 + // address[] memory owners = AddressBuilder.fill(2, from); + // IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = + // StructBuilder.fillAllowanceTransferDetail(2, tokens, 1 ** 18, address0, owners); + // snapStart("batchTransferFromMultiToken"); + // permit2.transferFrom(transferDetails); + // snapEnd(); + // assertEq(balanceOf(token0(), from), startBalanceFrom0 - 1 ** 18); + // assertEq(balanceOf(token1(), from), startBalanceFrom1 - 1 ** 18); + // assertEq(balanceOf(token0(), address0), startBalanceTo0 + 1 ** 18); + // assertEq(balanceOf(token1(), address0), startBalanceTo1 + 1 ** 18); + // (amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId - 1 ** 18); + // (amount,,) = permit2Allowance(from, token1(), address(this)); + // assertEq(amount, defaultAmountOrId - 1 ** 18); + // } + + // function testBatchTransferFromDifferentOwners() public { + // PermitAbstraction.IPermitSingle memory permit = + // defaultPermitAllowance(token0(), defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // PermitAbstraction.IPermitBatch memory permitDirty = + // defaultPermitBatchAllowance(token0(), defaultAmountOrId, defaultExpiration, dirtyNonce); + // bytes memory sigDirty = getPermitSignature(permitDirty, fromPrivateKeyDirty, DOMAIN_SEPARATOR); + + // uint256 startBalanceFrom = balanceOf(token0(), from); + // uint256 startBalanceTo = balanceOf(token0(), address(this)); + // uint256 startBalanceFromDirty = balanceOf(token0(), fromDirty); + + // // from and fromDirty approve address(this) as spender + // permit2Permit(from, permit, sig); + // permit2Permit(fromDirty, permitDirty, sigDirty); + + // (uint160 amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId); + // (uint160 amount1,,) = permit2Allowance(fromDirty, token1(), address(this)); + // assertEq(amount1, defaultAmountOrId); + + // address[] memory owners = AddressBuilder.fill(1, from).push(fromDirty); + // IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = + // StructBuilder.fillAllowanceTransferDetail(2, token0(), 1 ** 18, address(this), owners); + // snapStart("transferFrom with different owners"); + // permit2.transferFrom(transferDetails); + // snapEnd(); + + // assertEq(balanceOf(token0(), from), startBalanceFrom - 1 ** 18); + // assertEq(balanceOf(token0(), fromDirty), startBalanceFromDirty - 1 ** 18); + // assertEq(balanceOf(token0(), address(this)), startBalanceTo + 2 * 1 ** 18); + // (amount,,) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId - 1 ** 18); + // (amount,,) = permit2Allowance(fromDirty, token1(), address(this)); + // assertEq(amount, defaultAmountOrId - 1 ** 18); + // } + + // function testLockdown() public { + // address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + // PermitAbstraction.IPermitBatch memory permit = + // defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // permit2Permit(from, permit, sig); + + // (uint160 amount, uint48 expiration, uint48 nonce) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId); + // assertEq(expiration, defaultExpiration); + // assertEq(nonce, 1); + // (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2Allowance(from, token1(), address(this)); + // assertEq(amount1, defaultAmountOrId); + // assertEq(expiration1, defaultExpiration); + // assertEq(nonce1, 1); + + // IAllowanceTransfer.TokenSpenderPair[] memory approvals = new IAllowanceTransfer.TokenSpenderPair[](2); + // approvals[0] = IAllowanceTransfer.TokenSpenderPair(token0(), address(this)); + // approvals[1] = IAllowanceTransfer.TokenSpenderPair(token1(), address(this)); + + // vm.prank(from); + // snapStart("lockdown"); + // permit2.lockdown(approvals); + // snapEnd(); + + // (amount, expiration, nonce) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, 0); + // assertEq(expiration, defaultExpiration); + // assertEq(nonce, 1); + // (amount1, expiration1, nonce1) = permit2Allowance(from, token1(), address(this)); + // assertEq(amount1, 0); + // assertEq(expiration1, defaultExpiration); + // assertEq(nonce1, 1); + // } + + // function testLockdownEvent() public { + // address[] memory tokens = AddressBuilder.fill(1, token0()).push(token1()); + // PermitAbstraction.IPermitBatch memory permit = + // defaultPermitBatchAllowance(tokens, defaultAmountOrId, defaultExpiration, defaultNonce); + // bytes memory sig = getPermitBatchSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); + + // permit2Permit(from, permit, sig); + + // (uint160 amount, uint48 expiration, uint48 nonce) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, defaultAmountOrId); + // assertEq(expiration, defaultExpiration); + // assertEq(nonce, 1); + // (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2Allowance(from, token1(), address(this)); + // assertEq(amount1, defaultAmountOrId); + // assertEq(expiration1, defaultExpiration); + // assertEq(nonce1, 1); + + // IAllowanceTransfer.TokenSpenderPair[] memory approvals = new IAllowanceTransfer.TokenSpenderPair[](2); + // approvals[0] = IAllowanceTransfer.TokenSpenderPair(token0(), address(this)); + // approvals[1] = IAllowanceTransfer.TokenSpenderPair(token1(), address(this)); + + // //TODO :fix expecting multiple events, can only check for 1 + // vm.prank(from); + // vm.expectEmit(true, false, false, false); + // emit Lockdown(from, token0(), address(this)); + // permit2.lockdown(approvals); + + // (amount, expiration, nonce) = permit2Allowance(from, token0(), address(this)); + // assertEq(amount, 0); + // assertEq(expiration, defaultExpiration); + // assertEq(nonce, 1); + // (amount1, expiration1, nonce1) = permit2Allowance(from, token1(), address(this)); + // assertEq(amount1, 0); + // assertEq(expiration1, defaultExpiration); + // assertEq(nonce1, 1); + // } +} diff --git a/test/CompactSignature.t.sol b/test/CompactSignature.t.sol index 0f6cbc91..8f04bd30 100644 --- a/test/CompactSignature.t.sol +++ b/test/CompactSignature.t.sol @@ -12,7 +12,7 @@ contract CompactSignature is PermitSignature { uint8 v = 27; bytes32 vs; - (r, vs) = _getCompactSignature(v, r, s); + (r, vs) = getCompactSignature(v, r, s); assertEq(r, 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90); assertEq(vs, 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064); @@ -24,7 +24,7 @@ contract CompactSignature is PermitSignature { uint8 v = 28; bytes32 vs; - (r, vs) = _getCompactSignature(v, r, s); + (r, vs) = getCompactSignature(v, r, s); assertEq(r, 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76); assertEq(vs, 0x939c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793); diff --git a/test/Permit2Lib.t.sol b/test/Permit2Lib.t.sol index a74143ce..48cb6173 100644 --- a/test/Permit2Lib.t.sol +++ b/test/Permit2Lib.t.sol @@ -16,7 +16,7 @@ import {MockPermit2Lib} from "./mocks/MockPermit2Lib.sol"; import {SafeCast160} from "../src/ERC20/libraries/SafeCast160.sol"; import {MockPermitWithSmallDS, MockPermitWithLargerDS} from "./mocks/MockPermitWithDS.sol"; import {MockNonPermitNonERC20WithDS} from "./mocks/MockNonPermitNonERC20WithDS.sol"; -import {SignatureVerification} from "../src/ERC20/libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../src/shared/SignatureVerification.sol"; import {MockFallbackERC20} from "./mocks/MockFallbackERC20.sol"; contract Permit2LibTest is Test, PermitSignature, GasSnapshot { diff --git a/test/SignatureTransfer.t.sol b/test/SignatureTransfer.t.sol index 8952a1c2..2ee81ef9 100644 --- a/test/SignatureTransfer.t.sol +++ b/test/SignatureTransfer.t.sol @@ -3,9 +3,8 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {SafeERC20, IERC20, IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import {SignatureVerification} from "../src/ERC20/libraries/SignatureVerification.sol"; -import {TokenProvider} from "./utils/TokenProvider.sol"; -import {SignatureVerification} from "../src/ERC20/libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../src/shared/SignatureVerification.sol"; +import {TokenProviderERC20} from "./utils/TokenProviderERC20.sol"; import {PermitSignature} from "./utils/PermitSignature.sol"; import {AddressBuilder} from "./utils/AddressBuilder.sol"; import {AmountBuilder} from "./utils/AmountBuilder.sol"; @@ -14,9 +13,9 @@ import {Permit2} from "../src/ERC20/Permit2.sol"; import {SignatureTransfer} from "../src/ERC20/SignatureTransfer.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {ISignatureTransfer} from "../src/ERC20/interfaces/ISignatureTransfer.sol"; -import {InvalidNonce, SignatureExpired} from "../src/ERC20/PermitErrors.sol"; +import {InvalidNonce, SignatureExpired} from "../src/shared/PermitErrors.sol"; -contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnapshot { +contract SignatureTransferTest is Test, PermitSignature, TokenProviderERC20, GasSnapshot { using AddressBuilder for address[]; using AmountBuilder for uint256[]; @@ -68,10 +67,10 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps fromPrivateKey = 0x12341234; from = vm.addr(fromPrivateKey); - initializeERC20Tokens(); + initializeTokens(); - setERC20TestTokens(from); - setERC20TestTokenApprovals(vm, from, address(permit2)); + setTokens(from); + setTokenApprovals(vm, from, address(permit2)); } function testCorrectWitnessTypehashes() public { @@ -95,28 +94,28 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps function testPermitTransferFrom() public { uint256 nonce = 0; - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address2); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address2); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, defaultAmount); permit2.permitTransferFrom(permit, transferDetails, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo + defaultAmount); } function testPermitTransferFromCompactSig() public { uint256 nonce = 0; - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); bytes memory sig = getCompactPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); assertEq(sig.length, 64); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address2); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address2); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, defaultAmount); @@ -124,13 +123,13 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps permit2.permitTransferFrom(permit, transferDetails, from, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo + defaultAmount); } function testPermitTransferFromIncorrectSigLength() public { uint256 nonce = 0; - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); bytes memory sigExtra = bytes.concat(sig, bytes1(uint8(0))); assertEq(sigExtra.length, 66); @@ -144,23 +143,23 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps function testPermitTransferFromToSpender() public { uint256 nonce = 0; // signed spender is address(this) - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address0); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address0); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address0, defaultAmount); permit2.permitTransferFrom(permit, transferDetails, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address0), startBalanceTo + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom - defaultAmount); + assertEq(_token0.balanceOf(address0), startBalanceTo + defaultAmount); } function testPermitTransferFromInvalidNonce() public { uint256 nonce = 0; - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, defaultAmount); @@ -171,41 +170,41 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps } function testPermitTransferFromRandomNonceAndAmount(uint256 nonce, uint128 amount) public { - token0.mint(address(from), amount); - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + _token0.mint(address(from), amount); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); permit.permitted.amount = amount; bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address2); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address2); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, amount); permit2.permitTransferFrom(permit, transferDetails, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom - amount); - assertEq(token0.balanceOf(address2), startBalanceTo + amount); + assertEq(_token0.balanceOf(from), startBalanceFrom - amount); + assertEq(_token0.balanceOf(address2), startBalanceTo + amount); } function testPermitTransferSpendLessThanFull(uint256 nonce, uint128 amount) public { - token0.mint(address(from), amount); - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + _token0.mint(address(from), amount); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); permit.permitted.amount = amount; bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address2); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address2); uint256 amountToSpend = amount / 2; ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, amountToSpend); permit2.permitTransferFrom(permit, transferDetails, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom - amountToSpend); - assertEq(token0.balanceOf(address2), startBalanceTo + amountToSpend); + assertEq(_token0.balanceOf(from), startBalanceFrom - amountToSpend); + assertEq(_token0.balanceOf(address2), startBalanceTo + amountToSpend); } function testPermitBatchTransferFrom() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); @@ -213,104 +212,104 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(defaultAmount, to); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address2); - uint256 startBalanceTo1 = token1.balanceOf(address0); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceFrom1 = _token1.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address2); + uint256 startBalanceTo1 = _token1.balanceOf(address0); permit2.permitTransferFrom(permit, toAmountPairs, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom0 - defaultAmount); - assertEq(token1.balanceOf(from), startBalanceFrom1 - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo0 + defaultAmount); - assertEq(token1.balanceOf(address0), startBalanceTo1 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - defaultAmount); + assertEq(_token1.balanceOf(from), startBalanceFrom1 - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo0 + defaultAmount); + assertEq(_token1.balanceOf(address0), startBalanceTo1 + defaultAmount); } function testPermitBatchMultiPermitSingleTransfer() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - // must fill address to even though token0 wont get sent. + // must fill address to even though _token0 wont get sent. // transfer details must be lenght of permit address[] memory to = AddressBuilder.fill(1, address(address0)).push(address(address0)); ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(defaultAmount, to); - // spender doesnt need token0 even though user permitted it + // spender doesnt need _token0 even though user permitted it toAmountPairs[0].requestedAmount = 0; - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address2); - uint256 startBalanceTo1 = token1.balanceOf(address0); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceFrom1 = _token1.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address2); + uint256 startBalanceTo1 = _token1.balanceOf(address0); permit2.permitTransferFrom(permit, toAmountPairs, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom0); - assertEq(token1.balanceOf(from), startBalanceFrom1 - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo0); - assertEq(token1.balanceOf(address0), startBalanceTo1 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0); + assertEq(_token1.balanceOf(from), startBalanceFrom1 - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo0); + assertEq(_token1.balanceOf(address0), startBalanceTo1 + defaultAmount); } function testPermitBatchTransferFromSingleRecipient() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(2, defaultAmount, address(address2)); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address2); - uint256 startBalanceTo1 = token1.balanceOf(address2); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceFrom1 = _token1.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address2); + uint256 startBalanceTo1 = _token1.balanceOf(address2); snapStart("single recipient 2 tokens"); permit2.permitTransferFrom(permit, toAmountPairs, from, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom0 - defaultAmount); - assertEq(token1.balanceOf(from), startBalanceFrom1 - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo0 + defaultAmount); - assertEq(token1.balanceOf(address2), startBalanceTo1 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - defaultAmount); + assertEq(_token1.balanceOf(from), startBalanceFrom1 - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo0 + defaultAmount); + assertEq(_token1.balanceOf(address2), startBalanceTo1 + defaultAmount); } function testPermitBatchTransferMultiAddr() public { uint256 nonce = 0; // signed spender is address(this) - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address(this)); - uint256 startBalanceTo1 = token1.balanceOf(address2); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceFrom1 = _token1.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address(this)); + uint256 startBalanceTo1 = _token1.balanceOf(address2); address[] memory to = AddressBuilder.fill(1, address(this)).push(address2); ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(defaultAmount, to); permit2.permitTransferFrom(permit, toAmountPairs, from, sig); - assertEq(token0.balanceOf(from), startBalanceFrom0 - defaultAmount); - assertEq(token0.balanceOf(address(this)), startBalanceTo0 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - defaultAmount); + assertEq(_token0.balanceOf(address(this)), startBalanceTo0 + defaultAmount); - assertEq(token1.balanceOf(from), startBalanceFrom1 - defaultAmount); - assertEq(token1.balanceOf(address2), startBalanceTo1 + defaultAmount); + assertEq(_token1.balanceOf(from), startBalanceFrom1 - defaultAmount); + assertEq(_token1.balanceOf(address2), startBalanceTo1 + defaultAmount); } function testPermitBatchTransferSingleRecipientManyTokens() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(10, address(token0)); + address[] memory tokens = AddressBuilder.fill(10, address(_token0)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address(this)); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address(this)); ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(10, defaultAmount, address(this)); @@ -319,14 +318,14 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps permit2.permitTransferFrom(permit, toAmountPairs, from, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom0 - 10 * defaultAmount); - assertEq(token0.balanceOf(address(this)), startBalanceTo0 + 10 * defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - 10 * defaultAmount); + assertEq(_token0.balanceOf(address(this)), startBalanceTo0 + 10 * defaultAmount); } function testPermitBatchTransferInvalidAmountsLengthMismatch() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(2, address(token0)); + address[] memory tokens = AddressBuilder.fill(2, address(_token0)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); @@ -339,11 +338,11 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps function testGasSinglePermitTransferFrom() public { uint256 nonce = 0; - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), nonce); bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address2); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address2); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, defaultAmount); @@ -351,33 +350,33 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps permit2.permitTransferFrom(permit, transferDetails, from, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo + defaultAmount); } function testGasSinglePermitBatchTransferFrom() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(1, address(token0)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(1, defaultAmount, address(address2)); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address2); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address2); snapStart("permitBatchTransferFromSingleToken"); permit2.permitTransferFrom(permit, toAmountPairs, from, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom0 - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo0 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo0 + defaultAmount); } function testGasMultiplePermitBatchTransferFrom() public { uint256 nonce = 0; - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); @@ -385,28 +384,28 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(defaultAmount, to); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address(address2)); - uint256 startBalanceTo1 = token1.balanceOf(address(address2)); - uint256 startBalanceToThis1 = token1.balanceOf(address(this)); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceFrom1 = _token1.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address(address2)); + uint256 startBalanceTo1 = _token1.balanceOf(address(address2)); + uint256 startBalanceToThis1 = _token1.balanceOf(address(this)); snapStart("permitBatchTransferFromMultipleTokens"); permit2.permitTransferFrom(permit, toAmountPairs, from, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom0 - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo0 + defaultAmount); - assertEq(token1.balanceOf(from), startBalanceFrom1 - 2 * defaultAmount); - assertEq(token1.balanceOf(address2), startBalanceTo1 + defaultAmount); - assertEq(token1.balanceOf(address(this)), startBalanceToThis1 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo0 + defaultAmount); + assertEq(_token1.balanceOf(from), startBalanceFrom1 - 2 * defaultAmount); + assertEq(_token1.balanceOf(address2), startBalanceTo1 + defaultAmount); + assertEq(_token1.balanceOf(address(this)), startBalanceToThis1 + defaultAmount); } function testPermitBatchTransferFromTypedWitness() public { uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchWitnessSignature( @@ -417,26 +416,26 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps ISignatureTransfer.SignatureTransferDetails[] memory toAmountPairs = StructBuilder.fillSigTransferDetails(defaultAmount, to); - uint256 startBalanceFrom0 = token0.balanceOf(from); - uint256 startBalanceFrom1 = token1.balanceOf(from); - uint256 startBalanceTo0 = token0.balanceOf(address2); - uint256 startBalanceTo1 = token1.balanceOf(address0); + uint256 startBalanceFrom0 = _token0.balanceOf(from); + uint256 startBalanceFrom1 = _token1.balanceOf(from); + uint256 startBalanceTo0 = _token0.balanceOf(address2); + uint256 startBalanceTo1 = _token1.balanceOf(address0); snapStart("permitTransferFromBatchTypedWitness"); permit2.permitWitnessTransferFrom(permit, toAmountPairs, from, witness, WITNESS_TYPE_STRING, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom0 - defaultAmount); - assertEq(token1.balanceOf(from), startBalanceFrom1 - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo0 + defaultAmount); - assertEq(token1.balanceOf(address0), startBalanceTo1 + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom0 - defaultAmount); + assertEq(_token1.balanceOf(from), startBalanceFrom1 - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo0 + defaultAmount); + assertEq(_token1.balanceOf(address0), startBalanceTo1 + defaultAmount); } function testPermitBatchTransferFromTypedWitnessInvalidType() public { uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchWitnessSignature( permit, fromPrivateKey, FULL_EXAMPLE_WITNESS_BATCH_TYPEHASH, witness, DOMAIN_SEPARATOR @@ -454,7 +453,7 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchWitnessSignature(permit, fromPrivateKey, "fake typehash", witness, DOMAIN_SEPARATOR); @@ -471,7 +470,7 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - address[] memory tokens = AddressBuilder.fill(1, address(token0)).push(address(token1)); + address[] memory tokens = AddressBuilder.fill(1, address(_token0)).push(address(_token1)); ISignatureTransfer.PermitBatchTransferFrom memory permit = defaultERC20PermitMultiple(tokens, nonce); bytes memory sig = getPermitBatchWitnessSignature( permit, fromPrivateKey, FULL_EXAMPLE_WITNESS_BATCH_TYPEHASH, witness, DOMAIN_SEPARATOR @@ -488,7 +487,7 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps } function testInvalidateUnorderedNonces() public { - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), 0); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(_token0), 0); bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); uint256 bitmap = permit2.nonceBitmap(from, 0); @@ -511,13 +510,13 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(_token0), nonce); bytes memory sig = getPermitWitnessTransferSignature( permit, fromPrivateKey, FULL_EXAMPLE_WITNESS_TYPEHASH, witness, DOMAIN_SEPARATOR ); - uint256 startBalanceFrom = token0.balanceOf(from); - uint256 startBalanceTo = token0.balanceOf(address2); + uint256 startBalanceFrom = _token0.balanceOf(from); + uint256 startBalanceTo = _token0.balanceOf(address2); ISignatureTransfer.SignatureTransferDetails memory transferDetails = getTransferDetails(address2, defaultAmount); @@ -525,15 +524,15 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps permit2.permitWitnessTransferFrom(permit, transferDetails, from, witness, WITNESS_TYPE_STRING, sig); snapEnd(); - assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); - assertEq(token0.balanceOf(address2), startBalanceTo + defaultAmount); + assertEq(_token0.balanceOf(from), startBalanceFrom - defaultAmount); + assertEq(_token0.balanceOf(address2), startBalanceTo + defaultAmount); } function testPermitTransferFromTypedWitnessInvalidType() public { uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(_token0), nonce); bytes memory sig = getPermitWitnessTransferSignature( permit, fromPrivateKey, FULL_EXAMPLE_WITNESS_TYPEHASH, witness, DOMAIN_SEPARATOR ); @@ -548,7 +547,7 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps uint256 nonce = 0; MockWitness memory witnessData = MockWitness(10000000, address(5), true); bytes32 witness = keccak256(abi.encode(witnessData)); - ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(token0), nonce); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(_token0), nonce); bytes memory sig = getPermitWitnessTransferSignature(permit, fromPrivateKey, "fake typehash", witness, DOMAIN_SEPARATOR); diff --git a/test/TypehashGeneration.t.sol b/test/TypehashGeneration.t.sol index 724f7345..a02b049b 100644 --- a/test/TypehashGeneration.t.sol +++ b/test/TypehashGeneration.t.sol @@ -9,7 +9,7 @@ import {ISignatureTransfer} from "../src/ERC20/interfaces/ISignatureTransfer.sol import {MockSignatureVerification} from "./mocks/MockSignatureVerification.sol"; import {MockHash} from "./mocks/MockHash.sol"; import {AddressBuilder} from "./utils/AddressBuilder.sol"; -import {SignatureVerification} from "../src/ERC20/libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../src/shared/SignatureVerification.sol"; contract TypehashGeneration is Test, PermitSignature { using PermitHash for *; diff --git a/test/mocks/IMockPermit2.sol b/test/mocks/IMockPermit2.sol new file mode 100644 index 00000000..87667827 --- /dev/null +++ b/test/mocks/IMockPermit2.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Permit2} from "../../src/ERC20/Permit2.sol"; +import {IAllowanceTransfer} from "../../src/ERC20/interfaces/IAllowanceTransfer.sol"; +import {ISignatureTransfer} from "../../src/ERC20/interfaces/ISignatureTransfer.sol"; +import {Allowance} from "../../src/ERC20/libraries/Allowance.sol"; + +abstract contract IMockPermit2 { + // note that some parameters are unused in the erc20 and erc721 case but with this interface they can share some base tests + + function doStore(address from, address token, address spender, uint256 tokenId, uint256 word) public virtual {} + + function getStore(address from, address token, address spender, uint256 tokenId) + public + view + virtual + returns (uint256 word) + {} + + function mockUpdateSome( + address from, + address token, + address spender, + uint160 updateData, + uint256 tokenId, + uint48 expiration + ) public virtual {} + + function mockUpdateAll( + address from, + address token, + address spender, + uint160 updateData, + uint256 tokenId, + uint48 expiration, + uint48 nonce + ) public virtual {} + + function useUnorderedNonce(address from, uint256 nonce) public virtual {} +} diff --git a/test/mocks/MockPermit2.sol b/test/mocks/MockPermit2.sol index b1e26f34..d1ecadb0 100644 --- a/test/mocks/MockPermit2.sol +++ b/test/mocks/MockPermit2.sol @@ -4,46 +4,55 @@ pragma solidity ^0.8.17; import {Permit2} from "../../src/ERC20/Permit2.sol"; import {IAllowanceTransfer} from "../../src/ERC20/interfaces/IAllowanceTransfer.sol"; import {Allowance} from "../../src/ERC20/libraries/Allowance.sol"; +import {IMockPermit2} from "../mocks/IMockPermit2.sol"; -contract MockPermit2 is Permit2 { - function doStore(address from, address token, address spender, uint256 word) public { +contract MockPermit2 is IMockPermit2, Permit2 { + function doStore(address from, address token, address spender, uint256 tokenId, uint256 word) public override { IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; assembly { sstore(allowed.slot, word) } } - function getStore(address from, address token, address spender) public view returns (uint256 word) { + function getStore(address from, address token, address spender, uint256 tokenId) + public + view + override + returns (uint256 word) + { IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; assembly { word := sload(allowed.slot) } } - function mockUpdateAmountAndExpiration( + function mockUpdateSome( address from, address token, address spender, - uint160 amount, + uint160 updateData, + uint256 tokenId, uint48 expiration - ) public { + ) public override { + // uint256 tokenId unused IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; - Allowance.updateAmountAndExpiration(allowed, amount, expiration); + Allowance.updateAmountAndExpiration(allowed, updateData, expiration); } function mockUpdateAll( address from, address token, address spender, - uint160 amount, + uint160 updateData, + uint256 tokenId, uint48 expiration, uint48 nonce - ) public { + ) public override { IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; - Allowance.updateAll(allowed, amount, expiration, nonce); + Allowance.updateAll(allowed, updateData, expiration, nonce); } - function useUnorderedNonce(address from, uint256 nonce) public { + function useUnorderedNonce(address from, uint256 nonce) public override { _useUnorderedNonce(from, nonce); } } diff --git a/test/mocks/MockPermit2ERC721.sol b/test/mocks/MockPermit2ERC721.sol new file mode 100644 index 00000000..01fe93b0 --- /dev/null +++ b/test/mocks/MockPermit2ERC721.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Permit2ERC721} from "../../src/ERC721/Permit2ERC721.sol"; +import {IAllowanceTransferERC721} from "../../src/ERC721/interfaces/IAllowanceTransferERC721.sol"; +import {SignatureTransferERC721} from "../../src/ERC721/SignatureTransferERC721.sol"; +import {AllowanceERC721} from "../../src/ERC721/libraries/AllowanceERC721.sol"; +import {IMockPermit2} from "../mocks/IMockPermit2.sol"; + +contract MockPermit2ERC721 is IMockPermit2, Permit2ERC721 { + function doStore(address from, address token, address spender, uint256 tokenId, uint256 word) public override { + IAllowanceTransferERC721.PackedAllowance storage allowed = allowance[from][token][tokenId]; + assembly { + sstore(allowed.slot, word) + } + } + + function getStore(address from, address token, address spender, uint256 tokenId) + public + view + override + returns (uint256 word) + { + IAllowanceTransferERC721.PackedAllowance storage allowed = allowance[from][token][tokenId]; + assembly { + word := sload(allowed.slot) + } + } + + function mockUpdateSome( + address from, + address token, + address spender, + uint160 updateData, + uint256 tokenId, + uint48 expiration + ) public override { + // spender input unused in 721 case + IAllowanceTransferERC721.PackedAllowance storage allowed = allowance[from][token][tokenId]; + AllowanceERC721.updateSpenderAndExpiration(allowed, address(updateData), expiration); + } + + function mockUpdateAll( + address from, + address token, + address spender, + uint160 updateData, + uint256 tokenId, + uint48 expiration, + uint48 nonce + ) public override { + IAllowanceTransferERC721.PackedAllowance storage allowed = allowance[from][token][tokenId]; + AllowanceERC721.updateAll(allowed, address(updateData), expiration, nonce); + } + + function useUnorderedNonce(address from, uint256 nonce) public override { + _useUnorderedNonce(from, nonce); + } +} diff --git a/test/mocks/MockSignatureVerification.sol b/test/mocks/MockSignatureVerification.sol index 718eff5f..a27c75ad 100644 --- a/test/mocks/MockSignatureVerification.sol +++ b/test/mocks/MockSignatureVerification.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {SignatureVerification} from "../../src/ERC20/libraries/SignatureVerification.sol"; +import {SignatureVerification} from "../../src/shared/SignatureVerification.sol"; contract MockSignatureVerification { function verify(bytes calldata sig, bytes32 hashed, address signer) public view { diff --git a/test/shared/AllowanceUnitTestERC20.t.sol b/test/shared/AllowanceUnitTestERC20.t.sol new file mode 100644 index 00000000..b9a5d532 --- /dev/null +++ b/test/shared/AllowanceUnitTestERC20.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "../mocks/MockPermit2.sol"; +import {BaseAllowanceUnitTest} from "./BaseAllowanceUnitTest.sol"; +import {TokenProviderERC20} from "../utils/TokenProviderERC20.sol"; +import {Allowance} from "../../src/ERC20/libraries/Allowance.sol"; + +contract AllowanceUnitTestERC20 is BaseAllowanceUnitTest, TokenProviderERC20 { + function setUp() public override { + permit2 = new MockPermit2(); + initializeTokens(); + } + + function allowance(address from, address token, address spender, uint256 tokenId) + public + view + override + returns (uint160, uint48, uint48) + { + return MockPermit2(address(permit2)).allowance(from, token, spender); + } + + function token() public view override returns (address) { + return address(_token1); + } +} diff --git a/test/shared/AllowanceUnitTestERC721.t.sol b/test/shared/AllowanceUnitTestERC721.t.sol new file mode 100644 index 00000000..f1009e81 --- /dev/null +++ b/test/shared/AllowanceUnitTestERC721.t.sol @@ -0,0 +1,29 @@ +// // SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "../mocks/MockPermit2ERC721.sol"; +import {BaseAllowanceUnitTest} from "./BaseAllowanceUnitTest.sol"; +import {TokenProviderERC721} from "../utils/TokenProviderERC721.sol"; + +contract AllowanceUnitTestERC721 is BaseAllowanceUnitTest, TokenProviderERC721 { + function setUp() public override { + permit2 = new MockPermit2ERC721(); + initializeTokens(); + } + + function allowance(address from, address token, address spender, uint256 tokenId) + public + view + override + returns (uint160, uint48, uint48) + { + (address spender1, uint48 expiration1, uint48 nonce1) = + MockPermit2ERC721(address(permit2)).allowance(from, token, tokenId); + return (uint160(spender1), expiration1, nonce1); + } + + function token() public view override returns (address) { + return address(_token1); + } +} diff --git a/test/shared/BaseAllowanceUnitTest.sol b/test/shared/BaseAllowanceUnitTest.sol new file mode 100644 index 00000000..cd963e46 --- /dev/null +++ b/test/shared/BaseAllowanceUnitTest.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "../mocks/IMockPermit2.sol"; + +abstract contract BaseAllowanceUnitTest is Test { + IMockPermit2 permit2; + + address from = address(0xBEEE); + address spender = address(0xBBBB); + uint256 tokenId = 0; + + function setUp() public virtual {} + + function allowance(address from, address token, address spender, uint256 tokenId) + public + virtual + returns (uint160, uint48, uint48); + + function token() public virtual returns (address); + + function testUpdateSomeRandomly(uint160 amount, uint48 expiration) public { + address token = token(); + + (,, uint48 nonce) = allowance(from, token, spender, tokenId); + + // erc721s will update the spender field to the amount and will not use the spender input + permit2.mockUpdateSome(from, token, spender, amount, tokenId, expiration); + + uint48 timestampAfterUpdate = expiration == 0 ? uint48(block.timestamp) : expiration; + + (uint160 amount1, uint48 expiration1, uint48 nonce1) = allowance(from, token, spender, tokenId); + assertEq(amount, amount1); + assertEq(timestampAfterUpdate, expiration1); + /// nonce shouldnt change + assertEq(nonce, nonce1); + } + + function testUpdateAllRandomly(uint160 amount, uint48 expiration, uint48 nonce) public { + // there is overflow since we increment the nonce by 1 + // we assume we will never be able to reach 2**48 + vm.assume(nonce < type(uint48).max); + + address token = token(); + + permit2.mockUpdateAll(from, token, spender, amount, tokenId, expiration, nonce); + + uint48 nonceAfterUpdate = nonce + 1; + uint48 timestampAfterUpdate = expiration == 0 ? uint48(block.timestamp) : expiration; + + (uint160 amount1, uint48 expiration1, uint48 nonce1) = allowance(from, token, spender, tokenId); + + assertEq(amount, amount1); + assertEq(timestampAfterUpdate, expiration1); + assertEq(nonceAfterUpdate, nonce1); + } + + function testPackAndUnpack(uint160 amount, uint48 expiration, uint48 nonce) public { + // pack some numbers + address token = token(); + uint256 word = Allowance.pack(amount, expiration, nonce); + + // store the raw word + permit2.doStore(from, token, spender, tokenId, word); + uint256 word1 = permit2.getStore(from, token, spender, tokenId); + + // load it as a packed allowance + (uint160 amount1, uint48 expiration1, uint48 nonce1) = allowance(from, token, spender, tokenId); + assertEq(amount, amount1); + assertEq(expiration, expiration1); + assertEq(nonce, nonce1); + + // get the stored word + uint256 word2 = permit2.getStore(from, token, spender, tokenId); + assertEq(word, word2); + } +} diff --git a/test/NonceBitmap.t.sol b/test/shared/BaseNonceBitmapTest.t.sol similarity index 78% rename from test/NonceBitmap.t.sol rename to test/shared/BaseNonceBitmapTest.t.sol index 2f37f7f7..a15af389 100644 --- a/test/NonceBitmap.t.sol +++ b/test/shared/BaseNonceBitmapTest.t.sol @@ -3,15 +3,13 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {SafeERC20, IERC20, IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import {MockPermit2} from "./mocks/MockPermit2.sol"; -import {InvalidNonce} from "../src/ERC20/PermitErrors.sol"; +import {IMockPermit2} from "../mocks/MockPermit2.sol"; +import {InvalidNonce} from "../../src/shared/PermitErrors.sol"; -contract NonceBitmapTest is Test { - MockPermit2 permit2; +abstract contract BaseNonceBitmapTest is Test { + IMockPermit2 permit2; - function setUp() public { - permit2 = new MockPermit2(); - } + function setUp() public virtual {} function testLowNonces() public { permit2.useUnorderedNonce(address(this), 5); @@ -48,7 +46,7 @@ contract NonceBitmapTest is Test { } function testInvalidateFullWord() public { - permit2.invalidateUnorderedNonces(0, 2 ** 256 - 1); + invalidateUnorderedNonces(0, 2 ** 256 - 1); vm.expectRevert(InvalidNonce.selector); permit2.useUnorderedNonce(address(this), 0); @@ -62,7 +60,7 @@ contract NonceBitmapTest is Test { } function testInvalidateNonzeroWord() public { - permit2.invalidateUnorderedNonces(1, 2 ** 256 - 1); + invalidateUnorderedNonces(1, 2 ** 256 - 1); permit2.useUnorderedNonce(address(this), 0); permit2.useUnorderedNonce(address(this), 254); @@ -91,22 +89,25 @@ contract NonceBitmapTest is Test { } function testInvalidateNoncesRandomly(uint248 wordPos, uint256 mask) public { - permit2.invalidateUnorderedNonces(wordPos, mask); - assertEq(mask, permit2.nonceBitmap(address(this), wordPos)); + invalidateUnorderedNonces(wordPos, mask); + assertEq(mask, nonceBitmap(address(this), wordPos)); } function testInvalidateTwoNoncesRandomly(uint248 wordPos, uint256 startBitmap, uint256 mask) public { - permit2.invalidateUnorderedNonces(wordPos, startBitmap); - assertEq(startBitmap, permit2.nonceBitmap(address(this), wordPos)); + invalidateUnorderedNonces(wordPos, startBitmap); + assertEq(startBitmap, nonceBitmap(address(this), wordPos)); // invalidating with the mask changes the original bitmap uint256 finalBitmap = startBitmap | mask; - permit2.invalidateUnorderedNonces(wordPos, mask); - uint256 savedBitmap = permit2.nonceBitmap(address(this), wordPos); + invalidateUnorderedNonces(wordPos, mask); + uint256 savedBitmap = nonceBitmap(address(this), wordPos); assertEq(finalBitmap, savedBitmap); // invalidating with the same mask should do nothing - permit2.invalidateUnorderedNonces(wordPos, mask); - assertEq(savedBitmap, permit2.nonceBitmap(address(this), wordPos)); + invalidateUnorderedNonces(wordPos, mask); + assertEq(savedBitmap, nonceBitmap(address(this), wordPos)); } + + function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) public virtual; + function nonceBitmap(address addr, uint256 wordPos) public virtual returns (uint256); } diff --git a/test/shared/NonceBitmapTestERC20.t.sol b/test/shared/NonceBitmapTestERC20.t.sol new file mode 100644 index 00000000..37796130 --- /dev/null +++ b/test/shared/NonceBitmapTestERC20.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {BaseNonceBitmapTest} from "./BaseNonceBitmapTest.t.sol"; +import {MockPermit2} from "../mocks/MockPermit2.sol"; + +contract NonceBitmapTest_ERC20 is BaseNonceBitmapTest { + function setUp() public override { + permit2 = new MockPermit2(); + } + + function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) public override { + MockPermit2(address(permit2)).invalidateUnorderedNonces(wordPos, mask); + } + + function nonceBitmap(address addr, uint256 wordPos) public override returns (uint256) { + return MockPermit2(address(permit2)).nonceBitmap(addr, wordPos); + } +} diff --git a/test/shared/NonceBitmapTestERC721.t.sol b/test/shared/NonceBitmapTestERC721.t.sol new file mode 100644 index 00000000..effc7af6 --- /dev/null +++ b/test/shared/NonceBitmapTestERC721.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {BaseNonceBitmapTest} from "./BaseNonceBitmapTest.t.sol"; +import {MockPermit2ERC721} from "../mocks/MockPermit2ERC721.sol"; + +contract NonceBitmapTestERC721 is BaseNonceBitmapTest { + function setUp() public override { + permit2 = new MockPermit2ERC721(); + } + + function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) public override { + MockPermit2ERC721(address(permit2)).invalidateUnorderedNonces(wordPos, mask); + } + + function nonceBitmap(address addr, uint256 wordPos) public override returns (uint256) { + return MockPermit2ERC721(address(permit2)).nonceBitmap(addr, wordPos); + } +} diff --git a/test/shared/NonceBitmapTest_ERC20.t.sol b/test/shared/NonceBitmapTest_ERC20.t.sol new file mode 100644 index 00000000..37796130 --- /dev/null +++ b/test/shared/NonceBitmapTest_ERC20.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {BaseNonceBitmapTest} from "./BaseNonceBitmapTest.t.sol"; +import {MockPermit2} from "../mocks/MockPermit2.sol"; + +contract NonceBitmapTest_ERC20 is BaseNonceBitmapTest { + function setUp() public override { + permit2 = new MockPermit2(); + } + + function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) public override { + MockPermit2(address(permit2)).invalidateUnorderedNonces(wordPos, mask); + } + + function nonceBitmap(address addr, uint256 wordPos) public override returns (uint256) { + return MockPermit2(address(permit2)).nonceBitmap(addr, wordPos); + } +} diff --git a/test/utils/AmountBuilder.sol b/test/utils/AmountBuilder.sol index efcb832b..8b763b7e 100644 --- a/test/utils/AmountBuilder.sol +++ b/test/utils/AmountBuilder.sol @@ -23,8 +23,8 @@ library AmountBuilder { } } - function fillUInt64(uint256 length, uint64 exp) external pure returns (uint64[] memory exps) { - exps = new uint64[](length); + function fillUInt48(uint256 length, uint48 exp) external pure returns (uint48[] memory exps) { + exps = new uint48[](length); for (uint256 i = 0; i < length; ++i) { exps[i] = exp; } diff --git a/test/utils/PermitAbstraction.sol b/test/utils/PermitAbstraction.sol new file mode 100644 index 00000000..b437e361 --- /dev/null +++ b/test/utils/PermitAbstraction.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {IAllowanceTransfer} from "../../src/ERC20/interfaces/IAllowanceTransfer.sol"; +import {AmountBuilder} from "./AmountBuilder.sol"; + +// Structs are used for type abstraction in the tests and later converted to the respective permit2 structs +contract PermitAbstraction { + struct IPermitSingle { + address token; + uint160 amountOrId; + uint48 expiration; + uint48 nonce; + address spender; + uint256 sigDeadline; + } + + struct IPermitBatch { + address[] tokens; + uint160[] amountOrIds; + uint48[] expirations; + uint48[] nonces; + address spender; + uint256 sigDeadline; + } + + function defaultPermitAllowance(address token, uint160 amountOrId, uint48 expiration, uint48 nonce) + public + view + returns (IPermitSingle memory) + { + IAllowanceTransfer.PermitDetails memory details = + IAllowanceTransfer.PermitDetails({token: token, amount: amountOrId, expiration: expiration, nonce: nonce}); + return IPermitSingle({ + token: token, + amountOrId: amountOrId, + expiration: expiration, + nonce: nonce, + spender: address(this), + sigDeadline: block.timestamp + 100 + }); + } + + function defaultPermitBatchAllowance(address[] memory tokens, uint160 amountOrId, uint48 expiration, uint48 nonce) + public + view + returns (IPermitBatch memory) + { + uint160[] memory amountOrIds = AmountBuilder.fillUInt160(tokens.length, amountOrId); + uint48[] memory expirations = AmountBuilder.fillUInt48(tokens.length, expiration); + uint48[] memory nonces = AmountBuilder.fillUInt48(tokens.length, nonce); + return IPermitBatch({ + tokens: tokens, + amountOrIds: amountOrIds, + expirations: expirations, + nonces: nonces, + spender: address(this), + sigDeadline: block.timestamp + 100 + }); + } +} diff --git a/test/utils/PermitSignature.sol b/test/utils/PermitSignature.sol index f556b478..dc634d84 100644 --- a/test/utils/PermitSignature.sol +++ b/test/utils/PermitSignature.sol @@ -64,7 +64,7 @@ contract PermitSignature is Test { ) internal returns (bytes memory sig) { (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); bytes32 vs; - (r, vs) = _getCompactSignature(v, r, s); + (r, vs) = getCompactSignature(v, r, s); return bytes.concat(r, vs); } @@ -88,15 +88,11 @@ contract PermitSignature is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); bytes32 vs; - (r, vs) = _getCompactSignature(v, r, s); + (r, vs) = getCompactSignature(v, r, s); return bytes.concat(r, vs); } - function _getCompactSignature(uint8 vRaw, bytes32 rRaw, bytes32 sRaw) - internal - pure - returns (bytes32 r, bytes32 vs) - { + function getCompactSignature(uint8 vRaw, bytes32 rRaw, bytes32 sRaw) public pure returns (bytes32 r, bytes32 vs) { uint8 v = vRaw - 27; // 27 is 0, 28 is 1 vs = bytes32(uint256(v) << 255) | sRaw; return (rRaw, vs); @@ -235,13 +231,13 @@ contract PermitSignature is Test { return bytes.concat(r, s, bytes1(v)); } - function defaultERC20PermitAllowance(address token0, uint160 amount, uint48 expiration, uint48 nonce) + function defaultERC20PermitAllowance(address token, uint160 amount, uint48 expiration, uint48 nonce) internal view returns (IAllowanceTransfer.PermitSingle memory) { IAllowanceTransfer.PermitDetails memory details = - IAllowanceTransfer.PermitDetails({token: token0, amount: amount, expiration: expiration, nonce: nonce}); + IAllowanceTransfer.PermitDetails({token: token, amount: amount, expiration: expiration, nonce: nonce}); return IAllowanceTransfer.PermitSingle({ details: details, spender: address(this), @@ -272,25 +268,25 @@ contract PermitSignature is Test { }); } - function defaultERC20PermitTransfer(address token0, uint256 nonce) + function defaultERC20PermitTransfer(address token, uint256 nonce) internal view returns (ISignatureTransfer.PermitTransferFrom memory) { return ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({token: token0, amount: 10 ** 18}), + permitted: ISignatureTransfer.TokenPermissions({token: token, amount: 10 ** 18}), nonce: nonce, deadline: block.timestamp + 100 }); } - function defaultERC20PermitWitnessTransfer(address token0, uint256 nonce) + function defaultERC20PermitWitnessTransfer(address token, uint256 nonce) internal view returns (ISignatureTransfer.PermitTransferFrom memory) { return ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({token: token0, amount: 10 ** 18}), + permitted: ISignatureTransfer.TokenPermissions({token: token, amount: 10 ** 18}), nonce: nonce, deadline: block.timestamp + 100 }); diff --git a/test/utils/TokenProvider.sol b/test/utils/TokenProvider.sol deleted file mode 100644 index a240fa40..00000000 --- a/test/utils/TokenProvider.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {MockERC20} from "../mocks/MockERC20.sol"; -import {MockERC721} from "../mocks/MockERC721.sol"; -import {MockERC1155} from "../mocks/MockERC1155.sol"; - -contract TokenProvider { - uint256 public constant MINT_AMOUNT_ERC20 = 100 ** 18; - uint256 public constant MINT_AMOUNT_ERC1155 = 100; - - uint256 public constant TRANSFER_AMOUNT_ERC20 = 30 ** 18; - uint256 public constant TRANSFER_AMOUNT_ERC1155 = 10; - - MockERC20 token0; - MockERC20 token1; - MockERC721 nft1; - MockERC721 nft2; - MockERC1155 nft3; - MockERC1155 nft4; - - address faucet = address(0x98765); - - function initializeERC20Tokens() public { - token0 = new MockERC20("Test0", "TEST0", 18); - token1 = new MockERC20("Test1", "TEST1", 18); - } - - function setERC20TestTokens(address from) public { - token0.mint(from, MINT_AMOUNT_ERC20); - token1.mint(from, MINT_AMOUNT_ERC20); - } - - function setERC20TestTokenApprovals(Vm vm, address owner, address spender) public { - vm.startPrank(owner); - token0.approve(spender, type(uint256).max); - token1.approve(spender, type(uint256).max); - vm.stopPrank(); - } - - function initializeNFTTokens() public { - nft1 = new MockERC721("TestNFT1", "NFT1"); - nft2 = new MockERC721("TestNFT2", "NFT2"); - nft3 = new MockERC1155(); - nft4 = new MockERC1155(); - } - - // 721s - function setNFTTestTokens(address from) public { - // mint with id 1 - nft1.mint(from, 1); - // mint with id 2 - nft2.mint(from, 2); - // mint 10 with id 1 - nft3.mint(from, 1, MINT_AMOUNT_ERC1155); - // mint 10 with id 2 - nft4.mint(from, 2, MINT_AMOUNT_ERC1155); - } - - function setNFTTestTokenApprovals(Vm vm, address owner, address spender) public { - vm.startPrank(owner); - nft1.approve(spender, 1); - nft2.approve(spender, 2); - nft3.setApprovalForAll(spender, true); - nft4.setApprovalForAll(spender, true); - vm.stopPrank(); - } -} diff --git a/test/utils/TokenProviderERC20.sol b/test/utils/TokenProviderERC20.sol new file mode 100644 index 00000000..f3090a14 --- /dev/null +++ b/test/utils/TokenProviderERC20.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; + +contract TokenProviderERC20 { + uint256 public constant MINT_AMOUNT_ERC20 = 100 ** 18; + uint256 public constant MINT_AMOUNT_ERC1155 = 100; + + uint256 public constant TRANSFER_AMOUNT_ERC20 = 30 ** 18; + uint256 public constant TRANSFER_AMOUNT_ERC1155 = 10; + + MockERC20 _token0; + MockERC20 _token1; + + address faucet = address(0x98765); + + function initializeTokens() public { + _token0 = new MockERC20("Test0", "TEST0", 18); + _token1 = new MockERC20("Test1", "TEST1", 18); + } + + function setTokens(address from) public { + _token0.mint(from, MINT_AMOUNT_ERC20); + _token1.mint(from, MINT_AMOUNT_ERC20); + } + + function setTokenApprovals(Vm vm, address owner, address spender) public { + vm.startPrank(owner); + _token0.approve(spender, type(uint256).max); + _token1.approve(spender, type(uint256).max); + vm.stopPrank(); + } + + // function initializeERC721TestTokens() public { + // token_erc721_0 = new MockERC721("TestNFT1", "NFT1"); + // token_erc721_1 = new MockERC721("TestNFT2", "NFT2"); + // } + + // function setERC721TestTokens(address from) public { + // // mint with id 1 + // token_erc721_0.mint(from, 1); + // // mint with id 2 + // token_erc721_1.mint(from, 2); + // } + + // function setERC721TestTokenApprovals(Vm vm, address owner, address spender) public { + // vm.startPrank(owner); + // token_erc721_0.approve(spender, 1); + // token_erc721_1.approve(spender, 2); + // vm.stopPrank(); + // } + + // function initializeERC1155TestTokens() public { + // token_erc1155_0 = new MockERC1155(); + // token_erc1155_1 = new MockERC1155(); + // } + + // function setERC1155TestTokens(address from) public { + // // mint 10 with id 1 + // token_erc1155_0.mint(from, 1, MINT_AMOUNT_ERC1155); + // // mint 10 with id 2 + // token_erc1155_1.mint(from, 2, MINT_AMOUNT_ERC1155); + // } + + // function setERC1155TestTokenApprovals(Vm vm, address owner, address spender) public { + // vm.startPrank(owner); + // token_erc1155_0.setApprovalForAll(spender, true); + // token_erc1155_1.setApprovalForAll(spender, true); + // vm.stopPrank(); + // } +} diff --git a/test/utils/TokenProviderERC721.sol b/test/utils/TokenProviderERC721.sol new file mode 100644 index 00000000..c874e3ee --- /dev/null +++ b/test/utils/TokenProviderERC721.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {MockERC721} from "../mocks/MockERC721.sol"; + +contract TokenProviderERC721 { + uint256 public constant MINT_AMOUNT_ERC20 = 100 ** 18; + uint256 public constant MINT_AMOUNT_ERC1155 = 100; + + uint256 public constant TRANSFER_AMOUNT_ERC20 = 30 ** 18; + uint256 public constant TRANSFER_AMOUNT_ERC1155 = 10; + + MockERC721 _token0; + MockERC721 _token1; + + address faucet = address(0x98765); + + function initializeTokens() public { + _token0 = new MockERC721("TestNFT1", "NFT1"); + _token1 = new MockERC721("TestNFT2", "NFT2"); + } + + function setToken0(address from) public { + // mint with id 1 + _token0.mint(from, 1); + } + + function setToken1(address from) public { + // mint with id 2 + _token1.mint(from, 2); + } + + function setTokenApprovals0(Vm vm, address owner, address spender) public { + vm.startPrank(owner); + _token0.approve(spender, 1); + vm.stopPrank(); + } + + function setTokenApprovals1(Vm vm, address owner, address spender) public { + vm.startPrank(owner); + _token1.approve(spender, 2); + vm.stopPrank(); + } + + // function initializeERC1155TestTokens() public { + // token_erc1155_0 = new MockERC1155(); + // token_erc1155_1 = new MockERC1155(); + // } + + // function setERC1155TestTokens(address from) public { + // // mint 10 with id 1 + // token_erc1155_0.mint(from, 1, MINT_AMOUNT_ERC1155); + // // mint 10 with id 2 + // token_erc1155_1.mint(from, 2, MINT_AMOUNT_ERC1155); + // } + + // function setERC1155TestTokenApprovals(Vm vm, address owner, address spender) public { + // vm.startPrank(owner); + // token_erc1155_0.setApprovalForAll(spender, true); + // token_erc1155_1.setApprovalForAll(spender, true); + // vm.stopPrank(); + // } +}