Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ out/
.claude/
docs-internal/

# Deployments
deployments

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
Expand All @@ -19,3 +22,4 @@ docs-internal/
# Safe to ignore
.wake/
.vscode/
foundry.lock
55 changes: 37 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ forge fmt --check
- Implementation: `CEA` (src/CEA/CEA.sol)
- Deployment: Deterministic via `CEAFactory` using CREATE2 (src/CEA/CEAFactory.sol:32)
- Identity: One UEA → one CEA per external chain
- Gateway integration: Routes CEA→UEA transfers through `IUniversalGateway.sendUniversalTxViaCEA()` for FUNDS and FUNDS_AND_PAYLOAD tx types

### Proxy Pattern Architecture

Expand Down Expand Up @@ -122,10 +123,11 @@ Both UEA and CEA use minimal proxy (EIP-1167 clone) architecture:
**CEA Migration:**
- Mirrors UEA migration pattern for safe proxy upgrades (v1 → v2)
- `CEAMigration` (src/CEA/CEAMigration.sol) - Slot-writer migration singleton
- CEA detects `MIGRATION_SELECTOR` and routes to `_handleMigration()`
- CEA detects `MIGRATION_SELECTOR` at top-level payload (three-way branch in `_handleExecution`, aligned with UEA_EVM pattern)
- Migration payload format: `MIGRATION_SELECTOR` directly (no Multicall wrapper)
- Factory tracks migration contract via `CEA_MIGRATION_CONTRACT` state variable
- Migration executed via delegatecall (preserves all state and funds)
- Safety constraints: self-targeted, zero-value, standalone execution only
- Explicit prevention: `MIGRATION_SELECTOR` inside a Multicall array reverts with `InvalidCall`
- CEA stores factory reference to fetch migration contract at runtime

