├── .github ├── CODEOWNERS └── workflows │ ├── public-release-v1.0.0.yml │ ├── public-release.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SECURITY.md ├── example.env.secrets ├── foundry.toml ├── remappings.txt ├── script ├── 1-deploy-deterministic-PermitC.sh ├── DeployDeterministicPermitC.s.sol └── test │ └── generate-coverage-report.sh ├── src ├── CollateralizedPausableFlags.sol ├── Constants.sol ├── DataTypes.sol ├── Errors.sol ├── PermitC.sol ├── interfaces │ ├── IImmutableCreate2Factory.sol │ └── IPermitC.sol ├── libraries │ └── PermitHash.sol └── openzeppelin-optimized │ ├── EIP712.sol │ └── Ownable.sol └── test ├── Base.t.sol ├── DeterministicDeployment.t.sol ├── PermitC1155.ApprovalTransfer.t.sol ├── PermitC1155.PositionApprovalTransfer.t.sol ├── PermitC1155.SignatureTransfer.t.sol ├── PermitC20.ApprovalTransfer.t.sol ├── PermitC20.PositionApprovalTransfer.t.sol ├── PermitC20.SignatureTransfer.sol ├── PermitC721.ApprovalTransfer.t.sol ├── PermitC721.SignatureTransfer.t.sol ├── approve ├── approve.t.sol └── approve.tree ├── benchmarks ├── BenchmarkBase.t.sol ├── forge-metering │ ├── BenchmarkApprovals.t.sol │ ├── BenchmarkApprovedTransferERC20.t.sol │ ├── BenchmarkCancelNonce.t.sol │ └── BenchmarkPermitTransferFromERC20.t.sol ├── manual-metering │ ├── BenchmarkApprovals.t.sol │ ├── BenchmarkApprovedTransferERC20.t.sol │ ├── BenchmarkCancelNonce.t.sol │ └── BenchmarkPermitTransferFromERC20.t.sol └── mocks │ ├── ContractMock.sol │ └── TestERC20.sol ├── invalidateUnorderedApprovalNonces ├── invalidateUnorderedApprovalNonces.t.sol └── invalidateUnorderedApprovalNonces.tree ├── invalidateUnorderedSignatureNonces ├── invalidateUnorderedSignatureNonces.t.sol └── invalidateUnorderedSignatureNonces.tree ├── lockdown ├── lockdown.t.sol └── lockdown.tree ├── mocks ├── ERC1155Mock.sol ├── ERC1155Reverter.sol ├── ERC1271ContractSignerMock.sol ├── ERC1271InvalidContractSignerMock.sol ├── ERC20Mock.sol ├── ERC20Reverter.sol ├── ERC721Mock.sol └── ERC721Reverter.sol ├── permitTransferFrom ├── permitTransferFrom.t.sol ├── permitTransferFrom.tree └── permitTransferFromBatch.tree ├── permitTransferFromWithAdditionalData ├── permitTransferFromWithAdditionalData.tree └── permitTransferFromWithAdditionalDataBatch.tree ├── setApprovalForAll └── setApprovalForAll.tree ├── transferFrom ├── transferFrom.tree └── transferFromBatch.tree ├── updateApprovalBySignature ├── updateApprovalBySignature.tree └── updateApprovalBySignatureBatch.tree └── updateApprovalForAllBySignature └── updateApprovalForAllBySignature.tree /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @LB-mitch 2 | * @nathanglb 3 | * @LB-thomasp -------------------------------------------------------------------------------- /.github/workflows/public-release-v1.0.0.yml: -------------------------------------------------------------------------------- 1 | name: Release v1.0.0 Code to Public Repo 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'v1.0.0' 7 | 8 | jobs: 9 | squash_commits: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Source Repository 14 | uses: actions/checkout@v4 15 | - name: Setup environment variables 16 | uses: rlespinasse/github-slug-action@v4 17 | - uses: webfactory/ssh-agent@v0.8.0 18 | with: 19 | ssh-private-key: ${{ secrets.PUBLIC_REPO_SSH_KEY }} 20 | 21 | - name: Squash & Push Code to Public Repo 22 | run: | 23 | current_date=$(date '+%Y%m%d') 24 | git checkout -b "$GITHUB_REF_SLUG-$current_date" 25 | git config --global user.email "no-reply@limitbreak.com" 26 | git config --global user.name "Limit Break Inc" 27 | git reset $(git commit-tree HEAD^{tree} -m "Code Release from $GITHUB_REF_SLUG-$current_date") 28 | git remote add public-repo git@github.com:limitbreakinc/PermitC.git 29 | git push -f public-repo "$GITHUB_REF_SLUG-$current_date" 30 | -------------------------------------------------------------------------------- /.github/workflows/public-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Code to Public Repo 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'public-release-pre-audit' 7 | 8 | jobs: 9 | squash_commits: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Source Repository 14 | uses: actions/checkout@v4 15 | - name: Setup environment variables 16 | uses: rlespinasse/github-slug-action@v4 17 | - uses: webfactory/ssh-agent@v0.8.0 18 | with: 19 | ssh-private-key: ${{ secrets.PUBLIC_REPO_SSH_KEY }} 20 | 21 | - name: Squash & Push Code to Public Repo 22 | run: | 23 | current_date=$(date '+%Y%m%d') 24 | git checkout -b "$GITHUB_REF_SLUG-$current_date" 25 | git config --global user.email "no-reply@limitbreak.com" 26 | git config --global user.name "Limit Break Inc" 27 | git reset $(git commit-tree HEAD^{tree} -m "Code Release from $GITHUB_REF_SLUG-$current_date") 28 | git remote add public-repo git@github.com:limitbreakinc/PermitC.git 29 | git push -f public-repo "$GITHUB_REF_SLUG-$current_date" 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env* 15 | 16 | # Test coverage files 17 | coverage/ 18 | lcov* 19 | 20 | venv/ 21 | slither.md 22 | 23 | .vscode/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | [submodule "lib/solmate"] 8 | path = lib/solmate 9 | url = https://github.com/transmissions11/solmate 10 | [submodule "lib/forge-gas-metering"] 11 | path = lib/forge-gas-metering 12 | url = https://github.com/emo-eth/forge-gas-metering 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2023 Limit Break, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PermitC 2 | 3 | **Advanced approval system for ERC20, ERC721 and ERC1155** 4 | 5 | PermitC extends the Uniswap Permit2 system for ERC20, ERC721 and ERC1155 tokens. This is an advanced approval system which allows for easier and more secure approvals across applications. Abstracting the approval process and adding in expirations allows users to be more insulated from the advanced logic of protocols which are more likely to be exploited. 6 | 7 | 8 | To learn more about the system this is modeled after visit the [permit2 github](https://github.com/Uniswap/permit2) and the great explainer on approval abstraction [here](https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2). 9 | 10 | ## Features 11 | - **Time Bound Approvals:** Approvals via PermitC include an expiration timestamp, allowing for an approval to be only set for a specific period of time. If it’s used past the expiration timestamp, it will be invalid. 12 | - **One Click Approval Revoke:** To quickly revoke all approvals, you can call a single function `lockdown` which will invalidate all outstanding approvals. 13 | - **Signature Based Approvals:** Signatures can be provided for operators to increase approval on chain for multi-use approvals, or permit transfer for one off signatures. 14 | - **Additional Data Validation:** Signatures can use the `permitTransferFromWithAdditionalData` calls to append additional validation logic, such as approving only a specific order or set of orders without opening approvals to other functions on the operator contract. 15 | - **Unordered Execution:** Nonces retain the same functionality as Permit2, meaning they can be approved and executed in any order and prevent replay. 16 | - **Order Based Approvals:** Reusable signatures scoped to a specific order ID to allow for partial trades / fills allowing for gasless outstanding orders to be leveraged. Useful in ERC1155 sales and ERC20 limit orders. 17 | 18 | 19 | ## Usage 20 | PermitC is designed to be directly queried in your contracts, and acts as an intermediary between the user and the protocol. An example implementation would be: 21 | 22 | ``` 23 | pragma solidity ^0.8.4; 24 | 25 | import {SignatureECDSA} from "@permitC/DataTypes.sol"; 26 | import "@permitC/interfaces/IPermitC.sol"; 27 | 28 | contract ExampleContract { 29 | 30 | // ... constructuor logic ... 31 | 32 | function executeOrder(OrderDetails details, uint256 permitNonce, uint48 permitExpiration, uint8 v, bytes32 r, bytes32 s) public { 33 | // ... order validation and execution ... 34 | 35 | SignatureECDSA signedPermit = ({v: v, r: r, s: s}); 36 | permitC.permitTransferFromERC721(details.token, details.id, permitNonce, permitExpiration, details.from, details.to signedPermit); 37 | 38 | // ... post transfer logic ... 39 | } 40 | } 41 | ``` 42 | 43 | ## Backwards Compatability 44 | To implement PermitC with an existing protocol, a router contract would need to be launched which can act as the middleware for PermitC and the destination protocol. An example workflow is: 45 | 46 | 1. Protocol develops a router which acts as a proxy between PermitC and the base protocol. This router takes a permit signature to transfer a token from the user to itself, then acts on behalf of the user. 47 | 2. User sets base approval on PermitC - this abstracts the approvals from protocols and will only transfer tokens if valid signed messages are provided. 48 | 3. User signs a message with `permitTransferWithAdditionalData` including details on their transaction. 49 | 4. Router executes transaction and returns output to user 50 | 51 | 52 | ## Deployment 53 | PermitC is designed to be deployable by anyone to any EVM chain at a deterministic address to match all other chains. We have simplified this process into a shell script which will check for all dependencies, deploy them if they do not exist and then deploy PermitC. Follow the below steps to complete deployment: 54 | 55 | 1. Fund a personal account. We'll be potentially deploying 4 contracts, so make sure to have sufficient ETH (or chain equivalent native funds) to cover the expenses. 56 | 2. Run the command `cp example.env.secrets .env.secrets` 57 | 3. Fill in the RPC for the chain you're deploying to, the private and public key for the address you funded and the ETHERSCAN_API_KEY if applicable. 58 | 4. Run the command `./script/1-deploy-deterministic-PermitC.sh --gas-price {} --priority-gas-price {}` - NOTE: gas price and priority gas price are in human readble numbers, they are converted to the correct units within the script. 59 | 5. Confirm the input is as expected on your terminal and type `yes` to deploy. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | The LimitBreak team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/limit-break-inc/PermitC/security/advisories/new) tab. 6 | 7 | The LimitBreak team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 8 | 9 | ## LimitBreak Security Contact 10 | 11 | For other security concerns or bugs please contact security@limitbreak.com. -------------------------------------------------------------------------------- /example.env.secrets: -------------------------------------------------------------------------------- 1 | RPC_URL= 2 | ETHERSCAN_API_KEY= 3 | USER_ADDRESS= 4 | PK= -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | solc_version = "0.8.24" 7 | 8 | optimizer = true 9 | optimizer_runs = 1000000 10 | 11 | [fuzz] 12 | runs = 256 13 | max_test_rejects = 10240000 -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 3 | forge-std/=lib/forge-std/src/ 4 | @openzeppelin/=lib/openzeppelin-contracts/ 5 | @rari-capital/solmate/=lib/solmate/ 6 | forge-gas-metering/=lib/forge-gas-metering/ -------------------------------------------------------------------------------- /script/1-deploy-deterministic-PermitC.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -f .env.secrets ] 4 | then 5 | export $(cat .env.secrets | xargs) 6 | else 7 | echo "Please set your .env.secrets file" 8 | exit 1 9 | fi 10 | 11 | # Initialize variables 12 | GAS_PRICE="" 13 | PRIORITY_GAS_PRICE="" 14 | 15 | # Function to display usage 16 | usage() { 17 | echo "Usage: $0 --gas-price --priority-gas-price " 18 | exit 1 19 | } 20 | 21 | # Process arguments 22 | while [[ "$#" -gt 0 ]]; do 23 | case $1 in 24 | --gas-price) GAS_PRICE=$(($2 * 1000000000)); shift ;; 25 | --priority-gas-price) PRIORITY_GAS_PRICE=$(($2 * 1000000000)); shift ;; 26 | *) usage ;; 27 | esac 28 | shift 29 | done 30 | 31 | # Check if all parameters are set 32 | if [ -z "$GAS_PRICE" ] || [ -z "$PRIORITY_GAS_PRICE" ]; then 33 | usage 34 | fi 35 | 36 | # Check if the user's balance is sufficient - required .1 ETH or equivalent 37 | USER_BALANCE=$(cast balance $USER_ADDRESS --rpc-url $RPC_URL) 38 | 39 | if [ $USER_BALANCE -lt 100000000000000000 ]; then 40 | echo "Insufficient balance. Please top up your account." 41 | exit 1 42 | fi 43 | 44 | # Identify if any prerequisites are missing: 45 | # 1. Keyless Create2 Address (0x7A0D94F55792C434d74a40883C6ed8545E406D12) 46 | KEYLESS_CREATE2_BYTECODE=$(cast code 0x7A0D94F55792C434d74a40883C6ed8545E406D12 --rpc-url $RPC_URL) 47 | if [ "$KEYLESS_CREATE2_BYTECODE" = "0x" ]; then 48 | # Send .01 ETH to keyless deployer 49 | cast send 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1 --value 10000000000000000 --gas-price $GAS_PRICE --priority-gas-price $PRIORITY_GAS_PRICE --rpc-url $RPC_URL --private-key $PK 50 | 51 | # Create Keyless Create2 Address 52 | cast publish --rpc-url ${RPC_URL} 0xf87e8085174876e800830186a08080ad601f80600e600039806000f350fe60003681823780368234f58015156014578182fd5b80825250506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222 53 | fi 54 | 55 | # 2. Inefficient Create2 Address (0xcfA3A7637547094fF06246817a35B8333C315196) - deployed in script 56 | INEFFICIENT_CREATE2_BYTECODE=$(cast code 0xcfA3A7637547094fF06246817a35B8333C315196 --rpc-url $RPC_URL) 57 | if [ "$INEFFICIENT_CREATE2_BYTECODE" = "0x" ]; then 58 | cast send --rpc-url ${RPC_URL} --private-key ${PK} 0x7a0d94f55792c434d74a40883c6ed8545e406d12 0x608060405234801561001057600080fd5b50610833806100206000396000f3fe60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032 59 | fi 60 | # 3. Immutable Create2 Factory (0x0000000000ffe8b47b3e2130213b802212439497) - deployed in script 61 | IMMUTABLE_CREATE2_FACTORY_BYTECODE=$(cast code 0x0000000000ffe8b47b3e2130213b802212439497 --rpc-url $RPC_URL) 62 | if [ "$IMMUTABLE_CREATE2_FACTORY_BYTECODE" = "0x" ]; then 63 | cast send --rpc-url ${RPC_URL} --private-key ${PK} 0xcfa3a7637547094ff06246817a35b8333c315196 0x64e030870000000000000000000000000000000000000000f4b0218f13a6440a6f02000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000853608060405234801561001057600080fd5b50610833806100206000396000f3fe60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a003200000000000000000000000000 64 | fi 65 | 66 | 67 | echo "" 68 | echo "============= DEPLOYING PERMITC =============" 69 | 70 | echo "Gas Price (wei): $GAS_PRICE" 71 | echo "Priority Gas Price (wei): $PRIORITY_GAS_PRICE" 72 | echo "RPC URL: $RPC_URL" 73 | echo "User Address: $USER_ADDRESS" 74 | echo "User Balance: $USER_BALANCE" 75 | read -p "Do you want to proceed? (yes/no) " yn 76 | 77 | case $yn in 78 | yes ) echo ok, we will proceed;; 79 | no ) echo exiting...; 80 | exit;; 81 | * ) echo invalid response; 82 | exit 1;; 83 | esac 84 | 85 | forge script script/DeployDeterministicPermitC.s.sol:DeployDeterministicPermitC \ 86 | --gas-price $GAS_PRICE \ 87 | --priority-gas-price $PRIORITY_GAS_PRICE \ 88 | --rpc-url $RPC_URL \ 89 | --broadcast \ 90 | --optimizer-runs 20000 \ 91 | --verify -------------------------------------------------------------------------------- /script/test/generate-coverage-report.sh: -------------------------------------------------------------------------------- 1 | forge coverage --via-ir --report lcov 2 | 3 | lcov --remove ./lcov.info -o ./lcov.info.pruned '/test/mocks/*' 'test/mocks/*' 4 | 5 | genhtml lcov.info.pruned --output-directory coverage 6 | 7 | open coverage/index.html -------------------------------------------------------------------------------- /src/CollateralizedPausableFlags.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | /* 5 | @@@@@@@@@@@@@@ 6 | @@@@@@@@@@@@@@@@@@( 7 | @@@@@@@@@@@@@@@@@@@@@ 8 | @@@@@@@@@@@@@@@@@@@@@@@@ 9 | #@@@@@@@@@@@@@@ 10 | @@@@@@@@@@@@ 11 | @@@@@@@@@@@@@@* @@@@@@@@@@@@ 12 | @@@@@@@@@@@@@@@ @ @@@@@@@@@@@@ 13 | @@@@@@@@@@@@@@@ @ @@@@@@@@@@@ 14 | @@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@ 15 | @@@@@@@@@@@@@@@ #@@ @@@@@@@@@@@@/ 16 | @@@@@@@@@@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@ 17 | @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ 18 | @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ 19 | @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ 20 | @@@@@@@@@@@@@@@ @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@ 21 | @@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@ 22 | @@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@ 23 | @@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@ 24 | @@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@ 25 | @@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@& 26 | @@@@@@@@@@@@@@ *@@@@@@@ (@@@@@@@@@@@@ 27 | @@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@@@@@@@@ 28 | @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 29 | @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 30 | @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 31 | .@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 32 | @@@@@@@@@@@@@@% @@@@@@@@@@@@@@@@@@@@@@@@( 33 | @@@@@@@@@@@@@@@ 34 | @@@@@@@@@@@@@@@ 35 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 36 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 37 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@& 38 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 39 | 40 | * @title CollateralizedPausableFlags 41 | * @custom:version 1.0.0 42 | * @author Limit Break, Inc. 43 | * @description Collateralized Pausable Flags is an extension for contracts 44 | * that require features to be pausable in the event of potential 45 | * or actual threats without incurring a storage read overhead cost 46 | * during normal operations by using contract starting balance as 47 | * a signal for checking the paused state. 48 | * 49 | * Using contract balance to enable checking paused state creates an 50 | * economic penalty for developers that deploy code that can be 51 | * exploited as well as an economic incentive (recovery of collateral) 52 | * for them to mitigate the threat. 53 | * 54 | * Developers implementing Collateralized Pausable Flags should consider 55 | * their risk mitigation strategy and ensure funds are readily available 56 | * for pausing if ever necessary by setting an appropriate threshold 57 | * value and considering use of an escrow contract that can initiate the 58 | * pause with funds. 59 | * 60 | * There is no restriction on the depositor as this can be easily 61 | * circumvented through a `SELFDESTRUCT` opcode. 62 | * 63 | * Developers must be aware of potential outflows from the contract that 64 | * could reduce collateral below the pausable check threshold and protect 65 | * against those methods when pausing is required. 66 | */ 67 | abstract contract CollateralizedPausableFlags { 68 | 69 | /// @dev Emitted when the pausable flags are updated 70 | event PausableFlagsUpdated(uint256 previousFlags, uint256 newFlags); 71 | 72 | /// @dev Thrown when an execution path requires a flag to not be paused but it is paused 73 | error CollateralizedPausableFlags__Paused(); 74 | /// @dev Thrown when an executin path requires a flag to be paused but it is not paused 75 | error CollateralizedPausableFlags__NotPaused(); 76 | /// @dev Thrown when a call to withdraw funds fails 77 | error CollateralizedPausableFlags__WithdrawFailed(); 78 | 79 | /// @dev Immutable variable that defines the native funds threshold before flags are checked 80 | uint256 private immutable nativeValueToCheckPauseState; 81 | /// @dev Flags for current pausable state, each bit is considered a separate flag 82 | uint256 private pausableFlags; 83 | 84 | /// @dev Immutable pointer for the _requireNotPaused function to use based on value threshold 85 | function(uint256) internal view immutable _requireNotPaused; 86 | /// @dev Immutable pointer for the _requirePaused function to use based on value threshold 87 | function(uint256) internal view immutable _requirePaused; 88 | /// @dev Immutable pointer for the _getPausableFlags function to use based on value threshold 89 | function() internal view returns (uint256) immutable _getPausableFlags; 90 | 91 | constructor(uint256 _nativeValueToCheckPauseState) { 92 | // Optimizes value check at runtime by reducing the stored immutable 93 | // value by 1 so that greater than can be used instead of greater 94 | // than or equal while allowing the deployment parameter to reflect 95 | // the value at which the deployer wants to trigger pause checking. 96 | // Example: 97 | // Constructed with a value of 1000 98 | // Immutable value stored is 999 99 | // State checking enabled at 1000 units deposited because 100 | // 1000 > 999 evaluates true 101 | if (_nativeValueToCheckPauseState > 0) { 102 | unchecked { 103 | _nativeValueToCheckPauseState -= 1; 104 | } 105 | _requireNotPaused = _requireNotPausedWithCollateralCheck; 106 | _requirePaused = _requirePausedWithCollateralCheck; 107 | _getPausableFlags = _getPausableFlagsWithCollateralCheck; 108 | } else { 109 | _requireNotPaused = _requireNotPausedWithoutCollateralCheck; 110 | _requirePaused = _requirePausedWithoutCollateralCheck; 111 | _getPausableFlags = _getPausableFlagsWithoutCollateralCheck; 112 | } 113 | 114 | nativeValueToCheckPauseState = _nativeValueToCheckPauseState; 115 | } 116 | 117 | /** 118 | * @dev Modifier to make a function callable only when the specified flags are not paused 119 | * @dev Throws when any of the flags specified are paused 120 | * 121 | * @param _flags The flags to check for pause state 122 | */ 123 | modifier whenNotPaused(uint256 _flags) { 124 | _requireNotPaused(_flags); 125 | _; 126 | } 127 | 128 | /** 129 | * @dev Modifier to make a function callable only when the specified flags are paused 130 | * @dev Throws when any of the flags specified are not paused 131 | * 132 | * @param _flags The flags to check for pause state 133 | */ 134 | modifier whenPaused(uint256 _flags) { 135 | _requirePaused(_flags); 136 | _; 137 | } 138 | 139 | /** 140 | * @dev Modifier to make a function callable only by a permissioned account 141 | * @dev Throws when the caller does not have permission 142 | */ 143 | modifier onlyPausePermissionedCaller() { 144 | _requireCallerHasPausePermissions(); 145 | _; 146 | } 147 | 148 | /** 149 | * @notice Updates the pausable flags settings 150 | * 151 | * @dev Throws when the caller does not have permission 152 | * @dev **NOTE:** Pausable flag settings will only take effect if contract balance exceeds 153 | * @dev `nativeValueToPause` 154 | * 155 | * @dev

