diff --git a/.changeset/README.md b/.changeset/README.md
new file mode 100644
index 00000000..e5b6d8d6
--- /dev/null
+++ b/.changeset/README.md
@@ -0,0 +1,8 @@
+# Changesets
+
+Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
+with multi-package repos, or single-package repos to help you version and publish your code. You can
+find the full documentation for it [in our repository](https://github.com/changesets/changesets)
+
+We have a quick list of common questions to get you started engaging with this project in
+[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
diff --git a/.changeset/config.json b/.changeset/config.json
new file mode 100644
index 00000000..d88011f6
--- /dev/null
+++ b/.changeset/config.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
+ "changelog": "@changesets/cli/changelog",
+ "commit": false,
+ "fixed": [],
+ "linked": [],
+ "access": "restricted",
+ "baseBranch": "main",
+ "updateInternalDependencies": "patch",
+ "ignore": []
+}
diff --git a/.env.example b/.env.example
index 7e85472c..1d880964 100644
--- a/.env.example
+++ b/.env.example
@@ -2,28 +2,41 @@
# Copy this file to .env and fill in with actual values
# ============================================
-# UPGRADE TESTS
+# PRIVATE KEY (Required for deployment)
# ============================================
-UPGRADE_TEST_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY # Base
-UPGRADE_TEST_OLD_PROXY=0x7C27e3AEcbF42879B64d76f604dC3430F4886462
-UPGRADE_TEST_OLD_VERSION=0.5.10
+# NEVER commit your actual private key!
+PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
# ============================================
-# DEPLOYMENT SCRIPTS
+# RPC ENDPOINTS (Required)
# ============================================
-PRIVATE_KEY=
-
# Format: RPC_{chainId}
# Mainnet chains
RPC_1=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY # Ethereum
+RPC_42161=https://arb-mainnet.g.alchemy.com/v2/YOUR_API_KEY # Arbitrum
+RPC_8453=https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY # Base
# Testnet chains
RPC_11155111=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY # Sepolia
-# Test mnemonic for funding script
-GAS_SIGNER_MNEMONIC="dash between kangaroo vacant gaze glass way sudden retire output retire evil"
-
+# Local development
+RPC_28405=https://porto-dev-paros.rpc.ithaca.xyz # Porto Devnet
+RPC_28406=https://porto-dev-tinos.rpc.ithaca.xyz # Porto Devnet
+RPC_28407=https://porto-dev-leros.rpc.ithaca.xyz # Porto Devnet
+# ============================================
+# VERIFICATION API KEYS (Optional)
+# ============================================
+# Format: VERIFICATION_KEY_{chainId}
+# Get API keys from respective block explorers
+VERIFICATION_KEY_1=YOUR_ETHERSCAN_API_KEY # https://etherscan.io
+VERIFICATION_KEY_42161=YOUR_ARBISCAN_API_KEY # https://arbiscan.io
+VERIFICATION_KEY_8453=YOUR_BASESCAN_API_KEY # https://basescan.org
+# ============================================
+# CONFIGURATION
+# ============================================
+# All deployment configuration is in deploy/deploy-config.json
+# See deploy/README.md for detailed instructions
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..861f7131
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. macOS]
+ - Browser: [e.g. Chrome, Safari]
+ - Version: [e.g. 120]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser: [e.g. Safari, Chrome]
+ - Version: [e.g. 17]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md
new file mode 100644
index 00000000..5ec6ce41
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/custom.md
@@ -0,0 +1,10 @@
+---
+name: Custom issue template
+about: Describe this issue template's purpose here.
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..af184fda
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. E.g., I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/auto-assign.yaml b/.github/workflows/auto-assign.yaml
deleted file mode 100644
index 0ecd2e5e..00000000
--- a/.github/workflows/auto-assign.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: Auto Assign PR to Author
-
-on:
- pull_request:
- types: [opened, reopened]
-
-jobs:
- auto-assign:
- permissions:
- issues: write
- pull-requests: write
- uses: ithacaxyz/ci/.github/workflows/auto-assign-pr.yml@main
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 6915c959..c5f35baa 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -5,13 +5,11 @@ on:
push:
branches: [main]
-permissions:
- contents: write
- pull-requests: write
-
jobs:
test:
name: Tests
+ permissions:
+ contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -21,31 +19,22 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
- version: nightly
+ version: nightly
- name: Build
run: |
forge build
- - name: Run tests
- env:
- UPGRADE_TEST_RPC_URL: ${{ secrets.UPGRADE_TEST_RPC_URL }}
- UPGRADE_TEST_OLD_PROXY: ${{ secrets.UPGRADE_TEST_OLD_PROXY }}
- UPGRADE_TEST_OLD_VERSION: ${{ secrets.UPGRADE_TEST_OLD_VERSION }}
+ - name: Check formatting
run: |
- forge test -vvv
+ forge fmt --check
- - name: Format contracts and generate snapshots
- if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
+ - name: Run tests
run: |
- forge fmt
- forge snapshot --isolate --match-contract Benchmark --via-ir
+ forge test --rerun -vvvvv
- - name: Commit formatting and snapshot changes
- if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
- uses: stefanzweifel/git-auto-commit-action@v5
- with:
- commit_message: "chore: fmt contracts and update gas snapshots"
- file_pattern: "*.sol snapshots/"
- commit_user_name: github-actions[bot]
- commit_user_email: github-actions[bot]@users.noreply.github.com
+ - name: Snapshot main branch
+ run: git fetch origin main && git worktree prune &&rm -rf .snapshot_worktree && git worktree add .snapshot_worktree origin/main && (cd .snapshot_worktree && forge snapshot --match-contract Benchmark --snap .temp-snapshot) && cp .snapshot_worktree/.temp-snapshot gas-snapshots/.gas-snapshot-main && git worktree remove --force .snapshot_worktree && git worktree prune
+
+ - name: Compare gas snapshots
+ run: forge snapshot --match-contract Benchmark --diff gas-snapshots/.gas-snapshot-main
diff --git a/.github/workflows/claude-code.yml b/.github/workflows/claude-code.yml
index ac2e8dbb..e069019a 100644
--- a/.github/workflows/claude-code.yml
+++ b/.github/workflows/claude-code.yml
@@ -17,6 +17,8 @@ jobs:
check-permissions:
name: Check permissions
runs-on: ubuntu-latest
+ permissions:
+ contents: read
outputs:
has-permission: ${{ steps.check.outputs.has-permission }}
steps:
diff --git a/.github/workflows/manual-deployment.yml b/.github/workflows/manual-deployment.yml
index 6ce9859b..7a3a3f27 100644
--- a/.github/workflows/manual-deployment.yml
+++ b/.github/workflows/manual-deployment.yml
@@ -1,5 +1,8 @@
name: Manual Deployment Execution
+permissions:
+ contents: read
+
on:
workflow_dispatch:
inputs:
diff --git a/.github/workflows/test-infra.yml b/.github/workflows/test-infra.yml
index 6ce9859b..8b69956d 100644
--- a/.github/workflows/test-infra.yml
+++ b/.github/workflows/test-infra.yml
@@ -1,4 +1,6 @@
name: Manual Deployment Execution
+permissions:
+ contents: read
on:
workflow_dispatch:
diff --git a/.github/workflows/version-check.yaml b/.github/workflows/version-check.yaml
index f5946cb5..059fa1e4 100644
--- a/.github/workflows/version-check.yaml
+++ b/.github/workflows/version-check.yaml
@@ -57,22 +57,31 @@ jobs:
- name: Bump version if needed
if: steps.check.outputs.needs_version_bump == 'true'
run: |
+ # Configure git
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+
# Get the contracts that need bumping
CONTRACTS_TO_BUMP="${{ steps.check.outputs.contracts_to_bump }}"
-
+
echo "Bumping versions for contracts: $CONTRACTS_TO_BUMP"
-
+
# Update Solidity files using the upgrade script with specific contracts
CONTRACTS_TO_BUMP="$CONTRACTS_TO_BUMP" node prep/update-version.js
-
- - name: Commit version bump changes
- if: steps.check.outputs.needs_version_bump == 'true'
- uses: stefanzweifel/git-auto-commit-action@v5
- with:
- commit_message: "chore: bump contract versions due to bytecode changes - Contracts updated: ${{ steps.check.outputs.contracts_to_bump }}"
- file_pattern: "src/*.sol"
- commit_user_name: github-actions[bot]
- commit_user_email: github-actions[bot]@users.noreply.github.com
+
+ # Commit changes (only Solidity files, not package.json)
+ git add src/*.sol
+ git commit -m "chore: bump contract versions due to bytecode changes - Contracts updated: $CONTRACTS_TO_BUMP"
+
+ # Pull latest changes and rebase
+ # Pull latest changes and rebase
+ if ! git pull origin ${{ github.event.pull_request.head.ref }} --rebase; then
+ echo "Failed to rebase version bump changes. Manual intervention required."
+ exit 1
+ fi
+
+ # Push to the PR branch
+ git push origin HEAD:${{ github.event.pull_request.head.ref }}
- name: Create PR comment
if: steps.check.outputs.needs_version_bump == 'true'
diff --git a/.gitmodules b/.gitmodules
index 28a7bc52..7aed494c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,6 +7,9 @@
[submodule "lib/LayerZero-v2"]
path = lib/LayerZero-v2
url = https://github.com/LayerZero-Labs/LayerZero-v2
+[submodule "lib/devtools"]
+ path = lib/devtools
+ url = https://github.com/LayerZero-Labs/devtools
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 00000000..428e29b8
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+clear && forge fmt && forge snapshot --isolate --match-contract Benchmark --via-ir && git add snapshots
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/account.iml b/.idea/account.iml
new file mode 100644
index 00000000..1ba0d660
--- /dev/null
+++ b/.idea/account.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
new file mode 100644
index 00000000..cc4b430a
--- /dev/null
+++ b/.idea/caches/deviceStreaming.xml
@@ -0,0 +1,1698 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..4bec4ea8
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..a55e7a17
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
new file mode 100644
index 00000000..1f2ea11e
--- /dev/null
+++ b/.idea/copilot.data.migration.ask2agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 00000000..c61ea334
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..4ae0bf91
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..1995e14d
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.mergify.yml b/.mergify.yml
new file mode 100644
index 00000000..1502935a
--- /dev/null
+++ b/.mergify.yml
@@ -0,0 +1,8 @@
+queue_rules:
+ - name: account
+merge_queue:
+ skip_intermediate_results: true
+ mode: parallel
+scopes:
+ source:
+ files: {}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fd21859..f8bdceaf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,24 +1,5 @@
# porto-account
-> Note: After v0.5.5, all changelogs will be published along with the release notes.
-> From here on, this file is deprecated.
-
-## 0.5.5
-
-### Patch Changes
-- Update foundry config, to not include metadata in the bytecode. This ensures that the contract bytecode doesn't change because of some other change in the repository.
-
-
-## 0.5.4
-
-### Patch Changes
-
-- SimpleFunder supports multiple orchestrators instead of single immutable orchestrator
- - Replaced immutable `ORCHESTRATOR` with `orchestrators` mapping and `setOrchestrators()` function
- - Maintained backward compatibility with old `fund()` signature
- - Added `supported_orchestrators` config field for deployment
- - Version bumped to "0.1.5"
-
## 0.5.0
### Minor Changes
diff --git a/README.md b/README.md
index aa22490b..ea941768 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
[](https://deepwiki.com/ithacaxyz/account)
+> 🚧 **Work In Progress**
+> This repository is under active development. Contracts are **unaudited**, and the codebase may have **breaking changes** without notice.
+> A bug bounty is live on Base Mainnet — [details here](docs/bug-bounty.md).
+
**All-in-one EIP-7702 powered account contract, coupled with [Porto](https://github.com/ithacaxyz/porto)**
Every app needs an account, traditionally requiring separate services for auth, payments, and recovery. Doing this in a way that empowers users with control over their funds and their data is the core challenge of the crypto space. While crypto wallets have made great strides, users still face a fragmented experience - juggling private keys, managing account balances across networks,
@@ -19,24 +23,17 @@ We believe that unstoppable crypto-powered accounts should be excellent througho
# Features out of the box
-- Secure Login: Using WebAuthN-compatible credentials like PassKeys.
-- Call Batching: Send multiple calls in 1.
-- Gas Sponsorship: Allow anyone to pay for your fees in any ERC20 or ETH.
-- Access Control: Whitelist receivers, function selectors and arguments.
-- Session Keys: Allow transactions without confirmations if they pass low-security access control policies.
-- Multi-sig Support: If a call is outside of a certain access control policy, require multiple signatures.
-- Interop: Transaction on any chain invisibly.
-
-## Benchmarks
-
-
-
-Gas benchmark implementations are in the [test repository](test/Benchmark.t.sol). We currently benchmark against leading ERC-4337 accounts. To generate the benchmarks, use `forge snapshot --isolate`.
-
-## Security
-Contracts were audited in a 2 week engagement by @MiloTruck @rholterhus @kadenzipfel
-
-We also maintain an active bug bounty program, you can find more details about it [here](https://porto.sh/contracts/security-and-bug-bounty)
+- [x] Secure Login: Using WebAuthN-compatible credentials like PassKeys.
+- [x] Call Batching: Send multiple calls in 1.
+- [x] Gas Sponsorship: Allow anyone to pay for your fees in any ERC20 or ETH.
+- [x] Access Control: Whitelist receivers, function selectors and arguments.
+- [x] Session Keys: Allow transactions without confirmations if they pass low-security access control policies.
+- [x] Multi-sig Support: If a call is outside of a certain access control policy, require multiple signatures.
+- [x] Interop: Transaction on any chain invisibly.
+- [ ] Timelocks: Add a time delay between transaction verification and execution, for additional safety.
+- [ ] Optimized for L2: Using BLS signatures.
+- [ ] Privacy: Using stealth addresses, confidential ERC20 tokens, and privacy pool integrations.
+- [ ] Account Recovery & Identity: Using ZK {Email, OAUth, Passport} and more.
## Getting Help
diff --git a/deploy/ConfigureLayerZeroSettler.s.sol b/deploy/ConfigureLayerZeroSettler.s.sol
index 63187a5f..4e505329 100644
--- a/deploy/ConfigureLayerZeroSettler.s.sol
+++ b/deploy/ConfigureLayerZeroSettler.s.sol
@@ -3,7 +3,6 @@ pragma solidity ^0.8.23;
import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/Script.sol";
-import {Config} from "forge-std/Config.sol";
import {ILayerZeroEndpointV2} from
"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import {SetConfigParam} from
@@ -37,7 +36,7 @@ import {LayerZeroSettler} from "../src/LayerZeroSettler.sol";
* --private-key $L0_SETTLER_OWNER_PK \
* "[84532,11155420]"
*/
-contract ConfigureLayerZeroSettler is Script, Config {
+contract ConfigureLayerZeroSettler is Script {
// Configuration type constants (matching ULN302)
uint32 constant CONFIG_TYPE_EXECUTOR = 1;
uint32 constant CONFIG_TYPE_ULN = 2;
@@ -47,7 +46,6 @@ contract ConfigureLayerZeroSettler is Script, Config {
string name;
address layerZeroSettlerAddress;
address layerZeroEndpoint;
- address l0SettlerSigner;
uint32 eid;
address sendUln302;
address receiveUln302;
@@ -61,7 +59,6 @@ contract ConfigureLayerZeroSettler is Script, Config {
// Fork ids for chain switching
mapping(uint256 => uint256) public forkIds;
- mapping(uint256 => bool) public isForkInitialized;
mapping(uint256 => LayerZeroChainConfig) public chainConfigs;
uint256[] public configuredChainIds;
@@ -69,12 +66,8 @@ contract ConfigureLayerZeroSettler is Script, Config {
* @notice Configure all chains with LayerZero configuration
*/
function run() external {
- // Load configuration and setup forks
- string memory configPath = string.concat(vm.projectRoot(), "/deploy/config.toml");
- _loadConfigAndForks(configPath, false);
-
- // Get all chain IDs from configuration
- uint256[] memory chainIds = config.getChainIds();
+ // Get all chain IDs from fork configuration
+ uint256[] memory chainIds = vm.readForkChainIds();
run(chainIds);
}
@@ -86,15 +79,12 @@ contract ConfigureLayerZeroSettler is Script, Config {
console.log("Loading configuration from deploy/config.toml");
console.log("Configuring", chainIds.length, "chains");
- // If config not already loaded, load it
- if (address(config) == address(0)) {
- string memory configPath = string.concat(vm.projectRoot(), "/deploy/config.toml");
- _loadConfigAndForks(configPath, false);
- }
-
// Load configurations for all chains
loadConfigurations(chainIds);
+ // Create forks for all chains that have LayerZero config
+ createForks();
+
// Configure each chain
for (uint256 i = 0; i < configuredChainIds.length; i++) {
configureChain(configuredChainIds[i]);
@@ -110,22 +100,27 @@ contract ConfigureLayerZeroSettler is Script, Config {
for (uint256 i = 0; i < chainIds.length; i++) {
uint256 chainId = chainIds[i];
- // Switch to the fork for this chain (already created by _loadConfigAndForks)
- vm.selectFork(forkOf[chainId]);
+ // Create fork to read configuration
+ string memory rpcUrl = vm.envString(string.concat("RPC_", vm.toString(chainId)));
+ uint256 forkId = vm.createFork(rpcUrl);
+ vm.selectFork(forkId);
// Try to load LayerZero configuration
- LayerZeroChainConfig memory chainConfig = loadChainConfig(chainId);
-
- // Only add chains that have LayerZero configuration
- if (chainConfig.layerZeroSettlerAddress != address(0)) {
- chainConfigs[chainId] = chainConfig;
+ LayerZeroChainConfig memory config = loadChainConfig(chainId);
+
+ // Only store if chain has LayerZero configuration
+ if (config.sendUln302 != address(0)) {
+ chainConfigs[chainId] = config;
configuredChainIds.push(chainId);
- forkIds[chainId] = forkOf[chainId];
- isForkInitialized[forkOf[chainId]] = true;
+ forkIds[chainId] = forkId;
console.log(
string.concat(
- " Loaded LayerZero config for ", chainConfig.name, " (", vm.toString(chainId), ")"
+ " Loaded LayerZero config for ",
+ config.name,
+ " (",
+ vm.toString(chainId),
+ ")"
)
);
}
@@ -135,68 +130,62 @@ contract ConfigureLayerZeroSettler is Script, Config {
}
/**
- * @notice Load configuration for a single chain using StdConfig
+ * @notice Load configuration for a single chain from fork variables
*/
function loadChainConfig(uint256 chainId)
internal
view
- returns (LayerZeroChainConfig memory chainConfig)
+ returns (LayerZeroChainConfig memory config)
{
- chainConfig.chainId = chainId;
+ config.chainId = chainId;
// Load basic chain info - required
- chainConfig.name = config.get(chainId, "name").toString();
+ config.name = vm.readForkString("name");
- // Try to load LayerZero settler deployed address first, then fall back to expected address
- address settlerAddr = config.get(chainId, "layerzero_settler_deployed").toAddress();
- if (settlerAddr == address(0)) {
- // Fall back to expected address from config
- settlerAddr = config.get(chainId, "layerzero_settler_address").toAddress();
- }
- if (settlerAddr == address(0)) {
+ // Try to load LayerZero settler address - if not present, this chain doesn't have LayerZero config
+ try vm.readForkAddress("layerzero_settler_address") returns (address addr) {
+ config.layerZeroSettlerAddress = addr;
+ } catch {
// No LayerZero settler configured for this chain - this is ok, return empty config
- return chainConfig;
+ return config;
}
- chainConfig.layerZeroSettlerAddress = settlerAddr;
// If we have a LayerZero settler, all other LayerZero fields are required
- chainConfig.layerZeroEndpoint = config.get(chainId, "layerzero_endpoint").toAddress();
- chainConfig.l0SettlerSigner = config.get(chainId, "l0_settler_signer").toAddress();
- chainConfig.eid = uint32(config.get(chainId, "layerzero_eid").toUint256());
- chainConfig.sendUln302 = config.get(chainId, "layerzero_send_uln302").toAddress();
- chainConfig.receiveUln302 = config.get(chainId, "layerzero_receive_uln302").toAddress();
+ config.layerZeroEndpoint = vm.readForkAddress("layerzero_endpoint");
+ config.eid = uint32(vm.readForkUint("layerzero_eid"));
+ config.sendUln302 = vm.readForkAddress("layerzero_send_uln302");
+ config.receiveUln302 = vm.readForkAddress("layerzero_receive_uln302");
// Load destination chain IDs - required for LayerZero configuration
- chainConfig.destinationChainIds = config.get(chainId, "layerzero_destination_chain_ids").toUint256Array();
+ config.destinationChainIds = vm.readForkUintArray("layerzero_destination_chain_ids");
// Load DVN configuration - required and optional DVN arrays
- string[] memory requiredDVNNames = config.get(chainId, "layerzero_required_dvns").toStringArray();
- string[] memory optionalDVNNames = config.get(chainId, "layerzero_optional_dvns").toStringArray();
+ string[] memory requiredDVNNames = vm.readForkStringArray("layerzero_required_dvns");
+ string[] memory optionalDVNNames = vm.readForkStringArray("layerzero_optional_dvns");
// Resolve DVN names to addresses
- chainConfig.requiredDVNs = resolveDVNAddresses(chainId, requiredDVNNames);
- chainConfig.optionalDVNs = resolveDVNAddresses(chainId, optionalDVNNames);
+ config.requiredDVNs = resolveDVNAddresses(requiredDVNNames);
+ config.optionalDVNs = resolveDVNAddresses(optionalDVNNames);
// Load optional DVN threshold - required field
- chainConfig.optionalDVNThreshold = uint8(config.get(chainId, "layerzero_optional_dvn_threshold").toUint256());
+ config.optionalDVNThreshold = uint8(vm.readForkUint("layerzero_optional_dvn_threshold"));
// Load confirmations - required field
- chainConfig.confirmations = uint64(config.get(chainId, "layerzero_confirmations").toUint256());
+ config.confirmations = uint64(vm.readForkUint("layerzero_confirmations"));
// Load max message size - required field
- chainConfig.maxMessageSize = uint32(config.get(chainId, "layerzero_max_message_size").toUint256());
+ config.maxMessageSize = uint32(vm.readForkUint("layerzero_max_message_size"));
- return chainConfig;
+ return config;
}
/**
- * @notice Resolve DVN names to addresses using StdConfig
- * @dev Takes DVN variable names and looks up their addresses using config.get
- * @param chainId The chain ID to resolve DVN addresses for
+ * @notice Resolve DVN names to addresses by reading from fork variables
+ * @dev Takes DVN variable names and looks up their addresses using vm.readForkAddress
* @param dvnNames Array of DVN variable names from config (e.g., "dvn_layerzero_labs")
* @return addresses Array of resolved DVN addresses
*/
- function resolveDVNAddresses(uint256 chainId, string[] memory dvnNames)
+ function resolveDVNAddresses(string[] memory dvnNames)
internal
view
returns (address[] memory)
@@ -204,7 +193,7 @@ contract ConfigureLayerZeroSettler is Script, Config {
address[] memory addresses = new address[](dvnNames.length);
for (uint256 i = 0; i < dvnNames.length; i++) {
- addresses[i] = config.get(chainId, dvnNames[i]).toAddress();
+ addresses[i] = vm.readForkAddress(dvnNames[i]);
require(
addresses[i] != address(0),
string.concat("DVN address not configured for: ", dvnNames[i])
@@ -214,8 +203,18 @@ contract ConfigureLayerZeroSettler is Script, Config {
return addresses;
}
- function _selectFork(uint256 chainId) internal {
- vm.selectFork(forkOf[chainId]);
+ /**
+ * @notice Create forks for all configured chains
+ */
+ function createForks() internal {
+ console.log("\n=== Creating forks for configured chains ===");
+
+ for (uint256 i = 0; i < configuredChainIds.length; i++) {
+ uint256 chainId = configuredChainIds[i];
+ LayerZeroChainConfig memory config = chainConfigs[chainId];
+
+ console.log(" Fork created for", config.name, "fork ID:", forkIds[chainId]);
+ }
}
/**
@@ -228,59 +227,31 @@ contract ConfigureLayerZeroSettler is Script, Config {
console.log(string.concat("Configuring ", config.name, " (", vm.toString(chainId), ")"));
console.log(" LayerZero Settler:", config.layerZeroSettlerAddress);
console.log(" Endpoint:", config.layerZeroEndpoint);
- console.log(" L0SettlerSigner:", config.l0SettlerSigner);
console.log(" EID:", config.eid);
// Switch to the source chain
- _selectFork(chainId);
+ vm.selectFork(forkIds[chainId]);
LayerZeroSettler settler = LayerZeroSettler(payable(config.layerZeroSettlerAddress));
-
- // Set or update the endpoint on the settler
- address currentEndpoint = address(settler.endpoint());
- if (currentEndpoint != config.layerZeroEndpoint) {
- if (currentEndpoint == address(0)) {
- console.log(" Setting endpoint to:", config.layerZeroEndpoint);
- } else {
- console.log(" Updating endpoint from:", currentEndpoint);
- console.log(" To:", config.layerZeroEndpoint);
- }
- vm.broadcast();
- settler.setEndpoint(config.layerZeroEndpoint);
- console.log(" Endpoint configured successfully");
- } else {
- console.log(" Endpoint already set to:", config.layerZeroEndpoint);
- }
-
- // Set or update the L0SettlerSigner on the settler
- address currentSigner = settler.l0SettlerSigner();
- if (currentSigner != config.l0SettlerSigner) {
- if (currentSigner == address(0)) {
- console.log(" Setting L0SettlerSigner to:", config.l0SettlerSigner);
- } else {
- console.log(" Updating L0SettlerSigner from:", currentSigner);
- console.log(" To:", config.l0SettlerSigner);
- }
- vm.broadcast();
- settler.setL0SettlerSigner(config.l0SettlerSigner);
- console.log(" L0SettlerSigner configured successfully");
- } else {
- console.log(" L0SettlerSigner already set to:", config.l0SettlerSigner);
- }
-
ILayerZeroEndpointV2 endpoint = ILayerZeroEndpointV2(config.layerZeroEndpoint);
// Configure pathways to all destinations
for (uint256 i = 0; i < config.destinationChainIds.length; i++) {
uint256 destChainId = config.destinationChainIds[i];
+ // Skip if destination not configured
+ if (forkIds[destChainId] == 0) {
+ console.log(" Skipping unconfigured destination:", destChainId);
+ continue;
+ }
+
LayerZeroChainConfig memory destConfig = chainConfigs[destChainId];
console.log(string.concat("\n Configuring pathway to ", destConfig.name));
console.log(" Destination EID:", destConfig.eid);
// Set executor config (self-execution model)
- setExecutorConfig(config, settler, endpoint, destConfig.eid);
+ setExecutorConfig(settler, endpoint, destConfig.eid);
// Set send ULN config
setSendUlnConfig(
@@ -295,31 +266,11 @@ contract ConfigureLayerZeroSettler is Script, Config {
);
// Switch to destination chain to set receive config
- _selectFork(destChainId);
-
- // Ensure destination settler has endpoint set before configuring
- LayerZeroSettler destSettler =
- LayerZeroSettler(payable(destConfig.layerZeroSettlerAddress));
- address currentDestEndpoint = address(destSettler.endpoint());
- if (currentDestEndpoint != destConfig.layerZeroEndpoint) {
- if (currentDestEndpoint == address(0)) {
- console.log(
- " Setting endpoint on destination:", destConfig.layerZeroEndpoint
- );
- } else {
- console.log(" Updating endpoint on destination from:", currentDestEndpoint);
- console.log(" To:", destConfig.layerZeroEndpoint);
- }
- vm.broadcast();
- destSettler.setEndpoint(destConfig.layerZeroEndpoint);
- console.log(" Destination endpoint configured successfully");
- } else {
- console.log(" Destination endpoint already set:", destConfig.layerZeroEndpoint);
- }
+ vm.selectFork(forkIds[destChainId]);
// Set receive ULN config on destination
setReceiveUlnConfig(
- destSettler,
+ LayerZeroSettler(payable(destConfig.layerZeroSettlerAddress)),
ILayerZeroEndpointV2(destConfig.layerZeroEndpoint),
config.eid, // Source EID
destConfig.receiveUln302,
@@ -330,7 +281,7 @@ contract ConfigureLayerZeroSettler is Script, Config {
);
// Switch back to source chain
- _selectFork(chainId);
+ vm.selectFork(forkIds[chainId]);
}
console.log("\n Configuration complete for", config.name);
@@ -341,24 +292,12 @@ contract ConfigureLayerZeroSettler is Script, Config {
// ============================================
function setExecutorConfig(
- LayerZeroChainConfig memory config,
LayerZeroSettler settler,
ILayerZeroEndpointV2 endpoint,
uint32 destEid
) internal {
- console.log(" Setting executor config:");
- console.log(" Executor (self-execution):", address(settler));
- console.log(" Max message size:", config.maxMessageSize);
- console.log(" Send ULN302:", config.sendUln302);
-
- bytes memory executorConfig = abi.encode(config.maxMessageSize, settler);
- SetConfigParam[] memory params = new SetConfigParam[](1);
- params[0] =
- SetConfigParam({eid: destEid, configType: CONFIG_TYPE_EXECUTOR, config: executorConfig});
-
- vm.broadcast();
- endpoint.setConfig(address(settler), config.sendUln302, params);
- console.log(" Executor config set");
+ // LayerZeroSettler uses self-execution model, no executor config needed
+ console.log(" Using self-execution model (no executor config)");
}
function setSendUlnConfig(
@@ -398,7 +337,7 @@ contract ConfigureLayerZeroSettler is Script, Config {
});
// Get the L0 settler owner who should be the delegate
- address l0SettlerOwner = config.get(vm.getChainId(), "l0_settler_owner").toAddress();
+ address l0SettlerOwner = vm.readForkAddress("l0_settler_owner");
console.log(" L0 Settler Owner (delegate):", l0SettlerOwner);
vm.broadcast();
diff --git a/deploy/DeployMain.s.sol b/deploy/DeployMain.s.sol
index ee513799..5ebbb109 100644
--- a/deploy/DeployMain.s.sol
+++ b/deploy/DeployMain.s.sol
@@ -3,8 +3,7 @@ pragma solidity ^0.8.23;
import {Script, console} from "forge-std/Script.sol";
import {VmSafe} from "forge-std/Vm.sol";
-import {Config} from "forge-std/Config.sol";
-import {Variable, TypeKind} from "forge-std/LibVariable.sol";
+import {stdToml} from "forge-std/StdToml.sol";
import {SafeSingletonDeployer} from "./SafeSingletonDeployer.sol";
// Import contracts to deploy
@@ -16,7 +15,6 @@ import {SimpleFunder} from "../src/SimpleFunder.sol";
import {Escrow} from "../src/Escrow.sol";
import {SimpleSettler} from "../src/SimpleSettler.sol";
import {LayerZeroSettler} from "../src/LayerZeroSettler.sol";
-import {ExperimentERC20} from "./mock/ExperimentalERC20.sol";
/**
* @title DeployMain
@@ -51,26 +49,23 @@ import {ExperimentERC20} from "./mock/ExperimentalERC20.sol";
* --private-key $PRIVATE_KEY \
* "[1]" "/deploy/custom-config.toml"
*/
-contract DeployMain is Script, Config, SafeSingletonDeployer {
+contract DeployMain is Script, SafeSingletonDeployer {
+ using stdToml for string;
// Chain configuration struct
struct ChainConfig {
uint256 chainId;
string name;
bool isTestnet;
+ address pauseAuthority;
address funderOwner;
address funderSigner;
address settlerOwner;
address l0SettlerOwner;
- address l0SettlerSigner;
address layerZeroEndpoint;
- address[] oldOrchestrators;
uint32 layerZeroEid;
bytes32 salt;
string[] contracts; // Array of contract names to deploy
- // EXP Token configuration (testnet only)
- address expMinterAddress;
- uint256 expMintAmount;
}
struct DeployedContracts {
@@ -82,8 +77,6 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
address layerZeroSettler;
address simpleFunder;
address simulator;
- address expToken; // EXP token (testnet only)
- address exp2Token; // EXP2 token (testnet only)
}
// State
@@ -91,7 +84,9 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
mapping(uint256 => DeployedContracts) internal deployedContracts;
uint256[] internal targetChainIds;
- // Config path
+ // Paths and config
+ string internal registryPath;
+ string internal configContent; // For unified config
string internal configPath = "/deploy/config.toml";
// Events for tracking
@@ -109,17 +104,9 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
* @notice Deploy to all chains in config
*/
function run() external {
- // Load configuration and setup forks (enable write-back to save deployed addresses)
- string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
- _loadConfigAndForks(fullConfigPath, true);
-
- // Get all available chain IDs from configuration
- targetChainIds = config.getChainIds();
- require(targetChainIds.length > 0, "No chains found in configuration");
-
- // Load configuration for each chain
- loadConfigurations();
- loadDeployedContracts();
+ // Get all available chain IDs from fork configuration
+ uint256[] memory chainIds = vm.readForkChainIds();
+ initializeDeployment(chainIds);
executeDeployment();
}
@@ -128,20 +115,11 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
* @param chainIds Array of chain IDs to deploy to (empty array = all chains)
*/
function run(uint256[] memory chainIds) external {
- // Load configuration and setup forks (enable write-back to save deployed addresses)
- string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
- _loadConfigAndForks(fullConfigPath, true);
-
// If empty array, get all available chains
if (chainIds.length == 0) {
- chainIds = config.getChainIds();
+ chainIds = vm.readForkChainIds();
}
- targetChainIds = chainIds;
- require(targetChainIds.length > 0, "No chains found in configuration");
-
- // Load configuration for each chain
- loadConfigurations();
- loadDeployedContracts();
+ initializeDeployment(chainIds);
executeDeployment();
}
@@ -151,25 +129,47 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
* @param _configPath Path to custom TOML config file
*/
function run(uint256[] memory chainIds, string memory _configPath) external {
- configPath = _configPath;
-
- // Load configuration and setup forks (enable write-back to save deployed addresses)
- string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
- _loadConfigAndForks(fullConfigPath, true);
-
// If empty array, get all available chains
if (chainIds.length == 0) {
- chainIds = config.getChainIds();
+ chainIds = vm.readForkChainIds();
}
+ initializeDeployment(chainIds, _configPath);
+ executeDeployment();
+ }
+
+ /**
+ * @notice Initialize deployment with target chains using TOML config
+ * @param chainIds Array of chain IDs to deploy to
+ */
+ function initializeDeployment(uint256[] memory chainIds) internal {
+ require(chainIds.length > 0, "No chains found in configuration");
+
+ // Load unified configuration
+ string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
+ configContent = vm.readFile(fullConfigPath);
+
+ // Load registry path from config.toml
+ registryPath = configContent.readString(".profile.deployment.registry_path");
+
+ // Store target chain IDs
targetChainIds = chainIds;
- require(targetChainIds.length > 0, "No chains found in configuration");
-
+
// Load configuration for each chain
loadConfigurations();
+
+ // Load existing deployed contracts from registry
loadDeployedContracts();
- executeDeployment();
}
+ /**
+ * @notice Initialize deployment with custom config path
+ * @param chainIds Array of chain IDs to deploy to
+ * @param _configPath Path to the config file
+ */
+ function initializeDeployment(uint256[] memory chainIds, string memory _configPath) internal {
+ configPath = _configPath;
+ initializeDeployment(chainIds);
+ }
/**
* @notice Load configurations for all target chains
@@ -178,15 +178,20 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
for (uint256 i = 0; i < targetChainIds.length; i++) {
uint256 chainId = targetChainIds[i];
- // Switch to the fork for this chain (already created by _loadConfigAndForks)
- vm.selectFork(forkOf[chainId]);
+ // Use the RPC_{chainId} environment variable directly
+ // This matches the naming convention in config.toml
+ string memory rpcUrl = vm.envString(string.concat("RPC_", vm.toString(chainId)));
+
+ // Create fork using the RPC URL
+ uint256 forkId = vm.createFork(rpcUrl);
+ vm.selectFork(forkId);
// Verify we're on the correct chain
require(block.chainid == chainId, "Chain ID mismatch");
- // Load configuration using new StdConfig pattern
- ChainConfig memory chainConfig = loadChainConfigFromStdConfig(chainId);
- chainConfigs[chainId] = chainConfig;
+ // Load configuration from fork variables
+ ChainConfig memory config = loadChainConfigFromFork(chainId);
+ chainConfigs[chainId] = config;
}
// Log the loaded configuration for verification
@@ -194,66 +199,48 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
}
/**
- * @notice Load chain configuration using StdConfig
+ * @notice Load chain configuration from the currently active fork
* @param chainId The chain ID we're loading config for
*/
- function loadChainConfigFromStdConfig(uint256 chainId) internal view returns (ChainConfig memory) {
- ChainConfig memory chainConfig;
+ function loadChainConfigFromFork(uint256 chainId) internal view returns (ChainConfig memory) {
+ ChainConfig memory config;
- chainConfig.chainId = chainId;
+ config.chainId = chainId;
- // Use StdConfig to read variables
- chainConfig.name = config.get(chainId, "name").toString();
- chainConfig.isTestnet = config.get(chainId, "is_testnet").toBool();
+ // Use vm.readFork* functions to read variables from the active fork
+ config.name = vm.readForkString("name");
+ config.isTestnet = vm.readForkBool("is_testnet");
// Load addresses
- chainConfig.funderOwner = config.get(chainId, "funder_owner").toAddress();
- chainConfig.funderSigner = config.get(chainId, "funder_signer").toAddress();
- chainConfig.settlerOwner = config.get(chainId, "settler_owner").toAddress();
- chainConfig.l0SettlerOwner = config.get(chainId, "l0_settler_owner").toAddress();
- chainConfig.l0SettlerSigner = config.get(chainId, "l0_settler_signer").toAddress();
- chainConfig.layerZeroEndpoint = config.get(chainId, "layerzero_endpoint").toAddress();
+ config.pauseAuthority = vm.readForkAddress("pause_authority");
+ config.funderOwner = vm.readForkAddress("funder_owner");
+ config.funderSigner = vm.readForkAddress("funder_signer");
+ config.settlerOwner = vm.readForkAddress("settler_owner");
+ config.l0SettlerOwner = vm.readForkAddress("l0_settler_owner");
+ config.layerZeroEndpoint = vm.readForkAddress("layerzero_endpoint");
// Load other configuration
- chainConfig.layerZeroEid = uint32(config.get(chainId, "layerzero_eid").toUint256());
- chainConfig.salt = config.get(chainId, "salt").toBytes32();
-
- // Load EXP token configuration (testnet only)
- if (chainConfig.isTestnet) {
- chainConfig.expMinterAddress = config.get(chainId, "exp_minter_address").toAddress();
- chainConfig.expMintAmount = config.get(chainId, "exp_mint_amount").toUint256();
- }
+ config.layerZeroEid = uint32(vm.readForkUint("layerzero_eid"));
+ config.salt = vm.readForkBytes32("salt");
// Load contracts list - required field, will revert if not present
- string[] memory contractsList = config.get(chainId, "contracts").toStringArray();
+ string[] memory contractsList = vm.readForkStringArray("contracts");
// Check if user specified "ALL" to deploy all contracts
if (
contractsList.length == 1
&& keccak256(bytes(contractsList[0])) == keccak256(bytes("ALL"))
) {
- string[] memory baseContracts = getAllContracts();
- // For testnets with ALL specified, append ExpToken
- if (chainConfig.isTestnet) {
- string[] memory testnetContracts = new string[](baseContracts.length + 1);
- for (uint256 i = 0; i < baseContracts.length; i++) {
- testnetContracts[i] = baseContracts[i];
- }
- testnetContracts[baseContracts.length] = "ExpToken";
- chainConfig.contracts = testnetContracts;
- } else {
- // For non-testnets, use base contracts (no ExpToken)
- chainConfig.contracts = baseContracts;
- }
+ config.contracts = getAllContracts();
} else {
- chainConfig.contracts = contractsList;
+ config.contracts = contractsList;
}
- return chainConfig;
+ return config;
}
/**
- * @notice Get all available contracts (excluding ExpToken)
+ * @notice Get all available contracts
*/
function getAllContracts() internal pure returns (string[] memory) {
string[] memory contracts = new string[](8);
@@ -286,40 +273,31 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
}
/**
- * @notice Load deployed contracts from config
+ * @notice Load deployed contracts from registry
*/
function loadDeployedContracts() internal {
for (uint256 i = 0; i < targetChainIds.length; i++) {
uint256 chainId = targetChainIds[i];
-
- DeployedContracts memory deployed;
-
- // Read deployed contract addresses from config, defaulting to address(0) if not set
- deployed.orchestrator = tryGetAddress(chainId, "orchestrator_deployed");
- deployed.ithacaAccount = tryGetAddress(chainId, "ithaca_account_deployed");
- deployed.accountProxy = tryGetAddress(chainId, "account_proxy_deployed");
- deployed.escrow = tryGetAddress(chainId, "escrow_deployed");
- deployed.simpleSettler = tryGetAddress(chainId, "simple_settler_deployed");
- deployed.layerZeroSettler = tryGetAddress(chainId, "layerzero_settler_deployed");
- deployed.simpleFunder = tryGetAddress(chainId, "simple_funder_deployed");
- deployed.simulator = tryGetAddress(chainId, "simulator_deployed");
- deployed.expToken = tryGetAddress(chainId, "exp_token_deployed");
- deployed.exp2Token = tryGetAddress(chainId, "exp2_token_deployed");
-
- deployedContracts[chainId] = deployed;
- }
- }
-
- /**
- * @notice Try to get an address from config, return address(0) if not found
- */
- function tryGetAddress(uint256 chainId, string memory key) internal view returns (address) {
- Variable memory variable = config.get(chainId, key);
- // Check if variable exists (TypeKind.None means missing key)
- if (variable.ty.kind == TypeKind.None) {
- return address(0);
+ bytes32 salt = chainConfigs[chainId].salt;
+ string memory registryFile = getRegistryFilename(chainId, salt);
+
+ try vm.readFile(registryFile) returns (string memory registryJson) {
+ // Use individual parsing for flexibility with missing fields
+ DeployedContracts memory deployed;
+ deployed.ithacaAccount = tryReadAddress(registryJson, ".IthacaAccount");
+ deployed.accountProxy = tryReadAddress(registryJson, ".AccountProxy");
+ deployed.escrow = tryReadAddress(registryJson, ".Escrow");
+ deployed.orchestrator = tryReadAddress(registryJson, ".Orchestrator");
+ deployed.simpleSettler = tryReadAddress(registryJson, ".SimpleSettler");
+ deployed.layerZeroSettler = tryReadAddress(registryJson, ".LayerZeroSettler");
+ deployed.simpleFunder = tryReadAddress(registryJson, ".SimpleFunder");
+ deployed.simulator = tryReadAddress(registryJson, ".Simulator");
+
+ deployedContracts[chainId] = deployed;
+ } catch {
+ // No registry file exists yet
+ }
}
- return variable.toAddress();
}
/**
@@ -337,8 +315,8 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
console.log("Funder Owner:", config.funderOwner);
console.log("Funder Signer:", config.funderSigner);
console.log("L0 Settler Owner:", config.l0SettlerOwner);
- console.log("L0 Settler Signer:", config.l0SettlerSigner);
console.log("Settler Owner:", config.settlerOwner);
+ console.log("Pause Authority:", config.pauseAuthority);
console.log("LayerZero Endpoint:", config.layerZeroEndpoint);
console.log("LayerZero EID:", config.layerZeroEid);
console.log("Salt:");
@@ -445,7 +423,7 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
}
/**
- * @notice Save deployed contract address to config
+ * @notice Save deployed contract address to registry
*/
function saveDeployedContract(
uint256 chainId,
@@ -469,38 +447,124 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
deployedContracts[chainId].simpleSettler = contractAddress;
} else if (keccak256(bytes(contractName)) == keccak256("LayerZeroSettler")) {
deployedContracts[chainId].layerZeroSettler = contractAddress;
- } else if (keccak256(bytes(contractName)) == keccak256("ExpToken")) {
- deployedContracts[chainId].expToken = contractAddress;
- } else if (keccak256(bytes(contractName)) == keccak256("Exp2Token")) {
- deployedContracts[chainId].exp2Token = contractAddress;
- }
-
- // Only write to config file during actual broadcasts, not simulations
- if (vm.isContext(VmSafe.ForgeContext.ScriptBroadcast) || vm.isContext(VmSafe.ForgeContext.ScriptResume)) {
- if (keccak256(bytes(contractName)) == keccak256("Orchestrator")) {
- config.set(chainId, "orchestrator_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("IthacaAccount")) {
- config.set(chainId, "ithaca_account_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("AccountProxy")) {
- config.set(chainId, "account_proxy_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("Simulator")) {
- config.set(chainId, "simulator_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("SimpleFunder")) {
- config.set(chainId, "simple_funder_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("Escrow")) {
- config.set(chainId, "escrow_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("SimpleSettler")) {
- config.set(chainId, "simple_settler_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("LayerZeroSettler")) {
- config.set(chainId, "layerzero_settler_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("ExpToken")) {
- config.set(chainId, "exp_token_deployed", contractAddress);
- } else if (keccak256(bytes(contractName)) == keccak256("Exp2Token")) {
- config.set(chainId, "exp2_token_deployed", contractAddress);
- }
}
+
+ // Write to registry file
+ writeToRegistry(chainId, contractName, contractAddress);
+ }
+
+ /**
+ * @notice Write to registry file
+ */
+ function writeToRegistry(uint256 chainId, string memory contractName, address contractAddress)
+ internal
+ {
+ // Only save registry during actual broadcasts, not dry runs
+ if (
+ !vm.isContext(VmSafe.ForgeContext.ScriptBroadcast)
+ && !vm.isContext(VmSafe.ForgeContext.ScriptResume)
+ ) {
+ return;
+ }
+
+ DeployedContracts memory deployed = deployedContracts[chainId];
+
+ string memory json = "{";
+ bool first = true;
+
+ if (deployed.orchestrator != address(0)) {
+ json = string.concat(json, '"Orchestrator": "', vm.toString(deployed.orchestrator), '"');
+ first = false;
+ }
+
+ if (deployed.ithacaAccount != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json =
+ string.concat(json, '"IthacaAccount": "', vm.toString(deployed.ithacaAccount), '"');
+ first = false;
+ }
+
+ if (deployed.accountProxy != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json = string.concat(json, '"AccountProxy": "', vm.toString(deployed.accountProxy), '"');
+ first = false;
+ }
+
+ if (deployed.simulator != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json = string.concat(json, '"Simulator": "', vm.toString(deployed.simulator), '"');
+ first = false;
+ }
+
+ if (deployed.simpleFunder != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json = string.concat(json, '"SimpleFunder": "', vm.toString(deployed.simpleFunder), '"');
+ first = false;
+ }
+
+ if (deployed.escrow != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json = string.concat(json, '"Escrow": "', vm.toString(deployed.escrow), '"');
+ first = false;
+ }
+
+ if (deployed.simpleSettler != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json =
+ string.concat(json, '"SimpleSettler": "', vm.toString(deployed.simpleSettler), '"');
+ first = false;
+ }
+
+ if (deployed.layerZeroSettler != address(0)) {
+ if (!first) json = string.concat(json, ",");
+ json = string.concat(
+ json, '"LayerZeroSettler": "', vm.toString(deployed.layerZeroSettler), '"'
+ );
+ }
+
+ json = string.concat(json, "}");
+
+ bytes32 salt = chainConfigs[chainId].salt;
+ string memory registryFile = getRegistryFilename(chainId, salt);
+ vm.writeFile(registryFile, json);
+ }
+
+ /**
+ * @notice Get registry filename based on chainId and salt
+ */
+ function getRegistryFilename(uint256 chainId, bytes32 salt)
+ internal
+ view
+ returns (string memory)
+ {
+ string memory filename = string.concat(
+ vm.projectRoot(),
+ "/",
+ registryPath,
+ "deployment_",
+ vm.toString(chainId),
+ "_",
+ vm.toString(salt),
+ ".json"
+ );
+ return filename;
}
+ /**
+ * @notice Try to read an address from JSON
+ */
+ function tryReadAddress(string memory json, string memory key)
+ internal
+ pure
+ returns (address)
+ {
+ try vm.parseJson(json, key) returns (bytes memory data) {
+ if (data.length > 0) {
+ return abi.decode(data, (address));
+ }
+ } catch {}
+ return address(0);
+ }
/**
* @notice Verify Safe Singleton Factory is deployed
@@ -599,8 +663,6 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
deploySimpleSettler(chainId, config, deployed);
} else if (nameHash == keccak256("LayerZeroSettler")) {
deployLayerZeroSettler(chainId, config, deployed);
- } else if (nameHash == keccak256("ExpToken")) {
- deployExpToken(chainId, config, deployed);
} else {
console.log("Warning: Unknown contract name:", contractName);
}
@@ -611,12 +673,17 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
ChainConfig memory config,
DeployedContracts memory deployed
) internal {
- bytes memory creationCode = type(Orchestrator).creationCode;
- address orchestrator =
- deployContractWithCreate2(chainId, creationCode, "", "Orchestrator");
-
- saveDeployedContract(chainId, "Orchestrator", orchestrator);
- deployed.orchestrator = orchestrator;
+ if (deployed.orchestrator == address(0)) {
+ bytes memory creationCode = type(Orchestrator).creationCode;
+ bytes memory args = abi.encode(config.pauseAuthority);
+ address orchestrator =
+ deployContractWithCreate2(chainId, creationCode, args, "Orchestrator");
+
+ saveDeployedContract(chainId, "Orchestrator", orchestrator);
+ deployed.orchestrator = orchestrator;
+ } else {
+ console.log("Orchestrator already deployed:", deployed.orchestrator);
+ }
}
function deployIthacaAccount(
@@ -625,15 +692,22 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
DeployedContracts memory deployed
) internal {
// Ensure Orchestrator is deployed first (dependency)
- require(deployed.orchestrator != address(0), "Orchestrator must be deployed before IthacaAccount");
+ if (deployed.orchestrator == address(0)) {
+ console.log("Deploying Orchestrator first (dependency for IthacaAccount)...");
+ deployOrchestrator(chainId, config, deployed);
+ }
- bytes memory creationCode = type(IthacaAccount).creationCode;
- bytes memory args = abi.encode(deployed.orchestrator);
- address ithacaAccount =
- deployContractWithCreate2(chainId, creationCode, args, "IthacaAccount");
+ if (deployed.ithacaAccount == address(0)) {
+ bytes memory creationCode = type(IthacaAccount).creationCode;
+ bytes memory args = abi.encode(deployed.orchestrator);
+ address ithacaAccount =
+ deployContractWithCreate2(chainId, creationCode, args, "IthacaAccount");
- saveDeployedContract(chainId, "IthacaAccount", ithacaAccount);
- deployed.ithacaAccount = ithacaAccount;
+ saveDeployedContract(chainId, "IthacaAccount", ithacaAccount);
+ deployed.ithacaAccount = ithacaAccount;
+ } else {
+ console.log("IthacaAccount already deployed:", deployed.ithacaAccount);
+ }
}
function deployAccountProxy(
@@ -642,14 +716,21 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
DeployedContracts memory deployed
) internal {
// Ensure IthacaAccount is deployed first (dependency)
- require(deployed.ithacaAccount != address(0), "IthacaAccount must be deployed before AccountProxy");
+ if (deployed.ithacaAccount == address(0)) {
+ console.log("Deploying IthacaAccount first (dependency for AccountProxy)...");
+ deployIthacaAccount(chainId, config, deployed);
+ }
- bytes memory proxyCode = LibEIP7702.proxyInitCode(deployed.ithacaAccount, address(0));
- address accountProxy = deployContractWithCreate2(chainId, proxyCode, "", "AccountProxy");
+ if (deployed.accountProxy == address(0)) {
+ bytes memory proxyCode = LibEIP7702.proxyInitCode(deployed.ithacaAccount, address(0));
+ address accountProxy = deployContractWithCreate2(chainId, proxyCode, "", "AccountProxy");
- require(accountProxy != address(0), "Account proxy deployment failed");
- saveDeployedContract(chainId, "AccountProxy", accountProxy);
- deployed.accountProxy = accountProxy;
+ require(accountProxy != address(0), "Account proxy deployment failed");
+ saveDeployedContract(chainId, "AccountProxy", accountProxy);
+ deployed.accountProxy = accountProxy;
+ } else {
+ console.log("AccountProxy already deployed:", deployed.accountProxy);
+ }
}
function deploySimulator(
@@ -657,11 +738,15 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
ChainConfig memory config,
DeployedContracts memory deployed
) internal {
- bytes memory creationCode = type(Simulator).creationCode;
- address simulator = deployContractWithCreate2(chainId, creationCode, "", "Simulator");
+ if (deployed.simulator == address(0)) {
+ bytes memory creationCode = type(Simulator).creationCode;
+ address simulator = deployContractWithCreate2(chainId, creationCode, "", "Simulator");
- saveDeployedContract(chainId, "Simulator", simulator);
- deployed.simulator = simulator;
+ saveDeployedContract(chainId, "Simulator", simulator);
+ deployed.simulator = simulator;
+ } else {
+ console.log("Simulator already deployed:", deployed.simulator);
+ }
}
function deploySimpleFunder(
@@ -669,13 +754,23 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
ChainConfig memory config,
DeployedContracts memory deployed
) internal {
- bytes memory creationCode = type(SimpleFunder).creationCode;
+ // Ensure Orchestrator is deployed first (dependency)
+ if (deployed.orchestrator == address(0)) {
+ console.log("Deploying Orchestrator first (dependency for SimpleFunder)...");
+ deployOrchestrator(chainId, config, deployed);
+ }
- bytes memory args = abi.encode(config.funderSigner, config.funderOwner);
- address funder = deployContractWithCreate2(chainId, creationCode, args, "SimpleFunder");
+ if (deployed.simpleFunder == address(0)) {
+ bytes memory creationCode = type(SimpleFunder).creationCode;
+ bytes memory args =
+ abi.encode(config.funderSigner, deployed.orchestrator, config.funderOwner);
+ address funder = deployContractWithCreate2(chainId, creationCode, args, "SimpleFunder");
- saveDeployedContract(chainId, "SimpleFunder", funder);
- deployed.simpleFunder = funder;
+ saveDeployedContract(chainId, "SimpleFunder", funder);
+ deployed.simpleFunder = funder;
+ } else {
+ console.log("SimpleFunder already deployed:", deployed.simpleFunder);
+ }
}
function deployEscrow(
@@ -683,11 +778,15 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
ChainConfig memory config,
DeployedContracts memory deployed
) internal {
- bytes memory creationCode = type(Escrow).creationCode;
- address escrow = deployContractWithCreate2(chainId, creationCode, "", "Escrow");
+ if (deployed.escrow == address(0)) {
+ bytes memory creationCode = type(Escrow).creationCode;
+ address escrow = deployContractWithCreate2(chainId, creationCode, "", "Escrow");
- saveDeployedContract(chainId, "Escrow", escrow);
- deployed.escrow = escrow;
+ saveDeployedContract(chainId, "Escrow", escrow);
+ deployed.escrow = escrow;
+ } else {
+ console.log("Escrow already deployed:", deployed.escrow);
+ }
}
function deploySimpleSettler(
@@ -695,14 +794,18 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
ChainConfig memory config,
DeployedContracts memory deployed
) internal {
- bytes memory creationCode = type(SimpleSettler).creationCode;
- bytes memory args = abi.encode(config.settlerOwner);
- address settler =
- deployContractWithCreate2(chainId, creationCode, args, "SimpleSettler");
-
- console.log(" Owner:", config.settlerOwner);
- saveDeployedContract(chainId, "SimpleSettler", settler);
- deployed.simpleSettler = settler;
+ if (deployed.simpleSettler == address(0)) {
+ bytes memory creationCode = type(SimpleSettler).creationCode;
+ bytes memory args = abi.encode(config.settlerOwner);
+ address settler =
+ deployContractWithCreate2(chainId, creationCode, args, "SimpleSettler");
+
+ console.log(" Owner:", config.settlerOwner);
+ saveDeployedContract(chainId, "SimpleSettler", settler);
+ deployed.simpleSettler = settler;
+ } else {
+ console.log("SimpleSettler already deployed:", deployed.simpleSettler);
+ }
}
function deployLayerZeroSettler(
@@ -710,62 +813,19 @@ contract DeployMain is Script, Config, SafeSingletonDeployer {
ChainConfig memory config,
DeployedContracts memory deployed
) internal {
- bytes memory creationCode = type(LayerZeroSettler).creationCode;
- bytes memory args = abi.encode(config.l0SettlerOwner, config.l0SettlerSigner);
- address settler =
- deployContractWithCreate2(chainId, creationCode, args, "LayerZeroSettler");
-
- console.log(" Owner:", config.l0SettlerOwner);
- console.log(" L0SettlerSigner:", config.l0SettlerSigner);
- console.log(" Endpoint to be configured:", config.layerZeroEndpoint);
- console.log(" EID:", config.layerZeroEid);
- console.log(
- " Note: Endpoint must be set by owner via ConfigureLayerZeroSettler script"
- );
-
- saveDeployedContract(chainId, "LayerZeroSettler", settler);
- deployed.layerZeroSettler = settler;
- }
-
- function deployExpToken(
- uint256 chainId,
- ChainConfig memory config,
- DeployedContracts memory deployed
- ) internal {
- // Only deploy on testnets
- if (!config.isTestnet) {
- console.log("Skipping ExpToken deployment - not a testnet");
- return;
+ if (deployed.layerZeroSettler == address(0)) {
+ bytes memory creationCode = type(LayerZeroSettler).creationCode;
+ bytes memory args = abi.encode(config.layerZeroEndpoint, config.l0SettlerOwner);
+ address settler =
+ deployContractWithCreate2(chainId, creationCode, args, "LayerZeroSettler");
+
+ console.log(" Endpoint:", config.layerZeroEndpoint);
+ console.log(" Owner:", config.l0SettlerOwner);
+ console.log(" EID:", config.layerZeroEid);
+ saveDeployedContract(chainId, "LayerZeroSettler", settler);
+ deployed.layerZeroSettler = settler;
+ } else {
+ console.log("LayerZeroSettler already deployed:", deployed.layerZeroSettler);
}
-
- bytes memory creationCode = type(ExperimentERC20).creationCode;
-
- // Deploy EXP token
- // Hardcode name and symbol to "EXP"
- bytes memory args = abi.encode("EXP", "EXP", 1 ether);
- address expToken = deployContractWithCreate2(chainId, creationCode, args, "ExpToken");
-
- // Mint initial tokens to the configured minter address
- ExperimentERC20(payable(expToken)).mint(config.expMinterAddress, config.expMintAmount);
-
- console.log(" EXP Name/Symbol: EXP");
- console.log(" EXP Address:", expToken);
- saveDeployedContract(chainId, "ExpToken", expToken);
- deployed.expToken = expToken;
-
- // Deploy EXP2 token
- // Hardcode name and symbol to "EXP2"
- bytes memory args2 = abi.encode("EXP2", "EXP2", 1 ether);
- address exp2Token = deployContractWithCreate2(chainId, creationCode, args2, "Exp2Token");
-
- // Mint initial tokens to the configured minter address (same as EXP)
- ExperimentERC20(payable(exp2Token)).mint(config.expMinterAddress, config.expMintAmount);
-
- console.log(" EXP2 Name/Symbol: EXP2");
- console.log(" EXP2 Address:", exp2Token);
- console.log(" Minter:", config.expMinterAddress);
- console.log(" Mint Amount (each):", config.expMintAmount);
- saveDeployedContract(chainId, "Exp2Token", exp2Token);
- deployed.exp2Token = exp2Token;
}
}
diff --git a/deploy/FundSigners.s.sol b/deploy/FundSigners.s.sol
index 42618c6e..02820e27 100644
--- a/deploy/FundSigners.s.sol
+++ b/deploy/FundSigners.s.sol
@@ -2,14 +2,12 @@
pragma solidity ^0.8.23;
import {Script, console} from "forge-std/Script.sol";
-import {Config} from "forge-std/Config.sol";
+import {stdToml} from "forge-std/StdToml.sol";
// SimpleFunder interface for setting gas wallets
interface ISimpleFunder {
function setGasWallet(address[] memory wallets, bool isGasWallet) external;
function gasWallets(address) external view returns (bool);
- function setOrchestrators(address[] memory ocs, bool val) external;
- function orchestrators(address) external view returns (bool);
}
/**
@@ -48,7 +46,9 @@ interface ISimpleFunder {
* --private-key $PRIVATE_KEY \
* "[84532]" 5
*/
-contract FundSigners is Script, Config {
+contract FundSigners is Script {
+ using stdToml for string;
+
/**
* @notice Configuration for funding on a specific chain
*/
@@ -59,7 +59,6 @@ contract FundSigners is Script, Config {
uint256 targetBalance;
address simpleFunderAddress;
uint256 defaultNumSigners;
- address[] supportedOrchestrators;
}
/**
@@ -88,18 +87,18 @@ contract FundSigners is Script, Config {
uint256 private totalEthDistributed;
uint256 private chainsProcessed;
+ string internal configContent;
string internal configPath = "/deploy/config.toml";
/**
* @notice Fund all configured chains with default number of signers
*/
function run() external {
- // Load configuration and setup forks
- string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
- _loadConfigAndForks(fullConfigPath, false);
-
- // Get all chain IDs from configuration
- uint256[] memory chainIds = config.getChainIds();
+ // Default to common testnets
+ uint256[] memory chainIds = new uint256[](3);
+ chainIds[0] = 11155111; // Sepolia
+ chainIds[1] = 84532; // Base Sepolia
+ chainIds[2] = 11155420; // Optimism Sepolia
uint256 numSigners = 10; // Default
execute(chainIds, numSigners);
}
@@ -109,10 +108,6 @@ contract FundSigners is Script, Config {
* @param chainIds Array of chain IDs to fund
*/
function run(uint256[] memory chainIds) external {
- // Load configuration and setup forks
- string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
- _loadConfigAndForks(fullConfigPath, false);
-
uint256 numSigners = 10; // Default
execute(chainIds, numSigners);
}
@@ -123,10 +118,6 @@ contract FundSigners is Script, Config {
* @param numSigners Number of signers to fund (starting from index 0)
*/
function run(uint256[] memory chainIds, uint256 numSigners) external {
- // Load configuration and setup forks
- string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
- _loadConfigAndForks(fullConfigPath, false);
-
execute(chainIds, numSigners);
}
@@ -137,6 +128,9 @@ contract FundSigners is Script, Config {
console.log("=== Signer Funding Script (TOML Config) ===");
console.log("Number of signers to fund:", numSigners);
+ // Load configuration
+ loadConfig();
+
// Get mnemonic from environment
string memory mnemonic = vm.envString("GAS_SIGNER_MNEMONIC");
require(bytes(mnemonic).length > 0, "GAS_SIGNER_MNEMONIC not set");
@@ -217,9 +211,6 @@ contract FundSigners is Script, Config {
if (config.simpleFunderAddress != address(0) && config.simpleFunderAddress.code.length > 0)
{
setGasWalletsInSimpleFunder(config.simpleFunderAddress, signers);
- setOrchestratorsInSimpleFunder(
- config.simpleFunderAddress, config.supportedOrchestrators
- );
}
vm.stopBroadcast();
@@ -421,41 +412,6 @@ contract FundSigners is Script, Config {
);
}
- /**
- * @notice Set orchestrators in SimpleFunder contract
- */
- function setOrchestratorsInSimpleFunder(address simpleFunder, address[] memory orchestrators)
- internal
- {
- if (orchestrators.length == 0) {
- console.log("No orchestrators configured, skipping orchestrator setup");
- return;
- }
-
- ISimpleFunder funder = ISimpleFunder(simpleFunder);
-
- console.log("\nSetting orchestrators in SimpleFunder:");
- console.log(" SimpleFunder address:", simpleFunder);
- console.log(
- string.concat(" Setting ", vm.toString(orchestrators.length), " orchestrators...")
- );
-
- for (uint256 i = 0; i < orchestrators.length; i++) {
- console.log(
- string.concat(
- " Orchestrator ", vm.toString(i), ": ", vm.toString(orchestrators[i])
- )
- );
- }
-
- funder.setOrchestrators(orchestrators, true);
- console.log(
- string.concat(
- " Successfully set all ", vm.toString(orchestrators.length), " orchestrators"
- )
- );
- }
-
/**
* @notice Derive signer addresses from mnemonic
*/
@@ -475,29 +431,45 @@ contract FundSigners is Script, Config {
return signers;
}
+ /**
+ * @notice Load configuration from TOML
+ */
+ function loadConfig() internal {
+ string memory fullConfigPath = string.concat(vm.projectRoot(), configPath);
+ configContent = vm.readFile(fullConfigPath);
+ }
+
/**
* @notice Get chain funding configuration
*/
function getChainFundingConfig(uint256 chainId) internal returns (ChainFundingConfig memory) {
- // Switch to the fork for this chain (already created by _loadConfigAndForks)
- vm.selectFork(forkOf[chainId]);
-
- ChainFundingConfig memory chainConfig;
- chainConfig.chainId = chainId;
- chainConfig.name = config.get(chainId, "name").toString();
- chainConfig.isTestnet = config.get(chainId, "is_testnet").toBool();
- chainConfig.targetBalance = config.get(chainId, "target_balance").toUint256();
- chainConfig.simpleFunderAddress = config.get(chainId, "simple_funder_deployed").toAddress();
+ // Create fork and read configuration from fork variables
+ string memory rpcUrl = vm.envString(string.concat("RPC_", vm.toString(chainId)));
+ uint256 forkId = vm.createFork(rpcUrl);
+ vm.selectFork(forkId);
+
+ ChainFundingConfig memory config;
+ config.chainId = chainId;
+ config.name = vm.readForkString("name");
+ config.isTestnet = vm.readForkBool("is_testnet");
+ config.targetBalance = vm.readForkUint("target_balance");
+
+ // Try to read SimpleFunder address - may not be deployed on all chains
+ try vm.readForkAddress("simple_funder_address") returns (address addr) {
+ config.simpleFunderAddress = addr;
+ } catch {
+ // SimpleFunder not configured for this chain
+ config.simpleFunderAddress = address(0);
+ }
// Try to read default number of signers
- uint256 numSigners = config.get(chainId, "default_num_signers").toUint256();
- chainConfig.defaultNumSigners = numSigners == 0 ? 10 : numSigners; // Default fallback
-
- // Read supported orchestrators - required field
- chainConfig.supportedOrchestrators =
- config.get(chainId, "supported_orchestrators").toAddressArray();
+ try vm.readForkUint("default_num_signers") returns (uint256 num) {
+ config.defaultNumSigners = num;
+ } catch {
+ config.defaultNumSigners = 10; // Default fallback
+ }
- return chainConfig;
+ return config;
}
/**
diff --git a/deploy/README.md b/deploy/README.md
index b23c7606..cb27d1ef 100644
--- a/deploy/README.md
+++ b/deploy/README.md
@@ -1,6 +1,6 @@
# Deployment System
-Unified deployment and configuration system for the Ithaca Account Abstraction System.
+Unified deployment and configuration system for the Ithaca Account Abstraction System.
We use a single TOML config for fast and easy scripting.
## Available Scripts
@@ -55,90 +55,69 @@ optimism-sepolia = { key = "${ETHERSCAN_API_KEY}" }
## Configuration Structure
-All configuration is in `deploy/config.toml` using the StdConfig format:
+All configuration is in `deploy/config.toml`:
```toml
-[base-sepolia]
-endpoint_url = "${RPC_84532}"
+[profile.deployment]
+registry_path = "deploy/registry/"
-[base-sepolia.bool]
-is_testnet = true
+[forks.base-sepolia]
+rpc_url = "${RPC_84532}"
-[base-sepolia.address]
+[forks.base-sepolia.vars]
# Chain identification
-funder_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-funder_signer = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-settler_owner = "0x0000000000000000000000000000000000000004"
-l0_settler_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-l0_settler_signer = "0x0000000000000000000000000000000000000006"
-layerzero_endpoint = "0x6EDCE65403992e310A62460808c4b910D972f10f"
-simple_funder_address = "0x09F6eF9525efAdb6167dFe71fFcfbE306De07988"
-layerzero_settler_address = "0xd71d3c3ff2249A67cEa12030b20E66734fB1f833"
-layerzero_send_uln302 = "0xC1868e054425D378095A003EcbA3823a5D0135C9"
-layerzero_receive_uln302 = "0x12523de19dc41c91F7d2093E0CFbB76b17012C8d"
-dvn_layerzero_labs = "0xe1a12515F9AB2764b887bF60B923Ca494EBbB2d6"
-dvn_google_cloud = "0xFc9d8E5d3FaB22fB6E93E9E2C90916E9dCa83Ade"
-exp_minter_address = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-supported_orchestrators = ["0xEd7c1e839381c489Dcd1ED3CE1B0e79DaE714f77"]
-
-# Deployed contract addresses - automatically written during deployment
-orchestrator_deployed = "0xC662Af195CD57bC330552f3E2Be5E03Ef69cB041"
-ithaca_account_deployed = "0x49627C39cC7f39f95540C2100f18608f2365a59f"
-account_proxy_deployed = "0xD2a48e4635fCB2437d2e482122137F06C8433706"
-simulator_deployed = "0x332A5Cd675D9d26c4af3BF618A7175d0D623CABA"
-simple_funder_deployed = "0x1ADE5D4CE3183D913791DEcaeaD42Fff193AeF8F"
-escrow_deployed = "0xD1c7e21f2a50A2cDDCFaf554b998a800C3C35aD1"
-simple_settler_deployed = "0x3291f7Ce832997920874d70d68A8186B388024F5"
-layerzero_settler_deployed = "0xBDb45dA9e075a9fCbdf8fAa9d0c93A21b3D8671a"
-exp_token_deployed = "0xaeB83430528fB0DeE5E15bF07A5056B6c0b37809"
-exp2_token_deployed = "0x246c631Dac318a13023e98aB925499930c9801fB"
-
-[base-sepolia.uint]
chain_id = 84532
+name = "Base Sepolia"
+is_testnet = true
+
+# Contract ownership
+pause_authority = "0x..." # Can pause contracts
+funder_owner = "0x..." # Owns SimpleFunder
+funder_signer = "0x..." # Signs funding operations
+settler_owner = "0x..." # Owns SimpleSettler
+l0_settler_owner = "0x..." # Owns LayerZeroSettler
+
+# Deployment configuration
+salt = "0x0000..." # CREATE2 salt (SAVE THIS!)
+contracts = ["ALL"] # Or specific: ["Orchestrator", "IthacaAccount"]
+
+# Funding configuration (only needed for Funding Script)
+target_balance = 1000000000000000 # Target balance per signer (0.001 ETH)
+simple_funder_address = "0x..." # SimpleFunder address
+default_num_signers = 10 # Number of signers to fund
+
+# LayerZero configuration (only needed for ConfigureLayerZero)
+layerzero_settler_address = "0x..."
+layerzero_endpoint = "0x..."
layerzero_eid = 40245
-target_balance = "1000000000000000" # Target balance per signer (0.001 ETH) - must be quoted for large numbers
-default_num_signers = 10 # Number of signers to fund
-layerzero_confirmations = 1
-layerzero_max_message_size = 10000
-layerzero_optional_dvn_threshold = 0
-exp_mint_amount = "5000000000000000000000" # Amount to mint (in wei) - must be quoted
+layerzero_send_uln302 = "0x..."
+layerzero_receive_uln302 = "0x..."
layerzero_destination_chain_ids = [11155420]
-
-[base-sepolia.bytes32]
-salt = "0x0000000000000000000000000000000000000000000000000000000000005678" # CREATE2 salt (SAVE THIS!)
-
-[base-sepolia.string]
-name = "Base Sepolia"
-contracts = ["ALL"] # Or specific: ["Orchestrator", "IthacaAccount"]
layerzero_required_dvns = ["dvn_layerzero_labs"]
layerzero_optional_dvns = []
-```
-
-### Deployed Address Management
-
-**Automatic Address Writing**: The deployment scripts automatically write deployed contract addresses to the config file during broadcast operations:
+layerzero_optional_dvn_threshold = 0
+layerzero_confirmations = 1
+layerzero_max_message_size = 10000
-- ✅ **During `--broadcast`**: Deployed addresses are written to config.toml as `contract_name_deployed = "0x..."`
-- ❌ **During simulation** (no `--broadcast`): No config writes occur - safe for testing
-- 📍 **Address detection**: Scripts always check actual on-chain state, not config file data
-- 🔄 **State synchronization**: Config file stays in sync with actual deployments
+dvn_layerzero_labs = "0x..."
+dvn_google_cloud = "0x..."
+```
### Available Contracts
- **Orchestrator**
-- **IthacaAccount**
-- **AccountProxy**
-- **Simulator**
-- **SimpleFunder**
+- **IthacaAccount**
+- **AccountProxy**
+- **Simulator**
+- **SimpleFunder**
- **Escrow** (Only needed for Interop Chains)
- **SimpleSettler** (Only needed for Interop testing)
- **LayerZeroSettler** (Only needed for Interop Chains)
-- **ExpToken** - Test ERC20 tokens (Testnet only, automatically included with "ALL")
-- **ALL** - Deploys all contracts (+ ExpToken on testnets)
+- **ALL** - Deploys all contracts
-**Dependencies**:
-IthacaAccount requires Orchestrator;
-AccountProxy requires IthacaAccount;
+**Dependencies**:
+IthacaAccount requires Orchestrator;
+AccountProxy requires IthacaAccount;
SimpleFunder requires Orchestrator.
## Quick Start - Complete Workflow
@@ -170,7 +149,8 @@ forge script deploy/FundSigners.s.sol:FundSigners \
--private-key $PRIVATE_KEY \
"[84532,11155420]"
-# 5. Fund SimpleFunder contract
+# 5. Fund SimpleFunder contract
+SIMPLE_FUNDER=$(cat deploy/registry/deployment_84532_*.json | jq -r .SimpleFunder)
forge script deploy/FundSimpleFunder.s.sol:FundSimpleFunder \
--broadcast --multi --slow \
@@ -194,7 +174,7 @@ forge script deploy/FundSimpleFunder.s.sol:FundSimpleFunder \
forge script deploy/DeployMain.s.sol:DeployMain \
--broadcast --verify --multi --slow \
--sig "run()" \
- --private-key $PRIVATE_KEY
+ --private-key $PRIVATE_KEY
# Deploy to specific chains
forge script deploy/DeployMain.s.sol:DeployMain \
@@ -227,8 +207,7 @@ forge script deploy/DeployMain.s.sol:DeployMain \
**Purpose**: Configure LayerZero messaging pathways between chains.
-**Prerequisites**:
-
+**Prerequisites**:
- LayerZeroSettler deployed on source and destination chains
- Caller must be l0_settler_owner
@@ -251,18 +230,15 @@ forge script deploy/ConfigureLayerZeroSettler.s.sol:ConfigureLayerZeroSettler \
**Purpose**: Fund signers and register them as gas wallets in SimpleFunder.
-**Prerequisites**:
-
+**Prerequisites**:
- SimpleFunder deployed
- Caller must be funder_owner
- GAS_SIGNER_MNEMONIC environment variable set
**What it does**:
-
1. Derives signer addresses from mnemonic
2. Tops up signers below target_balance
3. Registers signers as gas wallets in SimpleFunder
-4. Sets configured orchestrators in SimpleFunder
```bash
# Fund default number of signers (from config)
@@ -305,7 +281,6 @@ forge script deploy/FundSimpleFunder.s.sol:FundSimpleFunder \
```
**Parameters**:
-
- SimpleFunder address (same across chains if using CREATE2)
- Array of (chainId, tokenAddress, amount)
- Use `0x0000000000000000000000000000000000000000` for native ETH
@@ -322,36 +297,37 @@ forge script deploy/FundSimpleFunder.s.sol:FundSimpleFunder \
All contracts deploy via Safe Singleton Factory (`0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7`) for deterministic addresses.
**Key Points**:
-
- Same salt + same bytecode = same address on every chain
- Addresses can be predicted before deployment
- **⚠️ SAVE YOUR SALT VALUES** - Required for deploying to same addresses on new chains
-- **Deployment decisions based on on-chain state** - Scripts check actual deployed contracts, not config file data
-## Adding New Chains
+## Registry Files
-1. Add configuration to `deploy/config.toml`:
+Deployment addresses are saved in `deploy/registry/deployment_{chainId}_{salt}.json`:
-```toml
-[new-chain]
-endpoint_url = "${RPC_CHAINID}"
+```json
+{
+ "Orchestrator": "0xb33adF2c2257a94314d408255aC843fd53B1a7e1",
+ "IthacaAccount": "0x5a87ef243CDA70a855828d4989Fad61B56A467d3",
+ "AccountProxy": "0x4ACD713815fbb363a89D9Ff046C56cEdC7EF3ad7",
+ "SimpleFunder": "0xA47C5C472449979a2F37dF2971627cD6587bADb8"
+}
+```
-[new-chain.bool]
-is_testnet = true
+Registry files are for reference only - deployment decisions are based on on-chain state.
-[new-chain.address]
-funder_owner = "0x..."
-# ... all required fields
+## Adding New Chains
-[new-chain.uint]
-chain_id = CHAINID
-# ... all required fields
+1. Add configuration to `deploy/config.toml`:
-[new-chain.bytes32]
-salt = "0x0000000000000000000000000000000000000000000000000000000000005678"
+```toml
+[forks.new-chain]
+rpc_url = "${RPC_CHAINID}"
-[new-chain.string]
+[forks.new-chain.vars]
+chain_id = CHAINID
name = "Chain Name"
+# ... all required fields
contracts = ["ALL"]
```
@@ -376,22 +352,18 @@ forge script deploy/DeployMain.s.sol:DeployMain \
### Common Issues
**"No chains found in configuration"**
-
- Verify config.toml has properly configured chains
- Check RPC URLs are set for target chains
**"Safe Singleton Factory not deployed"**
-
- Factory must exist at `0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7`
- Most major chains have this deployed
**Contract already deployed**
-
- Normal for CREATE2 - existing contracts are skipped
- Change salt value to deploy to new addresses
**RPC errors**
-
- Verify RPC URLs are correct and accessible
- Check rate limits on public RPCs
- Consider paid RPC services for production
@@ -404,145 +376,38 @@ forge script deploy/DeployMain.s.sol:DeployMain \
4. **Commit registry files** - Provides deployment history
5. **Use `--multi --slow`** - Ensures proper multi-chain ordering
6. **Verify while deploying** - Use `--verify` flag
-7. **Large numbers must be quoted in TOML** - Use `"1000000000000000000"` not `1000000000000000000`
## Configuration Field Reference
-| Field | Used By | Purpose |
-| --------------------------------------- | ------------------------------ | ------------------------------------------------ |
-| `chain_id`, `name`, `is_testnet` | All scripts | Chain identification |
-| `pause_authority` | DeployMain | Contract pause permissions |
-| `funder_owner`, `funder_signer` | DeployMain, FundSigners | SimpleFunder control |
-| `settler_owner` | DeployMain | SimpleSettler ownership |
-| `l0_settler_owner` | DeployMain, ConfigureLayerZero | LayerZeroSettler ownership |
-| `salt` | DeployMain | CREATE2 deployment salt |
-| `contracts` | DeployMain | Which contracts to deploy |
-| `target_balance` | FundSigners | Minimum signer balance |
-| `simple_funder_address` | FundSigners, FundSimpleFunder | SimpleFunder location |
-| `default_num_signers` | FundSigners | Number of signers |
-| `supported_orchestrators` | FundSigners | Orchestrator addresses to enable in SimpleFunder |
-| `layerzero_*` fields | ConfigureLayerZeroSettler | LayerZero configuration |
-| `exp_minter_address`, `exp_mint_amount` | DeployMain (testnet) | ExpToken deployment |
-| `*_deployed` fields | All scripts | Auto-written deployed contract addresses |
-
-## ExpToken Deployment (Testnets Only)
-
-### Automatic ExpToken Deployment
+| Field | Used By | Purpose |
+|-------|---------|---------|
+| `chain_id`, `name`, `is_testnet` | All scripts | Chain identification |
+| `pause_authority` | DeployMain | Contract pause permissions |
+| `funder_owner`, `funder_signer` | DeployMain, FundSigners | SimpleFunder control |
+| `settler_owner` | DeployMain | SimpleSettler ownership |
+| `l0_settler_owner` | DeployMain, ConfigureLayerZero | LayerZeroSettler ownership |
+| `salt` | DeployMain | CREATE2 deployment salt |
+| `contracts` | DeployMain | Which contracts to deploy |
+| `target_balance` | FundSigners | Minimum signer balance |
+| `simple_funder_address` | FundSigners, FundSimpleFunder | SimpleFunder location |
+| `default_num_signers` | FundSigners | Number of signers |
+| `layerzero_*` fields | ConfigureLayerZeroSettler | LayerZero configuration |
-**Purpose**: Deploy EXP and EXP2 test tokens automatically on testnet chains.
+## Test Token Deployment
-**Behavior**:
+### DeployEXP - Test ERC20 Token
-- **Testnets** (`is_testnet = true`): ExpToken automatically included when using `["ALL"]` contracts
-- **Production** (`is_testnet = false`): ExpToken never deployed, regardless of configuration
-- **Two tokens deployed**: "EXP" and "EXP2" with hardcoded names
-- **Same configuration**: Both tokens use the same minter address and mint amount
-
-### Configuration Requirements
-
-For testnet chains, **both fields are required** (deployment will fail if missing):
-
-```toml
-[testnet-name.bool]
-is_testnet = true
-
-[testnet-name.address]
-exp_minter_address = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4" # REQUIRED
-
-[testnet-name.uint]
-exp_mint_amount = "5000000000000000000000" # REQUIRED (5000 tokens in wei)
-
-[testnet-name.string]
-contracts = ["ALL"] # ExpToken automatically included for testnets
-```
-
-### Deployment Details
-
-**Two tokens are deployed**:
-
-1. **EXP Token**: Name and symbol "EXP"
-2. **EXP2 Token**: Name and symbol "EXP2"
-
-**Both tokens**:
-
-- Use CREATE2 for deterministic addresses
-- Mint `exp_mint_amount` tokens to `exp_minter_address`
-- Are deployed at the end of the contract deployment sequence
-- Are saved to registry files as "ExpToken" and "Exp2Token"
-
-### Examples
-
-**Base Sepolia (Testnet)**:
-
-```toml
-[base-sepolia.bool]
-is_testnet = true
-
-[base-sepolia.address]
-exp_minter_address = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-
-[base-sepolia.uint]
-exp_mint_amount = "5000000000000000000000"
-
-[base-sepolia.string]
-contracts = ["ALL"] # Deploys 8 core contracts + ExpToken
-```
-
-## Supporting Bash Scripts
-
-### Overview
-
-There are two main scripts that handle multi-chain deployments and configuration verification:
-
-- **`deploy/execute_config.sh`** - Brings up the whole environment, by calling all scripts correctly.
-- **`deploy/verify_config.sh`** - Verifies that the values in the config.toml are all set and configured correctly.
-
-### Usage
-
-#### Execute Deployment
-
-```bash
-# Deploy to specific chains
-./deploy/execute_config.sh 84532 11155420
-
-# Deploy to all chains in config.toml
-./deploy/execute_config.sh
-```
-
-The script performs these steps:
-1. Validates configuration for selected chains
-2. Deploys core contracts (IthacaAccount, SimpleFunder, SimpleSettler, etc.)
-3. Configures LayerZero cross-chain messaging
-4. Funds signer accounts with gas tokens
-5. Verifies all deployments match configuration
-
-#### Verify Configuration
+**Purpose**: Deploy a simple test ERC20 token for testing.
+**Usage**:
```bash
-# Verify specific chains
-./deploy/verify_config.sh 84532 11155420
-
-# Verify all chains in config.toml
-./deploy/verify_config.sh
+# Deploy test token to a specific chain
+forge script deploy/DeployEXP.s.sol:DeployEXP \
+ --broadcast \
+ --sig "run()" \
+ --private-key $PRIVATE_KEY \
+ --rpc-url $RPC_
```
-The script checks:
-- Required environment variables (RPC URLs, private keys)
-- Contract deployment addresses match config.toml
-- Signer accounts have sufficient gas balances
-- LayerZero endpoints and DVN configurations
-- Cross-chain pathway configurations
-
-### Configuration
-
-Both scripts read from `deploy/config.toml` which defines per-chain:
-- RPC endpoints and chain metadata
-- Contract addresses and owners
-- LayerZero endpoint configurations
-- Cross-chain destination mappings
-- Gas funding amounts
-
-Environment variables required:
-- `PRIVATE_KEY` - Deployer private key
-- `GAS_SIGNER_MNEMONIC` - Mnemonic for signer accounts
-- `RPC_` - RPC URL for each chain (e.g., `RPC_84532`)
\ No newline at end of file
+The test token includes basic ERC20 functionality and can be minted by anyone.
+It should only be used for testing purposes.
\ No newline at end of file
diff --git a/deploy/config.toml b/deploy/config.toml
index 32d4fd67..0644abd6 100644
--- a/deploy/config.toml
+++ b/deploy/config.toml
@@ -1,155 +1,136 @@
-[11155111]
-endpoint_url = "${RPC_11155111}"
+# Ithaca Account Deployment Configuration
+# Single source of truth for all deployment and fork configurations
+# This file extends foundry.toml and contains both Foundry fork configs and deployment-specific settings
-[11155111.bool]
-is_testnet = true
+# ============================================
+# GLOBAL DEPLOYMENT CONFIGURATION
+# ============================================
+
+[profile.deployment]
+registry_path = "deploy/registry/"
+
+# ============================================
+# FORK CONFIGURATIONS (for vm.readFork* cheatcodes)
+# ============================================
-[11155111.address]
+# Sepolia
+[forks.sepolia]
+rpc_url = "${RPC_11155111}"
+
+[forks.sepolia.vars]
+chain_id = 11155111
+name = "Sepolia"
+is_testnet = true
+pause_authority = "0x0000000000000000000000000000000000000001"
funder_owner = "0x0000000000000000000000000000000000000003"
funder_signer = "0x0000000000000000000000000000000000000002"
settler_owner = "0x0000000000000000000000000000000000000004"
l0_settler_owner = "0x0000000000000000000000000000000000000005"
-l0_settler_signer = "0x0000000000000000000000000000000000000006"
layerzero_endpoint = "0x6EDCE65403992e310A62460808c4b910D972f10f"
+layerzero_eid = 40161
+salt = "0x0000000000000000000000000000000000000000000000000000000000000000"
+target_balance = 50000000000000000 # 0.05 ether
+# Specify contracts to deploy. Use ["ALL"] to deploy all available contracts
+contracts = ["LayerZeroSettler"]
+# SimpleFunder configuration
+simple_funder_address = "0x2AD8F6a3bB1126a777606eaFa9da9b95530d9597" # TODO: automate write when foundry upgrades
+default_num_signers = 10
+# LayerZero configuration (copied from Base Sepolia for testing)
+# TODO: Set correct addresses here.
layerzero_settler_address = "0x4225041FF3DB1C7d7a1029406bB80C7298767aca"
layerzero_send_uln302 = "0xC1868e054425D378095A003EcbA3823a5D0135C9"
layerzero_receive_uln302 = "0x12523de19dc41c91F7d2093E0CFbB76b17012C8d"
-dvn_layerzero_labs = "0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193"
-dvn_google_cloud = "0x8e5a7b5959C5A7C732b87dCE401E07F5819eEC2d"
-exp_minter_address = "0x0000000000000000000000000000000000000003"
-supported_orchestrators = ["0xEd7c1e839381c489Dcd1ED3CE1B0e79DaE714f77"]
-orchestrator_deployed = "0xCa58a1296c5AE231A2963A26E11f722bfe65acd4"
-ithaca_account_deployed = "0xC203A827A6DB2d09fbF052f88e7aD794EEbf8928"
-account_proxy_deployed = "0x0B5d691F3A98B705756ab196CFe4af97f35e06E4"
-simulator_deployed = "0x3306F2DD87887885f4de81aF6BD88b648E467f4e"
-simple_funder_deployed = "0xa0BF9a30fD55A45BF09309513734B8194fBE3934"
-escrow_deployed = "0x669a3aB5816d1ADdbFdecE2F6B238D323F056248"
-simple_settler_deployed = "0xFB0F29c658145DbDb1Bf974C8A342F4E78BF9801"
-layerzero_settler_deployed = "0xC93D0142D960F7B33440F09B13917C195e09ffA0"
-exp_token_deployed = "0x4D335fa317FB8d5493b83e06161134ec611Bc188"
-exp2_token_deployed = "0x0348152303686f2197b24981571FCf8c36c66Cc5"
-
-[11155111.uint]
-chain_id = 11155111
-layerzero_eid = 40161
-target_balance = "50000000000000000"
-default_num_signers = 10
+layerzero_destination_chain_ids = [84532, 11155420]
+layerzero_required_dvns = ["dvn_layerzero_labs"] # optimism sepolia
+layerzero_optional_dvns = []
+layerzero_optional_dvn_threshold = 0
layerzero_confirmations = 1
layerzero_max_message_size = 10000
-layerzero_optional_dvn_threshold = 0
-exp_mint_amount = "1000000000000000000000"
-layerzero_destination_chain_ids = [
- 84532,
- 11155420,
-]
-
-[11155111.bytes32]
-salt = "0xdf08a9fa957a9ac004f84f3a8ab7318a7be55d59948f46497bd3717865295b61"
-
-[11155111.string]
-name = "Sepolia"
-contracts = ["ALL"]
-layerzero_required_dvns = ["dvn_layerzero_labs"]
-layerzero_optional_dvns = []
+# DVN addresses
+dvn_layerzero_labs = "0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193"
+dvn_google_cloud = "0x8e5a7b5959C5A7C732b87dCE401E07F5819eEC2d"
-[84532]
-endpoint_url = "${RPC_84532}"
+# Base Sepolia
+[forks.base-sepolia]
+rpc_url = "${RPC_84532}"
-[84532.bool]
+[forks.base-sepolia.vars]
+orchestrator_address = "0x0000000000000000000000000000000000000000"
+# Deployment Script
+chain_id = 84532
+name = "Base Sepolia"
is_testnet = true
-
-[84532.address]
-funder_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-funder_signer = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-settler_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-l0_settler_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-l0_settler_signer = "0x0000000000000000000000000000000000000006"
-layerzero_endpoint = "0x6EDCE65403992e310A62460808c4b910D972f10f"
-layerzero_settler_address = "0xd71d3c3ff2249A67cEa12030b20E66734fB1f833"
+pause_authority = "0x0000000000000000000000000000000000000001" # Set to separate PAUSE KEY
+funder_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4" # Set to Master Key
+funder_signer = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4" # Set to Master Key
+settler_owner = "0x0000000000000000000000000000000000000004" # Set to Master Key
+l0_settler_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4" # Set to Master Key
+layerzero_endpoint = "0x6EDCE65403992e310A62460808c4b910D972f10f" # Get from LayerZero docs
+layerzero_eid = 40245 # Get from LayerZero docs
+salt = "0x00000000000000000000000000000000000000000000000000000000000000c7" # Almost alwayse be bytes32(0)
+# Specify contracts to deploy. Use ["ALL"] to deploy all available contracts
+contracts = ["ALL"]
+# Funding Script
+target_balance = 1000000000000000 # 0.001 ether
+
+# SimpleFunder configuration
+simple_funder_address = "0x5e6c8c24A53275e2C6C3DC5f1c5C027Ec13439Ba"
+default_num_signers = 10
+# LayerZero configuration
+layerzero_settler_address = "0x4225041FF3DB1C7d7a1029406bB80C7298767aca"
layerzero_send_uln302 = "0xC1868e054425D378095A003EcbA3823a5D0135C9"
layerzero_receive_uln302 = "0x12523de19dc41c91F7d2093E0CFbB76b17012C8d"
-dvn_layerzero_labs = "0xe1a12515F9AB2764b887bF60B923Ca494EBbB2d6"
-dvn_google_cloud = "0xFc9d8E5d3FaB22fB6E93E9E2C90916E9dCa83Ade"
-exp_minter_address = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-supported_orchestrators = ["0xEd7c1e839381c489Dcd1ED3CE1B0e79DaE714f77"]
-orchestrator_deployed = "0xcA91Dba3dab46478FC466ef76491AaB78dbC2d0f"
-ithaca_account_deployed = "0x1E7a350c76CCd750470930B5026fEa26F44cf937"
-account_proxy_deployed = "0xDe938C3642205782d52535d12eC9dFb4D1bC832b"
-simulator_deployed = "0xb100Dc6e1BA9e54965fBB865F65Ac42b28BC25E8"
-simple_funder_deployed = "0x57C3563C06CeDda15F7622A83a1BdBa84125351D"
-escrow_deployed = "0xCd075ceb5Cd463a9233a8085fc915767139F655c"
-simple_settler_deployed = "0x5aE547Dc236c31d30f19a5B9040992728Ac441A0"
-layerzero_settler_deployed = "0x23D18C5b5Df22fDcf6dDE73b0f1a76ee5761d206"
-exp_token_deployed = "0xa8071DA5e994cB8e3eB56CaD0FBB6ca424dD8dc0"
-exp2_token_deployed = "0x61727778216127D0843A99A3e91e99C27e9f3BC7"
-
-[84532.uint]
-chain_id = 84532
-layerzero_eid = 40245
-target_balance = "1000000000000000"
-default_num_signers = 10
-layerzero_confirmations = 1
-layerzero_max_message_size = 10000
-layerzero_optional_dvn_threshold = 0
-exp_mint_amount = "5000000000000000000000"
-layerzero_destination_chain_ids = [11155420]
-
-[84532.bytes32]
-salt = "0xdf08a9fa957a9ac004f84f3a8ab7318a7be55d59948f46497bd3717865295b61"
-
-[84532.string]
-name = "Base Sepolia"
-contracts = ["ALL"]
+layerzero_destination_chain_ids = [11155420] # optimism sepolia
layerzero_required_dvns = ["dvn_layerzero_labs"]
layerzero_optional_dvns = []
+layerzero_optional_dvn_threshold = 0
+layerzero_confirmations = 1
+layerzero_max_message_size = 10000
+# DVN addresses
+dvn_layerzero_labs = "0xe1a12515F9AB2764b887bF60B923Ca494EBbB2d6"
+dvn_google_cloud = "0xFc9d8E5d3FaB22fB6E93E9E2C90916E9dCa83Ade"
-[11155420]
-endpoint_url = "${RPC_11155420}"
+# Optimism Sepolia
+[forks.optimism-sepolia]
+rpc_url = "${RPC_11155420}"
-[11155420.bool]
+[forks.optimism-sepolia.vars]
+chain_id = 11155420
+name = "Optimism Sepolia"
is_testnet = true
-
-[11155420.address]
-funder_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-funder_signer = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-settler_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
+pause_authority = "0x0000000000000000000000000000000000000001"
+funder_owner = "0x0000000000000000000000000000000000000003"
+funder_signer = "0x0000000000000000000000000000000000000002"
+settler_owner = "0x0000000000000000000000000000000000000004"
l0_settler_owner = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-l0_settler_signer = "0x0000000000000000000000000000000000000006"
layerzero_endpoint = "0x6EDCE65403992e310A62460808c4b910D972f10f"
-layerzero_settler_address = "0x9F610182BD096ab7734C1365A44f48dD0A574d0D"
+layerzero_eid = 40232
+salt = "0x0000000000000000000000000000000000000000000000000000000000000001"
+target_balance = 10000000000000000 # 0.01 ether
+# Specify contracts to deploy. Use ["ALL"] to deploy all available contracts
+contracts = ["ALL"] # Deploy all contracts
+# SimpleFunder configuration
+simple_funder_address = "0x2AD8F6a3bB1126a777606eaFa9da9b95530d9597"
+
+default_num_signers = 10
+# LayerZero configuration
+layerzero_settler_address = "0x4225041FF3DB1C7d7a1029406bB80C7298767aca"
layerzero_send_uln302 = "0xB31D2cb502E25B30C651842C7C3293c51Fe6d16f"
layerzero_receive_uln302 = "0x9284fd59B95b9143AF0b9795CAC16eb3C723C9Ca"
+layerzero_destination_chain_ids = [84532, 11155111]
+layerzero_required_dvns = ["dvn_layerzero_labs"]
+layerzero_optional_dvns = []
+layerzero_optional_dvn_threshold = 0
+layerzero_confirmations = 1
+layerzero_max_message_size = 10000
+# DVN addresses
dvn_layerzero_labs = "0x28B6140ead70cb2Fb669705b3598ffB4BEaA060b"
dvn_google_cloud = "0x28b8B8Ea5C695EFa657B6b86a4a8E90ccbA93E9e"
-exp_minter_address = "0xB6918DaaB07e31556B45d7Fd2a33021Bc829adf4"
-supported_orchestrators = ["0xEd7c1e839381c489Dcd1ED3CE1B0e79DaE714f77"]
-orchestrator_deployed = "0xcA91Dba3dab46478FC466ef76491AaB78dbC2d0f"
-ithaca_account_deployed = "0x1E7a350c76CCd750470930B5026fEa26F44cf937"
-account_proxy_deployed = "0xDe938C3642205782d52535d12eC9dFb4D1bC832b"
-simulator_deployed = "0xb100Dc6e1BA9e54965fBB865F65Ac42b28BC25E8"
-simple_funder_deployed = "0x57C3563C06CeDda15F7622A83a1BdBa84125351D"
-escrow_deployed = "0xCd075ceb5Cd463a9233a8085fc915767139F655c"
-simple_settler_deployed = "0x5aE547Dc236c31d30f19a5B9040992728Ac441A0"
-layerzero_settler_deployed = "0x23D18C5b5Df22fDcf6dDE73b0f1a76ee5761d206"
-exp_token_deployed = "0xa8071DA5e994cB8e3eB56CaD0FBB6ca424dD8dc0"
-exp2_token_deployed = "0x61727778216127D0843A99A3e91e99C27e9f3BC7"
-[11155420.uint]
-chain_id = 11155420
-layerzero_eid = 40232
-target_balance = "1000000000000000"
-default_num_signers = 10
-layerzero_confirmations = 1
-layerzero_max_message_size = 10000
-layerzero_optional_dvn_threshold = 0
-exp_mint_amount = "2000000000000000000000"
-layerzero_destination_chain_ids = [84532]
-[11155420.bytes32]
-salt = "0xdf08a9fa957a9ac004f84f3a8ab7318a7be55d59948f46497bd3717865295b61"
-[11155420.string]
-name = "Optimism Sepolia"
-contracts = ["ALL"]
-layerzero_required_dvns = ["dvn_layerzero_labs"]
-layerzero_optional_dvns = []
+# Master Key has gas funds on all chains.
+# 1. Deployment Key ( Master Key )
+# 2. Funder Owner Key ( Master Key )
+# 3. Gas Signers ( Funding is included in the fund signers script + setting them as gas wallets )
+# 4. Simple Funder Address ( Funds need to come from GK after bridging, no automation )
diff --git a/deploy/execute_config.sh b/deploy/execute_config.sh
deleted file mode 100755
index 8e459b82..00000000
--- a/deploy/execute_config.sh
+++ /dev/null
@@ -1,486 +0,0 @@
-#!/bin/bash
-
-# Comprehensive deployment testing script for multiple chains
-# This script deploys contracts, configures LayerZero, and funds signers
-#
-# Usage: bash deploy/test_deployment.sh [chain_id1] [chain_id2] ...
-#
-# Examples:
-# bash deploy/test_deployment.sh # Deploy to all chains in config.toml
-# bash deploy/test_deployment.sh 84532 # Deploy only to Base Sepolia
-# bash deploy/test_deployment.sh 84532 11155420 # Deploy to Base Sepolia and Optimism Sepolia
-# bash deploy/test_deployment.sh 11155111 84532 11155420 # Deploy to all three chains
-
-set -e # Exit on any error
-
-# Color codes for output
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-# Function to print colored output
-log_info() {
- echo -e "${GREEN}[INFO]${NC} $1"
-}
-
-log_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-log_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
-}
-
-# Function to check if a command succeeded
-check_success() {
- if [ $? -eq 0 ]; then
- log_info "$1 succeeded"
- else
- log_error "$1 failed"
- exit 1
- fi
-}
-
-# Function to get all chain IDs from config.toml
-get_all_chains() {
- grep '^\[' deploy/config.toml | grep -v '\.' | sed 's/\[//g' | sed 's/\]//g'
-}
-
-# Parse command-line arguments
-REQUESTED_CHAINS=()
-if [ $# -gt 0 ]; then
- # User provided specific chain IDs to deploy to
- REQUESTED_CHAINS=("$@")
- log_info "Requested deployment for chain IDs: ${REQUESTED_CHAINS[*]}"
-else
- # Get all chains from config.toml
- REQUESTED_CHAINS=($(get_all_chains))
- log_info "No specific chains requested, will deploy to all chains in config.toml: ${REQUESTED_CHAINS[*]}"
-fi
-
-# Start deployment process
-log_info "Starting comprehensive deployment test"
-if [ ${#REQUESTED_CHAINS[@]} -gt 0 ]; then
- log_info "Deploying to chains: ${REQUESTED_CHAINS[*]}"
-fi
-echo "========================================================================"
-
-# Step 1: Load environment variables
-log_info "Loading environment variables from .env"
-if [ ! -f .env ]; then
- log_error ".env file not found!"
- exit 1
-fi
-
-source .env
-
-# Step 2: Validate required environment variables
-log_info "Validating required environment variables"
-
-if [ -z "$PRIVATE_KEY" ]; then
- log_error "PRIVATE_KEY is not set in .env"
- exit 1
-fi
-
-# Check RPC URLs for requested chains
-for chain_id in "${REQUESTED_CHAINS[@]}"; do
- RPC_VAR="RPC_${chain_id}"
- if [ -z "${!RPC_VAR}" ]; then
- log_error "$RPC_VAR is not set in .env for chain $chain_id"
- log_info "Please set $RPC_VAR in your .env file"
- exit 1
- fi
-done
-
-if [ -z "$GAS_SIGNER_MNEMONIC" ]; then
- log_warning "GAS_SIGNER_MNEMONIC is not set - FundSigners may fail"
-fi
-
-log_info "All required environment variables are set"
-echo ""
-
-# Step 3: Generate random salt for new deployment addresses
-log_info "Generating random salt for new contract addresses"
-
-# Generate a random 32-byte hex string for salt
-RANDOM_SALT="0x$(openssl rand -hex 32)"
-log_info "Generated salt: $RANDOM_SALT"
-
-# Update salt in config.toml for all requested chains
-for chain_id in "${REQUESTED_CHAINS[@]}"; do
- if grep -q "^\[${chain_id}\.bytes32\]" deploy/config.toml; then
- # Get chain name if available
- CHAIN_NAME=$(sed -n "/^\[${chain_id}\.string\]/,/^\[/p" deploy/config.toml | grep "^name" | cut -d'"' -f2)
- if [ -z "$CHAIN_NAME" ]; then
- CHAIN_NAME="Chain $chain_id"
- fi
-
- log_info "Updating salt for $CHAIN_NAME ($chain_id) in config.toml"
- sed -i.bak "/^\[${chain_id}\.bytes32\]/,/^\[/ s/^salt = .*/salt = \"$RANDOM_SALT\"/" deploy/config.toml
- else
- log_warning "No bytes32 section found for chain $chain_id, skipping salt update"
- fi
-done
-
-# Clean up backup files
-rm -f deploy/config.toml.bak
-
-log_info "Salt updated in config.toml for all requested chains"
-log_warning "⚠️ IMPORTANT: Save this salt value if you need to deploy to the same addresses on other chains: $RANDOM_SALT"
-echo ""
-
-# Step 4: Deploy contracts to Base Sepolia and Optimism Sepolia
-echo "========================================================================"
-# Build chain array string for forge script
-CHAIN_ARRAY="["
-for i in "${!REQUESTED_CHAINS[@]}"; do
- if [ $i -gt 0 ]; then
- CHAIN_ARRAY="${CHAIN_ARRAY},"
- fi
- CHAIN_ARRAY="${CHAIN_ARRAY}${REQUESTED_CHAINS[$i]}"
-done
-CHAIN_ARRAY="${CHAIN_ARRAY}]"
-
-log_info "STEP 1: Deploying contracts to chains: $CHAIN_ARRAY"
-echo "========================================================================"
-
-forge script deploy/DeployMain.s.sol:DeployMain \
- --broadcast --multi --slow \
- --sig "run(uint256[])" "$CHAIN_ARRAY" \
- --private-key $PRIVATE_KEY \
- -vvv
-
-check_success "Contract deployment"
-echo ""
-
-# Step 5: Configure LayerZero for cross-chain communication
-echo "========================================================================"
-log_info "STEP 2: Configuring LayerZero for cross-chain communication"
-echo "========================================================================"
-
-# Note: Using the same PRIVATE_KEY as requested (instead of L0_SETTLER_OWNER_PK)
-forge script deploy/ConfigureLayerZeroSettler.s.sol:ConfigureLayerZeroSettler \
- --broadcast --multi --slow \
- --sig "run(uint256[])" "$CHAIN_ARRAY" \
- --private-key $PRIVATE_KEY \
- -vvv
-
-check_success "LayerZero configuration"
-echo ""
-
-# Base Sepolia LayerZero verification
-if [ ! -z "$LAYERZERO_SETTLER_BASE" ]; then
- log_info "Base Sepolia LayerZero configuration:"
-
- # Get LayerZero endpoint from config
- LZ_ENDPOINT_BASE=$(sed -n "/^\[84532\.address\]/,/^\[/p" deploy/config.toml | grep "layerzero_endpoint" | cut -d'"' -f2)
- LZ_EID_BASE=$(sed -n "/^\[84532\.uint\]/,/^\[/p" deploy/config.toml | grep "layerzero_eid" | awk -F' = ' '{print $2}')
- LZ_SEND_ULN_BASE=$(sed -n "/^\[84532\.address\]/,/^\[/p" deploy/config.toml | grep "layerzero_send_uln302" | cut -d'"' -f2)
- LZ_RECEIVE_ULN_BASE=$(sed -n "/^\[84532\.address\]/,/^\[/p" deploy/config.toml | grep "layerzero_receive_uln302" | cut -d'"' -f2)
-
- # Check if endpoint is set on LayerZeroSettler
- CURRENT_ENDPOINT=$(cast call $LAYERZERO_SETTLER_BASE "endpoint()(address)" --rpc-url $RPC_84532 2>/dev/null || echo "0x0000000000000000000000000000000000000000")
-
- if [ "$CURRENT_ENDPOINT" == "$LZ_ENDPOINT_BASE" ]; then
- log_info " ✓ Endpoint correctly set to $LZ_ENDPOINT_BASE"
- else
- log_error " ✗ Endpoint mismatch: Expected $LZ_ENDPOINT_BASE, got $CURRENT_ENDPOINT"
- fi
-
- # Check L0SettlerSigner
- LZ_SIGNER=$(cast call $LAYERZERO_SETTLER_BASE "l0SettlerSigner()(address)" --rpc-url $RPC_84532 2>/dev/null || echo "0x0000000000000000000000000000000000000000")
- log_info " L0SettlerSigner: $LZ_SIGNER"
-fi
-
-# Optimism Sepolia LayerZero verification
-if [ ! -z "$LAYERZERO_SETTLER_OP" ]; then
- log_info "Optimism Sepolia LayerZero configuration:"
-
- # Get LayerZero endpoint from config - these are in the address section
- LZ_ENDPOINT_OP=$(sed -n "/^\[11155420\.address\]/,/^\[/p" deploy/config.toml | grep "layerzero_endpoint" | cut -d'"' -f2)
- LZ_EID_OP=$(sed -n "/^\[11155420\.uint\]/,/^\[/p" deploy/config.toml | grep "layerzero_eid" | awk -F' = ' '{print $2}')
- LZ_SEND_ULN_OP=$(sed -n "/^\[11155420\.address\]/,/^\[/p" deploy/config.toml | grep "layerzero_send_uln302" | cut -d'"' -f2)
- LZ_RECEIVE_ULN_OP=$(sed -n "/^\[11155420\.address\]/,/^\[/p" deploy/config.toml | grep "layerzero_receive_uln302" | cut -d'"' -f2)
-
- # Check if endpoint is set on LayerZeroSettler
- CURRENT_ENDPOINT=$(cast call $LAYERZERO_SETTLER_OP "endpoint()(address)" --rpc-url $RPC_11155420 2>/dev/null || echo "0x0000000000000000000000000000000000000000")
-
- if [ "$CURRENT_ENDPOINT" == "$LZ_ENDPOINT_OP" ]; then
- log_info " ✓ Endpoint correctly set to $LZ_ENDPOINT_OP"
- else
- log_error " ✗ Endpoint mismatch: Expected $LZ_ENDPOINT_OP, got $CURRENT_ENDPOINT"
- fi
-
- # Check L0SettlerSigner
- LZ_SIGNER=$(cast call $LAYERZERO_SETTLER_OP "l0SettlerSigner()(address)" --rpc-url $RPC_11155420 2>/dev/null || echo "0x0000000000000000000000000000000000000000")
- log_info " L0SettlerSigner: $LZ_SIGNER"
-fi
-
-# Verify cross-chain pathway configuration
-log_info "Cross-chain pathway verification:"
-
-# Get EIDs for both chains - they are in the uint sections
-LZ_EID_BASE=$(sed -n "/^\[84532\.uint\]/,/^\[/p" deploy/config.toml | grep "layerzero_eid" | awk -F' = ' '{print $2}')
-LZ_EID_OP=$(sed -n "/^\[11155420\.uint\]/,/^\[/p" deploy/config.toml | grep "layerzero_eid" | awk -F' = ' '{print $2}')
-
-# Check Base Sepolia -> Optimism Sepolia pathway
-if [ ! -z "$LAYERZERO_SETTLER_BASE" ] && [ ! -z "$LZ_EID_OP" ] && [ ! -z "$LZ_ENDPOINT_BASE" ] && [ ! -z "$LZ_SEND_ULN_BASE" ]; then
- log_info " Base Sepolia -> Optimism Sepolia pathway:"
-
- # Check executor configuration using endpoint.getConfig()
- # CONFIG_TYPE_EXECUTOR = 1
- EXECUTOR_CONFIG_BYTES=$(cast call $LZ_ENDPOINT_BASE "getConfig(address,address,uint32,uint32)(bytes)" "$LAYERZERO_SETTLER_BASE" "$LZ_SEND_ULN_BASE" "$LZ_EID_OP" "1" --rpc-url $RPC_84532 2>/dev/null || echo "0x")
-
- if [ "$EXECUTOR_CONFIG_BYTES" != "0x" ] && [ ! -z "$EXECUTOR_CONFIG_BYTES" ]; then
- # The executor config is encoded as (uint32 maxMessageSize, address executor)
- # We need to decode the bytes - first 32 bytes is maxMessageSize, next 32 bytes is executor address
- # Remove 0x prefix and get the executor address (last 40 hex chars of the second 32-byte word)
- EXECUTOR_HEX=$(echo "$EXECUTOR_CONFIG_BYTES" | sed 's/0x//' | tail -c 41)
- if [ ! -z "$EXECUTOR_HEX" ]; then
- EXECUTOR_ADDR="0x$EXECUTOR_HEX"
-
- if [ "$(echo $EXECUTOR_ADDR | tr '[:upper:]' '[:lower:]')" == "$(echo $LAYERZERO_SETTLER_BASE | tr '[:upper:]' '[:lower:]')" ]; then
- log_info " ✓ Executor correctly set to LayerZeroSettler"
- else
- log_warning " ⚠ Executor not set to LayerZeroSettler (self-execution model)"
- fi
- else
- log_warning " ⚠ Could not parse executor configuration"
- fi
- else
- log_warning " ⚠ Executor configuration not set"
- fi
-
- # Check ULN configuration using endpoint.getConfig()
- # CONFIG_TYPE_ULN = 2
- ULN_CONFIG_BYTES=$(cast call $LZ_ENDPOINT_BASE "getConfig(address,address,uint32,uint32)(bytes)" "$LAYERZERO_SETTLER_BASE" "$LZ_SEND_ULN_BASE" "$LZ_EID_OP" "2" --rpc-url $RPC_84532 2>/dev/null || echo "0x")
-
- if [ "$ULN_CONFIG_BYTES" != "0x" ] && [ ! -z "$ULN_CONFIG_BYTES" ] && [ ${#ULN_CONFIG_BYTES} -gt 10 ]; then
- log_info " ✓ ULN configuration is set"
- else
- log_warning " ⚠ ULN configuration not set"
- fi
-fi
-
-# Check Optimism Sepolia -> Base Sepolia pathway
-if [ ! -z "$LAYERZERO_SETTLER_OP" ] && [ ! -z "$LZ_EID_BASE" ] && [ ! -z "$LZ_ENDPOINT_OP" ] && [ ! -z "$LZ_SEND_ULN_OP" ]; then
- log_info " Optimism Sepolia -> Base Sepolia pathway:"
-
- # Check executor configuration using endpoint.getConfig()
- # CONFIG_TYPE_EXECUTOR = 1
- EXECUTOR_CONFIG_BYTES=$(cast call $LZ_ENDPOINT_OP "getConfig(address,address,uint32,uint32)(bytes)" "$LAYERZERO_SETTLER_OP" "$LZ_SEND_ULN_OP" "$LZ_EID_BASE" "1" --rpc-url $RPC_11155420 2>/dev/null || echo "0x")
-
- if [ "$EXECUTOR_CONFIG_BYTES" != "0x" ] && [ ! -z "$EXECUTOR_CONFIG_BYTES" ]; then
- # The executor config is encoded as (uint32 maxMessageSize, address executor)
- # Remove 0x prefix and get the executor address (last 40 hex chars of the second 32-byte word)
- EXECUTOR_HEX=$(echo "$EXECUTOR_CONFIG_BYTES" | sed 's/0x//' | tail -c 41)
- if [ ! -z "$EXECUTOR_HEX" ]; then
- EXECUTOR_ADDR="0x$EXECUTOR_HEX"
-
- if [ "$(echo $EXECUTOR_ADDR | tr '[:upper:]' '[:lower:]')" == "$(echo $LAYERZERO_SETTLER_OP | tr '[:upper:]' '[:lower:]')" ]; then
- log_info " ✓ Executor correctly set to LayerZeroSettler"
- else
- log_warning " ⚠ Executor not set to LayerZeroSettler (self-execution model)"
- fi
- else
- log_warning " ⚠ Could not parse executor configuration"
- fi
- else
- log_warning " ⚠ Executor configuration not set"
- fi
-
- # Check ULN configuration using endpoint.getConfig()
- # CONFIG_TYPE_ULN = 2
- ULN_CONFIG_BYTES=$(cast call $LZ_ENDPOINT_OP "getConfig(address,address,uint32,uint32)(bytes)" "$LAYERZERO_SETTLER_OP" "$LZ_SEND_ULN_OP" "$LZ_EID_BASE" "2" --rpc-url $RPC_11155420 2>/dev/null || echo "0x")
-
- if [ "$ULN_CONFIG_BYTES" != "0x" ] && [ ! -z "$ULN_CONFIG_BYTES" ] && [ ${#ULN_CONFIG_BYTES} -gt 10 ]; then
- log_info " ✓ ULN configuration is set"
- else
- log_warning " ⚠ ULN configuration not set"
- fi
-fi
-
-echo ""
-
-# Step 3: Fund signers and set them as gas wallets
-echo "========================================================================"
-log_info "STEP 3: Funding signers and setting them as gas wallets"
-echo "========================================================================"
-
-if [ -z "$GAS_SIGNER_MNEMONIC" ]; then
- log_error "Cannot proceed with FundSigners - GAS_SIGNER_MNEMONIC not set"
- exit 1
-fi
-
-forge script deploy/FundSigners.s.sol:FundSigners \
- --broadcast --multi --slow \
- --sig "run(uint256[])" "$CHAIN_ARRAY" \
- --private-key $PRIVATE_KEY \
- -vvv
-
-check_success "Signer funding"
-echo ""
-
-# Final Step: Run comprehensive verification for all deployed contracts and configurations
-echo "========================================================================"
-log_info "FINAL VERIFICATION: Running comprehensive verification"
-echo "========================================================================"
-
-log_info "Running verification script for all deployments and configurations..."
-bash deploy/verify_config.sh "${REQUESTED_CHAINS[@]}"
-check_success "Comprehensive verification"
-
-echo ""
-echo "========================================================================"
-log_info "DEPLOYMENT COMPLETED SUCCESSFULLY!"
-echo "========================================================================"
-log_info "✅ All contracts deployed to requested chains"
-log_info "✅ LayerZero configured for cross-chain communication"
-log_info "✅ Signers funded and set as gas wallets"
-log_info "✅ All verifications passed"
-echo ""
-log_info "Deployment and configuration completed successfully for chains: ${REQUESTED_CHAINS[*]}"
-exit 0
-
-# The old verification code below is no longer needed since verify_config.sh handles everything
-: '
-# Old verification code starts here
-
-# Derive signer addresses from mnemonic (first 3 for verification)
-log_info "Checking signer balances..."
-
-# First signer address (derived from the mnemonic)
-SIGNER_0="0x33097354Acf259e1fD19fB91159BAE6ccf912Fdb"
-SIGNER_1="0x49e1f963ddb4122BD3ccC786eB8F9983dABa8658"
-SIGNER_2="0x46C66f82B32f04bf04D05ED92e10b57188BF408A"
-
-# Get target balance from config
-TARGET_BALANCE_BASE=$(sed -n "/^\[84532\.uint\]/,/^\[/p" deploy/config.toml | grep "target_balance" | awk -F' = ' '{print $2}' | tr -d '"')
-
-# Check balances on Base Sepolia
-log_info "Base Sepolia (84532) signer balances (target: $TARGET_BALANCE_BASE wei):"
-BALANCE_0_BASE=$(cast balance $SIGNER_0 --rpc-url $RPC_84532 2>/dev/null || echo "0")
-BALANCE_1_BASE=$(cast balance $SIGNER_1 --rpc-url $RPC_84532 2>/dev/null || echo "0")
-BALANCE_2_BASE=$(cast balance $SIGNER_2 --rpc-url $RPC_84532 2>/dev/null || echo "0")
-
-# Check if balances meet target
-if [ "$BALANCE_0_BASE" -ge "$TARGET_BALANCE_BASE" ]; then
- log_info " Signer 0 ($SIGNER_0): $BALANCE_0_BASE wei ✓"
-else
- log_warning " Signer 0 ($SIGNER_0): $BALANCE_0_BASE wei (below target)"
-fi
-
-if [ "$BALANCE_1_BASE" -ge "$TARGET_BALANCE_BASE" ]; then
- log_info " Signer 1 ($SIGNER_1): $BALANCE_1_BASE wei ✓"
-else
- log_warning " Signer 1 ($SIGNER_1): $BALANCE_1_BASE wei (below target)"
-fi
-
-if [ "$BALANCE_2_BASE" -ge "$TARGET_BALANCE_BASE" ]; then
- log_info " Signer 2 ($SIGNER_2): $BALANCE_2_BASE wei ✓"
-else
- log_warning " Signer 2 ($SIGNER_2): $BALANCE_2_BASE wei (below target)"
-fi
-
-# Get target balance from config
-TARGET_BALANCE_OP=$(sed -n "/^\[11155420\.uint\]/,/^\[/p" deploy/config.toml | grep "target_balance" | awk -F' = ' '{print $2}' | tr -d '"')
-
-# Check balances on Optimism Sepolia
-log_info "Optimism Sepolia (11155420) signer balances (target: $TARGET_BALANCE_OP wei):"
-BALANCE_0_OP=$(cast balance $SIGNER_0 --rpc-url $RPC_11155420 2>/dev/null || echo "0")
-BALANCE_1_OP=$(cast balance $SIGNER_1 --rpc-url $RPC_11155420 2>/dev/null || echo "0")
-BALANCE_2_OP=$(cast balance $SIGNER_2 --rpc-url $RPC_11155420 2>/dev/null || echo "0")
-
-# Check if balances meet target
-if [ "$BALANCE_0_OP" -ge "$TARGET_BALANCE_OP" ]; then
- log_info " Signer 0 ($SIGNER_0): $BALANCE_0_OP wei ✓"
-else
- log_warning " Signer 0 ($SIGNER_0): $BALANCE_0_OP wei (below target)"
-fi
-
-if [ "$BALANCE_1_OP" -ge "$TARGET_BALANCE_OP" ]; then
- log_info " Signer 1 ($SIGNER_1): $BALANCE_1_OP wei ✓"
-else
- log_warning " Signer 1 ($SIGNER_1): $BALANCE_1_OP wei (below target)"
-fi
-
-if [ "$BALANCE_2_OP" -ge "$TARGET_BALANCE_OP" ]; then
- log_info " Signer 2 ($SIGNER_2): $BALANCE_2_OP wei ✓"
-else
- log_warning " Signer 2 ($SIGNER_2): $BALANCE_2_OP wei (below target)"
-fi
-
-# Verify gas wallets and orchestrators in SimpleFunder
-log_info "Checking SimpleFunder configuration..."
-
-# Read orchestrator addresses from config.toml
-# supported_orchestrators is an array like ["0xAddr1", "0xAddr2"]
-# For simplicity, we'll extract the first orchestrator address
-ORCHESTRATOR_BASE_CONFIG=$(sed -n "/^\[84532\.address\]/,/^\[/p" deploy/config.toml | grep "supported_orchestrators" | sed 's/.*\["\([^"]*\)".*/\1/')
-ORCHESTRATOR_OP_CONFIG=$(sed -n "/^\[11155420\.address\]/,/^\[/p" deploy/config.toml | grep "supported_orchestrators" | sed 's/.*\["\([^"]*\)".*/\1/')
-
-# For Base Sepolia
-if [ ! -z "$SIMPLE_FUNDER_BASE" ]; then
- log_info "Base Sepolia SimpleFunder ($SIMPLE_FUNDER_BASE):"
-
- # Check if signers are gas wallets (using mapping gasWallets(address) => bool)
- IS_GAS_WALLET_0=$(cast call $SIMPLE_FUNDER_BASE "gasWallets(address)(bool)" $SIGNER_0 --rpc-url $RPC_84532 2>/dev/null || echo "false")
-
- if [ "$IS_GAS_WALLET_0" == "true" ]; then
- log_info " ✓ Signer 0 is registered as gas wallet"
- else
- log_warning " ✗ Signer 0 is NOT registered as gas wallet"
- fi
-
- # Check orchestrator configuration (using mapping orchestrators(address) => bool)
- if [ ! -z "$ORCHESTRATOR_BASE_CONFIG" ]; then
- IS_SUPPORTED=$(cast call $SIMPLE_FUNDER_BASE "orchestrators(address)(bool)" $ORCHESTRATOR_BASE_CONFIG --rpc-url $RPC_84532 2>/dev/null || echo "false")
-
- if [ "$IS_SUPPORTED" == "true" ]; then
- log_info " ✓ Orchestrator $ORCHESTRATOR_BASE_CONFIG is supported"
- else
- log_warning " ✗ Orchestrator $ORCHESTRATOR_BASE_CONFIG is NOT supported"
- fi
- fi
-fi
-
-# For Optimism Sepolia
-if [ ! -z "$SIMPLE_FUNDER_OP" ]; then
- log_info "Optimism Sepolia SimpleFunder ($SIMPLE_FUNDER_OP):"
-
- # Check if signers are gas wallets
- IS_GAS_WALLET_0=$(cast call $SIMPLE_FUNDER_OP "gasWallets(address)(bool)" $SIGNER_0 --rpc-url $RPC_11155420 2>/dev/null || echo "false")
-
- if [ "$IS_GAS_WALLET_0" == "true" ]; then
- log_info " ✓ Signer 0 is registered as gas wallet"
- else
- log_warning " ✗ Signer 0 is NOT registered as gas wallet"
- fi
-
- # Check orchestrator configuration
- if [ ! -z "$ORCHESTRATOR_OP_CONFIG" ]; then
- IS_SUPPORTED=$(cast call $SIMPLE_FUNDER_OP "orchestrators(address)(bool)" $ORCHESTRATOR_OP_CONFIG --rpc-url $RPC_11155420 2>/dev/null || echo "false")
-
- if [ "$IS_SUPPORTED" == "true" ]; then
- log_info " ✓ Orchestrator $ORCHESTRATOR_OP_CONFIG is supported"
- else
- log_warning " ✗ Orchestrator $ORCHESTRATOR_OP_CONFIG is NOT supported"
- fi
- fi
-fi
-
-echo ""
-
-# Step 7: Summary
-echo "========================================================================"
-log_info "DEPLOYMENT TEST COMPLETED SUCCESSFULLY!"
-echo "========================================================================"
-log_info "✅ Contracts deployed to Base Sepolia and Optimism Sepolia"
-log_info "✅ LayerZero configured for cross-chain communication"
-log_info "✅ Signers funded and set as gas wallets"
-echo ""
-
-echo ""
-log_info "All deployment steps completed successfully!"
-' # End of commented old verification code
\ No newline at end of file
diff --git a/deploy/registry/deployment_11155420_0x0000000000000000000000000000000000000000000000000000000000000001.json b/deploy/registry/deployment_11155420_0x0000000000000000000000000000000000000000000000000000000000000001.json
new file mode 100644
index 00000000..23cb5e8b
--- /dev/null
+++ b/deploy/registry/deployment_11155420_0x0000000000000000000000000000000000000000000000000000000000000001.json
@@ -0,0 +1 @@
+{"Orchestrator": "0xb33adF2c2257a94314d408255aC843fd53B1a7e1","IthacaAccount": "0x5a87ef243CDA70a855828d4989Fad61B56A467d3","AccountProxy": "0x4ACD713815fbb363a89D9Ff046C56cEdC7EF3ad7","Simulator": "0x65Ae218EB1987b8bd0F9eeb38D1B344726D41dA5","SimpleFunder": "0xA47C5C472449979a2F37dF2971627cD6587bADb8","Escrow": "0x24F50280cE3B51Ab1967F048746FB7ba3C7B4067","SimpleSettler": "0xb934afBB50b8aBBe24959f9398fE024BEe9Bf716","LayerZeroSettler": "0xB89f4A85d38C3A2407854269527fabD3b61fd56a"}
\ No newline at end of file
diff --git a/deploy/registry/deployment_84532_0x0000000000000000000000000000000000000000000000000000000000000001.json b/deploy/registry/deployment_84532_0x0000000000000000000000000000000000000000000000000000000000000001.json
new file mode 100644
index 00000000..c37d42b4
--- /dev/null
+++ b/deploy/registry/deployment_84532_0x0000000000000000000000000000000000000000000000000000000000000001.json
@@ -0,0 +1,10 @@
+{
+ "Orchestrator": "0xb33adF2c2257a94314d408255aC843fd53B1a7e1",
+ "IthacaAccount": "0x5a87ef243CDA70a855828d4989Fad61B56A467d3",
+ "AccountProxy": "0x4ACD713815fbb363a89D9Ff046C56cEdC7EF3ad7",
+ "Simulator": "0x65Ae218EB1987b8bd0F9eeb38D1B344726D41dA5",
+ "SimpleFunder": "0xA47C5C472449979a2F37dF2971627cD6587bADb8",
+ "Escrow": "0x24F50280cE3B51Ab1967F048746FB7ba3C7B4067",
+ "SimpleSettler": "0xb934afBB50b8aBBe24959f9398fE024BEe9Bf716",
+ "LayerZeroSettler": "0xB89f4A85d38C3A2407854269527fabD3b61fd56a"
+}
diff --git a/deploy/verify_config.sh b/deploy/verify_config.sh
deleted file mode 100755
index af3a0fcd..00000000
--- a/deploy/verify_config.sh
+++ /dev/null
@@ -1,553 +0,0 @@
-#!/bin/bash
-
-# Verification script for deployed contracts and configuration
-# This script performs all verification checks from test_deployment.sh
-# without running any deployments or modifications
-#
-# Usage: bash deploy/verify_config.sh [chain_id1] [chain_id2] ...
-#
-# Examples:
-# bash deploy/verify_config.sh # Verify all chains in config.toml
-# bash deploy/verify_config.sh 84532 # Verify only Base Sepolia
-# bash deploy/verify_config.sh 84532 11155420 # Verify Base Sepolia and Optimism Sepolia
-# bash deploy/verify_config.sh 11155111 # Verify only Sepolia
-
-set -e # Exit on any error
-
-# Color codes for output
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-# Function to print colored output
-log_info() {
- echo -e "${GREEN}[INFO]${NC} $1"
-}
-
-log_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-log_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
-}
-
-# Parse command-line arguments
-REQUESTED_CHAINS=()
-if [ $# -gt 0 ]; then
- # User provided specific chain IDs to check
- REQUESTED_CHAINS=("$@")
- log_info "Requested verification for chain IDs: ${REQUESTED_CHAINS[*]}"
-else
- log_info "No specific chains requested, will verify all chains in config.toml"
-fi
-
-# Start verification process
-echo "========================================================================"
-log_info "Configuration and Deployment Verification Script"
-log_info "This script verifies the current state without making any changes"
-if [ ${#REQUESTED_CHAINS[@]} -gt 0 ]; then
- log_info "Verifying chains: ${REQUESTED_CHAINS[*]}"
-else
- log_info "Verifying all chains in config.toml"
-fi
-echo "========================================================================"
-
-# Load environment variables
-log_info "Loading environment variables from .env"
-if [ ! -f .env ]; then
- log_error ".env file not found!"
- exit 1
-fi
-
-source .env
-
-# Validate required environment variables (just check core ones)
-log_info "Validating required environment variables"
-required_vars=("PRIVATE_KEY" "GAS_SIGNER_MNEMONIC")
-missing_vars=()
-
-for var in "${required_vars[@]}"; do
- if [ -z "${!var}" ]; then
- missing_vars+=("$var")
- fi
-done
-
-if [ ${#missing_vars[@]} -gt 0 ]; then
- log_error "Missing required environment variables: ${missing_vars[*]}"
- exit 1
-fi
-
-log_info "Core environment variables are set"
-echo ""
-
-# Function to extract value from TOML for a specific chain section
-extract_value() {
- local chain=$1
- local key=$2
- local config_file=$3
-
- # Get the next section after the chain section
- local next_section=$(awk "/^\[$chain\]/,0" "$config_file" | grep '^\[' | grep -v "^\[$chain" | head -1)
-
- if [ -z "$next_section" ]; then
- # This is the last section, get everything after it
- awk "/^\[$chain\]/,0" "$config_file" | grep "^$key" | head -1 | cut -d'"' -f2
- else
- # Get content between this section and the next
- awk "/^\[$chain\]/,/^${next_section//[/\\[}/ { if (/^$key/) print }" "$config_file" | head -1 | cut -d'"' -f2
- fi
-}
-
-# Function to extract uint value from TOML for a specific chain section
-extract_uint_value() {
- local chain=$1
- local key=$2
- local config_file=$3
-
- # Use sed to extract the section and grep for the key
- sed -n "/^\[$chain\.uint\]/,/^\[/p" "$config_file" | grep "^$key" | head -1 | awk -F' = ' '{print $2}' | tr -d '"'
-}
-
-# Get all chain sections from config.toml (excluding .uint, .bytes32, .address, .bool, .string subsections)
-get_chains() {
- grep '^\[' deploy/config.toml | grep -v '\.' | sed 's/\[//g' | sed 's/\]//g'
-}
-
-# Function to extract address value from TOML for a specific chain section
-extract_address_value() {
- local chain=$1
- local key=$2
- local config_file=$3
-
- # Use sed to extract the section and grep for the key
- sed -n "/^\[$chain\.address\]/,/^\[/p" "$config_file" | grep "^$key" | head -1 | cut -d'"' -f2
-}
-
-# Function to extract string value from TOML for a specific chain section
-extract_string_value() {
- local chain=$1
- local key=$2
- local config_file=$3
-
- # Use sed to extract the section and grep for the key
- sed -n "/^\[$chain\.string\]/,/^\[/p" "$config_file" | grep "^$key" | head -1 | cut -d'"' -f2
-}
-
-# ========================================================================
-# SECTION 1: Verify Contract Deployments
-# ========================================================================
-
-echo "========================================================================"
-log_info "SECTION 1: Verifying Contract Deployments"
-echo "========================================================================"
-
-log_info "Reading deployed contract addresses from config.toml..."
-
-# Contract types to verify
-CONTRACT_TYPES=("orchestrator_deployed:Orchestrator" "ithaca_account_deployed:IthacaAccount" "account_proxy_deployed:AccountProxy" "simulator_deployed:Simulator" "simple_funder_deployed:SimpleFunder" "escrow_deployed:Escrow" "simple_settler_deployed:SimpleSettler" "layerzero_settler_deployed:LayerZeroSettler" "exp_token_deployed:ExpToken" "exp2_token_deployed:Exp2Token")
-
-# Get chains to verify
-if [ ${#REQUESTED_CHAINS[@]} -gt 0 ]; then
- # Use the chains requested by the user
- CHAINS=("${REQUESTED_CHAINS[@]}")
- log_info "Verifying ${#CHAINS[@]} requested chain(s): ${CHAINS[*]}"
-else
- # Get all chains from config.toml
- CHAINS=($(get_chains))
- log_info "Found ${#CHAINS[@]} chain(s) in config: ${CHAINS[*]}"
-fi
-echo ""
-
-TOTAL_FAILED=0
-
-# Iterate over each chain
-for chain in "${CHAINS[@]}"; do
- # Verify this chain exists in config
- if ! grep -q "^\[$chain\]" deploy/config.toml; then
- log_error "Chain ID $chain not found in config.toml"
- continue
- fi
- # The chain variable IS the chain ID now
- CHAIN_ID=$chain
-
- # Get chain name for display purposes
- CHAIN_NAME=$(extract_string_value "$chain" "name" "deploy/config.toml")
- if [ -z "$CHAIN_NAME" ]; then
- CHAIN_NAME="Chain $CHAIN_ID"
- fi
-
- log_info "Checking deployed contracts on $CHAIN_NAME (ID: $CHAIN_ID)..."
-
- # Get RPC URL from environment variable
- RPC_VAR="RPC_${CHAIN_ID}"
- RPC_URL=${!RPC_VAR}
-
- if [ -z "$RPC_URL" ]; then
- log_warning " RPC URL not set for chain $chain (ID: $CHAIN_ID), skipping..."
- log_warning " Set environment variable $RPC_VAR to enable verification"
- continue
- fi
-
- log_info " Chain ID: $CHAIN_ID"
-
- CHAIN_FAILED=0
-
- # Check each contract type
- for contract_type in "${CONTRACT_TYPES[@]}"; do
- IFS=":" read -r key name <<< "$contract_type"
-
- # Extract address for this contract on this chain from the .address section
- addr=$(extract_address_value "$chain" "$key" "deploy/config.toml")
-
- if [ ! -z "$addr" ]; then
- CODE=$(cast code $addr --rpc-url $RPC_URL 2>/dev/null || echo "0x")
- if [ "$CODE" != "0x" ] && [ ! -z "$CODE" ]; then
- log_info " ✓ $name deployed at $addr"
- else
- log_error " ✗ $name NOT found at $addr"
- CHAIN_FAILED=$((CHAIN_FAILED + 1))
- fi
- else
- log_warning " ⚠ $name address not found in config.toml"
- fi
- done
-
- if [ $CHAIN_FAILED -eq 0 ]; then
- log_info " ✅ All contracts verified on $CHAIN_NAME"
- else
- log_error " ❌ $CHAIN_FAILED contract(s) failed verification on $CHAIN_NAME"
- TOTAL_FAILED=$((TOTAL_FAILED + CHAIN_FAILED))
- fi
- echo ""
-done
-
-if [ $TOTAL_FAILED -eq 0 ]; then
- log_info "✅ All contracts verified successfully across all chains!"
-else
- log_error "❌ Total contract verification failures: $TOTAL_FAILED"
-fi
-echo ""
-
-# ========================================================================
-# SECTION 2: Verify LayerZero Configuration
-# ========================================================================
-
-echo "========================================================================"
-log_info "SECTION 2: Verifying LayerZero Configuration"
-echo "========================================================================"
-
-log_info "Checking LayerZero configuration on all chains..."
-
-# Store LayerZero info for each chain (using arrays instead of associative arrays for compatibility)
-LZ_CHAINS=()
-LZ_SETTLERS=()
-LZ_ENDPOINTS=()
-LZ_EIDS=()
-LZ_SEND_ULNS=()
-LZ_RECEIVE_ULNS=()
-LZ_CHAIN_IDS=()
-LZ_RPC_URLS=()
-
-# Collect LayerZero configuration for each chain
-LZ_INDEX=0
-for chain in "${CHAINS[@]}"; do
- # Skip if chain doesn't exist in config
- if ! grep -q "^\[$chain\]" deploy/config.toml; then
- continue
- fi
- # Chain variable IS the chain ID now
- CHAIN_ID=$chain
-
- # Get LayerZero settler address
- settler=$(extract_address_value "$chain" "layerzero_settler_deployed" "deploy/config.toml")
-
- if [ -z "$settler" ]; then
- CHAIN_NAME=$(extract_string_value "$chain" "name" "deploy/config.toml")
- log_info " No LayerZero settler deployed on $CHAIN_NAME (ID: $CHAIN_ID), skipping LayerZero checks..."
- continue
- fi
-
- LZ_CHAINS+=("$chain")
- LZ_SETTLERS+=("$settler")
-
- # Get other LayerZero config from .address section
- LZ_ENDPOINTS+=($(extract_address_value "$chain" "layerzero_endpoint" "deploy/config.toml"))
- LZ_SEND_ULNS+=($(extract_address_value "$chain" "layerzero_send_uln302" "deploy/config.toml"))
- LZ_RECEIVE_ULNS+=($(extract_address_value "$chain" "layerzero_receive_uln302" "deploy/config.toml"))
-
- # Get EID from .uint section
- LZ_EIDS+=($(extract_uint_value "$chain" "layerzero_eid" "deploy/config.toml"))
-
- LZ_CHAIN_IDS+=("$CHAIN_ID")
-
- # Get RPC URL
- RPC_VAR="RPC_${CHAIN_ID}"
- LZ_RPC_URLS+=(${!RPC_VAR})
-done
-
-# Verify LayerZero configuration for each chain
-for i in "${!LZ_CHAINS[@]}"; do
- chain=${LZ_CHAINS[$i]}
- CHAIN_NAME=$(extract_string_value "$chain" "name" "deploy/config.toml")
- if [ -z "$CHAIN_NAME" ]; then
- CHAIN_NAME="Chain $chain"
- fi
- log_info "$CHAIN_NAME LayerZero configuration:"
-
- settler=${LZ_SETTLERS[$i]}
- endpoint=${LZ_ENDPOINTS[$i]}
- eid=${LZ_EIDS[$i]}
- rpc_url=${LZ_RPC_URLS[$i]}
-
- if [ -z "$rpc_url" ]; then
- log_warning " RPC URL not available for $CHAIN_NAME, skipping checks..."
- continue
- fi
-
- log_info " Settler: $settler"
- log_info " Expected Endpoint: $endpoint"
- log_info " EID: $eid"
-
- # Check if endpoint is set on LayerZeroSettler
- CURRENT_ENDPOINT=$(cast call $settler "endpoint()(address)" --rpc-url $rpc_url 2>/dev/null || echo "0x0000000000000000000000000000000000000000")
-
- if [ "$CURRENT_ENDPOINT" == "$endpoint" ]; then
- log_info " ✓ Endpoint correctly set to $endpoint"
- else
- log_error " ✗ Endpoint mismatch: Expected $endpoint, got $CURRENT_ENDPOINT"
- fi
-
- # Check L0SettlerSigner
- LZ_SIGNER=$(cast call $settler "l0SettlerSigner()(address)" --rpc-url $rpc_url 2>/dev/null || echo "0x0000000000000000000000000000000000000000")
- log_info " L0SettlerSigner: $LZ_SIGNER"
- echo ""
-done
-
-# Verify cross-chain pathway configuration
-log_info "Cross-chain pathway verification:"
-
-# Check pathways between all pairs of chains
-for i in "${!LZ_CHAINS[@]}"; do
- for j in "${!LZ_CHAINS[@]}"; do
- if [ "$i" == "$j" ]; then
- continue
- fi
-
- source_chain_id=${LZ_CHAINS[$i]}
- dest_chain_id=${LZ_CHAINS[$j]}
-
- source_chain=$(extract_string_value "$source_chain_id" "name" "deploy/config.toml")
- dest_chain=$(extract_string_value "$dest_chain_id" "name" "deploy/config.toml")
- if [ -z "$source_chain" ]; then
- source_chain="Chain $source_chain_id"
- fi
- if [ -z "$dest_chain" ]; then
- dest_chain="Chain $dest_chain_id"
- fi
-
- source_settler=${LZ_SETTLERS[$i]}
- source_endpoint=${LZ_ENDPOINTS[$i]}
- source_send_uln=${LZ_SEND_ULNS[$i]}
- source_rpc=${LZ_RPC_URLS[$i]}
- dest_eid=${LZ_EIDS[$j]}
-
- if [ -z "$source_rpc" ]; then
- continue
- fi
-
- log_info " $source_chain -> $dest_chain pathway:"
-
- # Check executor configuration using endpoint.getConfig()
- # CONFIG_TYPE_EXECUTOR = 1
- EXECUTOR_CONFIG_BYTES=$(cast call $source_endpoint "getConfig(address,address,uint32,uint32)(bytes)" "$source_settler" "$source_send_uln" "$dest_eid" "1" --rpc-url $source_rpc 2>/dev/null || echo "0x")
-
- if [ "$EXECUTOR_CONFIG_BYTES" != "0x" ] && [ ! -z "$EXECUTOR_CONFIG_BYTES" ]; then
- # Try to decode executor address using cast
- DECODED=$(cast abi-decode "f()(uint32,address)" "$EXECUTOR_CONFIG_BYTES" 2>/dev/null || echo "")
- if [ ! -z "$DECODED" ]; then
- EXECUTOR_ADDR=$(echo "$DECODED" | tail -1)
- if [ "$(echo $EXECUTOR_ADDR | tr '[:upper:]' '[:lower:]')" == "$(echo $source_settler | tr '[:upper:]' '[:lower:]')" ]; then
- log_info " ✓ Executor correctly set to LayerZeroSettler"
- else
- log_error " ⚠ Executor not set to LayerZeroSettler (self-execution model)"
- fi
- else
- log_error " ⚠ Could not parse executor configuration"
- fi
- else
- log_error " ⚠ Executor configuration not set"
- fi
-
- # Check ULN configuration using endpoint.getConfig()
- # CONFIG_TYPE_ULN = 2
- ULN_CONFIG_BYTES=$(cast call $source_endpoint "getConfig(address,address,uint32,uint32)(bytes)" "$source_settler" "$source_send_uln" "$dest_eid" "2" --rpc-url $source_rpc 2>/dev/null || echo "0x")
-
- if [ "$ULN_CONFIG_BYTES" != "0x" ] && [ ! -z "$ULN_CONFIG_BYTES" ] && [ ${#ULN_CONFIG_BYTES} -gt 10 ]; then
- log_info " ✓ ULN configuration is set"
- else
- log_error " ⚠ ULN configuration not set"
- fi
- done
-done
-echo ""
-
-# ========================================================================
-# SECTION 3: Verify Signer Funding and Gas Wallet Configuration
-# ========================================================================
-
-echo "========================================================================"
-log_info "SECTION 3: Verifying Signer Funding and Gas Wallet Configuration"
-echo "========================================================================"
-
-# Derive signer addresses from mnemonic (first 3 for verification)
-log_info "Checking signer balances..."
-
-# Derive signer addresses from mnemonic
-log_info "Deriving signer addresses from gas wallet mnemonic..."
-SIGNER_0=$(cast wallet address --mnemonic "$GAS_SIGNER_MNEMONIC" --mnemonic-index 0)
-SIGNER_1=$(cast wallet address --mnemonic "$GAS_SIGNER_MNEMONIC" --mnemonic-index 1)
-SIGNER_2=$(cast wallet address --mnemonic "$GAS_SIGNER_MNEMONIC" --mnemonic-index 2)
-
-log_info "Signer addresses:"
-log_info " Signer 0: $SIGNER_0"
-log_info " Signer 1: $SIGNER_1"
-log_info " Signer 2: $SIGNER_2"
-
-# Check balances on all chains
-for chain in "${CHAINS[@]}"; do
- # Skip if chain doesn't exist in config
- if ! grep -q "^\[$chain\]" deploy/config.toml; then
- continue
- fi
- # Chain variable IS the chain ID now
- CHAIN_ID=$chain
- RPC_VAR="RPC_${CHAIN_ID}"
- RPC_URL=${!RPC_VAR}
-
- # Get chain name for display
- CHAIN_NAME=$(extract_string_value "$chain" "name" "deploy/config.toml")
- if [ -z "$CHAIN_NAME" ]; then
- CHAIN_NAME="Chain $CHAIN_ID"
- fi
-
- if [ -z "$RPC_URL" ]; then
- log_warning " RPC URL not set for $CHAIN_NAME, skipping signer balance checks..."
- continue
- fi
-
- # Get target balance from config
- TARGET_BALANCE=$(extract_uint_value "$chain" "target_balance" "deploy/config.toml")
-
- if [ -z "$TARGET_BALANCE" ]; then
- log_error " Target balance not set for $CHAIN_NAME"
- continue
- fi
-
- log_info "$CHAIN_NAME (Chain ID: $CHAIN_ID) signer balances (target: $TARGET_BALANCE wei):"
-
- # Check each signer's balance
- for i in 0 1 2; do
- SIGNER_VAR="SIGNER_${i}"
- SIGNER_ADDR=${!SIGNER_VAR}
-
- BALANCE=$(cast balance $SIGNER_ADDR --rpc-url $RPC_URL 2>/dev/null || echo "0")
-
- if [ "$BALANCE" -ge "$TARGET_BALANCE" ] 2>/dev/null; then
- log_info " Signer $i ($SIGNER_ADDR): $BALANCE wei ✓"
- else
- log_error " Signer $i ($SIGNER_ADDR): $BALANCE wei (below target)"
- fi
- done
- echo ""
-done
-
-# Verify gas wallets and orchestrators in SimpleFunder
-log_info "Checking SimpleFunder configuration..."
-
-for chain in "${CHAINS[@]}"; do
- # Skip if chain doesn't exist in config
- if ! grep -q "^\[$chain\]" deploy/config.toml; then
- continue
- fi
-
- # Get SimpleFunder address
- SIMPLE_FUNDER=$(extract_address_value "$chain" "simple_funder_deployed" "deploy/config.toml")
-
- # Get chain name for display
- CHAIN_NAME=$(extract_string_value "$chain" "name" "deploy/config.toml")
- if [ -z "$CHAIN_NAME" ]; then
- CHAIN_NAME="Chain $chain"
- fi
-
- if [ -z "$SIMPLE_FUNDER" ]; then
- log_info " No SimpleFunder deployed on $CHAIN_NAME, skipping..."
- continue
- fi
-
- # Chain variable IS the chain ID now
- CHAIN_ID=$chain
- RPC_VAR="RPC_${CHAIN_ID}"
- RPC_URL=${!RPC_VAR}
-
- if [ -z "$RPC_URL" ]; then
- log_warning " RPC URL not set for $CHAIN_NAME, skipping SimpleFunder checks..."
- continue
- fi
-
- log_info "$CHAIN_NAME SimpleFunder ($SIMPLE_FUNDER):"
-
- # Check if signers are gas wallets
- GAS_WALLET_OK=0
- for i in 0 1 2; do
- SIGNER_VAR="SIGNER_${i}"
- SIGNER_ADDR=${!SIGNER_VAR}
- IS_GAS_WALLET=$(cast call $SIMPLE_FUNDER "gasWallets(address)(bool)" $SIGNER_ADDR --rpc-url $RPC_URL 2>/dev/null || echo "false")
-
- if [ "$IS_GAS_WALLET" == "true" ]; then
- log_info " ✓ Signer $i is registered as gas wallet"
- GAS_WALLET_OK=$((GAS_WALLET_OK + 1))
- else
- log_error " ✗ Signer $i is NOT registered as gas wallet"
- fi
- done
-
- # Check orchestrator configuration
- # Extract orchestrator from supported_orchestrators array in .address section
- ORCHESTRATOR_CONFIG=$(extract_address_value "$chain" "supported_orchestrators" "deploy/config.toml" | sed 's/.*\["\([^"]*\)".*/\1/')
-
- if [ ! -z "$ORCHESTRATOR_CONFIG" ]; then
- IS_SUPPORTED=$(cast call $SIMPLE_FUNDER "orchestrators(address)(bool)" $ORCHESTRATOR_CONFIG --rpc-url $RPC_URL 2>/dev/null || echo "false")
-
- if [ "$IS_SUPPORTED" == "true" ]; then
- log_info " ✓ Orchestrator $ORCHESTRATOR_CONFIG is supported"
- else
- log_error " ✗ Orchestrator $ORCHESTRATOR_CONFIG is NOT supported"
- fi
- fi
-
- # Check funder and owner
- FUNDER_ADDR=$(cast call $SIMPLE_FUNDER "funder()(address)" --rpc-url $RPC_URL 2>/dev/null || echo "0x0")
- OWNER_ADDR=$(cast call $SIMPLE_FUNDER "owner()(address)" --rpc-url $RPC_URL 2>/dev/null || echo "0x0")
- log_info " Funder: $FUNDER_ADDR"
- log_info " Owner: $OWNER_ADDR"
- echo ""
-done
-
-# ========================================================================
-# FINAL SUMMARY
-# ========================================================================
-
-echo "========================================================================"
-log_info "VERIFICATION COMPLETE"
-echo "========================================================================"
-
-log_info "Summary:"
-log_info " • Contract Deployments: Checked"
-log_info " • LayerZero Configuration: Checked"
-log_info " • Signer Funding: Checked"
-log_info " • Gas Wallet Configuration: Checked"
-echo ""
-log_info "Run 'bash deploy/test_deployment.sh' for full deployment and configuration"
-log_info "This script only verifies the current state without making changes"
\ No newline at end of file
diff --git a/docs/4337CallGraph.md b/docs/4337CallGraph.md
deleted file mode 100644
index ec9e788d..00000000
--- a/docs/4337CallGraph.md
+++ /dev/null
@@ -1,32 +0,0 @@
-sequenceDiagram
- participant User as User
- participant Bundler as Bundler
- participant EntryPoint as EntryPoint
- participant Paymaster as Paymaster
- participant Account as ERC4337 Account
-
- User->>Bundler: Submit signed UserOperation + paymasterAndData
- Bundler->>EntryPoint: handleOps([userOp])
-
- Note over EntryPoint: Validation Phase
- EntryPoint->>Account: validateUserOp(userOp, userOpHash, missingAccountFunds)
- Account-->>EntryPoint: validationData
-
- Note over EntryPoint: Paymaster validation & deposit check
- EntryPoint->>Paymaster: validatePaymasterUserOp(userOp, userOpHash, maxCost)
- Note over Paymaster: Validate user agreed to pay in USDC
- Paymaster-->>EntryPoint: (context, validationData)
-
-
- Note over EntryPoint: Execution Phase
- EntryPoint->>Account: execute(userOp.callData)
-
- Note over EntryPoint: Payment Phase - ETH movement
- EntryPoint-->>Bundler: ETH transfer (actualGasCost)
-
- EntryPoint->>Paymaster: postOp(mode, context, actualGasCost)
- Note over Paymaster: Calculate USDC amount needed
- Account-->>Paymaster: USDC transfer (calculated amount)
-
- EntryPoint-->>Bundler: Execution result
- Bundler-->>User: UserOp result
\ No newline at end of file
diff --git a/docs/IthacaCallGraph.md b/docs/IthacaCallGraph.md
deleted file mode 100644
index 7c35733c..00000000
--- a/docs/IthacaCallGraph.md
+++ /dev/null
@@ -1,25 +0,0 @@
-sequenceDiagram
- participant User as User
- participant Relayer as Relayer
- participant Orch as Orchestrator
- participant Account as IthacaAccount
-
- User->>Relayer: Submit signed intent
- Relayer->>Orch: execute(encodedIntent)
-
- Note over Orch: Verify signature
- Orch->>Account: unwrapAndValidateSignature(digest, signature)
- Account-->>Orch: (isValid, keyHash)
-
- Note over Orch: Increment nonce
- Orch->>Account: checkAndIncrementNonce(nonce)
-
- Note over Orch: Process payment
- Orch->>Account: pay(paymentAmount, keyHash, digest, intent)
- Account-->>Relayer: ERC20 transfer (paymentAmount)
-
- Note over Orch: Execute intent
- Orch->>Account: execute(executionData)
-
- Orch-->>Relayer: Return success/error code
- Relayer-->>User: Execution result
\ No newline at end of file
diff --git a/docs/benchmarks.jpeg b/docs/benchmarks.jpeg
deleted file mode 100644
index 43c3a861..00000000
Binary files a/docs/benchmarks.jpeg and /dev/null differ
diff --git a/foundry.lock b/foundry.lock
index 52f31524..fbc90982 100644
--- a/foundry.lock
+++ b/foundry.lock
@@ -2,11 +2,11 @@
"lib/LayerZero-v2": {
"rev": "88428755be6caa71cb1d2926141d73c8989296b5"
},
+ "lib/devtools": {
+ "rev": "01c1eaeb123e4364fbe96cdddf875640edf2566c"
+ },
"lib/forge-std": {
- "branch": {
- "name": "master",
- "rev": "c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899"
- }
+ "rev": "a3dca253700f19f15b1837c57c67b9388f5cc3fb"
},
"lib/murky": {
"rev": "5feccd1253d7da820f7cccccdedf64471025455d"
@@ -17,7 +17,7 @@
"lib/solady": {
"branch": {
"name": "main",
- "rev": "acd959aa4bd04720d640bf4e6a5c71037510cc4b"
+ "rev": "834bbc4fd366ca8bce8c532a0e3b34eca6be709c"
}
}
}
\ No newline at end of file
diff --git a/foundry.toml b/foundry.toml
index 9bc78c0c..b26c00b5 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -4,15 +4,17 @@
# The Default Profile
[profile.default]
-evm_version = "prague"
+# Extend the deployment configuration from deploy/config.toml
+# This inherits all [forks.*] configurations for multi-chain deployments
+extends = "deploy/config.toml"
+evm_version = "prague" # Cancun will be tested in the CI.
auto_detect_solc = false
-solc_version = "0.8.28"
-bytecode_hash = "none"
-cbor_metadata = false
optimizer = true
optimizer_runs = 200
+gas_limit = 100_000_000 # ETH is 30M, but we use a higher value.
fs_permissions = [
- { access = "read-write", path = "deploy/config.toml" }
+ { access = "read-write", path = "deploy/registry" },
+ { access = "read", path = "deploy/config.toml" }
]
[fmt]
@@ -32,3 +34,5 @@ runs = 10
# mainnet = { key = "${VERIFICATION_KEY_1}" }
# arbitrum = { key = "${VERIFICATION_KEY_42161}" }
# base = { key = "${VERIFICATION_KEY_8453}" }
+
+
diff --git a/gas-snapshots/.gas-snapshot-main b/gas-snapshots/.gas-snapshot-main
new file mode 100644
index 00000000..35524c7b
--- /dev/null
+++ b/gas-snapshots/.gas-snapshot-main
@@ -0,0 +1,11 @@
+BenchmarkTest:testERC20TransferDirect() (gas: 29222)
+BenchmarkTest:testERC20TransferViaERC4337Orchestrator() (gas: 168797)
+BenchmarkTest:testERC20TransferViaERC4337OrchestratorWithExtendedCalldata() (gas: 171723)
+BenchmarkTest:testERC20TransferViaMinimals() (gas: 69717)
+BenchmarkTest:testERC20TransferViaPortoOrchestrator() (gas: 97007)
+BenchmarkTest:testERC20TransferViaPortoOrchestratorWithExtendedCalldata() (gas: 99523)
+BenchmarkTest:testERC20TransferViaPortoOrchestratorWithSpendLimits() (gas: 117113)
+BenchmarkTest:testNativeTransferViaPortoOrchestrator() (gas: 106399)
+BenchmarkTest:testUniswapV2SwapViaERC4337Orchestrator() (gas: 216555)
+BenchmarkTest:testUniswapV2SwapViaPortoOrchestrator() (gas: 144868)
+BenchmarkTest:test__codesize() (gas: 124506)
\ No newline at end of file
diff --git a/lib/LayerZero-v2 b/lib/LayerZero-v2
index 88428755..ab9b0834 160000
--- a/lib/LayerZero-v2
+++ b/lib/LayerZero-v2
@@ -1 +1 @@
-Subproject commit 88428755be6caa71cb1d2926141d73c8989296b5
+Subproject commit ab9b083410b9359285a5756807e1b6145d4711a7
diff --git a/lib/forge-std b/lib/forge-std
index c2cf7017..07853315 160000
--- a/lib/forge-std
+++ b/lib/forge-std
@@ -1 +1 @@
-Subproject commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899
+Subproject commit 07853315f998f94dc724e464b1bab1270888ee64
diff --git a/prep/check-bytecode-changes.js b/prep/check-bytecode-changes.js
old mode 100755
new mode 100644
diff --git a/src/IthacaAccount.sol b/src/IthacaAccount.sol
index 0f0068ac..c15368d6 100644
--- a/src/IthacaAccount.sol
+++ b/src/IthacaAccount.sol
@@ -23,6 +23,7 @@ import {LibNonce} from "./libraries/LibNonce.sol";
import {TokenTransferLib} from "./libraries/TokenTransferLib.sol";
import {LibTStack} from "./libraries/LibTStack.sol";
import {IIthacaAccount} from "./interfaces/IIthacaAccount.sol";
+import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";
/// @title Account
/// @notice A account contract for EOAs with EIP7702.
@@ -485,9 +486,43 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
return isMultichain ? _hashTypedDataSansChainId(structHash) : _hashTypedData(structHash);
}
+ /// @dev Verifies the merkle sig
+ /// - Note: Each leaf of the merkle tree should be a standard digest.
+ /// - The signature for using merkle verification is encoded as:
+ /// - bytes signature = abi.encode(bytes32[] proof, bytes32 root, bytes rootSig)
+ function _verifyMerkleSig(bytes32 digest, bytes calldata signature)
+ internal
+ view
+ returns (bool isValid, bytes32 keyHash)
+ {
+ bytes32[] calldata proof;
+ bytes32 root;
+ bytes calldata rootSig;
+
+ assembly ("memory-safe") {
+ let proofOffset := add(signature.offset, calldataload(signature.offset))
+ proof.length := calldataload(proofOffset)
+ proof.offset := add(proofOffset, 0x20)
+
+ root := calldataload(add(signature.offset, 0x20))
+
+ let rootSigOffset := add(signature.offset, calldataload(add(signature.offset, 0x40)))
+ rootSig.length := calldataload(rootSigOffset)
+ rootSig.offset := add(rootSigOffset, 0x20)
+ }
+
+ if (MerkleProofLib.verifyCalldata(proof, root, digest)) {
+ (isValid, keyHash) = unwrapAndValidateSignature(root, rootSig);
+
+ return (isValid, keyHash);
+ }
+
+ return (false, bytes32(0));
+ }
+
/// @dev Returns if the signature is valid, along with its `keyHash`.
/// The `signature` is a wrapped signature, given by
- /// `abi.encodePacked(bytes(innerSignature), bytes32(keyHash), bool(prehash))`.
+ /// `abi.encode(bytes(innerSignature), bytes32(keyHash), bool(prehash), bool(merkle))`.
function unwrapAndValidateSignature(bytes32 digest, bytes calldata signature)
public
view
@@ -502,14 +537,20 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
return (ECDSA.recoverCalldata(digest, signature) == address(this), 0);
}
+ bool merkle;
unchecked {
- uint256 n = signature.length - 0x21;
+ uint256 n = signature.length - 0x22;
keyHash = LibBytes.loadCalldata(signature, n);
signature = LibBytes.truncatedCalldata(signature, n);
// Do the prehash if last byte is non-zero.
if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) {
digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`.
}
+ merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0;
+ }
+
+ if (merkle) {
+ return _verifyMerkleSig(digest, signature);
}
Key memory key = getKey(keyHash);
@@ -573,8 +614,9 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
// `keccak256(abi.encode(key.keyType, keccak256(key.publicKey)))`.
keyHash = hash(key);
AccountStorage storage $ = _getAccountStorage();
- $.keyStorage[keyHash]
- .set(abi.encodePacked(key.publicKey, key.expiry, key.keyType, key.isSuperAdmin));
+ $.keyStorage[keyHash].set(
+ abi.encodePacked(key.publicKey, key.expiry, key.keyType, key.isSuperAdmin)
+ );
$.keyHashes.add(keyHash);
}
@@ -747,6 +789,6 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
returns (string memory name, string memory version)
{
name = "IthacaAccount";
- version = "0.5.10";
+ version = "0.5.11";
}
}
diff --git a/src/SimpleFunder.sol b/src/SimpleFunder.sol
index e74947fb..959723d1 100644
--- a/src/SimpleFunder.sol
+++ b/src/SimpleFunder.sol
@@ -133,11 +133,10 @@ contract SimpleFunder is EIP712, Ownable, IFunder {
/// @dev Allows the orchestrator to fund an account.
/// The `digest` includes the intent nonce and the transfers.
- function fund(
- bytes32 digest,
- ICommon.Transfer[] memory transfers,
- bytes memory funderSignature
- ) public override {
+ function fund(bytes32 digest, ICommon.Transfer[] memory transfers, bytes memory funderSignature)
+ public
+ override
+ {
if (!orchestrators[msg.sender]) {
revert OnlyOrchestrator();
}
diff --git a/src/interfaces/IFunder.sol b/src/interfaces/IFunder.sol
index 90c2547f..ddbacc0f 100644
--- a/src/interfaces/IFunder.sol
+++ b/src/interfaces/IFunder.sol
@@ -16,9 +16,6 @@ interface IFunderV4 {
interface IFunder is IFunderV4 {
/// @dev Checks if fund transfers are valid given a funderSignature.
/// @dev Funder implementations must revert if the signature is invalid.
- function fund(
- bytes32 digest,
- ICommon.Transfer[] memory transfers,
- bytes memory funderSignature
- ) external;
+ function fund(bytes32 digest, ICommon.Transfer[] memory transfers, bytes memory funderSignature)
+ external;
}
diff --git a/src/interfaces/IMulticall3.sol b/src/interfaces/IMulticall3.sol
deleted file mode 100644
index c0ab86f2..00000000
--- a/src/interfaces/IMulticall3.sol
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.23;
-
-/// @notice Interface for Multicall3 contract
-interface IMulticall3 {
- struct Call {
- address target;
- bytes callData;
- }
-
- struct Call3 {
- address target;
- bool allowFailure;
- bytes callData;
- }
-
- struct Call3Value {
- address target;
- bool allowFailure;
- uint256 value;
- bytes callData;
- }
-
- struct Result {
- bool success;
- bytes returnData;
- }
-
- function aggregate(Call[] calldata calls)
- external
- payable
- returns (uint256 blockNumber, bytes[] memory returnData);
-
- function aggregate3(Call3[] calldata calls)
- external
- payable
- returns (Result[] memory returnData);
-}
diff --git a/src/vendor/layerzero/interfaces/IOAppCore.sol b/src/vendor/layerzero/interfaces/IOAppCore.sol
deleted file mode 100644
index 4a87d7a2..00000000
--- a/src/vendor/layerzero/interfaces/IOAppCore.sol
+++ /dev/null
@@ -1,54 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.20;
-
-import {
- ILayerZeroEndpointV2
-} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
-
-/**
- * @title IOAppCore
- */
-interface IOAppCore {
- // Custom error messages
- error OnlyPeer(uint32 eid, bytes32 sender);
- error NoPeer(uint32 eid);
- error InvalidEndpointCall();
- error InvalidDelegate();
-
- // Event emitted when a peer (OApp) is set for a corresponding endpoint
- event PeerSet(uint32 eid, bytes32 peer);
-
- /**
- * @notice Retrieves the OApp version information.
- * @return senderVersion The version of the OAppSender.sol contract.
- * @return receiverVersion The version of the OAppReceiver.sol contract.
- */
- function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);
-
- /**
- * @notice Retrieves the LayerZero endpoint associated with the OApp.
- * @return iEndpoint The LayerZero endpoint as an interface.
- */
- function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);
-
- /**
- * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.
- * @param _eid The endpoint ID.
- * @return peer The peer address (OApp instance) associated with the corresponding endpoint.
- */
- function peers(uint32 _eid) external view returns (bytes32 peer);
-
- /**
- * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
- * @param _eid The endpoint ID.
- * @param _peer The address of the peer to be associated with the corresponding endpoint.
- */
- function setPeer(uint32 _eid, bytes32 _peer) external;
-
- /**
- * @notice Sets the delegate address for the OApp Core.
- * @param _delegate The address of the delegate to be set.
- */
- function setDelegate(address _delegate) external;
-}
diff --git a/src/vendor/layerzero/interfaces/IOAppMsgInspector.sol b/src/vendor/layerzero/interfaces/IOAppMsgInspector.sol
deleted file mode 100644
index 4d3c82d6..00000000
--- a/src/vendor/layerzero/interfaces/IOAppMsgInspector.sol
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.20;
-
-/**
- * @title IOAppMsgInspector
- * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.
- */
-interface IOAppMsgInspector {
- // Custom error message for inspection failure
- error InspectionFailed(bytes message, bytes options);
-
- /**
- * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.
- * @param _message The message payload to be inspected.
- * @param _options Additional options or parameters for inspection.
- * @return valid A boolean indicating whether the inspection passed (true) or failed (false).
- *
- * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.
- */
- function inspect(bytes calldata _message, bytes calldata _options)
- external
- view
- returns (bool valid);
-}
diff --git a/src/vendor/layerzero/interfaces/IOAppReceiver.sol b/src/vendor/layerzero/interfaces/IOAppReceiver.sol
deleted file mode 100644
index 0fe4317f..00000000
--- a/src/vendor/layerzero/interfaces/IOAppReceiver.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.20;
-
-import {
- ILayerZeroReceiver,
- Origin
-} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol";
-
-interface IOAppReceiver is ILayerZeroReceiver {
- /**
- * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.
- * @param _origin The origin information containing the source endpoint and sender address.
- * - srcEid: The source chain endpoint ID.
- * - sender: The sender address on the src chain.
- * - nonce: The nonce of the message.
- * @param _message The lzReceive payload.
- * @param _sender The sender address.
- * @return isSender Is a valid sender.
- *
- * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.
- * @dev The default sender IS the OAppReceiver implementer.
- */
- function isComposeMsgSender(Origin calldata _origin, bytes calldata _message, address _sender)
- external
- view
- returns (bool isSender);
-}
diff --git a/src/vendor/layerzero/mocks/EndpointV2Mock.sol b/src/vendor/layerzero/mocks/EndpointV2Mock.sol
deleted file mode 100644
index 40042d7d..00000000
--- a/src/vendor/layerzero/mocks/EndpointV2Mock.sol
+++ /dev/null
@@ -1,418 +0,0 @@
-// SPDX-License-Identifier: LZBL-1.2
-
-pragma solidity ^0.8.20;
-
-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-// @dev oz4/5 breaking change... Ownable constructor
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-
-import {
- MessagingFee,
- MessagingParams,
- MessagingReceipt,
- Origin,
- ILayerZeroEndpointV2
-} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
-import {
- ISendLib,
- Packet
-} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol";
-import {
- ILayerZeroReceiver
-} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol";
-import {Errors} from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Errors.sol";
-import {GUID} from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol";
-import {Transfer} from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol";
-import {MessagingChannel} from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessagingChannel.sol";
-import {MessagingComposer} from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessagingComposer.sol";
-import {MessageLibManager} from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessageLibManager.sol";
-import {MessagingContext} from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessagingContext.sol";
-
-// LayerZero EndpointV2 is fully backward compatible with LayerZero Endpoint(V1), but it also supports additional
-// features that Endpoint(V1) does not support now and may not in the future. We have also changed some terminology
-// to clarify pre-existing language that might have been confusing.
-//
-// The following is a list of terminology changes:
-// -chainId -> eid
-// - Rationale: chainId was a term we initially used to describe an endpoint on a specific chain. Since
-// LayerZero supports non-EVMs we could not map the classic EVM chainIds to the LayerZero chainIds, making it
-// confusing for developers. With the addition of EndpointV2 and its backward compatible nature, we would have
-// two chainIds per chain that has Endpoint(V1), further confusing developers. We have decided to change the
-// name to Endpoint Id, or eid, for simplicity and clarity.
-// -adapterParams -> options
-// -userApplication -> oapp. Omnichain Application
-// -srcAddress -> sender
-// -dstAddress -> receiver
-// - Rationale: The sender/receiver on EVM is the address. However, on non-EVM chains, the sender/receiver could
-// represented as a public key, or some other identifier. The term sender/receiver is more generic
-// -payload -> message.
-// - Rationale: The term payload is used in the context of a packet, which is a combination of the message and GUID
-contract EndpointV2Mock is
- ILayerZeroEndpointV2,
- MessagingChannel,
- MessageLibManager,
- MessagingComposer,
- MessagingContext
-{
- address public lzToken;
-
- mapping(address oapp => address delegate) public delegates;
-
- /// @param _eid the unique Endpoint Id for this deploy that all other Endpoints can use to send to it
- // @dev oz4/5 breaking change... Ownable constructor
- constructor(uint32 _eid, address _owner) Ownable(_owner) MessagingChannel(_eid) {}
-
- /// @dev MESSAGING STEP 0
- /// @notice This view function gives the application built on top of LayerZero the ability to requests a quote
- /// with the same parameters as they would to send their message. Since the quotes are given on chain there is a
- /// race condition in which the prices could change between the time the user gets their quote and the time they
- /// submit their message. If the price moves up and the user doesn't send enough funds the transaction will revert,
- /// if the price goes down the _refundAddress provided by the app will be refunded the difference.
- /// @param _params the messaging parameters
- /// @param _sender the sender of the message
- function quote(MessagingParams calldata _params, address _sender)
- external
- view
- returns (MessagingFee memory)
- {
- // lzToken must be set to support payInLzToken
- if (_params.payInLzToken && lzToken == address(0x0)) revert Errors.LZ_LzTokenUnavailable();
-
- // get the correct outbound nonce
- uint64 nonce = outboundNonce[_sender][_params.dstEid][_params.receiver] + 1;
-
- // construct the packet with a GUID
- Packet memory packet = Packet({
- nonce: nonce,
- srcEid: eid,
- sender: _sender,
- dstEid: _params.dstEid,
- receiver: _params.receiver,
- guid: GUID.generate(nonce, eid, _sender, _params.dstEid, _params.receiver),
- message: _params.message
- });
-
- // get the send library by sender and dst eid
- // use _ to avoid variable shadowing
- address _sendLibrary = getSendLibrary(_sender, _params.dstEid);
-
- return ISendLib(_sendLibrary).quote(packet, _params.options, _params.payInLzToken);
- }
-
- /// @dev MESSAGING STEP 1 - OApp need to transfer the fees to the endpoint before sending the message
- /// @param _params the messaging parameters
- /// @param _refundAddress the address to refund both the native and lzToken
- function send(MessagingParams calldata _params, address _refundAddress)
- external
- payable
- sendContext(_params.dstEid, msg.sender)
- returns (MessagingReceipt memory)
- {
- if (_params.payInLzToken && lzToken == address(0x0)) {
- revert Errors.LZ_LzTokenUnavailable();
- }
-
- // send message
- (MessagingReceipt memory receipt, address _sendLibrary) = _send(msg.sender, _params);
-
- // OApp can simulate with 0 native value it will fail with error including the required fee, which can be provided in the actual call
- // this trick can be used to avoid the need to write the quote() function
- // however, without the quote view function it will be hard to compose an oapp on chain
- uint256 suppliedNative = _suppliedNative();
- uint256 suppliedLzToken = _suppliedLzToken(_params.payInLzToken);
- _assertMessagingFee(receipt.fee, suppliedNative, suppliedLzToken);
-
- // handle lz token fees
- _payToken(lzToken, receipt.fee.lzTokenFee, suppliedLzToken, _sendLibrary, _refundAddress);
-
- // handle native fees
- _payNative(receipt.fee.nativeFee, suppliedNative, _sendLibrary, _refundAddress);
-
- return receipt;
- }
-
- /// @dev internal function for sending the messages used by all external send methods
- /// @param _sender the address of the application sending the message to the destination chain
- /// @param _params the messaging parameters
- function _send(address _sender, MessagingParams calldata _params)
- internal
- returns (MessagingReceipt memory, address)
- {
- // get the correct outbound nonce
- uint64 latestNonce = _outbound(_sender, _params.dstEid, _params.receiver);
-
- // construct the packet with a GUID
- Packet memory packet = Packet({
- nonce: latestNonce,
- srcEid: eid,
- sender: _sender,
- dstEid: _params.dstEid,
- receiver: _params.receiver,
- guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver),
- message: _params.message
- });
-
- // get the send library by sender and dst eid
- address _sendLibrary = getSendLibrary(_sender, _params.dstEid);
-
- // messageLib always returns encodedPacket with guid
- (MessagingFee memory fee, bytes memory encodedPacket) =
- ISendLib(_sendLibrary).send(packet, _params.options, _params.payInLzToken);
-
- // Emit packet information for DVNs, Executors, and any other offchain infrastructure to only listen
- // for this one event to perform their actions.
- emit PacketSent(encodedPacket, _params.options, _sendLibrary);
-
- return (MessagingReceipt(packet.guid, latestNonce, fee), _sendLibrary);
- }
-
- /// @dev MESSAGING STEP 2 - on the destination chain
- /// @dev configured receive library verifies a message
- /// @param _origin a struct holding the srcEid, nonce, and sender of the message
- /// @param _receiver the receiver of the message
- /// @param _payloadHash the payload hash of the message
- function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external {
- if (!isValidReceiveLibrary(_receiver, _origin.srcEid, msg.sender)) {
- revert Errors.LZ_InvalidReceiveLibrary();
- }
-
- uint64 lazyNonce = lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender];
- if (!_initializable(_origin, _receiver, lazyNonce)) {
- revert Errors.LZ_PathNotInitializable();
- }
- if (!_verifiable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotVerifiable();
-
- // insert the message into the message channel
- _inbound(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, _payloadHash);
- emit PacketVerified(_origin, _receiver, _payloadHash);
- }
-
- /// @dev MESSAGING STEP 3 - the last step
- /// @dev execute a verified message to the designated receiver
- /// @dev the execution provides the execution context (caller, extraData) to the receiver. the receiver can optionally assert the caller and validate the untrusted extraData
- /// @dev cant reentrant because the payload is cleared before execution
- /// @param _origin the origin of the message
- /// @param _receiver the receiver of the message
- /// @param _guid the guid of the message
- /// @param _message the message
- /// @param _extraData the extra data provided by the executor. this data is untrusted and should be validated.
- function lzReceive(
- Origin calldata _origin,
- address _receiver,
- bytes32 _guid,
- bytes calldata _message,
- bytes calldata _extraData
- ) external payable {
- // clear the payload first to prevent reentrancy, and then execute the message
- _clearPayload(
- _receiver,
- _origin.srcEid,
- _origin.sender,
- _origin.nonce,
- abi.encodePacked(_guid, _message)
- );
- ILayerZeroReceiver(_receiver)
- .lzReceive{value: msg.value}(_origin, _guid, _message, msg.sender, _extraData);
- emit PacketDelivered(_origin, _receiver);
- }
-
- /// @param _origin the origin of the message
- /// @param _receiver the receiver of the message
- /// @param _guid the guid of the message
- /// @param _message the message
- /// @param _extraData the extra data provided by the executor.
- /// @param _reason the reason for failure
- function lzReceiveAlert(
- Origin calldata _origin,
- address _receiver,
- bytes32 _guid,
- uint256 _gas,
- uint256 _value,
- bytes calldata _message,
- bytes calldata _extraData,
- bytes calldata _reason
- ) external {
- emit LzReceiveAlert(
- _receiver, msg.sender, _origin, _guid, _gas, _value, _message, _extraData, _reason
- );
- }
-
- /// @dev Oapp uses this interface to clear a message.
- /// @dev this is a PULL mode versus the PUSH mode of lzReceive
- /// @dev the cleared message can be ignored by the app (effectively burnt)
- /// @dev authenticated by oapp
- /// @param _origin the origin of the message
- /// @param _guid the guid of the message
- /// @param _message the message
- function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message)
- external
- {
- _assertAuthorized(_oapp);
-
- bytes memory payload = abi.encodePacked(_guid, _message);
- _clearPayload(_oapp, _origin.srcEid, _origin.sender, _origin.nonce, payload);
- emit PacketDelivered(_origin, _oapp);
- }
-
- /// @dev allows reconfiguration to recover from wrong configurations
- /// @dev users should never approve the EndpointV2 contract to spend their non-layerzero tokens
- /// @dev override this function if the endpoint is charging ERC20 tokens as native
- /// @dev only owner
- /// @param _lzToken the new layer zero token address
- function setLzToken(address _lzToken) public virtual onlyOwner {
- lzToken = _lzToken;
- emit LzTokenSet(_lzToken);
- }
-
- /// @dev recover the token sent to this contract by mistake
- /// @dev only owner
- /// @param _token the token to recover. if 0x0 then it is native token
- /// @param _to the address to send the token to
- /// @param _amount the amount to send
- function recoverToken(address _token, address _to, uint256 _amount) external onlyOwner {
- Transfer.nativeOrToken(_token, _to, _amount);
- }
-
- /// @dev handling token payments on endpoint. the sender must approve the endpoint to spend the token
- /// @dev internal function
- /// @param _token the token to pay
- /// @param _required the amount required
- /// @param _supplied the amount supplied
- /// @param _receiver the receiver of the token
- function _payToken(
- address _token,
- uint256 _required,
- uint256 _supplied,
- address _receiver,
- address _refundAddress
- ) internal {
- if (_required > 0) {
- Transfer.token(_token, _receiver, _required);
- }
- if (_required < _supplied) {
- unchecked {
- // refund the excess
- Transfer.token(_token, _refundAddress, _supplied - _required);
- }
- }
- }
-
- /// @dev handling native token payments on endpoint
- /// @dev override this if the endpoint is charging ERC20 tokens as native
- /// @dev internal function
- /// @param _required the amount required
- /// @param _supplied the amount supplied
- /// @param _receiver the receiver of the native token
- /// @param _refundAddress the address to refund the excess to
- function _payNative(
- uint256 _required,
- uint256 _supplied,
- address _receiver,
- address _refundAddress
- ) internal virtual {
- if (_required > 0) {
- Transfer.native(_receiver, _required);
- }
- if (_required < _supplied) {
- unchecked {
- // refund the excess
- Transfer.native(_refundAddress, _supplied - _required);
- }
- }
- }
-
- /// @dev get the balance of the lzToken as the supplied lzToken fee if payInLzToken is true
- function _suppliedLzToken(bool _payInLzToken) internal view returns (uint256 supplied) {
- if (_payInLzToken) {
- supplied = IERC20(lzToken).balanceOf(address(this));
-
- // if payInLzToken is true, the supplied fee must be greater than 0 to prevent a race condition
- // in which an oapp sending a message with lz token and the lz token is set to a new token between the tx
- // being sent and the tx being mined. if the required lz token fee is 0 and the old lz token would be
- // locked in the contract instead of being refunded
- if (supplied == 0) revert Errors.LZ_ZeroLzTokenFee();
- }
- }
-
- /// @dev override this if the endpoint is charging ERC20 tokens as native
- function _suppliedNative() internal view virtual returns (uint256) {
- return msg.value;
- }
-
- /// @dev Assert the required fees and the supplied fees are enough
- function _assertMessagingFee(
- MessagingFee memory _required,
- uint256 _suppliedNativeFee,
- uint256 _suppliedLzTokenFee
- ) internal pure {
- if (_required.nativeFee > _suppliedNativeFee || _required.lzTokenFee > _suppliedLzTokenFee)
- {
- revert Errors.LZ_InsufficientFee(
- _required.nativeFee, _suppliedNativeFee, _required.lzTokenFee, _suppliedLzTokenFee
- );
- }
- }
-
- /// @dev override this if the endpoint is charging ERC20 tokens as native
- /// @return 0x0 if using native. otherwise the address of the native ERC20 token
- function nativeToken() external view virtual returns (address) {
- return address(0x0);
- }
-
- /// @notice delegate is authorized by the oapp to configure anything in layerzero
- function setDelegate(address _delegate) external {
- delegates[msg.sender] = _delegate;
- emit DelegateSet(msg.sender, _delegate);
- }
-
- // ========================= Internal =========================
- function _initializable(Origin calldata _origin, address _receiver, uint64 _lazyInboundNonce)
- internal
- view
- returns (bool)
- {
- return _lazyInboundNonce > 0 // allowInitializePath already checked
- || ILayerZeroReceiver(_receiver).allowInitializePath(_origin);
- }
-
- /// @dev bytes(0) payloadHash can never be submitted
- function _verifiable(Origin calldata _origin, address _receiver, uint64 _lazyInboundNonce)
- internal
- view
- returns (bool)
- {
- return _origin.nonce > _lazyInboundNonce // either initializing an empty slot or reverifying
- || inboundPayloadHash[_receiver][_origin.srcEid][_origin.sender][_origin.nonce]
- != EMPTY_PAYLOAD_HASH; // only allow reverifying if it hasn't been executed
- }
-
- /// @dev assert the caller to either be the oapp or the delegate
- function _assertAuthorized(address _oapp)
- internal
- view
- override(MessagingChannel, MessageLibManager)
- {
- if (msg.sender != _oapp && msg.sender != delegates[_oapp]) {
- revert Errors.LZ_Unauthorized();
- }
- }
-
- // ========================= VIEW FUNCTIONS FOR OFFCHAIN ONLY =========================
- // Not involved in any state transition function.
- // ====================================================================================
- function initializable(Origin calldata _origin, address _receiver)
- external
- view
- returns (bool)
- {
- return _initializable(
- _origin, _receiver, lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender]
- );
- }
-
- function verifiable(Origin calldata _origin, address _receiver) external view returns (bool) {
- return _verifiable(
- _origin, _receiver, lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender]
- );
- }
-}
diff --git a/src/vendor/layerzero/oapp/OApp.sol b/src/vendor/layerzero/oapp/OApp.sol
deleted file mode 100644
index a4d9576a..00000000
--- a/src/vendor/layerzero/oapp/OApp.sol
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.20;
-
-// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
-// solhint-disable-next-line no-unused-import
-import {OAppSender, MessagingFee, MessagingReceipt} from "./OAppSender.sol";
-// @dev Import the 'Origin' so it's exposed to OApp implementers
-// solhint-disable-next-line no-unused-import
-import {OAppReceiver, Origin} from "./OAppReceiver.sol";
-import {OAppCore} from "./OAppCore.sol";
-
-/**
- * @title OApp
- * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
- */
-abstract contract OApp is OAppSender, OAppReceiver {
- /**
- * @dev Constructor to initialize the OApp with the provided owner.
- * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
- */
- constructor(address _delegate) OAppCore(_delegate) {}
-
- /**
- * @notice Retrieves the OApp version information.
- * @return senderVersion The version of the OAppSender.sol implementation.
- * @return receiverVersion The version of the OAppReceiver.sol implementation.
- */
- function oAppVersion()
- public
- pure
- virtual
- override(OAppSender, OAppReceiver)
- returns (uint64 senderVersion, uint64 receiverVersion)
- {
- return (SENDER_VERSION, RECEIVER_VERSION);
- }
-}
diff --git a/src/vendor/layerzero/oapp/OAppCore.sol b/src/vendor/layerzero/oapp/OAppCore.sol
deleted file mode 100644
index 3eff1be5..00000000
--- a/src/vendor/layerzero/oapp/OAppCore.sol
+++ /dev/null
@@ -1,104 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.20;
-
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-import {IOAppCore, ILayerZeroEndpointV2} from "../interfaces/IOAppCore.sol";
-
-/**
- * @title OAppCore
- * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.
- */
-abstract contract OAppCore is IOAppCore, Ownable {
- // The LayerZero endpoint associated with the given OApp
- ILayerZeroEndpointV2 public endpoint;
-
- // Mapping to store peers associated with corresponding endpoints
- mapping(uint32 eid => bytes32 peer) internal _peers;
-
- /**
- * @dev Constructor to initialize the OAppCore with the provided delegate.
- * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
- *
- * @dev The delegate typically should be set as the owner of the contract.
- */
- constructor(address _delegate) {
- if (_delegate == address(0)) revert InvalidDelegate();
- _transferOwnership(_delegate);
- }
-
- /**
- * @notice Sets the LayerZero endpoint for this OApp.
- * @param _endpoint The address of the LayerZero endpoint.
- * @dev Can only be called by the owner.
- * @dev Should be called immediately after deployment.
- * Delegate is set to the owner of the OAppCore contract.
- */
- function setEndpoint(address _endpoint) external onlyOwner {
- if (_endpoint == address(0)) revert InvalidDelegate();
- endpoint = ILayerZeroEndpointV2(_endpoint);
- endpoint.setDelegate(msg.sender);
- }
-
- /**
- * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
- * @param _eid The endpoint ID.
- * @param _peer The address of the peer to be associated with the corresponding endpoint.
- *
- * @dev Only the owner/admin of the OApp can call this function.
- * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
- * @dev Set this to bytes32(0) to remove the peer address.
- * @dev Peer is a bytes32 to accommodate non-evm chains.
- */
- function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
- _setPeer(_eid, _peer);
- }
-
- /**
- * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
- * @param _eid The endpoint ID.
- * @param _peer The address of the peer to be associated with the corresponding endpoint.
- *
- * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
- * @dev Set this to bytes32(0) to remove the peer address.
- * @dev Peer is a bytes32 to accommodate non-evm chains.
- */
- function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {
- _peers[_eid] = _peer;
- emit PeerSet(_eid, _peer);
- }
-
- /**
- * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.
- * ie. the peer is set to bytes32(0).
- * @param _eid The endpoint ID.
- * @return peer The address of the peer associated with the specified endpoint.
- */
- function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
- bytes32 peer = peers(_eid);
- if (peer == bytes32(0)) revert NoPeer(_eid);
- return peer;
- }
-
- /**
- * @notice Retrieves the peer (OApp instance) for a corresponding endpoint.
- * @param _eid The endpoint ID.
- * @return peer The address of the peer associated with the specified endpoint.
- * @dev This function is virtual to allow overriding in derived contracts.
- */
- function peers(uint32 _eid) public view virtual returns (bytes32 peer) {
- return _peers[_eid];
- }
-
- /**
- * @notice Sets the delegate address for the OApp.
- * @param _delegate The address of the delegate to be set.
- *
- * @dev Only the owner/admin of the OApp can call this function.
- * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.
- */
- function setDelegate(address _delegate) public onlyOwner {
- if (address(endpoint) == address(0)) revert InvalidDelegate();
- endpoint.setDelegate(_delegate);
- }
-}
diff --git a/src/vendor/layerzero/oapp/OAppReceiver.sol b/src/vendor/layerzero/oapp/OAppReceiver.sol
deleted file mode 100644
index d3c34ad0..00000000
--- a/src/vendor/layerzero/oapp/OAppReceiver.sol
+++ /dev/null
@@ -1,143 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.20;
-
-import {IOAppReceiver, Origin} from "../interfaces/IOAppReceiver.sol";
-import {OAppCore} from "./OAppCore.sol";
-
-/**
- * @title OAppReceiver
- * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.
- */
-abstract contract OAppReceiver is IOAppReceiver, OAppCore {
- // Custom error message for when the caller is not the registered endpoint/
- error OnlyEndpoint(address addr);
-
- // @dev The version of the OAppReceiver implementation.
- // @dev Version is bumped when changes are made to this contract.
- uint64 internal constant RECEIVER_VERSION = 2;
-
- /**
- * @notice Retrieves the OApp version information.
- * @return senderVersion The version of the OAppSender.sol contract.
- * @return receiverVersion The version of the OAppReceiver.sol contract.
- *
- * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.
- * ie. this is a RECEIVE only OApp.
- * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.
- */
- function oAppVersion()
- public
- view
- virtual
- returns (uint64 senderVersion, uint64 receiverVersion)
- {
- return (0, RECEIVER_VERSION);
- }
-
- /**
- * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.
- * @dev _origin The origin information containing the source endpoint and sender address.
- * - srcEid: The source chain endpoint ID.
- * - sender: The sender address on the src chain.
- * - nonce: The nonce of the message.
- * @dev _message The lzReceive payload.
- * @param _sender The sender address.
- * @return isSender Is a valid sender.
- *
- * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.
- * @dev The default sender IS the OAppReceiver implementer.
- */
- function isComposeMsgSender(
- Origin calldata, /*_origin*/
- bytes calldata, /*_message*/
- address _sender
- )
- public
- view
- virtual
- returns (bool)
- {
- return _sender == address(this);
- }
-
- /**
- * @notice Checks if the path initialization is allowed based on the provided origin.
- * @param origin The origin information containing the source endpoint and sender address.
- * @return Whether the path has been initialized.
- *
- * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.
- * @dev This defaults to assuming if a peer has been set, its initialized.
- * Can be overridden by the OApp if there is other logic to determine this.
- */
- function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {
- return peers(origin.srcEid) == origin.sender;
- }
-
- /**
- * @notice Retrieves the next nonce for a given source endpoint and sender address.
- * @dev _srcEid The source endpoint ID.
- * @dev _sender The sender address.
- * @return nonce The next nonce.
- *
- * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.
- * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.
- * @dev This is also enforced by the OApp.
- * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.
- */
- function nextNonce(
- uint32,
- /*_srcEid*/
- bytes32 /*_sender*/
- )
- public
- view
- virtual
- returns (uint64 nonce)
- {
- return 0;
- }
-
- /**
- * @dev Entry point for receiving messages or packets from the endpoint.
- * @param _origin The origin information containing the source endpoint and sender address.
- * - srcEid: The source chain endpoint ID.
- * - sender: The sender address on the src chain.
- * - nonce: The nonce of the message.
- * @param _guid The unique identifier for the received LayerZero message.
- * @param _message The payload of the received message.
- * @param _executor The address of the executor for the received message.
- * @param _extraData Additional arbitrary data provided by the corresponding executor.
- *
- * @dev Entry point for receiving msg/packet from the LayerZero endpoint.
- */
- function lzReceive(
- Origin calldata _origin,
- bytes32 _guid,
- bytes calldata _message,
- address _executor,
- bytes calldata _extraData
- ) public payable virtual {
- // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
- if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);
-
- // Ensure that the sender matches the expected peer for the source endpoint.
- if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) {
- revert OnlyPeer(_origin.srcEid, _origin.sender);
- }
-
- // Call the internal OApp implementation of lzReceive.
- _lzReceive(_origin, _guid, _message, _executor, _extraData);
- }
-
- /**
- * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.
- */
- function _lzReceive(
- Origin calldata _origin,
- bytes32 _guid,
- bytes calldata _message,
- address _executor,
- bytes calldata _extraData
- ) internal virtual;
-}
diff --git a/src/vendor/layerzero/oapp/OAppSender.sol b/src/vendor/layerzero/oapp/OAppSender.sol
deleted file mode 100644
index 6fdf8ef4..00000000
--- a/src/vendor/layerzero/oapp/OAppSender.sol
+++ /dev/null
@@ -1,136 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.20;
-
-import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-import {
- MessagingParams,
- MessagingFee,
- MessagingReceipt
-} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
-import {OAppCore} from "./OAppCore.sol";
-
-/**
- * @title OAppSender
- * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.
- */
-abstract contract OAppSender is OAppCore {
- using SafeERC20 for IERC20;
-
- // Custom error messages
- error NotEnoughNative(uint256 msgValue);
- error LzTokenUnavailable();
-
- // @dev The version of the OAppSender implementation.
- // @dev Version is bumped when changes are made to this contract.
- uint64 internal constant SENDER_VERSION = 1;
-
- /**
- * @notice Retrieves the OApp version information.
- * @return senderVersion The version of the OAppSender.sol contract.
- * @return receiverVersion The version of the OAppReceiver.sol contract.
- *
- * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.
- * ie. this is a SEND only OApp.
- * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions
- */
- function oAppVersion()
- public
- view
- virtual
- returns (uint64 senderVersion, uint64 receiverVersion)
- {
- return (SENDER_VERSION, 0);
- }
-
- /**
- * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.
- * @param _dstEid The destination endpoint ID.
- * @param _message The message payload.
- * @param _options Additional options for the message.
- * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.
- * @return fee The calculated MessagingFee for the message.
- * - nativeFee: The native fee for the message.
- * - lzTokenFee: The LZ token fee for the message.
- */
- function _quote(
- uint32 _dstEid,
- bytes memory _message,
- bytes memory _options,
- bool _payInLzToken
- ) internal view virtual returns (MessagingFee memory fee) {
- return endpoint.quote(
- MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
- address(this)
- );
- }
-
- /**
- * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.
- * @param _dstEid The destination endpoint ID.
- * @param _message The message payload.
- * @param _options Additional options for the message.
- * @param _fee The calculated LayerZero fee for the message.
- * - nativeFee: The native fee.
- * - lzTokenFee: The lzToken fee.
- * @param _refundAddress The address to receive any excess fee values sent to the endpoint.
- * @return receipt The receipt for the sent message.
- * - guid: The unique identifier for the sent message.
- * - nonce: The nonce of the sent message.
- * - fee: The LayerZero fee incurred for the message.
- */
- function _lzSend(
- uint32 _dstEid,
- bytes memory _message,
- bytes memory _options,
- MessagingFee memory _fee,
- address _refundAddress
- ) internal virtual returns (MessagingReceipt memory receipt) {
- // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
- uint256 messageValue = _payNative(_fee.nativeFee);
- if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);
-
- return endpoint.
- // solhint-disable-next-line check-send-result
- send{
- value: messageValue
- }(
- MessagingParams(
- _dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0
- ),
- _refundAddress
- );
- }
-
- /**
- * @dev Internal function to pay the native fee associated with the message.
- * @param _nativeFee The native fee to be paid.
- * @return nativeFee The amount of native currency paid.
- *
- * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,
- * this will need to be overridden because msg.value would contain multiple lzFees.
- * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.
- * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.
- * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.
- */
- function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
- if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);
- return _nativeFee;
- }
-
- /**
- * @dev Internal function to pay the LZ token fee associated with the message.
- * @param _lzTokenFee The LZ token fee to be paid.
- *
- * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.
- * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().
- */
- function _payLzToken(uint256 _lzTokenFee) internal virtual {
- // @dev Cannot cache the token because it is not immutable in the endpoint.
- address lzToken = endpoint.lzToken();
- if (lzToken == address(0)) revert LzTokenUnavailable();
-
- // Pay LZ token fee by sending tokens to the endpoint.
- IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
- }
-}
diff --git a/test/Account.t.sol b/test/Account.t.sol
index d612ef5a..083e6b3a 100644
--- a/test/Account.t.sol
+++ b/test/Account.t.sol
@@ -6,6 +6,8 @@ import "./Base.t.sol";
import {MockSampleDelegateCallTarget} from "./utils/mocks/MockSampleDelegateCallTarget.sol";
import {LibEIP7702} from "solady/accounts/LibEIP7702.sol";
+import {Merkle} from "murky/Merkle.sol";
+
contract AccountTest is BaseTest {
struct _TestExecuteWithSignatureTemps {
TargetFunctionPayload[] targetFunctionPayloads;
@@ -68,6 +70,70 @@ contract AccountTest is BaseTest {
}
}
+ function testMerkleSignature(uint256 seed) public {
+ DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
+ PassKey memory k = _randomSecp256k1PassKey();
+ k.k.isSuperAdmin = true;
+
+ vm.prank(d.eoa);
+ d.d.authorize(k.k);
+
+ // Fuzz number of leaves (2 to 256)
+ uint256 numLeaves = bound(seed, 2, 256);
+ bytes32[] memory leaves = new bytes32[](numLeaves);
+
+ // Generate random leaves
+ for (uint256 i = 0; i < numLeaves; i++) {
+ leaves[i] = keccak256(abi.encodePacked("leaf", i, seed));
+ }
+
+ // Pick a random valid index
+ uint256 validIndex = seed % numLeaves;
+ bytes32 validDigest = leaves[validIndex];
+
+ Merkle merkle = new Merkle();
+ bytes32 root = merkle.getRoot(leaves);
+ bytes32[] memory proof = merkle.getProof(leaves, validIndex);
+
+ // Test valid merkle proof
+ {
+ bytes memory rootSig = abi.encode(proof, root, _sig(k, root));
+ bytes memory sig = abi.encodePacked(rootSig, bytes32(0), uint8(0), uint8(1));
+
+ (bool isValid, bytes32 keyHash) = d.d.unwrapAndValidateSignature(validDigest, sig);
+ assertEq(isValid, true);
+ assertEq(keyHash, k.keyHash);
+
+ // Test invalid digest not in tree
+ bytes32 invalidDigest = keccak256(abi.encodePacked("not_in_tree", seed));
+ (isValid, keyHash) = d.d.unwrapAndValidateSignature(invalidDigest, sig);
+ assertEq(isValid, false);
+ assertEq(keyHash, bytes32(0));
+ }
+
+ // Test tampered proof (only if proof has elements to tamper)
+ if (proof.length > 0) {
+ bytes32[] memory tamperedProof = new bytes32[](proof.length);
+ for (uint256 i = 0; i < proof.length; i++) {
+ tamperedProof[i] = i == 0 ? bytes32(uint256(proof[i]) ^ 1) : proof[i];
+ }
+ bytes memory tamperedRootSig = abi.encode(tamperedProof, root, _sig(k, root));
+ bytes memory tamperedSig =
+ abi.encodePacked(tamperedRootSig, bytes32(0), uint8(0), uint8(1));
+ (bool isValid,) = d.d.unwrapAndValidateSignature(validDigest, tamperedSig);
+ assertEq(isValid, false);
+ }
+
+ // Test wrong root (tampered root should fail verification)
+ {
+ bytes32 wrongRoot = bytes32(uint256(root) ^ 1);
+ bytes memory wrongRootSig = abi.encode(proof, wrongRoot, _sig(k, wrongRoot));
+ bytes memory wrongSig = abi.encodePacked(wrongRootSig, bytes32(0), uint8(0), uint8(1));
+ (bool isValid,) = d.d.unwrapAndValidateSignature(validDigest, wrongSig);
+ assertEq(isValid, false);
+ }
+ }
+
function testSignatureCheckerApproval(bytes32) public {
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
PassKey memory k = _randomSecp256k1PassKey();
@@ -93,8 +159,7 @@ contract AccountTest is BaseTest {
bytes32 replaySafeDigest = keccak256(abi.encode(d.d.SIGN_TYPEHASH(), digest));
- (, string memory name, string memory version,, address verifyingContract,,) =
- d.d.eip712Domain();
+ (,,,, address verifyingContract,,) = d.d.eip712Domain();
bytes32 domain = keccak256(
abi.encode(
0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749, // DOMAIN_TYPEHASH with only verifyingContract
@@ -397,6 +462,10 @@ contract AccountTest is BaseTest {
vm.prank(address(d.d));
d.d.execute(_ERC7821_BATCH_EXECUTION_MODE, executionData);
- assertEq(contextKeyHash, bytes32(0), "Context key hash should be zero for self-execution without opData");
+ assertEq(
+ contextKeyHash,
+ bytes32(0),
+ "Context key hash should be zero for self-execution without opData"
+ );
}
}
diff --git a/test/Base.t.sol b/test/Base.t.sol
index 94749645..87e82660 100644
--- a/test/Base.t.sol
+++ b/test/Base.t.sol
@@ -220,7 +220,7 @@ contract BaseTest is SoladyTest {
{
(bytes32 r, bytes32 s) = vm.signP256(privateKey, digest);
s = P256.normalized(s);
- return abi.encodePacked(abi.encode(r, s), keyHash, uint8(prehash ? 1 : 0));
+ return abi.encodePacked(abi.encode(r, s), keyHash, uint8(prehash ? 1 : 0), uint8(0));
}
function _secp256k1Sig(uint256 privateKey, bytes32 keyHash, bytes32 digest)
@@ -237,7 +237,8 @@ contract BaseTest is SoladyTest {
returns (bytes memory)
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
- return abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(prehash ? 1 : 0));
+ return
+ abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(prehash ? 1 : 0), uint8(0));
}
function _multiSig(MultiSigKey memory k, bytes32 keyHash, bool preHash, bytes32 digest)
@@ -250,7 +251,7 @@ contract BaseTest is SoladyTest {
signatures[i] = _sig(k.owners[i], digest);
}
- return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0));
+ return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0), uint8(0));
}
function _estimateGasForEOAKey(Orchestrator.Intent memory i)
@@ -282,7 +283,7 @@ contract BaseTest is SoladyTest {
{
(uint8 v, bytes32 r, bytes32 s) =
vm.sign(uint128(_randomUniform()), bytes32(_randomUniform()));
- i.signature = abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(0));
+ i.signature = abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(0), uint8(0));
return _estimateGas(i);
}
@@ -290,7 +291,7 @@ contract BaseTest is SoladyTest {
internal
returns (uint256 gExecute, uint256 gCombined, uint256 gUsed)
{
- i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), keyHash, uint8(0));
+ i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), keyHash, uint8(0), uint8(0));
return _estimateGas(i);
}
diff --git a/test/Benchmark.t.sol b/test/Benchmark.t.sol
index 2ce04524..b2464a8e 100644
--- a/test/Benchmark.t.sol
+++ b/test/Benchmark.t.sol
@@ -120,8 +120,9 @@ contract BenchmarkTest is BaseTest {
token0, token1, 1 ether, 1 ether, 1, 1, address(this), block.timestamp + 999
);
- IStakeManager(_ERC4337_ENTRYPOINT_V06_ADDR)
- .depositTo{value: 1 ether}(_PIMLICO_PAYMASTER_V06);
+ IStakeManager(_ERC4337_ENTRYPOINT_V06_ADDR).depositTo{value: 1 ether}(
+ _PIMLICO_PAYMASTER_V06
+ );
IStakeManager(_ERC4337_ENTRYPOINT_ADDR).depositTo{value: 1 ether}(_PIMLICO_PAYMASTER_V07);
(paymasterSigner, paymasterPrivateKey) = makeAddrAndKey("");
@@ -1805,6 +1806,52 @@ contract BenchmarkTest is BaseTest {
assertEq(paymentToken.balanceOf(address(0xbabe)), 1 ether);
}
+ function testERC20TransferViaPortoOrchestratorWithPasskey() public {
+ vm.pauseGasMetering();
+
+ PassKey memory k = _randomSecp256k1PassKey();
+
+ DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
+ vm.deal(d.eoa, type(uint128).max);
+ _mint(address(paymentToken), d.eoa, type(uint128).max);
+
+ vm.startPrank(d.eoa);
+ d.d.authorize(k.k);
+ d.d.setCanExecute(
+ k.keyHash, address(paymentToken), bytes4(keccak256("transfer(address,uint256)")), true
+ );
+ d.d.setSpendLimit(
+ k.keyHash, address(paymentToken), GuardedExecutor.SpendPeriod.Hour, type(uint256).max
+ );
+ d.d.setSpendLimit(k.keyHash, address(0), GuardedExecutor.SpendPeriod.Hour, type(uint256).max);
+ vm.stopPrank();
+
+ Orchestrator.Intent memory u;
+ u.eoa = d.eoa;
+ u.nonce = 0;
+ u.combinedGas = 1000000;
+ u.prePaymentAmount = 0 ether;
+ u.prePaymentMaxAmount = 0 ether;
+ u.totalPaymentAmount = 0.01 ether;
+ u.totalPaymentMaxAmount = 0.1 ether;
+ u.paymentToken = address(0);
+ // To maintain parity with the old benchmarks.
+ u.paymentRecipient = address(oc);
+ u.executionData = _transferExecutionData(address(paymentToken), address(0xbabe), 1 ether);
+ u.signature = _sig(k, u);
+
+ bytes[] memory encodedIntents = new bytes[](1);
+ encodedIntents[0] = abi.encode(u);
+
+ vm.resumeGasMetering();
+
+ oc.execute(encodedIntents);
+
+ vm.pauseGasMetering();
+ assertEq(paymentToken.balanceOf(address(0xbabe)), 1 ether);
+ vm.resumeGasMetering();
+ }
+
function testERC20TransferDirect() public {
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
_giveAccountSomeTokens(d.eoa);
diff --git a/test/LayerZeroSettler.t.sol b/test/LayerZeroSettler.t.sol
index 941f2311..fe9be6ae 100644
--- a/test/LayerZeroSettler.t.sol
+++ b/test/LayerZeroSettler.t.sol
@@ -716,9 +716,9 @@ contract LayerZeroSettlerTest is Test {
// Should revert with InvalidL0SettlerSignature
vm.expectRevert(abi.encodeWithSelector(LayerZeroSettler.InvalidL0SettlerSignature.selector));
- settlerA.executeSend{
- value: fee
- }(orchestrator, settlementId, settlerContext, invalidSignature);
+ settlerA.executeSend{value: fee}(
+ orchestrator, settlementId, settlerContext, invalidSignature
+ );
}
function test_executeSend_preventReplay() public {
diff --git a/test/MultiSigSigner.t.sol b/test/MultiSigSigner.t.sol
deleted file mode 100644
index 497e0618..00000000
--- a/test/MultiSigSigner.t.sol
+++ /dev/null
@@ -1,656 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.23;
-
-import "./Base.t.sol";
-import {MultiSigSigner} from "../src/MultiSigSigner.sol";
-import {IthacaAccount} from "../src/IthacaAccount.sol";
-import {ERC7821} from "solady/accounts/ERC7821.sol";
-
-contract MultiSigSignerTest is BaseTest {
- MultiSigSigner multiSigSigner;
- DelegatedEOA delegatedAccount;
-
- struct MultiSigTestTemps {
- PassKey[] owners;
- bytes32[] ownerKeyHashes;
- uint256 threshold;
- MultiSigKey multiSigKey;
- bytes32 digest;
- }
-
- function setUp() public override {
- super.setUp();
- multiSigSigner = new MultiSigSigner();
- delegatedAccount = _randomEIP7702DelegatedEOA();
- }
-
- function test_DuplicateOwnerSignatures() public {
- MultiSigTestTemps memory t;
-
- // Setup: Create a multisig with threshold 2 but only 1 owner
- t.threshold = 2;
- t.owners = new PassKey[](2);
- t.owners[0] = _randomPassKey();
- t.owners[1] = _randomPassKey();
-
- // Create the multisig key configuration
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
- t.multiSigKey.threshold = t.threshold;
- t.multiSigKey.owners = t.owners;
-
- // Authorize keys in the delegated account
- vm.startPrank(delegatedAccount.eoa);
- delegatedAccount.d.authorize(t.multiSigKey.k);
- delegatedAccount.d.authorize(t.owners[0].k);
-
- // Initialize multisig config with threshold=2 but only 1 owner
- t.ownerKeyHashes = new bytes32[](1);
- t.ownerKeyHashes[0] = _hash(t.owners[0].k);
-
- // This should revert because threshold > number of owners
- vm.expectRevert(MultiSigSigner.InvalidThreshold.selector);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Now test with a valid config: threshold=2, 2 owners
- t.ownerKeyHashes = new bytes32[](2);
- t.ownerKeyHashes[0] = _hash(t.owners[0].k);
- t.ownerKeyHashes[1] = _hash(t.owners[1].k);
-
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- vm.stopPrank();
-
- // Create a digest to sign
- t.digest = keccak256("test message");
-
- // Create signature array with the same signature duplicated
- // Note: Each owner currently only has 1 signer power.
- bytes[] memory signatures = new bytes[](2);
- signatures[0] = _sig(t.owners[0], t.digest);
- signatures[1] = signatures[0]; // Duplicate signature of the first owner
-
- // Call isValidSignatureWithKeyHash
- vm.prank(address(delegatedAccount.d));
- bytes4 result = multiSigSigner.isValidSignatureWithKeyHash(
- t.digest, _hash(t.multiSigKey.k), abi.encode(signatures)
- );
-
- // Same owner should not be able to sign multiple times
- assertEq(result, bytes4(0xffffffff), "Duplicate signature should not be valid");
-
- // Add the first owner twice in the owner key hash
- vm.startPrank(address(delegatedAccount.eoa));
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
- multiSigSigner.addOwner(_hash(t.multiSigKey.k), _hash(t.owners[0].k));
- vm.stopPrank();
-
- // Now it should be valid, because the first owner has 2 signer powers.
- vm.prank(address(delegatedAccount.d));
- result = multiSigSigner.isValidSignatureWithKeyHash(
- t.digest, _hash(t.multiSigKey.k), abi.encode(signatures)
- );
-
- assertEq(result, bytes4(0x8afc93b4), "Duplicate signature should be valid now");
- }
-
- function test_InitConfig() public {
- MultiSigTestTemps memory t;
-
- // Setup owners
- uint256 numOwners = 3;
- t.owners = new PassKey[](numOwners);
- t.ownerKeyHashes = new bytes32[](numOwners);
-
- for (uint256 i = 0; i < numOwners; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
-
- t.threshold = 2;
-
- // Create multisig key
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: false,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.prank(address(delegatedAccount.d));
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Verify config was set correctly
- (uint256 storedThreshold, bytes32[] memory storedOwners) =
- multiSigSigner.getConfig(address(delegatedAccount.d), _hash(t.multiSigKey.k));
-
- assertEq(storedThreshold, t.threshold);
- assertEq(storedOwners.length, t.ownerKeyHashes.length);
-
- for (uint256 i = 0; i < storedOwners.length; i++) {
- assertEq(storedOwners[i], t.ownerKeyHashes[i]);
- }
- }
-
- function test_InitConfig_RevertsOnReinit() public {
- MultiSigTestTemps memory t;
-
- t.owners = new PassKey[](1);
- t.owners[0] = _randomPassKey();
- t.ownerKeyHashes = new bytes32[](1);
- t.ownerKeyHashes[0] = _hash(t.owners[0].k);
- t.threshold = 1;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: false,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- // First initialization should succeed
- vm.startPrank(address(delegatedAccount.d));
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Second initialization should revert
- vm.expectRevert(MultiSigSigner.ConfigAlreadySet.selector);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
- vm.stopPrank();
- }
-
- function test_InitConfig_InvalidThreshold() public {
- MultiSigTestTemps memory t;
-
- t.owners = new PassKey[](2);
- t.ownerKeyHashes = new bytes32[](2);
- for (uint256 i = 0; i < 2; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: false,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
-
- // Test threshold = 0
- vm.expectRevert(MultiSigSigner.InvalidThreshold.selector);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), 0, t.ownerKeyHashes);
-
- // Test threshold > number of owners
- vm.expectRevert(MultiSigSigner.InvalidThreshold.selector);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), 3, t.ownerKeyHashes);
-
- vm.stopPrank();
- }
-
- function test_AddOwner() public {
- MultiSigTestTemps memory t;
-
- // Initial setup with 2 owners
- t.owners = new PassKey[](2);
- t.ownerKeyHashes = new bytes32[](2);
- for (uint256 i = 0; i < 2; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
- t.threshold = 2;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- // Initialize config
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Add a new owner
- PassKey memory newOwner = _randomPassKey();
- bytes32 newOwnerKeyHash = _hash(newOwner.k);
-
- // Set context key hash
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
- multiSigSigner.addOwner(_hash(t.multiSigKey.k), newOwnerKeyHash);
-
- // Verify owner was added
- (, bytes32[] memory storedOwners) =
- multiSigSigner.getConfig(address(delegatedAccount.d), _hash(t.multiSigKey.k));
-
- assertEq(storedOwners.length, 3);
- assertEq(storedOwners[2], newOwnerKeyHash);
-
- vm.stopPrank();
- }
-
- function test_AddOwner_InvalidKeyHash() public {
- MultiSigTestTemps memory t;
-
- t.owners = new PassKey[](1);
- t.owners[0] = _randomPassKey();
- t.ownerKeyHashes = new bytes32[](1);
- t.ownerKeyHashes[0] = _hash(t.owners[0].k);
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), 1, t.ownerKeyHashes);
-
- // Try to add owner with wrong context key hash
- PassKey memory newOwner = _randomPassKey();
-
- // Set wrong context key hash
- delegatedAccount.d.setContextKeyHash(bytes32(uint256(123)));
-
- vm.expectRevert(MultiSigSigner.InvalidKeyHash.selector);
- multiSigSigner.addOwner(_hash(t.multiSigKey.k), _hash(newOwner.k));
-
- vm.stopPrank();
- }
-
- function test_RemoveOwner() public {
- MultiSigTestTemps memory t;
-
- // Setup with 3 owners
- t.owners = new PassKey[](3);
- t.ownerKeyHashes = new bytes32[](3);
- for (uint256 i = 0; i < 3; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
- t.threshold = 2;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Remove the second owner
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
- multiSigSigner.removeOwner(_hash(t.multiSigKey.k), t.ownerKeyHashes[1]);
-
- // Verify owner was removed
- (, bytes32[] memory storedOwners) =
- multiSigSigner.getConfig(address(delegatedAccount.d), _hash(t.multiSigKey.k));
-
- assertEq(storedOwners.length, 2);
- // The last owner should have replaced the removed one
- assertEq(storedOwners[1], t.ownerKeyHashes[2]);
-
- vm.stopPrank();
- }
-
- function test_RemoveOwner_OwnerNotFound() public {
- MultiSigTestTemps memory t;
-
- t.owners = new PassKey[](2);
- t.ownerKeyHashes = new bytes32[](2);
- for (uint256 i = 0; i < 2; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), 1, t.ownerKeyHashes);
-
- // Try to remove non-existent owner
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
- vm.expectRevert(MultiSigSigner.OwnerNotFound.selector);
- multiSigSigner.removeOwner(_hash(t.multiSigKey.k), bytes32(uint256(999)));
-
- vm.stopPrank();
- }
-
- function test_RemoveOwner_ThresholdViolation() public {
- MultiSigTestTemps memory t;
-
- // Setup with 2 owners and threshold 2
- t.owners = new PassKey[](2);
- t.ownerKeyHashes = new bytes32[](2);
- for (uint256 i = 0; i < 2; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
- t.threshold = 2;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Try to remove owner when it would violate threshold
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
- vm.expectRevert(MultiSigSigner.InvalidThreshold.selector);
- multiSigSigner.removeOwner(_hash(t.multiSigKey.k), t.ownerKeyHashes[0]);
-
- vm.stopPrank();
- }
-
- function test_SetThreshold() public {
- MultiSigTestTemps memory t;
-
- // Setup with 3 owners
- t.owners = new PassKey[](3);
- t.ownerKeyHashes = new bytes32[](3);
- for (uint256 i = 0; i < 3; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
- t.threshold = 1;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Change threshold to 2
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
- multiSigSigner.setThreshold(_hash(t.multiSigKey.k), 2);
-
- // Verify threshold was changed
- (uint256 storedThreshold,) =
- multiSigSigner.getConfig(address(delegatedAccount.d), _hash(t.multiSigKey.k));
-
- assertEq(storedThreshold, 2);
-
- // Change threshold to 3
- multiSigSigner.setThreshold(_hash(t.multiSigKey.k), 3);
-
- (storedThreshold,) =
- multiSigSigner.getConfig(address(delegatedAccount.d), _hash(t.multiSigKey.k));
-
- assertEq(storedThreshold, 3);
-
- vm.stopPrank();
- }
-
- function test_SetThreshold_Invalid() public {
- MultiSigTestTemps memory t;
-
- t.owners = new PassKey[](2);
- t.ownerKeyHashes = new bytes32[](2);
- for (uint256 i = 0; i < 2; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- vm.startPrank(address(delegatedAccount.d));
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), 1, t.ownerKeyHashes);
- delegatedAccount.d.setContextKeyHash(_hash(t.multiSigKey.k));
-
- // Test threshold = 0
- vm.expectRevert(MultiSigSigner.InvalidThreshold.selector);
- multiSigSigner.setThreshold(_hash(t.multiSigKey.k), 0);
-
- // Test threshold > number of owners
- vm.expectRevert(MultiSigSigner.InvalidThreshold.selector);
- multiSigSigner.setThreshold(_hash(t.multiSigKey.k), 3);
-
- vm.stopPrank();
- }
-
- function test_ValidSignature_MeetsThreshold() public {
- MultiSigTestTemps memory t;
-
- // Setup with 3 owners, threshold 2
- t.owners = new PassKey[](3);
- t.ownerKeyHashes = new bytes32[](3);
-
- vm.startPrank(delegatedAccount.eoa);
- for (uint256 i = 0; i < 3; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- delegatedAccount.d.authorize(t.owners[i].k);
- }
-
- t.threshold = 2;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
- vm.stopPrank();
-
- // Create a digest to sign
- t.digest = keccak256("test message");
-
- // Create signatures from 2 owners (meets threshold)
- bytes[] memory signatures = new bytes[](2);
- signatures[0] = _sig(t.owners[0], t.digest);
- signatures[1] = _sig(t.owners[1], t.digest);
-
- // Validate signature
- vm.prank(address(delegatedAccount.d));
- bytes4 result = multiSigSigner.isValidSignatureWithKeyHash(
- t.digest, _hash(t.multiSigKey.k), abi.encode(signatures)
- );
-
- assertEq(result, bytes4(0x8afc93b4), "Valid signature should return MAGIC_VALUE");
- }
-
- function test_InvalidSignature_BelowThreshold() public {
- MultiSigTestTemps memory t;
-
- // Setup with 3 owners, threshold 2
- t.owners = new PassKey[](3);
- t.ownerKeyHashes = new bytes32[](3);
-
- vm.startPrank(delegatedAccount.eoa);
- for (uint256 i = 0; i < 3; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- delegatedAccount.d.authorize(t.owners[i].k);
- }
-
- t.threshold = 2;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
- vm.stopPrank();
-
- // Create a digest to sign
- t.digest = keccak256("test message");
-
- // Create signature from only 1 owner (below threshold)
- bytes[] memory signatures = new bytes[](1);
- signatures[0] = _sig(t.owners[0], t.digest);
-
- // Validate signature
- vm.prank(address(delegatedAccount.d));
- bytes4 result = multiSigSigner.isValidSignatureWithKeyHash(
- t.digest, _hash(t.multiSigKey.k), abi.encode(signatures)
- );
-
- assertEq(result, bytes4(0xffffffff), "Invalid signature should return FAIL_VALUE");
- }
-
- function test_InvalidSignature_NonOwner() public {
- MultiSigTestTemps memory t;
-
- // Setup with 2 owners
- t.owners = new PassKey[](2);
- t.ownerKeyHashes = new bytes32[](2);
-
- vm.startPrank(delegatedAccount.eoa);
- for (uint256 i = 0; i < 2; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- delegatedAccount.d.authorize(t.owners[i].k);
- }
-
- t.threshold = 2;
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(0))
- });
-
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), t.threshold, t.ownerKeyHashes);
-
- // Create a non-owner key
- PassKey memory nonOwner = _randomPassKey();
- delegatedAccount.d.authorize(nonOwner.k);
-
- vm.stopPrank();
-
- // Create a digest to sign
- t.digest = keccak256("test message");
-
- // Create signatures with one owner and one non-owner
- bytes[] memory signatures = new bytes[](2);
- signatures[0] = _sig(t.owners[0], t.digest);
- signatures[1] = _sig(nonOwner, t.digest);
-
- // Validate signature
- vm.prank(address(delegatedAccount.d));
- bytes4 result = multiSigSigner.isValidSignatureWithKeyHash(
- t.digest, _hash(t.multiSigKey.k), abi.encode(signatures)
- );
-
- assertEq(result, bytes4(0xffffffff), "Non-owner signature should return FAIL_VALUE");
- }
-
- function testFuzz_InitConfig(uint256 numOwners, uint256 threshold) public {
- numOwners = bound(numOwners, 1, 10);
- threshold = bound(threshold, 1, numOwners);
-
- MultiSigTestTemps memory t;
- t.owners = new PassKey[](numOwners);
- t.ownerKeyHashes = new bytes32[](numOwners);
-
- for (uint256 i = 0; i < numOwners; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- }
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: false,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(uint96(_random())))
- });
-
- vm.prank(address(delegatedAccount.d));
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), threshold, t.ownerKeyHashes);
-
- (uint256 storedThreshold, bytes32[] memory storedOwners) =
- multiSigSigner.getConfig(address(delegatedAccount.d), _hash(t.multiSigKey.k));
-
- assertEq(storedThreshold, threshold);
- assertEq(storedOwners.length, numOwners);
- }
-
- function testFuzz_SignatureValidation(uint256 numOwners, uint256 threshold, uint256 numSigners)
- public
- {
- numOwners = bound(numOwners, 1, 10);
- threshold = bound(threshold, 1, numOwners);
- numSigners = bound(numSigners, 0, numOwners);
-
- MultiSigTestTemps memory t;
- t.owners = new PassKey[](numOwners);
- t.ownerKeyHashes = new bytes32[](numOwners);
-
- vm.startPrank(delegatedAccount.eoa);
- for (uint256 i = 0; i < numOwners; i++) {
- t.owners[i] = _randomPassKey();
- t.ownerKeyHashes[i] = _hash(t.owners[i].k);
- delegatedAccount.d.authorize(t.owners[i].k);
- }
-
- t.multiSigKey.k = IthacaAccount.Key({
- expiry: 0,
- keyType: IthacaAccount.KeyType.External,
- isSuperAdmin: true,
- publicKey: abi.encodePacked(address(multiSigSigner), bytes12(uint96(_random())))
- });
-
- delegatedAccount.d.authorize(t.multiSigKey.k);
- multiSigSigner.initConfig(_hash(t.multiSigKey.k), threshold, t.ownerKeyHashes);
- vm.stopPrank();
-
- t.digest = keccak256(abi.encode("test", _random()));
-
- bytes[] memory signatures = new bytes[](numSigners);
- for (uint256 i = 0; i < numSigners; i++) {
- signatures[i] = _sig(t.owners[i], t.digest);
- }
-
- vm.prank(address(delegatedAccount.d));
- bytes4 result = multiSigSigner.isValidSignatureWithKeyHash(
- t.digest, _hash(t.multiSigKey.k), abi.encode(signatures)
- );
-
- if (numSigners >= threshold) {
- assertEq(result, bytes4(0x8afc93b4), "Should be valid when signatures >= threshold");
- } else {
- assertEq(result, bytes4(0xffffffff), "Should be invalid when signatures < threshold");
- }
- }
-}
diff --git a/test/Orchestrator.t.sol b/test/Orchestrator.t.sol
index d79c4cab..9f2ada0c 100644
--- a/test/Orchestrator.t.sol
+++ b/test/Orchestrator.t.sol
@@ -924,8 +924,9 @@ contract OrchestratorTest is BaseTest {
if (_randomChance(16)) {
u.combinedGas += 10_000;
// Fill with some junk signature, but with the session `keyHash`.
- u.signature =
- abi.encodePacked(keccak256("a"), keccak256("b"), kSession.keyHash, uint8(0));
+ u.signature = abi.encodePacked(
+ keccak256("a"), keccak256("b"), kSession.keyHash, uint8(0), uint8(0)
+ );
(t.gExecute, t.gCombined, t.gUsed) = _estimateGas(u);
diff --git a/test/SimulateExecute.t.sol b/test/SimulateExecute.t.sol
index 099d6acc..1e4a89a4 100644
--- a/test/SimulateExecute.t.sol
+++ b/test/SimulateExecute.t.sol
@@ -305,7 +305,8 @@ contract SimulateExecuteTest is BaseTest {
// it needs to add the variance for non-precompile P256 verification.
// We need the `keyHash` in the signature so that the simulation is able
// to hit all the gas for the GuardedExecutor stuff for the `keyHash`.
- i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), k.keyHash, uint8(0));
+ i.signature =
+ abi.encodePacked(keccak256("a"), keccak256("b"), k.keyHash, uint8(0), uint8(0));
uint256 snapshot = vm.snapshotState();
vm.deal(_ORIGIN_ADDRESS, type(uint192).max);
diff --git a/test/UpgradeTests.t.sol b/test/UpgradeTests.t.sol
deleted file mode 100644
index d0d28d09..00000000
--- a/test/UpgradeTests.t.sol
+++ /dev/null
@@ -1,566 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
-
-import "./utils/SoladyTest.sol";
-import {IthacaAccount} from "./utils/mocks/MockAccount.sol";
-import {GuardedExecutor} from "../src/IthacaAccount.sol";
-import {BaseTest} from "./Base.t.sol";
-import {EIP7702Proxy} from "solady/accounts/EIP7702Proxy.sol";
-import {LibEIP7702} from "solady/accounts/LibEIP7702.sol";
-import {MockPaymentToken} from "./utils/mocks/MockPaymentToken.sol";
-import {LibClone} from "solady/utils/LibClone.sol";
-import {Orchestrator, MockOrchestrator} from "./utils/mocks/MockOrchestrator.sol";
-import {ERC7821} from "solady/accounts/ERC7821.sol";
-
-contract UpgradeTests is BaseTest {
- address payable public oldProxyAddress;
- address public oldImplementation;
- address public newImplementation;
-
- // Test EOA that will be delegated to the proxy
- address public userEOA;
- uint256 public userEOAKey;
- IthacaAccount public userAccount;
-
- // Test keys
- PassKey public p256Key;
- PassKey public p256SuperAdminKey;
- PassKey public secp256k1Key;
- PassKey public secp256k1SuperAdminKey;
- PassKey public webAuthnP256Key;
- PassKey public externalKey;
-
- // Test tokens
- MockPaymentToken public mockToken1;
- MockPaymentToken public mockToken2;
-
- // Random addresses for testing transfers
- address[] public randomRecipients;
-
- // State capture - using simple mappings to avoid memory-to-storage issues
- // Pre-upgrade state
- bytes32[] preKeyHashes;
- mapping(bytes32 => IthacaAccount.Key) preKeys;
- mapping(bytes32 => bool) preAuthorized;
- uint256 preEthBalance;
- uint256 preToken1Balance;
- uint256 preToken2Balance;
- uint256 preNonce;
- // Get expected old version from environment variable
- string public expectedOldVersion = vm.envString("UPGRADE_TEST_OLD_VERSION");
-
- // Post-upgrade state
- bytes32[] postKeyHashes;
- mapping(bytes32 => IthacaAccount.Key) postKeys;
- mapping(bytes32 => bool) postAuthorized;
- uint256 postEthBalance;
- uint256 postToken1Balance;
- uint256 postToken2Balance;
- uint256 postNonce;
-
- function setUp() public override {
- super.setUp();
-
- // Fork the network to get the proxy bytecode
- string memory rpcUrl = vm.envString("UPGRADE_TEST_RPC_URL");
- vm.createSelectFork(rpcUrl);
-
- // Deploy test tokens
- mockToken1 = new MockPaymentToken();
- mockToken2 = new MockPaymentToken();
-
- // Setup random recipients
- for (uint256 i = 0; i < 5; i++) {
- randomRecipients.push(_randomAddress());
- }
-
- // Get old proxy address from environment
- oldProxyAddress = payable(vm.envAddress("UPGRADE_TEST_OLD_PROXY"));
-
- // Setup delegated EOA
- (userEOA, userEOAKey) = _randomUniqueSigner();
-
- vm.etch(userEOA, abi.encodePacked(hex"ef0100", address(oldProxyAddress)));
-
- userAccount = IthacaAccount(payable(userEOA));
-
- // Get the bytecode of the old proxy from the forked network
- bytes memory proxyBytecode = oldProxyAddress.code;
- require(proxyBytecode.length > 0, "No bytecode at old proxy address");
-
- newImplementation = address(new IthacaAccount(address(oc)));
-
- // Generate test keys
- p256Key = _randomSecp256r1PassKey();
- p256Key.k.isSuperAdmin = false;
- p256Key.k.expiry = 0; // Never expires
-
- p256SuperAdminKey = _randomSecp256r1PassKey();
- p256SuperAdminKey.k.isSuperAdmin = true;
- p256SuperAdminKey.k.expiry = uint40(block.timestamp + 365 days); // Expires in 1 year
-
- secp256k1Key = _randomSecp256k1PassKey();
- secp256k1Key.k.isSuperAdmin = false;
- secp256k1Key.k.expiry = 0;
-
- secp256k1SuperAdminKey = _randomSecp256k1PassKey();
- secp256k1SuperAdminKey.k.isSuperAdmin = true;
- secp256k1SuperAdminKey.k.expiry = uint40(block.timestamp + 30 days); // Expires in 30 days
-
- webAuthnP256Key = _randomSecp256r1PassKey();
- webAuthnP256Key.k.keyType = IthacaAccount.KeyType.WebAuthnP256;
- webAuthnP256Key.k.isSuperAdmin = false;
- webAuthnP256Key.k.expiry = 0;
-
- // Setup external key
- address externalSigner = _randomAddress();
- bytes12 salt = bytes12(uint96(_randomUniform()));
- externalKey.k.keyType = IthacaAccount.KeyType.External;
- externalKey.k.publicKey = abi.encodePacked(externalSigner, salt);
- externalKey.k.isSuperAdmin = false;
- externalKey.k.expiry = uint40(block.timestamp + 7 days);
- externalKey.keyHash = _hash(externalKey.k);
- }
-
- function test_ComprehensiveUpgrade() public {
- // Step 2: Setup the old account with various configurations
- _setupOldAccountState();
-
- // Step 3: Capture pre-upgrade state
- _capturePreUpgradeState();
-
- // Step 4: Perform upgrade
- _performUpgrade();
-
- // Step 5: Capture post-upgrade state
- _capturePostUpgradeState();
-
- // Step 6: Verify state preservation
- _verifyStatePreservation();
-
- // Step 7: Test post-upgrade functionality
- _testPostUpgradeFunctionality();
- }
-
- function _performUpgrade() internal {
- // Get the version from the new implementation for comparison
- IthacaAccount newImpl = IthacaAccount(payable(newImplementation));
- (,, string memory expectedNewVersion,,,,) = newImpl.eip712Domain();
-
- // Check version before upgrade matches expected old version
- (,, string memory versionBefore,,,,) = userAccount.eip712Domain();
- assertEq(
- keccak256(bytes(versionBefore)),
- keccak256(bytes(expectedOldVersion)),
- string.concat("Version before upgrade should be ", expectedOldVersion)
- );
-
- // Perform the upgrade
- bytes memory upgradeCalldata =
- abi.encodeWithSelector(IthacaAccount.upgradeProxyAccount.selector, newImplementation);
-
- vm.prank(userEOA);
- (bool success,) = userEOA.call(upgradeCalldata);
- require(success, "Upgrade failed");
-
- // Check version after upgrade
- (,, string memory versionAfter,,,,) = userAccount.eip712Domain();
-
- // Verify the version matches the new implementation version after upgrade
- assertEq(
- keccak256(bytes(versionAfter)),
- keccak256(bytes(expectedNewVersion)),
- string.concat("Version after upgrade should be ", expectedNewVersion)
- );
- }
-
- function _setupOldAccountState() internal {
- // Authorize various keys
- vm.startPrank(userEOA);
-
- userAccount.authorize(p256Key.k);
- p256Key.keyHash = _hash(p256Key.k);
-
- userAccount.authorize(secp256k1Key.k);
- secp256k1Key.keyHash = _hash(secp256k1Key.k);
-
- userAccount.authorize(secp256k1SuperAdminKey.k);
- secp256k1SuperAdminKey.keyHash = _hash(secp256k1SuperAdminKey.k);
-
- userAccount.authorize(webAuthnP256Key.k);
- webAuthnP256Key.keyHash = _hash(webAuthnP256Key.k);
-
- externalKey.keyHash = userAccount.authorize(externalKey.k);
-
- // Setup spending limits for different keys
- _setupSpendingLimits();
-
- // Setup execution permissions
- _setupExecutionPermissions();
-
- // Fund the account
- _fundAccount();
-
- vm.stopPrank();
- }
-
- function _setupSpendingLimits() internal {
- // Only set spending limits for non-super admin keys
-
- // Daily ETH limit for secp256k1Key (not a super admin)
- if (secp256k1Key.keyHash != bytes32(0)) {
- userAccount.setSpendLimit(
- secp256k1Key.keyHash, address(0), GuardedExecutor.SpendPeriod.Day, 1 ether
- );
-
- // Weekly ETH limit for secp256k1Key
- userAccount.setSpendLimit(
- secp256k1Key.keyHash, address(0), GuardedExecutor.SpendPeriod.Week, 5 ether
- );
-
- // Monthly token1 limit for secp256k1Key
- userAccount.setSpendLimit(
- secp256k1Key.keyHash,
- address(mockToken1),
- GuardedExecutor.SpendPeriod.Month,
- 1000e18
- );
- }
-
- // Daily token2 limit for webAuthnP256Key (not a super admin)
- if (webAuthnP256Key.keyHash != bytes32(0)) {
- userAccount.setSpendLimit(
- webAuthnP256Key.keyHash,
- address(mockToken2),
- GuardedExecutor.SpendPeriod.Day,
- 100e18
- );
- }
-
- // Hour ETH limit for externalKey (not a super admin)
- if (externalKey.keyHash != bytes32(0)) {
- userAccount.setSpendLimit(
- externalKey.keyHash, address(0), GuardedExecutor.SpendPeriod.Hour, 0.1 ether
- );
- }
-
- // Forever limit for p256Key (if authorized and not a super admin)
- if (p256Key.keyHash != bytes32(0) && !p256Key.k.isSuperAdmin) {
- userAccount.setSpendLimit(
- p256Key.keyHash, address(0), GuardedExecutor.SpendPeriod.Forever, 10 ether
- );
- }
- }
-
- function _setupExecutionPermissions() internal {
- // Setup canExecute permissions (only for non-super admin keys)
- address target1 = address(0x1234);
- address target2 = address(0x5678);
- bytes4 selector1 = bytes4(keccak256("transfer(address,uint256)"));
- bytes4 selector2 = bytes4(keccak256("approve(address,uint256)"));
-
- // Only set for non-super admin secp256k1Key
- if (secp256k1Key.keyHash != bytes32(0) && !secp256k1Key.k.isSuperAdmin) {
- userAccount.setCanExecute(secp256k1Key.keyHash, target1, selector1, true);
- userAccount.setCanExecute(secp256k1Key.keyHash, target2, selector2, true);
- }
-
- // Only set for p256Key if it's not a super admin
- if (p256Key.keyHash != bytes32(0) && !p256Key.k.isSuperAdmin) {
- userAccount.setCanExecute(p256Key.keyHash, target1, selector2, true);
- }
-
- // Only set for webAuthnP256Key if it's not a super admin
- if (webAuthnP256Key.keyHash != bytes32(0) && !webAuthnP256Key.k.isSuperAdmin) {
- userAccount.setCanExecute(webAuthnP256Key.keyHash, target2, selector1, true);
- }
-
- // Setup call checkers (only for non-super admin keys)
- address checker1 = address(0xAAAA);
- address checker2 = address(0xBBBB);
-
- if (secp256k1Key.keyHash != bytes32(0) && !secp256k1Key.k.isSuperAdmin) {
- userAccount.setCallChecker(secp256k1Key.keyHash, target1, checker1);
- }
-
- if (webAuthnP256Key.keyHash != bytes32(0) && !webAuthnP256Key.k.isSuperAdmin) {
- userAccount.setCallChecker(webAuthnP256Key.keyHash, target2, checker2);
- }
- }
-
- function _fundAccount() internal {
- // Fund with ETH
- vm.deal(address(userAccount), 10 ether);
-
- // Fund with tokens
- mockToken1.mint(address(userAccount), 10000e18);
- mockToken2.mint(address(userAccount), 5000e18);
- }
-
- function _capturePreUpgradeState() internal {
- // Capture authorized keys
- (, bytes32[] memory keyHashes) = userAccount.getKeys();
-
- // Clear and populate pre-upgrade key hashes
- delete preKeyHashes;
- for (uint256 i = 0; i < keyHashes.length; i++) {
- preKeyHashes.push(keyHashes[i]);
- bytes32 keyHash = keyHashes[i];
- preKeys[keyHash] = userAccount.getKey(keyHash);
- preAuthorized[keyHash] = true;
- }
-
- // Capture balances
- preEthBalance = address(userAccount).balance;
- preToken1Balance = mockToken1.balanceOf(address(userAccount));
- preToken2Balance = mockToken2.balanceOf(address(userAccount));
-
- // Capture nonce
- preNonce = userAccount.getNonce(0);
- }
-
- function _capturePostUpgradeState() internal {
- // Capture authorized keys
- (, bytes32[] memory keyHashes) = userAccount.getKeys();
-
- // Clear and populate post-upgrade key hashes
- delete postKeyHashes;
- for (uint256 i = 0; i < keyHashes.length; i++) {
- postKeyHashes.push(keyHashes[i]);
- bytes32 keyHash = keyHashes[i];
- postKeys[keyHash] = userAccount.getKey(keyHash);
- postAuthorized[keyHash] = true;
- }
-
- // Capture balances
- postEthBalance = address(userAccount).balance;
- postToken1Balance = mockToken1.balanceOf(address(userAccount));
- postToken2Balance = mockToken2.balanceOf(address(userAccount));
-
- // Capture nonce
- postNonce = userAccount.getNonce(0);
- }
-
- function _verifyStatePreservation() internal view {
- // Verify all keys are preserved
- assertEq(preKeyHashes.length, postKeyHashes.length, "Number of authorized keys changed");
-
- for (uint256 i = 0; i < preKeyHashes.length; i++) {
- bytes32 keyHash = preKeyHashes[i];
-
- assertTrue(postAuthorized[keyHash], "Key was deauthorized during upgrade");
-
- IthacaAccount.Key memory preKey = preKeys[keyHash];
- IthacaAccount.Key memory postKey = postKeys[keyHash];
-
- assertEq(preKey.expiry, postKey.expiry, "Key expiry changed");
- assertEq(uint8(preKey.keyType), uint8(postKey.keyType), "Key type changed");
- assertEq(preKey.isSuperAdmin, postKey.isSuperAdmin, "Key super admin status changed");
- assertEq(preKey.publicKey, postKey.publicKey, "Key public key changed");
- }
-
- // Verify balances preserved
- assertEq(preEthBalance, postEthBalance, "ETH balance changed");
- assertEq(preToken1Balance, postToken1Balance, "Token1 balance changed");
- assertEq(preToken2Balance, postToken2Balance, "Token2 balance changed");
-
- // Verify nonce preserved
- assertEq(preNonce, postNonce, "Nonce changed");
- }
-
- function _testPostUpgradeFunctionality() internal {
- vm.startPrank(userEOA);
-
- // Test 1: P256 keys can now be super admins (new in v0.5.7+)
- PassKey memory newP256SuperAdmin = _randomSecp256r1PassKey();
- newP256SuperAdmin.k.isSuperAdmin = true;
- newP256SuperAdmin.k.expiry = 0;
-
- // This should succeed in upgraded version
- bytes32 newP256KeyHash = userAccount.authorize(newP256SuperAdmin.k);
- IthacaAccount.Key memory retrievedKey = userAccount.getKey(newP256KeyHash);
- assertEq(
- uint8(retrievedKey.keyType), uint8(IthacaAccount.KeyType.P256), "Key type mismatch"
- );
- assertTrue(retrievedKey.isSuperAdmin, "P256 should be super admin after upgrade");
-
- // Test 2: Add a new non-super-admin key and set spending limit
- PassKey memory newRegularKey = _randomSecp256k1PassKey();
- newRegularKey.k.isSuperAdmin = false;
- newRegularKey.k.expiry = 0;
-
- bytes32 newRegularKeyHash = userAccount.authorize(newRegularKey.k);
-
- // Set spending limit for the regular key (not super admin)
- userAccount.setSpendLimit(
- newRegularKeyHash, address(0), GuardedExecutor.SpendPeriod.Week, 2 ether
- );
-
- GuardedExecutor.SpendInfo[] memory spendInfos = userAccount.spendInfos(newRegularKeyHash);
- bool foundWeeklyLimit = false;
- for (uint256 i = 0; i < spendInfos.length; i++) {
- if (
- spendInfos[i].period == GuardedExecutor.SpendPeriod.Week
- && spendInfos[i].token == address(0)
- ) {
- assertEq(spendInfos[i].limit, 2 ether, "Weekly limit not set correctly");
- foundWeeklyLimit = true;
- break;
- }
- }
- assertTrue(foundWeeklyLimit, "Weekly limit not found");
-
- // Test 3: Verify keys can still be used (without actual execution)
- // We verify the key is still authorized and has correct properties
- IthacaAccount.Key memory existingKey = userAccount.getKey(secp256k1Key.keyHash);
- assertEq(
- uint8(existingKey.keyType), uint8(IthacaAccount.KeyType.Secp256k1), "Key type changed"
- );
- assertFalse(existingKey.isSuperAdmin, "Key admin status changed");
-
- // Test 4: Test revoke and re-authorize with a new key
- // Create a new key to test revoke/re-authorize functionality
- PassKey memory testRevokeKey = _randomSecp256k1PassKey();
- testRevokeKey.k.isSuperAdmin = false;
- testRevokeKey.k.expiry = 0;
-
- bytes32 testRevokeKeyHash = userAccount.authorize(testRevokeKey.k);
-
- // Now revoke it
- userAccount.revoke(testRevokeKeyHash);
-
- // Verify key is revoked by checking it no longer exists
- // After revocation, getKey will revert with KeyDoesNotExist
- vm.expectRevert(abi.encodeWithSelector(IthacaAccount.KeyDoesNotExist.selector));
- userAccount.getKey(testRevokeKeyHash);
-
- // Re-authorize
- bytes32 reauthorizedHash = userAccount.authorize(testRevokeKey.k);
- assertEq(reauthorizedHash, testRevokeKeyHash, "Key hash changed on re-authorization");
-
- vm.stopPrank();
- }
-
- function test_UpgradeWithSpendLimitEnabledFlag() public {
- // This test verifies the spend limit enabled flag feature added in newer versions
-
- vm.startPrank(userEOA);
-
- // Authorize a key with spending limits
- PassKey memory testKey = _randomSecp256k1PassKey();
- bytes32 keyHash = userAccount.authorize(testKey.k);
-
- // Set spending limit
- userAccount.setSpendLimit(keyHash, address(0), GuardedExecutor.SpendPeriod.Day, 0.5 ether);
-
- // Fund account
- vm.deal(address(userAccount), 5 ether);
-
- vm.stopPrank();
-
- // Perform upgrade
- _performUpgrade();
-
- // Verify spending limits still work after upgrade
- GuardedExecutor.SpendInfo[] memory spendInfos = userAccount.spendInfos(keyHash);
- assertEq(spendInfos.length, 1, "Spending limit not preserved");
- assertEq(spendInfos[0].limit, 0.5 ether, "Spending limit value changed");
-
- vm.stopPrank();
- }
-
- function test_UpgradeWithMultipleKeyTypes() public {
- // Test upgrade with all key types authorized
-
- vm.startPrank(userEOA);
-
- // Authorize all key types
- PassKey[] memory keys = new PassKey[](4);
- keys[0] = _randomSecp256r1PassKey();
- keys[1] = _randomSecp256k1PassKey();
- keys[2] = _randomSecp256r1PassKey();
- keys[2].k.keyType = IthacaAccount.KeyType.WebAuthnP256;
- keys[3].k.keyType = IthacaAccount.KeyType.External;
- keys[3].k.publicKey = abi.encodePacked(_randomAddress(), bytes12(uint96(_randomUniform())));
- keys[3].keyHash = _hash(keys[3].k);
-
- bytes32[] memory keyHashes = new bytes32[](4);
- for (uint256 i = 0; i < keys.length; i++) {
- // Some key types might fail in old versions, handle gracefully
- try userAccount.authorize(keys[i].k) returns (bytes32 kh) {
- keyHashes[i] = kh;
- } catch {
- // Skip if authorization fails
- }
- }
-
- // Capture authorized count before upgrade
- (, bytes32[] memory keyHashesBefore) = userAccount.getKeys();
- uint256 authorizedCountBefore = keyHashesBefore.length;
-
- vm.stopPrank();
-
- // Perform upgrade
- _performUpgrade();
-
- // Verify all keys preserved
- (, bytes32[] memory keyHashesAfter) = userAccount.getKeys();
- uint256 authorizedCountAfter = keyHashesAfter.length;
- assertEq(authorizedCountBefore, authorizedCountAfter, "Key count changed during upgrade");
-
- vm.stopPrank();
- }
-
- function test_UpgradePreservesComplexSpendingState() public {
- // Test that complex spending state with partially spent limits is preserved
-
- vm.startPrank(userEOA);
-
- // Setup key and limits
- PassKey memory spendKey = _randomSecp256k1PassKey();
- bytes32 keyHash = userAccount.authorize(spendKey.k);
-
- // Set multiple spending limits
- userAccount.setSpendLimit(keyHash, address(0), GuardedExecutor.SpendPeriod.Day, 1 ether);
- userAccount.setSpendLimit(keyHash, address(0), GuardedExecutor.SpendPeriod.Week, 3 ether);
- userAccount.setSpendLimit(keyHash, address(0), GuardedExecutor.SpendPeriod.Month, 10 ether);
-
- // Fund account
- vm.deal(address(userAccount), 20 ether);
- vm.stopPrank();
-
- // Capture spending state before upgrade
- GuardedExecutor.SpendInfo[] memory spendsBefore = userAccount.spendInfos(keyHash);
-
- // Verify spending limits are set
- uint256 limitsCount = 0;
- for (uint256 i = 0; i < spendsBefore.length; i++) {
- if (spendsBefore[i].token == address(0)) {
- limitsCount++;
- }
- }
- assertEq(limitsCount, 3, "Should have 3 ETH spending limits");
-
- // Perform upgrade
- _performUpgrade();
-
- // Verify spending state preserved
- GuardedExecutor.SpendInfo[] memory spendsAfter = userAccount.spendInfos(keyHash);
-
- // Verify all limits still exist
- uint256 limitsCountAfter = 0;
- for (uint256 i = 0; i < spendsAfter.length; i++) {
- if (spendsAfter[i].token == address(0)) {
- limitsCountAfter++;
- }
- }
- assertEq(limitsCountAfter, 3, "Spending limits not preserved after upgrade");
-
- // Verify limits match
- assertEq(spendsBefore.length, spendsAfter.length, "Number of spending limits changed");
- for (uint256 i = 0; i < spendsBefore.length; i++) {
- assertEq(spendsBefore[i].limit, spendsAfter[i].limit, "Limit value changed");
- assertEq(uint8(spendsBefore[i].period), uint8(spendsAfter[i].period), "Period changed");
- }
- }
-}
diff --git a/test/utils/Brutalizer.sol b/test/utils/Brutalizer.sol
index 1c6447ae..969c5f86 100644
--- a/test/utils/Brutalizer.sol
+++ b/test/utils/Brutalizer.sol
@@ -825,7 +825,9 @@ contract Brutalizer {
let remainder := and(length, 0x1f)
if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } }
// Check if the memory allocated is sufficient.
- if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } }
+ if length {
+ if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 }
+ }
}
if (notZeroRightPadded) revert("Not zero right padded!");
if (insufficientMalloc) revert("Insufficient memory allocation!");
diff --git a/test/utils/interfaces/ISafe.sol b/test/utils/interfaces/ISafe.sol
deleted file mode 100644
index d9c3d3e1..00000000
--- a/test/utils/interfaces/ISafe.sol
+++ /dev/null
@@ -1,57 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.28;
-
-import "./IERC4337EntryPoint.sol";
-
-interface ISafe {
- function setup(
- address[] calldata _owners,
- uint256 _threshold,
- address to,
- bytes calldata data,
- address fallbackHandler,
- address paymentToken,
- uint256 payment,
- address paymentReceiver
- ) external;
-
- function enableModule(address module) external;
-
- function execTransaction(
- address to,
- uint256 value,
- bytes calldata data,
- uint8 operation,
- uint256 safeTxGas,
- uint256 baseGas,
- uint256 gasPrice,
- address gasToken,
- address payable refundReceiver,
- bytes memory signatures
- ) external payable returns (bool success);
-
- function getFallbackHandler() external view returns (address);
-}
-
-interface ISafeProxyFactory {
- function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce)
- external
- returns (address proxy);
-}
-
-interface ISafe4337Module {
- function SUPPORTED_ENTRYPOINT() external view returns (address);
-
- function getOperationHash(UserOperation calldata userOp)
- external
- view
- returns (bytes32 operationHash);
-
- function executeUserOp(address to, uint256 value, bytes calldata data, uint8 operation) external;
-
- function domainSeparator() external view returns (bytes32);
-}
-
-interface IAddModulesLib {
- function enableModules(address[] memory modules) external;
-}
diff --git a/test/utils/mocks/MockPayerWithSignatureOptimized.sol b/test/utils/mocks/MockPayerWithSignatureOptimized.sol
deleted file mode 100644
index 1d7701bf..00000000
--- a/test/utils/mocks/MockPayerWithSignatureOptimized.sol
+++ /dev/null
@@ -1,84 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
-
-import {TokenTransferLib} from "../../../src/libraries/TokenTransferLib.sol";
-import {Ownable} from "solady/auth/Ownable.sol";
-import {ECDSA} from "solady/utils/ECDSA.sol";
-import {ICommon} from "../../../src/interfaces/ICommon.sol";
-import {IOrchestrator} from "../../../src/interfaces/IOrchestrator.sol";
-/// @dev WARNING! This mock is strictly intended for testing purposes only.
-/// Do NOT copy anything here into production code unless you really know what you are doing.
-
-contract MockPayerWithSignatureOptimized is Ownable {
- error InvalidSignature();
-
- address public signer;
-
- address public immutable APPROVED_ORCHESTRATOR;
-
- event Compensated(
- address indexed paymentToken,
- address indexed paymentRecipient,
- uint256 paymentAmount,
- address indexed eoa,
- bytes32 keyHash
- );
-
- constructor(address orchestrator) {
- APPROVED_ORCHESTRATOR = orchestrator;
- _initializeOwner(msg.sender);
- }
-
- function setSigner(address newSinger) public onlyOwner {
- signer = newSinger;
- }
-
- /// @dev `address(0)` denote native token (i.e. Ether).
- function withdrawTokens(address token, address recipient, uint256 amount)
- public
- virtual
- onlyOwner
- {
- TokenTransferLib.safeTransfer(token, recipient, amount);
- }
-
- /// @dev Pays `paymentAmount` of `paymentToken` to the `paymentRecipient`.
- /// The EOA and token details are extracted from the `encodedIntent`.
- /// Reverts if the specified Orchestrator (`msg.sender`) is not approved.
- /// NOTE: This mock no longer verifies signatures within the pay function itself,
- /// aligning with the Account/Orchestrator pattern where verification happens before payment.
- /// @param paymentAmount The amount to pay.
- /// @param keyHash The key hash associated with the operation (not used in this mock's logic but kept for signature compatibility).
- /// @param encodedIntent ABI encoded Intent struct.
- function pay(
- uint256 paymentAmount,
- bytes32 keyHash,
- bytes32 digest,
- bytes calldata encodedIntent
- ) public virtual {
- if (msg.sender != APPROVED_ORCHESTRATOR) revert Unauthorized();
-
- ICommon.Intent calldata u;
- assembly {
- let t := calldataload(encodedIntent.offset)
- u := add(t, encodedIntent.offset)
- }
-
- bytes32 signatureDigest = computeSignatureDigest(digest);
-
- if (ECDSA.recover(signatureDigest, u.paymentSignature) != signer) {
- revert InvalidSignature();
- }
-
- TokenTransferLib.safeTransfer(u.paymentToken, u.paymentRecipient, paymentAmount);
-
- emit Compensated(u.paymentToken, u.paymentRecipient, paymentAmount, u.eoa, keyHash);
- }
-
- function computeSignatureDigest(bytes32 intentDigest) public view returns (bytes32) {
- // We shall just use this simplified hash instead of EIP712.
- return keccak256(abi.encode(intentDigest, block.chainid, address(this)));
- }
-
- receive() external payable {}
-}