### Token Primitives
Expand Down Expand Up @@ -186,8 +188,16 @@ struct UniversalPayload {
```

**Special Selectors:**
- `MULTICALL_SELECTOR = bytes4(keccak256("UEA_MULTICALL"))` - Batch multiple calls
- `MIGRATION_SELECTOR = bytes4(keccak256("UEA_MIGRATION"))` - Trigger migration
- `MULTICALL_SELECTOR = bytes4(keccak256("UEA_MULTICALL"))` - Batch multiple calls (0xc25b8d90)
- `MIGRATION_SELECTOR = bytes4(keccak256("UEA_MIGRATION"))` - Trigger migration (0xb0c47dc5)

**UEA_EVM Execution Routing (`UEA_EVM._handleExecution`):**
- Three-way dispatch based on `payload.data`:
1. `isMulticall(payload.data)` → `_handleMulticall(payload)` — batch execution
2. `isMigration(payload.data)` → `_handleMigration(payload)` — delegatecall to factory's migration contract
3. else → `_handleSingleCall(payload)` — single target call
- `UE_MODULE` (0x14191Ea54B4c176fCf86f51b0FAc7CB1E71Df7d7) can execute without signature verification
- Factory reference stored for fetching migration contract at runtime

### CEA Execution Flow

Expand All @@ -196,6 +206,7 @@ struct UniversalPayload {
**Payload Format:**
- New format: `MULTICALL_SELECTOR + abi.encode(Multicall[])`
- Old format: Direct `abi.encode(Multicall[])` (backwards compatible)
- Migration format: `MIGRATION_SELECTOR` at top level (no Multicall wrapper)

**Multicall Structure (src/libraries/Types.sol:31):**
```solidity
Expand All @@ -207,23 +218,26 @@ struct Multicall {
```

**Execution Routing (`CEA._handleExecution`):**
1. Check if payload starts with `MULTICALL_SELECTOR`
2. If yes, decode as `Multicall[]` and route based on content:
- Single-element with `MIGRATION_SELECTOR` → `_handleMigration()`
- Otherwise → `_handleMulticall()`
3. If no, route to `_handleSingleCall()` (backwards compatibility)
1. Check if payload starts with `MULTICALL_SELECTOR` → decode as `Multicall[]`, route to `_handleMulticall()`
2. Check if payload starts with `MIGRATION_SELECTOR` → route to `_handleMigration()` (no params, top-level)
3. Otherwise → route to `_handleSingleCall()` (backwards compatibility)

**Self-Call Pattern:**
- `sendUniversalTxToUEA(token, amount)` - Transfer funds from CEA to UEA
- `sendUniversalTxToUEA(token, amount, payload)` - Transfer funds/payload from CEA to UEA via gateway
- Only callable via self-call (`msg.sender == address(this)`)
- Must be included in multicall array for execution
- Always sends empty payload/signature (funds-only transfer)
- Supports two tx types:
- **FUNDS**: amount > 0, payload empty
- **FUNDS_AND_PAYLOAD**: amount > 0, payload non-empty
- Both tx types route through `IUniversalGateway.sendUniversalTxViaCEA()`
- SDK must include ERC20 approval steps before this call

**Safety Constraints:**
- Self-calls must have `value == 0`
- Migration must be standalone (cannot be batched)
- Self-calls must have `value == 0` (enforced in `_handleMulticall`)
- Migration must be standalone (cannot appear inside multicall arrays — `isMigrationCall` check)
- Migration rejects `msg.value > 0` (logic upgrade, not value transfer)
- All calls executed sequentially via `.call()`
- No strict `msg.value == totalValue` enforcement (CEA can spend pre-existing balance)

## Key Contracts Reference

Expand Down Expand Up @@ -252,6 +266,7 @@ struct Multicall {
- `ICEA`: src/Interfaces/ICEA.sol
- `ICEAFactory`: src/Interfaces/ICEAFactory.sol
- `IUniversalCore`: src/Interfaces/IUniversalCore.sol
- `IUniversalGateway`: src/Interfaces/IUniversalGateway.sol - UniversalTxRequest struct, sendUniversalTx/sendUniversalTxViaCEA
- `IPRC20`: src/Interfaces/IPRC20.sol

## Test Structure
Expand All @@ -260,10 +275,11 @@ Tests are organized by component:
- `test/tests_uea_and_factory/` - UEA and factory tests
- `test/tests_cea/` - CEA and factory tests
- `CEA.t.sol` - Core CEA tests (82 tests, canonical helpers)
- `CEA_multicalls.t.sol` - Multicall execution tests (130 tests)
- `CEA_selfCalls.t.sol` - Self-call pattern tests (93 tests)
- Total: 418 CEA tests
- `test/tests_ceaMigration/` - CEA migration tests (42 tests)
- `CEA_multicalls.t.sol` - Multicall execution tests (132 tests)
- `CEA_selfCalls.t.sol` - Self-call pattern tests (120 tests)
- `CEAFactory.t.sol` - Factory tests (116 tests)
- Total: 450 CEA tests
- `test/tests_ceaMigration/` - CEA migration tests (43 tests)
- `CEAMigration.t.sol` - Unit tests for migration contract
- `CEAFactory_Migration.t.sol` - Factory migration management
- `CEA_Migration.t.sol` - CEA migration logic tests
Expand All @@ -279,8 +295,11 @@ Test files follow the pattern `ContractName.t.sol` and use Foundry's testing fra
**Key Test Helpers (CEA.t.sol):**
- `makeCall(to, value, data)` - Create Multicall struct
- `encodeCalls(Multicall[])` - Encode with MULTICALL_SELECTOR
- `buildWithdrawPayload(token, amount)` - Build sendUniversalTxToUEA payload
- `buildSendToUEAPayload(token, amount)` - Build sendUniversalTxToUEA payload (funds-only, empty payload)
- `buildSendToUEAPayloadWithData(token, amount, payload)` - Build sendUniversalTxToUEA with UEA payload (FUNDS_AND_PAYLOAD)
- `buildExternalSingleCall(to, value, data)` - Single external call
- `buildSelfSendToUEACall(token, amount)` - Self-call Multicall with value=0
- `buildSendToUEAMulticallPayload(token, amount, approveGateway)` - Full multicall with optional ERC20 approval

## Important Constants

Expand Down
5 changes: 4 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ solc = "0.8.26"
optimizer = true
optimizer_runs = 99999
auto_detect_solc = false
evm_version = "shanghai"
evm_version = "shanghai"
via_ir = true

fs_permissions = [{ access = "read-write", path = "deployments/" }]
83 changes: 53 additions & 30 deletions scripts/cea/deployCEAFactory.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s
*/
contract DeployCEAFactoryScript is Script {
// ============================================================================
// DEPLOYMENT PARAMETERS - Pre-Deployement Checklist
// DEPLOYMENT PARAMETERS - Pre-Deployement Checklist
// ============================================================================

// Owner of the CEAFactory (can update implementations, pause, etc.)
Expand All @@ -36,13 +36,13 @@ contract DeployCEAFactoryScript is Script {
address public VAULT_ADDRESS = 0x0000000000000000000000000000000000000001;

// UniversalGateway contract address on this chain (handles cross-chain messages)
address public UNIVERSAL_GATEWAY_ADDRESS = 0x0000000000000000000000000000000000000002;

address public UNIVERSAL_GATEWAY_ADDRESS = 0x4DCab975cDe839632db6695e2e936A29ce3e325E;

function run() external {
// Get chain ID and deployer info
uint256 chainId = block.chainid;
address deployer = vm.addr(vm.envUint("KEY"));
uint256 deployerKey = vm.envUint("KEY");
address deployer = vm.addr(deployerKey);

console.log("=== CEAFactory Deployment Configuration ===");
console.log("Chain ID:", chainId);
Expand All @@ -63,7 +63,7 @@ contract DeployCEAFactoryScript is Script {
console.log("Universal Gateway:", universalGateway);
console.log("");

vm.startBroadcast();
vm.startBroadcast(deployerKey);

// 1. Deploy CEA implementation (logic contract)
CEA ceaImplementation = new CEA();
Expand All @@ -84,19 +84,16 @@ contract DeployCEAFactoryScript is Script {
// 5. Prepare initialization data for CEAFactory
bytes memory initData = abi.encodeWithSelector(
CEAFactory.initialize.selector,
owner, // initialOwner
vault, // initialVault
address(ceaProxyImplementation), // ceaProxyImplementation
address(ceaImplementation), // ceaImplementation
universalGateway // universalGateway
owner, // initialOwner
vault, // initialVault
address(ceaProxyImplementation), // ceaProxyImplementation
address(ceaImplementation), // ceaImplementation
universalGateway // universalGateway
);

// 6. Deploy TransparentUpgradeableProxy wrapping CEAFactory
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
address(ceaFactoryImplementation),
address(proxyAdmin),
initData
);
TransparentUpgradeableProxy proxy =
new TransparentUpgradeableProxy(address(ceaFactoryImplementation), address(proxyAdmin), initData);
console.log("[5/7] CEAFactory Proxy (CANONICAL):", address(proxy));

// 7. Wrap proxy in CEAFactory interface for verification
Expand Down Expand Up @@ -124,7 +121,11 @@ contract DeployCEAFactoryScript is Script {

console.log("Factory Owner:", verifiedOwner, verifiedOwner == owner ? "[OK]" : "[MISMATCH]");
console.log("Vault:", verifiedVault, verifiedVault == vault ? "[OK]" : "[MISMATCH]");
console.log("CEA Proxy Impl:", verifiedCEAProxy, verifiedCEAProxy == address(ceaProxyImplementation) ? "[OK]" : "[MISMATCH]");
console.log(
"CEA Proxy Impl:",
verifiedCEAProxy,
verifiedCEAProxy == address(ceaProxyImplementation) ? "[OK]" : "[MISMATCH]"
);
console.log("CEA Impl:", verifiedCEA, verifiedCEA == address(ceaImplementation) ? "[OK]" : "[MISMATCH]");
console.log("Gateway:", verifiedGateway, verifiedGateway == universalGateway ? "[OK]" : "[MISMATCH]");
console.log("ProxyAdmin:", verifiedProxyAdmin);
Expand All @@ -137,20 +138,42 @@ contract DeployCEAFactoryScript is Script {

// 10. Generate JSON output for deployment tracking
console.log("\n=== Deployment Addresses (JSON) ===");
string memory json = string(abi.encodePacked(
'{\n',
' "chainId": ', vm.toString(chainId), ',\n',
' "deployer": "', vm.toString(deployer), '",\n',
' "ceaImplementation": "', vm.toString(address(ceaImplementation)), '",\n',
' "ceaProxyImplementation": "', vm.toString(address(ceaProxyImplementation)), '",\n',
' "ceaFactoryImplementation": "', vm.toString(address(ceaFactoryImplementation)), '",\n',
' "proxyAdmin": "', vm.toString(address(proxyAdmin)), '",\n',
' "ceaFactoryProxy": "', vm.toString(address(proxy)), '",\n',
' "owner": "', vm.toString(owner), '",\n',
' "vault": "', vm.toString(vault), '",\n',
' "universalGateway": "', vm.toString(universalGateway), '"\n',
'}'
));
string memory json = string(
abi.encodePacked(
"{\n",
' "chainId": ',
vm.toString(chainId),
",\n",
' "deployer": "',
vm.toString(deployer),
'",\n',
' "ceaImplementation": "',
vm.toString(address(ceaImplementation)),
'",\n',
' "ceaProxyImplementation": "',
vm.toString(address(ceaProxyImplementation)),
'",\n',
' "ceaFactoryImplementation": "',
vm.toString(address(ceaFactoryImplementation)),
'",\n',
' "proxyAdmin": "',
vm.toString(address(proxyAdmin)),
'",\n',
' "ceaFactoryProxy": "',
vm.toString(address(proxy)),
'",\n',
' "owner": "',
vm.toString(owner),
'",\n',
' "vault": "',
vm.toString(vault),
'",\n',
' "universalGateway": "',
vm.toString(universalGateway),
'"\n',
"}"
)
);
console.log(json);

// Write to file
Expand Down
Loading