Postconditions:

156 | * @dev 1. address(this).balance increases by msg.value 157 | * @dev 2. `pausableFlags` is set to the new value 158 | * @dev 3. Emits a PausableFlagsUpdated event 159 | * 160 | * @param _pausableFlags The new pausable flags to set 161 | */ 162 | function pause(uint256 _pausableFlags) external payable onlyPausePermissionedCaller { 163 | _setPausableFlags(_pausableFlags); 164 | } 165 | 166 | /** 167 | * @notice Allows any account to supply funds for enabling the pausable checks 168 | * 169 | * @dev **NOTE:** The threshold check for pausable collateral does not pause 170 | * @dev any functions unless the associated pausable flag is set. 171 | */ 172 | function pausableDepositCollateral() external payable { 173 | // thank you for your contribution to safety 174 | } 175 | 176 | /** 177 | * @notice Resets all pausable flags to unpaused and withdraws funds 178 | * 179 | * @dev Throws when the caller does not have permission 180 | * 181 | * @dev

Postconditions:

182 | * @dev 1. `pausableFlags` is set to zero 183 | * @dev 2. Emits a PausableFlagsUpdated event 184 | * @dev 3. Transfers `withdrawAmount` of native funds to `withdrawTo` if non-zero 185 | * 186 | * @param withdrawTo The address to withdraw the collateral to 187 | * @param withdrawAmount The amount of collateral to withdraw 188 | */ 189 | function unpause(address withdrawTo, uint256 withdrawAmount) external onlyPausePermissionedCaller { 190 | _setPausableFlags(0); 191 | 192 | if (withdrawAmount > 0) { 193 | (bool success, ) = withdrawTo.call{value: withdrawAmount}(""); 194 | if(!success) revert CollateralizedPausableFlags__WithdrawFailed(); 195 | } 196 | } 197 | 198 | /** 199 | * @notice Returns collateralized pausable configuration information 200 | * 201 | * @return _nativeValueToCheckPauseState The collateral required to enable pause state checking 202 | * @return _pausableFlags The current pausable flags set, only checked when collateral met 203 | */ 204 | function pausableConfigurationSettings() external view returns( 205 | uint256 _nativeValueToCheckPauseState, 206 | uint256 _pausableFlags 207 | ) { 208 | unchecked { 209 | _nativeValueToCheckPauseState = nativeValueToCheckPauseState + 1; 210 | _pausableFlags = pausableFlags; 211 | } 212 | } 213 | 214 | /** 215 | * @notice Updates the `pausableFlags` variable and emits a PausableFlagsUpdated event 216 | * 217 | * @param _pausableFlags The new pausable flags to set 218 | */ 219 | function _setPausableFlags(uint256 _pausableFlags) internal { 220 | uint256 previousFlags = pausableFlags; 221 | 222 | pausableFlags = _pausableFlags; 223 | 224 | emit PausableFlagsUpdated(previousFlags, _pausableFlags); 225 | } 226 | 227 | /** 228 | * @notice Checks the current pause state of the supplied flags and reverts if any are paused 229 | * 230 | * @dev *Should* be called prior to any transfers of native funds out of the contract for efficiency 231 | * @dev Throws when the native funds balance is greater than the value to enable pausing AND 232 | * @dev one or more of the supplied `_flags` is paused. 233 | * 234 | * @param _flags The flags to check for pause state 235 | */ 236 | function _requireNotPausedWithCollateralCheck(uint256 _flags) private view { 237 | if (_nativeBalanceSubMsgValue() > nativeValueToCheckPauseState) { 238 | if (pausableFlags & _flags > 0) { 239 | revert CollateralizedPausableFlags__Paused(); 240 | } 241 | } 242 | } 243 | 244 | /** 245 | * @notice Checks the current pause state of the supplied flags and reverts if any are paused 246 | * 247 | * @dev Throws when one or more of the supplied `_flags` is paused. 248 | * 249 | * @param _flags The flags to check for pause state 250 | */ 251 | function _requireNotPausedWithoutCollateralCheck(uint256 _flags) private view { 252 | if (pausableFlags & _flags > 0) { 253 | revert CollateralizedPausableFlags__Paused(); 254 | } 255 | } 256 | 257 | /** 258 | * @notice Checks the current pause state of the supplied flags and reverts if none are paused 259 | * 260 | * @dev *Should* be called prior to any transfers of native funds out of the contract for efficiency 261 | * @dev Throws when the native funds balance is not greater than the value to enable pausing OR 262 | * @dev none of the supplied `_flags` are paused. 263 | * 264 | * @param _flags The flags to check for pause state 265 | */ 266 | function _requirePausedWithCollateralCheck(uint256 _flags) private view { 267 | if (_nativeBalanceSubMsgValue() <= nativeValueToCheckPauseState) { 268 | revert CollateralizedPausableFlags__NotPaused(); 269 | } else if (pausableFlags & _flags == 0) { 270 | revert CollateralizedPausableFlags__NotPaused(); 271 | } 272 | } 273 | 274 | /** 275 | * @notice Checks the current pause state of the supplied flags and reverts if none are paused 276 | * 277 | * @dev Throws when none of the supplied `_flags` are paused. 278 | * 279 | * @param _flags The flags to check for pause state 280 | */ 281 | function _requirePausedWithoutCollateralCheck(uint256 _flags) private view { 282 | if (pausableFlags & _flags == 0) { 283 | revert CollateralizedPausableFlags__NotPaused(); 284 | } 285 | } 286 | 287 | /** 288 | * @notice Returns the current state of the pausable flags 289 | * 290 | * @dev Will return zero if the native funds balance is not greater than the value to enable pausing 291 | * 292 | * @return _pausableFlags The current state of the pausable flags 293 | */ 294 | function _getPausableFlagsWithCollateralCheck() private view returns(uint256 _pausableFlags) { 295 | if (_nativeBalanceSubMsgValue() > nativeValueToCheckPauseState) { 296 | _pausableFlags = pausableFlags; 297 | } 298 | } 299 | 300 | /** 301 | * @notice Returns the current state of the pausable flags 302 | * 303 | * @return _pausableFlags The current state of the pausable flags 304 | */ 305 | function _getPausableFlagsWithoutCollateralCheck() private view returns(uint256 _pausableFlags) { 306 | _pausableFlags = pausableFlags; 307 | } 308 | 309 | /** 310 | * @notice Returns the current contract balance minus the value sent with the call 311 | * 312 | * @dev This is expected to be the contract balance at the beginning of a function call 313 | * @dev to efficiently determine whether a contract has the necessary collateral to enable 314 | * @dev the pausable flags checking for contracts that hold native token funds. 315 | * @dev This should **NOT** be used in any way to determine current balance for contract logic 316 | * @dev other than its intended purpose for pause state checking activation. 317 | */ 318 | function _nativeBalanceSubMsgValue() private view returns (uint256 _value) { 319 | unchecked { 320 | _value = address(this).balance - msg.value; 321 | } 322 | } 323 | 324 | /** 325 | * @dev To be implemented by an inheriting contract for authorization to `pause` and `unpause` 326 | * @dev functions as well as any functions in the inheriting contract that utilize the 327 | * @dev `onlyPausePermissionedCaller` modifier. 328 | * 329 | * @dev Implementing contract function **MUST** throw when the caller is not permissioned 330 | */ 331 | function _requireCallerHasPausePermissions() internal view virtual; 332 | } -------------------------------------------------------------------------------- /src/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @dev Constant bytes32 value of 0x000...000 5 | bytes32 constant ZERO_BYTES32 = bytes32(0); 6 | 7 | /// @dev Constant value of 0 8 | uint256 constant ZERO = 0; 9 | /// @dev Constant value of 1 10 | uint256 constant ONE = 1; 11 | 12 | /// @dev Constant value representing an open order in storage 13 | uint8 constant ORDER_STATE_OPEN = 0; 14 | /// @dev Constant value representing a filled order in storage 15 | uint8 constant ORDER_STATE_FILLED = 1; 16 | /// @dev Constant value representing a cancelled order in storage 17 | uint8 constant ORDER_STATE_CANCELLED = 2; 18 | 19 | /// @dev Constant value representing the ERC721 token type for signatures and transfer hooks 20 | uint256 constant TOKEN_TYPE_ERC721 = 721; 21 | /// @dev Constant value representing the ERC1155 token type for signatures and transfer hooks 22 | uint256 constant TOKEN_TYPE_ERC1155 = 1155; 23 | /// @dev Constant value representing the ERC20 token type for signatures and transfer hooks 24 | uint256 constant TOKEN_TYPE_ERC20 = 20; 25 | 26 | /// @dev Constant value to mask the upper bits of a signature that uses a packed `vs` value to extract `s` 27 | bytes32 constant UPPER_BIT_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 28 | 29 | /// @dev EIP-712 typehash used for validating signature based stored approvals 30 | bytes32 constant UPDATE_APPROVAL_TYPEHASH = 31 | keccak256("UpdateApprovalBySignature(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 approvalExpiration,uint256 sigDeadline,uint256 masterNonce)"); 32 | 33 | /// @dev EIP-712 typehash used for validating a single use permit without additional data 34 | bytes32 constant SINGLE_USE_PERMIT_TYPEHASH = 35 | keccak256("PermitTransferFrom(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce)"); 36 | 37 | /// @dev EIP-712 typehash used for validating a single use permit with additional data 38 | string constant SINGLE_USE_PERMIT_TRANSFER_ADVANCED_TYPEHASH_STUB = 39 | "PermitTransferFromWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce,"; 40 | 41 | /// @dev EIP-712 typehash used for validating an order permit that updates storage as it fills 42 | string constant PERMIT_ORDER_ADVANCED_TYPEHASH_STUB = 43 | "PermitOrderWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 salt,address operator,uint256 expiration,uint256 masterNonce,"; 44 | 45 | /// @dev Pausable flag for stored approval transfers of ERC721 assets 46 | uint256 constant PAUSABLE_APPROVAL_TRANSFER_FROM_ERC721 = 1 << 0; 47 | /// @dev Pausable flag for stored approval transfers of ERC1155 assets 48 | uint256 constant PAUSABLE_APPROVAL_TRANSFER_FROM_ERC1155 = 1 << 1; 49 | /// @dev Pausable flag for stored approval transfers of ERC20 assets 50 | uint256 constant PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20 = 1 << 2; 51 | 52 | /// @dev Pausable flag for single use permit transfers of ERC721 assets 53 | uint256 constant PAUSABLE_PERMITTED_TRANSFER_FROM_ERC721 = 1 << 3; 54 | /// @dev Pausable flag for single use permit transfers of ERC1155 assets 55 | uint256 constant PAUSABLE_PERMITTED_TRANSFER_FROM_ERC1155 = 1 << 4; 56 | /// @dev Pausable flag for single use permit transfers of ERC20 assets 57 | uint256 constant PAUSABLE_PERMITTED_TRANSFER_FROM_ERC20 = 1 << 5; 58 | 59 | /// @dev Pausable flag for order fill transfers of ERC1155 assets 60 | uint256 constant PAUSABLE_ORDER_TRANSFER_FROM_ERC1155 = 1 << 6; 61 | /// @dev Pausable flag for order fill transfers of ERC20 assets 62 | uint256 constant PAUSABLE_ORDER_TRANSFER_FROM_ERC20 = 1 << 7; -------------------------------------------------------------------------------- /src/DataTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @dev Storage data struct for stored approvals and order approvals 5 | struct PackedApproval { 6 | // Only used for partial fill position 1155 transfers 7 | uint8 state; 8 | // Amount allowed 9 | uint200 amount; 10 | // Permission expiry 11 | uint48 expiration; 12 | } 13 | 14 | /// @dev Calldata data struct for order fill amounts 15 | struct OrderFillAmounts { 16 | uint256 orderStartAmount; 17 | uint256 requestedFillAmount; 18 | uint256 minimumFillAmount; 19 | } -------------------------------------------------------------------------------- /src/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @dev Thrown when a stored approval exceeds type(uint200).max 5 | error PermitC__AmountExceedsStorageMaximum(); 6 | 7 | /// @dev Thrown when a transfer amount requested exceeds the permitted amount 8 | error PermitC__ApprovalTransferExceededPermittedAmount(); 9 | 10 | /// @dev Thrown when a transfer is requested after the permit has expired 11 | error PermitC__ApprovalTransferPermitExpiredOrUnset(); 12 | 13 | /// @dev Thrown when attempting to close an order by an account that is not the owner or operator 14 | error PermitC__CallerMustBeOwnerOrOperator(); 15 | 16 | /// @dev Thrown when attempting to approve a token type that is not valid for PermitC 17 | error PermitC__InvalidTokenType(); 18 | 19 | /// @dev Thrown when attempting to invalidate a nonce that has already been used 20 | error PermitC__NonceAlreadyUsedOrRevoked(); 21 | 22 | /// @dev Thrown when attempting to restore a nonce that has not been used 23 | error PermitC__NonceNotUsedOrRevoked(); 24 | 25 | /// @dev Thrown when attempting to fill an order that has already been filled or cancelled 26 | error PermitC__OrderIsEitherCancelledOrFilled(); 27 | 28 | /// @dev Thrown when a transfer amount requested exceeds the permitted amount 29 | error PermitC__SignatureTransferExceededPermittedAmount(); 30 | 31 | /// @dev Thrown when a transfer is requested after the permit has expired 32 | error PermitC__SignatureTransferExceededPermitExpired(); 33 | 34 | /// @dev Thrown when attempting to use an advanced permit typehash that is not registered 35 | error PermitC__SignatureTransferPermitHashNotRegistered(); 36 | 37 | /// @dev Thrown when a permit signature is invalid 38 | error PermitC__SignatureTransferInvalidSignature(); 39 | 40 | /// @dev Thrown when the remaining fill amount is less than the requested minimum fill 41 | error PermitC__UnableToFillMinimumRequestedQuantity(); -------------------------------------------------------------------------------- /src/interfaces/IImmutableCreate2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | 4 | /** 5 | * @title Immutable Create2 Contract Factory 6 | * @author 0age 7 | * @notice This contract provides a safeCreate2 function that takes a salt value 8 | * and a block of initialization code as arguments and passes them into inline 9 | * assembly. The contract prevents redeploys by maintaining a mapping of all 10 | * contracts that have already been deployed, and prevents frontrunning or other 11 | * collisions by requiring that the first 20 bytes of the salt are equal to the 12 | * address of the caller (this can be bypassed by setting the first 20 bytes to 13 | * the null address). There is also a view function that computes the address of 14 | * the contract that will be created when submitting a given salt or nonce along 15 | * with a given block of initialization code. 16 | * @dev This contract has not yet been fully tested or audited - proceed with 17 | * caution and please share any exploits or optimizations you discover. 18 | */ 19 | interface IImmutableCreate2Factory { 20 | 21 | function safeCreate2(bytes32 salt, bytes calldata initializationCode) external payable returns (address deploymentAddress); 22 | 23 | function findCreate2Address(bytes32 salt, bytes calldata initCode) external view returns (address deploymentAddress); 24 | 25 | function findCreate2AddressViaHash(bytes32 salt, bytes32 initCodeHash) external view returns (address deploymentAddress); 26 | 27 | function hasBeenDeployed(address deploymentAddress) external view returns (bool); 28 | } -------------------------------------------------------------------------------- /src/interfaces/IPermitC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | import {OrderFillAmounts} from "../DataTypes.sol"; 4 | 5 | interface IPermitC { 6 | 7 | /** 8 | * ================================================= 9 | * ==================== Events ===================== 10 | * ================================================= 11 | */ 12 | 13 | /// @dev Emitted when an approval is stored 14 | event Approval( 15 | address indexed owner, 16 | address indexed token, 17 | address indexed operator, 18 | uint256 id, 19 | uint200 amount, 20 | uint48 expiration 21 | ); 22 | 23 | /// @dev Emitted when a user increases their master nonce 24 | event Lockdown(address indexed owner); 25 | 26 | /// @dev Emitted when an order is opened 27 | event OrderOpened( 28 | bytes32 indexed orderId, 29 | address indexed owner, 30 | address indexed operator, 31 | uint256 fillableQuantity 32 | ); 33 | 34 | /// @dev Emitted when an order has a fill 35 | event OrderFilled( 36 | bytes32 indexed orderId, 37 | address indexed owner, 38 | address indexed operator, 39 | uint256 amount 40 | ); 41 | 42 | /// @dev Emitted when an order has been fully filled or cancelled 43 | event OrderClosed( 44 | bytes32 indexed orderId, 45 | address indexed owner, 46 | address indexed operator, 47 | bool wasCancellation); 48 | 49 | /// @dev Emitted when an order has an amount restored due to a failed transfer 50 | event OrderRestored( 51 | bytes32 indexed orderId, 52 | address indexed owner, 53 | uint256 amountRestoredToOrder 54 | ); 55 | 56 | /** 57 | * ================================================= 58 | * ============== Approval Transfers =============== 59 | * ================================================= 60 | */ 61 | function approve(uint256 tokenType, address token, uint256 id, address operator, uint200 amount, uint48 expiration) external; 62 | 63 | function updateApprovalBySignature( 64 | uint256 tokenType, 65 | address token, 66 | uint256 id, 67 | uint256 nonce, 68 | uint200 amount, 69 | address operator, 70 | uint48 approvalExpiration, 71 | uint48 sigDeadline, 72 | address owner, 73 | bytes calldata signedPermit 74 | ) external; 75 | 76 | function allowance( 77 | address owner, 78 | address operator, 79 | uint256 tokenType, 80 | address token, 81 | uint256 id 82 | ) external view returns (uint256 amount, uint256 expiration); 83 | 84 | /** 85 | * ================================================= 86 | * ================ Signed Transfers =============== 87 | * ================================================= 88 | */ 89 | function registerAdditionalDataHash(string memory additionalDataTypeString) external; 90 | 91 | function permitTransferFromERC721( 92 | address token, 93 | uint256 id, 94 | uint256 nonce, 95 | uint256 expiration, 96 | address owner, 97 | address to, 98 | bytes calldata signedPermit 99 | ) external returns (bool isError); 100 | 101 | function permitTransferFromWithAdditionalDataERC721( 102 | address token, 103 | uint256 id, 104 | uint256 nonce, 105 | uint256 expiration, 106 | address owner, 107 | address to, 108 | bytes32 additionalData, 109 | bytes32 advancedPermitHash, 110 | bytes calldata signedPermit 111 | ) external returns (bool isError); 112 | 113 | function permitTransferFromERC1155( 114 | address token, 115 | uint256 id, 116 | uint256 nonce, 117 | uint256 permitAmount, 118 | uint256 expiration, 119 | address owner, 120 | address to, 121 | uint256 transferAmount, 122 | bytes calldata signedPermit 123 | ) external returns (bool isError); 124 | 125 | function permitTransferFromWithAdditionalDataERC1155( 126 | address token, 127 | uint256 id, 128 | uint256 nonce, 129 | uint256 permitAmount, 130 | uint256 expiration, 131 | address owner, 132 | address to, 133 | uint256 transferAmount, 134 | bytes32 additionalData, 135 | bytes32 advancedPermitHash, 136 | bytes calldata signedPermit 137 | ) external returns (bool isError); 138 | 139 | function permitTransferFromERC20( 140 | address token, 141 | uint256 nonce, 142 | uint256 permitAmount, 143 | uint256 expiration, 144 | address owner, 145 | address to, 146 | uint256 transferAmount, 147 | bytes calldata signedPermit 148 | ) external returns (bool isError); 149 | 150 | function permitTransferFromWithAdditionalDataERC20( 151 | address token, 152 | uint256 nonce, 153 | uint256 permitAmount, 154 | uint256 expiration, 155 | address owner, 156 | address to, 157 | uint256 transferAmount, 158 | bytes32 additionalData, 159 | bytes32 advancedPermitHash, 160 | bytes calldata signedPermit 161 | ) external returns (bool isError); 162 | 163 | function isRegisteredTransferAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered); 164 | 165 | function isRegisteredOrderAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered); 166 | 167 | /** 168 | * ================================================= 169 | * =============== Order Transfers ================= 170 | * ================================================= 171 | */ 172 | function fillPermittedOrderERC1155( 173 | bytes calldata signedPermit, 174 | OrderFillAmounts calldata orderFillAmounts, 175 | address token, 176 | uint256 id, 177 | address owner, 178 | address to, 179 | uint256 nonce, 180 | uint48 expiration, 181 | bytes32 orderId, 182 | bytes32 advancedPermitHash 183 | ) external returns (uint256 quantityFilled, bool isError); 184 | 185 | function fillPermittedOrderERC20( 186 | bytes calldata signedPermit, 187 | OrderFillAmounts calldata orderFillAmounts, 188 | address token, 189 | address owner, 190 | address to, 191 | uint256 nonce, 192 | uint48 expiration, 193 | bytes32 orderId, 194 | bytes32 advancedPermitHash 195 | ) external returns (uint256 quantityFilled, bool isError); 196 | 197 | function closePermittedOrder( 198 | address owner, 199 | address operator, 200 | uint256 tokenType, 201 | address token, 202 | uint256 id, 203 | bytes32 orderId 204 | ) external; 205 | 206 | function allowance( 207 | address owner, 208 | address operator, 209 | uint256 tokenType, 210 | address token, 211 | uint256 id, 212 | bytes32 orderId 213 | ) external view returns (uint256 amount, uint256 expiration); 214 | 215 | 216 | /** 217 | * ================================================= 218 | * ================ Nonce Management =============== 219 | * ================================================= 220 | */ 221 | function invalidateUnorderedNonce(uint256 nonce) external; 222 | 223 | function isValidUnorderedNonce(address owner, uint256 nonce) external view returns (bool isValid); 224 | 225 | function lockdown() external; 226 | 227 | function masterNonce(address owner) external view returns (uint256); 228 | 229 | /** 230 | * ================================================= 231 | * ============== Transfer Functions =============== 232 | * ================================================= 233 | */ 234 | function transferFromERC721( 235 | address from, 236 | address to, 237 | address token, 238 | uint256 id 239 | ) external returns (bool isError); 240 | 241 | function transferFromERC1155( 242 | address from, 243 | address to, 244 | address token, 245 | uint256 id, 246 | uint256 amount 247 | ) external returns (bool isError); 248 | 249 | function transferFromERC20( 250 | address from, 251 | address to, 252 | address token, 253 | uint256 amount 254 | ) external returns (bool isError); 255 | 256 | /** 257 | * ================================================= 258 | * ============ Signature Verification ============= 259 | * ================================================= 260 | */ 261 | function domainSeparatorV4() external view returns (bytes32); 262 | } 263 | -------------------------------------------------------------------------------- /src/libraries/PermitHash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {SINGLE_USE_PERMIT_TYPEHASH, UPDATE_APPROVAL_TYPEHASH} from "../Constants.sol"; 5 | 6 | library PermitHash { 7 | 8 | /** 9 | * @notice Hashes the permit data for a stored approval 10 | * 11 | * @param tokenType The type of token 12 | * @param token The address of the token 13 | * @param id The id of the token 14 | * @param amount The amount authorized by the owner signature 15 | * @param nonce The nonce for the permit 16 | * @param operator The account that is allowed to use the permit 17 | * @param approvalExpiration The time the permit approval expires 18 | * @param sigDeadline The deadline for submitting the permit onchain 19 | * @param masterNonce The signers master nonce 20 | * 21 | * @return hash The hash of the permit data 22 | */ 23 | function hashOnChainApproval( 24 | uint256 tokenType, 25 | address token, 26 | uint256 id, 27 | uint256 amount, 28 | uint256 nonce, 29 | address operator, 30 | uint256 approvalExpiration, 31 | uint256 sigDeadline, 32 | uint256 masterNonce 33 | ) internal pure returns (bytes32 hash) { 34 | hash = keccak256( 35 | abi.encode( 36 | UPDATE_APPROVAL_TYPEHASH, 37 | tokenType, 38 | token, 39 | id, 40 | amount, 41 | nonce, 42 | operator, 43 | approvalExpiration, 44 | sigDeadline, 45 | masterNonce 46 | ) 47 | ); 48 | } 49 | 50 | /** 51 | * @notice Hashes the permit data with the single user permit without additional data typehash 52 | * 53 | * @param tokenType The type of token 54 | * @param token The address of the token 55 | * @param id The id of the token 56 | * @param amount The amount authorized by the owner signature 57 | * @param nonce The nonce for the permit 58 | * @param expiration The time the permit expires 59 | * @param masterNonce The signers master nonce 60 | * 61 | * @return hash The hash of the permit data 62 | */ 63 | function hashSingleUsePermit( 64 | uint256 tokenType, 65 | address token, 66 | uint256 id, 67 | uint256 amount, 68 | uint256 nonce, 69 | uint256 expiration, 70 | uint256 masterNonce 71 | ) internal view returns (bytes32 hash) { 72 | hash = keccak256( 73 | abi.encode( 74 | SINGLE_USE_PERMIT_TYPEHASH, 75 | tokenType, 76 | token, 77 | id, 78 | amount, 79 | nonce, 80 | msg.sender, 81 | expiration, 82 | masterNonce 83 | ) 84 | ); 85 | } 86 | 87 | /** 88 | * @notice Hashes the permit data with the supplied typehash 89 | * 90 | * @param tokenType The type of token 91 | * @param token The address of the token 92 | * @param id The id of the token 93 | * @param amount The amount authorized by the owner signature 94 | * @param nonce The nonce for the permit 95 | * @param expiration The time the permit expires 96 | * @param additionalData The additional data to validate with the permit signature 97 | * @param additionalDataTypeHash The typehash of the permit to use for validating the signature 98 | * @param masterNonce The signers master nonce 99 | * 100 | * @return hash The hash of the permit data with the supplied typehash 101 | */ 102 | function hashSingleUsePermitWithAdditionalData( 103 | uint256 tokenType, 104 | address token, 105 | uint256 id, 106 | uint256 amount, 107 | uint256 nonce, 108 | uint256 expiration, 109 | bytes32 additionalData, 110 | bytes32 additionalDataTypeHash, 111 | uint256 masterNonce 112 | ) internal view returns (bytes32 hash) { 113 | hash = keccak256( 114 | abi.encode( 115 | additionalDataTypeHash, 116 | tokenType, 117 | token, 118 | id, 119 | amount, 120 | nonce, 121 | msg.sender, 122 | expiration, 123 | masterNonce, 124 | additionalData 125 | ) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/openzeppelin-optimized/EIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) 3 | 4 | pragma solidity ^0.8.8; 5 | 6 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | 8 | /** 9 | * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. 10 | * 11 | * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, 12 | * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding 13 | * they need in their contracts using a combination of `abi.encode` and `keccak256`. 14 | * 15 | * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding 16 | * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA 17 | * ({_hashTypedDataV4}). 18 | * 19 | * The implementation of the domain separator was designed to be as efficient as possible while still properly updating 20 | * the chain id to protect against replay attacks on an eventual fork of the chain. 21 | * 22 | * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method 23 | * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. 24 | * 25 | * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain 26 | * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the 27 | * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. 28 | * 29 | * _Available since v3.4._ 30 | * 31 | * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment 32 | */ 33 | abstract contract EIP712 { 34 | bytes32 private constant _TYPE_HASH = 35 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); 36 | 37 | // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to 38 | // invalidate the cached domain separator if the chain id changes. 39 | bytes32 private immutable _cachedDomainSeparator; 40 | uint256 private immutable _cachedChainId; 41 | 42 | bytes32 private immutable _hashedName; 43 | bytes32 private immutable _hashedVersion; 44 | 45 | /** 46 | * @dev Initializes the domain separator and parameter caches. 47 | * 48 | * The meaning of `name` and `version` is specified in 49 | * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: 50 | * 51 | * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. 52 | * - `version`: the current major version of the signing domain. 53 | * 54 | * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart 55 | * contract upgrade]. 56 | */ 57 | constructor(string memory name, string memory version) { 58 | _hashedName = keccak256(bytes(name)); 59 | _hashedVersion = keccak256(bytes(version)); 60 | 61 | _cachedChainId = block.chainid; 62 | _cachedDomainSeparator = _buildDomainSeparator(); 63 | } 64 | 65 | /** 66 | * @dev Returns the domain separator for the current chain. 67 | */ 68 | function _domainSeparatorV4() internal view returns (bytes32) { 69 | if (block.chainid == _cachedChainId) { 70 | return _cachedDomainSeparator; 71 | } else { 72 | return _buildDomainSeparator(); 73 | } 74 | } 75 | 76 | function _buildDomainSeparator() private view returns (bytes32) { 77 | return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); 78 | } 79 | 80 | /** 81 | * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this 82 | * function returns the hash of the fully encoded EIP712 message for this domain. 83 | * 84 | * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: 85 | * 86 | * ```solidity 87 | * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( 88 | * keccak256("Mail(address to,string contents)"), 89 | * mailTo, 90 | * keccak256(bytes(mailContents)) 91 | * ))); 92 | * address signer = ECDSA.recover(digest, signature); 93 | * ``` 94 | */ 95 | function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { 96 | return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/openzeppelin-optimized/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import {Context} from "@openzeppelin/contracts/utils/Context.sol"; 7 | 8 | /** 9 | * @dev Contract module which provides a basic access control mechanism, where 10 | * there is an account (an owner) that can be granted exclusive access to 11 | * specific functions. 12 | * 13 | * By default, the owner account will be the one that deploys the contract. This 14 | * can later be changed with {transferOwnership}. 15 | * 16 | * This module is used through inheritance. It will make available the modifier 17 | * `onlyOwner`, which can be applied to your functions to restrict their use to 18 | * the owner. 19 | */ 20 | abstract contract Ownable is Context { 21 | error Ownable__CallerIsNotOwner(); 22 | error Ownable__NewOwnerIsZeroAddress(); 23 | 24 | address private _owner; 25 | 26 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 27 | 28 | /** 29 | * @dev Initializes the contract setting the deployer as the initial owner. 30 | */ 31 | constructor() { 32 | _transferOwnership(_msgSender()); 33 | } 34 | 35 | /** 36 | * @dev Throws if called by any account other than the owner. 37 | */ 38 | modifier onlyOwner() { 39 | _checkOwner(); 40 | _; 41 | } 42 | 43 | /** 44 | * @dev Returns the address of the current owner. 45 | */ 46 | function owner() public view virtual returns (address) { 47 | return _owner; 48 | } 49 | 50 | /** 51 | * @dev Throws if the sender is not the owner. 52 | */ 53 | function _checkOwner() internal view virtual { 54 | if(owner() != _msgSender()) revert Ownable__CallerIsNotOwner(); 55 | } 56 | 57 | /** 58 | * @dev Leaves the contract without owner. It will not be possible to call 59 | * `onlyOwner` functions. Can only be called by the current owner. 60 | * 61 | * NOTE: Renouncing ownership will leave the contract without an owner, 62 | * thereby disabling any functionality that is only available to the owner. 63 | */ 64 | function renounceOwnership() public virtual onlyOwner { 65 | _transferOwnership(address(0)); 66 | } 67 | 68 | /** 69 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 70 | * Can only be called by the current owner. 71 | */ 72 | function transferOwnership(address newOwner) public virtual onlyOwner { 73 | if(newOwner == address(0)) revert Ownable__NewOwnerIsZeroAddress(); 74 | _transferOwnership(newOwner); 75 | } 76 | 77 | /** 78 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 79 | * Internal function without access restriction. 80 | */ 81 | function _transferOwnership(address newOwner) internal virtual { 82 | address oldOwner = _owner; 83 | _owner = newOwner; 84 | emit OwnershipTransferred(oldOwner, newOwner); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/Base.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "forge-std/StdCheats.sol"; 4 | import "forge-std/StdAssertions.sol"; 5 | import "forge-std/StdUtils.sol"; 6 | import {TestBase} from "forge-std/Base.sol"; 7 | 8 | import "src/PermitC.sol"; 9 | import "src/CollateralizedPausableFlags.sol"; 10 | 11 | import "./mocks/ERC721Mock.sol"; 12 | import "./mocks/ERC1155Mock.sol"; 13 | import "./mocks/ERC20Mock.sol"; 14 | 15 | import "forge-std/console.sol"; 16 | 17 | contract BaseTest is TestBase, StdAssertions, StdCheats, StdUtils { 18 | event Approval( 19 | address indexed owner, 20 | address indexed token, 21 | address indexed operator, 22 | uint256 id, 23 | uint200 amount, 24 | uint48 expiration 25 | ); 26 | event Lockdown(address indexed owner); 27 | 28 | PermitC permitC; 29 | 30 | uint256 adminKey; 31 | uint256 aliceKey; 32 | uint256 bobKey; 33 | uint256 carolKey; 34 | 35 | address admin; 36 | address alice; 37 | address bob; 38 | address carol; 39 | 40 | uint256 internal constant pausableThreshold = 5 ether; 41 | 42 | function setUp() public virtual { 43 | (admin, adminKey) = makeAddrAndKey("admin"); 44 | (alice, aliceKey) = makeAddrAndKey("alice"); 45 | (bob, bobKey) = makeAddrAndKey("bob"); 46 | (carol, carolKey) = makeAddrAndKey("carol"); 47 | 48 | vm.deal(admin, 100 ether); 49 | 50 | permitC = new PermitC("PermitC", "1", admin, pausableThreshold); 51 | 52 | // Warp to a more realistic timestamp 53 | vm.warp(1703688340); 54 | } 55 | 56 | function _deployNew721(address creator, uint256 amountToMint) internal virtual returns (address) { 57 | vm.startPrank(creator); 58 | address token = address(new ERC721Mock()); 59 | ERC721Mock(token).mint(creator, amountToMint); 60 | changePrank(admin); 61 | return token; 62 | } 63 | 64 | function _deployNew1155(address creator, uint256 idToMint, uint256 amountToMint) 65 | internal 66 | virtual 67 | returns (address) 68 | { 69 | vm.startPrank(creator); 70 | address token = address(new ERC1155Mock()); 71 | ERC1155Mock(token).mint(creator, idToMint, amountToMint); 72 | changePrank(admin); 73 | return token; 74 | } 75 | 76 | function _deployNew20(address creator, uint256 amountToMint) internal virtual returns (address) { 77 | vm.startPrank(creator); 78 | address token = address(new ERC20Mock()); 79 | ERC20Mock(token).mint(creator, amountToMint); 80 | changePrank(admin); 81 | return token; 82 | } 83 | 84 | function _mint721(address tokenAddress, address to, uint256 tokenId) internal virtual { 85 | ERC721Mock(tokenAddress).mint(to, tokenId); 86 | } 87 | 88 | function _mint20(address tokenAddress, address to, uint256 amount) internal virtual { 89 | ERC20Mock(tokenAddress).mint(to, amount); 90 | } 91 | 92 | function _mint1155(address tokenAddress, address to, uint256 tokenId, uint256 amount) internal virtual { 93 | ERC1155Mock(tokenAddress).mint(to, tokenId, amount); 94 | } 95 | 96 | function changePrank(address msgSender) internal virtual override { 97 | vm.stopPrank(); 98 | vm.startPrank(msgSender); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/DeterministicDeployment.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./Base.t.sol"; 4 | import "../src/DataTypes.sol"; 5 | import "../src/Constants.sol"; 6 | import "../src/interfaces/IImmutableCreate2Factory.sol"; 7 | 8 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 9 | 10 | contract PermitC20ApprovalTransferTest is BaseTest { 11 | 12 | // To generate a salt use the below configuration for create2crunch 13 | // export FACTORY=0x0000000000FFe8B47B3e2130213B802212439497 14 | // export CALLER=0x0000000000000000000000000000000000000000 15 | // export INIT_CODE_HASH=cast keccak $(cast abi-encode "initcode(bytes,string,string)" $(forge inspect PermitC bytecode) "PermitC" "1") 16 | 17 | address constant initialDeployer = 0x4c8D290a1B368ac4728d83a9e8321fC3af2b39b1; 18 | address constant expectedIntermediaryAddress = 0xcfA3A7637547094fF06246817a35B8333C315196; 19 | address constant expectedFinalFactoryAddress = 0x0000000000FFe8B47B3e2130213B802212439497; 20 | address constant expectedPermitCAddress = 0x000000fCE53B6fC312838A002362a9336F7ce39B; 21 | 22 | function setUp() public override { 23 | super.setUp(); 24 | } 25 | 26 | 27 | function testDeterministicDeployment() public { 28 | bytes memory initialBytecode = hex"601f80600e600039806000f350fe60003681823780368234f58015156014578182fd5b80825250506014600cf3"; 29 | bytes memory intermediaryBytecode = hex"608060405234801561001057600080fd5b50610833806100206000396000f3fe60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032"; 30 | bytes memory finalFactoryBytecode = hex"64e030870000000000000000000000000000000000000000f4b0218f13a6440a6f02000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000853608060405234801561001057600080fd5b50610833806100206000396000f3fe60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a003200000000000000000000000000"; 31 | 32 | bytes32 salt = 0x0000000000000000000000000000000000000000771737a46d75ad995610000c; 33 | 34 | address inefficientFactory; 35 | uint256 length = initialBytecode.length; 36 | 37 | vm.deal(initialDeployer, 0.01 ether); 38 | 39 | vm.prank(initialDeployer); 40 | assembly{ 41 | mstore(0x0, initialBytecode) 42 | inefficientFactory := create(0, 0xa0, length) 43 | } 44 | (bool success, ) = address(inefficientFactory).call(intermediaryBytecode); 45 | (bool success1, ) = address(expectedIntermediaryAddress).call(finalFactoryBytecode); 46 | 47 | console.logBytes(abi.encode(type(PermitC).creationCode, "PermitC", "1")); 48 | 49 | console.logBytes32(keccak256(abi.encode(type(PermitC).creationCode, "PermitC", "1"))); 50 | 51 | address expectedAddressFromRead = IImmutableCreate2Factory(expectedFinalFactoryAddress).findCreate2Address(salt, abi.encode(type(PermitC).creationCode, "PermitC", "1")); 52 | 53 | address actualAddress = IImmutableCreate2Factory(expectedFinalFactoryAddress).safeCreate2(salt, abi.encode(type(PermitC).creationCode, "PermitC", "1")); 54 | console.log(expectedAddressFromRead); 55 | console.log(expectedPermitCAddress); 56 | console.log(actualAddress); 57 | } 58 | } -------------------------------------------------------------------------------- /test/PermitC1155.PositionApprovalTransfer.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./Base.t.sol"; 4 | import "../src/DataTypes.sol"; 5 | import "../src/Constants.sol"; 6 | import "./mocks/ERC1155Reverter.sol"; 7 | 8 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 9 | 10 | contract PermitC1155OrderApprovalTransferTest is BaseTest { 11 | event OrderRestored( 12 | bytes32 indexed orderId, 13 | address indexed owner, 14 | uint256 amountRestoredToOrder 15 | ); 16 | 17 | struct TestData { 18 | address token; 19 | address owner; 20 | address spender; 21 | uint256 tokenId; 22 | uint208 amount; 23 | uint48 expiration; 24 | bytes32 orderId; 25 | uint256 nonce; 26 | } 27 | 28 | struct SignatureDetails { 29 | address operator; 30 | address token; 31 | uint256 tokenId; 32 | bytes32 orderId; 33 | uint208 amount; 34 | uint256 nonce; 35 | uint48 approvalExpiration; 36 | uint48 sigDeadline; 37 | uint256 tokenOwnerKey; 38 | } 39 | 40 | TestData private testData; 41 | 42 | string constant FILL_PERMITTED_ORDER_STRING = "bytes32 orderDigest)"; 43 | bytes32 FILL_PERMITTED_ORDER_TYPEHASH = keccak256(bytes(string.concat(PERMIT_ORDER_ADVANCED_TYPEHASH_STUB, FILL_PERMITTED_ORDER_STRING))); 44 | 45 | function setUp() public override { 46 | super.setUp(); 47 | 48 | testData = TestData({ 49 | token: address(0), 50 | owner: alice, 51 | spender: bob, 52 | tokenId: 1, 53 | amount: 1, 54 | expiration: uint48(block.timestamp), 55 | orderId: bytes32(0), 56 | nonce: 0 57 | }); 58 | } 59 | 60 | modifier whenExpirationIsInTheFuture(uint48 expiration) { 61 | testData.expiration = uint48(bound(expiration, block.timestamp, type(uint48).max)); 62 | _; 63 | } 64 | 65 | modifier whenExpirationIsInThePast(uint48 expiration) { 66 | vm.assume(block.timestamp > 0); 67 | testData.expiration = uint48(bound(expiration, type(uint48).min + 1, block.timestamp - 1)); 68 | _; 69 | } 70 | 71 | modifier whenExpirationIsCurrentTimestamp() { 72 | testData.expiration = uint48(block.timestamp); 73 | _; 74 | } 75 | 76 | modifier whenExpirationIsZero() { 77 | testData.expiration = uint48(0); 78 | _; 79 | } 80 | 81 | modifier whenTokenIsERC721() { 82 | testData.token = _deployNew721(carol, 0); 83 | _; 84 | } 85 | 86 | modifier whenTokenIsReverter() { 87 | testData.token = address(new ERC1155Reverter()); 88 | _; 89 | } 90 | 91 | modifier whenTokenIsNotAContract(address token) { 92 | assumeAddressIsNot(token, AddressType.ZeroAddress, AddressType.Precompile, AddressType.ForgeAddress); 93 | vm.assume(token.code.length == 0); 94 | testData.token = token; 95 | _; 96 | } 97 | 98 | modifier whenTokenIsERC1155() { 99 | testData.token = _deployNew1155(carol, 0, 0); 100 | _; 101 | } 102 | 103 | modifier whenOrderIdIsNotZero(bytes32 orderId) { 104 | vm.assume(orderId != bytes32(0)); 105 | testData.orderId = orderId; 106 | _; 107 | } 108 | 109 | modifier whenOrderIdIsZero() { 110 | testData.orderId = bytes32(0); 111 | _; 112 | } 113 | 114 | function testFillOrder_ERC1155_base(uint48 expiration_, bytes32 orderId_) 115 | public 116 | whenExpirationIsInTheFuture(expiration_) 117 | whenTokenIsERC1155() 118 | whenOrderIdIsNotZero(orderId_) { 119 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 120 | _mint1155(token, owner, 1, 1); 121 | 122 | changePrank(owner); 123 | ERC1155(token).setApprovalForAll(address(permitC), true); 124 | 125 | 126 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 127 | 128 | bytes memory signedPermit = getSignature(SignatureDetails({ 129 | operator: spender, 130 | token: token, 131 | tokenId: tokenId, 132 | orderId: orderId, 133 | amount: amount, 134 | nonce: nonce, 135 | approvalExpiration: expiration, 136 | sigDeadline: expiration, 137 | tokenOwnerKey: aliceKey 138 | })); 139 | 140 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 141 | orderStartAmount: uint200(amount), 142 | requestedFillAmount: uint200(amount), 143 | minimumFillAmount: uint200(amount) 144 | }); 145 | 146 | changePrank(spender); 147 | (, bool isError) = permitC.fillPermittedOrderERC1155( 148 | signedPermit, orderFillAmounts, token, tokenId, owner, spender, nonce, expiration, 149 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 150 | ); 151 | 152 | assertEq(ERC1155(token).balanceOf(bob, tokenId), 1); 153 | assertFalse(isError); 154 | } 155 | 156 | function testFillOrder_ERC1155_OrderPartial_Transfer_OrderAgain(uint48 expiration_, bytes32 orderId_) 157 | public 158 | whenExpirationIsInTheFuture(expiration_) 159 | whenTokenIsERC1155() 160 | whenOrderIdIsNotZero(orderId_) { 161 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 162 | orderId = bytes32(0); 163 | _mint1155(token, owner, 1, 2000); 164 | 165 | changePrank(owner); 166 | ERC1155(token).setApprovalForAll(address(permitC), true); 167 | 168 | 169 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 170 | 171 | bytes memory signedPermit = getSignature(SignatureDetails({ 172 | operator: spender, 173 | token: token, 174 | tokenId: tokenId, 175 | orderId: orderId, 176 | amount: 1000, 177 | nonce: nonce, 178 | approvalExpiration: expiration, 179 | sigDeadline: expiration, 180 | tokenOwnerKey: aliceKey 181 | })); 182 | 183 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 184 | orderStartAmount: uint200(1000), 185 | requestedFillAmount: uint200(500), 186 | minimumFillAmount: uint200(500) 187 | }); 188 | 189 | changePrank(spender); 190 | (, bool isError) = permitC.fillPermittedOrderERC1155( 191 | signedPermit, orderFillAmounts, token, tokenId, owner, spender, nonce, expiration, 192 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 193 | ); 194 | 195 | assertEq(ERC1155(token).balanceOf(bob, tokenId), 500); 196 | assertFalse(isError); 197 | 198 | vm.expectRevert(PermitC__ApprovalTransferPermitExpiredOrUnset.selector); 199 | permitC.transferFromERC1155(owner, spender, token, tokenId, 500); 200 | assertEq(ERC1155(token).balanceOf(bob, tokenId), 500); 201 | 202 | 203 | (, isError) = permitC.fillPermittedOrderERC1155( 204 | signedPermit, orderFillAmounts, token, tokenId, owner, spender, nonce, expiration, 205 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 206 | ); 207 | 208 | assertEq(ERC1155(token).balanceOf(bob, tokenId), 1000); 209 | 210 | 211 | vm.expectRevert(PermitC__OrderIsEitherCancelledOrFilled.selector); 212 | (, isError) = permitC.fillPermittedOrderERC1155( 213 | signedPermit, orderFillAmounts, token, tokenId, owner, spender, nonce, expiration, 214 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 215 | ); 216 | assertEq(ERC1155(token).balanceOf(bob, tokenId), 1000); 217 | } 218 | 219 | function testFillOrder_ERC1155_AfterMasterNonceIncrease(uint48 expiration_, bytes32 orderId_) 220 | public 221 | whenExpirationIsInTheFuture(expiration_) 222 | whenTokenIsERC1155() 223 | whenOrderIdIsNotZero(orderId_) { 224 | _mint1155(testData.token, testData.owner, 1, 100); 225 | 226 | changePrank(testData.owner); 227 | ERC1155(testData.token).setApprovalForAll(address(permitC), true); 228 | 229 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 230 | 231 | testData.amount = 100; 232 | 233 | bytes memory signedPermit = getSignature(SignatureDetails({ 234 | operator: testData.spender, 235 | token: testData.token, 236 | tokenId: testData.tokenId, 237 | orderId: testData.orderId, 238 | amount: testData.amount, 239 | nonce: testData.nonce, 240 | approvalExpiration: testData.expiration, 241 | sigDeadline: testData.expiration, 242 | tokenOwnerKey: aliceKey 243 | })); 244 | 245 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 246 | orderStartAmount: uint200(testData.amount), 247 | requestedFillAmount: uint200(testData.amount - 50), 248 | minimumFillAmount: uint200(testData.amount - 50) 249 | }); 250 | 251 | changePrank(testData.spender); 252 | (, bool isError) = permitC.fillPermittedOrderERC1155( 253 | signedPermit, orderFillAmounts, testData.token, testData.tokenId, testData.owner, testData.spender, testData.nonce, testData.expiration, 254 | testData.orderId, FILL_PERMITTED_ORDER_TYPEHASH 255 | ); 256 | 257 | assertEq(ERC1155(testData.token).balanceOf(testData.spender, testData.tokenId), 50); 258 | assertFalse(isError); 259 | 260 | (uint256 allowance, uint256 expiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC1155, testData.token, testData.tokenId, testData.orderId); 261 | assertEq(allowance, 50); 262 | assertEq(expiration, testData.expiration); 263 | 264 | changePrank(testData.owner); 265 | permitC.lockdown(); 266 | 267 | (uint256 allowanceAfter, uint256 expirationAfter) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC1155, testData.token, testData.tokenId, testData.orderId); 268 | assertEq(allowanceAfter, 0); 269 | assertEq(expirationAfter, 0); 270 | } 271 | 272 | function testFillOrder_ERC1155_AllowanceAmountResetsAfterRevert(uint48 expiration_, bytes32 orderId_) 273 | public 274 | whenExpirationIsInTheFuture(expiration_) 275 | whenTokenIsReverter() 276 | whenOrderIdIsNotZero(orderId_) { 277 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 278 | _mint1155(token, owner, 1, 1); 279 | 280 | changePrank(owner); 281 | ERC1155(token).setApprovalForAll(address(permitC), true); 282 | 283 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 284 | 285 | bytes memory signedPermit = getSignature(SignatureDetails({ 286 | operator: spender, 287 | token: token, 288 | tokenId: tokenId, 289 | orderId: orderId, 290 | amount: amount, 291 | nonce: nonce, 292 | approvalExpiration: expiration, 293 | sigDeadline: expiration, 294 | tokenOwnerKey: aliceKey 295 | })); 296 | 297 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 298 | orderStartAmount: uint200(amount), 299 | requestedFillAmount: uint200(amount), 300 | minimumFillAmount: uint200(amount) 301 | }); 302 | 303 | changePrank(spender); 304 | vm.expectEmit(true, true, true, true); 305 | emit OrderRestored(orderId, owner, amount); 306 | (, bool isError) = permitC.fillPermittedOrderERC1155( 307 | signedPermit, orderFillAmounts, token, tokenId, owner, spender, nonce, expiration, 308 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 309 | ); 310 | 311 | assertEq(ERC1155(token).balanceOf(alice, tokenId), 1); 312 | assert(isError); 313 | } 314 | 315 | function getSignature(SignatureDetails memory details) internal view returns (bytes memory signedPermit) { 316 | uint8 v; 317 | bytes32 r; 318 | bytes32 s; 319 | { 320 | bytes32 tmpOrderId = details.orderId; 321 | bytes32 digest = ECDSA.toTypedDataHash( 322 | permitC.domainSeparatorV4(), 323 | keccak256( 324 | abi.encode( 325 | FILL_PERMITTED_ORDER_TYPEHASH, 326 | TOKEN_TYPE_ERC1155, 327 | details.token, 328 | details.tokenId, 329 | details.amount, 330 | details.nonce, 331 | details.operator, 332 | details.approvalExpiration, 333 | 0, 334 | tmpOrderId 335 | ) 336 | ) 337 | ); 338 | 339 | (v,r,s)= vm.sign(details.tokenOwnerKey, digest); 340 | } 341 | 342 | signedPermit = abi.encodePacked(r, s, v); 343 | } 344 | 345 | function _cacheData() internal view returns (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) { 346 | token = testData.token; 347 | owner = testData.owner; 348 | spender = testData.spender; 349 | tokenId = testData.tokenId; 350 | amount = testData.amount; 351 | expiration = testData.expiration; 352 | orderId = testData.orderId; 353 | nonce = testData.nonce; 354 | } 355 | } -------------------------------------------------------------------------------- /test/PermitC20.ApprovalTransfer.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./Base.t.sol"; 4 | import "../src/DataTypes.sol"; 5 | import "../src/Constants.sol"; 6 | import "./mocks/ERC20Reverter.sol"; 7 | 8 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 9 | 10 | contract PermitC20ApprovalTransferTest is BaseTest { 11 | struct TestData { 12 | address token; 13 | address owner; 14 | address spender; 15 | uint208 amount; 16 | uint48 expiration; 17 | bytes32 positionId; 18 | } 19 | 20 | struct SignatureDetails { 21 | address operator; 22 | address token; 23 | uint256 tokenId; 24 | uint208 amount; 25 | uint256 nonce; 26 | uint48 approvalExpiration; 27 | uint48 sigDeadline; 28 | uint256 tokenOwnerKey; 29 | } 30 | 31 | TestData private testData; 32 | 33 | 34 | modifier whenExpirationIsInTheFuture(uint48 expiration) { 35 | testData.expiration = uint48(bound(expiration, block.timestamp, type(uint48).max)); 36 | _; 37 | } 38 | 39 | modifier whenExpirationIsInThePast(uint48 expiration) { 40 | vm.assume(block.timestamp > 0); 41 | testData.expiration = uint48(bound(expiration, type(uint48).min + 1, block.timestamp - 1)); 42 | _; 43 | } 44 | 45 | modifier whenExpirationIsCurrentTimestamp() { 46 | testData.expiration = uint48(block.timestamp); 47 | _; 48 | } 49 | 50 | modifier whenExpirationIsZero() { 51 | testData.expiration = uint48(0); 52 | _; 53 | } 54 | 55 | modifier whenTokenIsERC20() { 56 | testData.token = _deployNew20(carol, 0); 57 | _; 58 | } 59 | 60 | modifier whenTokenIsReverter() { 61 | testData.token = address(new ERC20Reverter()); 62 | _; 63 | } 64 | 65 | modifier whenTokenIsNotAContract(address token) { 66 | assumeAddressIsNot(token, AddressType.ZeroAddress, AddressType.Precompile, AddressType.ForgeAddress); 67 | vm.assume(token.code.length == 0); 68 | testData.token = token; 69 | _; 70 | } 71 | 72 | modifier whenTokenIsAnERC1155() { 73 | testData.token = _deployNew1155(carol, 1, 1); 74 | _mint1155(testData.token, testData.owner, 1, testData.amount); 75 | _; 76 | } 77 | 78 | 79 | function setUp() public override { 80 | super.setUp(); 81 | 82 | testData = TestData({ 83 | token: address(0), 84 | owner: alice, 85 | spender: bob, 86 | amount: 1, 87 | expiration: uint48(block.timestamp), 88 | positionId: bytes32(0) 89 | }); 90 | } 91 | 92 | function testTransferFromWithApprovalOnChain_ERC20_base(uint48 expiration) 93 | whenExpirationIsInTheFuture(expiration) 94 | whenTokenIsERC20() 95 | public { 96 | _mint20(testData.token, testData.owner, 1); 97 | 98 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 99 | 100 | changePrank(testData.owner); 101 | ERC20(testData.token).approve(address(permitC), type(uint256).max); 102 | permitC.approve(TOKEN_TYPE_ERC20, testData.token, 0, testData.spender, 1, uint48(block.timestamp)); 103 | 104 | (uint256 allowanceAmount, uint256 allowanceExpiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 105 | assertEq(allowanceAmount, 1); 106 | assertEq(allowanceExpiration, uint48(block.timestamp)); 107 | 108 | changePrank(testData.spender); 109 | permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1); 110 | 111 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 112 | 113 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 1); 114 | assertEq(allowanceAmount, 0); 115 | } 116 | 117 | function testTransferFromWithApprovalOnChain_ERC20_MaxApprovalCorrectAfterFailedTransfer(uint48 expiration) 118 | whenExpirationIsInTheFuture(expiration) 119 | whenTokenIsERC20() 120 | public { 121 | _mint20(testData.token, testData.owner, 1000); 122 | 123 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1000); 124 | 125 | changePrank(testData.owner); 126 | ERC20(testData.token).approve(address(permitC), type(uint256).max); 127 | permitC.approve(TOKEN_TYPE_ERC20, testData.token, 0, testData.spender, type(uint200).max, uint48(block.timestamp)); 128 | 129 | (uint256 allowanceAmount, uint256 allowanceExpiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 130 | assertEq(allowanceAmount, type(uint200).max); 131 | assertEq(allowanceExpiration, uint48(block.timestamp)); 132 | 133 | changePrank(testData.spender); 134 | bool isError = permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1500); 135 | // expect transfer to fail as owner balance is 1000 136 | assertTrue(isError); 137 | 138 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 139 | 140 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1000); 141 | assertEq(allowanceAmount, type(uint200).max); 142 | } 143 | 144 | function testTransferFromWithApprovalOnChain_ERC20_RevertsWhenPaused(uint48 expiration) 145 | whenExpirationIsInTheFuture(expiration) 146 | whenTokenIsERC20() 147 | public { 148 | _mint20(testData.token, testData.owner, 1); 149 | 150 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 151 | 152 | changePrank(testData.owner); 153 | ERC20(testData.token).approve(address(permitC), type(uint256).max); 154 | permitC.approve(TOKEN_TYPE_ERC20, testData.token, 0, testData.spender, 1, uint48(block.timestamp)); 155 | 156 | (uint256 allowanceAmount, uint256 allowanceExpiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 157 | assertEq(allowanceAmount, 1); 158 | assertEq(allowanceExpiration, uint48(block.timestamp)); 159 | 160 | changePrank(admin); 161 | permitC.pause{value: pausableThreshold + 1}(PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20); 162 | 163 | changePrank(testData.spender); 164 | vm.expectRevert(CollateralizedPausableFlags.CollateralizedPausableFlags__Paused.selector); 165 | permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1); 166 | 167 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 168 | 169 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 0); 170 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 171 | assertEq(allowanceAmount, 1); 172 | } 173 | 174 | function testTransferFromWithApprovalOnChain_ERC20_SuccessWhenNotPaused(uint48 expiration) 175 | whenExpirationIsInTheFuture(expiration) 176 | whenTokenIsERC20() 177 | public { 178 | _mint20(testData.token, testData.owner, 1); 179 | 180 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 181 | 182 | changePrank(testData.owner); 183 | ERC20(testData.token).approve(address(permitC), type(uint256).max); 184 | permitC.approve(TOKEN_TYPE_ERC20, testData.token, 0, testData.spender, 1, uint48(block.timestamp)); 185 | 186 | (uint256 allowanceAmount, uint256 allowanceExpiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 187 | assertEq(allowanceAmount, 1); 188 | assertEq(allowanceExpiration, uint48(block.timestamp)); 189 | 190 | changePrank(admin); 191 | permitC.pause{value: pausableThreshold + 1}(type(uint256).max - PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20); 192 | 193 | changePrank(testData.spender); 194 | permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1); 195 | 196 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 197 | 198 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 1); 199 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 0); 200 | assertEq(allowanceAmount, 0); 201 | } 202 | 203 | function testTransferFromWithApprovalOnChain_ERC20_RevertsWhenPausedAllowedAfterUnpause(uint48 expiration) 204 | whenExpirationIsInTheFuture(expiration) 205 | whenTokenIsERC20() 206 | public { 207 | _mint20(testData.token, testData.owner, 1); 208 | 209 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 210 | 211 | changePrank(testData.owner); 212 | ERC20(testData.token).approve(address(permitC), type(uint256).max); 213 | permitC.approve(TOKEN_TYPE_ERC20, testData.token, 0, testData.spender, 1, uint48(block.timestamp)); 214 | 215 | (uint256 allowanceAmount, uint256 allowanceExpiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 216 | assertEq(allowanceAmount, 1); 217 | assertEq(allowanceExpiration, uint48(block.timestamp)); 218 | 219 | changePrank(admin); 220 | permitC.pause{value: pausableThreshold + 1}(PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20); 221 | 222 | changePrank(testData.spender); 223 | vm.expectRevert(CollateralizedPausableFlags.CollateralizedPausableFlags__Paused.selector); 224 | permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1); 225 | 226 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 227 | 228 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 0); 229 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 230 | assertEq(allowanceAmount, 1); 231 | 232 | changePrank(admin); 233 | permitC.unpause(admin, address(permitC).balance); 234 | assertEq(address(permitC).balance, 0); 235 | 236 | changePrank(testData.spender); 237 | permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1); 238 | 239 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 240 | 241 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 1); 242 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 0); 243 | assertEq(allowanceAmount, 0); 244 | } 245 | 246 | function testTransferFromWithApprovalOnChain_ERC20_AllowanceAmountRestoredAfterRevert(uint48 expiration) 247 | whenExpirationIsInTheFuture(expiration) 248 | whenTokenIsReverter() 249 | public { 250 | _mint20(testData.token, testData.owner, 1); 251 | 252 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 253 | 254 | changePrank(testData.owner); 255 | ERC20(testData.token).approve(address(permitC), type(uint256).max); 256 | permitC.approve(TOKEN_TYPE_ERC20, testData.token, 0, testData.spender, 1, uint48(block.timestamp)); 257 | 258 | (uint256 allowanceAmount, uint256 allowanceExpiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 259 | assertEq(allowanceAmount, 1); 260 | assertEq(allowanceExpiration, uint48(block.timestamp)); 261 | 262 | changePrank(testData.spender); 263 | bool isError = permitC.transferFromERC20(testData.owner, testData.spender, testData.token, 1); 264 | 265 | assert(isError); 266 | 267 | (allowanceAmount,) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, 0); 268 | 269 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 0); 270 | assertEq(ERC20(testData.token).balanceOf(testData.owner), 1); 271 | assertEq(allowanceAmount, 1); 272 | } 273 | 274 | } -------------------------------------------------------------------------------- /test/PermitC20.PositionApprovalTransfer.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./Base.t.sol"; 4 | import "../src/DataTypes.sol"; 5 | import "../src/Constants.sol"; 6 | import "./mocks/ERC20Reverter.sol"; 7 | 8 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 9 | 10 | contract PositionApprovalTransferTest is BaseTest { 11 | event OrderRestored( 12 | bytes32 indexed orderId, 13 | address indexed owner, 14 | uint256 amountRestoredToOrder 15 | ); 16 | 17 | struct TestData { 18 | address token; 19 | address owner; 20 | address spender; 21 | uint256 tokenId; 22 | uint208 amount; 23 | uint48 expiration; 24 | bytes32 orderId; 25 | uint256 nonce; 26 | } 27 | 28 | struct SignatureDetails { 29 | address operator; 30 | address token; 31 | uint256 tokenId; 32 | bytes32 orderId; 33 | uint208 amount; 34 | uint256 nonce; 35 | uint48 approvalExpiration; 36 | uint48 sigDeadline; 37 | uint256 tokenOwnerKey; 38 | } 39 | 40 | TestData private testData; 41 | 42 | string constant FILL_PERMITTED_ORDER_STRING = "bytes32 orderDigest)"; 43 | bytes32 FILL_PERMITTED_ORDER_TYPEHASH = keccak256(bytes(string.concat(PERMIT_ORDER_ADVANCED_TYPEHASH_STUB, FILL_PERMITTED_ORDER_STRING))); 44 | 45 | function setUp() public override { 46 | super.setUp(); 47 | 48 | testData = TestData({ 49 | token: address(0), 50 | owner: alice, 51 | spender: bob, 52 | tokenId: 0, 53 | amount: 1, 54 | expiration: uint48(block.timestamp), 55 | orderId: bytes32(0), 56 | nonce: 0 57 | }); 58 | } 59 | 60 | modifier whenExpirationIsInTheFuture(uint48 expiration) { 61 | testData.expiration = uint48(bound(expiration, block.timestamp, type(uint48).max)); 62 | _; 63 | } 64 | 65 | modifier whenExpirationIsInThePast(uint48 expiration) { 66 | vm.assume(block.timestamp > 0); 67 | testData.expiration = uint48(bound(expiration, type(uint48).min + 1, block.timestamp - 1)); 68 | _; 69 | } 70 | 71 | modifier whenExpirationIsCurrentTimestamp() { 72 | testData.expiration = uint48(block.timestamp); 73 | _; 74 | } 75 | 76 | modifier whenExpirationIsZero() { 77 | testData.expiration = uint48(0); 78 | _; 79 | } 80 | 81 | modifier whenTokenIsERC20() { 82 | testData.token = _deployNew20(carol, 0); 83 | _; 84 | } 85 | 86 | modifier whenTokenIsReverter() { 87 | testData.token = address(new ERC20Reverter()); 88 | _; 89 | } 90 | 91 | modifier whenTokenIsERC1155() { 92 | testData.token = _deployNew1155(carol, 0, 0); 93 | _; 94 | } 95 | 96 | modifier whenOrderIdIsNotZero(bytes32 orderId) { 97 | vm.assume(orderId != bytes32(0)); 98 | testData.orderId = orderId; 99 | _; 100 | } 101 | 102 | 103 | modifier whenTokenIsNotAContract(address token) { 104 | assumeAddressIsNot(token, AddressType.ZeroAddress, AddressType.Precompile, AddressType.ForgeAddress); 105 | vm.assume(token.code.length == 0); 106 | testData.token = token; 107 | _; 108 | } 109 | 110 | modifier whenTokenIsAnERC1155() { 111 | testData.token = _deployNew1155(carol, 1, 1); 112 | _mint1155(testData.token, testData.owner, 1, testData.amount); 113 | _; 114 | } 115 | 116 | function testFillOrder_ERC20_base(uint48 expiration_, bytes32 orderId_) 117 | public 118 | whenExpirationIsInTheFuture(expiration_) 119 | whenTokenIsERC20() 120 | whenOrderIdIsNotZero(orderId_) { 121 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 122 | _mint20(token, owner, 1); 123 | 124 | changePrank(owner); 125 | ERC20(testData.token).approve(address(permitC), 1); 126 | 127 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 128 | 129 | bytes memory signedPermit = getSignature(SignatureDetails({ 130 | operator: spender, 131 | token: token, 132 | tokenId: tokenId, 133 | orderId: orderId, 134 | amount: amount, 135 | nonce: nonce, 136 | approvalExpiration: expiration, 137 | sigDeadline: expiration, 138 | tokenOwnerKey: aliceKey 139 | })); 140 | 141 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 142 | orderStartAmount: uint200(amount), 143 | requestedFillAmount: uint200(amount), 144 | minimumFillAmount: uint200(amount) 145 | }); 146 | 147 | changePrank(spender); 148 | (, bool isError) = permitC.fillPermittedOrderERC20( 149 | signedPermit, orderFillAmounts, token, owner, spender, nonce, expiration, 150 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 151 | ); 152 | 153 | assertEq(ERC20(token).balanceOf(bob), 1); 154 | assertFalse(isError); 155 | } 156 | 157 | function testFillOrder_ERC20_RevertsAfterOrderCancel(uint48 expiration_, bytes32 orderId_) 158 | public 159 | whenExpirationIsInTheFuture(expiration_) 160 | whenTokenIsERC20() 161 | whenOrderIdIsNotZero(orderId_) { 162 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 163 | _mint20(token, owner, 1); 164 | 165 | changePrank(owner); 166 | ERC20(testData.token).approve(address(permitC), 1); 167 | 168 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 169 | 170 | bytes memory signedPermit = getSignature(SignatureDetails({ 171 | operator: spender, 172 | token: token, 173 | tokenId: tokenId, 174 | orderId: orderId, 175 | amount: amount, 176 | nonce: nonce, 177 | approvalExpiration: expiration, 178 | sigDeadline: expiration, 179 | tokenOwnerKey: aliceKey 180 | })); 181 | 182 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 183 | orderStartAmount: uint200(amount), 184 | requestedFillAmount: uint200(amount), 185 | minimumFillAmount: uint200(amount) 186 | }); 187 | 188 | changePrank(owner); 189 | permitC.closePermittedOrder(owner, spender, TOKEN_TYPE_ERC20, token, tokenId, orderId); 190 | 191 | changePrank(spender); 192 | vm.expectRevert(PermitC__OrderIsEitherCancelledOrFilled.selector); 193 | (, bool isError) = permitC.fillPermittedOrderERC20( 194 | signedPermit, orderFillAmounts, token, owner, spender, nonce, expiration, 195 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 196 | ); 197 | 198 | assertEq(ERC20(token).balanceOf(spender), 0); 199 | } 200 | 201 | function testClosePermittedOrderRevertsWhenNotOwnerOrOperator(uint48 expiration_, bytes32 orderId_, address maliciousCloser) 202 | public 203 | whenExpirationIsInTheFuture(expiration_) 204 | whenTokenIsERC20() 205 | whenOrderIdIsNotZero(orderId_) { 206 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 207 | _mint20(token, owner, 1); 208 | 209 | vm.assume(maliciousCloser != owner); 210 | vm.assume(maliciousCloser != spender); 211 | 212 | changePrank(owner); 213 | ERC20(testData.token).approve(address(permitC), 1); 214 | 215 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 216 | 217 | bytes memory signedPermit = getSignature(SignatureDetails({ 218 | operator: spender, 219 | token: token, 220 | tokenId: tokenId, 221 | orderId: orderId, 222 | amount: amount, 223 | nonce: nonce, 224 | approvalExpiration: expiration, 225 | sigDeadline: expiration, 226 | tokenOwnerKey: aliceKey 227 | })); 228 | 229 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 230 | orderStartAmount: uint200(amount), 231 | requestedFillAmount: uint200(amount), 232 | minimumFillAmount: uint200(amount) 233 | }); 234 | 235 | changePrank(maliciousCloser); 236 | vm.expectRevert(PermitC__CallerMustBeOwnerOrOperator.selector); 237 | permitC.closePermittedOrder(owner, spender, TOKEN_TYPE_ERC20, token, tokenId, orderId); 238 | } 239 | 240 | function testFillOrder_ERC20_AfterMasterNonceIncrease(uint48 expiration_, bytes32 orderId_) 241 | public 242 | whenExpirationIsInTheFuture(expiration_) 243 | whenTokenIsERC20() 244 | whenOrderIdIsNotZero(orderId_) { 245 | _mint20(testData.token, testData.owner, 100); 246 | 247 | changePrank(testData.owner); 248 | ERC20(testData.token).approve(address(permitC), 100); 249 | 250 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 251 | 252 | testData.amount = 100; 253 | 254 | bytes memory signedPermit = getSignature(SignatureDetails({ 255 | operator: testData.spender, 256 | token: testData.token, 257 | tokenId: testData.tokenId, 258 | orderId: testData.orderId, 259 | amount: testData.amount, 260 | nonce: testData.nonce, 261 | approvalExpiration: testData.expiration, 262 | sigDeadline: testData.expiration, 263 | tokenOwnerKey: aliceKey 264 | })); 265 | 266 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 267 | orderStartAmount: uint200(testData.amount), 268 | requestedFillAmount: uint200(testData.amount - 50), 269 | minimumFillAmount: uint200(testData.amount - 50) 270 | }); 271 | 272 | changePrank(testData.spender); 273 | (, bool isError) = permitC.fillPermittedOrderERC20( 274 | signedPermit, orderFillAmounts, testData.token, testData.owner, testData.spender, testData.nonce, testData.expiration, 275 | testData.orderId, FILL_PERMITTED_ORDER_TYPEHASH 276 | ); 277 | 278 | assertEq(ERC20(testData.token).balanceOf(testData.spender), 50); 279 | assertFalse(isError); 280 | 281 | (uint256 allowance, uint256 expiration) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, testData.tokenId, testData.orderId); 282 | assertEq(allowance, 50); 283 | assertEq(expiration, testData.expiration); 284 | 285 | changePrank(testData.owner); 286 | permitC.lockdown(); 287 | 288 | (uint256 allowanceAfter, uint256 expirationAfter) = permitC.allowance(testData.owner, testData.spender, TOKEN_TYPE_ERC20, testData.token, testData.tokenId, testData.orderId); 289 | assertEq(allowanceAfter, 0); 290 | assertEq(expirationAfter, 0); 291 | } 292 | 293 | function testFillOrder_ERC20_AllowanceAmountResetsAfterRevert(uint48 expiration_, bytes32 orderId_) 294 | public 295 | whenExpirationIsInTheFuture(expiration_) 296 | whenTokenIsReverter() 297 | whenOrderIdIsNotZero(orderId_) { 298 | (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) = _cacheData(); 299 | _mint20(token, owner, 1); 300 | 301 | changePrank(owner); 302 | ERC20(token).approve(address(permitC), 1); 303 | 304 | permitC.registerAdditionalDataHash(FILL_PERMITTED_ORDER_STRING); 305 | 306 | bytes memory signedPermit = getSignature(SignatureDetails({ 307 | operator: spender, 308 | token: token, 309 | tokenId: tokenId, 310 | orderId: orderId, 311 | amount: amount, 312 | nonce: nonce, 313 | approvalExpiration: expiration, 314 | sigDeadline: expiration, 315 | tokenOwnerKey: aliceKey 316 | })); 317 | 318 | OrderFillAmounts memory orderFillAmounts = OrderFillAmounts({ 319 | orderStartAmount: uint200(amount), 320 | requestedFillAmount: uint200(amount), 321 | minimumFillAmount: uint200(amount) 322 | }); 323 | 324 | changePrank(spender); 325 | vm.expectEmit(true, true, true, true); 326 | emit OrderRestored(orderId, owner, amount); 327 | (, bool isError) = permitC.fillPermittedOrderERC20( 328 | signedPermit, orderFillAmounts, token, owner, spender, nonce, expiration, 329 | orderId, FILL_PERMITTED_ORDER_TYPEHASH 330 | ); 331 | 332 | address tmpToken = token; 333 | uint256 tmpTokenId = tokenId; 334 | (uint256 allowedAmount, uint256 allowanceExpiration) = permitC.allowance(owner, spender, TOKEN_TYPE_ERC20, tmpToken, tmpTokenId, orderId); 335 | assertEq(allowedAmount, 1); 336 | assertEq(expiration, allowanceExpiration); 337 | assertEq(ERC20(token).balanceOf(alice), 1); 338 | assert(isError); 339 | } 340 | 341 | function getSignature(SignatureDetails memory details) internal view returns (bytes memory signedPermit) { 342 | uint8 v; 343 | bytes32 r; 344 | bytes32 s; 345 | { 346 | bytes32 tmpOrderId = details.orderId; 347 | bytes32 digest = ECDSA.toTypedDataHash( 348 | permitC.domainSeparatorV4(), 349 | keccak256( 350 | abi.encode( 351 | FILL_PERMITTED_ORDER_TYPEHASH, 352 | TOKEN_TYPE_ERC20, 353 | details.token, 354 | details.tokenId, 355 | details.amount, 356 | details.nonce, 357 | details.operator, 358 | details.approvalExpiration, 359 | 0, 360 | tmpOrderId 361 | ) 362 | ) 363 | ); 364 | 365 | (v,r,s)= vm.sign(details.tokenOwnerKey, digest); 366 | } 367 | 368 | signedPermit = abi.encodePacked(r, s, v); 369 | } 370 | 371 | function _cacheData() internal view returns (address token, address owner, address spender, uint256 tokenId, uint208 amount, uint48 expiration, bytes32 orderId, uint256 nonce) { 372 | token = testData.token; 373 | owner = testData.owner; 374 | spender = testData.spender; 375 | tokenId = testData.tokenId; 376 | amount = testData.amount; 377 | expiration = testData.expiration; 378 | orderId = testData.orderId; 379 | nonce = testData.nonce; 380 | } 381 | } -------------------------------------------------------------------------------- /test/approve/approve.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "../Base.t.sol"; 4 | 5 | contract ApproveTest is BaseTest { 6 | function setUp() public override { 7 | super.setUp(); 8 | } 9 | 10 | modifier whenExpirationIsZero() { 11 | _; 12 | } 13 | 14 | modifier whenExpirationIsNotZero() { 15 | _; 16 | } 17 | 18 | function testIncreaseApproveViaOnChainTx_ERC721_base(uint48 expiration) public whenExpirationIsNotZero { 19 | expiration = uint48(bound(expiration, block.timestamp, type(uint48).max)); 20 | address token = _deployNew721(carol, 0); 21 | 22 | _mint721(token, alice, 1); 23 | 24 | assertEq(ERC721(token).ownerOf(1), alice); 25 | 26 | changePrank(alice); 27 | ERC721(token).approve(address(permitC), 1); 28 | 29 | vm.expectEmit(true, true, true, true); 30 | emit Approval(alice, token, bob, 1, 1, expiration); 31 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, expiration); 32 | 33 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 34 | assertEq(allowanceBob, 1); 35 | changePrank(admin); 36 | } 37 | 38 | function testIncreaseApproveViaOnChainTx_ERC721_base() public whenExpirationIsZero { 39 | address token = _deployNew721(carol, 0); 40 | 41 | _mint721(token, alice, 1); 42 | 43 | assertEq(ERC721(token).ownerOf(1), alice); 44 | 45 | changePrank(alice); 46 | ERC721(token).approve(address(permitC), 1); 47 | 48 | vm.expectEmit(true, true, true, true); 49 | emit Approval(alice, token, bob, 1, 1, uint48(block.timestamp)); 50 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(block.timestamp)); 51 | 52 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 53 | assertEq(allowanceBob, 1); 54 | changePrank(admin); 55 | } 56 | 57 | function testIncreaseApproveViaOnChainTx_ERC721_ReturnZeroAfterExpiration(uint48 expiration) 58 | public 59 | whenExpirationIsNotZero 60 | { 61 | expiration = uint48(bound(expiration, 1001, type(uint48).max - 2000)); 62 | address token = _deployNew721(carol, 0); 63 | 64 | _mint721(token, alice, 1); 65 | 66 | assertEq(ERC721(token).ownerOf(1), alice); 67 | 68 | changePrank(alice); 69 | ERC721(token).approve(address(permitC), 1); 70 | 71 | vm.expectEmit(true, true, true, true); 72 | emit Approval(alice, token, bob, 1, 1, uint48(expiration)); 73 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(expiration)); 74 | 75 | vm.warp(expiration - 500); 76 | 77 | (uint256 allowanceBob, uint256 allowanceExpiration) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 78 | assertEq(allowanceBob, 1); 79 | assertEq(allowanceExpiration, expiration); 80 | 81 | vm.warp(expiration + 1); 82 | 83 | (allowanceBob, allowanceExpiration) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 84 | assertEq(allowanceBob, 0); 85 | 86 | changePrank(admin); 87 | } 88 | 89 | function testIncreaseApproveViaOnChainTx_ERC721_ReturnZeroAfterExpiration() public whenExpirationIsZero { 90 | address token = _deployNew721(carol, 0); 91 | 92 | _mint721(token, alice, 1); 93 | 94 | assertEq(ERC721(token).ownerOf(1), alice); 95 | 96 | changePrank(alice); 97 | ERC721(token).approve(address(permitC), 1); 98 | 99 | vm.expectEmit(true, true, true, true); 100 | emit Approval(alice, token, bob, 1, 1, uint48(block.timestamp)); 101 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(block.timestamp)); 102 | 103 | vm.warp(block.timestamp); 104 | 105 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 106 | assertEq(allowanceBob, 1); 107 | 108 | vm.warp(block.timestamp + 1); 109 | 110 | (allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 111 | assertEq(allowanceBob, 0); 112 | changePrank(admin); 113 | } 114 | 115 | function testIncreaseApproveViaOnChainTx_ERC1155_base(uint48 expiration) public whenExpirationIsNotZero { 116 | expiration = uint48(bound(expiration, block.timestamp, type(uint48).max)); 117 | address token = _deployNew1155(carol, 0, 0); 118 | 119 | _mint1155(token, alice, 1, 1); 120 | 121 | assertEq(ERC1155(token).balanceOf(alice, 1), 1); 122 | 123 | changePrank(alice); 124 | ERC1155(token).setApprovalForAll(address(permitC), true); 125 | 126 | vm.expectEmit(true, true, true, true); 127 | emit Approval(alice, token, bob, 1, 1, expiration); 128 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, expiration); 129 | 130 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 131 | assertEq(allowanceBob, 1); 132 | changePrank(admin); 133 | } 134 | 135 | function testIncreaseApproveViaOnChainTx_ERC1155_base() public whenExpirationIsZero { 136 | address token = _deployNew1155(carol, 0, 0); 137 | 138 | _mint1155(token, alice, 1, 1); 139 | 140 | assertEq(ERC1155(token).balanceOf(alice, 1), 1); 141 | 142 | changePrank(alice); 143 | ERC1155(token).setApprovalForAll(address(permitC), true); 144 | 145 | vm.expectEmit(true, true, true, true); 146 | emit Approval(alice, token, bob, 1, 1, uint48(block.timestamp)); 147 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(block.timestamp)); 148 | 149 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 150 | assertEq(allowanceBob, 1); 151 | changePrank(admin); 152 | } 153 | 154 | function testIncreaseApproveViaOnChainTx_ERC1155_ReturnZeroAfterExpiration(uint48 expiration) 155 | public 156 | whenExpirationIsNotZero 157 | { 158 | expiration = uint48(bound(expiration, 1001, type(uint48).max - 2000)); 159 | address token = _deployNew1155(carol, 0, 0); 160 | 161 | _mint1155(token, alice, 1, 1); 162 | 163 | assertEq(ERC1155(token).balanceOf(alice, 1), 1); 164 | 165 | changePrank(alice); 166 | ERC1155(token).setApprovalForAll(address(permitC), true); 167 | 168 | vm.expectEmit(true, true, true, true); 169 | emit Approval(alice, token, bob, 1, 1, uint48(expiration)); 170 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(expiration)); 171 | 172 | vm.warp(expiration - 500); 173 | 174 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 175 | assertEq(allowanceBob, 1); 176 | 177 | vm.warp(expiration + 1); 178 | 179 | (allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 180 | assertEq(allowanceBob, 0); 181 | changePrank(admin); 182 | } 183 | 184 | function testIncreaseApproveViaOnChainTx_ERC1155_ReturnZeroAfterExpiration() public whenExpirationIsZero { 185 | address token = _deployNew1155(carol, 0, 0); 186 | 187 | _mint1155(token, alice, 1, 1); 188 | 189 | assertEq(ERC1155(token).balanceOf(alice, 1), 1); 190 | 191 | changePrank(alice); 192 | ERC1155(token).setApprovalForAll(address(permitC), true); 193 | 194 | vm.expectEmit(true, true, true, true); 195 | emit Approval(alice, token, bob, 1, 1, uint48(block.timestamp)); 196 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(block.timestamp)); 197 | 198 | vm.warp(block.timestamp); 199 | 200 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 201 | assertEq(allowanceBob, 1); 202 | 203 | vm.warp(block.timestamp + 1); 204 | 205 | (allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 206 | assertEq(allowanceBob, 0); 207 | changePrank(admin); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /test/approve/approve.tree: -------------------------------------------------------------------------------- 1 | // Approve grants the provided operator permission to transfer a token on behalf of the owner 2 | // The approval includes the token address and ID, an operator, an amount and an expiration timestamp 3 | // There are no restrictions on this function, any address can approve an operator to approve a token on their behalf 4 | // There are no restrictions on the operator, it can be any address 5 | // There are no restrictions on the amount, it can be any value 6 | // There are no restrictions on the expiration timestamp, it can be any value 7 | // There are no restrictions on the token address, it can be any address 8 | // There are no restrictions on the token ID, it can be any value 9 | approve.t.sol 10 | |- when the expiration is 0 11 | |- it should update the packed approval found at key keccak256(abi.encode(owner, token, id, _masterNonces[owner])) with the provided values and an expiration at the current block.timestamp 12 | |- it should emit an Approval event 13 | |- when the expiration is not 0 14 | |- it should update the packed approval found at key keccak256(abi.encode(owner, token, id, _masterNonces[owner])) with the provided values and an expiration at the provided timestamp 15 | |- it should emit an Approval event -------------------------------------------------------------------------------- /test/benchmarks/forge-metering/BenchmarkApprovals.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ForgeBenchmarkApprove is BenchmarkBase { 7 | function testBenchmarkApproveERC20_x1000() public metered { 8 | _runBenchmarkApproveERC20(false, 1000, "Approve ERC20"); 9 | } 10 | 11 | function testBenchmarkSignatureApproveERC20_x1000() public metered { 12 | _runBenchmarkSignatureApproveERC20(false, 1000, "Signature Approve ERC20"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/benchmarks/forge-metering/BenchmarkApprovedTransferERC20.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ForgeBenchmarkApprovedTransferERC20 is BenchmarkBase { 7 | function setUp() public virtual override { 8 | super.setUp(); 9 | 10 | vm.prank(alice); 11 | permitC.approve( 12 | TOKEN_TYPE_ERC20, 13 | address(token20), 14 | 0, 15 | address(operator), 16 | uint200(USER_STARTING_BALANCE), 17 | uint48(block.timestamp + 100000) 18 | ); 19 | 20 | } 21 | 22 | function testBenchmarkApprovedTransferFromERC20_x1000() public metered { 23 | _runBenchmarkApprovedTransferFromERC20(false, 1000, "Approved Transfer From ERC20"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/benchmarks/forge-metering/BenchmarkCancelNonce.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ForgeBenchmarkCancelNonce is BenchmarkBase { 7 | function testBenchmarkCancelNonce_x1000() public metered { 8 | _runBenchmarkCancelNonce(false, 1000, "Cancel Nonce"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/benchmarks/forge-metering/BenchmarkPermitTransferFromERC20.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ForgeBenchmarkPermitTransferFromERC20 is BenchmarkBase { 7 | function testBenchmarkPermitTransferFromERC20_x1000() public metered { 8 | _runBenchmarkPermitTransferFromERC20(false, 1000, "Permit Transfer From ERC20"); 9 | } 10 | 11 | function testBenchmarkPermitTransferFromWithAddtionalDataHashERC20_x1000() public metered { 12 | _runBenchmarkPermitTransferFromWithAdditionalDataHashERC20(false, 1000, "Permit Transfer From With Additional Data Hash ERC20"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/benchmarks/manual-metering/BenchmarkApprovals.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ManualBenchmarkApprove is BenchmarkBase { 7 | function testBenchmarkApproveERC20_x1000() public manuallyMetered { 8 | _runBenchmarkApproveERC20(true, 1000, "Approve ERC20"); 9 | } 10 | 11 | function testBenchmarkSignatureApproveERC20_x1000() public manuallyMetered { 12 | _runBenchmarkSignatureApproveERC20(true, 1000, "Signature Approve ERC20"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/benchmarks/manual-metering/BenchmarkApprovedTransferERC20.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ManualBenchmarkApprovedTransferERC20 is BenchmarkBase { 7 | function setUp() public virtual override { 8 | super.setUp(); 9 | 10 | vm.prank(alice); 11 | permitC.approve( 12 | TOKEN_TYPE_ERC20, 13 | address(token20), 14 | 0, 15 | address(operator), 16 | uint200(USER_STARTING_BALANCE), 17 | uint48(block.timestamp + 100000) 18 | ); 19 | 20 | } 21 | 22 | function testBenchmarkApprovedTransferFromERC20_x1000() public manuallyMetered { 23 | _runBenchmarkApprovedTransferFromERC20(true, 1000, "Approved Transfer From ERC20"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/benchmarks/manual-metering/BenchmarkCancelNonce.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ManualBenchmarkCancelNonce is BenchmarkBase { 7 | function testBenchmarkCancelNonce_x1000() public manuallyMetered { 8 | _runBenchmarkCancelNonce(true, 1000, "Cancel Nonce"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/benchmarks/manual-metering/BenchmarkPermitTransferFromERC20.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "../BenchmarkBase.t.sol"; 4 | 5 | 6 | contract ManualBenchmarkPermitTransferFromERC20 is BenchmarkBase { 7 | function testBenchmarkPermitTransferFromERC20_x1000() public manuallyMetered { 8 | _runBenchmarkPermitTransferFromERC20(true, 1000, "Permit Transfer From ERC20"); 9 | } 10 | 11 | function testBenchmarkPermitTransferFromWithAddtionalDataHashERC20_x1000() public manuallyMetered { 12 | _runBenchmarkPermitTransferFromWithAdditionalDataHashERC20(true, 1000, "Permit Transfer From With Additional Data Hash ERC20"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/benchmarks/mocks/ContractMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.24; 3 | 4 | contract ContractMock { 5 | constructor() {} 6 | 7 | fallback() external payable {} 8 | receive() external payable {} 9 | } -------------------------------------------------------------------------------- /test/benchmarks/mocks/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.24; 3 | 4 | import { ERC20 as SolmateERC20 } from "@rari-capital/solmate/src/tokens/ERC20.sol"; 5 | 6 | // Used for minting test ERC20s in our tests 7 | contract TestERC20 is SolmateERC20("Test20", "TST20", 18) { 8 | constructor() {} 9 | 10 | function mint(address to, uint256 amount) external returns (bool) { 11 | _mint(to, amount); 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/invalidateUnorderedApprovalNonces/invalidateUnorderedApprovalNonces.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "../Base.t.sol"; 4 | 5 | contract invalidateUnorderedApprovalNonceTest is BaseTest { 6 | event UnorderedApprovalNonceInvalidation(address indexed owner, uint256 nonce); 7 | 8 | function setUp() public override { 9 | super.setUp(); 10 | } 11 | 12 | modifier whenNonceIsValid() { 13 | _; 14 | } 15 | 16 | modifier whenNonceIsInvalid() { 17 | _; 18 | } 19 | 20 | function testinvalidateUnorderedApprovalNonce() whenNonceIsValid public { 21 | assertEq(permitC.isValidUnorderedNonce(alice, 0), true); 22 | 23 | vm.prank(alice); 24 | permitC.invalidateUnorderedNonce(0); 25 | 26 | assertEq(permitC.isValidUnorderedNonce(alice, 0), false); 27 | } 28 | 29 | function testinvalidateUnorderedApprovalNonce_InvalidNonce() public whenNonceIsInvalid { 30 | vm.prank(alice); 31 | permitC.invalidateUnorderedNonce(0); 32 | 33 | assertEq(permitC.isValidUnorderedNonce(alice, 0), false); 34 | 35 | vm.prank(alice); 36 | vm.expectRevert(PermitC__NonceAlreadyUsedOrRevoked.selector); 37 | permitC.invalidateUnorderedNonce(0); 38 | 39 | assertEq(permitC.isValidUnorderedNonce(alice, 0), false); 40 | } 41 | 42 | function testinvalidateUnorderedApprovalNonce_MultipleSpaced() public whenNonceIsValid { 43 | for (uint256 i = 0; i <= 512; i++) { 44 | assertEq(permitC.isValidUnorderedNonce(alice, i), true); 45 | } 46 | 47 | vm.startPrank(alice); 48 | permitC.invalidateUnorderedNonce(0); 49 | permitC.invalidateUnorderedNonce(2); 50 | permitC.invalidateUnorderedNonce(257); 51 | permitC.invalidateUnorderedNonce(280); 52 | vm.stopPrank(); 53 | 54 | for (uint256 i = 0; i <= 512; i++) { 55 | assertEq(permitC.isValidUnorderedNonce(alice, i), i == 0 ? false : i == 2 ? false : i == 257 ? false : i == 280 ? false : true); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/invalidateUnorderedApprovalNonces/invalidateUnorderedApprovalNonces.tree: -------------------------------------------------------------------------------- 1 | // Invalidates the provided nonce, making all signatures using it invalid. 2 | // A nonce can be invalidated even if it is already invalid, saving some operation cost 3 | invalidateUnorderedApprovalNonces.t.sol 4 | |- when the nonce is valid 5 | |- set the bitmap storing the nonces to 1, invalidating the nonce 6 | |- emit an UnorderedApprovalNonceInvalidation event 7 | |- when the nonce is invalid 8 | |- set the bitmap storing the nonces to 1, leaving the nonce invalidated 9 | |- emit an UnorderedApprovalNonceInvalidation event 10 | -------------------------------------------------------------------------------- /test/invalidateUnorderedSignatureNonces/invalidateUnorderedSignatureNonces.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "../Base.t.sol"; 4 | 5 | contract invalidateUnorderedSignaturelNonceTest is BaseTest { 6 | event UnorderedSignatureNonceInvalidation(address indexed owner, uint256 nonce); 7 | 8 | function setUp() public override { 9 | super.setUp(); 10 | } 11 | 12 | modifier whenNonceIsValid() { 13 | _; 14 | } 15 | 16 | modifier whenNonceIsInvalid() { 17 | _; 18 | } 19 | 20 | function testinvalidateUnorderedNonce() whenNonceIsValid public { 21 | assertEq(permitC.isValidUnorderedNonce(alice, 0), true); 22 | 23 | vm.prank(alice); 24 | permitC.invalidateUnorderedNonce(0); 25 | 26 | assertEq(permitC.isValidUnorderedNonce(alice, 0), false); 27 | } 28 | 29 | function testinvalidateUnorderedNonce_InvalidNonce() whenNonceIsInvalid public { 30 | vm.prank(alice); 31 | permitC.invalidateUnorderedNonce(0); 32 | 33 | assertEq(permitC.isValidUnorderedNonce(alice, 0), false); 34 | 35 | vm.prank(alice); 36 | vm.expectRevert(PermitC__NonceAlreadyUsedOrRevoked.selector); 37 | permitC.invalidateUnorderedNonce(0); 38 | 39 | assertEq(permitC.isValidUnorderedNonce(alice, 0), false); 40 | } 41 | 42 | function testinvalidateUnorderedNonce_MultipleSpaced() whenNonceIsValid public { 43 | for (uint256 i = 0; i <= 512; i++) { 44 | assertEq(permitC.isValidUnorderedNonce(alice, i), true); 45 | } 46 | 47 | vm.startPrank(alice); 48 | permitC.invalidateUnorderedNonce(0); 49 | permitC.invalidateUnorderedNonce(2); 50 | permitC.invalidateUnorderedNonce(257); 51 | permitC.invalidateUnorderedNonce(280); 52 | vm.stopPrank(); 53 | 54 | for (uint256 i = 0; i <= 512; i++) { 55 | assertEq(permitC.isValidUnorderedNonce(alice, i), i == 0 ? false : i == 2 ? false : i == 257 ? false : i == 280 ? false : true); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/invalidateUnorderedSignatureNonces/invalidateUnorderedSignatureNonces.tree: -------------------------------------------------------------------------------- 1 | // Invalidates the provided nonce, making all signatures using it invalid. 2 | // A nonce can be invalidated even if it is already invalid, saving some operation cost 3 | invalidateUnorderedNonces.t.sol 4 | |- when the nonce is valid 5 | |- set the bitmap storing the nonces to 1, invalidating the nonce 6 | |- emit an UnorderedSignatureNonceInvalidation event 7 | |- when the nonce is invalid 8 | |- set the bitmap storing the nonces to 1, leaving the nonce invalidated 9 | |- emit an UnorderedSignatureNonceInvalidation event 10 | -------------------------------------------------------------------------------- /test/lockdown/lockdown.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "../Base.t.sol"; 4 | 5 | contract lockdownTest is BaseTest { 6 | 7 | function setUp() public override { 8 | super.setUp(); 9 | } 10 | 11 | function testLockdown_base(address account) public { 12 | for (uint256 i = 0; i < 100; i++) { 13 | uint256 previousMasterNonce = permitC.masterNonce(account); 14 | 15 | vm.startPrank(account); 16 | vm.expectEmit(true, false, false, false); 17 | emit Lockdown(account); 18 | permitC.lockdown(); 19 | vm.stopPrank(); 20 | 21 | uint256 updatedMasterNonce = permitC.masterNonce(account); 22 | assertEq(updatedMasterNonce - previousMasterNonce, 1); 23 | } 24 | } 25 | 26 | function testLockdown_ERC721() public { 27 | address token = _deployNew721(carol, 0); 28 | 29 | _mint721(token, alice, 1); 30 | 31 | assertEq(ERC721(token).ownerOf(1), alice); 32 | 33 | vm.startPrank(alice); 34 | ERC721(token).approve(address(permitC), 1); 35 | 36 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, bob, 1, uint48(block.timestamp)); 37 | permitC.approve(TOKEN_TYPE_ERC721, token, 1, carol, 1, uint48(block.timestamp + 1000)); 38 | 39 | vm.expectEmit(true, true, false, false); 40 | emit Lockdown(alice); 41 | permitC.lockdown(); 42 | vm.stopPrank(); 43 | 44 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC721, token, 1); 45 | (uint256 allowanceCarol,) = permitC.allowance(alice, carol, TOKEN_TYPE_ERC721, token, 1); 46 | 47 | assertEq(allowanceBob, 0); 48 | assertEq(allowanceCarol, 0); 49 | 50 | vm.prank(bob); 51 | vm.expectRevert(PermitC__ApprovalTransferPermitExpiredOrUnset.selector); 52 | permitC.transferFromERC721(alice, bob, token, 1); 53 | 54 | assertEq(ERC721(token).ownerOf(1), alice); 55 | } 56 | 57 | function testLockdown_ERC1155() public { 58 | address token = _deployNew1155(carol, 0, 0); 59 | 60 | _mint1155(token, alice, 1, 1); 61 | 62 | assertEq(ERC1155(token).balanceOf(alice, 1), 1); 63 | 64 | vm.startPrank(alice); 65 | ERC1155(token).setApprovalForAll(address(permitC), true); 66 | 67 | permitC.approve(TOKEN_TYPE_ERC1155, token, 1, bob, 1, uint48(block.timestamp)); 68 | permitC.approve(TOKEN_TYPE_ERC1155, token, 1, carol, 1, uint48(block.timestamp + 1000)); 69 | 70 | vm.expectEmit(true, true, false, false); 71 | emit Lockdown(alice); 72 | permitC.lockdown(); 73 | vm.stopPrank(); 74 | 75 | (uint256 allowanceBob,) = permitC.allowance(alice, bob, TOKEN_TYPE_ERC1155, token, 1); 76 | (uint256 allowanceCarol,) = permitC.allowance(alice, carol, TOKEN_TYPE_ERC1155, token, 1); 77 | assertEq(allowanceBob, 0); 78 | assertEq(allowanceCarol, 0); 79 | 80 | vm.prank(bob); 81 | vm.expectRevert(PermitC__ApprovalTransferPermitExpiredOrUnset.selector); 82 | permitC.transferFromERC1155(alice, bob, token, 1, 1); 83 | 84 | assertEq(ERC1155(token).balanceOf(alice, 1), 1); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/lockdown/lockdown.tree: -------------------------------------------------------------------------------- 1 | // Lockdown increments the master nonce of the msg.sender by 1 2 | // This invalidates all outstanding approval and permit signatures, as it is used to determine the signature's validity 3 | lockdown.t.sol 4 | |- when an address calls the lockdown function 5 | |- it should increment the master nonce of the caller by 1 6 | |- it should emit a Lockdown event -------------------------------------------------------------------------------- /test/mocks/ERC1155Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.24; 4 | 5 | import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 6 | 7 | contract ERC1155Mock is ERC1155 { 8 | constructor() ERC1155("") {} 9 | 10 | function mint(address to, uint256 tokenId, uint256 amount) external { 11 | _mint(to, tokenId, amount, ""); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/mocks/ERC1155Reverter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.24; 4 | 5 | import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 6 | 7 | contract ERC1155Reverter is ERC1155 { 8 | constructor() ERC1155("") {} 9 | 10 | function mint(address to, uint256 tokenId, uint256 amount) external { 11 | _mint(to, tokenId, amount, ""); 12 | } 13 | 14 | function safeTransferFrom( 15 | address from, 16 | address to, 17 | uint256 tokenId, 18 | uint256 amount, 19 | bytes memory data 20 | ) public override { 21 | revert(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/mocks/ERC1271ContractSignerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | contract ERC1271ContractSignerMock { 6 | // bytes4(keccak256("isValidSignature(bytes32,bytes)") 7 | bytes4 internal constant MAGICVALUE = 0x1626ba7e; 8 | 9 | /** 10 | * @dev Should return whether the signature provided is valid for the provided hash 11 | * 12 | * MUST return the bytes4 magic value 0x1626ba7e when function passes. 13 | * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) 14 | * MUST allow external calls 15 | */ 16 | function isValidSignature(bytes32, /*_hash*/ bytes memory /*_signature*/ ) 17 | public 18 | pure 19 | returns (bytes4 magicValue) 20 | { 21 | return MAGICVALUE; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/mocks/ERC1271InvalidContractSignerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | contract ERC1271InvalidContractSignerMock { 6 | // bytes4(keccak256("isValidSignature(bytes32,bytes)") 7 | bytes4 internal constant MAGICVALUE = 0x1626ba7e; 8 | 9 | /** 10 | * @dev Should return whether the signature provided is valid for the provided hash 11 | * 12 | * MUST return the bytes4 magic value 0x1626ba7e when function passes. 13 | * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) 14 | * MUST allow external calls 15 | */ 16 | function isValidSignature(bytes32, /*_hash*/ bytes memory /*_signature*/ ) 17 | public 18 | pure 19 | returns (bytes4 magicValue) 20 | { 21 | return 0x00000000; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/mocks/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract ERC20Mock is ERC20 { 8 | constructor() ERC20("ERC-721C Mock", "MOCK") {} 9 | 10 | function mint(address to, uint256 amount) external { 11 | _mint(to, amount); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/mocks/ERC20Reverter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract ERC20Reverter is ERC20 { 8 | constructor() ERC20("ERC-721C Mock", "MOCK") {} 9 | 10 | function mint(address to, uint256 amount) external { 11 | _mint(to, amount); 12 | } 13 | 14 | function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { 15 | revert(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/mocks/ERC721Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | contract ERC721Mock is ERC721 { 8 | constructor() ERC721("ERC-721C Mock", "MOCK") {} 9 | 10 | function mint(address to, uint256 tokenId) external { 11 | _mint(to, tokenId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/mocks/ERC721Reverter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | contract ERC721Reverter is ERC721 { 8 | constructor() ERC721("ERC-721C Mock", "MOCK") {} 9 | 10 | function mint(address to, uint256 tokenId) external { 11 | _mint(to, tokenId); 12 | } 13 | 14 | function transferFrom( 15 | address from, 16 | address to, 17 | uint256 tokenId 18 | ) public override { 19 | revert(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/permitTransferFrom/permitTransferFrom.t.sol: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/permitTransferFrom/permitTransferFrom.tree: -------------------------------------------------------------------------------- 1 | // Permit transfer from allows a token owner to sign a message to enable another user to execute a transfer from 2 | // This signature has a token address, ID, amount, nonce, operator and expiry 3 | // If any of these values are invalid, the signature will be rejected 4 | // The signature is generated by the token owner and sent to the operator 5 | // If the signature resolves to any address aside from the token owner, the signature will be rejected 6 | permitTransferFrom.t.sol 7 | |- when PermitC is not approved for the requested token ID 8 | |- it should revert 9 | |- when PermitC is approved for the requested token ID 10 | |- when the caller is not the operator on the signature 11 | |- it should revert 12 | |- when the caller is the operator on the signature 13 | |- when the function on the signature does not match `PermitTransferFrom(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 14 | |- it should revert 15 | |- when the function on the signature matches `PermitTransferFrom(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 16 | |- when the block timestamp is greater than the expiry 17 | |- it should revert 18 | |- when the block timestamp is less than the expiry 19 | |- when the requested amount is greater than the approved amount 20 | |- it should revert 21 | |- when the requested amount is less than the approved amount 22 | |- when the nonce has been used 23 | |- it should revert 24 | |- when the nonce has not been used 25 | |- when the token address is not the same as the token address on the signature 26 | |- it should revert 27 | |- when the token address is the same as the token address on the signature 28 | |- when the token ID is not the same as the token ID on the signature 29 | |- it should revert 30 | |- when the token ID is the same as the token ID on the signature 31 | |- when the operator is not the same as the operator on the signature 32 | |- it should revert 33 | |- when the operator is the same as the operator on the signature 34 | |- when the signer is not the token owner 35 | |- it should revert 36 | |- when the signer is the token owner 37 | |- when the owner's master nonce is less than the nonce on the signature 38 | |- it should revert 39 | |- when the owner's master nonce is greater than the nonce on the signature 40 | |- it should revert 41 | |- when the owner's master nonce is equal to the nonce on the signature 42 | |- when the signer is an EOA 43 | |- when the protocol is ERC721 44 | |- it should transfer the requested token ID 45 | |- it should emit a Transfer event 46 | |- it should mark the nonce as used 47 | |- when the protocol is ERC1155 48 | |- when the amount is 1 49 | |- it should transfer the requested token ID 50 | |- it should emit a TransferSingle event 51 | |- it should mark the nonce as used 52 | |- when the amount is greater than 1 53 | |- it should transfer the requested token ID 54 | |- it should emit a TransferBatch event 55 | |- it should mark the nonce as used 56 | |- when the protocol is not ERC721 or ERC1155 57 | |- it should revert 58 | |- when the signer is a smart contract 59 | |- when the smart contract implements EIP1271 60 | |- when the smart contract returns an invalid signature 61 | |- it should revert 62 | |- when the smart contract returns a valid signature 63 | |- it should transfer the requested amount 64 | |- it should emit a Transfer event 65 | |- it should mark the nonce as used 66 | |- when the smart contract does not implement EIP1271 67 | |- it should revert -------------------------------------------------------------------------------- /test/permitTransferFrom/permitTransferFromBatch.tree: -------------------------------------------------------------------------------- 1 | // The batch implementation of permitTransferFrom allows for an array fo token IDs to be transferred via a single signature 2 | // Permit transfer from allows a token owner to sign a message to enable another user to execute a transfer from 3 | // This signature has a token address, ID, amount, nonce, operator and expiry 4 | // If any of these values are invalid, the signature will be rejected 5 | // The signature is generated by the token owner and sent to the operator 6 | // If the signature resolves to any address aside from the token owner, the signature will be rejected 7 | permitTransferFrom.t.sol 8 | |- When the signature expiration is in the past 9 | |- it should revert 10 | |- When the signature expiration is the current timestamp or in the future 11 | |- When the length of the ID array is not equal to the length of the tokens array 12 | |- it should revert 13 | |- When the length of the ID array is equal to the length of the tokens array 14 | |- When the length of the ID array is not equal to the length of the amounts array 15 | |- it should revert 16 | |- When the length of the ID array is equal to the length of the amounts array 17 | |- When the length of the ID array is not equal to the length of the details array 18 | |- it should revert 19 | |- When the length of the ID array is equal to the length of the details array 20 | |- When the length of the ID array is 0 21 | |- it should revert 22 | |- When the length of the ID array is greater than 0 23 | |- When the signature nonce is marked as used 24 | |- it should revert 25 | |- When the signature nonce is not marked as used 26 | |- When the signature does not recover to the token owner 27 | |- it should revert 28 | |- When the signature does recover to the token owner 29 | |- when the owner's master nonce is less than the nonce on the signature 30 | |- it should revert 31 | |- when the owner's master nonce is greater than the nonce on the signature 32 | |- it should revert 33 | |- when the owner's master nonce is equal to the nonce on the signature 34 | |- when the signer is an EOA 35 | |- when the protocol is ERC721 36 | |- it should transfer the requested token ID 37 | |- it should emit a Transfer event 38 | |- it should mark the nonce as used 39 | |- when the protocol is ERC1155 40 | |- when the amount is 1 41 | |- it should transfer the requested token ID 42 | |- it should emit a TransferSingle event 43 | |- it should mark the nonce as used 44 | |- when the amount is greater than 1 45 | |- it should transfer the requested token ID 46 | |- it should emit a TransferBatch event 47 | |- it should mark the nonce as used 48 | |- when the protocol is not ERC721 or ERC1155 49 | |- it should revert 50 | |- when the signer is a smart contract 51 | |- when the smart contract implements EIP1271 52 | |- when the smart contract returns an invalid signature 53 | |- it should revert 54 | |- when the smart contract returns a valid signature 55 | |- it should transfer the requested amount 56 | |- it should emit a Transfer event 57 | |- it should mark the nonce as used 58 | |- when the smart contract does not implement EIP1271 59 | |- it should revert -------------------------------------------------------------------------------- /test/permitTransferFromWithAdditionalData/permitTransferFromWithAdditionalData.tree: -------------------------------------------------------------------------------- 1 | // Permit transfer from with additional data allows a token owner to sign a message with appended data to approve an operator to transfer a token ID 2 | // This signature has a token address, ID, amount, nonce, operator and expiry in addition to whatever additional data is defined 3 | // If any of these values are invalid, the signature will be rejected 4 | // The signature is generated by the token owner and sent to the operator 5 | // If the signature resolves to any address aside from the token owner, the signature will be rejected 6 | permitTransferFromWithAdditionalData.t.sol 7 | |- when PermitC is not approved for the requested token ID 8 | |- it should revert 9 | |- when PermitC is approved for the requested token ID 10 | |- when the caller is not the operator on the signature 11 | |- it should revert 12 | |- when the caller is the operator on the signature 13 | |- when the function on the signature is not a concatenation of `PermitTransferFromAdvanced(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,` and the additional data 14 | |- it should revert 15 | |- when the function on the signature is a concatenation of `PermitTransferFromAdvanced(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,` and the additional data 16 | |- when the block timestamp is greater than the expiry 17 | |- it should revert 18 | |- when the block timestamp is less than the expiry 19 | |- when the requested amount is greater than the approved amount 20 | |- it should revert 21 | |- when the requested amount is less than the approved amount 22 | |- when the nonce has been used 23 | |- it should revert 24 | |- when the nonce has not been used 25 | |- when the token address is not the same as the token address on the signature 26 | |- it should revert 27 | |- when the token address is the same as the token address on the signature 28 | |- when the token ID is not the same as the token ID on the signature 29 | |- it should revert 30 | |- when the token ID is the same as the token ID on the signature 31 | |- when the signer is not the token owner 32 | |- it should revert 33 | |- when the signer is the token owner 34 | |- when the additional data is not the same as the additional data on the signature 35 | |- it should revert 36 | |- when the additional data is the same as the additional data on the signature 37 | |- when the owner's master nonce is less than the nonce on the signature 38 | |- it should revert 39 | |- when the owner's master nonce is greater than the nonce on the signature 40 | |- it should revert 41 | |- when the owner's master nonce is equal to the nonce on the signature 42 | |- when the signer is an EOA 43 | |- when the protocol is ERC721 44 | |- it should transfer the requested token ID 45 | |- it should emit a Transfer event 46 | |- it should mark the nonce as used 47 | |- when the protocol is ERC1155 48 | |- when the amount is 1 49 | |- it should transfer the requested token ID 50 | |- it should emit a TransferSingle event 51 | |- it should mark the nonce as used 52 | |- when the amount is greater than 1 53 | |- it should transfer the requested token ID 54 | |- it should emit a TransferBatch event 55 | |- it should mark the nonce as used 56 | |- when the protocol is not ERC721 or ERC1155 57 | |- it should revert 58 | |- when the signer is a smart contract 59 | |- when the smart contract implements EIP1271 60 | |- when the smart contract returns an invalid signature 61 | |- it should revert 62 | |- when the smart contract returns a valid signature 63 | |- it should transfer the requested amount 64 | |- it should emit a Transfer event 65 | |- it should mark the nonce as used 66 | |- when the smart contract does not implement EIP1271 67 | |- it should revert 68 | -------------------------------------------------------------------------------- /test/permitTransferFromWithAdditionalData/permitTransferFromWithAdditionalDataBatch.tree: -------------------------------------------------------------------------------- 1 | // The batch implementation of permitTransferFromWithAdditionalData allows for an array fo token IDs to be transferred via a single signature while checking for additional signature data 2 | // This signature has a token address, ID, amount, nonce, operator and expiry in addition to whatever additional data is defined 3 | // If any of these values are invalid, the signature will be rejected 4 | // The signature is generated by the token owner and sent to the operator 5 | // If the signature resolves to any address aside from the token owner, the signature will be rejected 6 | permitTransferFromWithAdditionalData.t.sol 7 | |- When the signature expiration is in the past 8 | |- it should revert 9 | |- When the signature expiration is the current timestamp or in the future 10 | |- When the length of the ID array is not equal to the length of the tokens array 11 | |- it should revert 12 | |- When the length of the ID array is equal to the length of the tokens array 13 | |- When the length of the ID array is not equal to the length of the amounts array 14 | |- it should revert 15 | |- When the length of the ID array is equal to the length of the amounts array 16 | |- When the length of the ID array is not equal to the length of the details array 17 | |- it should revert 18 | |- When the length of the ID array is equal to the length of the details array 19 | |- When the length of the ID array is 0 20 | |- it should revert 21 | |- When the length of the ID array is greater than 0 22 | |- when PermitC is not approved for the requested token ID 23 | |- it should revert 24 | |- when PermitC is approved for the requested token ID 25 | |- when the caller is not the operator on the signature 26 | |- it should revert 27 | |- when the caller is the operator on the signature 28 | |- when the function on the signature is not a concatenation of `PermitTransferFromAdvanced(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,` and the additional data 29 | |- it should revert 30 | |- when the function on the signature is a concatenation of `PermitTransferFromAdvanced(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,` and the additional data 31 | |- when the block timestamp is greater than the expiry 32 | |- it should revert 33 | |- when the block timestamp is less than the expiry 34 | |- when the requested amount is greater than the approved amount 35 | |- it should revert 36 | |- when the requested amount is less than the approved amount 37 | |- when the nonce has been used 38 | |- it should revert 39 | |- when the nonce has not been used 40 | |- when the token address is not the same as the token address on the signature 41 | |- it should revert 42 | |- when the token address is the same as the token address on the signature 43 | |- when the token ID is not the same as the token ID on the signature 44 | |- it should revert 45 | |- when the token ID is the same as the token ID on the signature 46 | |- when the signer is not the token owner 47 | |- it should revert 48 | |- when the signer is the token owner 49 | |- when the additional data is not the same as the additional data on the signature 50 | |- it should revert 51 | |- when the additional data is the same as the additional data on the signature 52 | |- when the owner's master nonce is less than the nonce on the signature 53 | |- it should revert 54 | |- when the owner's master nonce is greater than the nonce on the signature 55 | |- it should revert 56 | |- when the owner's master nonce is equal to the nonce on the signature 57 | |- when the signer is an EOA 58 | |- when the protocol is ERC721 59 | |- it should transfer the requested token ID 60 | |- it should emit a Transfer event 61 | |- it should mark the nonce as used 62 | |- when the protocol is ERC1155 63 | |- when the amount is 1 64 | |- it should transfer the requested token ID 65 | |- it should emit a TransferSingle event 66 | |- it should mark the nonce as used 67 | |- when the amount is greater than 1 68 | |- it should transfer the requested token ID 69 | |- it should emit a TransferBatch event 70 | |- it should mark the nonce as used 71 | |- when the protocol is not ERC721 or ERC1155 72 | |- it should revert 73 | |- when the signer is a smart contract 74 | |- when the smart contract implements EIP1271 75 | |- when the smart contract returns an invalid signature 76 | |- it should revert 77 | |- when the smart contract returns a valid signature 78 | |- it should transfer the requested amount 79 | |- it should emit a Transfer event 80 | |- it should mark the nonce as used 81 | |- when the smart contract does not implement EIP1271 82 | |- it should revert 83 | -------------------------------------------------------------------------------- /test/setApprovalForAll/setApprovalForAll.tree: -------------------------------------------------------------------------------- 1 | // Set approval for all allows an operator to set an approval for all token IDs associated with a token address for an operator 2 | // The approval includes the token address, an operator, and an expiration timestamp 3 | // There are no restrictions on this function, any address can approve an operator to approve a token on their behalf 4 | // There are no restrictions on the operator, it can be any address 5 | // There are no restrictions on the expiration timestamp, it can be any value 6 | // There are no restrictions on the token address, it can be any address 7 | PermitC721.ApprovalTransfer.t.sol & PermitC1155.ApprovalTransfer.t.sol 8 | |- when the expiration is 0 9 | |- it should update the packed approval found at key keccak256(abi.encode(owner, operator, _masterNonces[owner])) with the provided values and an expiration at the current block.timestamp and unlimited amount 10 | |- it should emit an ApprovalForAll event 11 | |- when the expiration is not 0 12 | |- it should update the packed approval found at key keccak256(abi.encode(owner, operator, _masterNonces[owner])) with the provided values and an expiration at the provided timestamp and unlimited amount 13 | |- it should emit an ApprovalForAll event -------------------------------------------------------------------------------- /test/transferFrom/transferFrom.tree: -------------------------------------------------------------------------------- 1 | // Transfers a token from a provided address to another provided address. 2 | // The token must be owned by the from address. 3 | // The from address must have approved the transfer. 4 | // The from address must have approved PermitC for transfer 5 | // The token must be a valid ERC721 or ERC1155 token. 6 | // The recipient must not be address zero. 7 | transferFrom.t.sol 8 | |- when the msg.sender has approval for all tokens 9 | |- when the protocol is ERC721 10 | |- when the token is not a valid ERC721 token 11 | |- it should revert 12 | |- when the token is a valid ERC721 token 13 | |- when the token is not owned by the from address 14 | |- it should revert 15 | |- when the token is owned by the from address 16 | |- when the recipient is the zero address 17 | |- it should revert 18 | |- when the recipient is not the zero address 19 | |- when the recipient is a smart contract 20 | |- when the recipient does not implement the ERC721Receiver interface 21 | |- it should revert 22 | |- when the recipient implements the ERC721Receiver interface 23 | |- when the recipient does not accept the transfer 24 | |- it should revert 25 | |- when the recipient accepts the transfer 26 | |- it should reduce the balance of the from address by 1 27 | |- it should increase the balance of the recipient by 1 28 | |- it should set the owner of the token to the recipient 29 | |- it should emit a Transfer event 30 | |- when the recipient is not a smart contract 31 | |- it should reduce the balance of the from address by 1 32 | |- it should increase the balance of the recipient by 1 33 | |- it should set the owner of the token to the recipient 34 | |- it should emit a Transfer event 35 | |- when the protocol is ERC1155 36 | |- when the token is not a valid ERC1155 token 37 | |- it should revert 38 | |- when the token is a valid ERC1155 token 39 | |- when the token is not owned by the from address 40 | |- it should revert 41 | |- when the token is owned by the from address 42 | |- when the recipient is the zero address 43 | |- it should revert 44 | |- when the recipient is not the zero address 45 | |- when the recipient is a smart contract 46 | |- when the recipient does not implement the ERC1155Receiver interface 47 | |- it should revert 48 | |- when the recipient implements the ERC1155Receiver interface 49 | |- when the recipient does not accept the transfer 50 | |- it should revert 51 | |- when the recipient accepts the transfer 52 | |- it should reduce the balance of the from address by 1 53 | |- it should increase the balance of the recipient by 1 54 | |- it should emit a TransferSingle event 55 | |- when the recipient is not a smart contract 56 | |- it should reduce the balance of the from address by 1 57 | |- it should increase the balance of the recipient by 1 58 | |- it should emit a TransferSingle event 59 | |- when the protocol is not ERC721 or ERC1155 60 | |- it should revert 61 | |- when the msg.sender does not have approval for all tokens 62 | |- when the approval is not set for the token 63 | |- it should revert 64 | |- when the approval is set for the token 65 | |- when the approval expiration is in the past 66 | |- it should revert 67 | |- when the approval expiration is in the future or the current block.timestamp 68 | |- when the approval amount is less than the amount being transferred 69 | |- it should revert 70 | |- when the approval amount is greater than or equal to the amount being transferred 71 | |- it should decrement the approval amount by the amount being transferred 72 | |- when the protocol is ERC721 73 | |- when the token is not a valid ERC721 token 74 | |- it should revert 75 | |- when the token is a valid ERC721 token 76 | |- when the token is not owned by the from address 77 | |- it should revert 78 | |- when the token is owned by the from address 79 | |- when the recipient is the zero address 80 | |- it should revert 81 | |- when the recipient is not the zero address 82 | |- when the recipient is a smart contract 83 | |- when the recipient does not implement the ERC721Receiver interface 84 | |- it should revert 85 | |- when the recipient implements the ERC721Receiver interface 86 | |- when the recipient does not accept the transfer 87 | |- it should revert 88 | |- when the recipient accepts the transfer 89 | |- it should reduce the balance of the from address by 1 90 | |- it should increase the balance of the recipient by 1 91 | |- it should set the owner of the token to the recipient 92 | |- it should emit a Transfer event 93 | |- when the recipient is not a smart contract 94 | |- it should reduce the balance of the from address by 1 95 | |- it should increase the balance of the recipient by 1 96 | |- it should set the owner of the token to the recipient 97 | |- it should emit a Transfer event 98 | |- when the protocol is ERC1155 99 | |- when the token is not a valid ERC1155 token 100 | |- it should revert 101 | |- when the token is a valid ERC1155 token 102 | |- when the token is not owned by the from address 103 | |- it should revert 104 | |- when the token is owned by the from address 105 | |- when the recipient is the zero address 106 | |- it should revert 107 | |- when the recipient is not the zero address 108 | |- when the recipient is a smart contract 109 | |- when the recipient does not implement the ERC1155Receiver interface 110 | |- it should revert 111 | |- when the recipient implements the ERC1155Receiver interface 112 | |- when the recipient does not accept the transfer 113 | |- it should revert 114 | |- when the recipient accepts the transfer 115 | |- it should reduce the balance of the from address by 1 116 | |- it should increase the balance of the recipient by 1 117 | |- it should emit a TransferSingle event 118 | |- when the recipient is not a smart contract 119 | |- it should reduce the balance of the from address by 1 120 | |- it should increase the balance of the recipient by 1 121 | |- it should emit a TransferSingle event 122 | |- when the protocol is not ERC721 or ERC1155 123 | |- it should revert -------------------------------------------------------------------------------- /test/transferFrom/transferFromBatch.tree: -------------------------------------------------------------------------------- 1 | // Transfers a token from a provided address to another provided address. 2 | // The token must be owned by the from address. 3 | // The from address must have approved the transfer. 4 | // The from address must have approved PermitC for transfer 5 | // The token must be a valid ERC721 or ERC1155 token. 6 | // The recipient must not be address zero. 7 | transferFrom.t.sol 8 | |- When the signature expiration is in the past 9 | |- it should revert 10 | |- When the signature expiration is the current timestamp or in the future 11 | |- When the length of the ID array is not equal to the length of the tokens array 12 | |- it should revert 13 | |- When the length of the ID array is equal to the length of the tokens array 14 | |- When the length of the ID array is not equal to the length of the amounts array 15 | |- it should revert 16 | |- When the length of the ID array is equal to the length of the amounts array 17 | |- When the length of the ID array is 0 18 | |- it should revert 19 | |- When the length of the ID array is greater than 0 20 | |- when the msg.sender has approval for all tokens 21 | |- when the protocol is ERC721 22 | |- when the token is not a valid ERC721 token 23 | |- it should revert 24 | |- when the token is a valid ERC721 token 25 | |- when the token is not owned by the from address 26 | |- it should revert 27 | |- when the token is owned by the from address 28 | |- when the recipient is the zero address 29 | |- it should revert 30 | |- when the recipient is not the zero address 31 | |- when the recipient is a smart contract 32 | |- when the recipient does not implement the ERC721Receiver interface 33 | |- it should revert 34 | |- when the recipient implements the ERC721Receiver interface 35 | |- when the recipient does not accept the transfer 36 | |- it should revert 37 | |- when the recipient accepts the transfer 38 | |- it should reduce the balance of the from address by 1 39 | |- it should increase the balance of the recipient by 1 40 | |- it should set the owner of the token to the recipient 41 | |- it should emit a Transfer event 42 | |- when the recipient is not a smart contract 43 | |- it should reduce the balance of the from address by 1 44 | |- it should increase the balance of the recipient by 1 45 | |- it should set the owner of the token to the recipient 46 | |- it should emit a Transfer event 47 | |- when the protocol is ERC1155 48 | |- when the token is not a valid ERC1155 token 49 | |- it should revert 50 | |- when the token is a valid ERC1155 token 51 | |- when the token is not owned by the from address 52 | |- it should revert 53 | |- when the token is owned by the from address 54 | |- when the recipient is the zero address 55 | |- it should revert 56 | |- when the recipient is not the zero address 57 | |- when the recipient is a smart contract 58 | |- when the recipient does not implement the ERC1155Receiver interface 59 | |- it should revert 60 | |- when the recipient implements the ERC1155Receiver interface 61 | |- when the recipient does not accept the transfer 62 | |- it should revert 63 | |- when the recipient accepts the transfer 64 | |- it should reduce the balance of the from address by 1 65 | |- it should increase the balance of the recipient by 1 66 | |- it should emit a TransferSingle event 67 | |- when the recipient is not a smart contract 68 | |- it should reduce the balance of the from address by 1 69 | |- it should increase the balance of the recipient by 1 70 | |- it should emit a TransferSingle event 71 | |- when the protocol is not ERC721 or ERC1155 72 | |- it should revert 73 | |- when the msg.sender does not have approval for all tokens 74 | |- when the approval is not set for the token 75 | |- it should revert 76 | |- when the approval is set for the token 77 | |- when the approval expiration is in the past 78 | |- it should revert 79 | |- when the approval expiration is in the future or the current block.timestamp 80 | |- when the approval amount is less than the amount being transferred 81 | |- it should revert 82 | |- when the approval amount is greater than or equal to the amount being transferred 83 | |- it should decrement the approval amount by the amount being transferred 84 | |- when the protocol is ERC721 85 | |- when the token is not a valid ERC721 token 86 | |- it should revert 87 | |- when the token is a valid ERC721 token 88 | |- when the token is not owned by the from address 89 | |- it should revert 90 | |- when the token is owned by the from address 91 | |- when the recipient is the zero address 92 | |- it should revert 93 | |- when the recipient is not the zero address 94 | |- when the recipient is a smart contract 95 | |- when the recipient does not implement the ERC721Receiver interface 96 | |- it should revert 97 | |- when the recipient implements the ERC721Receiver interface 98 | |- when the recipient does not accept the transfer 99 | |- it should revert 100 | |- when the recipient accepts the transfer 101 | |- it should reduce the balance of the from address by 1 102 | |- it should increase the balance of the recipient by 1 103 | |- it should set the owner of the token to the recipient 104 | |- it should emit a Transfer event 105 | |- when the recipient is not a smart contract 106 | |- it should reduce the balance of the from address by 1 107 | |- it should increase the balance of the recipient by 1 108 | |- it should set the owner of the token to the recipient 109 | |- it should emit a Transfer event 110 | |- when the protocol is ERC1155 111 | |- when the token is not a valid ERC1155 token 112 | |- it should revert 113 | |- when the token is a valid ERC1155 token 114 | |- when the token is not owned by the from address 115 | |- it should revert 116 | |- when the token is owned by the from address 117 | |- when the recipient is the zero address 118 | |- it should revert 119 | |- when the recipient is not the zero address 120 | |- when the recipient is a smart contract 121 | |- when the recipient does not implement the ERC1155Receiver interface 122 | |- it should revert 123 | |- when the recipient implements the ERC1155Receiver interface 124 | |- when the recipient does not accept the transfer 125 | |- it should revert 126 | |- when the recipient accepts the transfer 127 | |- it should reduce the balance of the from address by 1 128 | |- it should increase the balance of the recipient by 1 129 | |- it should emit a TransferSingle event 130 | |- when the recipient is not a smart contract 131 | |- it should reduce the balance of the from address by 1 132 | |- it should increase the balance of the recipient by 1 133 | |- it should emit a TransferSingle event 134 | |- when the protocol is not ERC721 or ERC1155 135 | |- it should revert -------------------------------------------------------------------------------- /test/updateApprovalBySignature/updateApprovalBySignature.tree: -------------------------------------------------------------------------------- 1 | // Update approval by signature allows for an address to increment their on chain approval for a givent address via a signed message 2 | // Approvals allow for long standing and multiple use transfers vs. a single use transfer signature 3 | updateApproval.t.sol 4 | |- when the block.timestamp is greater than the expiration timestamp 5 | |- it should revert 6 | |- when the block.timestamp is less than the expiration timestamp 7 | |- when the function signature does not match `UpdateApproval(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 8 | |- it should revert 9 | |- when the function signature matches `UpdateApproval(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 10 | |- when the token address does not match 11 | |- it should revert 12 | |- when the token address matches 13 | |- when the token ID does not match 14 | |- it should revert 15 | |- when the token ID matches 16 | |- when the permit amount does not match 17 | |- it should revert 18 | |- when the permit amount matches 19 | |- when the operator does not match 20 | |- it should revert 21 | |- when the operator matches 22 | |- when the expiration does not match 23 | |- it should revert 24 | |- when the expiration matches 25 | |- when the master nonce does not match 26 | |- it should revert 27 | |- when the master nonce matches 28 | |- when the signer is a smart contract 29 | |- when the smart contract implements EIP1271 30 | |- when the smart contract returns an invalid signature 31 | |- it should revert 32 | |- when the smart contract returns a valid signature 33 | |- it should increment the approval by the provided amount 34 | |- it should set the expiration for the provided timestamp 35 | |- it should emit an ApprovalPermit event 36 | |- it should mark the signature nonce as used 37 | |- when the smart contract does not implement EIP1271 38 | |- it should revert 39 | |- when the signer is an EOA 40 | |- it should increment the approval by the provided amount 41 | |- it should set the expiration for the provided timestamp 42 | |- it should emit an ApprovalPermit event 43 | |- it should mark the signature nonce as used 44 | 45 | -------------------------------------------------------------------------------- /test/updateApprovalBySignature/updateApprovalBySignatureBatch.tree: -------------------------------------------------------------------------------- 1 | // Batch implementation of update approval by signature, allowing for a single signature to be used to update multiple approvals 2 | // Update approval by signature allows for an address to increment their on chain approval for a givent address via a signed message 3 | // Approvals allow for long standing and multiple use transfers vs. a single use transfer signature 4 | updateApprovalBySignature.t.sol 5 | |- When the signature expiration is in the past 6 | |- it should revert 7 | |- When the signature expiration is the current timestamp or in the future 8 | |- When the length of the ID array is not equal to the length of the tokens array 9 | |- it should revert 10 | |- When the length of the ID array is equal to the length of the tokens array 11 | |- When the length of the tokens array is not equal to the length of the amounts array 12 | |- it should revert 13 | |- When the length of the tokens array is equal to the length of the amounts array 14 | |- When the length of the tokens array is 0 15 | |- it should revert 16 | |- When the length of the tokens array is greater than 0 17 | |- when the function signature does not match `UpdateApproval(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 18 | |- it should revert 19 | |- when the function signature matches `UpdateApproval(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 20 | |- when the token address does not match 21 | |- it should revert 22 | |- when the token address matches 23 | |- when the token ID does not match 24 | |- it should revert 25 | |- when the token ID matches 26 | |- when the permit amount does not match 27 | |- it should revert 28 | |- when the permit amount matches 29 | |- when the operator does not match 30 | |- it should revert 31 | |- when the operator matches 32 | |- when the expiration does not match 33 | |- it should revert 34 | |- when the expiration matches 35 | |- when the master nonce does not match 36 | |- it should revert 37 | |- when the master nonce matches 38 | |- when the signer is a smart contract 39 | |- when the smart contract implements EIP1271 40 | |- when the smart contract returns an invalid signature 41 | |- it should revert 42 | |- when the smart contract returns a valid signature 43 | |- it should increment the approval by the provided amount 44 | |- it should set the expiration for the provided timestamp 45 | |- it should emit an ApprovalPermit event 46 | |- it should mark the signature nonce as used 47 | |- when the smart contract does not implement EIP1271 48 | |- it should revert 49 | |- when the signer is an EOA 50 | |- it should increment the approval by the provided amount 51 | |- it should set the expiration for the provided timestamp 52 | |- it should emit an ApprovalPermit event 53 | |- it should mark the signature nonce as used 54 | -------------------------------------------------------------------------------- /test/updateApprovalForAllBySignature/updateApprovalForAllBySignature.tree: -------------------------------------------------------------------------------- 1 | // Update approval for all by signature allows for an address to increment their on chain approval for a given address via a signed message 2 | // Approvals allow for long standing and multiple use transfers vs. a single use transfer signature 3 | // Approval for all allows for an operator to have access to all tokens in unlimited amounts for a given owner address 4 | updateApprovalForAll.t.sol 5 | |- when the block.timestamp is greater than the expiration timestamp 6 | |- it should revert 7 | |- when the block.timestamp is less than the expiration timestamp 8 | |- when the function signature does not match `UpdateApproval(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 9 | |- it should revert 10 | |- when the function signature matches `UpdateApproval(address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration)` 11 | |- when the token address does not match 12 | |- it should revert 13 | |- when the token address matches 14 | |- when the operator does not match 15 | |- it should revert 16 | |- when the operator matches 17 | |- when the expiration does not match 18 | |- it should revert 19 | |- when the expiration matches 20 | |- when the master nonce does not match 21 | |- it should revert 22 | |- when the master nonce matches 23 | |- when the signer is a smart contract 24 | |- when the smart contract implements EIP1271 25 | |- when the smart contract returns an invalid signature 26 | |- it should revert 27 | |- when the smart contract returns a valid signature 28 | |- it should set approval to uint208.max 29 | |- it should set the expiration for the provided timestamp 30 | |- it should emit an ApprovalForAllPermit event 31 | |- it should mark the signature nonce as used 32 | |- when the smart contract does not implement EIP1271 33 | |- it should revert 34 | |- when the signer is an EOA 35 | |- it should set approval to uint208.max 36 | |- it should set the expiration for the provided timestamp 37 | |- it should emit an ApprovalForAllPermit event 38 | |- it should mark the signature nonce as used 39 | 40 | --------------------------------------------------------------------------------