-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: added contracts/evm for evm permit2 update #915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
d10fa74
feat: added and implemented @x402/evm-contracts
CarsonRoscoe fe637d2
feat: initial draft of extensions
CarsonRoscoe 82e20c7
feat: added contracts/evm foundry project
CarsonRoscoe 92d523b
chore: removed @x402/evm-contracts (as contracts/evm replaced)
CarsonRoscoe e1c018a
feat: add foundry lib dependencies as git submodules
CarsonRoscoe 7ffbee8
feat: pr feedback
CarsonRoscoe 75d3dd1
feat: forge ci
CarsonRoscoe 38066f2
fix: reduced scope of permissions for workflow
CarsonRoscoe 1a2cb3c
fix: lockfiles
CarsonRoscoe 7674e49
fix: update foundry version in ci from nightly to current
CarsonRoscoe 2b03486
feat: sha pin the foundry-rs/foundry-toolchain version in ci
CarsonRoscoe b0a2b10
fix: lint
CarsonRoscoe 5801c26
feat: cleaned up tests
CarsonRoscoe 7249abb
feat: replace X402PermitTransfer event with Settled and SettledWith26…
CarsonRoscoe c3e234e
feat: refactor x402Permit2Proxy into x402ExactPermit2Proxy and x402Up…
CarsonRoscoe 84ba46f
feat: removed extraneous test
CarsonRoscoe 23b60f6
feat: add @x402/extensions exports & unit tests for gasless extensions
CarsonRoscoe ec11c73
feat: extract out common base from exact and upto contracts
CarsonRoscoe 5a973b7
feat: mined vanity addresses & formatted solidity
CarsonRoscoe 082614e
feat: removed validBefore from witness; Permit2 deadline enforces upp…
CarsonRoscoe 9ecccb5
fix: transient network issue with forge checks in ci
CarsonRoscoe a513eb5
fix: normalize etherscan api keys for all networks
CarsonRoscoe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| name: Forge CI | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'contracts/evm/**' | ||
|
|
||
| env: | ||
| FOUNDRY_PROFILE: ci | ||
|
|
||
| jobs: | ||
| forge-checks: | ||
| name: Forge Build, Test & Format | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: contracts/evm | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| submodules: recursive | ||
|
|
||
| - name: Install Foundry | ||
| uses: foundry-rs/foundry-toolchain@8b0419c685ef46cb79ec93fbdc131174afceb730 # v1.6.0 | ||
| with: | ||
| version: v1.2.2 | ||
|
|
||
| - name: Show Forge version | ||
| run: forge --version | ||
|
|
||
| - name: Run Forge build | ||
| run: forge build --sizes | ||
| id: build | ||
|
|
||
| - name: Run Forge tests | ||
| run: forge test -vvv | ||
| id: test | ||
|
|
||
| - name: Check formatting | ||
| run: forge fmt --check | ||
| id: fmt | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [submodule "contracts/evm/lib/forge-std"] | ||
| path = contracts/evm/lib/forge-std | ||
| url = https://github.com/foundry-rs/forge-std | ||
| [submodule "contracts/evm/lib/openzeppelin-contracts"] | ||
| path = contracts/evm/lib/openzeppelin-contracts | ||
| url = https://github.com/openzeppelin/openzeppelin-contracts |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Compiler output | ||
| out/ | ||
| cache/ | ||
|
|
||
| # Environment | ||
| .env | ||
| .env.local | ||
|
|
||
| # Coverage | ||
| lcov.info | ||
| coverage/ | ||
|
|
||
| # Gas snapshots (optional - can commit for tracking) | ||
| # .gas-snapshot | ||
|
|
||
| # IDE | ||
| .idea/ | ||
| .vscode/ | ||
|
|
||
| # OS | ||
| .DS_Store | ||
| Thumbs.db | ||
|
|
||
| # Broadcast files (deployment artifacts) | ||
| broadcast/ | ||
|
|
||
| # Debug | ||
| debug/ | ||
|
|
||
|
|
||
| # Rust vanity-miner build artifacts | ||
| vanity-miner/target/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| # x402 EVM Contracts | ||
|
|
||
| Smart contracts for the x402 payment protocol on EVM chains. | ||
|
|
||
| ## Overview | ||
|
|
||
| The x402 Permit2 Proxy contracts enable trustless, gasless payments using [Permit2](https://github.com/Uniswap/permit2). There are two variants: | ||
|
|
||
| ### `x402ExactPermit2Proxy` | ||
| Transfers the **exact** permitted amount (similar to EIP-3009's `transferWithAuthorization`). The facilitator cannot choose a different amount—it's always the full permitted amount. | ||
|
|
||
| ### `x402UptoPermit2Proxy` | ||
| Allows the facilitator to transfer **up to** the permitted amount. Useful for scenarios where the actual amount is determined at settlement time. | ||
|
|
||
| Both contracts: | ||
| - Use the **witness pattern** to cryptographically bind payment destinations | ||
| - Prevent facilitators from redirecting funds | ||
| - Support both standard Permit2 and EIP-2612 flows | ||
| - Deploy to the **same address on all EVM chains** via CREATE2 | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - [Foundry](https://book.getfoundry.sh/getting-started/installation) | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| forge install | ||
|
|
||
| # Build contracts | ||
| forge build | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| ```bash | ||
| # Run all tests | ||
| forge test | ||
|
|
||
| # Run with verbosity | ||
| forge test -vvv | ||
|
|
||
| # Run Exact proxy tests | ||
| forge test --match-contract X402ExactPermit2ProxyTest | ||
|
|
||
| # Run Upto proxy tests | ||
| forge test --match-contract X402UptoPermit2ProxyTest | ||
|
|
||
| # Run with gas reporting | ||
| forge test --gas-report | ||
|
|
||
| # Run fuzz tests with more runs | ||
| forge test --fuzz-runs 1000 | ||
|
|
||
| # Run invariant tests | ||
| forge test --match-contract Invariants | ||
| ``` | ||
|
|
||
| ### Fork Testing | ||
|
|
||
| Fork tests run against real Permit2 on Base Sepolia: | ||
|
|
||
| ```bash | ||
| # Set up environment | ||
| export BASE_SEPOLIA_RPC_URL="https://sepolia.base.org" | ||
|
|
||
| # Run fork tests for Exact variant | ||
| forge test --match-contract X402ExactPermit2ProxyForkTest --fork-url $BASE_SEPOLIA_RPC_URL | ||
|
|
||
| # Run fork tests for Upto variant | ||
| forge test --match-contract X402UptoPermit2ProxyForkTest --fork-url $BASE_SEPOLIA_RPC_URL | ||
| ``` | ||
|
|
||
| ## Deployment | ||
|
|
||
| ### Compute Expected Addresses | ||
|
|
||
| ```bash | ||
| forge script script/ComputeAddress.s.sol | ||
| ``` | ||
|
|
||
| ### Deploy to Testnet | ||
|
|
||
| ```bash | ||
| # Set environment variables | ||
| export PRIVATE_KEY="your_private_key" | ||
| export BASE_SEPOLIA_RPC_URL="https://sepolia.base.org" | ||
| export BASESCAN_API_KEY="your_api_key" | ||
|
|
||
| # Deploy both contracts with verification | ||
| forge script script/Deploy.s.sol \ | ||
| --rpc-url $BASE_SEPOLIA_RPC_URL \ | ||
| --broadcast \ | ||
| --verify | ||
| ``` | ||
|
|
||
| ### Deploy to Mainnet | ||
|
|
||
| ```bash | ||
| export BASE_RPC_URL="https://mainnet.base.org" | ||
|
|
||
| forge script script/Deploy.s.sol \ | ||
| --rpc-url $BASE_RPC_URL \ | ||
| --broadcast \ | ||
| --verify | ||
| ``` | ||
|
|
||
| ## Vanity Address Mining | ||
|
|
||
| The deployment uses vanity addresses starting with `0x4020`. To mine new salts: | ||
|
|
||
| ```bash | ||
| # Simple Solidity miner (slower) | ||
| forge script script/MineVanity.s.sol | ||
|
|
||
| # For faster mining, use create2crunch or the TypeScript miner | ||
| ``` | ||
|
|
||
| ## Contract Architecture | ||
|
|
||
| ``` | ||
| src/ | ||
| ├── x402ExactPermit2Proxy.sol # Exact amount transfers (EIP-3009-like) | ||
| ├── x402UptoPermit2Proxy.sol # Flexible amount transfers (up to permitted) | ||
| └── interfaces/ | ||
| └── ISignatureTransfer.sol # Permit2 SignatureTransfer interface | ||
|
|
||
| test/ | ||
| ├── x402ExactPermit2Proxy.t.sol # Exact variant unit tests | ||
| ├── x402ExactPermit2Proxy.fork.t.sol # Exact variant fork tests | ||
| ├── x402UptoPermit2Proxy.t.sol # Upto variant unit tests | ||
| ├── x402UptoPermit2Proxy.fork.t.sol # Upto variant fork tests | ||
| ├── invariants/ | ||
| │ ├── X402ExactInvariants.t.sol # Exact variant invariant tests | ||
| │ └── X402UptoInvariants.t.sol # Upto variant invariant tests | ||
| └── mocks/ | ||
| ├── MockERC20.sol | ||
| ├── MockERC20Permit.sol | ||
| ├── MockPermit2.sol | ||
| ├── MaliciousReentrantExact.sol | ||
| └── MaliciousReentrantUpto.sol | ||
|
|
||
| script/ | ||
| ├── Deploy.s.sol # CREATE2 deployment for both contracts | ||
| ├── ComputeAddress.s.sol # Address computation for both contracts | ||
| └── MineVanity.s.sol # Vanity address miner for both contracts | ||
| ``` | ||
|
|
||
| ## Key Functions | ||
|
|
||
| ### `x402ExactPermit2Proxy.settle()` | ||
|
|
||
| Standard settlement path - always transfers the exact permitted amount. | ||
|
|
||
| ```solidity | ||
| function settle( | ||
| ISignatureTransfer.PermitTransferFrom calldata permit, | ||
| address owner, | ||
| Witness calldata witness, | ||
| bytes calldata signature | ||
| ) external; | ||
| ``` | ||
|
|
||
| ### `x402UptoPermit2Proxy.settle()` | ||
|
|
||
| Standard settlement path - transfers the specified amount (up to permitted). | ||
|
|
||
| ```solidity | ||
| function settle( | ||
| ISignatureTransfer.PermitTransferFrom calldata permit, | ||
| uint256 amount, // Facilitator specifies amount to transfer | ||
| address owner, | ||
| Witness calldata witness, | ||
| bytes calldata signature | ||
| ) external; | ||
| ``` | ||
|
|
||
| ### `settleWithPermit()` | ||
|
|
||
| Both contracts support settlement with EIP-2612 permit for fully gasless flow. | ||
| The function signatures follow the same pattern as `settle()` for each variant. | ||
|
|
||
| ## Security | ||
|
|
||
| - **Immutable:** No upgrade mechanism | ||
| - **No custody:** Contracts never hold tokens | ||
| - **Destination locked:** Witness pattern enforces payTo address | ||
| - **Reentrancy protected:** Uses OpenZeppelin's ReentrancyGuard | ||
| - **Deterministic:** Same address on all chains via CREATE2 | ||
|
|
||
| ## Coverage | ||
|
|
||
| ```bash | ||
| # Full coverage report (includes test/script files) | ||
| forge coverage | ||
|
|
||
| # Coverage for src/ contracts only (excludes mocks, tests, scripts) | ||
| forge coverage --no-match-coverage "(test|script)/.*" --offline | ||
| ``` | ||
|
|
||
| ## Gas Snapshots | ||
|
|
||
| ```bash | ||
| # Create snapshot | ||
| forge snapshot | ||
|
|
||
| # Compare against baseline | ||
| forge snapshot --diff | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| Apache-2.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| [profile.default] | ||
| src = "src" | ||
| out = "out" | ||
| libs = ["lib"] | ||
| test = "test" | ||
| script = "script" | ||
| solc = "0.8.28" | ||
| optimizer = true | ||
| optimizer_runs = 200 | ||
| via_ir = false | ||
| ffi = false | ||
| fs_permissions = [{ access = "read", path = "./" }] | ||
| gas_reports = ["x402ExactPermit2Proxy", "x402UptoPermit2Proxy"] | ||
|
|
||
| [profile.default.fuzz] | ||
| runs = 256 | ||
| max_test_rejects = 65536 | ||
|
|
||
| [profile.default.invariant] | ||
| runs = 256 | ||
| depth = 50 | ||
| fail_on_revert = false | ||
|
|
||
| [rpc_endpoints] | ||
| mainnet = "${MAINNET_RPC_URL}" | ||
| base = "${BASE_RPC_URL}" | ||
| optimism = "${OPTIMISM_RPC_URL}" | ||
| arbitrum = "${ARBITRUM_RPC_URL}" | ||
| polygon = "${POLYGON_RPC_URL}" | ||
| sepolia = "${SEPOLIA_RPC_URL}" | ||
| base_sepolia = "${BASE_SEPOLIA_RPC_URL}" | ||
| optimism_sepolia = "${OPTIMISM_SEPOLIA_RPC_URL}" | ||
| arbitrum_sepolia = "${ARBITRUM_SEPOLIA_RPC_URL}" | ||
|
|
||
| [etherscan] | ||
| mainnet = { key = "${ETHERSCAN_API_KEY}" } | ||
| base = { key = "${BASESCAN_API_KEY}" } | ||
| base_sepolia = { key = "${BASESCAN_API_KEY}" } | ||
| optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" } | ||
| arbitrum = { key = "${ARBISCAN_API_KEY}" } | ||
CarsonRoscoe marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| [fmt] | ||
| line_length = 120 | ||
| tab_width = 4 | ||
| bracket_spacing = false | ||
| int_types = "long" | ||
| multiline_func_header = "params_first" | ||
| quote_style = "double" | ||
| number_underscore = "thousands" | ||
| single_line_statement_blocks = "preserve" | ||
|
|
||
Submodule openzeppelin-contracts
added at
fcbae5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ | ||
| forge-std/=lib/forge-std/src/ | ||
| permit2/=lib/permit2/src/ | ||
|
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.