├── .env.example ├── .forge-snapshots ├── batchTransferFrom.snap ├── batchTransferFromMultiToken.snap ├── lockdown.snap ├── permit2 + transferFrom2 with WETH9's mainnet address.snap ├── permit2 + transferFrom2 with a non EIP-2612 native token with fallback.snap ├── permit2 + transferFrom2 with a non EIP-2612 native token.snap ├── permit2 + transferFrom2 with an EIP-2612 native token.snap ├── permitBatchCleanWrite.snap ├── permitBatchDirtyWrite.snap ├── permitBatchTransferFromMultipleTokens.snap ├── permitBatchTransferFromSingleToken.snap ├── permitCleanWrite.snap ├── permitCompactSig.snap ├── permitDirtyNonce.snap ├── permitDirtyWrite.snap ├── permitInvalidSigner.snap ├── permitSetMaxAllowanceCleanWrite.snap ├── permitSetMaxAllowanceDirtyWrite.snap ├── permitSignatureExpired.snap ├── permitTransferFromBatchTypedWitness.snap ├── permitTransferFromCompactSig.snap ├── permitTransferFromSingleToken.snap ├── permitTransferFromTypedWitness.snap ├── safePermit + safeTransferFrom with an EIP-2612 native token.snap ├── simplePermit2 + transferFrom2 with a non EIP-2612 native token.snap ├── single recipient 2 tokens.snap ├── single recipient many tokens.snap ├── transferFrom with different owners.snap └── transferFrom.snap ├── .gas-snapshot ├── .gitattributes ├── .github └── workflows │ ├── gas.yml │ ├── integration-tests.yml │ ├── lint.yml │ └── unit-tests.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── audits ├── ABDKAudit.pdf └── Chainsecurity Audit.pdf ├── foundry.toml ├── package.json ├── remappings.txt ├── script └── DeployPermit2.s.sol ├── src ├── AllowanceTransfer.sol ├── EIP712.sol ├── Permit2.sol ├── PermitErrors.sol ├── SignatureTransfer.sol ├── interfaces │ ├── IAllowanceTransfer.sol │ ├── IDAIPermit.sol │ ├── IEIP712.sol │ ├── IERC1271.sol │ ├── IPermit2.sol │ └── ISignatureTransfer.sol └── libraries │ ├── Allowance.sol │ ├── Permit2Lib.sol │ ├── PermitHash.sol │ ├── SafeCast160.sol │ └── SignatureVerification.sol └── test ├── AllowanceTransferInvariants.t.sol ├── AllowanceTransferTest.t.sol ├── AllowanceUnitTest.sol ├── CompactSignature.t.sol ├── EIP712.t.sol ├── NonceBitmap.t.sol ├── Permit2Lib.t.sol ├── SignatureTransfer.t.sol ├── TypehashGeneration.t.sol ├── actors ├── Permitter.sol └── Spender.sol ├── integration ├── Argent.t.sol ├── GnosisSafe.t.sol ├── MainnetToken.t.sol └── tokens │ ├── DAI.t.sol │ ├── FeeOnTransferToken.t.sol │ ├── RebasingToken.t.sol │ ├── TooManyReturnBytesToken.t.sol │ ├── UNI.t.sol │ ├── USDC.t.sol │ ├── USDT.t.sol │ ├── WBTC.t.sol │ └── ZRX.t.sol ├── mocks ├── MockERC1155.sol ├── MockERC20.sol ├── MockERC721.sol ├── MockFallbackERC20.sol ├── MockHash.sol ├── MockNonPermitERC20.sol ├── MockNonPermitNonERC20WithDS.sol ├── MockPermit2.sol ├── MockPermit2Lib.sol ├── MockPermitWithDS.sol └── MockSignatureVerification.sol └── utils ├── AddressBuilder.sol ├── AmountBuilder.sol ├── DeployPermit2.sol ├── DeployPermit2.t.sol ├── PermitSignature.sol ├── StructBuilder.sol └── TokenProvider.sol /.env.example: -------------------------------------------------------------------------------- 1 | FORK_URL= -------------------------------------------------------------------------------- /.forge-snapshots/batchTransferFrom.snap: -------------------------------------------------------------------------------- 1 | 61797 -------------------------------------------------------------------------------- /.forge-snapshots/batchTransferFromMultiToken.snap: -------------------------------------------------------------------------------- 1 | 81786 -------------------------------------------------------------------------------- /.forge-snapshots/lockdown.snap: -------------------------------------------------------------------------------- 1 | 28435 -------------------------------------------------------------------------------- /.forge-snapshots/permit2 + transferFrom2 with WETH9's mainnet address.snap: -------------------------------------------------------------------------------- 1 | 60346 -------------------------------------------------------------------------------- /.forge-snapshots/permit2 + transferFrom2 with a non EIP-2612 native token with fallback.snap: -------------------------------------------------------------------------------- 1 | 65533 -------------------------------------------------------------------------------- /.forge-snapshots/permit2 + transferFrom2 with a non EIP-2612 native token.snap: -------------------------------------------------------------------------------- 1 | 60811 -------------------------------------------------------------------------------- /.forge-snapshots/permit2 + transferFrom2 with an EIP-2612 native token.snap: -------------------------------------------------------------------------------- 1 | 46296 -------------------------------------------------------------------------------- /.forge-snapshots/permitBatchCleanWrite.snap: -------------------------------------------------------------------------------- 1 | 91924 -------------------------------------------------------------------------------- /.forge-snapshots/permitBatchDirtyWrite.snap: -------------------------------------------------------------------------------- 1 | 57724 -------------------------------------------------------------------------------- /.forge-snapshots/permitBatchTransferFromMultipleTokens.snap: -------------------------------------------------------------------------------- 1 | 143387 -------------------------------------------------------------------------------- /.forge-snapshots/permitBatchTransferFromSingleToken.snap: -------------------------------------------------------------------------------- 1 | 88867 -------------------------------------------------------------------------------- /.forge-snapshots/permitCleanWrite.snap: -------------------------------------------------------------------------------- 1 | 63119 -------------------------------------------------------------------------------- /.forge-snapshots/permitCompactSig.snap: -------------------------------------------------------------------------------- 1 | 63094 -------------------------------------------------------------------------------- /.forge-snapshots/permitDirtyNonce.snap: -------------------------------------------------------------------------------- 1 | 44014 -------------------------------------------------------------------------------- /.forge-snapshots/permitDirtyWrite.snap: -------------------------------------------------------------------------------- 1 | 46019 -------------------------------------------------------------------------------- /.forge-snapshots/permitInvalidSigner.snap: -------------------------------------------------------------------------------- 1 | 40301 -------------------------------------------------------------------------------- /.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap: -------------------------------------------------------------------------------- 1 | 61114 -------------------------------------------------------------------------------- /.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap: -------------------------------------------------------------------------------- 1 | 44014 -------------------------------------------------------------------------------- /.forge-snapshots/permitSignatureExpired.snap: -------------------------------------------------------------------------------- 1 | 31700 -------------------------------------------------------------------------------- /.forge-snapshots/permitTransferFromBatchTypedWitness.snap: -------------------------------------------------------------------------------- 1 | 120325 -------------------------------------------------------------------------------- /.forge-snapshots/permitTransferFromCompactSig.snap: -------------------------------------------------------------------------------- 1 | 86066 -------------------------------------------------------------------------------- /.forge-snapshots/permitTransferFromSingleToken.snap: -------------------------------------------------------------------------------- 1 | 86092 -------------------------------------------------------------------------------- /.forge-snapshots/permitTransferFromTypedWitness.snap: -------------------------------------------------------------------------------- 1 | 87817 -------------------------------------------------------------------------------- /.forge-snapshots/safePermit + safeTransferFrom with an EIP-2612 native token.snap: -------------------------------------------------------------------------------- 1 | 48268 -------------------------------------------------------------------------------- /.forge-snapshots/simplePermit2 + transferFrom2 with a non EIP-2612 native token.snap: -------------------------------------------------------------------------------- 1 | 60811 -------------------------------------------------------------------------------- /.forge-snapshots/single recipient 2 tokens.snap: -------------------------------------------------------------------------------- 1 | 118525 -------------------------------------------------------------------------------- /.forge-snapshots/single recipient many tokens.snap: -------------------------------------------------------------------------------- 1 | 133544 -------------------------------------------------------------------------------- /.forge-snapshots/transferFrom with different owners.snap: -------------------------------------------------------------------------------- 1 | 61886 -------------------------------------------------------------------------------- /.forge-snapshots/transferFrom.snap: -------------------------------------------------------------------------------- 1 | 52197 -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | AllowanceTransferInvariants:invariant_balanceEqualsSpent() (runs: 256, calls: 3840, reverts: 879) 2 | AllowanceTransferInvariants:invariant_permit2NeverHoldsBalance() (runs: 256, calls: 3840, reverts: 878) 3 | AllowanceTransferInvariants:invariant_spendNeverExceedsPermit() (runs: 256, calls: 3840, reverts: 881) 4 | AllowanceTransferTest:testApprove() (gas: 47570) 5 | AllowanceTransferTest:testBatchTransferFrom() (gas: 159197) 6 | AllowanceTransferTest:testBatchTransferFromDifferentOwners() (gas: 235094) 7 | AllowanceTransferTest:testBatchTransferFromMultiToken() (gas: 231841) 8 | AllowanceTransferTest:testBatchTransferFromWithGasSnapshot() (gas: 159857) 9 | AllowanceTransferTest:testExcessiveInvalidation() (gas: 64205) 10 | AllowanceTransferTest:testInvalidateMultipleNonces() (gas: 83150) 11 | AllowanceTransferTest:testInvalidateNonces() (gas: 62847) 12 | AllowanceTransferTest:testInvalidateNoncesInvalid() (gas: 16327) 13 | AllowanceTransferTest:testLockdown() (gas: 145984) 14 | AllowanceTransferTest:testLockdownEvent() (gas: 117749) 15 | AllowanceTransferTest:testMaxAllowance() (gas: 134888) 16 | AllowanceTransferTest:testMaxAllowanceDirtyWrite() (gas: 117455) 17 | AllowanceTransferTest:testPartialAllowance() (gas: 105140) 18 | AllowanceTransferTest:testReuseOrderedNonceInvalid() (gas: 69154) 19 | AllowanceTransferTest:testSetAllowance() (gas: 89627) 20 | AllowanceTransferTest:testSetAllowanceBatch() (gas: 133740) 21 | AllowanceTransferTest:testSetAllowanceBatchDifferentNonces() (gas: 118603) 22 | AllowanceTransferTest:testSetAllowanceBatchDirtyWrite() (gas: 99210) 23 | AllowanceTransferTest:testSetAllowanceBatchEvent() (gas: 116049) 24 | AllowanceTransferTest:testSetAllowanceCompactSig() (gas: 89587) 25 | AllowanceTransferTest:testSetAllowanceDeadlinePassed() (gas: 56512) 26 | AllowanceTransferTest:testSetAllowanceDirtyWrite() (gas: 72175) 27 | AllowanceTransferTest:testSetAllowanceIncorrectSigLength() (gas: 29198) 28 | AllowanceTransferTest:testSetAllowanceInvalidSignature() (gas: 64065) 29 | AllowanceTransferTest:testSetAllowanceTransfer() (gas: 103115) 30 | AllowanceTransferTest:testSetAllowanceTransferDirtyNonceDirtyTransfer() (gas: 97194) 31 | AllowanceTransferTest:testTransferFromWithGasSnapshot() (gas: 132867) 32 | AllowanceUnitTest:testPackAndUnpack(uint160,uint48,uint48) (runs: 256, μ: 39025, ~: 39103) 33 | AllowanceUnitTest:testUpdateAllRandomly(uint160,uint48,uint48) (runs: 256, μ: 40243, ~: 40244) 34 | AllowanceUnitTest:testUpdateAmountExpirationRandomly(uint160,uint48) (runs: 256, μ: 39169, ~: 39170) 35 | CompactSignature:testCompactSignature27() (gas: 300) 36 | CompactSignature:testCompactSignature28() (gas: 144) 37 | DeployPermit2Test:testAllowanceTransferSanityCheck() (gas: 101876) 38 | DeployPermit2Test:testDeployPermit2() (gas: 4337527) 39 | DeployPermit2Test:testSignatureTransferSanityCheck() (gas: 92792) 40 | EIP712Test:testDomainSeparator() (gas: 5881) 41 | EIP712Test:testDomainSeparatorAfterFork() (gas: 10830) 42 | MockPermit2Lib:testPermit2Code(address):(bool) (runs: 256, μ: 3003, ~: 3016) 43 | NonceBitmapTest:testHighNonces() (gas: 36305) 44 | NonceBitmapTest:testInvalidateFullWord() (gas: 63061) 45 | NonceBitmapTest:testInvalidateNoncesRandomly(uint248,uint256) (runs: 256, μ: 30439, ~: 31139) 46 | NonceBitmapTest:testInvalidateNonzeroWord() (gas: 85642) 47 | NonceBitmapTest:testInvalidateTwoNoncesRandomly(uint248,uint256,uint256) (runs: 256, μ: 39182, ~: 39182) 48 | NonceBitmapTest:testLowNonces() (gas: 41041) 49 | NonceBitmapTest:testNonceWordBoundary() (gas: 42284) 50 | NonceBitmapTest:testUseTwoRandomNonces(uint256,uint256) (runs: 256, μ: 49190, ~: 51625) 51 | NonceBitmapTest:testUsingNonceTwiceFails(uint256) (runs: 256, μ: 21935, ~: 21960) 52 | Permit2LibTest:testOZSafePermit() (gas: 24682) 53 | Permit2LibTest:testOZSafePermitPlusOZSafeTransferFrom() (gas: 129329) 54 | Permit2LibTest:testOZSafeTransferFrom() (gas: 39007) 55 | Permit2LibTest:testPermit2() (gas: 22941) 56 | Permit2LibTest:testPermit2DSLessToken() (gas: 7143) 57 | Permit2LibTest:testPermit2DSMore32Token() (gas: 7252) 58 | Permit2LibTest:testPermit2DSMoreToken() (gas: 7023) 59 | Permit2LibTest:testPermit2Full() (gas: 42356) 60 | Permit2LibTest:testPermit2InvalidAmount() (gas: 21011) 61 | Permit2LibTest:testPermit2LargerDS() (gas: 51464) 62 | Permit2LibTest:testPermit2LargerDSRevert() (gas: 32841) 63 | Permit2LibTest:testPermit2NonPermitFallback() (gas: 37245) 64 | Permit2LibTest:testPermit2NonPermitToken() (gas: 32164) 65 | Permit2LibTest:testPermit2PlusTransferFrom2() (gas: 126995) 66 | Permit2LibTest:testPermit2PlusTransferFrom2WithNonPermit() (gas: 148221) 67 | Permit2LibTest:testPermit2PlusTransferFrom2WithNonPermitFallback() (gas: 174749) 68 | Permit2LibTest:testPermit2PlusTransferFrom2WithWETH9Mainnet() (gas: 147934) 69 | Permit2LibTest:testPermit2SmallerDS() (gas: 77688) 70 | Permit2LibTest:testPermit2SmallerDSNoRevert() (gas: 59324) 71 | Permit2LibTest:testPermit2WETH9Mainnet() (gas: 28774) 72 | Permit2LibTest:testSimplePermit2() (gas: 29117) 73 | Permit2LibTest:testSimplePermit2InvalidAmount() (gas: 16944) 74 | Permit2LibTest:testSimplePermit2PlusTransferFrom2WithNonPermit() (gas: 148463) 75 | Permit2LibTest:testStandardPermit() (gas: 22535) 76 | Permit2LibTest:testStandardTransferFrom() (gas: 38143) 77 | Permit2LibTest:testTransferFrom2() (gas: 38734) 78 | Permit2LibTest:testTransferFrom2Full() (gas: 53368) 79 | Permit2LibTest:testTransferFrom2InvalidAmount() (gas: 12732) 80 | Permit2LibTest:testTransferFrom2NonPermitToken() (gas: 53170) 81 | SignatureTransferTest:testCorrectWitnessTypehashes() (gas: 3091) 82 | SignatureTransferTest:testGasMultiplePermitBatchTransferFrom() (gas: 270972) 83 | SignatureTransferTest:testGasSinglePermitBatchTransferFrom() (gas: 183860) 84 | SignatureTransferTest:testGasSinglePermitTransferFrom() (gas: 123854) 85 | SignatureTransferTest:testInvalidateUnorderedNonces() (gas: 41396) 86 | SignatureTransferTest:testPermitBatchMultiPermitSingleTransfer() (gas: 133675) 87 | SignatureTransferTest:testPermitBatchTransferFrom() (gas: 162019) 88 | SignatureTransferTest:testPermitBatchTransferFromSingleRecipient() (gas: 187957) 89 | SignatureTransferTest:testPermitBatchTransferFromTypedWitness() (gas: 239926) 90 | SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidType() (gas: 84489) 91 | SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidTypeHash() (gas: 86007) 92 | SignatureTransferTest:testPermitBatchTransferFromTypedWitnessInvalidWitness() (gas: 85751) 93 | SignatureTransferTest:testPermitBatchTransferInvalidAmountsLengthMismatch() (gas: 41574) 94 | SignatureTransferTest:testPermitBatchTransferMultiAddr() (gas: 160547) 95 | SignatureTransferTest:testPermitBatchTransferSingleRecipientManyTokens() (gas: 209422) 96 | SignatureTransferTest:testPermitTransferFrom() (gas: 92909) 97 | SignatureTransferTest:testPermitTransferFromCompactSig() (gas: 124059) 98 | SignatureTransferTest:testPermitTransferFromIncorrectSigLength() (gas: 51346) 99 | SignatureTransferTest:testPermitTransferFromInvalidNonce() (gas: 72928) 100 | SignatureTransferTest:testPermitTransferFromRandomNonceAndAmount(uint256,uint128) (runs: 256, μ: 95752, ~: 96728) 101 | SignatureTransferTest:testPermitTransferFromToSpender() (gas: 93283) 102 | SignatureTransferTest:testPermitTransferFromTypedWitness() (gas: 125159) 103 | SignatureTransferTest:testPermitTransferFromTypedWitnessInvalidType() (gas: 55947) 104 | SignatureTransferTest:testPermitTransferFromTypedWitnessInvalidTypehash() (gas: 56879) 105 | SignatureTransferTest:testPermitTransferSpendLessThanFull(uint256,uint128) (runs: 256, μ: 97604, ~: 99733) 106 | TypehashGeneration:testPermitBatch() (gas: 40473) 107 | TypehashGeneration:testPermitBatchTransferFrom() (gas: 49837) 108 | TypehashGeneration:testPermitBatchTransferFromWithWitness() (gas: 56621) 109 | TypehashGeneration:testPermitBatchTransferFromWithWitnessIncorrectPermitData() (gas: 56744) 110 | TypehashGeneration:testPermitBatchTransferFromWithWitnessIncorrectTypehashStub() (gas: 57353) 111 | TypehashGeneration:testPermitSingle() (gas: 28138) 112 | TypehashGeneration:testPermitTransferFrom() (gas: 36511) 113 | TypehashGeneration:testPermitTransferFromWithWitness() (gas: 43469) 114 | TypehashGeneration:testPermitTransferFromWithWitnessIncorrectPermitData() (gas: 43436) 115 | TypehashGeneration:testPermitTransferFromWithWitnessIncorrectTypehashStub() (gas: 43956) -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | .gas-snapshot linguist-language=Julia -------------------------------------------------------------------------------- /.github/workflows/gas.yml: -------------------------------------------------------------------------------- 1 | name: Gas 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install Foundry 16 | uses: foundry-rs/foundry-toolchain@v1 17 | with: 18 | version: nightly 19 | 20 | - name: Check gas snapshots 21 | run: forge snapshot --check 22 | -------------------------------------------------------------------------------- /.github/workflows/integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: Integration Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install Foundry 16 | uses: foundry-rs/foundry-toolchain@v1 17 | with: 18 | version: nightly 19 | 20 | - name: Install dependencies 21 | run: forge install 22 | 23 | - name: Check contract sizes 24 | run: forge build --sizes 25 | 26 | - name: Run tests 27 | run: forge test 28 | env: 29 | FOUNDRY_PROFILE: integration 30 | FORK_URL: ${{ secrets.FORK_URL }} 31 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install Foundry 16 | uses: foundry-rs/foundry-toolchain@v1 17 | with: 18 | version: nightly 19 | 20 | - name: Check formatting 21 | run: forge fmt --check 22 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install Foundry 16 | uses: foundry-rs/foundry-toolchain@v1 17 | with: 18 | version: nightly 19 | 20 | - name: Install dependencies 21 | run: forge install 22 | 23 | - name: Check contract sizes 24 | run: forge build --sizes 25 | 26 | - name: Run tests 27 | run: forge test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /cache 3 | /out 4 | broadcast/ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solmate"] 5 | path = lib/solmate 6 | url = https://github.com/rari-capital/solmate 7 | [submodule "lib/openzeppelin-contracts"] 8 | path = lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | [submodule "lib/forge-gas-snapshot"] 11 | path = lib/forge-gas-snapshot 12 | url = https://github.com/marktoda/forge-gas-snapshot 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "src", 3 | "solidity.packageDefaultDependenciesDirectory": "lib", 4 | "solidity.compileUsingRemoteVersion": "v0.8.17", 5 | "solidity.formatter": "forge", 6 | "search.exclude": { "lib": true }, 7 | "files.associations": { 8 | ".gas-snapshot": "julia" 9 | }, 10 | "editor.formatOnSave": true 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Uniswap Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # permit2 2 | 3 | Permit2 introduces a low-overhead, next-generation token approval/meta-tx system to make token approvals easier, more secure, and more consistent across applications. 4 | 5 | ## Features 6 | 7 | - **Signature Based Approvals**: Any ERC20 token, even those that do not support [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612), can now use permit style approvals. This allows applications to have a single transaction flow by sending a permit signature along with the transaction data when using `Permit2` integrated contracts. 8 | - **Batched Token Approvals**: Set permissions on different tokens to different spenders with one signature. 9 | - **Signature Based Token Transfers**: Owners can sign messages to transfer tokens directly to signed spenders, bypassing setting any allowance. This means that approvals aren't necessary for applications to receive tokens and that there will never be hanging approvals when using this method. The signature is valid only for the duration of the transaction in which it is spent. 10 | - **Batched Token Transfers**: Transfer different tokens to different recipients with one signature. 11 | - **Safe Arbitrary Data Verification**: Verify any extra data by passing through a witness hash and witness type. The type string must follow the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) standard. 12 | - **Signature Verification for Contracts**: All signature verification supports [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) so contracts can approve tokens and transfer tokens through signatures. 13 | - **Non-monotonic Replay Protection**: Signature based transfers use unordered, non-monotonic nonces so that signed permits do not need to be transacted in any particular order. 14 | - **Expiring Approvals**: Approvals can be time-bound, removing security concerns around hanging approvals on a wallet’s entire token balance. This also means that revoking approvals do not necessarily have to be a new transaction since an approval that expires will no longer be valid. 15 | - **Batch Revoke Allowances**: Remove allowances on any number of tokens and spenders in one transaction. 16 | 17 | ## Architecture 18 | 19 | Permit2 is the union of two contracts: [`AllowanceTransfer`](https://github.com/Uniswap/permit2/blob/main/src/AllowanceTransfer.sol) and [`SignatureTransfer`](https://github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol). 20 | 21 | The `SignatureTransfer` contract handles all signature-based transfers, meaning that an allowance on the token is bypassed and permissions to the spender only last for the duration of the transaction that the one-time signature is spent. 22 | 23 | The `AllowanceTransfer` contract handles setting allowances on tokens, giving permissions to spenders on a specified amount for a specified duration of time. Any transfers that then happen through the `AllowanceTransfer` contract will only succeed if the proper permissions have been set. 24 | 25 | ## Integrating with Permit2 26 | 27 | Before integrating, contracts can request users’ tokens through `Permit2`, users must approve the `Permit2` contract through the specific token contract. To see a detailed technical reference, visit the Uniswap [documentation site](https://docs.uniswap.org/contracts/permit2/overview). 28 | 29 | ### Note on viaIR compilation 30 | Permit2 uses viaIR compilation, so importing and deploying it in an integration for tests will require the integrating repository to also use viaIR compilation. This is often quite slow, so can be avoided using the precompiled `DeployPermit2` utility: 31 | ``` 32 | import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; 33 | 34 | contract MyTest is DeployPermit2 { 35 | address permit2; 36 | 37 | function setUp() public { 38 | permit2 = deployPermit2(); 39 | } 40 | } 41 | ``` 42 | 43 | ## Bug Bounty 44 | 45 | This repository is subject to the Uniswap Labs Bug Bounty program, per the terms defined [here](https://uniswap.org/bug-bounty). 46 | 47 | ## Contributing 48 | 49 | You will need a copy of [Foundry](https://github.com/foundry-rs/foundry) installed before proceeding. See the [installation guide](https://github.com/foundry-rs/foundry#installation) for details. 50 | 51 | ### Setup 52 | 53 | ```sh 54 | git clone https://github.com/Uniswap/permit2.git 55 | cd permit2 56 | forge install 57 | ``` 58 | 59 | ### Lint 60 | 61 | ```sh 62 | forge fmt [--check] 63 | ``` 64 | 65 | ### Run Tests 66 | 67 | ```sh 68 | # unit 69 | forge test 70 | 71 | # integration 72 | source .env 73 | FOUNDRY_PROFILE=integration forge test 74 | ``` 75 | 76 | ### Update Gas Snapshots 77 | 78 | ```sh 79 | forge snapshot 80 | ``` 81 | 82 | ### Deploy 83 | 84 | Run the command below. Remove `--broadcast`, `---rpc-url`, `--private-key` and `--verify` options to test locally 85 | 86 | ```sh 87 | forge script --broadcast --rpc-url --private-key --verify script/DeployPermit2.s.sol:DeployPermit2 88 | ``` 89 | 90 | ## Acknowledgments 91 | 92 | Inspired by [merklejerk](https://github.com/merklejerk)'s [permit-everywhere](https://github.com/merklejerk/permit-everywhere) contracts which introduce permit based approvals for all tokens regardless of EIP2612 support. 93 | -------------------------------------------------------------------------------- /audits/ABDKAudit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/permit2/cc56ad0f3439c502c246fc5cfcc3db92bb8b7219/audits/ABDKAudit.pdf -------------------------------------------------------------------------------- /audits/Chainsecurity Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/permit2/cc56ad0f3439c502c246fc5cfcc3db92bb8b7219/audits/Chainsecurity Audit.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc = "0.8.17" 3 | bytecode_hash = "none" 4 | optimizer = true 5 | via_ir = true 6 | optimizer_runs = 1000000 7 | no_match_path = "*/integration/*" 8 | fuzz_runs = 10000 9 | ffi = true 10 | fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] 11 | 12 | [profile.integration] 13 | no_match_path = "" 14 | match_path = "*/integration/*" 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uniswap/permit2", 3 | "description": "Low-overhead, next generation token approval/meta-tx system to make token approvals easier, more secure, and more consistent across applications", 4 | "version": "1.0.0", 5 | "bugs": "https://github.com/Uniswap/permit2/issues", 6 | "keywords": [ 7 | "ethereum", 8 | "permit2", 9 | "uniswap" 10 | ], 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Uniswap/permit2.git" 15 | } 16 | } -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | solmate/=lib/solmate 2 | -------------------------------------------------------------------------------- /script/DeployPermit2.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/console2.sol"; 5 | import "forge-std/Script.sol"; 6 | import {Permit2} from "src/Permit2.sol"; 7 | 8 | bytes32 constant SALT = bytes32(uint256(0x0000000000000000000000000000000000000000d3af2663da51c10215000000)); 9 | 10 | contract DeployPermit2 is Script { 11 | function setUp() public {} 12 | 13 | function run() public returns (Permit2 permit2) { 14 | vm.startBroadcast(); 15 | 16 | permit2 = new Permit2{salt: SALT}(); 17 | console2.log("Permit2 Deployed:", address(permit2)); 18 | 19 | vm.stopBroadcast(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AllowanceTransfer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; 6 | import {PermitHash} from "./libraries/PermitHash.sol"; 7 | import {SignatureVerification} from "./libraries/SignatureVerification.sol"; 8 | import {EIP712} from "./EIP712.sol"; 9 | import {IAllowanceTransfer} from "./interfaces/IAllowanceTransfer.sol"; 10 | import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; 11 | import {Allowance} from "./libraries/Allowance.sol"; 12 | 13 | contract AllowanceTransfer is IAllowanceTransfer, EIP712 { 14 | using SignatureVerification for bytes; 15 | using SafeTransferLib for ERC20; 16 | using PermitHash for PermitSingle; 17 | using PermitHash for PermitBatch; 18 | using Allowance for PackedAllowance; 19 | 20 | /// @notice Maps users to tokens to spender addresses and information about the approval on the token 21 | /// @dev Indexed in the order of token owner address, token address, spender address 22 | /// @dev The stored word saves the allowed amount, expiration on the allowance, and nonce 23 | mapping(address => mapping(address => mapping(address => PackedAllowance))) public allowance; 24 | 25 | /// @inheritdoc IAllowanceTransfer 26 | function approve(address token, address spender, uint160 amount, uint48 expiration) external { 27 | PackedAllowance storage allowed = allowance[msg.sender][token][spender]; 28 | allowed.updateAmountAndExpiration(amount, expiration); 29 | emit Approval(msg.sender, token, spender, amount, expiration); 30 | } 31 | 32 | /// @inheritdoc IAllowanceTransfer 33 | function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external { 34 | if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline); 35 | 36 | // Verify the signer address from the signature. 37 | signature.verify(_hashTypedData(permitSingle.hash()), owner); 38 | 39 | _updateApproval(permitSingle.details, owner, permitSingle.spender); 40 | } 41 | 42 | /// @inheritdoc IAllowanceTransfer 43 | function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external { 44 | if (block.timestamp > permitBatch.sigDeadline) revert SignatureExpired(permitBatch.sigDeadline); 45 | 46 | // Verify the signer address from the signature. 47 | signature.verify(_hashTypedData(permitBatch.hash()), owner); 48 | 49 | address spender = permitBatch.spender; 50 | unchecked { 51 | uint256 length = permitBatch.details.length; 52 | for (uint256 i = 0; i < length; ++i) { 53 | _updateApproval(permitBatch.details[i], owner, spender); 54 | } 55 | } 56 | } 57 | 58 | /// @inheritdoc IAllowanceTransfer 59 | function transferFrom(address from, address to, uint160 amount, address token) external { 60 | _transfer(from, to, amount, token); 61 | } 62 | 63 | /// @inheritdoc IAllowanceTransfer 64 | function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external { 65 | unchecked { 66 | uint256 length = transferDetails.length; 67 | for (uint256 i = 0; i < length; ++i) { 68 | AllowanceTransferDetails memory transferDetail = transferDetails[i]; 69 | _transfer(transferDetail.from, transferDetail.to, transferDetail.amount, transferDetail.token); 70 | } 71 | } 72 | } 73 | 74 | /// @notice Internal function for transferring tokens using stored allowances 75 | /// @dev Will fail if the allowed timeframe has passed 76 | function _transfer(address from, address to, uint160 amount, address token) private { 77 | PackedAllowance storage allowed = allowance[from][token][msg.sender]; 78 | 79 | if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration); 80 | 81 | uint256 maxAmount = allowed.amount; 82 | if (maxAmount != type(uint160).max) { 83 | if (amount > maxAmount) { 84 | revert InsufficientAllowance(maxAmount); 85 | } else { 86 | unchecked { 87 | allowed.amount = uint160(maxAmount) - amount; 88 | } 89 | } 90 | } 91 | 92 | // Transfer the tokens from the from address to the recipient. 93 | ERC20(token).safeTransferFrom(from, to, amount); 94 | } 95 | 96 | /// @inheritdoc IAllowanceTransfer 97 | function lockdown(TokenSpenderPair[] calldata approvals) external { 98 | address owner = msg.sender; 99 | // Revoke allowances for each pair of spenders and tokens. 100 | unchecked { 101 | uint256 length = approvals.length; 102 | for (uint256 i = 0; i < length; ++i) { 103 | address token = approvals[i].token; 104 | address spender = approvals[i].spender; 105 | 106 | allowance[owner][token][spender].amount = 0; 107 | emit Lockdown(owner, token, spender); 108 | } 109 | } 110 | } 111 | 112 | /// @inheritdoc IAllowanceTransfer 113 | function invalidateNonces(address token, address spender, uint48 newNonce) external { 114 | uint48 oldNonce = allowance[msg.sender][token][spender].nonce; 115 | 116 | if (newNonce <= oldNonce) revert InvalidNonce(); 117 | 118 | // Limit the amount of nonces that can be invalidated in one transaction. 119 | unchecked { 120 | uint48 delta = newNonce - oldNonce; 121 | if (delta > type(uint16).max) revert ExcessiveInvalidation(); 122 | } 123 | 124 | allowance[msg.sender][token][spender].nonce = newNonce; 125 | emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce); 126 | } 127 | 128 | /// @notice Sets the new values for amount, expiration, and nonce. 129 | /// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1. 130 | /// @dev Emits a Permit event. 131 | function _updateApproval(PermitDetails memory details, address owner, address spender) private { 132 | uint48 nonce = details.nonce; 133 | address token = details.token; 134 | uint160 amount = details.amount; 135 | uint48 expiration = details.expiration; 136 | PackedAllowance storage allowed = allowance[owner][token][spender]; 137 | 138 | if (allowed.nonce != nonce) revert InvalidNonce(); 139 | 140 | allowed.updateAll(amount, expiration, nonce); 141 | emit Permit(owner, token, spender, amount, expiration, nonce); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/EIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {IEIP712} from "./interfaces/IEIP712.sol"; 5 | 6 | /// @notice EIP712 helpers for permit2 7 | /// @dev Maintains cross-chain replay protection in the event of a fork 8 | /// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol 9 | contract EIP712 is IEIP712 { 10 | // Cache the domain separator as an immutable value, but also store the chain id that it 11 | // corresponds to, in order to invalidate the cached domain separator if the chain id changes. 12 | bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; 13 | uint256 private immutable _CACHED_CHAIN_ID; 14 | 15 | bytes32 private constant _HASHED_NAME = keccak256("Permit2"); 16 | bytes32 private constant _TYPE_HASH = 17 | keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 18 | 19 | constructor() { 20 | _CACHED_CHAIN_ID = block.chainid; 21 | _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); 22 | } 23 | 24 | /// @notice Returns the domain separator for the current chain. 25 | /// @dev Uses cached version if chainid and address are unchanged from construction. 26 | function DOMAIN_SEPARATOR() public view override returns (bytes32) { 27 | return block.chainid == _CACHED_CHAIN_ID 28 | ? _CACHED_DOMAIN_SEPARATOR 29 | : _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); 30 | } 31 | 32 | /// @notice Builds a domain separator using the current chainId and contract address. 33 | function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) { 34 | return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this))); 35 | } 36 | 37 | /// @notice Creates an EIP-712 typed data hash 38 | function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) { 39 | return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Permit2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {SignatureTransfer} from "./SignatureTransfer.sol"; 5 | import {AllowanceTransfer} from "./AllowanceTransfer.sol"; 6 | 7 | /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. 8 | /// @dev Users must approve Permit2 before calling any of the transfer functions. 9 | contract Permit2 is SignatureTransfer, AllowanceTransfer { 10 | // Permit2 unifies the two contracts so users have maximal flexibility with their approval. 11 | } 12 | -------------------------------------------------------------------------------- /src/PermitErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /// @notice Shared errors between signature based transfers and allowance based transfers. 5 | 6 | /// @notice Thrown when validating an inputted signature that is stale 7 | /// @param signatureDeadline The timestamp at which a signature is no longer valid 8 | error SignatureExpired(uint256 signatureDeadline); 9 | 10 | /// @notice Thrown when validating that the inputted nonce has not been used 11 | error InvalidNonce(); 12 | -------------------------------------------------------------------------------- /src/SignatureTransfer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {ISignatureTransfer} from "./interfaces/ISignatureTransfer.sol"; 5 | import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; 6 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 7 | import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; 8 | import {SignatureVerification} from "./libraries/SignatureVerification.sol"; 9 | import {PermitHash} from "./libraries/PermitHash.sol"; 10 | import {EIP712} from "./EIP712.sol"; 11 | 12 | contract SignatureTransfer is ISignatureTransfer, EIP712 { 13 | using SignatureVerification for bytes; 14 | using SafeTransferLib for ERC20; 15 | using PermitHash for PermitTransferFrom; 16 | using PermitHash for PermitBatchTransferFrom; 17 | 18 | /// @inheritdoc ISignatureTransfer 19 | mapping(address => mapping(uint256 => uint256)) public nonceBitmap; 20 | 21 | /// @inheritdoc ISignatureTransfer 22 | function permitTransferFrom( 23 | PermitTransferFrom memory permit, 24 | SignatureTransferDetails calldata transferDetails, 25 | address owner, 26 | bytes calldata signature 27 | ) external { 28 | _permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature); 29 | } 30 | 31 | /// @inheritdoc ISignatureTransfer 32 | function permitWitnessTransferFrom( 33 | PermitTransferFrom memory permit, 34 | SignatureTransferDetails calldata transferDetails, 35 | address owner, 36 | bytes32 witness, 37 | string calldata witnessTypeString, 38 | bytes calldata signature 39 | ) external { 40 | _permitTransferFrom( 41 | permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature 42 | ); 43 | } 44 | 45 | /// @notice Transfers a token using a signed permit message. 46 | /// @param permit The permit data signed over by the owner 47 | /// @param dataHash The EIP-712 hash of permit data to include when checking signature 48 | /// @param owner The owner of the tokens to transfer 49 | /// @param transferDetails The spender's requested transfer details for the permitted token 50 | /// @param signature The signature to verify 51 | function _permitTransferFrom( 52 | PermitTransferFrom memory permit, 53 | SignatureTransferDetails calldata transferDetails, 54 | address owner, 55 | bytes32 dataHash, 56 | bytes calldata signature 57 | ) private { 58 | uint256 requestedAmount = transferDetails.requestedAmount; 59 | 60 | if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline); 61 | if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount); 62 | 63 | _useUnorderedNonce(owner, permit.nonce); 64 | 65 | signature.verify(_hashTypedData(dataHash), owner); 66 | 67 | ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount); 68 | } 69 | 70 | /// @inheritdoc ISignatureTransfer 71 | function permitTransferFrom( 72 | PermitBatchTransferFrom memory permit, 73 | SignatureTransferDetails[] calldata transferDetails, 74 | address owner, 75 | bytes calldata signature 76 | ) external { 77 | _permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature); 78 | } 79 | 80 | /// @inheritdoc ISignatureTransfer 81 | function permitWitnessTransferFrom( 82 | PermitBatchTransferFrom memory permit, 83 | SignatureTransferDetails[] calldata transferDetails, 84 | address owner, 85 | bytes32 witness, 86 | string calldata witnessTypeString, 87 | bytes calldata signature 88 | ) external { 89 | _permitTransferFrom( 90 | permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature 91 | ); 92 | } 93 | 94 | /// @notice Transfers tokens using a signed permit messages 95 | /// @param permit The permit data signed over by the owner 96 | /// @param dataHash The EIP-712 hash of permit data to include when checking signature 97 | /// @param owner The owner of the tokens to transfer 98 | /// @param signature The signature to verify 99 | function _permitTransferFrom( 100 | PermitBatchTransferFrom memory permit, 101 | SignatureTransferDetails[] calldata transferDetails, 102 | address owner, 103 | bytes32 dataHash, 104 | bytes calldata signature 105 | ) private { 106 | uint256 numPermitted = permit.permitted.length; 107 | 108 | if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline); 109 | if (numPermitted != transferDetails.length) revert LengthMismatch(); 110 | 111 | _useUnorderedNonce(owner, permit.nonce); 112 | signature.verify(_hashTypedData(dataHash), owner); 113 | 114 | unchecked { 115 | for (uint256 i = 0; i < numPermitted; ++i) { 116 | TokenPermissions memory permitted = permit.permitted[i]; 117 | uint256 requestedAmount = transferDetails[i].requestedAmount; 118 | 119 | if (requestedAmount > permitted.amount) revert InvalidAmount(permitted.amount); 120 | 121 | if (requestedAmount != 0) { 122 | // allow spender to specify which of the permitted tokens should be transferred 123 | ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount); 124 | } 125 | } 126 | } 127 | } 128 | 129 | /// @inheritdoc ISignatureTransfer 130 | function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external { 131 | nonceBitmap[msg.sender][wordPos] |= mask; 132 | 133 | emit UnorderedNonceInvalidation(msg.sender, wordPos, mask); 134 | } 135 | 136 | /// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces 137 | /// @param nonce The nonce to get the associated word and bit positions 138 | /// @return wordPos The word position or index into the nonceBitmap 139 | /// @return bitPos The bit position 140 | /// @dev The first 248 bits of the nonce value is the index of the desired bitmap 141 | /// @dev The last 8 bits of the nonce value is the position of the bit in the bitmap 142 | function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) { 143 | wordPos = uint248(nonce >> 8); 144 | bitPos = uint8(nonce); 145 | } 146 | 147 | /// @notice Checks whether a nonce is taken and sets the bit at the bit position in the bitmap at the word position 148 | /// @param from The address to use the nonce at 149 | /// @param nonce The nonce to spend 150 | function _useUnorderedNonce(address from, uint256 nonce) internal { 151 | (uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce); 152 | uint256 bit = 1 << bitPos; 153 | uint256 flipped = nonceBitmap[from][wordPos] ^= bit; 154 | 155 | if (flipped & bit == 0) revert InvalidNonce(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/interfaces/IAllowanceTransfer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IEIP712} from "./IEIP712.sol"; 5 | 6 | /// @title AllowanceTransfer 7 | /// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts 8 | /// @dev Requires user's token approval on the Permit2 contract 9 | interface IAllowanceTransfer is IEIP712 { 10 | /// @notice Thrown when an allowance on a token has expired. 11 | /// @param deadline The timestamp at which the allowed amount is no longer valid 12 | error AllowanceExpired(uint256 deadline); 13 | 14 | /// @notice Thrown when an allowance on a token has been depleted. 15 | /// @param amount The maximum amount allowed 16 | error InsufficientAllowance(uint256 amount); 17 | 18 | /// @notice Thrown when too many nonces are invalidated. 19 | error ExcessiveInvalidation(); 20 | 21 | /// @notice Emits an event when the owner successfully invalidates an ordered nonce. 22 | event NonceInvalidation( 23 | address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce 24 | ); 25 | 26 | /// @notice Emits an event when the owner successfully sets permissions on a token for the spender. 27 | event Approval( 28 | address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration 29 | ); 30 | 31 | /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender. 32 | event Permit( 33 | address indexed owner, 34 | address indexed token, 35 | address indexed spender, 36 | uint160 amount, 37 | uint48 expiration, 38 | uint48 nonce 39 | ); 40 | 41 | /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function. 42 | event Lockdown(address indexed owner, address token, address spender); 43 | 44 | /// @notice The permit data for a token 45 | struct PermitDetails { 46 | // ERC20 token address 47 | address token; 48 | // the maximum amount allowed to spend 49 | uint160 amount; 50 | // timestamp at which a spender's token allowances become invalid 51 | uint48 expiration; 52 | // an incrementing value indexed per owner,token,and spender for each signature 53 | uint48 nonce; 54 | } 55 | 56 | /// @notice The permit message signed for a single token allowance 57 | struct PermitSingle { 58 | // the permit data for a single token alownce 59 | PermitDetails details; 60 | // address permissioned on the allowed tokens 61 | address spender; 62 | // deadline on the permit signature 63 | uint256 sigDeadline; 64 | } 65 | 66 | /// @notice The permit message signed for multiple token allowances 67 | struct PermitBatch { 68 | // the permit data for multiple token allowances 69 | PermitDetails[] details; 70 | // address permissioned on the allowed tokens 71 | address spender; 72 | // deadline on the permit signature 73 | uint256 sigDeadline; 74 | } 75 | 76 | /// @notice The saved permissions 77 | /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message 78 | /// @dev Setting amount to type(uint160).max sets an unlimited approval 79 | struct PackedAllowance { 80 | // amount allowed 81 | uint160 amount; 82 | // permission expiry 83 | uint48 expiration; 84 | // an incrementing value indexed per owner,token,and spender for each signature 85 | uint48 nonce; 86 | } 87 | 88 | /// @notice A token spender pair. 89 | struct TokenSpenderPair { 90 | // the token the spender is approved 91 | address token; 92 | // the spender address 93 | address spender; 94 | } 95 | 96 | /// @notice Details for a token transfer. 97 | struct AllowanceTransferDetails { 98 | // the owner of the token 99 | address from; 100 | // the recipient of the token 101 | address to; 102 | // the amount of the token 103 | uint160 amount; 104 | // the token to be transferred 105 | address token; 106 | } 107 | 108 | /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval. 109 | /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress] 110 | /// @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. 111 | function allowance(address user, address token, address spender) 112 | external 113 | view 114 | returns (uint160 amount, uint48 expiration, uint48 nonce); 115 | 116 | /// @notice Approves the spender to use up to amount of the specified token up until the expiration 117 | /// @param token The token to approve 118 | /// @param spender The spender address to approve 119 | /// @param amount The approved amount of the token 120 | /// @param expiration The timestamp at which the approval is no longer valid 121 | /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve 122 | /// @dev Setting amount to type(uint160).max sets an unlimited approval 123 | function approve(address token, address spender, uint160 amount, uint48 expiration) external; 124 | 125 | /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature 126 | /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce 127 | /// @param owner The owner of the tokens being approved 128 | /// @param permitSingle Data signed over by the owner specifying the terms of approval 129 | /// @param signature The owner's signature over the permit data 130 | function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external; 131 | 132 | /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature 133 | /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce 134 | /// @param owner The owner of the tokens being approved 135 | /// @param permitBatch Data signed over by the owner specifying the terms of approval 136 | /// @param signature The owner's signature over the permit data 137 | function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external; 138 | 139 | /// @notice Transfer approved tokens from one address to another 140 | /// @param from The address to transfer from 141 | /// @param to The address of the recipient 142 | /// @param amount The amount of the token to transfer 143 | /// @param token The token address to transfer 144 | /// @dev Requires the from address to have approved at least the desired amount 145 | /// of tokens to msg.sender. 146 | function transferFrom(address from, address to, uint160 amount, address token) external; 147 | 148 | /// @notice Transfer approved tokens in a batch 149 | /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers 150 | /// @dev Requires the from addresses to have approved at least the desired amount 151 | /// of tokens to msg.sender. 152 | function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external; 153 | 154 | /// @notice Enables performing a "lockdown" of the sender's Permit2 identity 155 | /// by batch revoking approvals 156 | /// @param approvals Array of approvals to revoke. 157 | function lockdown(TokenSpenderPair[] calldata approvals) external; 158 | 159 | /// @notice Invalidate nonces for a given (token, spender) pair 160 | /// @param token The token to invalidate nonces for 161 | /// @param spender The spender to invalidate nonces for 162 | /// @param newNonce The new nonce to set. Invalidates all nonces less than it. 163 | /// @dev Can't invalidate more than 2**16 nonces per transaction. 164 | function invalidateNonces(address token, address spender, uint48 newNonce) external; 165 | } 166 | -------------------------------------------------------------------------------- /src/interfaces/IDAIPermit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IDAIPermit { 5 | /// @param holder The address of the token owner. 6 | /// @param spender The address of the token spender. 7 | /// @param nonce The owner's nonce, increases at each call to permit. 8 | /// @param expiry The timestamp at which the permit is no longer valid. 9 | /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0. 10 | /// @param v Must produce valid secp256k1 signature from the owner along with r and s. 11 | /// @param r Must produce valid secp256k1 signature from the owner along with v and s. 12 | /// @param s Must produce valid secp256k1 signature from the owner along with r and v. 13 | function permit( 14 | address holder, 15 | address spender, 16 | uint256 nonce, 17 | uint256 expiry, 18 | bool allowed, 19 | uint8 v, 20 | bytes32 r, 21 | bytes32 s 22 | ) external; 23 | } 24 | -------------------------------------------------------------------------------- /src/interfaces/IEIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IEIP712 { 5 | function DOMAIN_SEPARATOR() external view returns (bytes32); 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/IERC1271.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC1271 { 5 | /// @dev Should return whether the signature provided is valid for the provided data 6 | /// @param hash Hash of the data to be signed 7 | /// @param signature Signature byte array associated with _data 8 | /// @return magicValue The bytes4 magic value 0x1626ba7e 9 | function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/IPermit2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {ISignatureTransfer} from "./ISignatureTransfer.sol"; 5 | import {IAllowanceTransfer} from "./IAllowanceTransfer.sol"; 6 | 7 | /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. 8 | /// @dev Users must approve Permit2 before calling any of the transfer functions. 9 | interface IPermit2 is ISignatureTransfer, IAllowanceTransfer { 10 | // IPermit2 unifies the two interfaces so users have maximal flexibility with their approval. 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/ISignatureTransfer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IEIP712} from "./IEIP712.sol"; 5 | 6 | /// @title SignatureTransfer 7 | /// @notice Handles ERC20 token transfers through signature based actions 8 | /// @dev Requires user's token approval on the Permit2 contract 9 | interface ISignatureTransfer is IEIP712 { 10 | /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount 11 | /// @param maxAmount The maximum amount a spender can request to transfer 12 | error InvalidAmount(uint256 maxAmount); 13 | 14 | /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred 15 | /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred 16 | error LengthMismatch(); 17 | 18 | /// @notice Emits an event when the owner successfully invalidates an unordered nonce. 19 | event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask); 20 | 21 | /// @notice The token and amount details for a transfer signed in the permit transfer signature 22 | struct TokenPermissions { 23 | // ERC20 token address 24 | address token; 25 | // the maximum amount that can be spent 26 | uint256 amount; 27 | } 28 | 29 | /// @notice The signed permit message for a single token transfer 30 | struct PermitTransferFrom { 31 | TokenPermissions permitted; 32 | // a unique value for every token owner's signature to prevent signature replays 33 | uint256 nonce; 34 | // deadline on the permit signature 35 | uint256 deadline; 36 | } 37 | 38 | /// @notice Specifies the recipient address and amount for batched transfers. 39 | /// @dev Recipients and amounts correspond to the index of the signed token permissions array. 40 | /// @dev Reverts if the requested amount is greater than the permitted signed amount. 41 | struct SignatureTransferDetails { 42 | // recipient address 43 | address to; 44 | // spender requested amount 45 | uint256 requestedAmount; 46 | } 47 | 48 | /// @notice Used to reconstruct the signed permit message for multiple token transfers 49 | /// @dev Do not need to pass in spender address as it is required that it is msg.sender 50 | /// @dev Note that a user still signs over a spender address 51 | struct PermitBatchTransferFrom { 52 | // the tokens and corresponding amounts permitted for a transfer 53 | TokenPermissions[] permitted; 54 | // a unique value for every token owner's signature to prevent signature replays 55 | uint256 nonce; 56 | // deadline on the permit signature 57 | uint256 deadline; 58 | } 59 | 60 | /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection 61 | /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order 62 | /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce 63 | /// @dev It returns a uint256 bitmap 64 | /// @dev The index, or wordPosition is capped at type(uint248).max 65 | function nonceBitmap(address, uint256) external view returns (uint256); 66 | 67 | /// @notice Transfers a token using a signed permit message 68 | /// @dev Reverts if the requested amount is greater than the permitted signed amount 69 | /// @param permit The permit data signed over by the owner 70 | /// @param owner The owner of the tokens to transfer 71 | /// @param transferDetails The spender's requested transfer details for the permitted token 72 | /// @param signature The signature to verify 73 | function permitTransferFrom( 74 | PermitTransferFrom memory permit, 75 | SignatureTransferDetails calldata transferDetails, 76 | address owner, 77 | bytes calldata signature 78 | ) external; 79 | 80 | /// @notice Transfers a token using a signed permit message 81 | /// @notice Includes extra data provided by the caller to verify signature over 82 | /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition 83 | /// @dev Reverts if the requested amount is greater than the permitted signed amount 84 | /// @param permit The permit data signed over by the owner 85 | /// @param owner The owner of the tokens to transfer 86 | /// @param transferDetails The spender's requested transfer details for the permitted token 87 | /// @param witness Extra data to include when checking the user signature 88 | /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash 89 | /// @param signature The signature to verify 90 | function permitWitnessTransferFrom( 91 | PermitTransferFrom memory permit, 92 | SignatureTransferDetails calldata transferDetails, 93 | address owner, 94 | bytes32 witness, 95 | string calldata witnessTypeString, 96 | bytes calldata signature 97 | ) external; 98 | 99 | /// @notice Transfers multiple tokens using a signed permit message 100 | /// @param permit The permit data signed over by the owner 101 | /// @param owner The owner of the tokens to transfer 102 | /// @param transferDetails Specifies the recipient and requested amount for the token transfer 103 | /// @param signature The signature to verify 104 | function permitTransferFrom( 105 | PermitBatchTransferFrom memory permit, 106 | SignatureTransferDetails[] calldata transferDetails, 107 | address owner, 108 | bytes calldata signature 109 | ) external; 110 | 111 | /// @notice Transfers multiple tokens using a signed permit message 112 | /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition 113 | /// @notice Includes extra data provided by the caller to verify signature over 114 | /// @param permit The permit data signed over by the owner 115 | /// @param owner The owner of the tokens to transfer 116 | /// @param transferDetails Specifies the recipient and requested amount for the token transfer 117 | /// @param witness Extra data to include when checking the user signature 118 | /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash 119 | /// @param signature The signature to verify 120 | function permitWitnessTransferFrom( 121 | PermitBatchTransferFrom memory permit, 122 | SignatureTransferDetails[] calldata transferDetails, 123 | address owner, 124 | bytes32 witness, 125 | string calldata witnessTypeString, 126 | bytes calldata signature 127 | ) external; 128 | 129 | /// @notice Invalidates the bits specified in mask for the bitmap at the word position 130 | /// @dev The wordPos is maxed at type(uint248).max 131 | /// @param wordPos A number to index the nonceBitmap at 132 | /// @param mask A bitmap masked against msg.sender's current bitmap at the word position 133 | function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external; 134 | } 135 | -------------------------------------------------------------------------------- /src/libraries/Allowance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; 5 | 6 | library Allowance { 7 | // note if the expiration passed is 0, then it the approval set to the block.timestamp 8 | uint256 private constant BLOCK_TIMESTAMP_EXPIRATION = 0; 9 | 10 | /// @notice Sets the allowed amount, expiry, and nonce of the spender's permissions on owner's token. 11 | /// @dev Nonce is incremented. 12 | /// @dev If the inputted expiration is 0, the stored expiration is set to block.timestamp 13 | function updateAll( 14 | IAllowanceTransfer.PackedAllowance storage allowed, 15 | uint160 amount, 16 | uint48 expiration, 17 | uint48 nonce 18 | ) internal { 19 | uint48 storedNonce; 20 | unchecked { 21 | storedNonce = nonce + 1; 22 | } 23 | 24 | uint48 storedExpiration = expiration == BLOCK_TIMESTAMP_EXPIRATION ? uint48(block.timestamp) : expiration; 25 | 26 | uint256 word = pack(amount, storedExpiration, storedNonce); 27 | assembly { 28 | sstore(allowed.slot, word) 29 | } 30 | } 31 | 32 | /// @notice Sets the allowed amount and expiry of the spender's permissions on owner's token. 33 | /// @dev Nonce does not need to be incremented. 34 | function updateAmountAndExpiration( 35 | IAllowanceTransfer.PackedAllowance storage allowed, 36 | uint160 amount, 37 | uint48 expiration 38 | ) internal { 39 | // If the inputted expiration is 0, the allowance only lasts the duration of the block. 40 | allowed.expiration = expiration == 0 ? uint48(block.timestamp) : expiration; 41 | allowed.amount = amount; 42 | } 43 | 44 | /// @notice Computes the packed slot of the amount, expiration, and nonce that make up PackedAllowance 45 | function pack(uint160 amount, uint48 expiration, uint48 nonce) internal pure returns (uint256 word) { 46 | word = (uint256(nonce) << 208) | uint256(expiration) << 160 | amount; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/libraries/Permit2Lib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | 6 | import {IDAIPermit} from "../interfaces/IDAIPermit.sol"; 7 | import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; 8 | import {SafeCast160} from "./SafeCast160.sol"; 9 | 10 | /// @title Permit2Lib 11 | /// @notice Enables efficient transfers and EIP-2612/DAI 12 | /// permits for any token by falling back to Permit2. 13 | library Permit2Lib { 14 | using SafeCast160 for uint256; 15 | /*////////////////////////////////////////////////////////////// 16 | CONSTANTS 17 | //////////////////////////////////////////////////////////////*/ 18 | 19 | /// @dev The unique EIP-712 domain domain separator for the DAI token contract. 20 | bytes32 internal constant DAI_DOMAIN_SEPARATOR = 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; 21 | 22 | /// @dev The address for the WETH9 contract on Ethereum mainnet, encoded as a bytes32. 23 | bytes32 internal constant WETH9_ADDRESS = 0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2; 24 | 25 | /// @dev The address of the Permit2 contract the library will use. 26 | IAllowanceTransfer internal constant PERMIT2 = 27 | IAllowanceTransfer(address(0x000000000022D473030F116dDEE9F6B43aC78BA3)); 28 | 29 | /// @notice Transfer a given amount of tokens from one user to another. 30 | /// @param token The token to transfer. 31 | /// @param from The user to transfer from. 32 | /// @param to The user to transfer to. 33 | /// @param amount The amount to transfer. 34 | function transferFrom2(ERC20 token, address from, address to, uint256 amount) internal { 35 | // Generate calldata for a standard transferFrom call. 36 | bytes memory inputData = abi.encodeCall(ERC20.transferFrom, (from, to, amount)); 37 | 38 | bool success; // Call the token contract as normal, capturing whether it succeeded. 39 | assembly { 40 | success := 41 | and( 42 | // Set success to whether the call reverted, if not we check it either 43 | // returned exactly 1 (can't just be non-zero data), or had no return data. 44 | or(eq(mload(0), 1), iszero(returndatasize())), 45 | // Counterintuitively, this call() must be positioned after the or() in the 46 | // surrounding and() because and() evaluates its arguments from right to left. 47 | // We use 0 and 32 to copy up to 32 bytes of return data into the first slot of scratch space. 48 | call(gas(), token, 0, add(inputData, 32), mload(inputData), 0, 32) 49 | ) 50 | } 51 | 52 | // We'll fall back to using Permit2 if calling transferFrom on the token directly reverted. 53 | if (!success) PERMIT2.transferFrom(from, to, amount.toUint160(), address(token)); 54 | } 55 | 56 | /*////////////////////////////////////////////////////////////// 57 | PERMIT LOGIC 58 | //////////////////////////////////////////////////////////////*/ 59 | 60 | /// @notice Permit a user to spend a given amount of 61 | /// another user's tokens via native EIP-2612 permit if possible, falling 62 | /// back to Permit2 if native permit fails or is not implemented on the token. 63 | /// @param token The token to permit spending. 64 | /// @param owner The user to permit spending from. 65 | /// @param spender The user to permit spending to. 66 | /// @param amount The amount to permit spending. 67 | /// @param deadline The timestamp after which the signature is no longer valid. 68 | /// @param v Must produce valid secp256k1 signature from the owner along with r and s. 69 | /// @param r Must produce valid secp256k1 signature from the owner along with v and s. 70 | /// @param s Must produce valid secp256k1 signature from the owner along with r and v. 71 | function permit2( 72 | ERC20 token, 73 | address owner, 74 | address spender, 75 | uint256 amount, 76 | uint256 deadline, 77 | uint8 v, 78 | bytes32 r, 79 | bytes32 s 80 | ) internal { 81 | // Generate calldata for a call to DOMAIN_SEPARATOR on the token. 82 | bytes memory inputData = abi.encodeWithSelector(ERC20.DOMAIN_SEPARATOR.selector); 83 | 84 | bool success; // Call the token contract as normal, capturing whether it succeeded. 85 | bytes32 domainSeparator; // If the call succeeded, we'll capture the return value here. 86 | 87 | assembly { 88 | // If the token is WETH9, we know it doesn't have a DOMAIN_SEPARATOR, and we'll skip this step. 89 | // We make sure to mask the token address as its higher order bits aren't guaranteed to be clean. 90 | if iszero(eq(and(token, 0xffffffffffffffffffffffffffffffffffffffff), WETH9_ADDRESS)) { 91 | success := 92 | and( 93 | // Should resolve false if its not 32 bytes or its first word is 0. 94 | and(iszero(iszero(mload(0))), eq(returndatasize(), 32)), 95 | // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. 96 | // Counterintuitively, this call must be positioned second to the and() call in the 97 | // surrounding and() call or else returndatasize() will be zero during the computation. 98 | // We send a maximum of 5000 gas to prevent tokens with fallbacks from using a ton of gas. 99 | // which should be plenty to allow tokens to fetch their DOMAIN_SEPARATOR from storage, etc. 100 | staticcall(5000, token, add(inputData, 32), mload(inputData), 0, 32) 101 | ) 102 | 103 | domainSeparator := mload(0) // Copy the return value into the domainSeparator variable. 104 | } 105 | } 106 | 107 | // If the call to DOMAIN_SEPARATOR succeeded, try using permit on the token. 108 | if (success) { 109 | // We'll use DAI's special permit if it's DOMAIN_SEPARATOR matches, 110 | // otherwise we'll just encode a call to the standard permit function. 111 | inputData = domainSeparator == DAI_DOMAIN_SEPARATOR 112 | ? abi.encodeCall(IDAIPermit.permit, (owner, spender, token.nonces(owner), deadline, true, v, r, s)) 113 | : abi.encodeCall(ERC20.permit, (owner, spender, amount, deadline, v, r, s)); 114 | 115 | assembly { 116 | success := call(gas(), token, 0, add(inputData, 32), mload(inputData), 0, 0) 117 | } 118 | } 119 | 120 | if (!success) { 121 | // If the initial DOMAIN_SEPARATOR call on the token failed or a 122 | // subsequent call to permit failed, fall back to using Permit2. 123 | simplePermit2(token, owner, spender, amount, deadline, v, r, s); 124 | } 125 | } 126 | 127 | /// @notice Simple unlimited permit on the Permit2 contract. 128 | /// @param token The token to permit spending. 129 | /// @param owner The user to permit spending from. 130 | /// @param spender The user to permit spending to. 131 | /// @param amount The amount to permit spending. 132 | /// @param deadline The timestamp after which the signature is no longer valid. 133 | /// @param v Must produce valid secp256k1 signature from the owner along with r and s. 134 | /// @param r Must produce valid secp256k1 signature from the owner along with v and s. 135 | /// @param s Must produce valid secp256k1 signature from the owner along with r and v. 136 | function simplePermit2( 137 | ERC20 token, 138 | address owner, 139 | address spender, 140 | uint256 amount, 141 | uint256 deadline, 142 | uint8 v, 143 | bytes32 r, 144 | bytes32 s 145 | ) internal { 146 | (,, uint48 nonce) = PERMIT2.allowance(owner, address(token), spender); 147 | 148 | PERMIT2.permit( 149 | owner, 150 | IAllowanceTransfer.PermitSingle({ 151 | details: IAllowanceTransfer.PermitDetails({ 152 | token: address(token), 153 | amount: amount.toUint160(), 154 | // Use an unlimited expiration because it most 155 | // closely mimics how a standard approval works. 156 | expiration: type(uint48).max, 157 | nonce: nonce 158 | }), 159 | spender: spender, 160 | sigDeadline: deadline 161 | }), 162 | bytes.concat(r, s, bytes1(v)) 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/libraries/PermitHash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; 5 | import {ISignatureTransfer} from "../interfaces/ISignatureTransfer.sol"; 6 | 7 | library PermitHash { 8 | bytes32 public constant _PERMIT_DETAILS_TYPEHASH = 9 | keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); 10 | 11 | bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256( 12 | "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" 13 | ); 14 | 15 | bytes32 public constant _PERMIT_BATCH_TYPEHASH = keccak256( 16 | "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" 17 | ); 18 | 19 | bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); 20 | 21 | bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256( 22 | "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" 23 | ); 24 | 25 | bytes32 public constant _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = keccak256( 26 | "PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" 27 | ); 28 | 29 | string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 amount)"; 30 | 31 | string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB = 32 | "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,"; 33 | 34 | string public constant _PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB = 35 | "PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,"; 36 | 37 | function hash(IAllowanceTransfer.PermitSingle memory permitSingle) internal pure returns (bytes32) { 38 | bytes32 permitHash = _hashPermitDetails(permitSingle.details); 39 | return 40 | keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)); 41 | } 42 | 43 | function hash(IAllowanceTransfer.PermitBatch memory permitBatch) internal pure returns (bytes32) { 44 | uint256 numPermits = permitBatch.details.length; 45 | bytes32[] memory permitHashes = new bytes32[](numPermits); 46 | for (uint256 i = 0; i < numPermits; ++i) { 47 | permitHashes[i] = _hashPermitDetails(permitBatch.details[i]); 48 | } 49 | return keccak256( 50 | abi.encode( 51 | _PERMIT_BATCH_TYPEHASH, 52 | keccak256(abi.encodePacked(permitHashes)), 53 | permitBatch.spender, 54 | permitBatch.sigDeadline 55 | ) 56 | ); 57 | } 58 | 59 | function hash(ISignatureTransfer.PermitTransferFrom memory permit) internal view returns (bytes32) { 60 | bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); 61 | return keccak256( 62 | abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline) 63 | ); 64 | } 65 | 66 | function hash(ISignatureTransfer.PermitBatchTransferFrom memory permit) internal view returns (bytes32) { 67 | uint256 numPermitted = permit.permitted.length; 68 | bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); 69 | 70 | for (uint256 i = 0; i < numPermitted; ++i) { 71 | tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]); 72 | } 73 | 74 | return keccak256( 75 | abi.encode( 76 | _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH, 77 | keccak256(abi.encodePacked(tokenPermissionHashes)), 78 | msg.sender, 79 | permit.nonce, 80 | permit.deadline 81 | ) 82 | ); 83 | } 84 | 85 | function hashWithWitness( 86 | ISignatureTransfer.PermitTransferFrom memory permit, 87 | bytes32 witness, 88 | string calldata witnessTypeString 89 | ) internal view returns (bytes32) { 90 | bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString)); 91 | 92 | bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); 93 | return keccak256(abi.encode(typeHash, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline, witness)); 94 | } 95 | 96 | function hashWithWitness( 97 | ISignatureTransfer.PermitBatchTransferFrom memory permit, 98 | bytes32 witness, 99 | string calldata witnessTypeString 100 | ) internal view returns (bytes32) { 101 | bytes32 typeHash = 102 | keccak256(abi.encodePacked(_PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB, witnessTypeString)); 103 | 104 | uint256 numPermitted = permit.permitted.length; 105 | bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); 106 | 107 | for (uint256 i = 0; i < numPermitted; ++i) { 108 | tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]); 109 | } 110 | 111 | return keccak256( 112 | abi.encode( 113 | typeHash, 114 | keccak256(abi.encodePacked(tokenPermissionHashes)), 115 | msg.sender, 116 | permit.nonce, 117 | permit.deadline, 118 | witness 119 | ) 120 | ); 121 | } 122 | 123 | function _hashPermitDetails(IAllowanceTransfer.PermitDetails memory details) private pure returns (bytes32) { 124 | return keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, details)); 125 | } 126 | 127 | function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory permitted) 128 | private 129 | pure 130 | returns (bytes32) 131 | { 132 | return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permitted)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/libraries/SafeCast160.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | library SafeCast160 { 5 | /// @notice Thrown when a valude greater than type(uint160).max is cast to uint160 6 | error UnsafeCast(); 7 | 8 | /// @notice Safely casts uint256 to uint160 9 | /// @param value The uint256 to be cast 10 | function toUint160(uint256 value) internal pure returns (uint160) { 11 | if (value > type(uint160).max) revert UnsafeCast(); 12 | return uint160(value); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/libraries/SignatureVerification.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC1271} from "../interfaces/IERC1271.sol"; 5 | 6 | library SignatureVerification { 7 | /// @notice Thrown when the passed in signature is not a valid length 8 | error InvalidSignatureLength(); 9 | 10 | /// @notice Thrown when the recovered signer is equal to the zero address 11 | error InvalidSignature(); 12 | 13 | /// @notice Thrown when the recovered signer does not equal the claimedSigner 14 | error InvalidSigner(); 15 | 16 | /// @notice Thrown when the recovered contract signature is incorrect 17 | error InvalidContractSignature(); 18 | 19 | bytes32 constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); 20 | 21 | function verify(bytes calldata signature, bytes32 hash, address claimedSigner) internal view { 22 | bytes32 r; 23 | bytes32 s; 24 | uint8 v; 25 | 26 | if (claimedSigner.code.length == 0) { 27 | if (signature.length == 65) { 28 | (r, s) = abi.decode(signature, (bytes32, bytes32)); 29 | v = uint8(signature[64]); 30 | } else if (signature.length == 64) { 31 | // EIP-2098 32 | bytes32 vs; 33 | (r, vs) = abi.decode(signature, (bytes32, bytes32)); 34 | s = vs & UPPER_BIT_MASK; 35 | v = uint8(uint256(vs >> 255)) + 27; 36 | } else { 37 | revert InvalidSignatureLength(); 38 | } 39 | address signer = ecrecover(hash, v, r, s); 40 | if (signer == address(0)) revert InvalidSignature(); 41 | if (signer != claimedSigner) revert InvalidSigner(); 42 | } else { 43 | bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature); 44 | if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/AllowanceTransferInvariants.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.17; 2 | 3 | import {Test} from "forge-std/Test.sol"; 4 | import {StdInvariant} from "forge-std/StdInvariant.sol"; 5 | import {TokenProvider} from "./utils/TokenProvider.sol"; 6 | import {Permit2} from "../src/Permit2.sol"; 7 | import {IAllowanceTransfer} from "../src/interfaces/IAllowanceTransfer.sol"; 8 | import {SignatureVerification} from "../src/libraries/SignatureVerification.sol"; 9 | import {PermitSignature} from "./utils/PermitSignature.sol"; 10 | import {MockERC20} from "./mocks/MockERC20.sol"; 11 | import {Permitter} from "./actors/Permitter.sol"; 12 | import {Spender} from "./actors/Spender.sol"; 13 | 14 | contract Runner { 15 | Permit2 public permit2; 16 | Permitter public permitter1; 17 | Permitter public permitter2; 18 | Spender public spender1; 19 | Spender public spender2; 20 | MockERC20 public token; 21 | uint256 private index; 22 | 23 | address[] owners; 24 | IAllowanceTransfer.PermitSingle[] permits; 25 | bytes[] sigs; 26 | 27 | constructor(Permit2 _permit2) { 28 | permit2 = _permit2; 29 | token = new MockERC20("TEST", "test", 18); 30 | permitter1 = new Permitter(_permit2, token, 0x01); 31 | permitter2 = new Permitter(_permit2, token, 0x02); 32 | spender1 = new Spender(_permit2, token); 33 | spender2 = new Spender(_permit2, token); 34 | } 35 | 36 | function createPermit(uint128 amount, bool firstPermitter, bool firstSpender) public { 37 | Permitter permitter = firstPermitter ? permitter1 : permitter2; 38 | Spender spender = firstSpender ? spender1 : spender2; 39 | (IAllowanceTransfer.PermitSingle memory permit, bytes memory sig) = 40 | permitter.createPermit(amount, address(spender)); 41 | permits.push(permit); 42 | sigs.push(sig); 43 | owners.push(address(permitter.signer())); 44 | } 45 | 46 | function approve(uint128 amount, bool firstPermitter, bool firstSpender) public { 47 | Permitter permitter = firstPermitter ? permitter1 : permitter2; 48 | Spender spender = firstSpender ? spender1 : spender2; 49 | permitter.approve(amount, address(spender)); 50 | } 51 | 52 | // always uses permits in order for nonces 53 | function usePermit() public { 54 | if (permits.length <= index) { 55 | return; 56 | } 57 | permit2.permit(owners[index], permits[index], sigs[index]); 58 | index++; 59 | } 60 | 61 | function spendPermit(uint160 amount, bool firstPermitter, bool firstSpender) public { 62 | Permitter permitter = firstPermitter ? permitter1 : permitter2; 63 | Spender spender = firstSpender ? spender1 : spender2; 64 | spender.spendPermit(amount, address(permitter.signer())); 65 | } 66 | 67 | function amountPermitted() public view returns (uint256) { 68 | return permitter1.amountPermitted() + permitter2.amountPermitted(); 69 | } 70 | 71 | function amountSpent() public view returns (uint256) { 72 | return spender1.amountSpent() + spender2.amountSpent(); 73 | } 74 | 75 | function balanceOf(address who) public view returns (uint256) { 76 | return token.balanceOf(who); 77 | } 78 | } 79 | 80 | contract AllowanceTransferInvariants is StdInvariant, Test { 81 | Permit2 permit2; 82 | Runner runner; 83 | MockERC20 token; 84 | 85 | function setUp() public { 86 | permit2 = new Permit2(); 87 | runner = new Runner(permit2); 88 | 89 | targetContract(address(runner)); 90 | targetSender(address(vm.addr(0xb0b0))); 91 | } 92 | 93 | function invariant_spendNeverExceedsPermit() public { 94 | uint256 permitted = runner.amountPermitted(); 95 | uint256 spent = runner.amountSpent(); 96 | assertGe(permitted, spent); 97 | } 98 | 99 | function invariant_balanceEqualsSpent() public { 100 | uint256 spent = runner.amountSpent(); 101 | assertEq(runner.balanceOf(address(runner.spender1())) + runner.balanceOf(address(runner.spender2())), spent); 102 | } 103 | 104 | function invariant_permit2NeverHoldsBalance() public { 105 | assertEq(runner.balanceOf(address(permit2)), 0); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/AllowanceUnitTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {MockPermit2} from "./mocks/MockPermit2.sol"; 6 | import {TokenProvider} from "./utils/TokenProvider.sol"; 7 | import {Allowance} from "../src/libraries/Allowance.sol"; 8 | 9 | contract AllowanceUnitTest is Test, TokenProvider { 10 | MockPermit2 permit2; 11 | 12 | address from = address(0xBEEE); 13 | address spender = address(0xBBBB); 14 | 15 | function setUp() public { 16 | permit2 = new MockPermit2(); 17 | initializeERC20Tokens(); 18 | } 19 | 20 | function testUpdateAmountExpirationRandomly(uint160 amount, uint48 expiration) public { 21 | address token = address(token1); 22 | 23 | (,, uint48 nonce) = permit2.allowance(from, token, spender); 24 | 25 | permit2.mockUpdateAmountAndExpiration(from, token, spender, amount, expiration); 26 | 27 | uint48 timestampAfterUpdate = expiration == 0 ? uint48(block.timestamp) : expiration; 28 | 29 | (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, token, spender); 30 | assertEq(amount, amount1); 31 | assertEq(timestampAfterUpdate, expiration1); 32 | /// nonce shouldnt change 33 | assertEq(nonce, nonce1); 34 | } 35 | 36 | function testUpdateAllRandomly(uint160 amount, uint48 expiration, uint48 nonce) public { 37 | // there is overflow since we increment the nonce by 1 38 | // we assume we will never be able to reach 2**48 39 | vm.assume(nonce < type(uint48).max); 40 | 41 | address token = address(token1); 42 | 43 | permit2.mockUpdateAll(from, token, spender, amount, expiration, nonce); 44 | 45 | uint48 nonceAfterUpdate = nonce + 1; 46 | uint48 timestampAfterUpdate = expiration == 0 ? uint48(block.timestamp) : expiration; 47 | 48 | (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, token, spender); 49 | 50 | assertEq(amount, amount1); 51 | assertEq(timestampAfterUpdate, expiration1); 52 | assertEq(nonceAfterUpdate, nonce1); 53 | } 54 | 55 | function testPackAndUnpack(uint160 amount, uint48 expiration, uint48 nonce) public { 56 | // pack some numbers 57 | uint256 word = Allowance.pack(amount, expiration, nonce); 58 | 59 | // store the raw word 60 | permit2.doStore(from, address(token1), spender, word); 61 | 62 | // load it as a packed allowance 63 | (uint160 amount1, uint48 expiration1, uint48 nonce1) = permit2.allowance(from, address(token1), spender); 64 | assertEq(amount, amount1); 65 | assertEq(expiration, expiration1); 66 | assertEq(nonce, nonce1); 67 | 68 | // get the stored word 69 | uint256 word1 = permit2.getStore(from, address(token1), spender); 70 | assertEq(word, word1); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/CompactSignature.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {PermitSignature} from "./utils/PermitSignature.sol"; 6 | 7 | contract CompactSignature is Test, PermitSignature { 8 | /// test cases pulled from EIP-2098 9 | function testCompactSignature27() public { 10 | bytes32 r = 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90; 11 | bytes32 s = 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064; 12 | uint8 v = 27; 13 | 14 | bytes32 vs; 15 | (r, vs) = _getCompactSignature(v, r, s); 16 | 17 | assertEq(r, 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90); 18 | assertEq(vs, 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064); 19 | } 20 | 21 | function testCompactSignature28() public { 22 | bytes32 r = 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76; 23 | bytes32 s = 0x139c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793; 24 | uint8 v = 28; 25 | 26 | bytes32 vs; 27 | (r, vs) = _getCompactSignature(v, r, s); 28 | 29 | assertEq(r, 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76); 30 | assertEq(vs, 0x939c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/EIP712.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {Permit2} from "../src/Permit2.sol"; 6 | 7 | // forge test --match-contract EIP712 8 | contract EIP712Test is Test { 9 | bytes32 private constant TYPE_HASH = 10 | keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 11 | bytes32 private constant NAME_HASH = keccak256("Permit2"); 12 | 13 | Permit2 permit2; 14 | 15 | function setUp() public { 16 | permit2 = new Permit2(); 17 | } 18 | 19 | function testDomainSeparator() public { 20 | bytes32 expectedDomainSeparator = keccak256(abi.encode(TYPE_HASH, NAME_HASH, block.chainid, address(permit2))); 21 | 22 | assertEq(permit2.DOMAIN_SEPARATOR(), expectedDomainSeparator); 23 | } 24 | 25 | function testDomainSeparatorAfterFork() public { 26 | bytes32 beginningSeparator = permit2.DOMAIN_SEPARATOR(); 27 | uint256 newChainId = block.chainid + 1; 28 | vm.chainId(newChainId); 29 | assertTrue(permit2.DOMAIN_SEPARATOR() != beginningSeparator); 30 | 31 | bytes32 expectedDomainSeparator = keccak256(abi.encode(TYPE_HASH, NAME_HASH, newChainId, address(permit2))); 32 | assertEq(permit2.DOMAIN_SEPARATOR(), expectedDomainSeparator); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/NonceBitmap.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {SafeERC20, IERC20, IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | import {MockPermit2} from "./mocks/MockPermit2.sol"; 7 | import {InvalidNonce} from "../src/PermitErrors.sol"; 8 | 9 | contract NonceBitmapTest is Test { 10 | MockPermit2 permit2; 11 | 12 | function setUp() public { 13 | permit2 = new MockPermit2(); 14 | } 15 | 16 | function testLowNonces() public { 17 | permit2.useUnorderedNonce(address(this), 5); 18 | permit2.useUnorderedNonce(address(this), 0); 19 | permit2.useUnorderedNonce(address(this), 1); 20 | 21 | vm.expectRevert(InvalidNonce.selector); 22 | permit2.useUnorderedNonce(address(this), 1); 23 | vm.expectRevert(InvalidNonce.selector); 24 | permit2.useUnorderedNonce(address(this), 5); 25 | vm.expectRevert(InvalidNonce.selector); 26 | permit2.useUnorderedNonce(address(this), 0); 27 | permit2.useUnorderedNonce(address(this), 4); 28 | } 29 | 30 | function testNonceWordBoundary() public { 31 | permit2.useUnorderedNonce(address(this), 255); 32 | permit2.useUnorderedNonce(address(this), 256); 33 | 34 | vm.expectRevert(InvalidNonce.selector); 35 | permit2.useUnorderedNonce(address(this), 255); 36 | vm.expectRevert(InvalidNonce.selector); 37 | permit2.useUnorderedNonce(address(this), 256); 38 | } 39 | 40 | function testHighNonces() public { 41 | permit2.useUnorderedNonce(address(this), 2 ** 240); 42 | permit2.useUnorderedNonce(address(this), 2 ** 240 + 1); 43 | 44 | vm.expectRevert(InvalidNonce.selector); 45 | permit2.useUnorderedNonce(address(this), 2 ** 240); 46 | vm.expectRevert(InvalidNonce.selector); 47 | permit2.useUnorderedNonce(address(this), 2 ** 240 + 1); 48 | } 49 | 50 | function testInvalidateFullWord() public { 51 | permit2.invalidateUnorderedNonces(0, 2 ** 256 - 1); 52 | 53 | vm.expectRevert(InvalidNonce.selector); 54 | permit2.useUnorderedNonce(address(this), 0); 55 | vm.expectRevert(InvalidNonce.selector); 56 | permit2.useUnorderedNonce(address(this), 1); 57 | vm.expectRevert(InvalidNonce.selector); 58 | permit2.useUnorderedNonce(address(this), 254); 59 | vm.expectRevert(InvalidNonce.selector); 60 | permit2.useUnorderedNonce(address(this), 255); 61 | permit2.useUnorderedNonce(address(this), 256); 62 | } 63 | 64 | function testInvalidateNonzeroWord() public { 65 | permit2.invalidateUnorderedNonces(1, 2 ** 256 - 1); 66 | 67 | permit2.useUnorderedNonce(address(this), 0); 68 | permit2.useUnorderedNonce(address(this), 254); 69 | permit2.useUnorderedNonce(address(this), 255); 70 | vm.expectRevert(InvalidNonce.selector); 71 | permit2.useUnorderedNonce(address(this), 256); 72 | vm.expectRevert(InvalidNonce.selector); 73 | permit2.useUnorderedNonce(address(this), 511); 74 | permit2.useUnorderedNonce(address(this), 512); 75 | } 76 | 77 | function testUsingNonceTwiceFails(uint256 nonce) public { 78 | permit2.useUnorderedNonce(address(this), nonce); 79 | vm.expectRevert(InvalidNonce.selector); 80 | permit2.useUnorderedNonce(address(this), nonce); 81 | } 82 | 83 | function testUseTwoRandomNonces(uint256 first, uint256 second) public { 84 | permit2.useUnorderedNonce(address(this), first); 85 | if (first == second) { 86 | vm.expectRevert(InvalidNonce.selector); 87 | permit2.useUnorderedNonce(address(this), second); 88 | } else { 89 | permit2.useUnorderedNonce(address(this), second); 90 | } 91 | } 92 | 93 | function testInvalidateNoncesRandomly(uint248 wordPos, uint256 mask) public { 94 | permit2.invalidateUnorderedNonces(wordPos, mask); 95 | assertEq(mask, permit2.nonceBitmap(address(this), wordPos)); 96 | } 97 | 98 | function testInvalidateTwoNoncesRandomly(uint248 wordPos, uint256 startBitmap, uint256 mask) public { 99 | permit2.invalidateUnorderedNonces(wordPos, startBitmap); 100 | assertEq(startBitmap, permit2.nonceBitmap(address(this), wordPos)); 101 | 102 | // invalidating with the mask changes the original bitmap 103 | uint256 finalBitmap = startBitmap | mask; 104 | permit2.invalidateUnorderedNonces(wordPos, mask); 105 | uint256 savedBitmap = permit2.nonceBitmap(address(this), wordPos); 106 | assertEq(finalBitmap, savedBitmap); 107 | 108 | // invalidating with the same mask should do nothing 109 | permit2.invalidateUnorderedNonces(wordPos, mask); 110 | assertEq(savedBitmap, permit2.nonceBitmap(address(this), wordPos)); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/Permit2Lib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | 6 | import {SafeERC20, IERC20, IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import {DSTestPlus} from "solmate/src/test/utils/DSTestPlus.sol"; 8 | import {MockERC20, ERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 9 | import {Permit2} from "../src/Permit2.sol"; 10 | import {Permit2Lib} from "../src/libraries/Permit2Lib.sol"; 11 | import {MockNonPermitERC20} from "./mocks/MockNonPermitERC20.sol"; 12 | import {PermitSignature} from "./utils/PermitSignature.sol"; 13 | import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; 14 | import {IAllowanceTransfer} from "../src/interfaces/IAllowanceTransfer.sol"; 15 | import {MockPermit2Lib} from "./mocks/MockPermit2Lib.sol"; 16 | import {SafeCast160} from "../src/libraries/SafeCast160.sol"; 17 | import {MockPermitWithSmallDS, MockPermitWithLargerDS} from "./mocks/MockPermitWithDS.sol"; 18 | import {MockNonPermitNonERC20WithDS} from "./mocks/MockNonPermitNonERC20WithDS.sol"; 19 | import {SignatureVerification} from "../src/libraries/SignatureVerification.sol"; 20 | import {MockFallbackERC20} from "./mocks/MockFallbackERC20.sol"; 21 | 22 | contract Permit2LibTest is Test, PermitSignature, GasSnapshot { 23 | bytes32 constant PERMIT_TYPEHASH = 24 | keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); 25 | 26 | bytes32 immutable TOKEN_DOMAIN_SEPARATOR; 27 | bytes32 immutable PERMIT2_DOMAIN_SEPARATOR; 28 | bytes32 immutable TEST_SML_DS_DOMAIN_SEPARATOR; 29 | bytes32 immutable TEST_LG_DS_DOMAIN_SEPARATOR; 30 | 31 | uint256 immutable PK; 32 | address immutable PK_OWNER; 33 | 34 | Permit2 immutable permit2 = Permit2(0x000000000022D473030F116dDEE9F6B43aC78BA3); 35 | 36 | ERC20 immutable weth9Mainnet = ERC20(payable(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2))); 37 | 38 | // Use to test errors in Permit2Lib calls. 39 | MockPermit2Lib immutable permit2Lib = new MockPermit2Lib(); 40 | 41 | MockERC20 immutable token = new MockERC20("Mock Token", "MOCK", 18); 42 | 43 | MockNonPermitERC20 immutable nonPermitToken = new MockNonPermitERC20("Mock NonPermit Token", "MOCK", 18); 44 | MockFallbackERC20 immutable fallbackToken = new MockFallbackERC20("Mock Fallback Token", "MOCK", 18); 45 | MockPermitWithSmallDS immutable lessDSToken = 46 | new MockPermitWithSmallDS("Mock Permit Token Small Domain Sep", "MOCK", 18); 47 | MockPermitWithLargerDS immutable largerDSToken = 48 | new MockPermitWithLargerDS("Mock Permit Token Larger Domain Sep", "MOCK", 18); 49 | MockNonPermitNonERC20WithDS immutable largerNonStandardDSToken = new MockNonPermitNonERC20WithDS(); 50 | 51 | constructor() { 52 | PK = 0xBEEF; 53 | PK_OWNER = vm.addr(PK); 54 | Permit2 tempPermit2 = new Permit2(); 55 | vm.etch(address(permit2), address(tempPermit2).code); 56 | vm.etch(address(weth9Mainnet), address(nonPermitToken).code); 57 | 58 | TOKEN_DOMAIN_SEPARATOR = token.DOMAIN_SEPARATOR(); 59 | PERMIT2_DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); 60 | TEST_SML_DS_DOMAIN_SEPARATOR = lessDSToken.DOMAIN_SEPARATOR(); 61 | TEST_LG_DS_DOMAIN_SEPARATOR = largerDSToken.DOMAIN_SEPARATOR(); 62 | 63 | token.mint(address(this), type(uint128).max); 64 | token.approve(address(this), type(uint128).max); 65 | token.approve(address(permit2), type(uint128).max); 66 | 67 | lessDSToken.mint(address(this), type(uint128).max); 68 | lessDSToken.approve(address(this), type(uint128).max); 69 | lessDSToken.approve(address(permit2), type(uint128).max); 70 | 71 | lessDSToken.mint(PK_OWNER, type(uint128).max); 72 | vm.prank(PK_OWNER); 73 | lessDSToken.approve(address(permit2), type(uint128).max); 74 | 75 | token.mint(PK_OWNER, type(uint128).max); 76 | vm.prank(PK_OWNER); 77 | token.approve(address(permit2), type(uint128).max); 78 | 79 | nonPermitToken.mint(address(this), type(uint128).max); 80 | nonPermitToken.approve(address(this), type(uint128).max); 81 | nonPermitToken.approve(address(permit2), type(uint128).max); 82 | 83 | nonPermitToken.mint(PK_OWNER, type(uint128).max); 84 | vm.prank(PK_OWNER); 85 | nonPermitToken.approve(address(permit2), type(uint128).max); 86 | 87 | MockNonPermitERC20(address(weth9Mainnet)).mint(address(this), type(uint128).max); 88 | weth9Mainnet.approve(address(this), type(uint128).max); 89 | weth9Mainnet.approve(address(permit2), type(uint128).max); 90 | 91 | MockNonPermitERC20(address(weth9Mainnet)).mint(PK_OWNER, type(uint128).max); 92 | vm.prank(PK_OWNER); 93 | weth9Mainnet.approve(address(permit2), type(uint128).max); 94 | 95 | fallbackToken.mint(address(this), type(uint128).max); 96 | fallbackToken.approve(address(this), type(uint128).max); 97 | fallbackToken.approve(address(permit2), type(uint128).max); 98 | 99 | fallbackToken.mint(PK_OWNER, type(uint128).max); 100 | vm.prank(PK_OWNER); 101 | fallbackToken.approve(address(permit2), type(uint128).max); 102 | } 103 | 104 | function setUp() public { 105 | testPermit2Full(); 106 | testPermit2NonPermitFallback(); 107 | testPermit2NonPermitToken(); 108 | testPermit2WETH9Mainnet(); 109 | testStandardPermit(); 110 | } 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | BASIC PERMIT2 BENCHMARKS 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | function testStandardPermit() public { 117 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 118 | PK, 119 | keccak256( 120 | abi.encodePacked( 121 | "\x19\x01", 122 | TOKEN_DOMAIN_SEPARATOR, 123 | keccak256( 124 | abi.encode( 125 | PERMIT_TYPEHASH, PK_OWNER, address(0xB00B), 1e18, token.nonces(PK_OWNER), block.timestamp 126 | ) 127 | ) 128 | ) 129 | ) 130 | ); 131 | 132 | token.permit(PK_OWNER, address(0xB00B), 1e18, block.timestamp, v, r, s); 133 | } 134 | 135 | function testOZSafePermit() public { 136 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 137 | PK, 138 | keccak256( 139 | abi.encodePacked( 140 | "\x19\x01", 141 | TOKEN_DOMAIN_SEPARATOR, 142 | keccak256( 143 | abi.encode( 144 | PERMIT_TYPEHASH, PK_OWNER, address(0xB00B), 1e18, token.nonces(PK_OWNER), block.timestamp 145 | ) 146 | ) 147 | ) 148 | ) 149 | ); 150 | 151 | SafeERC20.safePermit(IERC20Permit(address(token)), PK_OWNER, address(0xB00B), 1e18, block.timestamp, v, r, s); 152 | } 153 | 154 | function testPermit2() public { 155 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 156 | PK, 157 | keccak256( 158 | abi.encodePacked( 159 | "\x19\x01", 160 | TOKEN_DOMAIN_SEPARATOR, 161 | keccak256( 162 | abi.encode( 163 | PERMIT_TYPEHASH, PK_OWNER, address(0xB00B), 1e18, token.nonces(PK_OWNER), block.timestamp 164 | ) 165 | ) 166 | ) 167 | ) 168 | ); 169 | 170 | Permit2Lib.permit2(token, PK_OWNER, address(0xB00B), 1e18, block.timestamp, v, r, s); 171 | } 172 | 173 | function testPermit2InvalidAmount() public { 174 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(nonPermitToken), address(0xCAFE)); 175 | 176 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 177 | details: IAllowanceTransfer.PermitDetails({ 178 | token: address(nonPermitToken), 179 | amount: type(uint160).max, 180 | expiration: type(uint48).max, 181 | nonce: nonce 182 | }), 183 | spender: address(0xCAFE), 184 | sigDeadline: block.timestamp 185 | }); 186 | 187 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 188 | vm.expectRevert(SafeCast160.UnsafeCast.selector); 189 | permit2Lib.permit2(nonPermitToken, PK_OWNER, address(0xCAFE), 2 ** 170, block.timestamp, v, r, s); 190 | } 191 | 192 | /*////////////////////////////////////////////////////////////// 193 | BASIC SIMPLE PERMIT2 BENCHMARKS 194 | //////////////////////////////////////////////////////////////*/ 195 | 196 | function testSimplePermit2InvalidAmount() public { 197 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(nonPermitToken), address(0xCAFE)); 198 | 199 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 200 | details: IAllowanceTransfer.PermitDetails({ 201 | token: address(nonPermitToken), 202 | amount: type(uint160).max, 203 | expiration: type(uint48).max, 204 | nonce: nonce 205 | }), 206 | spender: address(0xCAFE), 207 | sigDeadline: block.timestamp 208 | }); 209 | 210 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 211 | vm.expectRevert(SafeCast160.UnsafeCast.selector); 212 | permit2Lib.simplePermit2(nonPermitToken, PK_OWNER, address(0xCAFE), 2 ** 170, block.timestamp, v, r, s); 213 | } 214 | 215 | function testSimplePermit2() public { 216 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(token), address(0xCAFE)); 217 | 218 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 219 | details: IAllowanceTransfer.PermitDetails({ 220 | token: address(token), 221 | amount: 1e18, 222 | expiration: type(uint48).max, 223 | nonce: nonce 224 | }), 225 | spender: address(0xCAFE), 226 | sigDeadline: block.timestamp 227 | }); 228 | 229 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 230 | 231 | Permit2Lib.simplePermit2(token, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 232 | } 233 | 234 | /*////////////////////////////////////////////////////////////// 235 | BASIC TRANSFERFROM2 BENCHMARKS 236 | //////////////////////////////////////////////////////////////*/ 237 | 238 | function testStandardTransferFrom() public { 239 | token.transferFrom(address(this), address(0xBEEF), 1e18); 240 | } 241 | 242 | function testOZSafeTransferFrom() public { 243 | SafeERC20.safeTransferFrom(IERC20(address(token)), address(this), address(0xB00B), 1e18); 244 | } 245 | 246 | function testTransferFrom2() public { 247 | Permit2Lib.transferFrom2(token, address(this), address(0xB00B), 1e18); 248 | } 249 | 250 | /*////////////////////////////////////////////////////////////// 251 | ADVANCED PERMIT2 BENCHMARKS 252 | //////////////////////////////////////////////////////////////*/ 253 | 254 | function testPermit2Full() public { 255 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(token), address(0xCAFE)); 256 | 257 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 258 | details: IAllowanceTransfer.PermitDetails({ 259 | token: address(token), 260 | amount: 1e18, 261 | expiration: type(uint48).max, 262 | nonce: nonce 263 | }), 264 | spender: address(0xCAFE), 265 | sigDeadline: block.timestamp 266 | }); 267 | 268 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 269 | 270 | Permit2Lib.permit2(token, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 271 | } 272 | 273 | function testPermit2NonPermitToken() public { 274 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(nonPermitToken), address(0xCAFE)); 275 | 276 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 277 | details: IAllowanceTransfer.PermitDetails({ 278 | token: address(nonPermitToken), 279 | amount: 1e18, 280 | expiration: type(uint48).max, 281 | nonce: nonce 282 | }), 283 | spender: address(0xCAFE), 284 | sigDeadline: block.timestamp 285 | }); 286 | 287 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 288 | 289 | Permit2Lib.permit2(nonPermitToken, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 290 | } 291 | 292 | function testPermit2WETH9Mainnet() public { 293 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(weth9Mainnet), address(0xCAFE)); 294 | 295 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 296 | details: IAllowanceTransfer.PermitDetails({ 297 | token: address(weth9Mainnet), 298 | amount: 1e18, 299 | expiration: type(uint48).max, 300 | nonce: nonce 301 | }), 302 | spender: address(0xCAFE), 303 | sigDeadline: block.timestamp 304 | }); 305 | 306 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 307 | 308 | Permit2Lib.permit2(weth9Mainnet, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 309 | } 310 | 311 | function testPermit2NonPermitFallback() public { 312 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(fallbackToken), address(0xCAFE)); 313 | 314 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 315 | details: IAllowanceTransfer.PermitDetails({ 316 | token: address(fallbackToken), 317 | amount: 1e18, 318 | expiration: type(uint48).max, 319 | nonce: nonce 320 | }), 321 | spender: address(0xCAFE), 322 | sigDeadline: block.timestamp 323 | }); 324 | 325 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 326 | 327 | uint256 gas1 = gasleft(); 328 | 329 | Permit2Lib.permit2(ERC20(address(fallbackToken)), PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 330 | 331 | assertLt(gas1 - gasleft(), 50000); // If unbounded the staticcall will consume a wild amount of gas. 332 | } 333 | 334 | function testPermit2SmallerDS() public { 335 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(lessDSToken), address(0xCAFE)); 336 | 337 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 338 | details: IAllowanceTransfer.PermitDetails({ 339 | token: address(lessDSToken), 340 | amount: 1e18, 341 | expiration: type(uint48).max, 342 | nonce: nonce 343 | }), 344 | spender: address(0xCAFE), 345 | sigDeadline: block.timestamp 346 | }); 347 | 348 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 349 | 350 | Permit2Lib.permit2(MockERC20(address(lessDSToken)), PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 351 | (uint160 amount,,) = permit2.allowance(PK_OWNER, address(lessDSToken), address(0xCAFE)); 352 | assertEq(amount, 1e18); 353 | } 354 | 355 | function testPermit2LargerDS() public { 356 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(largerDSToken), address(0xCAFE)); 357 | 358 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 359 | details: IAllowanceTransfer.PermitDetails({ 360 | token: address(largerDSToken), 361 | amount: 1e18, 362 | expiration: type(uint48).max, 363 | nonce: nonce 364 | }), 365 | spender: address(0xCAFE), 366 | sigDeadline: block.timestamp 367 | }); 368 | 369 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 370 | 371 | Permit2Lib.permit2(MockERC20(address(largerDSToken)), PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 372 | (uint160 amount,,) = permit2.allowance(PK_OWNER, address(largerDSToken), address(0xCAFE)); 373 | assertEq(amount, 1e18); 374 | } 375 | 376 | function testPermit2LargerDSRevert() public { 377 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 378 | PK, 379 | keccak256( 380 | abi.encodePacked( 381 | "\x19\x01", 382 | TEST_LG_DS_DOMAIN_SEPARATOR, 383 | keccak256( 384 | abi.encode( 385 | PERMIT_TYPEHASH, PK_OWNER, address(0xB00B), 1e18, token.nonces(PK_OWNER), block.timestamp 386 | ) 387 | ) 388 | ) 389 | ) 390 | ); 391 | // cannot recover signature 392 | vm.expectRevert(SignatureVerification.InvalidSigner.selector); 393 | permit2Lib.permit2(MockERC20(address(largerDSToken)), PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 394 | } 395 | 396 | function testPermit2SmallerDSNoRevert() public { 397 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 398 | PK, 399 | keccak256( 400 | abi.encodePacked( 401 | "\x19\x01", 402 | TEST_SML_DS_DOMAIN_SEPARATOR, 403 | keccak256( 404 | abi.encode( 405 | PERMIT_TYPEHASH, 406 | PK_OWNER, 407 | address(0xB00B), 408 | 1e18, 409 | lessDSToken.nonces(PK_OWNER), 410 | block.timestamp 411 | ) 412 | ) 413 | ) 414 | ) 415 | ); 416 | 417 | Permit2Lib.permit2(lessDSToken, PK_OWNER, address(0xB00B), 1e18, block.timestamp, v, r, s); 418 | } 419 | 420 | /*/////////////////f///////////////////////////////////////////// 421 | ADVANCED TRANSFERFROM BENCHMARKS 422 | //////////////////////////////////////////////////////////////*/ 423 | 424 | function testTransferFrom2Full() public { 425 | vm.startPrank(address(0xCAFE)); 426 | 427 | Permit2Lib.transferFrom2(token, PK_OWNER, address(0xB00B), 1e18); 428 | } 429 | 430 | function testTransferFrom2NonPermitToken() public { 431 | vm.startPrank(address(0xCAFE)); 432 | 433 | Permit2Lib.transferFrom2(nonPermitToken, PK_OWNER, address(0xB00B), 1e18); 434 | } 435 | 436 | function testTransferFrom2InvalidAmount() public { 437 | vm.startPrank(address(0xCAFE)); 438 | vm.expectRevert(SafeCast160.UnsafeCast.selector); 439 | permit2Lib.transferFrom2(nonPermitToken, PK_OWNER, address(0xB00B), 2 ** 170); 440 | } 441 | 442 | /*////////////////////////////////////////////////////////////// 443 | END TO END BENCHMARKS 444 | //////////////////////////////////////////////////////////////*/ 445 | 446 | function testOZSafePermitPlusOZSafeTransferFrom() public { 447 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 448 | PK, 449 | keccak256( 450 | abi.encodePacked( 451 | "\x19\x01", 452 | TOKEN_DOMAIN_SEPARATOR, 453 | keccak256( 454 | abi.encode( 455 | PERMIT_TYPEHASH, PK_OWNER, address(0xB00B), 1e18, token.nonces(PK_OWNER), block.timestamp 456 | ) 457 | ) 458 | ) 459 | ) 460 | ); 461 | 462 | vm.startPrank(address(0xB00B)); 463 | 464 | snapStart("safePermit + safeTransferFrom with an EIP-2612 native token"); 465 | 466 | SafeERC20.safePermit(IERC20Permit(address(token)), PK_OWNER, address(0xB00B), 1e18, block.timestamp, v, r, s); 467 | SafeERC20.safeTransferFrom(IERC20(address(token)), PK_OWNER, address(0xB00B), 1e18); 468 | 469 | snapEnd(); 470 | } 471 | 472 | function testPermit2PlusTransferFrom2() public { 473 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 474 | PK, 475 | keccak256( 476 | abi.encodePacked( 477 | "\x19\x01", 478 | TOKEN_DOMAIN_SEPARATOR, 479 | keccak256( 480 | abi.encode( 481 | PERMIT_TYPEHASH, PK_OWNER, address(0xB00B), 1e18, token.nonces(PK_OWNER), block.timestamp 482 | ) 483 | ) 484 | ) 485 | ) 486 | ); 487 | 488 | vm.startPrank(address(0xB00B)); 489 | 490 | snapStart("permit2 + transferFrom2 with an EIP-2612 native token"); 491 | 492 | Permit2Lib.permit2(token, PK_OWNER, address(0xB00B), 1e18, block.timestamp, v, r, s); 493 | Permit2Lib.transferFrom2(token, PK_OWNER, address(0xB00B), 1e18); 494 | 495 | snapEnd(); 496 | } 497 | 498 | function testPermit2PlusTransferFrom2WithNonPermit() public { 499 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(nonPermitToken), address(0xCAFE)); 500 | 501 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 502 | details: IAllowanceTransfer.PermitDetails({ 503 | token: address(nonPermitToken), 504 | amount: 1e18, 505 | expiration: type(uint48).max, 506 | nonce: nonce 507 | }), 508 | spender: address(0xCAFE), 509 | sigDeadline: block.timestamp 510 | }); 511 | 512 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 513 | 514 | vm.startPrank(address(0xCAFE)); 515 | 516 | snapStart("permit2 + transferFrom2 with a non EIP-2612 native token"); 517 | 518 | Permit2Lib.permit2(nonPermitToken, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 519 | Permit2Lib.transferFrom2(nonPermitToken, PK_OWNER, address(0xB00B), 1e18); 520 | 521 | snapEnd(); 522 | } 523 | 524 | function testPermit2PlusTransferFrom2WithNonPermitFallback() public { 525 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(fallbackToken), address(0xCAFE)); 526 | 527 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 528 | details: IAllowanceTransfer.PermitDetails({ 529 | token: address(fallbackToken), 530 | amount: 1e18, 531 | expiration: type(uint48).max, 532 | nonce: nonce 533 | }), 534 | spender: address(0xCAFE), 535 | sigDeadline: block.timestamp 536 | }); 537 | 538 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 539 | 540 | vm.startPrank(address(0xCAFE)); 541 | 542 | snapStart("permit2 + transferFrom2 with a non EIP-2612 native token with fallback"); 543 | 544 | Permit2Lib.permit2(ERC20(address(fallbackToken)), PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 545 | Permit2Lib.transferFrom2(ERC20(address(fallbackToken)), PK_OWNER, address(0xB00B), 1e18); 546 | 547 | snapEnd(); 548 | } 549 | 550 | function testPermit2PlusTransferFrom2WithWETH9Mainnet() public { 551 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(weth9Mainnet), address(0xCAFE)); 552 | 553 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 554 | details: IAllowanceTransfer.PermitDetails({ 555 | token: address(weth9Mainnet), 556 | amount: 1e18, 557 | expiration: type(uint48).max, 558 | nonce: nonce 559 | }), 560 | spender: address(0xCAFE), 561 | sigDeadline: block.timestamp 562 | }); 563 | 564 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 565 | 566 | vm.startPrank(address(0xCAFE)); 567 | 568 | snapStart("permit2 + transferFrom2 with WETH9's mainnet address"); 569 | 570 | Permit2Lib.permit2(weth9Mainnet, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 571 | Permit2Lib.transferFrom2(weth9Mainnet, PK_OWNER, address(0xB00B), 1e18); 572 | 573 | snapEnd(); 574 | } 575 | 576 | function testSimplePermit2PlusTransferFrom2WithNonPermit() public { 577 | (,, uint48 nonce) = permit2.allowance(PK_OWNER, address(nonPermitToken), address(0xCAFE)); 578 | 579 | IAllowanceTransfer.PermitSingle memory permit = IAllowanceTransfer.PermitSingle({ 580 | details: IAllowanceTransfer.PermitDetails({ 581 | token: address(nonPermitToken), 582 | amount: 1e18, 583 | expiration: type(uint48).max, 584 | nonce: nonce 585 | }), 586 | spender: address(0xCAFE), 587 | sigDeadline: block.timestamp 588 | }); 589 | 590 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, PK, PERMIT2_DOMAIN_SEPARATOR); 591 | 592 | vm.startPrank(address(0xCAFE)); 593 | 594 | snapStart("simplePermit2 + transferFrom2 with a non EIP-2612 native token"); 595 | 596 | Permit2Lib.permit2(nonPermitToken, PK_OWNER, address(0xCAFE), 1e18, block.timestamp, v, r, s); 597 | Permit2Lib.transferFrom2(nonPermitToken, PK_OWNER, address(0xB00B), 1e18); 598 | 599 | snapEnd(); 600 | } 601 | 602 | // mock tests 603 | function testPermit2DSLessToken() public { 604 | bool success = permit2Lib.testPermit2Code(MockERC20(address(lessDSToken))); 605 | assertEq(success, true); 606 | } 607 | 608 | function testPermit2DSMoreToken() public { 609 | bool success = permit2Lib.testPermit2Code(MockERC20(address(largerNonStandardDSToken))); 610 | assertEq(success, false); 611 | } 612 | 613 | function testPermit2DSMore32Token() public { 614 | bool success = permit2Lib.testPermit2Code(MockERC20(address(largerDSToken))); 615 | assertEq(success, false); 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /test/TypehashGeneration.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {PermitSignature} from "./utils/PermitSignature.sol"; 6 | import {PermitHash} from "../src/libraries/PermitHash.sol"; 7 | import {IAllowanceTransfer} from "../src/interfaces/IAllowanceTransfer.sol"; 8 | import {ISignatureTransfer} from "../src/interfaces/ISignatureTransfer.sol"; 9 | import {MockSignatureVerification} from "./mocks/MockSignatureVerification.sol"; 10 | import {MockHash} from "./mocks/MockHash.sol"; 11 | import {AddressBuilder} from "./utils/AddressBuilder.sol"; 12 | import {SignatureVerification} from "../src/libraries/SignatureVerification.sol"; 13 | 14 | contract TypehashGeneration is Test, PermitSignature { 15 | using PermitHash for *; 16 | using AddressBuilder for address[]; 17 | 18 | MockHash mockHash; 19 | 20 | uint256 PRIV_KEY_TEST = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; 21 | address from = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; 22 | 23 | address verifyingContract; 24 | uint256 chainId; 25 | 26 | address token1; 27 | address token2; 28 | address spender; 29 | uint160 amount; 30 | uint48 expiration; 31 | uint256 sigDeadline; 32 | uint48 nonce; 33 | 34 | bytes32 DOMAIN_SEPARATOR; 35 | 36 | bytes32 WITNESS_TYPE_HASH = keccak256("MockWitness(address person,uint256 amount)"); 37 | 38 | MockSignatureVerification mockSig; 39 | 40 | address person = 0xd5F5175D014F28c85F7D67A111C2c9335D7CD771; 41 | 42 | struct MockWitness { 43 | address person; 44 | uint256 amount; 45 | } 46 | 47 | function setUp() public { 48 | mockHash = new MockHash(); 49 | // hardcoding these to match mm inputs 50 | verifyingContract = 0xCe71065D4017F316EC606Fe4422e11eB2c47c246; 51 | chainId = 1; 52 | token1 = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984; 53 | token2 = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 54 | spender = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; 55 | amount = 100; 56 | expiration = 946902158100; 57 | sigDeadline = 146902158100; 58 | nonce = 0; 59 | 60 | DOMAIN_SEPARATOR = _buildDomainSeparator(); 61 | 62 | mockSig = new MockSignatureVerification(); 63 | } 64 | 65 | function _buildDomainSeparator() private view returns (bytes32) { 66 | bytes32 nameHash = keccak256("Permit2"); 67 | bytes32 typeHash = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 68 | return keccak256(abi.encode(typeHash, nameHash, chainId, verifyingContract)); 69 | } 70 | 71 | function testPermitSingle() public view { 72 | // metamask wallet signed data 73 | // 0xdb5507adaba8ed8e1d83dc7cb64980735c4769076c657d80563ce9a991fbb1981d07973917923c7942307e63285ff2e9e8d435fc4da8cdc7546a669bf474fb6d1b 74 | bytes32 r = 0xdb5507adaba8ed8e1d83dc7cb64980735c4769076c657d80563ce9a991fbb198; 75 | bytes32 s = 0x1d07973917923c7942307e63285ff2e9e8d435fc4da8cdc7546a669bf474fb6d; 76 | uint8 v = 0x1b; 77 | 78 | bytes memory sig = bytes.concat(r, s, bytes1(v)); 79 | 80 | // generate local data 81 | IAllowanceTransfer.PermitDetails memory details = 82 | IAllowanceTransfer.PermitDetails({token: token1, amount: amount, expiration: expiration, nonce: nonce}); 83 | 84 | IAllowanceTransfer.PermitSingle memory permit = 85 | IAllowanceTransfer.PermitSingle({details: details, spender: spender, sigDeadline: sigDeadline}); 86 | 87 | // generate hash of local data 88 | bytes32 hashedPermit = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permit.hash())); 89 | 90 | // verify the signed data againt the locally generated hash 91 | // this should not revert, validating that from is indeed the signer 92 | mockSig.verify(sig, hashedPermit, from); 93 | } 94 | 95 | function testPermitBatch() public view { 96 | // metamask wallet signed data 97 | // 0x3d298c897075538134ee0003bba9b149fac6e4b3496e34272f6731c32be2a710682657710eb4208db1eb6a6dac08b375f171733604e4e1deed30d49e22d0c42f1c 98 | bytes32 r = 0x3d298c897075538134ee0003bba9b149fac6e4b3496e34272f6731c32be2a710; 99 | bytes32 s = 0x682657710eb4208db1eb6a6dac08b375f171733604e4e1deed30d49e22d0c42f; 100 | uint8 v = 0x1c; 101 | 102 | bytes memory sig = bytes.concat(r, s, bytes1(v)); 103 | 104 | // generate local data 105 | address[] memory tokens = AddressBuilder.fill(1, token1).push(token2); 106 | IAllowanceTransfer.PermitDetails[] memory details = new IAllowanceTransfer.PermitDetails[](tokens.length); 107 | 108 | for (uint256 i = 0; i < tokens.length; ++i) { 109 | details[i] = IAllowanceTransfer.PermitDetails({ 110 | token: tokens[i], 111 | amount: amount, 112 | expiration: expiration, 113 | nonce: nonce 114 | }); 115 | } 116 | 117 | IAllowanceTransfer.PermitBatch memory permit = 118 | IAllowanceTransfer.PermitBatch({details: details, spender: spender, sigDeadline: sigDeadline}); 119 | 120 | // generate hash of local data 121 | bytes32 hashedPermit = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permit.hash())); 122 | 123 | // verify the signed data againt the locally generated hash 124 | // this should not revert, validating that from is indeed the signer 125 | mockSig.verify(sig, hashedPermit, from); 126 | } 127 | 128 | function testPermitTransferFrom() public { 129 | // metamask wallet signed data 130 | // 0x3d298c897075538134ee0003bba9b149fac6e4b3496e34272f6731c32be2a710682657710eb4208db1eb6a6dac08b375f171733604e4e1deed30d49e22d0c42f1c 131 | bytes32 r = 0xc12d33a96aef9ea42f1ad72587f52b5113b68d7b8fe35675fc0bb1ade3773455; 132 | bytes32 s = 0x56f3bbecb0c791bc9e23e58ce3a889f39c4b37b315faa264b8e4b5f2d5f7b365; 133 | uint8 v = 0x1b; 134 | 135 | bytes memory sig = bytes.concat(r, s, bytes1(v)); 136 | 137 | ISignatureTransfer.PermitTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitTransferFrom({ 138 | permitted: ISignatureTransfer.TokenPermissions({token: token1, amount: amount}), 139 | nonce: nonce, 140 | deadline: sigDeadline 141 | }); 142 | 143 | vm.prank(spender); 144 | bytes32 permitTransferFromHash = mockHash.hash(permitTransferFrom); 145 | bytes32 hashedPermit = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permitTransferFromHash)); 146 | 147 | // verify the signed data againt the locally generated hash 148 | // this should not revert, validating that from is indeed the signer 149 | mockSig.verify(sig, hashedPermit, from); 150 | } 151 | 152 | function testPermitBatchTransferFrom() public { 153 | // metamask wallet signed data 154 | // 0x8987ef38bdbf7f7dd8f133c92a331b5359036ca9732b2cf15750f1a56050159e10a62544d74648d917ce4c1b670024a771aadb8bace7db63ef6f5d3975451b231b 155 | bytes32 r = 0x8987ef38bdbf7f7dd8f133c92a331b5359036ca9732b2cf15750f1a56050159e; 156 | bytes32 s = 0x10a62544d74648d917ce4c1b670024a771aadb8bace7db63ef6f5d3975451b23; 157 | uint8 v = 0x1b; 158 | 159 | bytes memory sig = bytes.concat(r, s, bytes1(v)); 160 | 161 | address[] memory tokens = AddressBuilder.fill(1, token1).push(token2); 162 | 163 | ISignatureTransfer.TokenPermissions[] memory permitted = 164 | new ISignatureTransfer.TokenPermissions[](tokens.length); 165 | 166 | for (uint256 i = 0; i < tokens.length; ++i) { 167 | permitted[i] = ISignatureTransfer.TokenPermissions({token: tokens[i], amount: amount}); 168 | } 169 | ISignatureTransfer.PermitBatchTransferFrom memory permitBatchTransferFrom = 170 | ISignatureTransfer.PermitBatchTransferFrom({permitted: permitted, nonce: nonce, deadline: sigDeadline}); 171 | 172 | vm.prank(spender); 173 | bytes32 permitBatchTransferFromHash = mockHash.hash(permitBatchTransferFrom); 174 | bytes32 hashedPermit = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permitBatchTransferFromHash)); 175 | 176 | // verify the signed data againt the locally generated hash 177 | // this should not revert, validating that from is indeed the signer 178 | mockSig.verify(sig, hashedPermit, from); 179 | } 180 | 181 | function testPermitTransferFromWithWitness() public { 182 | string memory WITNESS_TYPE_STRING_STUB = 183 | "MockWitness witness)MockWitness(address person,uint256 amount)TokenPermissions(address token,uint256 amount)"; 184 | bytes memory sig = _getSingleWitnessMetamaskSignature(); 185 | bytes32 hashedPermit = _getLocalSingleWitnessHash(amount, WITNESS_TYPE_STRING_STUB); 186 | 187 | // verify the signed data againt the locally generated hash 188 | // this should not revert, validating that from is indeed the signer 189 | mockSig.verify(sig, hashedPermit, from); 190 | } 191 | 192 | function testPermitTransferFromWithWitnessIncorrectTypehashStub() public { 193 | string memory INCORRECT_WITNESS_TYPE_STRING_STUB = 194 | "MockWitness witness)TokenPermissions(address token,uint256 amount)MockWitness(address person,uint256 amount)"; 195 | bytes memory sig = _getSingleWitnessMetamaskSignature(); 196 | bytes32 hashedPermit = _getLocalSingleWitnessHash(amount, INCORRECT_WITNESS_TYPE_STRING_STUB); 197 | 198 | // verify the signed data againt the locally generated hash 199 | // should revert since the typehash is incorrect 200 | vm.expectRevert(SignatureVerification.InvalidSigner.selector); 201 | mockSig.verify(sig, hashedPermit, from); 202 | } 203 | 204 | function testPermitTransferFromWithWitnessIncorrectPermitData() public { 205 | string memory WITNESS_TYPE_STRING_STUB = 206 | "MockWitness witness)MockWitness(address person,uint256 amount)TokenPermissions(address token,uint256 amount)"; 207 | bytes memory sig = _getSingleWitnessMetamaskSignature(); 208 | uint256 incorrectAmount = 10000000000; 209 | bytes32 hashedPermit = _getLocalSingleWitnessHash(incorrectAmount, WITNESS_TYPE_STRING_STUB); 210 | 211 | // verify the signed data againt the locally generated hash 212 | // should revert since the incorrect amount is passed 213 | vm.expectRevert(SignatureVerification.InvalidSigner.selector); 214 | mockSig.verify(sig, hashedPermit, from); 215 | } 216 | 217 | function testPermitBatchTransferFromWithWitness() public { 218 | string memory WITNESS_TYPE_STRING_STUB = 219 | "MockWitness witness)MockWitness(address person,uint256 amount)TokenPermissions(address token,uint256 amount)"; 220 | bytes memory sig = _getBatchedWitnessMetamaskSignature(); 221 | bytes32 hashedPermit = _getLocalBatchedWitnessHash(amount, WITNESS_TYPE_STRING_STUB); 222 | 223 | // verify the signed data againt the locally generated hash 224 | // this should not revert, validating that from is indeed the signer 225 | mockSig.verify(sig, hashedPermit, from); 226 | } 227 | 228 | function testPermitBatchTransferFromWithWitnessIncorrectTypehashStub() public { 229 | string memory INCORRECT_WITNESS_TYPE_STRING_STUB = 230 | "MockWitness witness)TokenPermissions(address token,uint256 amount)MockWitness(address person,uint256 amount)"; 231 | bytes memory sig = _getBatchedWitnessMetamaskSignature(); 232 | bytes32 hashedPermit = _getLocalBatchedWitnessHash(amount, INCORRECT_WITNESS_TYPE_STRING_STUB); 233 | 234 | // verify the signed data againt the locally generated hash 235 | // this should revert since the typehash is incorrect 236 | vm.expectRevert(SignatureVerification.InvalidSigner.selector); 237 | mockSig.verify(sig, hashedPermit, from); 238 | } 239 | 240 | function testPermitBatchTransferFromWithWitnessIncorrectPermitData() public { 241 | string memory INCORRECT_WITNESS_TYPE_STRING_STUB = 242 | "MockWitness witness)TokenPermissions(address token,uint256 amount)MockWitness(address person,uint256 amount)"; 243 | bytes memory sig = _getBatchedWitnessMetamaskSignature(); 244 | uint256 incorrectAmount = 100000000000; 245 | bytes32 hashedPermit = _getLocalBatchedWitnessHash(incorrectAmount, INCORRECT_WITNESS_TYPE_STRING_STUB); 246 | 247 | // verify the signed data againt the locally generated hash 248 | // this should revert since the incorrect amount is passed 249 | vm.expectRevert(SignatureVerification.InvalidSigner.selector); 250 | mockSig.verify(sig, hashedPermit, from); 251 | } 252 | 253 | function _getSingleWitnessMetamaskSignature() private pure returns (bytes memory sig) { 254 | // metamask wallet signed data 255 | // 0x6cf7721a2a489c29d86fe0bb9b1f5f440a6a7e3fea5f5533ec080068025a7d4f30d7d8452106654827fd3b44f24260bacb8cf191ec185fc19fc24f5941d573d71c 256 | bytes32 r = 0x6cf7721a2a489c29d86fe0bb9b1f5f440a6a7e3fea5f5533ec080068025a7d4f; 257 | bytes32 s = 0x30d7d8452106654827fd3b44f24260bacb8cf191ec185fc19fc24f5941d573d7; 258 | uint8 v = 0x1c; 259 | 260 | sig = bytes.concat(r, s, bytes1(v)); 261 | } 262 | 263 | function _getLocalSingleWitnessHash(uint256 amountToHash, string memory typehashStub) 264 | private 265 | returns (bytes32 hashedPermit) 266 | { 267 | ISignatureTransfer.PermitTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitTransferFrom({ 268 | permitted: ISignatureTransfer.TokenPermissions({token: token1, amount: amountToHash}), 269 | nonce: nonce, 270 | deadline: sigDeadline 271 | }); 272 | 273 | MockWitness memory witness = MockWitness({person: person, amount: amount}); 274 | bytes32 hashedWitness = hashTypedWitness(WITNESS_TYPE_HASH, witness); 275 | 276 | vm.prank(spender); 277 | bytes32 permitTrasferFromWitnessHash = mockHash.hashWithWitness(permitTransferFrom, hashedWitness, typehashStub); 278 | 279 | hashedPermit = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permitTrasferFromWitnessHash)); 280 | } 281 | 282 | function _getBatchedWitnessMetamaskSignature() private pure returns (bytes memory sig) { 283 | // metamask wallet signed data 284 | // 0x0dff2ebed15802a2a21eaac44a12fb182ac41771aaaf6ff33a6a5c78ac66aec306e693dba180302dc0b6aecd97261adfa91f27fd0964e71f58c8b40444ce2f7a1b 285 | bytes32 r = 0x0dff2ebed15802a2a21eaac44a12fb182ac41771aaaf6ff33a6a5c78ac66aec3; 286 | bytes32 s = 0x06e693dba180302dc0b6aecd97261adfa91f27fd0964e71f58c8b40444ce2f7a; 287 | uint8 v = 0x1b; 288 | 289 | sig = bytes.concat(r, s, bytes1(v)); 290 | } 291 | 292 | function _getLocalBatchedWitnessHash(uint256 amountToHash, string memory typehashStub) 293 | private 294 | returns (bytes32 hashedPermit) 295 | { 296 | MockWitness memory witness = MockWitness({person: person, amount: amount}); 297 | bytes32 hashedWitness = hashTypedWitness(WITNESS_TYPE_HASH, witness); 298 | 299 | address[] memory tokens = AddressBuilder.fill(1, token1).push(token2); 300 | ISignatureTransfer.TokenPermissions[] memory permitted = 301 | new ISignatureTransfer.TokenPermissions[](tokens.length); 302 | for (uint256 i = 0; i < tokens.length; ++i) { 303 | permitted[i] = ISignatureTransfer.TokenPermissions({token: tokens[i], amount: amountToHash}); 304 | } 305 | ISignatureTransfer.PermitBatchTransferFrom memory permitBatchTransferFrom = 306 | ISignatureTransfer.PermitBatchTransferFrom({permitted: permitted, nonce: nonce, deadline: sigDeadline}); 307 | 308 | vm.prank(spender); 309 | bytes32 permitBatchTransferFromWitnessHash = 310 | mockHash.hashWithWitness(permitBatchTransferFrom, hashedWitness, typehashStub); 311 | hashedPermit = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, permitBatchTransferFromWitnessHash)); 312 | } 313 | 314 | function hashTypedWitness(bytes32 typehash, MockWitness memory typedWitness) 315 | private 316 | pure 317 | returns (bytes32 witness) 318 | { 319 | return keccak256(abi.encode(typehash, typedWitness)); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /test/actors/Permitter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.17; 2 | 3 | import {Vm} from "forge-std/Vm.sol"; 4 | import {Permit2} from "../../src/Permit2.sol"; 5 | import {IAllowanceTransfer} from "../../src/interfaces/IAllowanceTransfer.sol"; 6 | import {PermitSignature} from "../utils/PermitSignature.sol"; 7 | import {MockERC20} from "../mocks/MockERC20.sol"; 8 | 9 | contract Permitter is PermitSignature { 10 | uint256 private immutable privateKey; 11 | Permit2 private immutable permit2; 12 | MockERC20 private immutable token; 13 | Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); 14 | 15 | address public immutable signer; 16 | 17 | mapping(address => uint48) private nonces; 18 | uint256 public amountPermitted; 19 | 20 | constructor(Permit2 _permit2, MockERC20 _token, uint256 _privateKey) { 21 | permit2 = _permit2; 22 | token = _token; 23 | privateKey = _privateKey; 24 | 25 | signer = vm.addr(privateKey); 26 | token.mint(signer, type(uint160).max); 27 | vm.prank(signer); 28 | token.approve(address(permit2), type(uint256).max); 29 | } 30 | 31 | function createPermit(uint128 amount, address spender) 32 | public 33 | returns (IAllowanceTransfer.PermitSingle memory permit, bytes memory sig) 34 | { 35 | uint48 nonce = nonces[spender]; 36 | permit = IAllowanceTransfer.PermitSingle({ 37 | details: IAllowanceTransfer.PermitDetails({ 38 | token: address(token), 39 | amount: amount, 40 | expiration: uint48(block.timestamp + 1000), 41 | nonce: nonce 42 | }), 43 | spender: spender, 44 | sigDeadline: block.timestamp + 1000 45 | }); 46 | sig = getPermitSignature(permit, privateKey, permit2.DOMAIN_SEPARATOR()); 47 | 48 | nonces[spender]++; 49 | amountPermitted += amount; 50 | } 51 | 52 | function approve(uint128 amount, address spender) public { 53 | permit2.approve(address(token), spender, uint160(amount), uint48(block.timestamp + 1000)); 54 | amountPermitted += amount; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/actors/Spender.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.17; 2 | 3 | import {Test} from "forge-std/Test.sol"; 4 | import {Permit2} from "../../src/Permit2.sol"; 5 | import {MockERC20} from "../mocks/MockERC20.sol"; 6 | 7 | contract Spender is Test { 8 | Permit2 private immutable permit2; 9 | MockERC20 private immutable token; 10 | 11 | uint256 public amountSpent; 12 | 13 | constructor(Permit2 _permit2, MockERC20 _token) { 14 | permit2 = _permit2; 15 | token = _token; 16 | } 17 | 18 | function spendPermit(uint160 amount, address from) public { 19 | permit2.transferFrom(from, address(this), amount, address(token)); 20 | amountSpent += amount; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/integration/Argent.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {IERC1271} from "../../src/interfaces/IERC1271.sol"; 6 | 7 | interface WalletFactory { 8 | function owner() external returns (address); 9 | 10 | function addManager(address _manager) external; 11 | 12 | function createCounterfactualWallet( 13 | address _owner, 14 | address[] calldata _modules, 15 | address _guardian, 16 | bytes20 _salt, 17 | uint256 _refundAmount, 18 | address _refundToken, 19 | bytes calldata _ownerSignature, 20 | bytes calldata _managerSignature 21 | ) external returns (IERC1271 _wallet); 22 | } 23 | 24 | contract SampleCaller { 25 | function checkIsValidSignature(IERC1271 target, bytes32 hash, bytes calldata signature) 26 | external 27 | view 28 | returns (bytes4) 29 | { 30 | return target.isValidSignature(hash, signature); 31 | } 32 | } 33 | 34 | contract ArgentTest is Test { 35 | address from; 36 | uint256 fromPrivateKey; 37 | SampleCaller sampleCaller; 38 | 39 | function setUp() public { 40 | vm.createSelectFork(vm.envString("FORK_URL")); 41 | 42 | fromPrivateKey = 0x12341234; 43 | from = vm.addr(fromPrivateKey); 44 | sampleCaller = new SampleCaller(); 45 | } 46 | 47 | WalletFactory walletFactory = WalletFactory(0x536384FCd25b576265B6775F383D5ac408FF9dB7); 48 | address argentModule = 0x9D58779365B067D5D3fCc6e92d237aCd06F1e6a1; 49 | 50 | function testIsValidSignature() public { 51 | // deploy an argent wallet 52 | address[] memory _modules = new address[](1); 53 | _modules[0] = argentModule; 54 | vm.prank(walletFactory.owner()); 55 | walletFactory.addManager(address(1)); 56 | vm.prank(address(1)); 57 | IERC1271 wallet = 58 | walletFactory.createCounterfactualWallet(from, _modules, address(1), bytes20(0), 0, address(0), "", ""); 59 | 60 | // test the functionality 61 | bytes32 dataHash = keccak256(""); 62 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(fromPrivateKey, dataHash); 63 | bytes4 magicValue = sampleCaller.checkIsValidSignature(wallet, dataHash, bytes.concat(r, s, bytes1(v))); 64 | assertEq(magicValue, IERC1271.isValidSignature.selector); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/integration/GnosisSafe.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {IERC1271} from "../../src/interfaces/IERC1271.sol"; 6 | 7 | interface GnosisSafeProxy is IERC1271 { 8 | function setup( 9 | address[] calldata _owners, 10 | uint256 _threshold, 11 | address to, 12 | bytes calldata data, 13 | address fallbackHandler, 14 | address paymentToken, 15 | uint256 payment, 16 | address payable paymentReceiver 17 | ) external; 18 | function domainSeparator() external view returns (bytes32); 19 | } 20 | 21 | interface GnosisSafeProxyFactory { 22 | function createProxy(address singleton, bytes memory data) external returns (GnosisSafeProxy proxy); 23 | } 24 | 25 | contract SampleCaller { 26 | function checkIsValidSignature(IERC1271 target, bytes32 hash) external view returns (bytes4) { 27 | return target.isValidSignature(hash, ""); 28 | } 29 | } 30 | 31 | contract GnosisSafeTest is Test { 32 | address from; 33 | uint256 fromPrivateKey; 34 | SampleCaller sampleCaller; 35 | 36 | function setUp() public { 37 | vm.createSelectFork(vm.envString("FORK_URL")); 38 | 39 | fromPrivateKey = 0x12341234; 40 | from = vm.addr(fromPrivateKey); 41 | sampleCaller = new SampleCaller(); 42 | } 43 | 44 | GnosisSafeProxyFactory gnosisSafeProxyFactory = GnosisSafeProxyFactory(0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2); 45 | address singleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; 46 | address compatibilityFallbackHandler = 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4; 47 | 48 | function testSignMessage() public { 49 | // deploy a safe 50 | address[] memory owners = new address[](1); 51 | owners[0] = from; 52 | GnosisSafeProxy safe = gnosisSafeProxyFactory.createProxy( 53 | singleton, 54 | abi.encodeCall( 55 | GnosisSafeProxy.setup, 56 | (owners, 1, address(0), "", compatibilityFallbackHandler, address(0), 0, payable(address(0))) 57 | ) 58 | ); 59 | 60 | bytes32 dataHash = keccak256(""); 61 | 62 | // manually calculate the output of SignMessageLib#getMessageHash to avoid delegatecall issues 63 | bytes32 SAFE_MSG_TYPEHASH = keccak256("SafeMessage(bytes message)"); 64 | bytes32 safeMessageHash = keccak256(abi.encode(SAFE_MSG_TYPEHASH, keccak256(abi.encode(dataHash)))); 65 | bytes32 messageHash = 66 | keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), safe.domainSeparator(), safeMessageHash)); 67 | 68 | // ensure revert 69 | vm.expectRevert("Hash not approved"); 70 | sampleCaller.checkIsValidSignature(safe, dataHash); 71 | 72 | // manually set signedMessages[dataHash] to 1 73 | uint256 SIGNED_MESSAGES_MAPPING_STORAGE_SLOT = 7; 74 | bytes32 expectedSlot = keccak256(abi.encode(messageHash, SIGNED_MESSAGES_MAPPING_STORAGE_SLOT)); 75 | assertEq(vm.load(address(safe), expectedSlot), bytes32(0)); 76 | vm.store(address(safe), expectedSlot, bytes32(uint256(1))); 77 | 78 | // test the functionality 79 | bytes4 magicValue = sampleCaller.checkIsValidSignature(safe, dataHash); 80 | assertEq(magicValue, IERC1271.isValidSignature.selector); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/integration/MainnetToken.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; 6 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 7 | import {AddressBuilder} from "../utils/AddressBuilder.sol"; 8 | import {StructBuilder} from "../utils/StructBuilder.sol"; 9 | import {PermitSignature} from "../utils/PermitSignature.sol"; 10 | import {Permit2} from "../../src/Permit2.sol"; 11 | import {IAllowanceTransfer} from "../../src/interfaces/IAllowanceTransfer.sol"; 12 | import {ISignatureTransfer} from "../../src/interfaces/ISignatureTransfer.sol"; 13 | 14 | /// @dev generic token test suite 15 | /// @dev extend this contract with a concrete token on mainnet to test it 16 | abstract contract MainnetTokenTest is Test, PermitSignature { 17 | using SafeTransferLib for ERC20; 18 | 19 | address constant RECIPIENT = address(0x1234123412341234123412341234123412341234); 20 | uint160 constant AMOUNT = 10 ** 6; 21 | uint48 constant NONCE = 0; 22 | uint48 EXPIRATION; 23 | 24 | address from; 25 | uint256 fromPrivateKey; 26 | bytes32 DOMAIN_SEPARATOR; 27 | Permit2 permit2; 28 | 29 | struct MockWitness { 30 | uint256 value; 31 | address person; 32 | bool test; 33 | } 34 | 35 | bytes32 constant FULL_EXAMPLE_WITNESS_TYPEHASH = keccak256( 36 | "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,MockWitness witness)MockWitness(uint256 value,address person,bool test)TokenPermissions(address token,uint256 amount)" 37 | ); 38 | 39 | string constant WITNESS_TYPE_STRING = 40 | "MockWitness witness)MockWitness(uint256 value,address person,bool test)TokenPermissions(address token,uint256 amount)"; 41 | 42 | function setUp() public { 43 | vm.createSelectFork(vm.envString("FORK_URL"), 15979000); 44 | 45 | fromPrivateKey = 0x12341234; 46 | from = vm.addr(fromPrivateKey); 47 | permit2 = new Permit2(); 48 | DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); 49 | EXPIRATION = uint48(block.timestamp + 1000); 50 | 51 | setupToken(); 52 | } 53 | 54 | function testApprove() public { 55 | vm.prank(from); 56 | permit2.approve(address(token()), address(this), AMOUNT, EXPIRATION); 57 | 58 | (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token()), address(this)); 59 | assertEq(amount, AMOUNT); 60 | assertEq(expiration, EXPIRATION); 61 | assertEq(nonce, 0); 62 | } 63 | 64 | function testPermit() public { 65 | IAllowanceTransfer.PermitSingle memory permit = 66 | defaultERC20PermitAllowance(address(token()), AMOUNT, EXPIRATION, NONCE); 67 | bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 68 | 69 | permit2.permit(from, permit, sig); 70 | 71 | (uint160 amount, uint48 expiration, uint48 nonce) = permit2.allowance(from, address(token()), address(this)); 72 | assertEq(amount, AMOUNT); 73 | assertEq(expiration, EXPIRATION); 74 | assertEq(nonce, 1); 75 | } 76 | 77 | function testTransferFrom() public { 78 | IAllowanceTransfer.PermitSingle memory permit = 79 | defaultERC20PermitAllowance(address(token()), AMOUNT, EXPIRATION, NONCE); 80 | bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 81 | 82 | uint256 startBalanceFrom = token().balanceOf(from); 83 | uint256 startBalanceTo = token().balanceOf(RECIPIENT); 84 | 85 | permit2.permit(from, permit, sig); 86 | 87 | (uint160 amount,,) = permit2.allowance(from, address(token()), address(this)); 88 | 89 | assertEq(amount, AMOUNT); 90 | 91 | permit2.transferFrom(from, RECIPIENT, AMOUNT, address(token())); 92 | 93 | assertEq(token().balanceOf(from), startBalanceFrom - AMOUNT); 94 | assertEq(token().balanceOf(RECIPIENT), startBalanceTo + AMOUNT); 95 | } 96 | 97 | function testTransferFromInsufficientBalance() public { 98 | IAllowanceTransfer.PermitSingle memory permit = 99 | defaultERC20PermitAllowance(address(token()), AMOUNT * 2, EXPIRATION, NONCE); 100 | bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 101 | 102 | permit2.permit(from, permit, sig); 103 | 104 | (uint160 amount,,) = permit2.allowance(from, address(token()), address(this)); 105 | assertEq(amount, AMOUNT * 2); 106 | 107 | vm.expectRevert(); 108 | permit2.transferFrom(from, RECIPIENT, AMOUNT * 2, address(token())); 109 | } 110 | 111 | function testBatchTransferFrom() public { 112 | IAllowanceTransfer.PermitSingle memory permit = 113 | defaultERC20PermitAllowance(address(token()), AMOUNT, EXPIRATION, NONCE); 114 | bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 115 | 116 | uint256 startBalanceFrom = token().balanceOf(from); 117 | uint256 startBalanceTo = token().balanceOf(RECIPIENT); 118 | 119 | permit2.permit(from, permit, sig); 120 | 121 | (uint160 amount,,) = permit2.allowance(from, address(token()), address(this)); 122 | assertEq(amount, AMOUNT); 123 | 124 | // permit token() for 1 ** 18 125 | address[] memory owners = AddressBuilder.fill(3, from); 126 | uint256 eachTransfer = 10 ** 5; 127 | IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails = 128 | StructBuilder.fillAllowanceTransferDetail(3, address(token()), uint160(eachTransfer), RECIPIENT, owners); 129 | permit2.transferFrom(transferDetails); 130 | 131 | assertEq(token().balanceOf(from), startBalanceFrom - 3 * eachTransfer); 132 | assertEq(token().balanceOf(RECIPIENT), startBalanceTo + 3 * eachTransfer); 133 | (amount,,) = permit2.allowance(from, address(token()), address(this)); 134 | assertEq(amount, AMOUNT - 3 * eachTransfer); 135 | } 136 | 137 | function testPermitTransferFrom() public { 138 | ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token()), NONCE); 139 | bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 140 | 141 | uint256 startBalanceFrom = token().balanceOf(from); 142 | uint256 startBalanceTo = token().balanceOf(RECIPIENT); 143 | 144 | ISignatureTransfer.SignatureTransferDetails memory transferDetails = 145 | ISignatureTransfer.SignatureTransferDetails({to: RECIPIENT, requestedAmount: AMOUNT}); 146 | 147 | permit2.permitTransferFrom(permit, transferDetails, from, sig); 148 | 149 | assertEq(token().balanceOf(from), startBalanceFrom - AMOUNT); 150 | assertEq(token().balanceOf(RECIPIENT), startBalanceTo + AMOUNT); 151 | } 152 | 153 | function testPermitTransferFromTypedWitness() public { 154 | MockWitness memory witnessData = MockWitness(10000000, address(5), true); 155 | bytes32 witness = keccak256(abi.encode(witnessData)); 156 | ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitWitnessTransfer(address(token()), NONCE); 157 | bytes memory sig = getPermitWitnessTransferSignature( 158 | permit, fromPrivateKey, FULL_EXAMPLE_WITNESS_TYPEHASH, witness, DOMAIN_SEPARATOR 159 | ); 160 | 161 | uint256 startBalanceFrom = token().balanceOf(from); 162 | uint256 startBalanceTo = token().balanceOf(RECIPIENT); 163 | 164 | ISignatureTransfer.SignatureTransferDetails memory transferDetails = 165 | ISignatureTransfer.SignatureTransferDetails({to: RECIPIENT, requestedAmount: AMOUNT}); 166 | 167 | permit2.permitWitnessTransferFrom(permit, transferDetails, from, witness, WITNESS_TYPE_STRING, sig); 168 | 169 | assertEq(token().balanceOf(from), startBalanceFrom - AMOUNT); 170 | assertEq(token().balanceOf(RECIPIENT), startBalanceTo + AMOUNT); 171 | } 172 | 173 | /// @dev for some reason safeApprove gets stack too deep for USDT 174 | /// so helper function for setup 175 | function setupToken() internal virtual { 176 | dealTokens(from, AMOUNT); 177 | vm.prank(from); 178 | token().safeApprove(address(permit2), AMOUNT); 179 | } 180 | 181 | function token() internal virtual returns (ERC20); 182 | 183 | // sometimes the balances slot is not easy to find for forge 184 | function dealTokens(address to, uint256 amount) internal virtual { 185 | deal(address(token()), to, amount); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /test/integration/tokens/DAI.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract DAITest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/tokens/FeeOnTransferToken.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract FeeOnTransferTokenTest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0xF5238462E7235c7B62811567E63Dd17d12C2EAA0); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/tokens/RebasingToken.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract RebasingTokenTest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0xD46bA6D942050d489DBd938a2C909A5d5039A161); 10 | } 11 | 12 | function dealTokens(address to, uint256 amount) internal override { 13 | // large holder 14 | vm.prank(0xc3a947372191453Dd9dB02B0852d378dCCBDf271); 15 | token().transfer(to, amount); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/integration/tokens/TooManyReturnBytesToken.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract TooManyReturnBytesTokenTest is MainnetTokenTest { 8 | ReturnsTooMuchToken _token; 9 | 10 | function setupToken() internal override { 11 | _token = new ReturnsTooMuchToken(); 12 | deal(address(token()), from, AMOUNT); 13 | vm.prank(from); 14 | token().approve(address(permit2), AMOUNT); 15 | } 16 | 17 | function token() internal view override returns (ERC20) { 18 | return ERC20(address(_token)); 19 | } 20 | } 21 | 22 | contract ReturnsTooMuchToken { 23 | /*/////////////////////////////////////////////////////////////// 24 | EVENTS 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | event Transfer(address indexed from, address indexed to, uint256 amount); 28 | 29 | event Approval(address indexed owner, address indexed spender, uint256 amount); 30 | 31 | /*/////////////////////////////////////////////////////////////// 32 | METADATA STORAGE 33 | //////////////////////////////////////////////////////////////*/ 34 | 35 | string public constant name = "ReturnsTooMuchToken"; 36 | 37 | string public constant symbol = "RTMT"; 38 | 39 | uint8 public constant decimals = 18; 40 | 41 | /*/////////////////////////////////////////////////////////////// 42 | ERC20 STORAGE 43 | //////////////////////////////////////////////////////////////*/ 44 | 45 | uint256 public totalSupply; 46 | 47 | mapping(address => uint256) public balanceOf; 48 | 49 | mapping(address => mapping(address => uint256)) public allowance; 50 | 51 | /*/////////////////////////////////////////////////////////////// 52 | CONSTRUCTOR 53 | //////////////////////////////////////////////////////////////*/ 54 | 55 | constructor() { 56 | totalSupply = type(uint256).max; 57 | balanceOf[msg.sender] = type(uint256).max; 58 | } 59 | 60 | /*/////////////////////////////////////////////////////////////// 61 | ERC20 LOGIC 62 | //////////////////////////////////////////////////////////////*/ 63 | 64 | function approve(address spender, uint256 amount) public virtual { 65 | allowance[msg.sender][spender] = amount; 66 | 67 | emit Approval(msg.sender, spender, amount); 68 | 69 | assembly { 70 | mstore(0, 1) 71 | return(0, 4096) 72 | } 73 | } 74 | 75 | function transfer(address to, uint256 amount) public virtual { 76 | balanceOf[msg.sender] -= amount; 77 | 78 | // Cannot overflow because the sum of all user 79 | // balances can't exceed the max uint256 value. 80 | unchecked { 81 | balanceOf[to] += amount; 82 | } 83 | 84 | emit Transfer(msg.sender, to, amount); 85 | 86 | assembly { 87 | mstore(0, 1) 88 | return(0, 4096) 89 | } 90 | } 91 | 92 | function transferFrom(address from, address to, uint256 amount) public virtual { 93 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 94 | 95 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 96 | 97 | balanceOf[from] -= amount; 98 | 99 | // Cannot overflow because the sum of all user 100 | // balances can't exceed the max uint256 value. 101 | unchecked { 102 | balanceOf[to] += amount; 103 | } 104 | 105 | emit Transfer(from, to, amount); 106 | 107 | assembly { 108 | mstore(0, 1) 109 | return(0, 4096) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/integration/tokens/UNI.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract UNITest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/tokens/USDC.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract USDCTest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/tokens/USDT.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; 5 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 6 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 7 | 8 | contract USDTTest is MainnetTokenTest { 9 | using SafeTransferLib for ERC20; 10 | 11 | function token() internal pure override returns (ERC20) { 12 | return ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/integration/tokens/WBTC.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract WBTCTest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/tokens/ZRX.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {MainnetTokenTest} from "../MainnetToken.t.sol"; 6 | 7 | contract ZRXTest is MainnetTokenTest { 8 | function token() internal pure override returns (ERC20) { 9 | return ERC20(0xE41d2489571d322189246DaFA5ebDe1F4699F498); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/mocks/MockERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC1155} from "solmate/src/tokens/ERC1155.sol"; 5 | 6 | contract MockERC1155 is ERC1155 { 7 | constructor() ERC1155() {} 8 | 9 | function mint(address to, uint256 id, uint256 amount) public { 10 | _mint(to, id, amount, ""); 11 | } 12 | 13 | function uri(uint256) public view virtual override returns (string memory) { 14 | return ""; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/mocks/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol, decimals) {} 8 | 9 | function mint(address _to, uint256 _amount) public { 10 | _mint(_to, _amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/mocks/MockERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC721} from "solmate/src/tokens/ERC721.sol"; 5 | 6 | contract MockERC721 is ERC721 { 7 | constructor(string memory name, string memory symbol) ERC721(name, symbol) {} 8 | 9 | function mint(address to, uint256 id) public { 10 | _mint(to, id); 11 | } 12 | 13 | function tokenURI(uint256) public view virtual override returns (string memory) { 14 | return ""; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/mocks/MockFallbackERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | 6 | contract MockFallbackERC20 { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event Transfer(address indexed from, address indexed to, uint256 amount); 12 | 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | /*////////////////////////////////////////////////////////////// 16 | METADATA STORAGE 17 | //////////////////////////////////////////////////////////////*/ 18 | 19 | string public name; 20 | 21 | string public symbol; 22 | 23 | uint8 public immutable decimals; 24 | 25 | /*////////////////////////////////////////////////////////////// 26 | ERC20 STORAGE 27 | //////////////////////////////////////////////////////////////*/ 28 | 29 | uint256 public totalSupply; 30 | 31 | mapping(address => uint256) public balanceOf; 32 | 33 | mapping(address => mapping(address => uint256)) public allowance; 34 | 35 | /*////////////////////////////////////////////////////////////// 36 | CONSTRUCTOR 37 | //////////////////////////////////////////////////////////////*/ 38 | 39 | constructor(string memory _name, string memory _symbol, uint8 _decimals) { 40 | name = _name; 41 | symbol = _symbol; 42 | decimals = _decimals; 43 | } 44 | 45 | /*////////////////////////////////////////////////////////////// 46 | ERC20 LOGIC 47 | //////////////////////////////////////////////////////////////*/ 48 | 49 | function approve(address spender, uint256 amount) public virtual returns (bool) { 50 | allowance[msg.sender][spender] = amount; 51 | 52 | emit Approval(msg.sender, spender, amount); 53 | 54 | return true; 55 | } 56 | 57 | function transfer(address to, uint256 amount) public virtual returns (bool) { 58 | balanceOf[msg.sender] -= amount; 59 | 60 | // Cannot overflow because the sum of all user 61 | // balances can't exceed the max uint256 value. 62 | unchecked { 63 | balanceOf[to] += amount; 64 | } 65 | 66 | emit Transfer(msg.sender, to, amount); 67 | 68 | return true; 69 | } 70 | 71 | function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { 72 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 73 | 74 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 75 | 76 | balanceOf[from] -= amount; 77 | 78 | // Cannot overflow because the sum of all user 79 | // balances can't exceed the max uint256 value. 80 | unchecked { 81 | balanceOf[to] += amount; 82 | } 83 | 84 | emit Transfer(from, to, amount); 85 | 86 | return true; 87 | } 88 | 89 | /*////////////////////////////////////////////////////////////// 90 | FALLBACK MOCK LOGIC 91 | //////////////////////////////////////////////////////////////*/ 92 | 93 | uint256 counter; 94 | 95 | receive() external payable { 96 | counter++; 97 | } 98 | 99 | fallback() external { 100 | counter++; 101 | } 102 | 103 | function mint(address _to, uint256 _amount) public { 104 | _mint(_to, _amount); 105 | } 106 | 107 | function _mint(address to, uint256 amount) internal virtual { 108 | totalSupply += amount; 109 | 110 | // Cannot overflow because the sum of all user 111 | // balances can't exceed the max uint256 value. 112 | unchecked { 113 | balanceOf[to] += amount; 114 | } 115 | 116 | emit Transfer(address(0), to, amount); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/mocks/MockHash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ISignatureTransfer} from "../../src/interfaces/ISignatureTransfer.sol"; 5 | import {PermitHash} from "../../src/libraries/PermitHash.sol"; 6 | 7 | contract MockHash { 8 | using PermitHash for ISignatureTransfer.PermitTransferFrom; 9 | using PermitHash for ISignatureTransfer.PermitBatchTransferFrom; 10 | 11 | function hash(ISignatureTransfer.PermitTransferFrom memory permit) external view returns (bytes32) { 12 | return permit.hash(); 13 | } 14 | 15 | function hash(ISignatureTransfer.PermitBatchTransferFrom memory permit) external view returns (bytes32) { 16 | return permit.hash(); 17 | } 18 | 19 | function hashWithWitness( 20 | ISignatureTransfer.PermitTransferFrom memory permit, 21 | bytes32 witness, 22 | string calldata witnessTypeString 23 | ) external view returns (bytes32) { 24 | return permit.hashWithWitness(witness, witnessTypeString); 25 | } 26 | 27 | function hashWithWitness( 28 | ISignatureTransfer.PermitBatchTransferFrom memory permit, 29 | bytes32 witness, 30 | string calldata witnessTypeString 31 | ) external view returns (bytes32) { 32 | return permit.hashWithWitness(witness, witnessTypeString); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/mocks/MockNonPermitERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 5 | 6 | contract MockNonPermitERC20 is MockERC20 { 7 | constructor(string memory _name, string memory _symbol, uint8 _decimals) MockERC20(_name, _symbol, _decimals) {} 8 | 9 | function DOMAIN_SEPARATOR() public pure override returns (bytes32) { 10 | return 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/mocks/MockNonPermitNonERC20WithDS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract MockNonPermitNonERC20WithDS { 5 | function DOMAIN_SEPARATOR() external pure returns (bytes memory) { 6 | bytes memory returnData = "123456789012345678901234567890123"; 7 | require(returnData.length == 33); 8 | return returnData; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/mocks/MockPermit2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Permit2} from "../../src/Permit2.sol"; 5 | import {IAllowanceTransfer} from "../../src/interfaces/IAllowanceTransfer.sol"; 6 | import {Allowance} from "../../src/libraries/Allowance.sol"; 7 | 8 | contract MockPermit2 is Permit2 { 9 | function doStore(address from, address token, address spender, uint256 word) public { 10 | IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; 11 | assembly { 12 | sstore(allowed.slot, word) 13 | } 14 | } 15 | 16 | function getStore(address from, address token, address spender) public view returns (uint256 word) { 17 | IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; 18 | assembly { 19 | word := sload(allowed.slot) 20 | } 21 | } 22 | 23 | function mockUpdateAmountAndExpiration( 24 | address from, 25 | address token, 26 | address spender, 27 | uint160 amount, 28 | uint48 expiration 29 | ) public { 30 | IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; 31 | Allowance.updateAmountAndExpiration(allowed, amount, expiration); 32 | } 33 | 34 | function mockUpdateAll( 35 | address from, 36 | address token, 37 | address spender, 38 | uint160 amount, 39 | uint48 expiration, 40 | uint48 nonce 41 | ) public { 42 | IAllowanceTransfer.PackedAllowance storage allowed = allowance[from][token][spender]; 43 | Allowance.updateAll(allowed, amount, expiration, nonce); 44 | } 45 | 46 | function useUnorderedNonce(address from, uint256 nonce) public { 47 | _useUnorderedNonce(from, nonce); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/mocks/MockPermit2Lib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | import {Permit2Lib} from "../../src/libraries/Permit2Lib.sol"; 6 | 7 | contract MockPermit2Lib { 8 | /// @dev The address for the WETH9 contract on Ethereum mainnet, encoded as a bytes32. 9 | bytes32 internal constant WETH9_ADDRESS = 0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2; 10 | 11 | function permit2( 12 | ERC20 token, 13 | address owner, 14 | address spender, 15 | uint256 amount, 16 | uint256 deadline, 17 | uint8 v, 18 | bytes32 r, 19 | bytes32 s 20 | ) public { 21 | Permit2Lib.permit2(token, owner, spender, amount, deadline, v, r, s); 22 | } 23 | 24 | function simplePermit2( 25 | ERC20 token, 26 | address owner, 27 | address spender, 28 | uint256 amount, 29 | uint256 deadline, 30 | uint8 v, 31 | bytes32 r, 32 | bytes32 s 33 | ) public { 34 | Permit2Lib.simplePermit2(token, owner, spender, amount, deadline, v, r, s); 35 | } 36 | 37 | function transferFrom2(ERC20 token, address from, address to, uint256 amount) public { 38 | Permit2Lib.transferFrom2(token, from, to, amount); 39 | } 40 | 41 | function testPermit2Code(ERC20 token) external view returns (bool) { 42 | // Generate calldata for a call to DOMAIN_SEPARATOR on the token. 43 | bytes memory inputData = abi.encodeWithSelector(ERC20.DOMAIN_SEPARATOR.selector); 44 | 45 | bool success; // Call the token contract as normal, capturing whether it succeeded. 46 | bytes32 domainSeparator; // If the call succeeded, we'll capture the return value here. 47 | assembly { 48 | // If the token is WETH9, we know it doesn't have a DOMAIN_SEPARATOR, and we'll skip this step. 49 | // We make sure to mask the token address as its higher order bits aren't guaranteed to be clean. 50 | if iszero(eq(and(token, 0xffffffffffffffffffffffffffffffffffffffff), WETH9_ADDRESS)) { 51 | success := 52 | and( 53 | // Should resolve false if its not 32 bytes or its first word is 0. 54 | and(iszero(iszero(mload(0))), eq(returndatasize(), 32)), 55 | // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. 56 | // Counterintuitively, this call must be positioned second to the and() call in the 57 | // surrounding and() call or else returndatasize() will be zero during the computation. 58 | // We send a maximum of 5000 gas to prevent tokens with fallbacks from using a ton of gas. 59 | // which should be plenty to allow tokens to fetch their DOMAIN_SEPARATOR from storage, etc. 60 | staticcall(5000, token, add(inputData, 32), mload(inputData), 0, 32) 61 | ) 62 | 63 | domainSeparator := mload(0) // Copy the return value into the domainSeparator variable. 64 | } 65 | } 66 | return success; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/mocks/MockPermitWithDS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 5 | 6 | contract MockPermitWithSmallDS is MockERC20 { 7 | constructor(string memory _name, string memory _symbol, uint8 _decimals) MockERC20(_name, _symbol, _decimals) {} 8 | 9 | function DOMAIN_SEPARATOR() public pure override returns (bytes32) { 10 | bytes31 returnData = 0x11111111111111111111111111111111111111111111111111111111111111; 11 | return returnData; 12 | } 13 | } 14 | 15 | contract MockPermitWithLargerDS is MockERC20 { 16 | constructor(string memory _name, string memory _symbol, uint8 _decimals) MockERC20(_name, _symbol, _decimals) {} 17 | 18 | function DOMAIN_SEPARATOR() public pure override returns (bytes32) { 19 | assembly { 20 | mstore(0, 0xBBBBBBBBBBBBBBBBBBBBBBBBBB) 21 | mstore(32, 0xAAAAAAAAAAAAAAAAAAAAAAAAAA) 22 | return(0, 64) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/mocks/MockSignatureVerification.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {SignatureVerification} from "../../src/libraries/SignatureVerification.sol"; 5 | 6 | contract MockSignatureVerification { 7 | function verify(bytes calldata sig, bytes32 hashed, address signer) public view { 8 | SignatureVerification.verify(sig, hashed, signer); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/utils/AddressBuilder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | library AddressBuilder { 5 | function fill(uint256 length, address a) external pure returns (address[] memory addresses) { 6 | addresses = new address[](length); 7 | for (uint256 i = 0; i < length; ++i) { 8 | addresses[i] = a; 9 | } 10 | } 11 | 12 | function push(address[] calldata a, address b) external pure returns (address[] memory addresses) { 13 | addresses = new address[](a.length + 1); 14 | for (uint256 i = 0; i < a.length; ++i) { 15 | addresses[i] = a[i]; 16 | } 17 | addresses[a.length] = b; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/utils/AmountBuilder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | library AmountBuilder { 5 | function fill(uint256 length, uint256 amount) external pure returns (uint256[] memory amounts) { 6 | amounts = new uint256[](length); 7 | for (uint256 i = 0; i < length; ++i) { 8 | amounts[i] = amount; 9 | } 10 | } 11 | 12 | function fillUInt8(uint256 length, uint8 tokenType) external pure returns (uint8[] memory tokenTypes) { 13 | tokenTypes = new uint8[](length); 14 | for (uint256 i = 0; i < length; ++i) { 15 | tokenTypes[i] = tokenType; 16 | } 17 | } 18 | 19 | function fillUInt160(uint256 length, uint160 amount) external pure returns (uint160[] memory amounts) { 20 | amounts = new uint160[](length); 21 | for (uint256 i = 0; i < length; ++i) { 22 | amounts[i] = amount; 23 | } 24 | } 25 | 26 | function fillUInt64(uint256 length, uint64 exp) external pure returns (uint64[] memory exps) { 27 | exps = new uint64[](length); 28 | for (uint256 i = 0; i < length; ++i) { 29 | exps[i] = exp; 30 | } 31 | } 32 | 33 | function push(uint256[] calldata a, uint256 b) external pure returns (uint256[] memory amounts) { 34 | amounts = new uint256[](a.length + 1); 35 | for (uint256 i = 0; i < a.length; ++i) { 36 | amounts[i] = a[i]; 37 | } 38 | amounts[a.length] = b; 39 | } 40 | 41 | function pushUInt8(uint8[] calldata a, uint8 b) external pure returns (uint8[] memory tokenTypes) { 42 | tokenTypes = new uint8[](a.length + 1); 43 | for (uint256 i = 0; i < a.length; ++i) { 44 | tokenTypes[i] = a[i]; 45 | } 46 | tokenTypes[a.length] = b; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/utils/DeployPermit2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | 6 | /// @notice helper to deploy permit2 from precompiled bytecode 7 | /// @dev useful if testing externally against permit2 and want to avoid 8 | /// recompiling entirely and requiring viaIR compilation 9 | contract DeployPermit2 is Script { 10 | address constant PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3; 11 | 12 | function deployPermit2() public returns (address) { 13 | return run(); 14 | } 15 | 16 | function run() public returns (address) { 17 | bytes memory bytecode = 18 | hex"6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f0000000000000000000000000000000000000000000000000000000000007a6903611b69577fd5a17abc3865df5c1400c0299bd4ce2eefc8114aec5f9d3dded1745783e57b9890565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"; 19 | 20 | vm.etch(PERMIT2_ADDRESS, bytecode); 21 | return PERMIT2_ADDRESS; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/utils/DeployPermit2.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {IAllowanceTransfer} from "../../src/interfaces/IAllowanceTransfer.sol"; 6 | import {ISignatureTransfer} from "../../src/interfaces/ISignatureTransfer.sol"; 7 | import {TokenProvider} from "./TokenProvider.sol"; 8 | import {PermitSignature} from "./PermitSignature.sol"; 9 | import {DeployPermit2} from "./DeployPermit2.sol"; 10 | import {Permit2} from "../../src/Permit2.sol"; 11 | 12 | contract DeployPermit2Test is Test, DeployPermit2, PermitSignature, TokenProvider { 13 | Permit2 permit2; 14 | address from; 15 | uint256 fromPrivateKey; 16 | 17 | address address0 = address(0); 18 | address address1 = address(2); 19 | 20 | uint160 defaultAmount = 10 ** 18; 21 | uint48 defaultNonce = 0; 22 | uint32 dirtyNonce = 1; 23 | uint48 defaultExpiration = uint48(block.timestamp + 5); 24 | 25 | bytes32 DOMAIN_SEPARATOR; 26 | 27 | function setUp() public { 28 | permit2 = Permit2(deployPermit2()); 29 | DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); 30 | fromPrivateKey = 0x12341234; 31 | from = vm.addr(fromPrivateKey); 32 | 33 | initializeERC20Tokens(); 34 | 35 | setERC20TestTokens(from); 36 | setERC20TestTokenApprovals(vm, from, address(permit2)); 37 | } 38 | 39 | function testDeployPermit2() public { 40 | Permit2 realPermit2 = new Permit2(); 41 | // assert bytecode equals 42 | assertEq(address(permit2).code, address(realPermit2).code); 43 | } 44 | 45 | function testAllowanceTransferSanityCheck() public { 46 | IAllowanceTransfer.PermitSingle memory permit = 47 | defaultERC20PermitAllowance(address(token0), defaultAmount, defaultExpiration, defaultNonce); 48 | bytes memory sig = getPermitSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 49 | 50 | uint256 startBalanceFrom = token0.balanceOf(from); 51 | uint256 startBalanceTo = token0.balanceOf(address0); 52 | 53 | permit2.permit(from, permit, sig); 54 | 55 | (uint160 amount,,) = permit2.allowance(from, address(token0), address(this)); 56 | 57 | assertEq(amount, defaultAmount); 58 | 59 | permit2.transferFrom(from, address0, defaultAmount, address(token0)); 60 | 61 | assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); 62 | assertEq(token0.balanceOf(address0), startBalanceTo + defaultAmount); 63 | } 64 | 65 | function testSignatureTransferSanityCheck() public { 66 | uint256 nonce = 0; 67 | ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce); 68 | bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR); 69 | 70 | uint256 startBalanceFrom = token0.balanceOf(from); 71 | uint256 startBalanceTo = token0.balanceOf(address1); 72 | 73 | ISignatureTransfer.SignatureTransferDetails memory transferDetails = 74 | ISignatureTransfer.SignatureTransferDetails({to: address1, requestedAmount: defaultAmount}); 75 | 76 | permit2.permitTransferFrom(permit, transferDetails, from, sig); 77 | 78 | assertEq(token0.balanceOf(from), startBalanceFrom - defaultAmount); 79 | assertEq(token0.balanceOf(address1), startBalanceTo + defaultAmount); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/utils/PermitSignature.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Vm} from "forge-std/Vm.sol"; 5 | import {EIP712} from "openzeppelin-contracts/contracts/utils/cryptography/draft-EIP712.sol"; 6 | import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; 7 | import {IAllowanceTransfer} from "../../src/interfaces/IAllowanceTransfer.sol"; 8 | import {ISignatureTransfer} from "../../src/interfaces/ISignatureTransfer.sol"; 9 | 10 | contract PermitSignature { 11 | Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); 12 | 13 | bytes32 public constant _PERMIT_DETAILS_TYPEHASH = 14 | keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); 15 | 16 | bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256( 17 | "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" 18 | ); 19 | 20 | bytes32 public constant _PERMIT_BATCH_TYPEHASH = keccak256( 21 | "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" 22 | ); 23 | 24 | bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); 25 | 26 | bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256( 27 | "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" 28 | ); 29 | 30 | bytes32 public constant _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = keccak256( 31 | "PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" 32 | ); 33 | 34 | function getPermitSignatureRaw( 35 | IAllowanceTransfer.PermitSingle memory permit, 36 | uint256 privateKey, 37 | bytes32 domainSeparator 38 | ) internal pure returns (uint8 v, bytes32 r, bytes32 s) { 39 | bytes32 permitHash = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); 40 | 41 | bytes32 msgHash = keccak256( 42 | abi.encodePacked( 43 | "\x19\x01", 44 | domainSeparator, 45 | keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permit.spender, permit.sigDeadline)) 46 | ) 47 | ); 48 | 49 | (v, r, s) = vm.sign(privateKey, msgHash); 50 | } 51 | 52 | function getPermitSignature( 53 | IAllowanceTransfer.PermitSingle memory permit, 54 | uint256 privateKey, 55 | bytes32 domainSeparator 56 | ) internal pure returns (bytes memory sig) { 57 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); 58 | return bytes.concat(r, s, bytes1(v)); 59 | } 60 | 61 | function getCompactPermitSignature( 62 | IAllowanceTransfer.PermitSingle memory permit, 63 | uint256 privateKey, 64 | bytes32 domainSeparator 65 | ) internal pure returns (bytes memory sig) { 66 | (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); 67 | bytes32 vs; 68 | (r, vs) = _getCompactSignature(v, r, s); 69 | return bytes.concat(r, vs); 70 | } 71 | 72 | function getCompactPermitTransferSignature( 73 | ISignatureTransfer.PermitTransferFrom memory permit, 74 | uint256 privateKey, 75 | bytes32 domainSeparator 76 | ) internal view returns (bytes memory sig) { 77 | bytes32 tokenPermissions = keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted)); 78 | bytes32 msgHash = keccak256( 79 | abi.encodePacked( 80 | "\x19\x01", 81 | domainSeparator, 82 | keccak256( 83 | abi.encode( 84 | _PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissions, address(this), permit.nonce, permit.deadline 85 | ) 86 | ) 87 | ) 88 | ); 89 | 90 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); 91 | bytes32 vs; 92 | (r, vs) = _getCompactSignature(v, r, s); 93 | return bytes.concat(r, vs); 94 | } 95 | 96 | function _getCompactSignature(uint8 vRaw, bytes32 rRaw, bytes32 sRaw) 97 | internal 98 | pure 99 | returns (bytes32 r, bytes32 vs) 100 | { 101 | uint8 v = vRaw - 27; // 27 is 0, 28 is 1 102 | vs = bytes32(uint256(v) << 255) | sRaw; 103 | return (rRaw, vs); 104 | } 105 | 106 | function getPermitBatchSignature( 107 | IAllowanceTransfer.PermitBatch memory permit, 108 | uint256 privateKey, 109 | bytes32 domainSeparator 110 | ) internal pure returns (bytes memory sig) { 111 | bytes32[] memory permitHashes = new bytes32[](permit.details.length); 112 | for (uint256 i = 0; i < permit.details.length; ++i) { 113 | permitHashes[i] = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details[i])); 114 | } 115 | bytes32 msgHash = keccak256( 116 | abi.encodePacked( 117 | "\x19\x01", 118 | domainSeparator, 119 | keccak256( 120 | abi.encode( 121 | _PERMIT_BATCH_TYPEHASH, 122 | keccak256(abi.encodePacked(permitHashes)), 123 | permit.spender, 124 | permit.sigDeadline 125 | ) 126 | ) 127 | ) 128 | ); 129 | 130 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); 131 | return bytes.concat(r, s, bytes1(v)); 132 | } 133 | 134 | function getPermitTransferSignature( 135 | ISignatureTransfer.PermitTransferFrom memory permit, 136 | uint256 privateKey, 137 | bytes32 domainSeparator 138 | ) internal view returns (bytes memory sig) { 139 | bytes32 tokenPermissions = keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted)); 140 | bytes32 msgHash = keccak256( 141 | abi.encodePacked( 142 | "\x19\x01", 143 | domainSeparator, 144 | keccak256( 145 | abi.encode( 146 | _PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissions, address(this), permit.nonce, permit.deadline 147 | ) 148 | ) 149 | ) 150 | ); 151 | 152 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); 153 | return bytes.concat(r, s, bytes1(v)); 154 | } 155 | 156 | function getPermitWitnessTransferSignature( 157 | ISignatureTransfer.PermitTransferFrom memory permit, 158 | uint256 privateKey, 159 | bytes32 typehash, 160 | bytes32 witness, 161 | bytes32 domainSeparator 162 | ) internal view returns (bytes memory sig) { 163 | bytes32 tokenPermissions = keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted)); 164 | 165 | bytes32 msgHash = keccak256( 166 | abi.encodePacked( 167 | "\x19\x01", 168 | domainSeparator, 169 | keccak256(abi.encode(typehash, tokenPermissions, address(this), permit.nonce, permit.deadline, witness)) 170 | ) 171 | ); 172 | 173 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); 174 | return bytes.concat(r, s, bytes1(v)); 175 | } 176 | 177 | function getPermitBatchTransferSignature( 178 | ISignatureTransfer.PermitBatchTransferFrom memory permit, 179 | uint256 privateKey, 180 | bytes32 domainSeparator 181 | ) internal view returns (bytes memory sig) { 182 | bytes32[] memory tokenPermissions = new bytes32[](permit.permitted.length); 183 | for (uint256 i = 0; i < permit.permitted.length; ++i) { 184 | tokenPermissions[i] = keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted[i])); 185 | } 186 | bytes32 msgHash = keccak256( 187 | abi.encodePacked( 188 | "\x19\x01", 189 | domainSeparator, 190 | keccak256( 191 | abi.encode( 192 | _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH, 193 | keccak256(abi.encodePacked(tokenPermissions)), 194 | address(this), 195 | permit.nonce, 196 | permit.deadline 197 | ) 198 | ) 199 | ) 200 | ); 201 | 202 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); 203 | return bytes.concat(r, s, bytes1(v)); 204 | } 205 | 206 | function getPermitBatchWitnessSignature( 207 | ISignatureTransfer.PermitBatchTransferFrom memory permit, 208 | uint256 privateKey, 209 | bytes32 typeHash, 210 | bytes32 witness, 211 | bytes32 domainSeparator 212 | ) internal view returns (bytes memory sig) { 213 | bytes32[] memory tokenPermissions = new bytes32[](permit.permitted.length); 214 | for (uint256 i = 0; i < permit.permitted.length; ++i) { 215 | tokenPermissions[i] = keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted[i])); 216 | } 217 | 218 | bytes32 msgHash = keccak256( 219 | abi.encodePacked( 220 | "\x19\x01", 221 | domainSeparator, 222 | keccak256( 223 | abi.encode( 224 | typeHash, 225 | keccak256(abi.encodePacked(tokenPermissions)), 226 | address(this), 227 | permit.nonce, 228 | permit.deadline, 229 | witness 230 | ) 231 | ) 232 | ) 233 | ); 234 | 235 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); 236 | return bytes.concat(r, s, bytes1(v)); 237 | } 238 | 239 | function defaultERC20PermitAllowance(address token0, uint160 amount, uint48 expiration, uint48 nonce) 240 | internal 241 | view 242 | returns (IAllowanceTransfer.PermitSingle memory) 243 | { 244 | IAllowanceTransfer.PermitDetails memory details = 245 | IAllowanceTransfer.PermitDetails({token: token0, amount: amount, expiration: expiration, nonce: nonce}); 246 | return IAllowanceTransfer.PermitSingle({ 247 | details: details, 248 | spender: address(this), 249 | sigDeadline: block.timestamp + 100 250 | }); 251 | } 252 | 253 | function defaultERC20PermitBatchAllowance(address[] memory tokens, uint160 amount, uint48 expiration, uint48 nonce) 254 | internal 255 | view 256 | returns (IAllowanceTransfer.PermitBatch memory) 257 | { 258 | IAllowanceTransfer.PermitDetails[] memory details = new IAllowanceTransfer.PermitDetails[](tokens.length); 259 | 260 | for (uint256 i = 0; i < tokens.length; ++i) { 261 | details[i] = IAllowanceTransfer.PermitDetails({ 262 | token: tokens[i], 263 | amount: amount, 264 | expiration: expiration, 265 | nonce: nonce 266 | }); 267 | } 268 | 269 | return IAllowanceTransfer.PermitBatch({ 270 | details: details, 271 | spender: address(this), 272 | sigDeadline: block.timestamp + 100 273 | }); 274 | } 275 | 276 | function defaultERC20PermitTransfer(address token0, uint256 nonce) 277 | internal 278 | view 279 | returns (ISignatureTransfer.PermitTransferFrom memory) 280 | { 281 | return ISignatureTransfer.PermitTransferFrom({ 282 | permitted: ISignatureTransfer.TokenPermissions({token: token0, amount: 10 ** 18}), 283 | nonce: nonce, 284 | deadline: block.timestamp + 100 285 | }); 286 | } 287 | 288 | function defaultERC20PermitWitnessTransfer(address token0, uint256 nonce) 289 | internal 290 | view 291 | returns (ISignatureTransfer.PermitTransferFrom memory) 292 | { 293 | return ISignatureTransfer.PermitTransferFrom({ 294 | permitted: ISignatureTransfer.TokenPermissions({token: token0, amount: 10 ** 18}), 295 | nonce: nonce, 296 | deadline: block.timestamp + 100 297 | }); 298 | } 299 | 300 | function defaultERC20PermitMultiple(address[] memory tokens, uint256 nonce) 301 | internal 302 | view 303 | returns (ISignatureTransfer.PermitBatchTransferFrom memory) 304 | { 305 | ISignatureTransfer.TokenPermissions[] memory permitted = 306 | new ISignatureTransfer.TokenPermissions[](tokens.length); 307 | for (uint256 i = 0; i < tokens.length; ++i) { 308 | permitted[i] = ISignatureTransfer.TokenPermissions({token: tokens[i], amount: 1 ** 18}); 309 | } 310 | return ISignatureTransfer.PermitBatchTransferFrom({ 311 | permitted: permitted, 312 | nonce: nonce, 313 | deadline: block.timestamp + 100 314 | }); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /test/utils/StructBuilder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IAllowanceTransfer} from "../../src/interfaces/IAllowanceTransfer.sol"; 5 | import {ISignatureTransfer} from "../../src/interfaces/ISignatureTransfer.sol"; 6 | import {AddressBuilder} from "./AddressBuilder.sol"; 7 | 8 | library StructBuilder { 9 | function fillAllowanceTransferDetail( 10 | uint256 length, 11 | address[] memory tokens, 12 | uint160 amount, 13 | address to, 14 | address[] memory owners 15 | ) external pure returns (IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails) { 16 | transferDetails = new IAllowanceTransfer.AllowanceTransferDetails[](length); 17 | for (uint256 i = 0; i < length; ++i) { 18 | transferDetails[i] = 19 | IAllowanceTransfer.AllowanceTransferDetails({from: owners[i], token: tokens[i], amount: amount, to: to}); 20 | } 21 | } 22 | 23 | function fillAllowanceTransferDetail( 24 | uint256 length, 25 | address token, 26 | uint160 amount, 27 | address to, 28 | address[] memory owners 29 | ) external pure returns (IAllowanceTransfer.AllowanceTransferDetails[] memory transferDetails) { 30 | transferDetails = new IAllowanceTransfer.AllowanceTransferDetails[](length); 31 | for (uint256 i = 0; i < length; ++i) { 32 | transferDetails[i] = 33 | IAllowanceTransfer.AllowanceTransferDetails({from: owners[i], token: token, amount: amount, to: to}); 34 | } 35 | } 36 | 37 | function fillSigTransferDetails(uint256 length, uint256 amount, address to) 38 | external 39 | pure 40 | returns (ISignatureTransfer.SignatureTransferDetails[] memory transferDetails) 41 | { 42 | return fillSigTransferDetails(amount, AddressBuilder.fill(length, to)); 43 | } 44 | 45 | function fillSigTransferDetails(uint256 amount, address[] memory tos) 46 | public 47 | pure 48 | returns (ISignatureTransfer.SignatureTransferDetails[] memory transferDetails) 49 | { 50 | transferDetails = new ISignatureTransfer.SignatureTransferDetails[](tos.length); 51 | for (uint256 i = 0; i < tos.length; ++i) { 52 | transferDetails[i] = ISignatureTransfer.SignatureTransferDetails({to: tos[i], requestedAmount: amount}); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/utils/TokenProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Vm} from "forge-std/Test.sol"; 5 | import {MockERC20} from "../mocks/MockERC20.sol"; 6 | import {MockERC721} from "../mocks/MockERC721.sol"; 7 | import {MockERC1155} from "../mocks/MockERC1155.sol"; 8 | 9 | contract TokenProvider { 10 | uint256 public constant MINT_AMOUNT_ERC20 = 100 ** 18; 11 | uint256 public constant MINT_AMOUNT_ERC1155 = 100; 12 | 13 | uint256 public constant TRANSFER_AMOUNT_ERC20 = 30 ** 18; 14 | uint256 public constant TRANSFER_AMOUNT_ERC1155 = 10; 15 | 16 | MockERC20 token0; 17 | MockERC20 token1; 18 | MockERC721 nft1; 19 | MockERC721 nft2; 20 | MockERC1155 nft3; 21 | MockERC1155 nft4; 22 | 23 | address faucet = address(0x98765); 24 | 25 | function initializeERC20Tokens() public { 26 | token0 = new MockERC20("Test0", "TEST0", 18); 27 | token1 = new MockERC20("Test1", "TEST1", 18); 28 | } 29 | 30 | function setERC20TestTokens(address from) public { 31 | token0.mint(from, MINT_AMOUNT_ERC20); 32 | token1.mint(from, MINT_AMOUNT_ERC20); 33 | } 34 | 35 | function setERC20TestTokenApprovals(Vm vm, address owner, address spender) public { 36 | vm.startPrank(owner); 37 | token0.approve(spender, type(uint256).max); 38 | token1.approve(spender, type(uint256).max); 39 | vm.stopPrank(); 40 | } 41 | 42 | function initializeNFTTokens() public { 43 | nft1 = new MockERC721("TestNFT1", "NFT1"); 44 | nft2 = new MockERC721("TestNFT2", "NFT2"); 45 | nft3 = new MockERC1155(); 46 | nft4 = new MockERC1155(); 47 | } 48 | 49 | // 721s 50 | function setNFTTestTokens(address from) public { 51 | // mint with id 1 52 | nft1.mint(from, 1); 53 | // mint with id 2 54 | nft2.mint(from, 2); 55 | // mint 10 with id 1 56 | nft3.mint(from, 1, MINT_AMOUNT_ERC1155); 57 | // mint 10 with id 2 58 | nft4.mint(from, 2, MINT_AMOUNT_ERC1155); 59 | } 60 | 61 | function setNFTTestTokenApprovals(Vm vm, address owner, address spender) public { 62 | vm.startPrank(owner); 63 | nft1.approve(spender, 1); 64 | nft2.approve(spender, 2); 65 | nft3.setApprovalForAll(spender, true); 66 | nft4.setApprovalForAll(spender, true); 67 | vm.stopPrank(); 68 | } 69 | } 70 | --------------------------------------------------------------------------------