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..dcfc7f4c 100644
--- a/.env.example
+++ b/.env.example
@@ -4,7 +4,7 @@
# ============================================
# UPGRADE TESTS
# ============================================
-UPGRADE_TEST_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY # Base
+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
@@ -16,10 +16,10 @@ PRIVATE_KEY=
# Format: RPC_{chainId}
# Mainnet chains
-RPC_1=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY # Ethereum
+RPC_1=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY# Ethereum
# Testnet chains
-RPC_11155111=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY # Sepolia
+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"
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/.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..d6ebd480
--- /dev/null
+++ b/.idea/account.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
new file mode 100644
index 00000000..94e429a7
--- /dev/null
+++ b/.idea/caches/deviceStreaming.xml
@@ -0,0 +1,2007 @@
+
+
+
+
+
+
\ 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/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..862d09bd
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ 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..bb3b0703
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fd21859..dae9f612 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
@@ -113,7 +94,7 @@
- All fill related functions removed from EP.
- EP is now completely stateless, also does not have a constructor.
- PreCall with `nonce = type(uint256).max` is not replayable anymore.
-- `OpDataTooShort` error, udpated to `OpDataError`, to enforce tighter validation of opdata.
+- `OpDataTooShort` error, updated to `OpDataError`, to enforce tighter validation of opdata.
- `checkAndIncrementNonce` function added to account. Can only be called by EP.
- 6b3294a: Optimize `_isSuperAdmin`
@@ -150,7 +131,7 @@
- Add back the INSUFFICIENT_GAS check, which prevents the relay from setting up the `execute` call on the
account, in such a way causing it to intentionally fail.
- For the relay, gExecute now has to be set atleast as `gExecute > (gCombined + 100_000) * 64/63)`
+ For the relay, gExecute now has to be set at least as `gExecute > (gCombined + 100_000) * 64/63)`
### Patch 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
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
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..fe1fb56d 100644
--- a/foundry.lock
+++ b/foundry.lock
@@ -5,7 +5,7 @@
"lib/forge-std": {
"branch": {
"name": "master",
- "rev": "c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899"
+ "rev": "27ab851c7cde58b22cc4de426d59e23a31c5cb9c"
}
},
"lib/murky": {
@@ -17,7 +17,7 @@
"lib/solady": {
"branch": {
"name": "main",
- "rev": "acd959aa4bd04720d640bf4e6a5c71037510cc4b"
+ "rev": "73f13dd1483707ef6b4d16cb0543570b7e1715a8"
}
}
}
\ 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/.idea/.gitignore b/lib/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/lib/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/lib/.idea/caches/deviceStreaming.xml b/lib/.idea/caches/deviceStreaming.xml
new file mode 100644
index 00000000..94e429a7
--- /dev/null
+++ b/lib/.idea/caches/deviceStreaming.xml
@@ -0,0 +1,2007 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/lib.iml b/lib/.idea/lib.iml
new file mode 100644
index 00000000..d6ebd480
--- /dev/null
+++ b/lib/.idea/lib.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/misc.xml b/lib/.idea/misc.xml
new file mode 100644
index 00000000..862d09bd
--- /dev/null
+++ b/lib/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/modules.xml b/lib/.idea/modules.xml
new file mode 100644
index 00000000..b0c4ff69
--- /dev/null
+++ b/lib/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/vcs.xml b/lib/.idea/vcs.xml
new file mode 100644
index 00000000..d3a8bd59
--- /dev/null
+++ b/lib/.idea/vcs.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/lib/solady b/lib/solady
index acd959aa..90db92ce 160000
--- a/lib/solady
+++ b/lib/solady
@@ -1 +1 @@
-Subproject commit acd959aa4bd04720d640bf4e6a5c71037510cc4b
+Subproject commit 90db92ce173856605d24a554969f2c67cadbc7e9
diff --git a/prep/check-bytecode-changes.js b/prep/check-bytecode-changes.js
old mode 100755
new mode 100644
diff --git a/snapshots/BenchmarkTest.json b/snapshots/BenchmarkTest.json
index 82f7d2ae..dedb663c 100644
--- a/snapshots/BenchmarkTest.json
+++ b/snapshots/BenchmarkTest.json
@@ -1,53 +1,53 @@
{
- "testERC20Transfer_AlchemyModularAccount": "179494",
- "testERC20Transfer_AlchemyModularAccount_AppSponsor": "176436",
- "testERC20Transfer_AlchemyModularAccount_ERC20SelfPay": "207779",
- "testERC20Transfer_Batch100_AlchemyModularAccount_AppSponsor": "8897167",
- "testERC20Transfer_Batch100_CoinbaseSmartWallet": "9952203",
- "testERC20Transfer_Batch100_CoinbaseSmartWallet_AppSponsor": "8787327",
- "testERC20Transfer_Batch100_CoinbaseSmartWallet_ERC20SelfPay": "11335605",
- "testERC20Transfer_Batch100_Safe4337": "11685484",
- "testERC20Transfer_Batch100_Safe4337_AppSponsor": "10198375",
- "testERC20Transfer_Batch100_Safe4337_ERC20SelfPay": "12757523",
- "testERC20Transfer_Batch100_ZerodevKernel_AppSponsor": "11427583",
- "testERC20Transfer_CoinbaseSmartWallet": "177855",
- "testERC20Transfer_CoinbaseSmartWallet_AppSponsor": "175259",
- "testERC20Transfer_CoinbaseSmartWallet_ERC20SelfPay": "204919",
- "testERC20Transfer_ERC4337MinimalAccount": "171509",
- "testERC20Transfer_ERC4337MinimalAccount_AppSponsor": "168500",
- "testERC20Transfer_ERC4337MinimalAccount_ERC20SelfPay": "199831",
- "testERC20Transfer_IthacaAccount": "128195",
- "testERC20Transfer_IthacaAccountWithSpendLimits": "193658",
- "testERC20Transfer_IthacaAccount_AppSponsor": "138709",
- "testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "144010",
- "testERC20Transfer_IthacaAccount_ERC20SelfPay": "128684",
- "testERC20Transfer_Safe4337": "197561",
- "testERC20Transfer_Safe4337_AppSponsor": "191725",
- "testERC20Transfer_Safe4337_ERC20SelfPay": "221464",
- "testERC20Transfer_ZerodevKernel": "207117",
- "testERC20Transfer_ZerodevKernel_AppSponsor": "204120",
- "testERC20Transfer_ZerodevKernel_ERC20SelfPay": "235489",
- "testERC20Transfer_batch100_AlchemyModularAccount": "10109066",
- "testERC20Transfer_batch100_AlchemyModularAccount_ERC20SelfPay": "11609298",
- "testERC20Transfer_batch100_IthacaAccount": "7545392",
- "testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8151496",
- "testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7977508",
- "testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7366652",
- "testERC20Transfer_batch100_ZerodevKernel": "12631318",
- "testERC20Transfer_batch100_ZerodevKernel_ERC20SelfPay": "14149937",
- "testNativeTransfer_AlchemyModularAccount": "180829",
- "testNativeTransfer_CoinbaseSmartWallet": "178916",
- "testNativeTransfer_IthacaAccount": "129551",
- "testNativeTransfer_IthacaAccount_AppSponsor": "140096",
- "testNativeTransfer_IthacaAccount_ERC20SelfPay": "137340",
- "testNativeTransfer_Safe4337": "198595",
- "testNativeTransfer_ZerodevKernel": "208635",
- "testUniswapV2Swap_AlchemyModularAccount": "238647",
- "testUniswapV2Swap_CoinbaseSmartWallet": "237451",
- "testUniswapV2Swap_ERC4337MinimalAccount": "230691",
- "testUniswapV2Swap_IthacaAccount": "187339",
- "testUniswapV2Swap_IthacaAccount_AppSponsor": "197817",
- "testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192628",
- "testUniswapV2Swap_Safe4337": "257333",
- "testUniswapV2Swap_ZerodevKernel": "266367"
+ "testERC20Transfer_AlchemyModularAccount": "159052",
+ "testERC20Transfer_AlchemyModularAccount_AppSponsor": "134278",
+ "testERC20Transfer_AlchemyModularAccount_ERC20SelfPay": "160169",
+ "testERC20Transfer_Batch100_AlchemyModularAccount_AppSponsor": "7053863",
+ "testERC20Transfer_Batch100_CoinbaseSmartWallet": "9801043",
+ "testERC20Transfer_Batch100_CoinbaseSmartWallet_AppSponsor": "6483523",
+ "testERC20Transfer_Batch100_CoinbaseSmartWallet_ERC20SelfPay": "8487729",
+ "testERC20Transfer_Batch100_Safe4337": "11165400",
+ "testERC20Transfer_Batch100_Safe4337_AppSponsor": "7525827",
+ "testERC20Transfer_Batch100_Safe4337_ERC20SelfPay": "9540939",
+ "testERC20Transfer_Batch100_ZerodevKernel_AppSponsor": "8956539",
+ "testERC20Transfer_CoinbaseSmartWallet": "150133",
+ "testERC20Transfer_CoinbaseSmartWallet_AppSponsor": "126009",
+ "testERC20Transfer_CoinbaseSmartWallet_ERC20SelfPay": "150241",
+ "testERC20Transfer_ERC4337MinimalAccount": "148602",
+ "testERC20Transfer_ERC4337MinimalAccount_AppSponsor": "123885",
+ "testERC20Transfer_ERC4337MinimalAccount_ERC20SelfPay": "149776",
+ "testERC20Transfer_IthacaAccount": "91666",
+ "testERC20Transfer_IthacaAccountWithSpendLimits": "113167",
+ "testERC20Transfer_IthacaAccount_AppSponsor": "99218",
+ "testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "104319",
+ "testERC20Transfer_IthacaAccount_ERC20SelfPay": "91967",
+ "testERC20Transfer_Safe4337": "163675",
+ "testERC20Transfer_Safe4337_AppSponsor": "136323",
+ "testERC20Transfer_Safe4337_ERC20SelfPay": "160610",
+ "testERC20Transfer_ZerodevKernel": "175447",
+ "testERC20Transfer_ZerodevKernel_AppSponsor": "150734",
+ "testERC20Transfer_ZerodevKernel_ERC20SelfPay": "176663",
+ "testERC20Transfer_batch100_AlchemyModularAccount": "10438358",
+ "testERC20Transfer_batch100_AlchemyModularAccount_ERC20SelfPay": "9221814",
+ "testERC20Transfer_batch100_IthacaAccount": "6201928",
+ "testERC20Transfer_batch100_IthacaAccount_AppSponsor": "6758228",
+ "testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "6565428",
+ "testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "6004328",
+ "testERC20Transfer_batch100_ZerodevKernel": "12332690",
+ "testERC20Transfer_batch100_ZerodevKernel_ERC20SelfPay": "11134785",
+ "testNativeTransfer_AlchemyModularAccount": "168453",
+ "testNativeTransfer_CoinbaseSmartWallet": "159248",
+ "testNativeTransfer_IthacaAccount": "101064",
+ "testNativeTransfer_IthacaAccount_AppSponsor": "108635",
+ "testNativeTransfer_IthacaAccount_ERC20SelfPay": "101365",
+ "testNativeTransfer_Safe4337": "172763",
+ "testNativeTransfer_ZerodevKernel": "184855",
+ "testUniswapV2Swap_AlchemyModularAccount": "210111",
+ "testUniswapV2Swap_CoinbaseSmartWallet": "201623",
+ "testUniswapV2Swap_ERC4337MinimalAccount": "199690",
+ "testUniswapV2Swap_IthacaAccount": "142728",
+ "testUniswapV2Swap_IthacaAccount_AppSponsor": "150244",
+ "testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "143029",
+ "testUniswapV2Swap_Safe4337": "215353",
+ "testUniswapV2Swap_ZerodevKernel": "226591"
}
\ No newline at end of file
diff --git a/src/Escrow.sol b/src/Escrow.sol
index a924bc59..6138cab9 100644
--- a/src/Escrow.sol
+++ b/src/Escrow.sol
@@ -132,12 +132,12 @@ contract Escrow is IEscrow {
for (uint256 i = 0; i < escrowIds.length; i++) {
Escrow storage _escrow = escrows[escrowIds[i]];
// If refund timestamp hasn't passed yet, then the refund is invalid.
- if (block.timestamp <= _escrow.refundTimestamp) {
+ if (block.timestamp > _escrow.refundTimestamp || msg.sender == _escrow.recipient) {
+ _refundDepositor(escrowIds[i], _escrow);
+ _refundRecipient(escrowIds[i], _escrow);
+ } else {
revert RefundInvalid();
}
-
- _refundDepositor(escrowIds[i], _escrow);
- _refundRecipient(escrowIds[i], _escrow);
}
}
@@ -147,10 +147,11 @@ contract Escrow is IEscrow {
for (uint256 i = 0; i < escrowIds.length; i++) {
Escrow storage _escrow = escrows[escrowIds[i]];
// If refund timestamp hasn't passed yet, then the refund is invalid.
- if (block.timestamp <= _escrow.refundTimestamp) {
+ if (block.timestamp > _escrow.refundTimestamp || msg.sender == _escrow.depositor) {
+ _refundDepositor(escrowIds[i], _escrow);
+ } else {
revert RefundInvalid();
}
- _refundDepositor(escrowIds[i], _escrow);
}
}
@@ -181,11 +182,11 @@ contract Escrow is IEscrow {
Escrow storage _escrow = escrows[escrowIds[i]];
// If settlement is still within the deadline, then refund is invalid.
- if (block.timestamp <= _escrow.refundTimestamp) {
+ if (block.timestamp > _escrow.refundTimestamp || msg.sender == _escrow.recipient) {
+ _refundRecipient(escrowIds[i], _escrow);
+ } else {
revert RefundInvalid();
}
-
- _refundRecipient(escrowIds[i], _escrow);
}
}
diff --git a/src/GuardedExecutor.sol b/src/GuardedExecutor.sol
index cf5eb502..4d655929 100644
--- a/src/GuardedExecutor.sol
+++ b/src/GuardedExecutor.sol
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
-import {ERC7821} from "solady/accounts/ERC7821.sol";
import {LibSort} from "solady/utils/LibSort.sol";
import {LibBytes} from "solady/utils/LibBytes.sol";
import {LibZip} from "solady/utils/LibZip.sol";
@@ -13,6 +12,7 @@ import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {FixedPointMathLib as Math} from "solady/utils/FixedPointMathLib.sol";
import {DateTimeLib} from "solady/utils/DateTimeLib.sol";
import {ICallChecker} from "./interfaces/ICallChecker.sol";
+import {ERC7821Ithaca as ERC7821} from "./libraries/ERC7821Ithaca.sol";
/// @title GuardedExecutor
/// @notice Mixin for spend limits and calldata execution guards.
@@ -401,7 +401,7 @@ abstract contract GuardedExecutor is ERC7821 {
checkKeyHashIsNonZero(keyHash)
{
if (keyHash != ANY_KEYHASH) {
- if (_isSuperAdmin(keyHash)) revert SuperAdminCanSpendAnything();
+ if (_isSuperAdmin(keyHash)) revert SuperAdminCanExecuteEverything();
}
// It is ok even if we don't check for `_isSelfExecute` here, as we will still
@@ -698,10 +698,10 @@ abstract contract GuardedExecutor is ERC7821 {
// Configurables
////////////////////////////////////////////////////////////////////////
- /// @dev To be overriden to return if `keyHash` corresponds to a super admin key.
+ /// @dev To be overridden to return if `keyHash` corresponds to a super admin key.
function _isSuperAdmin(bytes32 keyHash) internal view virtual returns (bool);
- /// @dev To be overriden to return the storage slot seed for a `keyHash`.
+ /// @dev To be overridden to return the storage slot seed for a `keyHash`.
function _getGuardedExecutorKeyStorageSeed(bytes32 keyHash)
internal
view
diff --git a/src/IthacaAccount.sol b/src/IthacaAccount.sol
index 0f0068ac..ddef33fe 100644
--- a/src/IthacaAccount.sol
+++ b/src/IthacaAccount.sol
@@ -274,8 +274,8 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
(bool isValid, bytes32 keyHash) = unwrapAndValidateSignature(digest, signature);
if (LibBit.and(keyHash != 0, isValid)) {
- isValid = _isSuperAdmin(keyHash)
- || _getKeyExtraStorage(keyHash).checkers.contains(msg.sender);
+ isValid =
+ _isSuperAdmin(keyHash) || _getKeyExtraStorage(keyHash).checkers.contains(msg.sender);
}
// `bytes4(keccak256("isValidSignature(bytes32,bytes)")) = 0x1626ba7e`.
// We use `0xffffffff` for invalid, in convention with the reference implementation.
@@ -399,7 +399,12 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
}
/// @dev Returns arrays of all (non-expired) authorized keys and their hashes.
- function getKeys() public view virtual returns (Key[] memory keys, bytes32[] memory keyHashes) {
+ function getKeys()
+ public
+ view
+ virtual
+ returns (Key[] memory keys, bytes32[] memory keyHashes)
+ {
uint256 totalCount = keyCount();
keys = new Key[](totalCount);
@@ -573,8 +578,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);
}
@@ -622,10 +628,12 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
if or(shr(64, t), lt(encodedIntent.length, 0x20)) { revert(0x00, 0x00) }
}
- if (!LibBit.and(
+ if (
+ !LibBit.and(
msg.sender == ORCHESTRATOR,
LibBit.or(intent.eoa == address(this), intent.payer == address(this))
- )) {
+ )
+ ) {
revert Unauthorized();
}
@@ -747,6 +755,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/MultiSigSigner.sol b/src/MultiSigSigner.sol
index 73d82df9..6f6fb272 100644
--- a/src/MultiSigSigner.sol
+++ b/src/MultiSigSigner.sol
@@ -12,7 +12,7 @@ contract MultiSigSigner is ISigner {
////////////////////////////////////////////////////////////////////////
/// @dev The magic value returned by `isValidSignatureWithKeyHash` when the signature is valid.
- /// - Calcualated as: bytes4(keccak256("isValidSignatureWithKeyHash(bytes32,bytes32,bytes)")
+ /// - Calculated as: bytes4(keccak256("isValidSignatureWithKeyHash(bytes32,bytes32,bytes)")
bytes4 internal constant _MAGIC_VALUE = 0x8afc93b4;
/// @dev The magic value returned by `isValidSignatureWithKeyHash` when the signature is invalid.
@@ -175,7 +175,7 @@ contract MultiSigSigner is ISigner {
/// for each owner key hash in the config.
/// - Signature of a multi-sig should be encoded as abi.encode(bytes[] memory ownerSignatures)
/// - For efficiency, place the signatures in the same order as the ownerKeyHashes in the config.
- /// - Failing owner signatures are ignored, as long as valid signaturs > threshold.
+ /// - Failing owner signatures are ignored, as long as valid signatures > threshold.
function isValidSignatureWithKeyHash(bytes32 digest, bytes32 keyHash, bytes memory signature)
public
view
diff --git a/src/Orchestrator.sol b/src/Orchestrator.sol
index 06586bcc..72815450 100644
--- a/src/Orchestrator.sol
+++ b/src/Orchestrator.sol
@@ -20,6 +20,7 @@ import {ICommon} from "./interfaces/ICommon.sol";
import {IFunder} from "./interfaces/IFunder.sol";
import {ISettler} from "./interfaces/ISettler.sol";
import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";
+import {IntentHelpers} from "./libraries/IntentHelpers.sol";
/// @title Orchestrator
/// @notice Enables atomic verification, gas compensation and execution across eoas.
@@ -41,7 +42,13 @@ import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";
/// This means once an Intent is signed, it is infeasible to
/// alter or rearrange it to force it to fail.
-contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGuardTransient {
+contract Orchestrator is
+ IOrchestrator,
+ EIP712,
+ CallContextChecker,
+ ReentrancyGuardTransient,
+ IntentHelpers
+{
using LibERC7579 for bytes32[];
using EfficientHashLib for bytes32[];
using LibBitmap for LibBitmap.Bitmap;
@@ -110,12 +117,12 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// @dev For EIP712 signature digest calculation for the `execute` function.
bytes32 public constant INTENT_TYPEHASH = keccak256(
- "Intent(bool multichain,address eoa,Call[] calls,uint256 nonce,address payer,address paymentToken,uint256 paymentMaxAmount,uint256 combinedGas,bytes[] encodedPreCalls,bytes[] encodedFundTransfers,address settler,uint256 expiry)Call(address to,uint256 value,bytes data)"
+ "Intent(Call[] calls,address eoa,uint256 nonce,address payer,address paymentToken,uint256 paymentMaxAmount,uint256 combinedGas,uint256 expiry)Call(address to,uint256 value,bytes data)"
);
/// @dev For EIP712 signature digest calculation for SignedCalls
bytes32 public constant SIGNED_CALL_TYPEHASH = keccak256(
- "SignedCall(bool multichain,address eoa,Call[] calls,uint256 nonce)Call(address to,uint256 value,bytes data)"
+ "SignedCall(address eoa,Call[] calls,uint256 nonce)Call(address to,uint256 value,bytes data)"
);
/// @dev For EIP712 signature digest calculation for the `execute` function.
@@ -127,6 +134,10 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// This constant is a pun for "chain ID 0".
uint16 public constant MULTICHAIN_NONCE_PREFIX = 0xc1d0;
+ /// @dev Nonce prefix to signal that the payload should use merkle verification.
+ /// This constant is "mv" in hex.
+ uint16 public constant MERKLE_VERIFICATION = 0x6D76;
+
/// @dev For ensuring that the remaining gas is sufficient for a self-call with
/// overhead for cleaning up after the self-call. This also has an added benefit
/// of preventing the censorship vector of calling `execute` in a very deep call-stack.
@@ -195,7 +206,7 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// If sufficient gas is provided, returns an error selector that is non-zero
/// if there is an error during the payment, verification, and call execution.
function execute(bytes calldata encodedIntent)
- public
+ external
payable
virtual
nonReentrant
@@ -211,7 +222,6 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
public
payable
virtual
- nonReentrant
returns (bytes4[] memory errs)
{
// This allocation and loop was initially in assembly, but I've normified it for now.
@@ -220,7 +230,7 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
// We reluctantly use regular Solidity to access `encodedIntents[i]`.
// This generates an unnecessary check for `i < encodedIntents.length`, but helps
// generate all the implicit calldata bound checks on `encodedIntents[i]`.
- (, errs[i]) = _execute(encodedIntents[i], 0, _NORMAL_MODE_FLAG);
+ errs[i] = this.execute(encodedIntents[i]);
}
}
@@ -232,11 +242,16 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// But the balance of tx.origin has to be greater than or equal to type(uint192).max, to prove that a state override has been made offchain,
/// and this is not an onchain call. This mode has been added so that receipt logs can be generated for `eth_simulateV1`
/// @return gasUsed The amount of gas used by the execution. (Only returned if `isStateOverride` is true)
- function simulateExecute(
- bool isStateOverride,
- uint256 combinedGasOverride,
- bytes calldata encodedIntent
- ) external payable returns (uint256) {
+ function simulateExecute(bytes calldata encodedIntent) external payable returns (uint256) {
+ bool isStateOverride;
+ uint256 combinedGasOverride;
+ assembly ("memory-safe") {
+ let endOfBytes := add(encodedIntent.offset, encodedIntent.length)
+ isStateOverride := calldataload(sub(endOfBytes, 0x40))
+ combinedGasOverride := calldataload(sub(endOfBytes, 0x20))
+ encodedIntent.length := sub(encodedIntent.length, 0x40)
+ }
+
// If Simulation Fails, then it will revert here.
(uint256 gUsed, bytes4 err) =
_execute(encodedIntent, combinedGasOverride, _SIMULATION_MODE_FLAG);
@@ -260,33 +275,7 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
}
}
- /// @dev Extracts the Intent from the calldata bytes, with minimal checks.
- function _extractIntent(bytes calldata encodedIntent)
- internal
- view
- virtual
- returns (Intent calldata i)
- {
- // This function does NOT allocate memory to avoid quadratic memory expansion costs.
- // Otherwise, it will be unfair to the Intents at the back of the batch.
-
- // `dynamicStructInCalldata` internally performs out-of-bounds checks.
- bytes calldata intentCalldata = LibBytes.dynamicStructInCalldata(encodedIntent, 0x00);
- assembly ("memory-safe") {
- i := intentCalldata.offset
- }
- // These checks are included for more safety: Swiss Cheese Model.
- // Ensures that all the dynamic children in `encodedIntent` are contained.
- LibBytes.checkInCalldata(i.executionData, intentCalldata);
- LibBytes.checkInCalldata(i.encodedPreCalls, intentCalldata);
- LibBytes.checkInCalldata(i.encodedFundTransfers, intentCalldata);
- LibBytes.checkInCalldata(i.funderSignature, intentCalldata);
- LibBytes.checkInCalldata(i.settlerContext, intentCalldata);
- LibBytes.checkInCalldata(i.signature, intentCalldata);
- LibBytes.checkInCalldata(i.paymentSignature, intentCalldata);
- }
/// @dev Extracts the PreCall from the calldata bytes, with minimal checks.
-
function _extractPreCall(bytes calldata encodedPreCall)
internal
virtual
@@ -303,7 +292,7 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// @dev Executes a single encoded intent.
/// @dev If flags is non-zero, then all errors are bubbled up.
/// Currently there can only be 2 modes - simulation mode, and execution mode.
- /// But we use a uint256 for efficient stack operations, and more flexiblity in the future.
+ /// But we use a uint256 for efficient stack operations, and more flexibility in the future.
/// Note: We keep the flags in the stack/memory (TSTORE doesn't work) to make sure they are reset in each new call context,
/// to provide protection against attacks which could spoof the execute function to believe it is in simulation mode.
function _execute(bytes calldata encodedIntent, uint256 combinedGasOverride, uint256 flags)
@@ -311,12 +300,11 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
virtual
returns (uint256 gUsed, bytes4 err)
{
- Intent calldata i = _extractIntent(encodedIntent);
-
- uint256 g = Math.coalesce(uint96(combinedGasOverride), i.combinedGas);
+ uint256 g = Math.coalesce(uint96(combinedGasOverride), _getCombinedGas());
uint256 gStart = gasleft();
+ address eoa = _getEoa();
- if (i.paymentAmount > i.paymentMaxAmount) {
+ if (_getPaymentAmount() > _getPaymentMaxAmount()) {
err = PaymentError.selector;
if (flags == _SIMULATION_MODE_FLAG) {
@@ -328,15 +316,20 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
// Check if there's sufficient gas left for the gas-limited self calls
// via the 63/64 rule. This is for gas estimation. If the total amount of gas
// for the whole transaction is insufficient, revert.
- if (((gasleft() * 63) >> 6) < Math.saturatingAdd(g, _INNER_GAS_OVERHEAD)) {
+ uint256 gasAvailable = (gasleft() * 63) >> 6;
+ uint256 gasRequired = Math.saturatingAdd(g, _INNER_GAS_OVERHEAD);
+
+ if (gasAvailable < gasRequired) {
if (flags != _SIMULATION_MODE_FLAG) {
revert InsufficientGas();
}
}
}
- if (i.supportedAccountImplementation != address(0)) {
- if (accountImplementationOf(i.eoa) != i.supportedAccountImplementation) {
+ address accountImpl = _getSupportedAccountImplementation();
+ if (accountImpl != address(0)) {
+ address currentImpl = accountImplementationOf(eoa);
+ if (currentImpl != accountImpl) {
err = UnsupportedAccountImplementation.selector;
if (flags == _SIMULATION_MODE_FLAG) {
revert UnsupportedAccountImplementation();
@@ -344,18 +337,16 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
}
}
- address payer = Math.coalesce(i.payer, i.eoa);
+ address payer = Math.coalesce(_getPayer(), eoa);
// Early skip the entire pay-verify-call workflow if the payer lacks tokens,
// so that less gas is wasted when the Intent fails.
// For multi chain mode, we skip this check, as the funding happens inside the self call.
- if (!i.isMultichain && LibBit.and(i.paymentAmount != 0, err == 0)) {
- if (TokenTransferLib.balanceOf(i.paymentToken, payer) < i.paymentAmount) {
- err = PaymentError.selector;
+ if (TokenTransferLib.balanceOf(_getPaymentToken(), payer) < _getPaymentAmount()) {
+ err = PaymentError.selector;
- if (flags == _SIMULATION_MODE_FLAG) {
- revert PaymentError();
- }
+ if (flags == _SIMULATION_MODE_FLAG) {
+ revert PaymentError();
}
}
@@ -364,11 +355,14 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
assembly ("memory-safe") {
let m := mload(0x40) // Grab the free memory pointer.
if iszero(err) {
- // Copy the encoded user op to the memory to be ready to pass to the self call.
- calldatacopy(add(m, 0x40), encodedIntent.offset, encodedIntent.length)
mstore(m, 0x00000000) // `selfCallPayVerifyCall537021665()`.
// The word after the function selector contains the simulation flags.
mstore(add(m, 0x20), flags)
+ // Copy the encoded user op to the memory to be ready to pass to the self call.
+ // We skip adding the calldata offset and length since we don't read that, but
+ // add `flags` in the first slot.
+ calldatacopy(add(m, 0x60), encodedIntent.offset, encodedIntent.length)
+
mstore(0x00, 0) // Zeroize the return slot.
// To prevent griefing, we need to do a non-reverting gas-limited self call.
@@ -380,26 +374,27 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
address(),
0,
add(m, 0x1c),
- add(encodedIntent.length, 0x24),
+ add(encodedIntent.length, 0x44),
0x00,
0x20
)
err := mload(0x00) // The self call will do another self call to execute.
-
- if iszero(selfCallSuccess) {
- // If it is a simulation, we simply revert with the full error.
- if eq(flags, _SIMULATION_MODE_FLAG) {
- returndatacopy(mload(0x40), 0x00, returndatasize())
- revert(mload(0x40), returndatasize())
- }
-
- // If we don't get an error selector, then we set this one.
- if iszero(err) { err := shl(224, 0xad4db224) } // `VerifiedCallError()`.
+ }
+ }
+ assembly ("memory-safe") {
+ if iszero(selfCallSuccess) {
+ // If it is a simulation, we simply revert with the full error.
+ if eq(flags, _SIMULATION_MODE_FLAG) {
+ returndatacopy(mload(0x40), 0x00, returndatasize())
+ revert(mload(0x40), returndatasize())
}
+
+ // If we don't get an error selector, then we set this one.
+ if iszero(err) { err := shl(224, 0xad4db224) } // `VerifiedCallError()`.
}
}
- emit IntentExecuted(i.eoa, i.nonce, selfCallSuccess, err);
+ emit IntentExecuted(_getEoa(), _getNonce(), selfCallSuccess, err);
if (selfCallSuccess) {
gUsed = Math.rawSub(gStart, gasleft());
}
@@ -432,25 +427,36 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
function selfCallPayVerifyCall537021665() public payable {
require(msg.sender == address(this));
- Intent calldata i;
uint256 flags;
assembly ("memory-safe") {
- i := add(0x24, calldataload(0x24))
flags := calldataload(0x04)
}
// Check if intent has expired (only if expiry is set)
// If expiry timestamp is set to 0, then expiry is considered to be infinite.
- if (i.expiry != 0 && block.timestamp > i.expiry) {
- revert IntentExpired();
+ {
+ uint256 expiry = _getExpiry();
+ if (expiry != 0 && block.timestamp > expiry) {
+ revert IntentExpired();
+ }
}
- address eoa = i.eoa;
- uint256 nonce = i.nonce;
- bytes32 digest = _computeDigest(i);
+ address eoa = _getEoa();
+
+ // Start a calldata pointer to traverse all the inner dynamic bytes within. Every time we get the next dynamic bytes,
+ // the pointer is advanced. A memory pointer gives some efficiency and will produce relatively clean top-level code.
+ CalldataPointer memory ptr;
+ bytes32 digest = _computeDigest(ptr);
- _fund(eoa, i.funder, digest, i.encodedFundTransfers, i.funderSignature);
+ {
+ bytes calldata fundData = _getNextBytes(ptr);
+ if (fundData.length > 0) {
+ (address funder, bytes calldata sig, bytes[] calldata transfers) =
+ _parseFundData(fundData);
+ _fund(_getEoa(), funder, digest, transfers, sig);
+ }
+ }
// The chicken and egg problem:
// A off-chain simulation of a successful Intent may not guarantee on-chain success.
// The state may change in the window between simulation and actual on-chain execution.
@@ -466,57 +472,82 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
// simulation, and suggests banning users that intentionally grief the simulation.
// Handle the sub Intents after initialize (if any), and before the `_verify`.
- if (i.encodedPreCalls.length != 0) _handlePreCalls(eoa, i.payer, flags, i.encodedPreCalls);
-
+ {
+ bytes calldata preCallsBytes = _getNextBytes(ptr);
+ if (preCallsBytes.length > 0) {
+ _handlePreCalls(eoa, _getPayer(), flags, preCallsBytes);
+ }
+ }
// If `_verify` is invalid, just revert.
// The verification gas is determined by `executionData` and the account logic.
// Off-chain simulation of `_verify` should suffice, provided that the eoa's
// account is not changed, and the `keyHash` is not revoked
// in the window between off-chain simulation and on-chain execution.
-
- bool isValid;
bytes32 keyHash;
- if (i.isMultichain) {
- // For multi chain intents, we have to verify using merkle sigs.
- (isValid, keyHash) = _verifyMerkleSig(digest, eoa, i.signature);
-
- // If this is an output intent, then send the digest as the settlementId
- // on all input chains.
- if (i.encodedFundTransfers.length > 0) {
- // Output intent
- ISettler(i.settler).send(digest, i.settlerContext);
+ {
+ bool isValid;
+ bytes calldata signature = _getNextBytes(ptr);
+
+ uint256 nonce = _getNonce();
+ (isValid, keyHash) = _verify(digest, eoa, signature);
+
+ if (nonce >> 240 == MERKLE_VERIFICATION) {
+ bytes calldata settlerData = _getNextBytes(ptr);
+ // If this is an output intent, then send the digest as the settlementId
+ // on all input chains.
+ if (settlerData.length > 0) {
+ // Output intent - first 32 bytes of settler data contains the settler address
+ // Then, it contains 2 offsets then the real data
+ ISettler(address(uint160(uint256(bytes32(settlerData[:32])))))
+ .send(digest, settlerData[96:]);
+ }
}
- } else {
- (isValid, keyHash) = _verify(digest, eoa, i.signature);
- }
- if (flags == _SIMULATION_MODE_FLAG) {
- isValid = true;
- }
+ if (flags == _SIMULATION_MODE_FLAG) {
+ isValid = true;
+ }
- if (!isValid) revert VerificationError();
+ if (!isValid) {
+ revert VerificationError();
+ }
- _checkAndIncrementNonce(eoa, nonce);
+ _checkAndIncrementNonce(eoa, nonce);
+ }
// Payment
// If `_pay` fails, just revert.
// Off-chain simulation of `_pay` should suffice,
// provided that the token balance does not decrease in the window between
// off-chain simulation and on-chain execution.
- if (i.paymentAmount != 0) _pay(keyHash, digest, i);
+ {
+ uint256 paymentAmount = _getPaymentAmount();
+ bytes calldata paymentSignature = _getNextBytes(ptr);
+ if (paymentAmount != 0) {
+ _pay(
+ paymentAmount,
+ keyHash,
+ digest,
+ eoa,
+ _getPayer(),
+ _getPaymentToken(),
+ _getPaymentRecipient(),
+ paymentSignature
+ );
+ }
+ }
// This re-encodes the ERC7579 `executionData` with the optional `opData`.
// We expect that the account supports ERC7821
// (an extension of ERC7579 tailored for 7702 accounts).
- bytes memory data = LibERC7579.reencodeBatchAsExecuteCalldata(
+ bytes memory executeData = LibERC7579.reencodeBatchAsExecuteCalldata(
hex"01000000000078210001", // ERC7821 batch execution mode.
- i.executionData,
+ _getExecutionData(),
abi.encode(keyHash) // `opData`.
);
assembly ("memory-safe") {
mstore(0x00, 0) // Zeroize the return slot.
- if iszero(call(gas(), eoa, 0, add(0x20, data), mload(data), 0x00, 0x20)) {
+ if iszero(call(gas(), eoa, 0, add(0x20, executeData), mload(executeData), 0x00, 0x20)) {
if eq(flags, _SIMULATION_MODE_FLAG) {
returndatacopy(mload(0x40), 0x00, returndatasize())
revert(mload(0x40), returndatasize())
@@ -539,10 +570,15 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
address parentEOA,
address payer,
uint256 flags,
- bytes[] calldata encodedPreCalls
+ bytes calldata encodedPreCalls
) internal virtual {
- for (uint256 j; j < encodedPreCalls.length; ++j) {
- SignedCall calldata p = _extractPreCall(encodedPreCalls[j]);
+ bytes[] calldata calls;
+ assembly ("memory-safe") {
+ calls.length := calldataload(add(encodedPreCalls.offset, 0x20))
+ calls.offset := add(encodedPreCalls.offset, 0x40)
+ }
+ for (uint256 j; j < calls.length; ++j) {
+ SignedCall calldata p = _extractPreCall(calls[j]);
address eoa = Math.coalesce(p.eoa, parentEOA);
uint256 nonce = p.nonce;
@@ -598,28 +634,6 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
// Multi Chain Functions
////////////////////////////////////////////////////////////////////////
- /// @dev Verifies the merkle sig for the multi chain intents.
- /// - Note: Each leaf of the merkle tree should be a standard intent digest, computed with chainId.
- /// - Leaf intents do NOT need to have the multichain nonce prefix.
- /// - The signature for multi chain intents using merkle verification is encoded as:
- /// - bytes signature = abi.encode(bytes32[] memory proof, bytes32 root, bytes memory rootSig)
- function _verifyMerkleSig(bytes32 digest, address eoa, bytes memory signature)
- internal
- view
- returns (bool isValid, bytes32 keyHash)
- {
- (bytes32[] memory proof, bytes32 root, bytes memory rootSig) =
- abi.decode(signature, (bytes32[], bytes32, bytes));
-
- if (MerkleProofLib.verify(proof, root, digest)) {
- (isValid, keyHash) = IIthacaAccount(eoa).unwrapAndValidateSignature(root, rootSig);
-
- return (isValid, keyHash);
- }
-
- return (false, bytes32(0));
- }
-
/// @dev Funds the eoa with with the encoded fund transfers, before executing the intent.
/// - For ERC20 tokens, the funder needs to approve the orchestrator to pull funds.
/// - For native assets like ETH, the funder needs to transfer the funds to the orchestrator
@@ -629,7 +643,7 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
address eoa,
address funder,
bytes32 digest,
- bytes[] memory encodedFundTransfers,
+ bytes[] calldata encodedFundTransfers,
bytes memory funderSignature
) internal virtual {
// Note: The fund function is mostly only used in the multi chain mode.
@@ -664,51 +678,59 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// @dev Makes the `eoa` perform a payment to the `paymentRecipient` directly.
/// This reverts if the payment is insufficient or fails. Otherwise returns nothing.
- function _pay(bytes32 keyHash, bytes32 digest, Intent calldata i) internal virtual {
- uint256 paymentAmount = i.paymentAmount;
- uint256 requiredBalanceAfter = Math.saturatingAdd(
- TokenTransferLib.balanceOf(i.paymentToken, i.paymentRecipient), paymentAmount
- );
+ function _pay(
+ uint256 paymentAmount,
+ bytes32 keyHash,
+ bytes32 intentDigest,
+ address eoa,
+ address payer,
+ address paymentToken,
+ address paymentRecipient,
+ bytes calldata paymentSignature
+ ) internal virtual {
+ uint256 currentBalance = TokenTransferLib.balanceOf(paymentToken, paymentRecipient);
- address payer = Math.coalesce(i.payer, i.eoa);
+ uint256 requiredBalanceAfter = Math.saturatingAdd(currentBalance, paymentAmount);
// Call the pay function on the account contract
// Equivalent Solidity code:
// IIthacaAccount(payer).pay(paymentAmount, keyHash, digest, abi.encode(i));
// Gas Savings:
// Saves ~2k gas for normal use cases, by avoiding abi.encode and solidity external call overhead
+ address callee = Math.coalesce(payer, eoa);
+
assembly ("memory-safe") {
let m := mload(0x40) // Load the free memory pointer
- mstore(m, 0xf81d87a7) // `pay(uint256,bytes32,bytes32,bytes)`
- mstore(add(m, 0x20), paymentAmount) // Add payment amount as first param
+ mstore(m, 0x38e11b2a) // `pay(uint256,bytes32,bytes32,address,address,address,address,bytes)`
+ mstore(add(m, 0x20), paymentAmount) // Add paymentAmount as first param
mstore(add(m, 0x40), keyHash) // Add keyHash as second param
- mstore(add(m, 0x60), digest) // Add digest as third param
- mstore(add(m, 0x80), 0x80) // Add offset of encoded Intent as third param
-
- let encodedSize := sub(calldatasize(), i)
+ mstore(add(m, 0x60), intentDigest) // Add intentDigest as third param
+ mstore(add(m, 0x80), eoa) // Add eoa as fourth param
+ mstore(add(m, 0xa0), payer) // Add payer as fifth param
+ mstore(add(m, 0xc0), paymentToken) // Add paymentToken as sixth param
+ mstore(add(m, 0xe0), paymentRecipient) // Add paymentRecipient as seventh param
+ mstore(add(m, 0x100), 0x100) // Add offset for paymentSignature as eighth param
- mstore(add(m, 0xa0), add(encodedSize, 0x20)) // Store length of encoded Intent at offset.
- mstore(add(m, 0xc0), 0x20) // Offset at which the Intent struct starts in encoded Intent.
-
- // Copy the intent data to memory
- calldatacopy(add(m, 0xe0), i, encodedSize)
+ // Store paymentSignature length and data
+ mstore(add(m, 0x120), paymentSignature.length)
+ calldatacopy(add(m, 0x140), paymentSignature.offset, paymentSignature.length)
// We revert here, so that if the payment fails, the execution is also reverted.
// The revert for payment is caught inside the selfCallPayVerify function.
if iszero(
call(
gas(), // gas
- payer, // address
+ callee, // address
0, // value
add(m, 0x1c), // input memory offset
- add(0xc4, encodedSize), // input size
+ add(0x124, paymentSignature.length), // input size
0x00, // output memory offset
0x20 // output size
)
) { revert(0x00, 0x20) }
}
- if (TokenTransferLib.balanceOf(i.paymentToken, i.paymentRecipient) < requiredBalanceAfter) {
+ if (TokenTransferLib.balanceOf(paymentToken, paymentRecipient) < requiredBalanceAfter) {
revert PaymentError();
}
}
@@ -752,42 +774,39 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
/// @dev Computes the EIP712 digest for the PreCall.
function _computeDigest(SignedCall calldata p) internal view virtual returns (bytes32) {
- bool isMultichain = p.nonce >> 240 == MULTICHAIN_NONCE_PREFIX;
// To avoid stack-too-deep. Faster than a regular Solidity array anyways.
- bytes32[] memory f = EfficientHashLib.malloc(5);
+ bytes32[] memory f = EfficientHashLib.malloc(4);
f.set(0, SIGNED_CALL_TYPEHASH);
- f.set(1, LibBit.toUint(isMultichain));
- f.set(2, uint160(p.eoa));
- f.set(3, _executionDataHash(p.executionData));
- f.set(4, p.nonce);
+ f.set(1, uint160(p.eoa));
+ f.set(2, _executionDataHash(p.executionData));
+ f.set(3, p.nonce);
- return isMultichain ? _hashTypedDataSansChainId(f.hash()) : _hashTypedData(f.hash());
+ return p.nonce >> 240 == MULTICHAIN_NONCE_PREFIX
+ ? _hashTypedDataSansChainId(f.hash())
+ : _hashTypedData(f.hash());
}
- /// @dev Computes the EIP712 digest for the Intent.
- /// If the the nonce starts with `MULTICHAIN_NONCE_PREFIX`,
- /// the digest will be computed without the chain ID.
- /// Otherwise, the digest will be computed with the chain ID.
- function _computeDigest(Intent calldata i) internal view virtual returns (bytes32) {
- bool isMultichain = i.nonce >> 240 == MULTICHAIN_NONCE_PREFIX;
+ /// @dev Computes the EIP712 digest for the Intent
+ /// @dev Also updates the passed in CalldataPointer to point to the start of the next dynamic bytes.
+ function _computeDigest(CalldataPointer memory p) internal view virtual returns (bytes32) {
+ bytes32 digest = INTENT_TYPEHASH;
- // To avoid stack-too-deep. Faster than a regular Solidity array anyways.
- bytes32[] memory f = EfficientHashLib.malloc(13);
- f.set(0, INTENT_TYPEHASH);
- f.set(1, LibBit.toUint(isMultichain));
- f.set(2, uint160(i.eoa));
- f.set(3, _executionDataHash(i.executionData));
- f.set(4, i.nonce);
- f.set(5, uint160(i.payer));
- f.set(6, uint160(i.paymentToken));
- f.set(7, i.paymentMaxAmount);
- f.set(8, i.combinedGas);
- f.set(9, _encodedArrHash(i.encodedPreCalls));
- f.set(10, _encodedArrHash(i.encodedFundTransfers));
- f.set(11, uint160(i.settler));
- f.set(12, i.expiry);
-
- return isMultichain ? _hashTypedDataSansChainId(f.hash()) : _hashTypedData(f.hash());
+ assembly ("memory-safe") {
+ let fmp := mload(0x40)
+ mstore(fmp, digest)
+ let length := calldataload(_EXECUTION_DATA_OFFSET)
+ calldatacopy(add(fmp, 0x20), add(_EXECUTION_DATA_OFFSET, 0x20), length)
+ mstore(add(fmp, 0x20), keccak256(add(fmp, 0x20), length))
+ calldatacopy(add(fmp, 0x40), _EOA_OFFSET, 224) // copy 7 words
+ digest := keccak256(fmp, 288) // hash 9 words, inc typehash
+
+ // update the offset for the memory ptr
+ mstore(p, add(add(_EXECUTION_DATA_OFFSET, 0x20), length))
+ }
+
+ return _getNonce() >> 240 == MULTICHAIN_NONCE_PREFIX
+ ? _hashTypedDataSansChainId(digest)
+ : _hashTypedData(digest);
}
/// @dev Helper function to return the hash of the `execuctionData`.
@@ -840,7 +859,7 @@ contract Orchestrator is IOrchestrator, EIP712, CallContextChecker, ReentrancyGu
returns (string memory name, string memory version)
{
name = "Orchestrator";
- version = "0.5.5";
+ version = "0.5.6";
}
////////////////////////////////////////////////////////////////////////
diff --git a/src/SimpleFunder.sol b/src/SimpleFunder.sol
index e74947fb..7109f028 100644
--- a/src/SimpleFunder.sol
+++ b/src/SimpleFunder.sol
@@ -58,7 +58,7 @@ contract SimpleFunder is EIP712, Ownable, IFunder {
returns (string memory name, string memory version)
{
name = "SimpleFunder";
- version = "0.1.8";
+ version = "0.1.9";
}
////////////////////////////////////////////////////////////////////////
@@ -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();
}
@@ -183,7 +182,7 @@ contract SimpleFunder is EIP712, Ownable, IFunder {
if gt(amount, allowance) {
mstore(m, 0x095ea7b3) // `approve(address,uint256)`.
mstore(add(m, 0x20), caller())
- mstore(add(m, 0x40), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) // type(uint256).max
+ mstore(add(m, 0x40), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) // 20-byte all-ones sentinel (2^160-1), not uint256 max
// Orchestrator checks for token transfer success, so we don't need to check it here.
pop(call(gas(), token, 0, add(m, 0x1c), 0x44, 0x00, 0x00))
}
diff --git a/src/Simulator.sol b/src/Simulator.sol
index 1e468d6b..2a5e2a76 100644
--- a/src/Simulator.sol
+++ b/src/Simulator.sol
@@ -4,10 +4,12 @@ pragma solidity ^0.8.23;
import {ICommon} from "./interfaces/ICommon.sol";
import {IMulticall3} from "./interfaces/IMulticall3.sol";
import {FixedPointMathLib as Math} from "solady/utils/FixedPointMathLib.sol";
+import {MockOrchestrator} from "../test/utils/mocks/MockOrchestrator.sol";
+import {IntentHelpers} from "./libraries/IntentHelpers.sol";
/// @title Simulator
/// @notice A separate contract for calling the Orchestrator contract solely for gas simulation.
-contract Simulator {
+contract Simulator is IntentHelpers {
////////////////////////////////////////////////////////////////////////
// EIP-5267 Support
////////////////////////////////////////////////////////////////////////
@@ -54,15 +56,23 @@ contract Simulator {
/// @dev Updates the payment amounts for the Intent passed in.
function _updatePaymentAmounts(
- ICommon.Intent memory u,
+ bytes memory u,
uint256 gas,
uint8 paymentPerGasPrecision,
uint256 paymentPerGas
) internal pure {
uint256 paymentAmount = Math.fullMulDiv(gas, paymentPerGas, 10 ** paymentPerGasPrecision);
- u.paymentAmount += paymentAmount;
- u.paymentMaxAmount += paymentAmount;
+ // 36 because we don't have the calldata offset, or the function selector
+ uint256 paymentOffset = _PAYMENT_AMOUNT_OFFSET - 36;
+ uint256 paymentMaxOffset = _PAYMENT_MAX_AMOUNT_OFFSET - 36;
+ assembly ("memory-safe") {
+ let currentPaymentAmount := mload(add(u, paymentOffset))
+ mstore(add(u, paymentOffset), add(currentPaymentAmount, paymentAmount))
+
+ let currentPaymentMaxAmount := mload(add(u, paymentMaxOffset))
+ mstore(add(u, paymentMaxOffset), add(currentPaymentMaxAmount, paymentAmount))
+ }
}
/// @dev Performs a call to the Orchestrator, and returns the gas used by the Intent.
@@ -101,10 +111,8 @@ contract Simulator {
bytes calldata encodedIntent
) internal freeTempMemory returns (uint256) {
bytes memory data = abi.encodeWithSignature(
- "simulateExecute(bool,uint256,bytes)",
- isStateOverride,
- combinedGasOverride,
- encodedIntent
+ "simulateExecute(bytes)",
+ abi.encodePacked(encodedIntent, isStateOverride, combinedGasOverride)
);
return _callOrchestrator(oc, isStateOverride, data);
}
@@ -115,13 +123,10 @@ contract Simulator {
address oc,
bool isStateOverride,
uint256 combinedGasOverride,
- ICommon.Intent memory u
+ bytes memory u
) internal freeTempMemory returns (uint256) {
bytes memory data = abi.encodeWithSignature(
- "simulateExecute(bool,uint256,bytes)",
- isStateOverride,
- combinedGasOverride,
- abi.encode(u)
+ "simulateExecute(bytes)", abi.encodePacked(u, isStateOverride, combinedGasOverride)
);
return _callOrchestrator(oc, isStateOverride, data);
}
@@ -257,7 +262,7 @@ contract Simulator {
/// @dev The closer this number is to 10_000, the more precise combined gas will be. But more iterations will be needed.
/// @dev This number should always be > 10_000, to get correct results.
//// If the increment is too small, the function might run out of gas while finding the combined gas value.
- /// @param encodedIntent The encoded user operation
+ /// @param calldataIntent The encoded intent
/// @return gasUsed The gas used in the successful simulation
/// @return combinedGas The first combined gas value that gives a successful simulation.
/// This function reverts if the primary simulation run with max combinedGas fails.
@@ -268,10 +273,10 @@ contract Simulator {
uint8 paymentPerGasPrecision,
uint256 paymentPerGas,
uint256 combinedGasIncrement,
- bytes calldata encodedIntent
+ bytes calldata calldataIntent
) public payable virtual returns (uint256 gasUsed, uint256 combinedGas) {
// 1. Primary Simulation Run to get initial gasUsed value with combinedGasOverride
- gasUsed = _callOrchestratorCalldata(oc, false, type(uint256).max, encodedIntent);
+ gasUsed = _callOrchestratorCalldata(oc, false, type(uint256).max, calldataIntent);
// If the simulation failed, bubble up the full revert.
assembly ("memory-safe") {
@@ -282,15 +287,21 @@ contract Simulator {
}
}
- // Update payment amounts using the gasUsed value
- ICommon.Intent memory u = abi.decode(encodedIntent, (ICommon.Intent));
+ bytes memory memoryIntent = calldataIntent;
- u.combinedGas += gasUsed;
+ // 36 because we don't have the calldata offset, or the function selector
+ uint256 offset = _COMBINED_GAS_OFFSET - 36;
+ assembly ("memory-safe") {
+ let currentCombinedGas := mload(add(memoryIntent, offset))
+ let newCombinedGas := add(currentCombinedGas, gasUsed)
+ mstore(add(memoryIntent, offset), newCombinedGas)
+ combinedGas := newCombinedGas
+ }
- _updatePaymentAmounts(u, u.combinedGas, paymentPerGasPrecision, paymentPerGas);
+ _updatePaymentAmounts(memoryIntent, combinedGas, paymentPerGasPrecision, paymentPerGas);
while (true) {
- gasUsed = _callOrchestratorMemory(oc, false, 0, u);
+ gasUsed = _callOrchestratorMemory(oc, false, 0, memoryIntent);
// If the simulation failed, bubble up the full revert.
assembly ("memory-safe") {
@@ -305,22 +316,34 @@ contract Simulator {
}
if (gasUsed != 0) {
- return (gasUsed, u.combinedGas);
+ // Get current combinedGas from bytes
+ assembly ("memory-safe") {
+ combinedGas := mload(add(memoryIntent, offset))
+ }
+ return (gasUsed, combinedGas);
}
- uint256 gasIncrement = Math.mulDiv(u.combinedGas, combinedGasIncrement, 10_000);
+ // Get current combinedGas and calculate increment
+ assembly ("memory-safe") {
+ combinedGas := mload(add(memoryIntent, offset))
+ }
+ uint256 gasIncrement = Math.mulDiv(combinedGas, combinedGasIncrement, 10_000);
- _updatePaymentAmounts(u, gasIncrement, paymentPerGasPrecision, paymentPerGas);
+ _updatePaymentAmounts(memoryIntent, gasIncrement, paymentPerGasPrecision, paymentPerGas);
- // Step up the combined gas, until we see a simulation passing
- u.combinedGas += gasIncrement;
+ // Update combinedGas with increment using assembly
+ assembly ("memory-safe") {
+ let currentCombinedGas := mload(add(memoryIntent, offset))
+ let newCombinedGas := add(currentCombinedGas, gasIncrement)
+ mstore(add(memoryIntent, offset), newCombinedGas)
+ }
}
}
/// @dev Same as simulateCombinedGas, but with an additional verification run
/// that generates a successful non reverting state override simulation.
/// Which can be used in eth_simulateV1 to get the trace.\
- /// @param combinedGasVerificationOffset is a static value that is added after a succesful combinedGas is found.
+ /// @param combinedGasVerificationOffset is a static value that is added after a successful combinedGas is found.
/// This can be used to account for variations in sig verification gas, for keytypes like P256.
/// @param paymentPerGasPrecision The precision of the payment per gas value.
/// paymentAmount = gas * paymentPerGas / (10 ** paymentPerGasPrecision)
@@ -338,15 +361,18 @@ contract Simulator {
combinedGas += combinedGasVerificationOffset;
- ICommon.Intent memory u = abi.decode(encodedIntent, (ICommon.Intent));
+ bytes memory encodedIntentCopy = encodedIntent;
- _updatePaymentAmounts(u, combinedGas, paymentPerGasPrecision, paymentPerGas);
+ _updatePaymentAmounts(encodedIntentCopy, combinedGas, paymentPerGasPrecision, paymentPerGas);
- u.combinedGas = combinedGas;
+ // 36 because we don't have the calldata offset, or the function selector
+ uint256 offset = _COMBINED_GAS_OFFSET - 36;
+ assembly ("memory-safe") {
+ mstore(add(encodedIntentCopy, offset), combinedGas)
+ }
// Verification Run to generate the logs with the correct combinedGas and payment amounts.
- gasUsed = _callOrchestratorMemory(oc, true, 0, u);
-
+ gasUsed = _callOrchestratorMemory(oc, true, 0, encodedIntentCopy);
// If the simulation failed, bubble up full revert
assembly ("memory-safe") {
if iszero(gasUsed) {
diff --git a/src/interfaces/ICommon.sol b/src/interfaces/ICommon.sol
index 54ccf388..9057b1f3 100644
--- a/src/interfaces/ICommon.sol
+++ b/src/interfaces/ICommon.sol
@@ -2,86 +2,6 @@
pragma solidity ^0.8.23;
interface ICommon {
- ////////////////////////////////////////////////////////////////////////
- // Data Structures
- ////////////////////////////////////////////////////////////////////////
- /// @dev A struct to hold the intent fields.
- /// Since L2s already include calldata compression with savings forwarded to users,
- /// we don't need to be too concerned about calldata overhead
- struct Intent {
- ////////////////////////////////////////////////////////////////////////
- // EIP-712 Fields
- ////////////////////////////////////////////////////////////////////////
- /// @dev The user's address.
- address eoa;
- /// @dev An encoded array of calls, using ERC7579 batch execution encoding.
- /// `abi.encode(calls)`, where `calls` is of type `Call[]`.
- /// This allows for more efficient safe forwarding to the EOA.
- bytes executionData;
- /// @dev Per delegated EOA.
- /// This nonce is a 4337-style 2D nonce with some specializations:
- /// - Upper 192 bits are used for the `seqKey` (sequence key).
- /// The upper 16 bits of the `seqKey` is `MULTICHAIN_NONCE_PREFIX`,
- /// then the Intent EIP712 hash will exclude the chain ID.
- /// - Lower 64 bits are used for the sequential nonce corresponding to the `seqKey`.
- uint256 nonce;
- /// @dev The account paying the payment token.
- /// If this is `address(0)`, it defaults to the `eoa`.
- address payer;
- /// @dev The ERC20 or native token used to pay for gas.
- address paymentToken;
- /// @dev The maximum amount of the token to pay.
- uint256 paymentMaxAmount;
- /// @dev The combined gas limit for payment, verification, and calling the EOA.
- uint256 combinedGas;
- /// @dev Optional array of encoded SignedCalls that will be verified and executed
- /// before the validation of the overall Intent.
- /// A PreCall will NOT have its gas limit or payment applied.
- /// The overall Intent's gas limit and payment will be applied, encompassing all its PreCalls.
- /// The execution of a PreCall will check and increment the nonce in the PreCall.
- /// If at any point, any PreCall cannot be verified to be correct, or fails in execution,
- /// the overall Intent will revert before validation, and execute will return a non-zero error.
- bytes[] encodedPreCalls;
- /// @dev Only relevant for multi chain intents.
- /// There should not be any duplicate token addresses. Use address(0) for native token.
- /// If native token is used, the first transfer should be the native token transfer.
- /// If encodedFundTransfers is not empty, then the intent is considered the output intent.
- bytes[] encodedFundTransfers;
- /// @dev The settler address.
- address settler;
- /// @dev The expiry timestamp for the intent. The intent is invalid after this timestamp.
- /// If expiry timestamp is set to 0, then expiry is considered to be infinite.
- uint256 expiry;
- ////////////////////////////////////////////////////////////////////////
- // Additional Fields (Not included in EIP-712)
- ////////////////////////////////////////////////////////////////////////
- /// @dev Whether the intent should use the multichain mode - i.e verify with merkle sigs
- /// and send the cross chain message.
- bool isMultichain;
- /// @dev The funder address.
- address funder;
- /// @dev The funder signature.
- bytes funderSignature;
- /// @dev The settler context data to be passed to the settler.
- bytes settlerContext;
- /// @dev The actual payment amount, requested by the filler. MUST be less than or equal to `paymentMaxAmount`
- uint256 paymentAmount;
- /// @dev The payment recipient for the ERC20 token.
- /// Excluded from signature. The filler can replace this with their own address.
- /// This enables multiple fillers, allowing for competitive filling, better uptime.
- address paymentRecipient;
- /// @dev The wrapped signature.
- /// `abi.encodePacked(innerSignature, keyHash, prehash)`.
- bytes signature;
- /// @dev Optional payment signature to be passed into the `compensate` function
- /// on the `payer`. This signature is NOT included in the EIP712 signature.
- bytes paymentSignature;
- /// @dev Optional. If non-zero, the EOA must use `supportedAccountImplementation`.
- /// Otherwise, if left as `address(0)`, any EOA implementation will be supported.
- /// This field is NOT included in the EIP712 signature.
- address supportedAccountImplementation;
- }
-
/// @dev A struct to hold the fields for a SignedCall.
/// A SignedCall is a struct that contains a signed execution batch along with the nonce
// and address of the user.
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/IIthacaAccount.sol b/src/interfaces/IIthacaAccount.sol
index 099f466d..9056db7c 100644
--- a/src/interfaces/IIthacaAccount.sol
+++ b/src/interfaces/IIthacaAccount.sol
@@ -7,14 +7,23 @@ import {ICommon} from "../interfaces/ICommon.sol";
/// @notice Interface for the Account contract
interface IIthacaAccount is ICommon {
/// @dev Pays `paymentAmount` of `paymentToken` to the `paymentRecipient`.
+ /// @param paymentAmount The amount to pay
/// @param keyHash The hash of the key used to authorize the operation
- /// @param encodedIntent The encoded user operation
/// @param intentDigest The digest of the user operation
+ /// @param eoa The EOA address
+ /// @param payer The payer address
+ /// @param paymentToken The token to pay with
+ /// @param paymentRecipient The recipient of the payment
+ /// @param paymentSignature The payment signature
function pay(
uint256 paymentAmount,
bytes32 keyHash,
bytes32 intentDigest,
- bytes calldata encodedIntent
+ address eoa,
+ address payer,
+ address paymentToken,
+ address paymentRecipient,
+ bytes calldata paymentSignature
) external;
/// @dev Returns if the signature is valid, along with its `keyHash`.
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/interfaces/IOrchestrator.sol b/src/interfaces/IOrchestrator.sol
index ffb4de93..05dfabb4 100644
--- a/src/interfaces/IOrchestrator.sol
+++ b/src/interfaces/IOrchestrator.sol
@@ -27,11 +27,10 @@ interface IOrchestrator is ICommon {
/// But the balance of tx.origin has to be greater than or equal to uint192.max, to prove that a state override has been made offchain,
/// and this is not an onchain call. This mode has been added so that receipt logs can be generated for `eth_simulateV1`
/// @return gasUsed The amount of gas used by the execution. (Only returned if `isStateOverride` is true)
- function simulateExecute(
- bool isStateOverride,
- uint256 combinedGasOverride,
- bytes calldata encodedIntent
- ) external payable returns (uint256 gasUsed);
+ function simulateExecute(bytes calldata encodedIntent)
+ external
+ payable
+ returns (uint256 gasUsed);
/// @dev Allows the orchestrator owner to withdraw tokens.
/// @param token The token address (0 for native token)
diff --git a/src/libraries/ERC7821Ithaca.sol b/src/libraries/ERC7821Ithaca.sol
new file mode 100644
index 00000000..5f726b42
--- /dev/null
+++ b/src/libraries/ERC7821Ithaca.sol
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.4;
+
+import {Receiver} from "solady/accounts/Receiver.sol";
+
+/// @notice Minimal batch executor mixin.
+/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC7821.sol)
+///
+/// @dev This contract can be inherited to create fully-fledged smart accounts.
+/// If you merely want to combine approve-swap transactions into a single transaction
+/// using [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), you will need to implement basic
+/// [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) `isValidSignature` functionality to
+/// validate signatures with `ecrecover` against the EOA address. This is necessary because some
+/// signature checks skip `ecrecover` if the signer has code. For a basic EOA batch executor,
+/// please refer to [BEBE](https://github.com/vectorized/bebe), which inherits from this class.
+contract ERC7821Ithaca is Receiver {
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* STRUCTS */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ /// @dev Call struct for the `execute` function.
+ struct Call {
+ address to; // Replaced as `address(this)` if `address(0)`. Renamed to `to` for Ithaca Porto.
+ uint256 value; // Amount of native currency (i.e. Ether) to send.
+ bytes data; // Calldata to send with the call.
+ }
+
+ struct CallSansTo {
+ uint256 value; // Amount of native currency (i.e. Ether) to send.
+ bytes data; // Calldata to send with the call.
+ }
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* CUSTOM ERRORS */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ /// @dev The execution mode is not supported.
+ error UnsupportedExecutionMode();
+
+ /// @dev Cannot decode `executionData` as a batch of batches `abi.encode(bytes[])`.
+ error BatchOfBatchesDecodingError();
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* EXECUTION OPERATIONS */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ /// @dev Executes the calls in `executionData`.
+ /// Reverts and bubbles up error if any call fails.
+ ///
+ /// `executionData` encoding (single batch):
+ /// - If `opData` is empty, `executionData` is simply `abi.encode(calls)`.
+ /// - Else, `executionData` is `abi.encode(calls, opData)`.
+ /// See: https://eips.ethereum.org/EIPS/eip-7579
+ ///
+ /// `executionData` encoding (batch of batches):
+ /// - `executionData` is `abi.encode(bytes[])`, where each element in `bytes[]`
+ /// is an `executionData` for a single batch.
+ ///
+ /// Supported modes:
+ /// - `0x01000000000000000000...`: Single batch. Does not support optional `opData`.
+ /// - `0x01000000000078210001...`: Single batch. Supports optional `opData`.
+ /// - `0x01000000000078210002...`: Batch of batches.
+ /// - `0x01000000000078210003...`: Single batch with common `to` address and optional `opData`.
+ ///
+ /// For the "batch of batches" mode, each batch will be recursively passed into
+ /// `execute` internally with mode `0x01000000000078210001...`.
+ /// Useful for passing in batches signed by different signers.
+ ///
+ /// Authorization checks:
+ /// - If `opData` is empty, the implementation SHOULD require that
+ /// `msg.sender == address(this)`.
+ /// - If `opData` is not empty, the implementation SHOULD use the signature
+ /// encoded in `opData` to determine if the caller can perform the execution.
+ /// - If `msg.sender` is an authorized entry point, then `execute` MAY accept
+ /// calls from the entry point, and MAY use `opData` for specialized logic.
+ ///
+ /// `opData` may be used to store additional data for authentication,
+ /// paymaster data, gas limits, etc.
+ function execute(bytes32 mode, bytes calldata executionData) public payable virtual {
+ uint256 id = _executionModeId(mode);
+ if (id == 3) return _executeBatchOfBatches(mode, executionData);
+ if (id == 4) return _executeBatchCommonTo(mode, executionData);
+ Call[] calldata calls;
+ bytes calldata opData;
+
+ /// @solidity memory-safe-assembly
+ assembly {
+ if iszero(id) {
+ mstore(0x00, 0x7f181275) // `UnsupportedExecutionMode()`.
+ revert(0x1c, 0x04)
+ }
+ // Use inline assembly to extract the calls and optional `opData` efficiently.
+ opData.length := 0
+ let o := add(executionData.offset, calldataload(executionData.offset))
+ calls.offset := add(o, 0x20)
+ calls.length := calldataload(o)
+ // If the offset of `executionData` allows for `opData`, and the mode supports it.
+ if gt(eq(id, 2), gt(0x40, calldataload(executionData.offset))) {
+ let q := add(executionData.offset, calldataload(add(0x20, executionData.offset)))
+ opData.offset := add(q, 0x20)
+ opData.length := calldataload(q)
+ }
+ // Bounds checking for `executionData` is skipped here for efficiency.
+ // This is safe if it is only used as an argument to `execute` externally.
+ // If `executionData` used as an argument to other functions externally,
+ // please perform the bounds checks via `LibERC7579.decodeBatchAndOpData`
+ /// or `abi.decode` in the other functions for safety.
+ }
+ _execute(mode, executionData, calls, opData);
+ }
+
+ /// @dev Provided for execution mode support detection.
+ function supportsExecutionMode(bytes32 mode) public view virtual returns (bool result) {
+ return _executionModeId(mode) != 0;
+ }
+
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
+ /* INTERNAL HELPERS */
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+
+ /// @dev 0: invalid mode, 1: no `opData` support, 2: with `opData` support, 3: batch of batches.
+ function _executionModeId(bytes32 mode) internal view virtual returns (uint256 id) {
+ // Only supports atomic batched executions.
+ // For the encoding scheme, see: https://eips.ethereum.org/EIPS/eip-7579
+ // Bytes Layout:
+ // - [0] ( 1 byte ) `0x01` for batch call.
+ // - [1] ( 1 byte ) `0x00` for revert on any failure.
+ // - [2..5] ( 4 bytes) Reserved by ERC7579 for future standardization.
+ // - [6..9] ( 4 bytes) `0x00000000` or `0x78210001` or `0x78210002`.
+ // - [10..31] (22 bytes) Unused. Free for use.
+ /// @solidity memory-safe-assembly
+ assembly {
+ let m := and(shr(mul(22, 8), mode), 0xffff00000000ffffffff)
+ id := eq(m, 0x01000000000000000000) // 1.
+ id := or(shl(1, eq(m, 0x01000000000078210001)), id) // 2.
+ id := or(mul(3, eq(m, 0x01000000000078210002)), id) // 3.
+ id := or(mul(4, eq(m, 0x01000000000078210003)), id) // 4.
+ }
+ }
+
+ /// @dev For execution of a batch of batches with a common `to` address.
+ /// @dev if to == address(0), it will be replaced with address(this)
+ /// Execution Data: abi.encode(address to, CallSansTo[] calls, bytes opData)
+ function _executeBatchCommonTo(bytes32 mode, bytes calldata executionData) internal virtual {
+ address to;
+ CallSansTo[] calldata calls;
+ bytes calldata opData;
+
+ /// @solidity memory-safe-assembly
+ assembly {
+ to := calldataload(executionData.offset)
+
+ let callOffset :=
+ add(executionData.offset, calldataload(add(0x20, executionData.offset)))
+ calls.offset := add(callOffset, 0x20)
+ calls.length := calldataload(callOffset)
+
+ // This line is needed to ensure that opdata is valid in all code paths.
+ // Otherwise the compiler complains.
+ opData.length := 0
+ // If the offset of `executionData` allows for `opData`, and the mode supports it.
+ if gt(calldataload(add(0x20, executionData.offset)), 0x40) {
+ let opDataOffset :=
+ add(executionData.offset, calldataload(add(0x40, executionData.offset)))
+ opData.offset := add(opDataOffset, 0x20)
+ opData.length := calldataload(opDataOffset)
+ }
+ }
+
+ _execute(mode, executionData, to, calls, opData);
+ }
+
+ /// @dev For execution of a batch of batches.
+ function _executeBatchOfBatches(bytes32 mode, bytes calldata executionData) internal virtual {
+ // Replace with `0x0100________78210001...` while preserving optional and reserved fields.
+ mode ^= bytes32(uint256(3 << (22 * 8))); // `2 XOR 3 = 1`.
+ (uint256 n, uint256 o, uint256 e) = (0, 0, 0);
+ /// @solidity memory-safe-assembly
+ assembly {
+ let j := calldataload(executionData.offset)
+ let t := add(executionData.offset, j)
+ n := calldataload(t) // `batches.length`.
+ o := add(0x20, t) // Offset of `batches[0]`.
+ e := add(executionData.offset, executionData.length) // End of `executionData`.
+ // Do the bounds check on `executionData` treating it as `abi.encode(bytes[])`.
+ // Not too expensive, so we will just do it right here right now.
+ if or(shr(64, j), or(lt(executionData.length, 0x20), gt(add(o, shl(5, n)), e))) {
+ mstore(0x00, 0x3995943b) // `BatchOfBatchesDecodingError()`.
+ revert(0x1c, 0x04)
+ }
+ }
+ unchecked {
+ for (uint256 i; i != n; ++i) {
+ bytes calldata batch;
+ /// @solidity memory-safe-assembly
+ assembly {
+ let j := calldataload(add(o, shl(5, i)))
+ let t := add(o, j)
+ batch.offset := add(t, 0x20)
+ batch.length := calldataload(t)
+ // Validate that `batches[i]` is not out-of-bounds.
+ if or(shr(64, j), gt(add(batch.offset, batch.length), e)) {
+ mstore(0x00, 0x3995943b) // `BatchOfBatchesDecodingError()`.
+ revert(0x1c, 0x04)
+ }
+ }
+ execute(mode, batch);
+ }
+ }
+ }
+
+ /// @dev Executes the calls.
+ /// Reverts and bubbles up error if any call fails.
+ /// The `mode` and `executionData` are passed along in case there's a need to use them.
+ function _execute(
+ bytes32 mode,
+ bytes calldata executionData,
+ Call[] calldata calls,
+ bytes calldata opData
+ ) internal virtual {
+ // Silence compiler warning on unused variables.
+ mode = mode;
+ executionData = executionData;
+ // Very basic auth to only allow this contract to be called by itself.
+ // Override this function to perform more complex auth with `opData`.
+ if (opData.length == uint256(0)) {
+ require(msg.sender == address(this));
+ // Remember to return `_execute(calls, extraData)` when you override this function.
+ return _execute(calls, bytes32(0));
+ }
+ revert(); // In your override, replace this with logic to operate on `opData`.
+ }
+
+ /// @dev Executes the calls.
+ /// Reverts and bubbles up error if any call fails.
+ /// The `mode` and `executionData` are passed along in case there's a need to use them.
+ function _execute(
+ bytes32 mode,
+ bytes calldata executionData,
+ address to,
+ CallSansTo[] calldata calls,
+ bytes calldata opData
+ ) internal virtual {
+ // Silence compiler warning on unused variables.
+ mode = mode;
+ executionData = executionData;
+ // Very basic auth to only allow this contract to be called by itself.
+ // Override this function to perform more complex auth with `opData`.
+ if (opData.length == uint256(0)) {
+ require(msg.sender == address(this));
+ // Remember to return `_execute(calls, extraData)` when you override this function.
+ return _execute(calls, to, bytes32(0));
+ }
+ revert(); // In your override, replace this with logic to operate on `opData`.
+ }
+
+ /// @dev Executes the calls.
+ /// Reverts and bubbles up error if any call fails.
+ /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash).
+ function _execute(Call[] calldata calls, bytes32 extraData) internal virtual {
+ unchecked {
+ uint256 i;
+ if (calls.length == uint256(0)) return;
+ do {
+ (address to, uint256 value, bytes calldata data) = _get(calls, i);
+ _execute(to, value, data, extraData);
+ } while (++i != calls.length);
+ }
+ }
+
+ /// @dev Executes the calls.
+ /// Reverts and bubbles up error if any call fails.
+ /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash).
+ function _execute(CallSansTo[] calldata calls, address to, bytes32 keyHash) internal virtual {
+ unchecked {
+ uint256 i;
+ // If `to` is address(0), it will be replaced with address(this)
+ /// @solidity memory-safe-assembly
+ assembly {
+ let t := shr(96, shl(96, to))
+ to := or(mul(address(), iszero(t)), t)
+ }
+ if (calls.length == uint256(0)) return;
+ do {
+ (uint256 value, bytes calldata data) = _get(calls, i);
+ _execute(to, value, data, keyHash);
+ } while (++i != calls.length);
+ }
+ }
+
+ /// @dev Executes the call.
+ /// Reverts and bubbles up error if any call fails.
+ /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash).
+ function _execute(address to, uint256 value, bytes calldata data, bytes32 extraData)
+ internal
+ virtual
+ {
+ /// @solidity memory-safe-assembly
+ assembly {
+ extraData := extraData // Silence unused variable compiler warning.
+ let m := mload(0x40) // Grab the free memory pointer.
+ calldatacopy(m, data.offset, data.length)
+ if iszero(call(gas(), to, value, m, data.length, codesize(), 0x00)) {
+ // Bubble up the revert if the call reverts.
+ returndatacopy(m, 0x00, returndatasize())
+ revert(m, returndatasize())
+ }
+ }
+ }
+
+ /// @dev Convenience function for getting `calls[i]`, without bounds checks.
+ function _get(CallSansTo[] calldata calls, uint256 i)
+ internal
+ view
+ virtual
+ returns (uint256 value, bytes calldata data)
+ {
+ /// @solidity memory-safe-assembly
+ assembly {
+ let c := add(calls.offset, calldataload(add(calls.offset, shl(5, i))))
+ value := calldataload(c)
+ let o := add(c, calldataload(add(c, 0x20)))
+ data.offset := add(o, 0x20)
+ data.length := calldataload(o)
+ }
+ }
+
+ /// @dev Convenience function for getting `calls[i]`, without bounds checks.
+ function _get(Call[] calldata calls, uint256 i)
+ internal
+ view
+ virtual
+ returns (address to, uint256 value, bytes calldata data)
+ {
+ /// @solidity memory-safe-assembly
+ assembly {
+ let c := add(calls.offset, calldataload(add(calls.offset, shl(5, i))))
+ // Replaces `to` with `address(this)` if `address(0)` is provided.
+ let t := shr(96, shl(96, calldataload(c)))
+ to := or(mul(address(), iszero(t)), t)
+ value := calldataload(add(c, 0x20))
+ let o := add(c, calldataload(add(c, 0x40)))
+ data.offset := add(o, 0x20)
+ data.length := calldataload(o)
+ }
+ }
+}
diff --git a/src/libraries/IntentHelpers.sol b/src/libraries/IntentHelpers.sol
new file mode 100644
index 00000000..fc4f2722
--- /dev/null
+++ b/src/libraries/IntentHelpers.sol
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.23;
+
+import {ICommon} from "../interfaces/ICommon.sol";
+
+contract IntentHelpers {
+ /**
+ * The intent's layout in calldata:
+ * bytes4 fnSel // [0:4]
+ * uint256 offset; // [4:36]
+ * uint256 length; // [36:68]
+ * address supportedAccountImplementation;
+ * address eoa;
+ * uint256 nonce;
+ * address payer;
+ * address paymentToken;
+ * uint256 paymentMaxAmount;
+ * uint256 combinedGas;
+ * uint256 expiry;
+ * uint256 paymentAmount;
+ * address paymentRecipient;
+ * uint256 executionData.length
+ * bytes executionData;
+ * uint256 fundData.length
+ * bytes fundData; // abi.encode(funder, funderSignature, encodedFundTransfers)
+ * uint256 encodedPreCalls.length
+ * bytes encodedPreCalls; // abi.encode(bytes[])
+ * uint256 signature.length
+ * bytes signature;
+ * uint256 settlerData.length
+ * bytes settlerData; // abi.encode(settler, settlerContext). To use settler, nonce needs to have `MERKLE_VERIFICATION` prefix
+ * uint256 paymentSignature.length
+ * bytes paymentSignature;
+ */
+ uint256 internal constant _SUPPORTED_ACCOUNT_IMPLEMENTATION_OFFSET = 68;
+ uint256 internal constant _EOA_OFFSET = 88;
+ uint256 internal constant _NONCE_OFFSET = 120;
+ uint256 internal constant _PAYER_OFFSET = 152;
+ uint256 internal constant _PAYMENT_TOKEN_OFFSET = 184;
+ uint256 internal constant _PAYMENT_MAX_AMOUNT_OFFSET = 216;
+ uint256 internal constant _COMBINED_GAS_OFFSET = 248;
+ uint256 internal constant _EXPIRY_OFFSET = 280;
+ uint256 internal constant _PAYMENT_AMOUNT_OFFSET = 312;
+ uint256 internal constant _PAYMENT_RECIPIENT_OFFSET = 344;
+ uint256 internal constant _EXECUTION_DATA_OFFSET = 364;
+
+ struct CalldataPointer {
+ uint256 offset;
+ }
+
+ function _getSupportedAccountImplementation() internal pure returns (address) {
+ return address(
+ bytes20(
+ msg.data[
+ _SUPPORTED_ACCOUNT_IMPLEMENTATION_OFFSET:
+ _SUPPORTED_ACCOUNT_IMPLEMENTATION_OFFSET + 20
+ ]
+ )
+ );
+ }
+
+ function _getEoa() internal pure returns (address a) {
+ assembly ("memory-safe") {
+ a := calldataload(_EOA_OFFSET)
+ }
+ }
+
+ function _getNonce() internal pure returns (uint256 a) {
+ assembly ("memory-safe") {
+ a := calldataload(_NONCE_OFFSET)
+ }
+ }
+
+ function _getPayer() internal pure returns (address a) {
+ assembly ("memory-safe") {
+ a := calldataload(_PAYER_OFFSET)
+ }
+ }
+
+ function _getPaymentToken() internal pure returns (address a) {
+ assembly ("memory-safe") {
+ a := calldataload(_PAYMENT_TOKEN_OFFSET)
+ }
+ }
+
+ function _getPaymentMaxAmount() internal pure returns (uint256 a) {
+ assembly ("memory-safe") {
+ a := calldataload(_PAYMENT_MAX_AMOUNT_OFFSET)
+ }
+ }
+
+ function _getCombinedGas() internal pure returns (uint256 a) {
+ assembly ("memory-safe") {
+ a := calldataload(_COMBINED_GAS_OFFSET)
+ }
+ }
+
+ function _getExpiry() internal pure returns (uint256 a) {
+ assembly ("memory-safe") {
+ a := calldataload(_EXPIRY_OFFSET)
+ }
+ }
+
+ function _getPaymentAmount() internal pure returns (uint256 a) {
+ assembly ("memory-safe") {
+ a := calldataload(_PAYMENT_AMOUNT_OFFSET)
+ }
+ }
+
+ function _getPaymentRecipient() internal pure returns (address a) {
+ return address(bytes20(msg.data[_PAYMENT_RECIPIENT_OFFSET:_PAYMENT_RECIPIENT_OFFSET + 20]));
+ }
+
+ function _getExecutionData() internal pure returns (bytes calldata data) {
+ assembly ("memory-safe") {
+ data.length := calldataload(_EXECUTION_DATA_OFFSET)
+ data.offset := add(_EXECUTION_DATA_OFFSET, 0x20)
+ }
+ }
+
+ /// @dev Splits data containing [uint256(len), bytes(data), ...] into [uint256(len), bytes(data)] and [...]
+ /// @dev Modifies the memory pointer to point to the next
+ function _getNextBytes(CalldataPointer memory p)
+ internal
+ pure
+ returns (bytes calldata returnData)
+ {
+ uint256 o = p.offset;
+ assembly ("memory-safe") {
+ returnData.length := calldataload(o)
+ returnData.offset := add(o, 0x20)
+ mstore(p, add(o, add(returnData.length, 0x20)))
+ }
+ }
+
+ function _parseFundData(bytes calldata data)
+ internal
+ pure
+ returns (address funder, bytes calldata sig, bytes[] calldata transfers)
+ {
+ // fundData = abi.encode(funder, funderSignature, encodedFundTransfers)
+ // This gives a calldata layout of:
+ // 0x00: funder (20 bytes, left padded to 32 bytes)
+ // 0x20: offset to funderSignature (32 bytes) - we skip this and assume its starts at 0x60
+ // 0x40: offset to encodedFundTransfers (32 bytes) - we skip this and assume it starts at 0x60+funderSignature.length+0x20
+ // 0x60: funderSignature length (32 bytes)
+ // ...
+ assembly ("memory-safe") {
+ funder := calldataload(data.offset)
+ sig.offset := add(data.offset, 0x80)
+ sig.length := calldataload(add(data.offset, 0x60))
+ // transfers array starts after sig, but needs to account for padding
+ let sigLenWords := mul(div(add(sig.length, 31), 32), 32)
+ transfers.length := calldataload(add(sig.offset, sigLenWords))
+ transfers.offset := add(add(sig.offset, sigLenWords), 0x20)
+ }
+ }
+}
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..e4b8310b 100644
--- a/test/Account.t.sol
+++ b/test/Account.t.sol
@@ -354,49 +354,4 @@ contract AccountTest is BaseTest {
uint256 keysCount137 = IthacaAccount(eoaAddress).keyCount();
assertEq(keysCount137, 2, "Keys should be added on chain 137");
}
-
- function testContextKeyHash() public {
- DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
-
- PassKey memory key = _randomPassKey();
- key.k.isSuperAdmin = true;
-
- vm.prank(d.eoa);
- d.d.authorize(key.k);
-
- // Orchestrator workflow
- vm.prank(d.d.ORCHESTRATOR());
- ERC7821.Call[] memory calls = new ERC7821.Call[](1);
- calls[0].data = abi.encodeWithSelector(this.targetFunctionContextKeyHash.selector);
- calls[0].to = address(this);
- calls[0].value = 0;
- bytes memory opData = abi.encode(key.keyHash);
- bytes memory executionData = abi.encode(calls, opData);
- d.d.execute(_ERC7821_BATCH_EXECUTION_MODE, executionData);
-
- assertEq(contextKeyHash, key.keyHash, "Context key hash mismatch orchestrator workflow");
-
- // Reset context key hash
- contextKeyHash = bytes32(0);
-
- // Workflow with opData
- uint256 nonce = d.d.getNonce(0);
- bytes memory signature = _sig(key, d.d.computeDigest(calls, nonce));
- opData = abi.encodePacked(nonce, signature);
- executionData = abi.encode(calls, opData);
- d.d.execute(_ERC7821_BATCH_EXECUTION_MODE, executionData);
-
- assertEq(contextKeyHash, key.keyHash, "Context key hash mismatch orchestrator workflow");
-
- // Reset context key hash
- contextKeyHash = bytes32(0);
-
- // Simple workflow without opData (self-execution)
- bytes memory emptyOpData;
- executionData = abi.encode(calls, emptyOpData);
- 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");
- }
}
diff --git a/test/Base.t.sol b/test/Base.t.sol
index 94749645..4e353a9a 100644
--- a/test/Base.t.sol
+++ b/test/Base.t.sol
@@ -4,7 +4,6 @@ pragma solidity ^0.8.4;
import "./utils/SoladyTest.sol";
import {EIP7702Proxy} from "solady/accounts/EIP7702Proxy.sol";
import {LibEIP7702} from "solady/accounts/LibEIP7702.sol";
-import {ERC7821} from "solady/accounts/ERC7821.sol";
import {LibERC7579} from "solady/accounts/LibERC7579.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol";
@@ -24,6 +23,8 @@ import {IOrchestrator} from "../src/interfaces/IOrchestrator.sol";
import {Simulator} from "../src/Simulator.sol";
import {ICommon} from "../src/interfaces/ICommon.sol";
+import {ERC7821Ithaca as ERC7821} from "../src/libraries/ERC7821Ithaca.sol";
+
contract BaseTest is SoladyTest {
using LibRLP for LibRLP.List;
@@ -34,7 +35,6 @@ contract BaseTest is SoladyTest {
EIP7702Proxy eip7702Proxy;
TargetFunctionPayload[] targetFunctionPayloads;
Simulator simulator;
- bytes32 contextKeyHash;
struct TargetFunctionPayload {
address by;
@@ -56,6 +56,9 @@ contract BaseTest is SoladyTest {
bytes32 internal constant _ERC7821_BATCH_EXECUTION_MODE =
0x0100000000007821000100000000000000000000000000000000000000000000;
+ bytes32 internal constant _ERC7821_BATCH_SANS_TO_EXECUTION_MODE =
+ 0x0100000000007821000300000000000000000000000000000000000000000000;
+
bytes32 internal constant _ERC7579_DELEGATE_CALL_MODE =
0xff00000000000000000000000000000000000000000000000000000000000000;
@@ -95,10 +98,6 @@ contract BaseTest is SoladyTest {
targetFunctionPayloads.push(TargetFunctionPayload(msg.sender, msg.value, data));
}
- function targetFunctionContextKeyHash() public payable {
- contextKeyHash = IthacaAccount(payable(msg.sender)).getContextKeyHash();
- }
-
function _setEIP7702Delegation(address eoa) internal {
vm.etch(eoa, abi.encodePacked(hex"ef0100", address(account)));
}
diff --git a/test/Benchmark.t.sol b/test/Benchmark.t.sol
index 2ce04524..53219a9d 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("");
@@ -1518,7 +1519,7 @@ contract BenchmarkTest is BaseTest {
}
}
- function testERC20Transfer_IthacaAccount() public {
+ function testERC20Transfer_IthacaAccount1() public {
DelegatedEOA[] memory delegatedEOAs = _createIthacaAccount(1);
bytes memory payload =
_transferExecutionData(address(paymentToken), address(0xbabe), 1 ether);
@@ -1714,7 +1715,7 @@ contract BenchmarkTest is BaseTest {
) internal view returns (bytes[] memory) {
bytes[] memory encodedIntents = new bytes[](delegatedEOAs.length);
for (uint256 i = 0; i < delegatedEOAs.length; i++) {
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = delegatedEOAs[i].eoa;
u.nonce = 0;
u.combinedGas = 1000000;
@@ -1750,14 +1751,14 @@ contract BenchmarkTest is BaseTest {
_paymentType == PaymentType.APP_SPONSOR
|| _paymentType == PaymentType.APP_SPONSOR_ERC20
) {
- bytes32 digest = oc.computeDigest(u);
+ bytes32 digest = computeDigest(u);
bytes32 signatureDigest = appSponsor.computeSignatureDigest(digest);
u.paymentSignature = _eoaSig(paymasterPrivateKey, signatureDigest);
}
u.signature = _sig(delegatedEOAs[i], u);
- encodedIntents[i] = abi.encodePacked(abi.encode(u), junk);
+ encodedIntents[i] = abi.encodePacked(encodeIntent(u), junk);
}
return encodedIntents;
@@ -1785,7 +1786,7 @@ contract BenchmarkTest is BaseTest {
d.d.setSpendLimit(k.keyHash, address(0), GuardedExecutor.SpendPeriod.Hour, 1 ether);
vm.stopPrank();
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.nonce = 0;
u.combinedGas = 1000000;
@@ -1797,7 +1798,7 @@ contract BenchmarkTest is BaseTest {
u.signature = _sig(k, u);
bytes[] memory encodedIntents = new bytes[](1);
- encodedIntents[0] = abi.encode(u);
+ encodedIntents[0] = encodeIntent(u);
oc.execute(encodedIntents);
vm.snapshotGasLastCall("testERC20Transfer_IthacaAccountWithSpendLimits");
@@ -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/Escrow.t.sol b/test/Escrow.t.sol
index 57a6e8c0..ce105e8a 100644
--- a/test/Escrow.t.sol
+++ b/test/Escrow.t.sol
@@ -723,6 +723,80 @@ contract EscrowTest is BaseTest {
assertEq(uint8(escrow.statuses(escrowId3)), uint8(IEscrow.EscrowStatus.CREATED));
}
+ // ========== Early Refund Functionality Tests ==========
+
+ function testEarlyRefundFunctionality() public {
+ // Test 1: Recipient can trigger early refund for both parties
+ IEscrow.Escrow memory escrowData = _createEscrowData(1000, 800);
+ bytes32 escrowId = _createAndFundEscrow(escrowData);
+ bytes32[] memory escrowIds = new bytes32[](1);
+ escrowIds[0] = escrowId;
+
+ uint256 depositorBalanceBefore = token.balanceOf(depositor);
+ uint256 recipientBalanceBefore = token.balanceOf(recipient);
+
+ vm.prank(recipient);
+ escrow.refund(escrowIds);
+
+ assertEq(token.balanceOf(depositor) - depositorBalanceBefore, 800); // Depositor gets refundAmount
+ assertEq(token.balanceOf(recipient) - recipientBalanceBefore, 200); // Recipient gets escrowAmount - refundAmount
+ assertEq(uint256(escrow.statuses(escrowId)), uint256(IEscrow.EscrowStatus.FINALIZED));
+
+ // Test 2: Recipient can trigger early refund for themselves only
+ escrowData.salt = bytes12(uint96(2));
+ escrowId = _createAndFundEscrow(escrowData);
+ escrowIds[0] = escrowId;
+
+ recipientBalanceBefore = token.balanceOf(recipient);
+
+ vm.prank(recipient);
+ escrow.refundRecipient(escrowIds);
+
+ assertEq(token.balanceOf(recipient) - recipientBalanceBefore, 200); // Recipient gets escrowAmount - refundAmount
+ assertEq(uint256(escrow.statuses(escrowId)), uint256(IEscrow.EscrowStatus.REFUND_RECIPIENT));
+
+ // Test 3: Depositor can trigger early refund for themselves only
+ escrowData.salt = bytes12(uint96(3));
+ escrowId = _createAndFundEscrow(escrowData);
+ escrowIds[0] = escrowId;
+
+ depositorBalanceBefore = token.balanceOf(depositor);
+
+ vm.prank(depositor);
+ escrow.refundDepositor(escrowIds);
+
+ assertEq(token.balanceOf(depositor) - depositorBalanceBefore, 800); // Depositor gets refundAmount
+ assertEq(uint256(escrow.statuses(escrowId)), uint256(IEscrow.EscrowStatus.REFUND_DEPOSIT));
+
+ // Test 4: Unauthorized users cannot trigger early refunds
+ escrowData.salt = bytes12(uint96(4));
+ escrowId = _createAndFundEscrow(escrowData);
+ escrowIds[0] = escrowId;
+
+ vm.expectRevert(bytes4(keccak256("RefundInvalid()")));
+ vm.prank(randomUser);
+ escrow.refund(escrowIds);
+
+ vm.expectRevert(bytes4(keccak256("RefundInvalid()")));
+ vm.prank(attacker);
+ escrow.refundDepositor(escrowIds);
+
+ vm.expectRevert(bytes4(keccak256("RefundInvalid()")));
+ vm.prank(randomUser);
+ escrow.refundRecipient(escrowIds);
+
+ // Test 5: Normal timeout refunds still work after early refunds
+ depositorBalanceBefore = token.balanceOf(depositor);
+ recipientBalanceBefore = token.balanceOf(recipient);
+
+ vm.warp(block.timestamp + 2 hours);
+ vm.prank(randomUser);
+ escrow.refund(escrowIds);
+
+ assertEq(token.balanceOf(depositor) - depositorBalanceBefore, 800); // Depositor gets refundAmount
+ assertEq(token.balanceOf(recipient) - recipientBalanceBefore, 200); // Recipient gets escrowAmount - refundAmount
+ }
+
// ========== Helper Functions ==========
function _createEscrowData(uint256 escrowAmount, uint256 refundAmount)
diff --git a/test/GuardedExecutor.t.sol b/test/GuardedExecutor.t.sol
index a30c941e..d41e341f 100644
--- a/test/GuardedExecutor.t.sol
+++ b/test/GuardedExecutor.t.sol
@@ -25,7 +25,7 @@ contract GuardedExecutorTest is BaseTest {
vm.prank(d.eoa);
d.d.authorize(k.k);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.combinedGas = 10000000;
@@ -38,7 +38,8 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
assertEq(
- oc.execute(abi.encode(u)), bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)"))
+ oc.execute(encodeIntent(u)),
+ bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)"))
);
// Set the call checker.
bytes32 forKeyHash = _randomChance(2) ? k.keyHash : _ANY_KEYHASH;
@@ -56,7 +57,8 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
assertEq(
- oc.execute(abi.encode(u)), bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)"))
+ oc.execute(encodeIntent(u)),
+ bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)"))
);
// Try, now with the checker configured to authorize the call..
@@ -66,7 +68,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(0));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(0));
assertEq(counter.counter(), 1);
// Try, now with the checker removed.
@@ -79,7 +81,8 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
assertEq(
- oc.execute(abi.encode(u)), bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)"))
+ oc.execute(encodeIntent(u)),
+ bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)"))
);
}
@@ -94,7 +97,7 @@ contract GuardedExecutorTest is BaseTest {
d.d.setCanExecute(k.keyHash, address(paymentToken), _ANY_FN_SEL, true);
vm.stopPrank();
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.combinedGas = 10000000;
@@ -112,7 +115,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("NoSpendPermissions()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("NoSpendPermissions()")));
// Check that after the spend permission has been done, the token can be approved
// and moved via `transferFrom`.
@@ -127,7 +130,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(paymentToken.balanceOf(address(0xb0b)), 0.1 ether);
// Check that the spent has been increased.
@@ -232,7 +235,7 @@ contract GuardedExecutorTest is BaseTest {
vm.prank(address(0xb0b));
paymentToken.approve(d.eoa, 1 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.combinedGas = 10000000;
@@ -258,12 +261,12 @@ contract GuardedExecutorTest is BaseTest {
emit LogBool("transferToSelf:", transferToSelf);
if (transferToSelf) {
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(paymentToken.balanceOf(d.eoa), 0.1 ether);
return;
}
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("NoSpendPermissions()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("NoSpendPermissions()")));
vm.startPrank(d.eoa);
d.d
@@ -274,7 +277,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(paymentToken.balanceOf(address(0xdad)), 0.1 ether);
assertEq(d.d.spendInfos(k.keyHash)[0].spent, 0.1 ether);
}
@@ -285,7 +288,7 @@ contract GuardedExecutorTest is BaseTest {
}
function testOnlySuperAdminAndEOACanSelfExecute() public {
- Orchestrator.Intent memory u;
+ Intent memory u;
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
u.eoa = d.eoa;
u.combinedGas = 10000000;
@@ -327,14 +330,14 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _eoaSig(d.privateKey, u);
- assertEq(oc.execute(abi.encode(u)), 0, "2");
+ assertEq(oc.execute(encodeIntent(u)), 0, "2");
assertEq(d.d.x(), x, "3");
d.d.resetX();
u.nonce = d.d.getNonce(0);
u.signature = _sig(kSuperAdmin, u);
- assertEq(oc.execute(abi.encode(u)), 0, "4");
+ assertEq(oc.execute(encodeIntent(u)), 0, "4");
assertEq(d.d.x(), x, "5");
d.d.resetX();
@@ -342,7 +345,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.signature = _sig(kRegular, u);
assertEq(
- oc.execute(abi.encode(u)),
+ oc.execute(encodeIntent(u)),
bytes4(keccak256("UnauthorizedCall(bytes32,address,bytes)")),
"6"
);
@@ -353,7 +356,7 @@ contract GuardedExecutorTest is BaseTest {
}
function testSetAndRemoveSpendLimitRevertsForSuperAdmin() public {
- Orchestrator.Intent memory u;
+ Intent memory u;
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
u.eoa = d.eoa;
@@ -371,11 +374,11 @@ contract GuardedExecutorTest is BaseTest {
calls[0].data = abi.encodeWithSelector(IthacaAccount.authorize.selector, k.k);
u.executionData = abi.encode(calls);
- u.nonce = 0xc1d0 << 240;
+ u.nonce = d.d.getNonce(0);
u.signature = _sig(d, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
}
// Set spend limits.
{
@@ -386,7 +389,7 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _sig(d, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("SuperAdminCanSpendAnything()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("SuperAdminCanSpendAnything()")));
}
// Remove spend limits.
{
@@ -397,14 +400,14 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _sig(d, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("SuperAdminCanSpendAnything()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("SuperAdminCanSpendAnything()")));
}
}
function testSetAndRemoveSpendLimit(uint256 amount) public {
vm.warp(86400 * 100);
- Orchestrator.Intent memory u;
+ Intent memory u;
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
u.eoa = d.eoa;
@@ -442,11 +445,11 @@ contract GuardedExecutorTest is BaseTest {
calls[3] = _setSpendLimitCall(k, token, periods[1], 1 ether);
u.executionData = abi.encode(calls);
- u.nonce = 0xc1d0 << 240;
+ u.nonce = d.d.getNonce(0);
u.signature = _eoaSig(d.privateKey, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(d.d.spendInfos(k.keyHash).length, 2);
}
@@ -458,7 +461,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
@@ -474,7 +477,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
infos = d.d.spendInfos(k.keyHash);
assertEq(infos.length, 1);
@@ -490,7 +493,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
if (infos[i].period == periods[0]) {
@@ -513,7 +516,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
@@ -534,7 +537,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(d.d.spendInfos(k.keyHash).length, 0);
}
@@ -555,9 +558,9 @@ contract GuardedExecutorTest is BaseTest {
&& calls[0].data[2] == bytes1(uint8(0x24))
&& calls[0].data[3] == bytes1(uint8(0xd5))) || amount == 0
) {
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
} else {
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("NoSpendPermissions()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("NoSpendPermissions()")));
}
}
@@ -570,7 +573,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
@@ -587,7 +590,7 @@ contract GuardedExecutorTest is BaseTest {
u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
@@ -597,7 +600,7 @@ contract GuardedExecutorTest is BaseTest {
}
function testSetSpendLimitWithTwoPeriods() public {
- Orchestrator.Intent memory u;
+ Intent memory u;
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
u.eoa = d.eoa;
@@ -628,11 +631,11 @@ contract GuardedExecutorTest is BaseTest {
calls[5] = _setSpendLimitCall(k, token1, GuardedExecutor.SpendPeriod.Year, 1 ether);
u.executionData = abi.encode(calls);
- u.nonce = 0xc1d0 << 240;
+ u.nonce = d.d.getNonce(0);
u.signature = _eoaSig(d.privateKey, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(d.d.spendInfos(k.keyHash).length, 4);
}
@@ -642,11 +645,11 @@ contract GuardedExecutorTest is BaseTest {
calls[0] = _transferCall2(token0, address(0xb0b), amount0);
calls[1] = _transferCall2(token1, address(0xb0b), amount1);
- u.nonce = 0;
+ u.nonce = d.d.getNonce(0);
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
GuardedExecutor.SpendInfo[] memory infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
if (infos[i].token == token0) assertEq(infos[i].spent, amount0);
@@ -655,7 +658,7 @@ contract GuardedExecutorTest is BaseTest {
}
function testSpends(bytes32) public {
- Orchestrator.Intent memory u;
+ Intent memory u;
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
u.eoa = d.eoa;
@@ -690,17 +693,17 @@ contract GuardedExecutorTest is BaseTest {
}
u.executionData = abi.encode(calls);
- u.nonce = 0xc1d0 << 240;
+ u.nonce = d.d.getNonce(0);
u.signature = _eoaSig(d.privateKey, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(d.d.spendInfos(k.keyHash).length, tokens.length);
}
// Test spends.
{
- u.nonce = 0;
+ u.nonce = d.d.getNonce(0);
_deployPermit2();
if (_randomChance(2)) {
@@ -756,7 +759,7 @@ contract GuardedExecutorTest is BaseTest {
u.executionData = abi.encode(calls);
u.signature = _sig(k, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
GuardedExecutor.SpendInfo[] memory infos = d.d.spendInfos(k.keyHash);
for (uint256 i; i < infos.length; ++i) {
assertEq(infos[i].spent, expectedSpents[0][infos[i].token]);
@@ -806,7 +809,7 @@ contract GuardedExecutorTest is BaseTest {
}
function _testSpendWithPassKeyViaOrchestrator(PassKey memory k, address tokenToSpend) internal {
- Orchestrator.Intent memory u;
+ Intent memory u;
GuardedExecutor.SpendInfo memory info;
uint256 gExecute;
@@ -839,12 +842,12 @@ contract GuardedExecutorTest is BaseTest {
);
u.executionData = abi.encode(calls);
- u.nonce = 0xc1d0 << 240;
(gExecute, u.combinedGas,) = _estimateGasForEOAKey(u);
+
u.signature = _eoaSig(d.privateKey, u);
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
assertEq(d.d.spendInfos(k.keyHash).length, 2);
assertEq(d.d.spendInfos(k.keyHash)[0].spent, 0);
@@ -854,7 +857,7 @@ contract GuardedExecutorTest is BaseTest {
// Prep Intent, and submit it. This Intent should pass.
{
- u.nonce = 0;
+ u.nonce++;
ERC7821.Call[] memory calls = new ERC7821.Call[](1);
calls[0] = _transferCall2(tokenToSpend, address(0xb0b), 0.6 ether);
@@ -864,7 +867,7 @@ contract GuardedExecutorTest is BaseTest {
u.signature = _sig(k, u);
// Intent should pass.
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
assertEq(_balanceOf(tokenToSpend, address(0xb0b)), 0.6 ether);
assertEq(d.d.spendInfos(k.keyHash)[0].spent, 0.6 ether);
@@ -878,7 +881,7 @@ contract GuardedExecutorTest is BaseTest {
u.signature = _sig(k, u);
// Intent should fail.
- assertEq(oc.execute(abi.encode(u)), GuardedExecutor.ExceededSpendLimit.selector);
+ assertEq(oc.execute(encodeIntent(u)), GuardedExecutor.ExceededSpendLimit.selector);
}
// Prep Intent to try to exactly hit daily spend limit. This Intent should pass.
@@ -892,7 +895,7 @@ contract GuardedExecutorTest is BaseTest {
(gExecute, u.combinedGas,) = _estimateGas(k, u);
u.signature = _sig(k, u);
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
assertEq(_balanceOf(tokenToSpend, address(0xb0b)), 1 ether);
assertEq(d.d.spendInfos(k.keyHash)[0].spent, 1 ether);
}
@@ -932,7 +935,7 @@ contract GuardedExecutorTest is BaseTest {
u.signature = _sig(k, u);
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
assertEq(_balanceOf(tokenToSpend, address(0xb0b)), 1.5 ether);
assertEq(d.d.spendInfos(k.keyHash)[0].spent, 0.5 ether);
}
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..66d1bba7 100644
--- a/test/Orchestrator.t.sol
+++ b/test/Orchestrator.t.sol
@@ -20,7 +20,7 @@ import {IEscrow} from "../src/interfaces/IEscrow.sol";
contract OrchestratorTest is BaseTest {
struct _TestFullFlowTemps {
- Orchestrator.Intent[] intents;
+ Intent[] intents;
TargetFunctionPayload[] targetFunctionPayloads;
DelegatedEOA[] delegatedEOAs;
bytes[] encodedIntents;
@@ -29,7 +29,7 @@ contract OrchestratorTest is BaseTest {
function testFullFlow(uint256) public {
_TestFullFlowTemps memory t;
- t.intents = new Orchestrator.Intent[](_random() & 3);
+ t.intents = new Intent[](_random() & 3);
t.targetFunctionPayloads = new TargetFunctionPayload[](t.intents.length);
t.delegatedEOAs = new DelegatedEOA[](t.intents.length);
t.encodedIntents = new bytes[](t.intents.length);
@@ -38,7 +38,7 @@ contract OrchestratorTest is BaseTest {
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
t.delegatedEOAs[i] = d;
- Orchestrator.Intent memory u = t.intents[i];
+ Intent memory u = t.intents[i];
u.eoa = d.eoa;
vm.deal(u.eoa, 2 ** 128 - 1);
@@ -54,7 +54,7 @@ contract OrchestratorTest is BaseTest {
u.combinedGas = 10000000;
u.signature = _sig(d, u);
- t.encodedIntents[i] = abi.encode(u);
+ t.encodedIntents[i] = encodeIntent(u);
}
bytes4[] memory errors = oc.execute(t.encodedIntents);
@@ -74,11 +74,12 @@ contract OrchestratorTest is BaseTest {
vm.deal(alice.eoa, 10 ether);
vm.deal(bob.eoa, 10 ether);
paymentToken.mint(alice.eoa, 50 ether);
+ paymentToken.mint(bob.eoa, 50 ether);
bytes memory executionData =
_transferExecutionData(address(paymentToken), address(0xabcd), 1 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = alice.eoa;
u.nonce = 0;
u.executionData = executionData;
@@ -88,11 +89,9 @@ contract OrchestratorTest is BaseTest {
u.paymentAmount = 0.1 ether;
u.paymentMaxAmount = 0.5 ether;
u.combinedGas = 10000000;
- u.signature = "";
-
u.signature = _sig(alice, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("PaymentError()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("Unauthorized()")));
}
function testExecuteWithSecp256k1PassKey() public {
@@ -107,7 +106,7 @@ contract OrchestratorTest is BaseTest {
vm.prank(d.eoa);
d.d.authorize(k.k);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.nonce = 0;
u.executionData = _transferExecutionData(address(paymentToken), address(0xabcd), 1 ether);
@@ -131,7 +130,7 @@ contract OrchestratorTest is BaseTest {
combinedGasVerificationOffset: 0
})
);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
uint256 actualAmount = 0.1 ether;
assertEq(paymentToken.balanceOf(address(oc)), actualAmount);
assertEq(paymentToken.balanceOf(d.eoa), 50 ether - actualAmount - 1 ether);
@@ -154,7 +153,7 @@ contract OrchestratorTest is BaseTest {
calls[0].to = target;
calls[0].data = abi.encodeWithSignature("revertWithData(bytes)", data);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.nonce = 0;
u.executionData = abi.encode(calls);
@@ -163,7 +162,7 @@ contract OrchestratorTest is BaseTest {
u.signature = _sig(k, u);
(bool success, bytes memory result) =
- address(oc).call(abi.encodeWithSignature("simulateFailed(bytes)", abi.encode(u)));
+ address(oc).call(abi.encodeWithSignature("simulateFailed(bytes)", encodeIntent(u)));
assertFalse(success);
assertEq(result, abi.encodeWithSignature("ErrorWithData(bytes)", data));
@@ -174,7 +173,7 @@ contract OrchestratorTest is BaseTest {
paymentToken.mint(d.eoa, 500 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.nonce = 0;
u.executionData = _transferExecutionData(address(paymentToken), address(0xabcd), 1 ether);
@@ -195,7 +194,7 @@ contract OrchestratorTest is BaseTest {
})
);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
uint256 actualAmount = 10 ether;
assertEq(paymentToken.balanceOf(address(this)), actualAmount);
assertEq(paymentToken.balanceOf(d.eoa), 500 ether - actualAmount - 1 ether);
@@ -212,7 +211,7 @@ contract OrchestratorTest is BaseTest {
ds[i] = _randomEIP7702DelegatedEOA();
paymentToken.mint(ds[i].eoa, 1 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = ds[i].eoa;
u.nonce = 0;
u.executionData =
@@ -224,7 +223,7 @@ contract OrchestratorTest is BaseTest {
u.paymentMaxAmount = 0.5 ether;
u.combinedGas = 10000000;
u.signature = _sig(ds[i], u);
- encodedIntents[i] = abi.encode(u);
+ encodedIntents[i] = encodeIntent(u);
}
bytes4[] memory errs = oc.execute(encodedIntents);
@@ -247,7 +246,7 @@ contract OrchestratorTest is BaseTest {
calls[i] = _transferCall(address(paymentToken), address(0xabcd), 0.5 ether);
}
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.nonce = 0;
u.executionData = abi.encode(calls);
@@ -260,7 +259,7 @@ contract OrchestratorTest is BaseTest {
(uint256 gExecute,,) = _estimateGas(u);
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
assertEq(paymentToken.balanceOf(address(0xabcd)), 0.5 ether * n);
assertEq(paymentToken.balanceOf(d.eoa), 100 ether - (u.paymentAmount + 0.5 ether * n));
assertEq(d.d.getNonce(0), 1);
@@ -271,7 +270,7 @@ contract OrchestratorTest is BaseTest {
paymentToken.mint(d.eoa, 500 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.nonce = 0;
u.executionData = _transferExecutionData(address(paymentToken), address(0xabcd), 1 ether);
@@ -313,7 +312,7 @@ contract OrchestratorTest is BaseTest {
paymentToken.mint(d.eoa, 10 ether);
// Create base intent with common fields
- Orchestrator.Intent memory baseIntent;
+ Intent memory baseIntent;
baseIntent.eoa = d.eoa;
baseIntent.paymentToken = address(paymentToken);
baseIntent.paymentAmount = 0.1 ether;
@@ -322,50 +321,49 @@ contract OrchestratorTest is BaseTest {
// Test case 1: Intent with no expiry (expiry = 0) should always be valid
{
- Orchestrator.Intent memory u = baseIntent;
+ Intent memory u = baseIntent;
u.nonce = d.d.getNonce(0);
u.executionData =
_transferExecutionData(address(paymentToken), address(0xabcd), 1 ether);
u.expiry = 0; // No expiry
u.signature = _sig(d, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(paymentToken.balanceOf(address(0xabcd)), 1 ether);
}
// Test case 2: Intent with future expiry should be valid
{
- Orchestrator.Intent memory u = baseIntent;
+ Intent memory u = baseIntent;
u.nonce = d.d.getNonce(0);
u.executionData =
_transferExecutionData(address(paymentToken), address(0xbcde), 1 ether);
u.expiry = block.timestamp + 1 hours; // Future expiry
u.signature = _sig(d, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(paymentToken.balanceOf(address(0xbcde)), 1 ether);
}
// Test case 3: Intent with past expiry should fail
{
- Orchestrator.Intent memory u = baseIntent;
+ Intent memory u = baseIntent;
u.nonce = d.d.getNonce(0); // This will be 2 after the previous two intents
u.executionData =
_transferExecutionData(address(paymentToken), address(0xcdef), 1 ether);
u.expiry = block.timestamp - 1; // Past expiry
u.signature = _sig(d, u);
- bytes4 result = oc.execute(abi.encode(u));
+ bytes4 result = oc.execute(encodeIntent(u));
assertEq(result, bytes4(keccak256("IntentExpired()")));
assertEq(paymentToken.balanceOf(address(0xcdef)), 0); // Transfer should not happen
}
-
// Test case 4: Batch execution with mixed expired and valid intents
{
bytes[] memory encodedIntents = new bytes[](3);
// Create base intent for batch with smaller amounts
- Orchestrator.Intent memory batchBase;
+ Intent memory batchBase;
batchBase.eoa = d.eoa;
batchBase.paymentToken = address(paymentToken);
batchBase.paymentAmount = 0.05 ether;
@@ -373,31 +371,31 @@ contract OrchestratorTest is BaseTest {
batchBase.combinedGas = 10000000;
// Valid intent with nonce 2
- Orchestrator.Intent memory u1 = batchBase;
+ Intent memory u1 = batchBase;
u1.nonce = 2;
u1.executionData =
_transferExecutionData(address(paymentToken), address(0x1111), 0.5 ether);
u1.expiry = block.timestamp + 1 hours;
u1.signature = _sig(d, u1);
- encodedIntents[0] = abi.encode(u1);
+ encodedIntents[0] = encodeIntent(u1);
// Expired intent with nonce 3
- Orchestrator.Intent memory u2 = batchBase;
+ Intent memory u2 = batchBase;
u2.nonce = 3;
u2.executionData =
_transferExecutionData(address(paymentToken), address(0x2222), 0.5 ether);
u2.expiry = block.timestamp - 1;
u2.signature = _sig(d, u2);
- encodedIntents[1] = abi.encode(u2);
+ encodedIntents[1] = encodeIntent(u2);
// Another valid intent with nonce 3 (since nonce 3 wasn't consumed due to expiry)
- Orchestrator.Intent memory u3 = batchBase;
+ Intent memory u3 = batchBase;
u3.nonce = 3;
u3.executionData =
_transferExecutionData(address(paymentToken), address(0x3333), 0.5 ether);
u3.expiry = 0; // No expiry
u3.signature = _sig(d, u3);
- encodedIntents[2] = abi.encode(u3);
+ encodedIntents[2] = encodeIntent(u3);
bytes4[] memory errors = oc.execute(encodedIntents);
assertEq(errors.length, 3);
@@ -424,7 +422,7 @@ contract OrchestratorTest is BaseTest {
paymentToken.mint(ds[i].eoa, 1 ether);
vm.deal(ds[i].eoa, 1 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = ds[i].eoa;
u.nonce = 0;
u.executionData =
@@ -437,7 +435,7 @@ contract OrchestratorTest is BaseTest {
u.combinedGas = 10000000;
u.signature = _sig(ds[i], u);
- encodeIntents[i] = abi.encode(u);
+ encodeIntents[i] = encodeIntent(u);
}
bytes memory data = abi.encodeWithSignature("execute(bytes[])", encodeIntents);
@@ -463,7 +461,7 @@ contract OrchestratorTest is BaseTest {
vm.prank(d.eoa);
d.d.authorize(k.k);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.executionData = _executionData(address(0), 0, bytes(""));
u.nonce = 0x2;
@@ -473,12 +471,12 @@ contract OrchestratorTest is BaseTest {
u.combinedGas = 20000000;
u.signature = _sig(k, u);
- oc.execute(abi.encode(u));
+ oc.execute(encodeIntent(u));
}
function testInvalidateNonce(uint96 seqKey, uint64 seq, uint64 seq2) public {
uint256 nonce = (uint256(seqKey) << 64) | uint256(seq);
- Orchestrator.Intent memory u;
+ Intent memory u;
DelegatedEOA memory d = _randomEIP7702DelegatedEOA();
u.eoa = d.eoa;
@@ -518,9 +516,9 @@ contract OrchestratorTest is BaseTest {
u.signature = _sig(d, u);
if (seq > type(uint64).max - 2) {
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("InvalidNonce()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("InvalidNonce()")));
} else {
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
}
}
@@ -539,7 +537,7 @@ contract OrchestratorTest is BaseTest {
p.paymentPerGas,
p.combinedGasIncrement,
p.combinedGasVerificationOffset,
- abi.encode(p.u)
+ encodeIntent(p.u)
);
vm.revertToStateAndDelete(snapshot);
@@ -593,7 +591,7 @@ contract OrchestratorTest is BaseTest {
preCall.signature = _eoaSig(payer.privateKey, oc.computeDigest(preCall));
// Create an Intent with the pre-call and fee payer
- ICommon.Intent memory u;
+ Intent memory u;
u.eoa = eoa;
u.payer = address(payer.d);
u.nonce = 0;
@@ -615,7 +613,7 @@ contract OrchestratorTest is BaseTest {
u.encodedPreCalls[0] = abi.encode(preCall);
// Sign the intent with the ephemeral key and the payment with the payer
- bytes32 digest = oc.computeDigest(u);
+ bytes32 digest = computeDigest(u);
u.signature = _eoaSig(ephemeralPK, digest);
u.paymentSignature = _eoaSig(payer.privateKey, digest);
@@ -623,7 +621,7 @@ contract OrchestratorTest is BaseTest {
uint256 payerBalanceBefore = paymentToken.balanceOf(address(payer.d));
assertFalse(MockAccount(payable(payer.eoa)).keyCount() > 0);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
// Verify the session key was authorized in the payer's account
assertTrue(MockAccount(payable(payer.eoa)).keyCount() > 0);
@@ -656,7 +654,7 @@ contract OrchestratorTest is BaseTest {
function testInitAndTransferInOneShot(bytes32) public {
_TestAuthorizeWithPreCallsAndTransferTemps memory t;
- Orchestrator.Intent memory u;
+ Intent memory u;
uint256 ephemeralPK = _randomPrivateKey();
t.eoa = vm.addr(ephemeralPK);
@@ -745,14 +743,14 @@ contract OrchestratorTest is BaseTest {
// Test without gas estimation.
u.combinedGas = 10000000;
u.signature = _sig(kSession, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(_balanceOf(tokenToTransfer, address(0xabcd)), 0.5 ether);
}
function testAuthorizeWithPreCallsAndTransfer(bytes32) public {
_TestAuthorizeWithPreCallsAndTransferTemps memory t;
- Orchestrator.Intent memory u;
+ Intent memory u;
Orchestrator.SignedCall memory pInit;
if (_randomChance(2)) {
@@ -902,21 +900,21 @@ contract OrchestratorTest is BaseTest {
if (t.testInvalidPreCallEOA) {
u.combinedGas = 10000000;
u.signature = _sig(kSession, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("InvalidPreCallEOA()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("InvalidPreCallEOA()")));
return; // Skip the rest.
}
if (t.testPreCallVerificationError) {
u.combinedGas = 10000000;
u.signature = _sig(kSession, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("PreCallVerificationError()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("PreCallVerificationError()")));
return; // Skip the rest.
}
if (t.testPreCallError) {
u.combinedGas = 10000000;
u.signature = _sig(kSession, u);
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("PreCallError()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("PreCallError()")));
return; // Skip the rest.
}
@@ -924,20 +922,21 @@ 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);
u.combinedGas = t.gCombined;
u.signature = _sig(kSession, u);
- assertEq(oc.execute{gas: t.gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: t.gExecute}(encodeIntent(u)), 0);
} else {
// Otherwise, test without gas estimation.
u.combinedGas = 10000000;
u.signature = _sig(kSession, u);
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
}
assertEq(paymentToken.balanceOf(address(0xabcd)), 0.5 ether);
@@ -967,7 +966,7 @@ contract OrchestratorTest is BaseTest {
// 1 ether in the EOA for execution.
vm.deal(address(d.d), 1 ether);
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = d.eoa;
u.payer = address(payer.d);
@@ -980,7 +979,7 @@ contract OrchestratorTest is BaseTest {
u.executionData = _transferExecutionData(address(0), address(0xabcd), 1 ether);
u.paymentRecipient = address(0x12345);
- bytes32 digest = oc.computeDigest(u);
+ bytes32 digest = computeDigest(u);
uint256 snapshot = vm.snapshotState();
// To allow paymasters to be used in simulation mode.
@@ -989,12 +988,12 @@ contract OrchestratorTest is BaseTest {
vm.revertToStateAndDelete(snapshot);
u.combinedGas = gCombined;
- digest = oc.computeDigest(u);
+ digest = computeDigest(u);
u.signature = _eoaSig(d.privateKey, digest);
u.paymentSignature = _eoaSig(payer.privateKey, digest);
uint256 payerBalanceBefore = _balanceOf(u.paymentToken, address(payer.d));
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
assertEq(d.d.getNonce(0), u.nonce + 1);
assertEq(_balanceOf(u.paymentToken, u.paymentRecipient), u.paymentAmount);
assertEq(_balanceOf(u.paymentToken, address(payer.d)), payerBalanceBefore - u.paymentAmount);
@@ -1033,7 +1032,7 @@ contract OrchestratorTest is BaseTest {
t.token = _randomChance(2) ? address(0) : address(paymentToken);
t.isWithState = _randomChance(2);
- Orchestrator.Intent memory u;
+ Intent memory u;
t.d = _randomEIP7702DelegatedEOA();
vm.deal(t.d.eoa, type(uint192).max);
@@ -1051,7 +1050,7 @@ contract OrchestratorTest is BaseTest {
if (t.isWithState) {
t.withState.increaseFunds(u.paymentToken, u.eoa, t.funds);
} else {
- bytes32 digest = oc.computeDigest(u);
+ bytes32 digest = computeDigest(u);
digest = t.withSignature.computeSignatureDigest(digest);
u.paymentSignature = _sig(t.withSignatureEOA, digest);
t.corruptSignature = _randomChance(2);
@@ -1069,7 +1068,7 @@ contract OrchestratorTest is BaseTest {
t.withSignature.setApprovedOrchestrator(address(oc), false);
}
if ((t.unapprovedOrchestrator && u.paymentAmount != 0)) {
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("Unauthorized()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("Unauthorized()")));
if (u.paymentAmount != 0) {
assertEq(t.d.d.getNonce(0), u.nonce);
@@ -1080,7 +1079,7 @@ contract OrchestratorTest is BaseTest {
} else if (t.isWithState && u.paymentAmount > t.funds && u.paymentAmount != 0) {
// Arithmetic underflow error
assertEq(
- oc.execute(abi.encode(u)),
+ oc.execute(encodeIntent(u)),
0x4e487b7100000000000000000000000000000000000000000000000000000000
);
@@ -1098,7 +1097,7 @@ contract OrchestratorTest is BaseTest {
}
} else if ((!t.isWithState && t.corruptSignature && u.paymentAmount != 0)) {
// Pre payment will not happen
- assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("InvalidSignature()")));
+ assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("InvalidSignature()")));
// If prePayment is 0, then nonce is incremented, because the prePayment doesn't fail.
if (u.paymentAmount == 0) {
assertEq(t.d.d.getNonce(0), u.nonce + 1);
@@ -1108,7 +1107,7 @@ contract OrchestratorTest is BaseTest {
assertEq(_balanceOf(t.token, u.payer), t.balanceBefore);
assertEq(_balanceOf(address(0), address(0xabcd)), 0);
} else {
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(t.d.d.getNonce(0), u.nonce + 1);
assertEq(_balanceOf(t.token, u.payer), t.balanceBefore - u.paymentAmount);
assertEq(_balanceOf(address(0), address(0xabcd)), 1 ether);
@@ -1127,7 +1126,7 @@ contract OrchestratorTest is BaseTest {
t.testImplementationCheck = _randomChance(2);
t.requireWrongImplementation = _randomChance(2);
- Orchestrator.Intent memory u;
+ Intent memory u;
vm.deal(t.d.eoa, type(uint192).max);
u.eoa = t.d.eoa;
@@ -1147,12 +1146,12 @@ contract OrchestratorTest is BaseTest {
if (t.testImplementationCheck && t.requireWrongImplementation) {
assertEq(
- oc.execute(abi.encode(u)), bytes4(keccak256("UnsupportedAccountImplementation()"))
+ oc.execute(encodeIntent(u)), bytes4(keccak256("UnsupportedAccountImplementation()"))
);
assertEq(t.d.d.getNonce(0), u.nonce);
assertEq(_balanceOf(address(0), address(0xabcd)), 0);
} else {
- assertEq(oc.execute(abi.encode(u)), 0);
+ assertEq(oc.execute(encodeIntent(u)), 0);
assertEq(t.d.d.getNonce(0), u.nonce + 1);
assertEq(_balanceOf(address(0), address(0xabcd)), 1 ether);
}
@@ -1239,7 +1238,7 @@ contract OrchestratorTest is BaseTest {
vm.expectRevert(bytes4(keccak256("InvalidKeyHash()")));
t.d.d.execute(_ERC7821_BATCH_EXECUTION_MODE, abi.encode(calls));
- Orchestrator.Intent memory u;
+ Intent memory u;
u.eoa = t.d.eoa;
u.nonce = t.d.d.getNonce(0);
u.executionData = abi.encode(calls);
@@ -1249,14 +1248,13 @@ contract OrchestratorTest is BaseTest {
u.signature = _sig(t.multiSigKey, u);
// Test unwrapAndValidateSignature
- bytes32 digest = oc.computeDigest(u);
+ bytes32 digest = computeDigest(u);
(bool isValid, bytes32 keyHash) =
t.d.d.unwrapAndValidateSignature(digest, _sig(t.multiSigKey, digest));
assertEq(isValid, true);
assertEq(keyHash, _hash(t.multiSigKey.k));
-
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
(uint256 _threshold, bytes32[] memory o) =
t.multiSigSigner.getConfig(address(t.d.d), _hash(t.multiSigKey.k));
@@ -1280,14 +1278,13 @@ contract OrchestratorTest is BaseTest {
if (newThreshold == 0) {
vm.expectRevert(bytes4(keccak256("InvalidThreshold()")));
}
-
(gExecute, gCombined,) = _estimateGasForMultiSigKey(t.multiSigKey, u);
u.combinedGas = gCombined;
u.signature = _sig(t.multiSigKey, u);
if (newThreshold > 0) {
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
(_threshold, o) = t.multiSigSigner.getConfig(address(t.d.d), _hash(t.multiSigKey.k));
assertEq(_threshold, newThreshold);
@@ -1296,7 +1293,6 @@ contract OrchestratorTest is BaseTest {
t.multiSigKey.threshold = newThreshold;
}
}
-
// Test removeOwner
{
uint256 removeIndex = _bound(_random(), 0, o.length - 1);
@@ -1315,7 +1311,7 @@ contract OrchestratorTest is BaseTest {
u.combinedGas = gCombined;
u.signature = _sig(t.multiSigKey, u);
- assertEq(oc.execute{gas: gExecute}(abi.encode(u)), 0);
+ assertEq(oc.execute{gas: gExecute}(encodeIntent(u)), 0);
(_threshold, o) = t.multiSigSigner.getConfig(address(t.d.d), _hash(t.multiSigKey.k));
assertEq(o.length, t.multiSigKey.owners.length);
@@ -1336,9 +1332,9 @@ contract OrchestratorTest is BaseTest {
DelegatedEOA d;
PassKey k;
// Intent data
- ICommon.Intent baseIntent;
- ICommon.Intent arbIntent;
- ICommon.Intent outputIntent;
+ Intent baseIntent;
+ Intent arbIntent;
+ Intent outputIntent;
// Merkle data
bytes32[] leafs;
bytes32 root;
@@ -1426,12 +1422,11 @@ contract OrchestratorTest is BaseTest {
// 1. Prepare the output intent first to get its digest as settlementId
t.outputIntent.eoa = t.d.eoa;
- t.outputIntent.nonce = t.d.d.getNonce(0);
+ t.outputIntent.nonce = t.d.d.getNonce(0x6D76 << 176);
t.outputIntent.executionData =
_transferExecutionData(address(t.usdcMainnet), t.friend, 1000);
t.outputIntent.combinedGas = 1000000;
t.outputIntent.settler = address(t.settler);
- t.outputIntent.isMultichain = true;
{
bytes[] memory encodedFundTransfers = new bytes[](1);
@@ -1450,13 +1445,12 @@ contract OrchestratorTest is BaseTest {
// Compute the output intent digest to use as settlementId
vm.chainId(1); // Mainnet
- t.settlementId = oc.computeDigest(t.outputIntent);
+ t.settlementId = computeDigest(t.outputIntent);
// Base Intent with escrow execution data
t.baseIntent.eoa = t.d.eoa;
- t.baseIntent.nonce = t.d.d.getNonce(0);
+ t.baseIntent.nonce = t.d.d.getNonce(0x6D76 << 176);
t.baseIntent.combinedGas = 1000000;
- t.baseIntent.isMultichain = true;
// Create Base escrow execution data
{
@@ -1496,9 +1490,8 @@ contract OrchestratorTest is BaseTest {
// Arbitrum Intent with escrow execution data
t.arbIntent.eoa = t.d.eoa;
- t.arbIntent.nonce = t.d.d.getNonce(0);
+ t.arbIntent.nonce = t.d.d.getNonce(0x6D76 << 176);
t.arbIntent.combinedGas = 1000000;
- t.arbIntent.isMultichain = true;
// Create Arbitrum escrow execution data
{
IEscrow.Escrow[] memory escrows = new IEscrow.Escrow[](1);
@@ -1545,13 +1538,14 @@ contract OrchestratorTest is BaseTest {
// User has 600 USDC on base
t.usdcBase.mint(t.d.eoa, 600);
- t.encodedIntents[0] = abi.encode(t.baseIntent);
+ t.encodedIntents[0] = encodeIntent(t.baseIntent);
// User escrows funds on Base
vm.expectEmit(true, false, false, false, address(t.escrowBase));
emit Escrow.EscrowCreated(t.escrowIdBase);
vm.prank(t.gasWallet);
t.errs = oc.execute(t.encodedIntents);
assertEq(uint256(bytes32(t.errs[0])), 0);
+
// Verify funds are escrowed, not transferred yet
vm.assertEq(t.usdcBase.balanceOf(address(t.escrowBase)), 600);
vm.assertEq(t.usdcBase.balanceOf(t.relay), 0);
@@ -1562,7 +1556,7 @@ contract OrchestratorTest is BaseTest {
// User has 500 USDC on arb
t.usdcArb.mint(t.d.eoa, 500);
// Unhappy case, try to send base intent to arb
- t.encodedIntents[0] = abi.encode(t.baseIntent);
+ t.encodedIntents[0] = encodeIntent(t.baseIntent);
vm.prank(t.gasWallet);
t.errs = oc.execute(t.encodedIntents);
assertEq(
@@ -1574,15 +1568,15 @@ contract OrchestratorTest is BaseTest {
bytes32[] memory wrongLeafs = new bytes32[](3);
// Some random leaf
- wrongLeafs[0] = oc.computeDigest(t.arbIntent);
- wrongLeafs[1] = oc.computeDigest(t.arbIntent);
- wrongLeafs[2] = oc.computeDigest(t.outputIntent);
+ wrongLeafs[0] = computeDigest(t.arbIntent);
+ wrongLeafs[1] = computeDigest(t.arbIntent);
+ wrongLeafs[2] = computeDigest(t.outputIntent);
bytes memory correctSig = t.arbIntent.signature;
t.arbIntent.signature =
abi.encode(merkleHelper.getProof(wrongLeafs, 1), t.root, t.rootSig);
- t.encodedIntents[0] = abi.encode(t.arbIntent);
+ t.encodedIntents[0] = encodeIntent(t.arbIntent);
vm.prank(t.gasWallet);
t.errs = oc.execute(t.encodedIntents);
assertEq(
@@ -1595,7 +1589,7 @@ contract OrchestratorTest is BaseTest {
}
// User escrows funds on Arb
- t.encodedIntents[0] = abi.encode(t.arbIntent);
+ t.encodedIntents[0] = encodeIntent(t.arbIntent);
vm.expectEmit(true, false, false, false, address(t.escrowArb));
emit Escrow.EscrowCreated(t.escrowIdArb);
vm.prank(t.gasWallet);
@@ -1621,7 +1615,7 @@ contract OrchestratorTest is BaseTest {
emit SimpleSettler.Sent(address(oc), t.settlementId, 42161); // Arbitrum
// Relay funds the user account, and the intended execution happens.
- t.encodedIntents[0] = abi.encode(t.outputIntent);
+ t.encodedIntents[0] = encodeIntent(t.outputIntent);
vm.prank(t.gasWallet);
t.errs = oc.execute(t.encodedIntents);
assertEq(uint256(bytes32(t.errs[0])), 0);
@@ -1656,7 +1650,7 @@ contract OrchestratorTest is BaseTest {
// Re-execute the escrow on Base (to recreate the state)
t.usdcBase.mint(t.d.eoa, 600);
- t.encodedIntents[0] = abi.encode(t.baseIntent);
+ t.encodedIntents[0] = encodeIntent(t.baseIntent);
vm.expectEmit(true, false, false, false, address(t.escrowBase));
emit Escrow.EscrowCreated(t.escrowIdBase);
vm.prank(t.gasWallet);
@@ -1685,7 +1679,7 @@ contract OrchestratorTest is BaseTest {
// Re-execute the escrow on Arbitrum (to recreate the state)
t.usdcArb.mint(t.d.eoa, 500);
- t.encodedIntents[0] = abi.encode(t.arbIntent);
+ t.encodedIntents[0] = encodeIntent(t.arbIntent);
vm.expectEmit(true, false, false, false, address(t.escrowArb));
emit Escrow.EscrowCreated(t.escrowIdArb);
vm.prank(t.gasWallet);
@@ -1732,7 +1726,7 @@ contract OrchestratorTest is BaseTest {
t.outputIntent.funderSignature = _eoaSig(wrongPrivateKey, t.leafs[2]);
- t.encodedIntents[0] = abi.encode(t.outputIntent);
+ t.encodedIntents[0] = encodeIntent(t.outputIntent);
vm.prank(t.gasWallet);
t.errs = oc.execute(t.encodedIntents);
@@ -1755,11 +1749,11 @@ contract OrchestratorTest is BaseTest {
function _computeMerkleData(_TestMultiChainIntentTemps memory t) internal {
t.leafs = new bytes32[](3);
vm.chainId(8453);
- t.leafs[0] = oc.computeDigest(t.baseIntent);
+ t.leafs[0] = computeDigest(t.baseIntent);
vm.chainId(42161);
- t.leafs[1] = oc.computeDigest(t.arbIntent);
+ t.leafs[1] = computeDigest(t.arbIntent);
vm.chainId(1);
- t.leafs[2] = oc.computeDigest(t.outputIntent);
+ t.leafs[2] = computeDigest(t.outputIntent);
t.root = merkleHelper.getRoot(t.leafs);
@@ -1768,8 +1762,23 @@ contract OrchestratorTest is BaseTest {
t.outputIntent.funderSignature = _eoaSig(t.funderPrivateKey, t.leafs[2]);
- t.baseIntent.signature = abi.encode(merkleHelper.getProof(t.leafs, 0), t.root, t.rootSig);
- t.arbIntent.signature = abi.encode(merkleHelper.getProof(t.leafs, 1), t.root, t.rootSig);
- t.outputIntent.signature = abi.encode(merkleHelper.getProof(t.leafs, 2), t.root, t.rootSig);
+ t.baseIntent.signature = abi.encodePacked(
+ abi.encode(merkleHelper.getProof(t.leafs, 0), t.root, t.rootSig),
+ bytes32(0),
+ uint8(0),
+ uint8(1)
+ );
+ t.arbIntent.signature = abi.encodePacked(
+ abi.encode(merkleHelper.getProof(t.leafs, 1), t.root, t.rootSig),
+ bytes32(0),
+ uint8(0),
+ uint8(1)
+ );
+ t.outputIntent.signature = abi.encodePacked(
+ abi.encode(merkleHelper.getProof(t.leafs, 2), t.root, t.rootSig),
+ bytes32(0),
+ uint8(0),
+ uint8(1)
+ );
}
}
diff --git a/test/SimulateExecute.t.sol b/test/SimulateExecute.t.sol
index 099d6acc..8e2d52b5 100644
--- a/test/SimulateExecute.t.sol
+++ b/test/SimulateExecute.t.sol
@@ -58,7 +58,7 @@ contract SimulateExecuteTest is BaseTest {
abi.encodeWithSignature("burnGas(uint256,uint256)", t.gasToBurn, t.randomness)
);
- Orchestrator.Intent memory i;
+ Intent memory i;
i.eoa = d.eoa;
i.nonce = 0;
i.executionData = t.executionData;
@@ -79,19 +79,19 @@ contract SimulateExecuteTest is BaseTest {
// If the caller does not have max balance, then the simulation should revert.
vm.expectRevert(bytes4(keccak256("StateOverrideError()")));
(t.gUsed, t.gCombined) =
- simulator.simulateV1Logs(address(oc), 0, 1, 11_000, 10_000, abi.encode(i));
+ simulator.simulateV1Logs(address(oc), 0, 1, 11_000, 10_000, encodeIntent(i));
vm.expectRevert(bytes4(keccak256("StateOverrideError()")));
- oc.simulateExecute(true, type(uint256).max, abi.encode(i));
+ oc.simulateExecute(encodeIntent(i, true, type(uint256).max));
vm.expectPartialRevert(bytes4(keccak256("SimulationPassed(uint256)")));
- oc.simulateExecute(false, type(uint256).max, abi.encode(i));
+ oc.simulateExecute(encodeIntent(i, false, type(uint256).max));
uint256 snapshot = vm.snapshotState();
vm.deal(_ORIGIN_ADDRESS, type(uint192).max);
(t.gUsed, t.gCombined) =
- simulator.simulateV1Logs(address(oc), 2, 1e11, 11_000, 0, abi.encode(i));
+ simulator.simulateV1Logs(address(oc), 2, 1e11, 11_000, 0, encodeIntent(i));
vm.revertToStateAndDelete(snapshot);
i.combinedGas = t.gCombined;
@@ -101,11 +101,11 @@ contract SimulateExecuteTest is BaseTest {
i.signature = _sig(d, i);
vm.expectRevert(bytes4(keccak256("InsufficientGas()")));
- oc.execute{gas: t.gExecute}(abi.encode(i));
+ oc.execute{gas: t.gExecute}(encodeIntent(i));
t.gExecute = Math.mulDiv(t.gCombined + 110_000, 64, 63);
- assertEq(oc.execute{gas: t.gExecute}(abi.encode(i)), 0);
+ assertEq(oc.execute{gas: t.gExecute}(encodeIntent(i)), 0);
}
function testSimulateExecuteNoRevertUnderfundedReverts() public {
@@ -127,7 +127,7 @@ contract SimulateExecuteTest is BaseTest {
abi.encodeWithSignature("burnGas(uint256,uint256)", t.gasToBurn, t.randomness)
);
- Orchestrator.Intent memory i;
+ Intent memory i;
i.eoa = d.eoa;
i.nonce = 0;
i.executionData = t.executionData;
@@ -146,11 +146,11 @@ contract SimulateExecuteTest is BaseTest {
}
vm.expectRevert(bytes4(keccak256("PaymentError()")));
- simulator.simulateV1Logs(address(oc), 0, 1, 11_000, 0, abi.encode(i));
+ simulator.simulateV1Logs(address(oc), 0, 1, 11_000, 0, encodeIntent(i));
deal(i.paymentToken, address(i.eoa), 0x112233112233112233112233);
vm.expectRevert(bytes4(keccak256("PaymentError()")));
- simulator.simulateCombinedGas(address(oc), 0, 1, 11_000, abi.encode(i));
+ simulator.simulateCombinedGas(address(oc), 0, 1, 11_000, encodeIntent(i));
}
function testSimulateExecuteNoRevert() public {
@@ -173,7 +173,7 @@ contract SimulateExecuteTest is BaseTest {
abi.encodeWithSignature("burnGas(uint256,uint256)", t.gasToBurn, t.randomness)
);
- Orchestrator.Intent memory i;
+ Intent memory i;
i.eoa = d.eoa;
i.nonce = 0;
i.executionData = t.executionData;
@@ -195,7 +195,7 @@ contract SimulateExecuteTest is BaseTest {
vm.deal(_ORIGIN_ADDRESS, type(uint192).max);
(t.gUsed, t.gCombined) =
- simulator.simulateV1Logs(address(oc), 2, 1e11, 11_000, 0, abi.encode(i));
+ simulator.simulateV1Logs(address(oc), 2, 1e11, 11_000, 0, encodeIntent(i));
vm.revertToStateAndDelete(snapshot);
@@ -205,7 +205,7 @@ contract SimulateExecuteTest is BaseTest {
i.signature = _sig(d, i);
- assertEq(oc.execute{gas: t.gExecute}(abi.encode(i)), 0);
+ assertEq(oc.execute{gas: t.gExecute}(encodeIntent(i)), 0);
assertEq(gasBurner.randomness(), t.randomness);
}
@@ -229,7 +229,7 @@ contract SimulateExecuteTest is BaseTest {
abi.encodeWithSignature("burnGas(uint256,uint256)", t.gasToBurn, t.randomness)
);
- Orchestrator.Intent memory i;
+ Intent memory i;
i.eoa = d.eoa;
i.nonce = 0;
i.executionData = t.executionData;
@@ -251,7 +251,7 @@ contract SimulateExecuteTest is BaseTest {
vm.deal(_ORIGIN_ADDRESS, type(uint192).max);
(t.gUsed, t.gCombined) =
- simulator.simulateV1Logs(address(oc), 2, 1e11, 10_800, 0, abi.encode(i));
+ simulator.simulateV1Logs(address(oc), 2, 1e11, 10_800, 0, encodeIntent(i));
vm.revertToStateAndDelete(snapshot);
@@ -261,7 +261,7 @@ contract SimulateExecuteTest is BaseTest {
i.signature = _sig(d, i);
- assertEq(oc.execute{gas: t.gExecute}(abi.encode(i)), 0);
+ assertEq(oc.execute{gas: t.gExecute}(encodeIntent(i)), 0);
assertEq(gasBurner.randomness(), t.randomness);
}
@@ -289,7 +289,7 @@ contract SimulateExecuteTest is BaseTest {
abi.encodeWithSignature("burnGas(uint256,uint256)", t.gasToBurn, t.randomness)
);
- Orchestrator.Intent memory i;
+ Intent memory i;
i.eoa = d.eoa;
i.nonce = 0;
i.executionData = t.executionData;
@@ -305,13 +305,14 @@ 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);
(t.gUsed, t.gCombined) =
- simulator.simulateV1Logs(address(oc), 2, 1e11, 12_000, 10_000, abi.encode(i));
+ simulator.simulateV1Logs(address(oc), 2, 1e11, 12_000, 10_000, encodeIntent(i));
vm.revertToStateAndDelete(snapshot);
@@ -320,7 +321,7 @@ contract SimulateExecuteTest is BaseTest {
i.signature = _sig(k, i);
- assertEq(oc.execute{gas: t.gExecute}(abi.encode(i)), 0);
+ assertEq(oc.execute{gas: t.gExecute}(encodeIntent(i)), 0);
assertEq(gasBurner.randomness(), t.randomness);
}
diff --git a/test/SubAccounts.t.sol b/test/SubAccounts.t.sol
new file mode 100644
index 00000000..68417582
--- /dev/null
+++ b/test/SubAccounts.t.sol
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.4;
+
+import "./Base.t.sol";
+import {ERC20} from "solady/tokens/ERC20.sol";
+import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
+import {ERC7821} from "solady/accounts/ERC7821.sol";
+
+/// @title SubAccounts Test Suite
+/// @notice Tests the parent-child account architecture with spending permissions
+contract SubAccountsTest is BaseTest {
+ using SafeTransferLib for address;
+
+ // Main account (parent) - controlled by user
+ DelegatedEOA mainAccount;
+ PassKey mainAccountKey;
+
+ // Sub account (child) - controlled by DApp
+ DelegatedEOA subAccount;
+ PassKey dappKey;
+
+ // Test tokens
+ MockPaymentToken usdc;
+ MockPaymentToken dai;
+
+ // DApp that controls the sub account
+ address dappAddress;
+ address constant dappRecipient = address(0xDA99);
+
+ // Spend IDs for tracking permissions
+ uint256 constant PARENT_SWEEP_SPEND_ID = 1;
+ uint256 constant CHILD_PULL_SPEND_ID = 2;
+
+ function setUp() public override {
+ super.setUp();
+
+ // Setup tokens
+ usdc = new MockPaymentToken();
+ dai = new MockPaymentToken();
+
+ // Setup main account (parent)
+ mainAccount = _randomEIP7702DelegatedEOA();
+ mainAccountKey = _randomPassKey();
+ mainAccountKey.k.isSuperAdmin = true; // Main account key needs to be super admin to call setSpend
+ vm.prank(address(mainAccount.d));
+ mainAccount.d.authorize(mainAccountKey.k);
+
+ // Setup sub account (controlled by DApp)
+ subAccount = _randomEIP7702DelegatedEOA();
+ dappKey = _randomPassKey();
+ dappKey.k.isSuperAdmin = true; // DApp has full control of sub account
+ vm.prank(address(subAccount.d));
+ subAccount.d.authorize(dappKey.k);
+
+ // Setup DApp address
+ dappAddress = vm.addr(uint256(keccak256("dapp")));
+ }
+
+ /// @notice Tests the complete subaccount flow with DApp integration
+ /// Flow:
+ /// 1. Create main account with funds
+ /// 2. Create sub account (controlled by DApp, no initial funds needed)
+ /// 3. Sub account grants parent sweep permission
+ /// 4. Main account grants sub account limited pull permission
+ /// 5. DApp executes bundle: pulls funds from main, sends to DApp
+ /// 6. Parent can sweep remaining funds back
+ function test_CompleteSubAccountFlowWithDApp() public {
+ // ============================================
+ // STEP 1: Fund the main account
+ // ============================================
+ uint256 mainAccountInitialBalance = 1000e6; // 1000 USDC
+ usdc.mint(address(mainAccount.eoa), mainAccountInitialBalance);
+ dai.mint(address(mainAccount.eoa), 500e18); // 500 DAI
+
+ assertEq(usdc.balanceOf(address(mainAccount.eoa)), mainAccountInitialBalance);
+ assertEq(dai.balanceOf(address(mainAccount.eoa)), 500e18);
+
+ // Sub account starts with 0 funds (DApp pays for gas)
+ assertEq(usdc.balanceOf(address(subAccount.eoa)), 0);
+
+ // ============================================
+ // STEP 2: Sub account grants parent sweep permission
+ // This allows parent to recover any funds from sub account
+ // ============================================
+ {
+ // Create setSpend call to grant parent sweep permission
+ ERC7821.Call[] memory calls = new ERC7821.Call[](1);
+
+ calls[0] = ERC7821.Call({
+ to: address(subAccount.d), // Call to self
+ value: 0,
+ data: abi.encodeWithSelector(
+ IthacaAccount.setSpend.selector,
+ PARENT_SWEEP_SPEND_ID,
+ true, // isParent = true (can sweep everything)
+ uint32(0), // No expiry for parent
+ address(mainAccount.eoa), // Parent is the spender
+ new address[](0), // Tokens are ignored, because parent can sweep everything
+ new uint256[](0) // Limits are ignored, because parent can sweep everything
+ )
+ });
+
+ // Execute as the subAccount EOA directly (no signature needed)
+ vm.prank(subAccount.eoa);
+ subAccount.d.execute(_ERC7821_BATCH_EXECUTION_MODE, abi.encode(calls));
+ }
+
+ // ============================================
+ // STEP 3: Main account grants sub account limited pull permission
+ // This allows sub account to pull up to 100 USDC and 50 DAI
+ // ============================================
+ {
+ // Create setSpend call to grant sub account limited permission
+ ERC7821.Call[] memory calls = new ERC7821.Call[](1);
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(usdc);
+ tokens[1] = address(dai);
+ uint256[] memory limits = new uint256[](2);
+ limits[0] = 100e6; // 100 USDC limit
+ limits[1] = 50e18; // 50 DAI limit
+
+ calls[0] = ERC7821.Call({
+ to: address(mainAccount.d), // Call to self
+ value: 0,
+ data: abi.encodeWithSelector(
+ IthacaAccount.setSpend.selector,
+ CHILD_PULL_SPEND_ID,
+ false, // isParent = false (limited permissions)
+ uint32(block.timestamp + 30 days), // 30 day expiry
+ address(subAccount.eoa), // Sub account is the spender
+ tokens,
+ limits
+ )
+ });
+
+ // Execute as the mainAccount EOA directly (no signature needed)
+ vm.prank(mainAccount.eoa);
+ mainAccount.d.execute(_ERC7821_BATCH_EXECUTION_MODE, abi.encode(calls));
+ }
+
+ // ============================================
+ // STEP 4: Test direct spend call from subAccount to mainAccount
+ // ============================================
+ uint256 amountToPull = 50e6; // 50 USDC
+ {
+ // SubAccount calls spend on mainAccount directly
+ address[] memory pullTokens = new address[](1);
+ pullTokens[0] = address(usdc);
+ uint256[] memory pullAmounts = new uint256[](1);
+ pullAmounts[0] = amountToPull;
+
+ vm.prank(subAccount.eoa);
+ mainAccount.d.spend(
+ CHILD_PULL_SPEND_ID, pullTokens, pullAmounts, address(subAccount.eoa)
+ );
+
+ // Verify the transfer worked
+ assertEq(
+ usdc.balanceOf(address(mainAccount.eoa)), mainAccountInitialBalance - amountToPull
+ );
+ assertEq(usdc.balanceOf(address(subAccount.eoa)), amountToPull);
+ }
+
+ // ============================================
+ // STEP 5: DApp executes bundle via sub account
+ // - Pulls more funds from main account
+ // - Then sends to DApp recipient
+ // ============================================
+ uint256 secondPullAmount = 30e6; // 30 USDC
+ {
+ Orchestrator.Intent memory intent;
+ intent.eoa = address(subAccount.eoa);
+ intent.nonce = subAccount.d.getNonce(0);
+ intent.expiry = block.timestamp + 1 days;
+ intent.paymentToken = address(paymentToken);
+ intent.paymentAmount = 0 ether;
+ intent.paymentRecipient = address(0xfee);
+ intent.combinedGas = 1000000;
+
+ // Create bundle: pull from main, then send to DApp
+ ERC7821.Call[] memory calls = new ERC7821.Call[](2);
+
+ address[] memory pullTokens = new address[](1);
+ pullTokens[0] = address(usdc);
+ uint256[] memory pullAmounts = new uint256[](1);
+ pullAmounts[0] = secondPullAmount;
+
+ calls[0] = ERC7821.Call({
+ to: address(mainAccount.d),
+ value: 0,
+ data: abi.encodeWithSelector(
+ IthacaAccount.spend.selector,
+ CHILD_PULL_SPEND_ID,
+ pullTokens,
+ pullAmounts,
+ address(subAccount.eoa)
+ )
+ });
+
+ // Send all funds to DApp (first pull + second pull)
+ calls[1] = ERC7821.Call({
+ to: address(usdc),
+ value: 0,
+ data: abi.encodeWithSelector(
+ ERC20.transfer.selector, dappRecipient, amountToPull + secondPullAmount
+ )
+ });
+
+ intent.executionData = abi.encode(calls);
+ intent.signature = _sig(dappKey, intent);
+
+ // Execute the bundle
+ assertEq(oc.execute(abi.encode(intent)), 0);
+
+ // Verify balances
+ assertEq(
+ usdc.balanceOf(address(mainAccount.eoa)),
+ mainAccountInitialBalance - amountToPull - secondPullAmount
+ );
+ assertEq(usdc.balanceOf(address(subAccount.eoa)), 0);
+ assertEq(usdc.balanceOf(dappRecipient), amountToPull + secondPullAmount);
+ }
+
+ // ============================================
+ // STEP 6: Test that sub account cannot exceed limits (should fail)
+ // ============================================
+ {
+ // Try to pull 21 more USDC (total would be 50+30+21 = 101, exceeding 100 limit)
+ address[] memory pullTokens = new address[](1);
+ pullTokens[0] = address(usdc);
+ uint256[] memory pullAmounts = new uint256[](1);
+ pullAmounts[0] = 21e6; // This would exceed the 100 USDC limit
+
+ // Should revert due to exceeding limit
+ vm.prank(subAccount.eoa);
+ vm.expectRevert();
+ mainAccount.d.spend(
+ CHILD_PULL_SPEND_ID, pullTokens, pullAmounts, address(subAccount.eoa)
+ );
+ }
+
+ // // ============================================
+ // // STEP 7: Parent sweeps funds back from sub account
+ // // ============================================
+ {
+ // Parent can sweep everything from sub account
+ address[] memory sweepTokens = new address[](1);
+ sweepTokens[0] = address(usdc);
+ uint256[] memory sweepAmounts = new uint256[](1);
+ sweepAmounts[0] = usdc.balanceOf(address(subAccount.eoa));
+
+ uint256 mainAccountPreBalance = usdc.balanceOf(address(mainAccount.eoa));
+
+ // Parent directly calls spend on sub account
+ vm.prank(mainAccount.eoa);
+ subAccount.d.spend(
+ PARENT_SWEEP_SPEND_ID,
+ sweepTokens,
+ sweepAmounts,
+ address(mainAccount.eoa) // Sweep back to parent
+ );
+
+ // Verify parent recovered all the funds
+ assertEq(usdc.balanceOf(address(subAccount.eoa)), 0);
+ assertEq(
+ usdc.balanceOf(address(mainAccount.eoa)), mainAccountPreBalance + sweepAmounts[0]
+ );
+ }
+ }
+}
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/IPimlicoPaymaster.sol b/test/utils/interfaces/IPimlicoPaymaster.sol
index 2665aedc..d3d62e0b 100644
--- a/test/utils/interfaces/IPimlicoPaymaster.sol
+++ b/test/utils/interfaces/IPimlicoPaymaster.sol
@@ -19,10 +19,10 @@ library PimlicoHelpers {
/// @notice The length of the mode and allowAllBundlers bytes.
uint8 constant MODE_AND_ALLOW_ALL_BUNDLERS_LENGTH = 1;
- /// @notice The length of the ERC-20 config without singature.
+ /// @notice The length of the ERC-20 config without signature.
uint8 constant ERC20_PAYMASTER_DATA_LENGTH = 117;
- /// @notice The length of the verfiying config without singature.
+ /// @notice The length of the verifying config without signature.
uint8 constant VERIFYING_PAYMASTER_DATA_LENGTH = 12; // 12
uint256 constant PAYMASTER_DATA_OFFSET = 52;
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/MockOrchestrator.sol b/test/utils/mocks/MockOrchestrator.sol
index e21a2360..a0c3226e 100644
--- a/test/utils/mocks/MockOrchestrator.sol
+++ b/test/utils/mocks/MockOrchestrator.sol
@@ -15,8 +15,13 @@ contract MockOrchestrator is Orchestrator, Brutalizer {
return _computeDigest(preCall);
}
- function computeDigest(Intent calldata intent) public view returns (bytes32) {
- return _computeDigest(intent);
+ // Expose internal functions for testing
+ function hashTypedData(bytes32 structHash) public view returns (bytes32) {
+ return _hashTypedData(structHash);
+ }
+
+ function hashTypedDataSansChainId(bytes32 structHash) public view returns (bytes32) {
+ return _hashTypedDataSansChainId(structHash);
}
function simulateFailed(bytes calldata encodedIntent) public payable virtual {
diff --git a/test/utils/mocks/MockPayerWithSignature.sol b/test/utils/mocks/MockPayerWithSignature.sol
index 1de4c6d5..60678b52 100644
--- a/test/utils/mocks/MockPayerWithSignature.sol
+++ b/test/utils/mocks/MockPayerWithSignature.sol
@@ -51,38 +51,44 @@ contract MockPayerWithSignature is Ownable {
}
/// @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,
- /// if the signature is invalid, or if the nonce has already been used.
- /// @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 digest The digest of the intent (used for nonce tracking).
- /// @param encodedIntent ABI encoded Intent struct.
+ /// @param paymentAmount The amount to pay
+ /// @param keyHash The hash of the key used to authorize the operation
+ /// @param intentDigest The digest of the user operation
+ /// @param eoa The EOA address
+ /// @param payer The payer address
+ /// @param paymentToken The token to pay with
+ /// @param paymentRecipient The recipient of the payment
+ /// @param paymentSignature The payment signature
function pay(
uint256 paymentAmount,
bytes32 keyHash,
- bytes32 digest,
- bytes calldata encodedIntent
+ bytes32 intentDigest,
+ address eoa,
+ address payer,
+ address paymentToken,
+ address paymentRecipient,
+ bytes calldata paymentSignature
) public virtual {
if (!isApprovedOrchestrator[msg.sender]) revert Unauthorized();
// Check and set nonce to prevent replay attacks
- if (paymasterNonces[digest]) {
+ if (paymasterNonces[intentDigest]) {
revert PaymasterNonceError();
}
- paymasterNonces[digest] = true;
+ paymasterNonces[intentDigest] = true;
- ICommon.Intent memory u = abi.decode(encodedIntent, (ICommon.Intent));
+ bytes32 signatureDigest = computeSignatureDigest(intentDigest);
- bytes32 signatureDigest = computeSignatureDigest(digest);
-
- if (ECDSA.recover(signatureDigest, u.paymentSignature) != signer) {
+ if (ECDSA.recover(signatureDigest, paymentSignature) != signer) {
revert InvalidSignature();
}
- TokenTransferLib.safeTransfer(u.paymentToken, u.paymentRecipient, paymentAmount);
+ TokenTransferLib.safeTransfer(paymentToken, paymentRecipient, paymentAmount);
+
+ emit Compensated(paymentToken, paymentRecipient, paymentAmount, eoa, keyHash);
- emit Compensated(u.paymentToken, u.paymentRecipient, paymentAmount, u.eoa, keyHash);
+ // Unused parameters
+ payer;
}
function computeSignatureDigest(bytes32 intentDigest) public view returns (bytes32) {
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 {}
-}
diff --git a/test/utils/mocks/MockPayerWithState.sol b/test/utils/mocks/MockPayerWithState.sol
index 365db440..e5e4c35d 100644
--- a/test/utils/mocks/MockPayerWithState.sol
+++ b/test/utils/mocks/MockPayerWithState.sol
@@ -54,35 +54,42 @@ contract MockPayerWithState is Ownable {
}
/// @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,
- /// if the EOA lacks sufficient funds, or if the nonce has already been used.
- /// @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 digest The digest of the intent (used for nonce tracking).
- /// @param encodedIntent ABI encoded Intent struct.
+ /// @param paymentAmount The amount to pay
+ /// @param keyHash The hash of the key used to authorize the operation
+ /// @param intentDigest The digest of the user operation
+ /// @param eoa The EOA address
+ /// @param payer The payer address
+ /// @param paymentToken The token to pay with
+ /// @param paymentRecipient The recipient of the payment
+ /// @param paymentSignature The payment signature
function pay(
uint256 paymentAmount,
bytes32 keyHash,
- bytes32 digest,
- bytes calldata encodedIntent
+ bytes32 intentDigest,
+ address eoa,
+ address payer,
+ address paymentToken,
+ address paymentRecipient,
+ bytes calldata paymentSignature
) public virtual {
if (!isApprovedOrchestrator[msg.sender]) revert Unauthorized();
// Check and set nonce to prevent replay attacks
- if (paymasterNonces[digest]) {
+ if (paymasterNonces[intentDigest]) {
revert PaymasterNonceError();
}
- paymasterNonces[digest] = true;
-
- ICommon.Intent memory u = abi.decode(encodedIntent, (ICommon.Intent));
+ paymasterNonces[intentDigest] = true;
// We shall rely on arithmetic underflow error to revert if there's insufficient funds.
- funds[u.paymentToken][u.eoa] -= paymentAmount;
- TokenTransferLib.safeTransfer(u.paymentToken, u.paymentRecipient, paymentAmount);
+ funds[paymentToken][eoa] -= paymentAmount;
+ TokenTransferLib.safeTransfer(paymentToken, paymentRecipient, paymentAmount);
// Emit the event for debugging.
- emit Compensated(u.paymentToken, u.paymentRecipient, paymentAmount, u.eoa, keyHash);
+ emit Compensated(paymentToken, paymentRecipient, paymentAmount, eoa, keyHash);
+
+ // Unused parameters
+ payer;
+ paymentSignature;
}
receive() external payable {}
diff --git a/utils/JsonBindings.sol b/utils/JsonBindings.sol
new file mode 100644
index 00000000..1a76c5b2
--- /dev/null
+++ b/utils/JsonBindings.sol
@@ -0,0 +1,767 @@
+// Automatically generated by forge bind-json.
+
+pragma solidity >=0.6.2 <0.9.0;
+pragma experimental ABIEncoderV2;
+
+import {GuardedExecutor} from "src/GuardedExecutor.sol";
+import {IthacaAccount} from "src/IthacaAccount.sol";
+import {MultiSigSigner} from "src/MultiSigSigner.sol";
+import {ICommon} from "src/interfaces/ICommon.sol";
+import {IEscrow} from "src/interfaces/IEscrow.sol";
+import {LibTStack} from "src/libraries/LibTStack.sol";
+import {AccountTest} from "test/Account.t.sol";
+import {BaseTest} from "test/Base.t.sol";
+import {EnforcedOptionParam} from "test/LayerZeroSettler.t.sol";
+import {MultiSigSignerTest} from "test/MultiSigSigner.t.sol";
+import {OrchestratorTest} from "test/Orchestrator.t.sol";
+import {SimulateExecuteTest} from "test/SimulateExecute.t.sol";
+import {SignatureWrapper} from "test/utils/interfaces/ICoinbaseSmartWallet.sol";
+import {IERC4337EntryPoint, IStakeManager, PackedUserOperation, UserOperation} from "test/utils/interfaces/IERC4337EntryPoint.sol";
+
+interface Vm {
+ function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);
+ function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory);
+ function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);
+ function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json);
+ function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json);
+}
+
+library JsonBindings {
+ Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
+
+ string constant schema_Intent = "Intent(address eoa,bytes executionData,uint256 nonce,address payer,address paymentToken,uint256 paymentMaxAmount,uint256 combinedGas,bytes[] encodedPreCalls,bytes[] encodedFundTransfers,address settler,uint256 expiry,bool isMultichain,address funder,bytes funderSignature,bytes settlerContext,uint256 paymentAmount,address paymentRecipient,bytes signature,bytes paymentSignature,address supportedAccountImplementation)";
+ string constant schema_SignedCall = "SignedCall(address eoa,bytes executionData,uint256 nonce,bytes signature)";
+ string constant schema_Transfer = "Transfer(address token,uint256 amount)";
+ string constant schema_SpendInfo = "SpendInfo(address token,uint8 period,uint256 limit,uint256 spent,uint256 lastUpdated,uint256 currentSpent,uint256 current)";
+ string constant schema_CallCheckerInfo = "CallCheckerInfo(address target,address checker)";
+ string constant schema_TokenPeriodSpend = "TokenPeriodSpend(uint256 limit,uint256 spent,uint256 lastUpdated)";
+ string constant schema__ExecuteTemps = "_ExecuteTemps(DynamicArray approvedERC20s,DynamicArray approvalSpenders,DynamicArray erc20s,DynamicArray transferAmounts,DynamicArray permit2ERC20s,DynamicArray permit2Spenders)DynamicArray(uint256[] data)";
+ string constant schema_TStack = "TStack(uint256 slot)";
+ string constant schema_Key = "Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)";
+ string constant schema_KeyExtraStorage = "KeyExtraStorage(AddressSet checkers)AddressSet(uint256 _spacer)";
+ string constant schema_Escrow = "Escrow(bytes12 salt,address depositor,address recipient,address token,uint256 escrowAmount,uint256 refundAmount,uint256 refundTimestamp,address settler,address sender,bytes32 settlementId,uint256 senderChainId)";
+ string constant schema_Config = "Config(uint256 threshold,bytes32[] ownerKeyHashes)";
+ string constant schema_TargetFunctionPayload = "TargetFunctionPayload(address by,uint256 value,bytes data)";
+ string constant schema_PassKey = "PassKey(Key k,uint256 privateKey,bytes32 keyHash)Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)";
+ string constant schema_MultiSigKey = "MultiSigKey(Key k,uint256 threshold,PassKey[] owners)Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)PassKey(Key k,uint256 privateKey,bytes32 keyHash)";
+ string constant schema_DelegatedEOA = "DelegatedEOA(address eoa,uint256 privateKey,address d)";
+ string constant schema__EstimateGasParams = "_EstimateGasParams(Intent u,uint8 paymentPerGasPrecision,uint256 paymentPerGas,uint256 combinedGasIncrement,uint256 combinedGasVerificationOffset)Intent(address eoa,bytes executionData,uint256 nonce,address payer,address paymentToken,uint256 paymentMaxAmount,uint256 combinedGas,bytes[] encodedPreCalls,bytes[] encodedFundTransfers,address settler,uint256 expiry,bool isMultichain,address funder,bytes funderSignature,bytes settlerContext,uint256 paymentAmount,address paymentRecipient,bytes signature,bytes paymentSignature,address supportedAccountImplementation)";
+ string constant schema__TestExecuteWithSignatureTemps = "_TestExecuteWithSignatureTemps(TargetFunctionPayload[] targetFunctionPayloads,Call[] calls,uint256 n,uint256 nonce,bytes opData,bytes executionData)Call(address to,uint256 value,bytes data)TargetFunctionPayload(address by,uint256 value,bytes data)";
+ string constant schema__TestUpgradeAccountWithPassKeyTemps = "_TestUpgradeAccountWithPassKeyTemps(uint256 randomVersion,address implementation,Call[] calls,uint256 nonce,bytes opData,bytes executionData)Call(address to,uint256 value,bytes data)";
+ string constant schema_DepositInfo = "DepositInfo(uint256 deposit,bool staked,uint112 stake,uint32 unstakeDelaySec,uint48 withdrawTime)";
+ string constant schema_StakeInfo = "StakeInfo(uint256 stake,uint256 unstakeDelaySec)";
+ string constant schema_PackedUserOperation = "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData,bytes signature)";
+ string constant schema_UserOperation = "UserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)";
+ string constant schema_UserOpsPerAggregator = "UserOpsPerAggregator(PackedUserOperation[] userOps,address aggregator,bytes signature)PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData,bytes signature)";
+ string constant schema_ReturnInfo = "ReturnInfo(uint256 preOpGas,uint256 prefund,uint256 accountValidationData,uint256 paymasterValidationData,bytes paymasterContext)";
+ string constant schema_SignatureWrapper = "SignatureWrapper(uint8 ownerIndex,bytes signatureData)";
+ string constant schema_EnforcedOptionParam = "EnforcedOptionParam(uint32 eid,uint16 msgType,bytes options)";
+ string constant schema_MultiSigTestTemps = "MultiSigTestTemps(PassKey[] owners,bytes32[] ownerKeyHashes,uint256 threshold,MultiSigKey multiSigKey,bytes32 digest)Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)MultiSigKey(Key k,uint256 threshold,PassKey[] owners)PassKey(Key k,uint256 privateKey,bytes32 keyHash)";
+ string constant schema__TestFullFlowTemps = "_TestFullFlowTemps(Intent[] intents,TargetFunctionPayload[] targetFunctionPayloads,DelegatedEOA[] delegatedEOAs,bytes[] encodedIntents)DelegatedEOA(address eoa,uint256 privateKey,address d)Intent(address eoa,bytes executionData,uint256 nonce,address payer,address paymentToken,uint256 paymentMaxAmount,uint256 combinedGas,bytes[] encodedPreCalls,bytes[] encodedFundTransfers,address settler,uint256 expiry,bool isMultichain,address funder,bytes funderSignature,bytes settlerContext,uint256 paymentAmount,address paymentRecipient,bytes signature,bytes paymentSignature,address supportedAccountImplementation)TargetFunctionPayload(address by,uint256 value,bytes data)";
+ string constant schema__TestAuthorizeWithPreCallsAndTransferTemps = "_TestAuthorizeWithPreCallsAndTransferTemps(uint256 gExecute,uint256 gCombined,uint256 gUsed,bool success,bytes result,bool testInvalidPreCallEOA,bool testPreCallVerificationError,bool testPreCallError,bool testInit,bool testEOACoalesce,bool testSkipNonce,uint192 superAdminNonceSeqKey,uint192 sessionNonceSeqKey,uint256 retrievedSuperAdminNonce,uint256 retrievedSessionNonce,PassKey kInit,DelegatedEOA d,address eoa)DelegatedEOA(address eoa,uint256 privateKey,address d)Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)PassKey(Key k,uint256 privateKey,bytes32 keyHash)";
+ string constant schema__TestPayViaAnotherPayerTemps = "_TestPayViaAnotherPayerTemps(address withState,address withSignature,DelegatedEOA withSignatureEOA,address token,uint256 funds,bool isWithState,bool corruptSignature,bool unapprovedOrchestrator,uint256 balanceBefore,DelegatedEOA d)DelegatedEOA(address eoa,uint256 privateKey,address d)";
+ string constant schema__TestAccountImplementationVerificationTemps = "_TestAccountImplementationVerificationTemps(bool testImplementationCheck,bool requireWrongImplementation,DelegatedEOA d)DelegatedEOA(address eoa,uint256 privateKey,address d)";
+ string constant schema__TestMultiSigTemps = "_TestMultiSigTemps(DelegatedEOA d,address multiSigSigner,uint256 numKeys,MultiSigKey multiSigKey)DelegatedEOA(address eoa,uint256 privateKey,address d)Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)MultiSigKey(Key k,uint256 threshold,PassKey[] owners)PassKey(Key k,uint256 privateKey,bytes32 keyHash)";
+ string constant schema__TestMultiChainIntentTemps = "_TestMultiChainIntentTemps(address funder,address settler,uint256 funderPrivateKey,address usdcMainnet,address usdcArb,address usdcBase,DelegatedEOA d,PassKey k,Intent baseIntent,Intent arbIntent,Intent outputIntent,bytes32[] leafs,bytes32 root,bytes rootSig,address gasWallet,address relay,address friend,address settlementOracle,address escrowBase,address escrowArb,bytes32 settlementId,bytes32 escrowIdBase,bytes32 escrowIdArb,bytes[] encodedIntents,bytes4[] errs,uint256 snapshot)DelegatedEOA(address eoa,uint256 privateKey,address d)Intent(address eoa,bytes executionData,uint256 nonce,address payer,address paymentToken,uint256 paymentMaxAmount,uint256 combinedGas,bytes[] encodedPreCalls,bytes[] encodedFundTransfers,address settler,uint256 expiry,bool isMultichain,address funder,bytes funderSignature,bytes settlerContext,uint256 paymentAmount,address paymentRecipient,bytes signature,bytes paymentSignature,address supportedAccountImplementation)Key(uint40 expiry,uint8 keyType,bool isSuperAdmin,bytes publicKey)PassKey(Key k,uint256 privateKey,bytes32 keyHash)";
+ string constant schema__SimulateExecuteTemps = "_SimulateExecuteTemps(uint256 gasToBurn,uint256 randomness,uint256 gExecute,uint256 gCombined,uint256 gUsed,bytes executionData,bool success,bytes result)";
+
+ function serialize(ICommon.Intent memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_Intent, abi.encode(value));
+ }
+
+ function serialize(ICommon.Intent memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_Intent, abi.encode(value));
+ }
+
+ function deserializeIntent(string memory json) public pure returns (ICommon.Intent memory) {
+ return abi.decode(vm.parseJsonType(json, schema_Intent), (ICommon.Intent));
+ }
+
+ function deserializeIntent(string memory json, string memory path) public pure returns (ICommon.Intent memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_Intent), (ICommon.Intent));
+ }
+
+ function deserializeIntentArray(string memory json, string memory path) public pure returns (ICommon.Intent[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_Intent), (ICommon.Intent[]));
+ }
+
+ function serialize(ICommon.SignedCall memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_SignedCall, abi.encode(value));
+ }
+
+ function serialize(ICommon.SignedCall memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_SignedCall, abi.encode(value));
+ }
+
+ function deserializeSignedCall(string memory json) public pure returns (ICommon.SignedCall memory) {
+ return abi.decode(vm.parseJsonType(json, schema_SignedCall), (ICommon.SignedCall));
+ }
+
+ function deserializeSignedCall(string memory json, string memory path) public pure returns (ICommon.SignedCall memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_SignedCall), (ICommon.SignedCall));
+ }
+
+ function deserializeSignedCallArray(string memory json, string memory path) public pure returns (ICommon.SignedCall[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_SignedCall), (ICommon.SignedCall[]));
+ }
+
+ function serialize(ICommon.Transfer memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_Transfer, abi.encode(value));
+ }
+
+ function serialize(ICommon.Transfer memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_Transfer, abi.encode(value));
+ }
+
+ function deserializeTransfer(string memory json) public pure returns (ICommon.Transfer memory) {
+ return abi.decode(vm.parseJsonType(json, schema_Transfer), (ICommon.Transfer));
+ }
+
+ function deserializeTransfer(string memory json, string memory path) public pure returns (ICommon.Transfer memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_Transfer), (ICommon.Transfer));
+ }
+
+ function deserializeTransferArray(string memory json, string memory path) public pure returns (ICommon.Transfer[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_Transfer), (ICommon.Transfer[]));
+ }
+
+ function serialize(GuardedExecutor.SpendInfo memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_SpendInfo, abi.encode(value));
+ }
+
+ function serialize(GuardedExecutor.SpendInfo memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_SpendInfo, abi.encode(value));
+ }
+
+ function deserializeSpendInfo(string memory json) public pure returns (GuardedExecutor.SpendInfo memory) {
+ return abi.decode(vm.parseJsonType(json, schema_SpendInfo), (GuardedExecutor.SpendInfo));
+ }
+
+ function deserializeSpendInfo(string memory json, string memory path) public pure returns (GuardedExecutor.SpendInfo memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_SpendInfo), (GuardedExecutor.SpendInfo));
+ }
+
+ function deserializeSpendInfoArray(string memory json, string memory path) public pure returns (GuardedExecutor.SpendInfo[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_SpendInfo), (GuardedExecutor.SpendInfo[]));
+ }
+
+ function serialize(GuardedExecutor.CallCheckerInfo memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_CallCheckerInfo, abi.encode(value));
+ }
+
+ function serialize(GuardedExecutor.CallCheckerInfo memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_CallCheckerInfo, abi.encode(value));
+ }
+
+ function deserializeCallCheckerInfo(string memory json) public pure returns (GuardedExecutor.CallCheckerInfo memory) {
+ return abi.decode(vm.parseJsonType(json, schema_CallCheckerInfo), (GuardedExecutor.CallCheckerInfo));
+ }
+
+ function deserializeCallCheckerInfo(string memory json, string memory path) public pure returns (GuardedExecutor.CallCheckerInfo memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_CallCheckerInfo), (GuardedExecutor.CallCheckerInfo));
+ }
+
+ function deserializeCallCheckerInfoArray(string memory json, string memory path) public pure returns (GuardedExecutor.CallCheckerInfo[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_CallCheckerInfo), (GuardedExecutor.CallCheckerInfo[]));
+ }
+
+ function serialize(GuardedExecutor.TokenPeriodSpend memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_TokenPeriodSpend, abi.encode(value));
+ }
+
+ function serialize(GuardedExecutor.TokenPeriodSpend memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_TokenPeriodSpend, abi.encode(value));
+ }
+
+ function deserializeTokenPeriodSpend(string memory json) public pure returns (GuardedExecutor.TokenPeriodSpend memory) {
+ return abi.decode(vm.parseJsonType(json, schema_TokenPeriodSpend), (GuardedExecutor.TokenPeriodSpend));
+ }
+
+ function deserializeTokenPeriodSpend(string memory json, string memory path) public pure returns (GuardedExecutor.TokenPeriodSpend memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_TokenPeriodSpend), (GuardedExecutor.TokenPeriodSpend));
+ }
+
+ function deserializeTokenPeriodSpendArray(string memory json, string memory path) public pure returns (GuardedExecutor.TokenPeriodSpend[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_TokenPeriodSpend), (GuardedExecutor.TokenPeriodSpend[]));
+ }
+
+ function serialize(GuardedExecutor._ExecuteTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__ExecuteTemps, abi.encode(value));
+ }
+
+ function serialize(GuardedExecutor._ExecuteTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__ExecuteTemps, abi.encode(value));
+ }
+
+ function deserialize_ExecuteTemps(string memory json) public pure returns (GuardedExecutor._ExecuteTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__ExecuteTemps), (GuardedExecutor._ExecuteTemps));
+ }
+
+ function deserialize_ExecuteTemps(string memory json, string memory path) public pure returns (GuardedExecutor._ExecuteTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__ExecuteTemps), (GuardedExecutor._ExecuteTemps));
+ }
+
+ function deserialize_ExecuteTempsArray(string memory json, string memory path) public pure returns (GuardedExecutor._ExecuteTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__ExecuteTemps), (GuardedExecutor._ExecuteTemps[]));
+ }
+
+ function serialize(LibTStack.TStack memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_TStack, abi.encode(value));
+ }
+
+ function serialize(LibTStack.TStack memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_TStack, abi.encode(value));
+ }
+
+ function deserializeTStack(string memory json) public pure returns (LibTStack.TStack memory) {
+ return abi.decode(vm.parseJsonType(json, schema_TStack), (LibTStack.TStack));
+ }
+
+ function deserializeTStack(string memory json, string memory path) public pure returns (LibTStack.TStack memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_TStack), (LibTStack.TStack));
+ }
+
+ function deserializeTStackArray(string memory json, string memory path) public pure returns (LibTStack.TStack[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_TStack), (LibTStack.TStack[]));
+ }
+
+ function serialize(IthacaAccount.Key memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_Key, abi.encode(value));
+ }
+
+ function serialize(IthacaAccount.Key memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_Key, abi.encode(value));
+ }
+
+ function deserializeKey(string memory json) public pure returns (IthacaAccount.Key memory) {
+ return abi.decode(vm.parseJsonType(json, schema_Key), (IthacaAccount.Key));
+ }
+
+ function deserializeKey(string memory json, string memory path) public pure returns (IthacaAccount.Key memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_Key), (IthacaAccount.Key));
+ }
+
+ function deserializeKeyArray(string memory json, string memory path) public pure returns (IthacaAccount.Key[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_Key), (IthacaAccount.Key[]));
+ }
+
+ function serialize(IthacaAccount.KeyExtraStorage memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_KeyExtraStorage, abi.encode(value));
+ }
+
+ function serialize(IthacaAccount.KeyExtraStorage memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_KeyExtraStorage, abi.encode(value));
+ }
+
+ function deserializeKeyExtraStorage(string memory json) public pure returns (IthacaAccount.KeyExtraStorage memory) {
+ return abi.decode(vm.parseJsonType(json, schema_KeyExtraStorage), (IthacaAccount.KeyExtraStorage));
+ }
+
+ function deserializeKeyExtraStorage(string memory json, string memory path) public pure returns (IthacaAccount.KeyExtraStorage memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_KeyExtraStorage), (IthacaAccount.KeyExtraStorage));
+ }
+
+ function deserializeKeyExtraStorageArray(string memory json, string memory path) public pure returns (IthacaAccount.KeyExtraStorage[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_KeyExtraStorage), (IthacaAccount.KeyExtraStorage[]));
+ }
+
+ function serialize(IEscrow.Escrow memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_Escrow, abi.encode(value));
+ }
+
+ function serialize(IEscrow.Escrow memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_Escrow, abi.encode(value));
+ }
+
+ function deserializeEscrow(string memory json) public pure returns (IEscrow.Escrow memory) {
+ return abi.decode(vm.parseJsonType(json, schema_Escrow), (IEscrow.Escrow));
+ }
+
+ function deserializeEscrow(string memory json, string memory path) public pure returns (IEscrow.Escrow memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_Escrow), (IEscrow.Escrow));
+ }
+
+ function deserializeEscrowArray(string memory json, string memory path) public pure returns (IEscrow.Escrow[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_Escrow), (IEscrow.Escrow[]));
+ }
+
+ function serialize(MultiSigSigner.Config memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_Config, abi.encode(value));
+ }
+
+ function serialize(MultiSigSigner.Config memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_Config, abi.encode(value));
+ }
+
+ function deserializeConfig(string memory json) public pure returns (MultiSigSigner.Config memory) {
+ return abi.decode(vm.parseJsonType(json, schema_Config), (MultiSigSigner.Config));
+ }
+
+ function deserializeConfig(string memory json, string memory path) public pure returns (MultiSigSigner.Config memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_Config), (MultiSigSigner.Config));
+ }
+
+ function deserializeConfigArray(string memory json, string memory path) public pure returns (MultiSigSigner.Config[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_Config), (MultiSigSigner.Config[]));
+ }
+
+ function serialize(BaseTest.TargetFunctionPayload memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_TargetFunctionPayload, abi.encode(value));
+ }
+
+ function serialize(BaseTest.TargetFunctionPayload memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_TargetFunctionPayload, abi.encode(value));
+ }
+
+ function deserializeTargetFunctionPayload(string memory json) public pure returns (BaseTest.TargetFunctionPayload memory) {
+ return abi.decode(vm.parseJsonType(json, schema_TargetFunctionPayload), (BaseTest.TargetFunctionPayload));
+ }
+
+ function deserializeTargetFunctionPayload(string memory json, string memory path) public pure returns (BaseTest.TargetFunctionPayload memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_TargetFunctionPayload), (BaseTest.TargetFunctionPayload));
+ }
+
+ function deserializeTargetFunctionPayloadArray(string memory json, string memory path) public pure returns (BaseTest.TargetFunctionPayload[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_TargetFunctionPayload), (BaseTest.TargetFunctionPayload[]));
+ }
+
+ function serialize(BaseTest.PassKey memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_PassKey, abi.encode(value));
+ }
+
+ function serialize(BaseTest.PassKey memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_PassKey, abi.encode(value));
+ }
+
+ function deserializePassKey(string memory json) public pure returns (BaseTest.PassKey memory) {
+ return abi.decode(vm.parseJsonType(json, schema_PassKey), (BaseTest.PassKey));
+ }
+
+ function deserializePassKey(string memory json, string memory path) public pure returns (BaseTest.PassKey memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_PassKey), (BaseTest.PassKey));
+ }
+
+ function deserializePassKeyArray(string memory json, string memory path) public pure returns (BaseTest.PassKey[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_PassKey), (BaseTest.PassKey[]));
+ }
+
+ function serialize(BaseTest.MultiSigKey memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_MultiSigKey, abi.encode(value));
+ }
+
+ function serialize(BaseTest.MultiSigKey memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_MultiSigKey, abi.encode(value));
+ }
+
+ function deserializeMultiSigKey(string memory json) public pure returns (BaseTest.MultiSigKey memory) {
+ return abi.decode(vm.parseJsonType(json, schema_MultiSigKey), (BaseTest.MultiSigKey));
+ }
+
+ function deserializeMultiSigKey(string memory json, string memory path) public pure returns (BaseTest.MultiSigKey memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_MultiSigKey), (BaseTest.MultiSigKey));
+ }
+
+ function deserializeMultiSigKeyArray(string memory json, string memory path) public pure returns (BaseTest.MultiSigKey[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_MultiSigKey), (BaseTest.MultiSigKey[]));
+ }
+
+ function serialize(BaseTest.DelegatedEOA memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_DelegatedEOA, abi.encode(value));
+ }
+
+ function serialize(BaseTest.DelegatedEOA memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_DelegatedEOA, abi.encode(value));
+ }
+
+ function deserializeDelegatedEOA(string memory json) public pure returns (BaseTest.DelegatedEOA memory) {
+ return abi.decode(vm.parseJsonType(json, schema_DelegatedEOA), (BaseTest.DelegatedEOA));
+ }
+
+ function deserializeDelegatedEOA(string memory json, string memory path) public pure returns (BaseTest.DelegatedEOA memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_DelegatedEOA), (BaseTest.DelegatedEOA));
+ }
+
+ function deserializeDelegatedEOAArray(string memory json, string memory path) public pure returns (BaseTest.DelegatedEOA[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_DelegatedEOA), (BaseTest.DelegatedEOA[]));
+ }
+
+ function serialize(BaseTest._EstimateGasParams memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__EstimateGasParams, abi.encode(value));
+ }
+
+ function serialize(BaseTest._EstimateGasParams memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__EstimateGasParams, abi.encode(value));
+ }
+
+ function deserialize_EstimateGasParams(string memory json) public pure returns (BaseTest._EstimateGasParams memory) {
+ return abi.decode(vm.parseJsonType(json, schema__EstimateGasParams), (BaseTest._EstimateGasParams));
+ }
+
+ function deserialize_EstimateGasParams(string memory json, string memory path) public pure returns (BaseTest._EstimateGasParams memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__EstimateGasParams), (BaseTest._EstimateGasParams));
+ }
+
+ function deserialize_EstimateGasParamsArray(string memory json, string memory path) public pure returns (BaseTest._EstimateGasParams[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__EstimateGasParams), (BaseTest._EstimateGasParams[]));
+ }
+
+ function serialize(AccountTest._TestExecuteWithSignatureTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestExecuteWithSignatureTemps, abi.encode(value));
+ }
+
+ function serialize(AccountTest._TestExecuteWithSignatureTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestExecuteWithSignatureTemps, abi.encode(value));
+ }
+
+ function deserialize_TestExecuteWithSignatureTemps(string memory json) public pure returns (AccountTest._TestExecuteWithSignatureTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestExecuteWithSignatureTemps), (AccountTest._TestExecuteWithSignatureTemps));
+ }
+
+ function deserialize_TestExecuteWithSignatureTemps(string memory json, string memory path) public pure returns (AccountTest._TestExecuteWithSignatureTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestExecuteWithSignatureTemps), (AccountTest._TestExecuteWithSignatureTemps));
+ }
+
+ function deserialize_TestExecuteWithSignatureTempsArray(string memory json, string memory path) public pure returns (AccountTest._TestExecuteWithSignatureTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestExecuteWithSignatureTemps), (AccountTest._TestExecuteWithSignatureTemps[]));
+ }
+
+ function serialize(AccountTest._TestUpgradeAccountWithPassKeyTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestUpgradeAccountWithPassKeyTemps, abi.encode(value));
+ }
+
+ function serialize(AccountTest._TestUpgradeAccountWithPassKeyTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestUpgradeAccountWithPassKeyTemps, abi.encode(value));
+ }
+
+ function deserialize_TestUpgradeAccountWithPassKeyTemps(string memory json) public pure returns (AccountTest._TestUpgradeAccountWithPassKeyTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestUpgradeAccountWithPassKeyTemps), (AccountTest._TestUpgradeAccountWithPassKeyTemps));
+ }
+
+ function deserialize_TestUpgradeAccountWithPassKeyTemps(string memory json, string memory path) public pure returns (AccountTest._TestUpgradeAccountWithPassKeyTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestUpgradeAccountWithPassKeyTemps), (AccountTest._TestUpgradeAccountWithPassKeyTemps));
+ }
+
+ function deserialize_TestUpgradeAccountWithPassKeyTempsArray(string memory json, string memory path) public pure returns (AccountTest._TestUpgradeAccountWithPassKeyTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestUpgradeAccountWithPassKeyTemps), (AccountTest._TestUpgradeAccountWithPassKeyTemps[]));
+ }
+
+ function serialize(IStakeManager.DepositInfo memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_DepositInfo, abi.encode(value));
+ }
+
+ function serialize(IStakeManager.DepositInfo memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_DepositInfo, abi.encode(value));
+ }
+
+ function deserializeDepositInfo(string memory json) public pure returns (IStakeManager.DepositInfo memory) {
+ return abi.decode(vm.parseJsonType(json, schema_DepositInfo), (IStakeManager.DepositInfo));
+ }
+
+ function deserializeDepositInfo(string memory json, string memory path) public pure returns (IStakeManager.DepositInfo memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_DepositInfo), (IStakeManager.DepositInfo));
+ }
+
+ function deserializeDepositInfoArray(string memory json, string memory path) public pure returns (IStakeManager.DepositInfo[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_DepositInfo), (IStakeManager.DepositInfo[]));
+ }
+
+ function serialize(IStakeManager.StakeInfo memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_StakeInfo, abi.encode(value));
+ }
+
+ function serialize(IStakeManager.StakeInfo memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_StakeInfo, abi.encode(value));
+ }
+
+ function deserializeStakeInfo(string memory json) public pure returns (IStakeManager.StakeInfo memory) {
+ return abi.decode(vm.parseJsonType(json, schema_StakeInfo), (IStakeManager.StakeInfo));
+ }
+
+ function deserializeStakeInfo(string memory json, string memory path) public pure returns (IStakeManager.StakeInfo memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_StakeInfo), (IStakeManager.StakeInfo));
+ }
+
+ function deserializeStakeInfoArray(string memory json, string memory path) public pure returns (IStakeManager.StakeInfo[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_StakeInfo), (IStakeManager.StakeInfo[]));
+ }
+
+ function serialize(PackedUserOperation memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_PackedUserOperation, abi.encode(value));
+ }
+
+ function serialize(PackedUserOperation memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_PackedUserOperation, abi.encode(value));
+ }
+
+ function deserializePackedUserOperation(string memory json) public pure returns (PackedUserOperation memory) {
+ return abi.decode(vm.parseJsonType(json, schema_PackedUserOperation), (PackedUserOperation));
+ }
+
+ function deserializePackedUserOperation(string memory json, string memory path) public pure returns (PackedUserOperation memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_PackedUserOperation), (PackedUserOperation));
+ }
+
+ function deserializePackedUserOperationArray(string memory json, string memory path) public pure returns (PackedUserOperation[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_PackedUserOperation), (PackedUserOperation[]));
+ }
+
+ function serialize(UserOperation memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_UserOperation, abi.encode(value));
+ }
+
+ function serialize(UserOperation memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_UserOperation, abi.encode(value));
+ }
+
+ function deserializeUserOperation(string memory json) public pure returns (UserOperation memory) {
+ return abi.decode(vm.parseJsonType(json, schema_UserOperation), (UserOperation));
+ }
+
+ function deserializeUserOperation(string memory json, string memory path) public pure returns (UserOperation memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_UserOperation), (UserOperation));
+ }
+
+ function deserializeUserOperationArray(string memory json, string memory path) public pure returns (UserOperation[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_UserOperation), (UserOperation[]));
+ }
+
+ function serialize(IERC4337EntryPoint.UserOpsPerAggregator memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_UserOpsPerAggregator, abi.encode(value));
+ }
+
+ function serialize(IERC4337EntryPoint.UserOpsPerAggregator memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_UserOpsPerAggregator, abi.encode(value));
+ }
+
+ function deserializeUserOpsPerAggregator(string memory json) public pure returns (IERC4337EntryPoint.UserOpsPerAggregator memory) {
+ return abi.decode(vm.parseJsonType(json, schema_UserOpsPerAggregator), (IERC4337EntryPoint.UserOpsPerAggregator));
+ }
+
+ function deserializeUserOpsPerAggregator(string memory json, string memory path) public pure returns (IERC4337EntryPoint.UserOpsPerAggregator memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_UserOpsPerAggregator), (IERC4337EntryPoint.UserOpsPerAggregator));
+ }
+
+ function deserializeUserOpsPerAggregatorArray(string memory json, string memory path) public pure returns (IERC4337EntryPoint.UserOpsPerAggregator[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_UserOpsPerAggregator), (IERC4337EntryPoint.UserOpsPerAggregator[]));
+ }
+
+ function serialize(IERC4337EntryPoint.ReturnInfo memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_ReturnInfo, abi.encode(value));
+ }
+
+ function serialize(IERC4337EntryPoint.ReturnInfo memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_ReturnInfo, abi.encode(value));
+ }
+
+ function deserializeReturnInfo(string memory json) public pure returns (IERC4337EntryPoint.ReturnInfo memory) {
+ return abi.decode(vm.parseJsonType(json, schema_ReturnInfo), (IERC4337EntryPoint.ReturnInfo));
+ }
+
+ function deserializeReturnInfo(string memory json, string memory path) public pure returns (IERC4337EntryPoint.ReturnInfo memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_ReturnInfo), (IERC4337EntryPoint.ReturnInfo));
+ }
+
+ function deserializeReturnInfoArray(string memory json, string memory path) public pure returns (IERC4337EntryPoint.ReturnInfo[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_ReturnInfo), (IERC4337EntryPoint.ReturnInfo[]));
+ }
+
+ function serialize(SignatureWrapper memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_SignatureWrapper, abi.encode(value));
+ }
+
+ function serialize(SignatureWrapper memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_SignatureWrapper, abi.encode(value));
+ }
+
+ function deserializeSignatureWrapper(string memory json) public pure returns (SignatureWrapper memory) {
+ return abi.decode(vm.parseJsonType(json, schema_SignatureWrapper), (SignatureWrapper));
+ }
+
+ function deserializeSignatureWrapper(string memory json, string memory path) public pure returns (SignatureWrapper memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_SignatureWrapper), (SignatureWrapper));
+ }
+
+ function deserializeSignatureWrapperArray(string memory json, string memory path) public pure returns (SignatureWrapper[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_SignatureWrapper), (SignatureWrapper[]));
+ }
+
+ function serialize(EnforcedOptionParam memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_EnforcedOptionParam, abi.encode(value));
+ }
+
+ function serialize(EnforcedOptionParam memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_EnforcedOptionParam, abi.encode(value));
+ }
+
+ function deserializeEnforcedOptionParam(string memory json) public pure returns (EnforcedOptionParam memory) {
+ return abi.decode(vm.parseJsonType(json, schema_EnforcedOptionParam), (EnforcedOptionParam));
+ }
+
+ function deserializeEnforcedOptionParam(string memory json, string memory path) public pure returns (EnforcedOptionParam memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_EnforcedOptionParam), (EnforcedOptionParam));
+ }
+
+ function deserializeEnforcedOptionParamArray(string memory json, string memory path) public pure returns (EnforcedOptionParam[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_EnforcedOptionParam), (EnforcedOptionParam[]));
+ }
+
+ function serialize(MultiSigSignerTest.MultiSigTestTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema_MultiSigTestTemps, abi.encode(value));
+ }
+
+ function serialize(MultiSigSignerTest.MultiSigTestTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema_MultiSigTestTemps, abi.encode(value));
+ }
+
+ function deserializeMultiSigTestTemps(string memory json) public pure returns (MultiSigSignerTest.MultiSigTestTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema_MultiSigTestTemps), (MultiSigSignerTest.MultiSigTestTemps));
+ }
+
+ function deserializeMultiSigTestTemps(string memory json, string memory path) public pure returns (MultiSigSignerTest.MultiSigTestTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema_MultiSigTestTemps), (MultiSigSignerTest.MultiSigTestTemps));
+ }
+
+ function deserializeMultiSigTestTempsArray(string memory json, string memory path) public pure returns (MultiSigSignerTest.MultiSigTestTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema_MultiSigTestTemps), (MultiSigSignerTest.MultiSigTestTemps[]));
+ }
+
+ function serialize(OrchestratorTest._TestFullFlowTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestFullFlowTemps, abi.encode(value));
+ }
+
+ function serialize(OrchestratorTest._TestFullFlowTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestFullFlowTemps, abi.encode(value));
+ }
+
+ function deserialize_TestFullFlowTemps(string memory json) public pure returns (OrchestratorTest._TestFullFlowTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestFullFlowTemps), (OrchestratorTest._TestFullFlowTemps));
+ }
+
+ function deserialize_TestFullFlowTemps(string memory json, string memory path) public pure returns (OrchestratorTest._TestFullFlowTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestFullFlowTemps), (OrchestratorTest._TestFullFlowTemps));
+ }
+
+ function deserialize_TestFullFlowTempsArray(string memory json, string memory path) public pure returns (OrchestratorTest._TestFullFlowTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestFullFlowTemps), (OrchestratorTest._TestFullFlowTemps[]));
+ }
+
+ function serialize(OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestAuthorizeWithPreCallsAndTransferTemps, abi.encode(value));
+ }
+
+ function serialize(OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestAuthorizeWithPreCallsAndTransferTemps, abi.encode(value));
+ }
+
+ function deserialize_TestAuthorizeWithPreCallsAndTransferTemps(string memory json) public pure returns (OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestAuthorizeWithPreCallsAndTransferTemps), (OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps));
+ }
+
+ function deserialize_TestAuthorizeWithPreCallsAndTransferTemps(string memory json, string memory path) public pure returns (OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestAuthorizeWithPreCallsAndTransferTemps), (OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps));
+ }
+
+ function deserialize_TestAuthorizeWithPreCallsAndTransferTempsArray(string memory json, string memory path) public pure returns (OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestAuthorizeWithPreCallsAndTransferTemps), (OrchestratorTest._TestAuthorizeWithPreCallsAndTransferTemps[]));
+ }
+
+ function serialize(OrchestratorTest._TestPayViaAnotherPayerTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestPayViaAnotherPayerTemps, abi.encode(value));
+ }
+
+ function serialize(OrchestratorTest._TestPayViaAnotherPayerTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestPayViaAnotherPayerTemps, abi.encode(value));
+ }
+
+ function deserialize_TestPayViaAnotherPayerTemps(string memory json) public pure returns (OrchestratorTest._TestPayViaAnotherPayerTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestPayViaAnotherPayerTemps), (OrchestratorTest._TestPayViaAnotherPayerTemps));
+ }
+
+ function deserialize_TestPayViaAnotherPayerTemps(string memory json, string memory path) public pure returns (OrchestratorTest._TestPayViaAnotherPayerTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestPayViaAnotherPayerTemps), (OrchestratorTest._TestPayViaAnotherPayerTemps));
+ }
+
+ function deserialize_TestPayViaAnotherPayerTempsArray(string memory json, string memory path) public pure returns (OrchestratorTest._TestPayViaAnotherPayerTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestPayViaAnotherPayerTemps), (OrchestratorTest._TestPayViaAnotherPayerTemps[]));
+ }
+
+ function serialize(OrchestratorTest._TestAccountImplementationVerificationTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestAccountImplementationVerificationTemps, abi.encode(value));
+ }
+
+ function serialize(OrchestratorTest._TestAccountImplementationVerificationTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestAccountImplementationVerificationTemps, abi.encode(value));
+ }
+
+ function deserialize_TestAccountImplementationVerificationTemps(string memory json) public pure returns (OrchestratorTest._TestAccountImplementationVerificationTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestAccountImplementationVerificationTemps), (OrchestratorTest._TestAccountImplementationVerificationTemps));
+ }
+
+ function deserialize_TestAccountImplementationVerificationTemps(string memory json, string memory path) public pure returns (OrchestratorTest._TestAccountImplementationVerificationTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestAccountImplementationVerificationTemps), (OrchestratorTest._TestAccountImplementationVerificationTemps));
+ }
+
+ function deserialize_TestAccountImplementationVerificationTempsArray(string memory json, string memory path) public pure returns (OrchestratorTest._TestAccountImplementationVerificationTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestAccountImplementationVerificationTemps), (OrchestratorTest._TestAccountImplementationVerificationTemps[]));
+ }
+
+ function serialize(OrchestratorTest._TestMultiSigTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestMultiSigTemps, abi.encode(value));
+ }
+
+ function serialize(OrchestratorTest._TestMultiSigTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestMultiSigTemps, abi.encode(value));
+ }
+
+ function deserialize_TestMultiSigTemps(string memory json) public pure returns (OrchestratorTest._TestMultiSigTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestMultiSigTemps), (OrchestratorTest._TestMultiSigTemps));
+ }
+
+ function deserialize_TestMultiSigTemps(string memory json, string memory path) public pure returns (OrchestratorTest._TestMultiSigTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestMultiSigTemps), (OrchestratorTest._TestMultiSigTemps));
+ }
+
+ function deserialize_TestMultiSigTempsArray(string memory json, string memory path) public pure returns (OrchestratorTest._TestMultiSigTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestMultiSigTemps), (OrchestratorTest._TestMultiSigTemps[]));
+ }
+
+ function serialize(OrchestratorTest._TestMultiChainIntentTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__TestMultiChainIntentTemps, abi.encode(value));
+ }
+
+ function serialize(OrchestratorTest._TestMultiChainIntentTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__TestMultiChainIntentTemps, abi.encode(value));
+ }
+
+ function deserialize_TestMultiChainIntentTemps(string memory json) public pure returns (OrchestratorTest._TestMultiChainIntentTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__TestMultiChainIntentTemps), (OrchestratorTest._TestMultiChainIntentTemps));
+ }
+
+ function deserialize_TestMultiChainIntentTemps(string memory json, string memory path) public pure returns (OrchestratorTest._TestMultiChainIntentTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__TestMultiChainIntentTemps), (OrchestratorTest._TestMultiChainIntentTemps));
+ }
+
+ function deserialize_TestMultiChainIntentTempsArray(string memory json, string memory path) public pure returns (OrchestratorTest._TestMultiChainIntentTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__TestMultiChainIntentTemps), (OrchestratorTest._TestMultiChainIntentTemps[]));
+ }
+
+ function serialize(SimulateExecuteTest._SimulateExecuteTemps memory value) internal pure returns (string memory) {
+ return vm.serializeJsonType(schema__SimulateExecuteTemps, abi.encode(value));
+ }
+
+ function serialize(SimulateExecuteTest._SimulateExecuteTemps memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
+ return vm.serializeJsonType(objectKey, valueKey, schema__SimulateExecuteTemps, abi.encode(value));
+ }
+
+ function deserialize_SimulateExecuteTemps(string memory json) public pure returns (SimulateExecuteTest._SimulateExecuteTemps memory) {
+ return abi.decode(vm.parseJsonType(json, schema__SimulateExecuteTemps), (SimulateExecuteTest._SimulateExecuteTemps));
+ }
+
+ function deserialize_SimulateExecuteTemps(string memory json, string memory path) public pure returns (SimulateExecuteTest._SimulateExecuteTemps memory) {
+ return abi.decode(vm.parseJsonType(json, path, schema__SimulateExecuteTemps), (SimulateExecuteTest._SimulateExecuteTemps));
+ }
+
+ function deserialize_SimulateExecuteTempsArray(string memory json, string memory path) public pure returns (SimulateExecuteTest._SimulateExecuteTemps[] memory) {
+ return abi.decode(vm.parseJsonTypeArray(json, path, schema__SimulateExecuteTemps), (SimulateExecuteTest._SimulateExecuteTemps[]));
+ }
+}