> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How the executor contract works

> Isolated contract that executes intent calls on the destination chain

The Executor is an isolated contract that executes intent calls on the destination chain. It provides security isolation by separating arbitrary call execution from the Portal's storage and balance.

## Purpose

The Executor serves as a security boundary for intent execution:

**Without Executor:**

```
Portal (with storage) → executes arbitrary calls
❌ Risk: Malicious calls can manipulate Portal state
```

**With Executor:**

```
Portal (with storage) → Executor (stateless) → executes arbitrary calls
✅ Portal state protected from execution context
```

## Architecture

```
Portal
    ↓ calls
Executor (single instance)
    ↓ executes
User-specified calls (swaps, transfers, etc.)
```

**Key Properties:**

* One executor per Portal deployment
* Stateless (no storage between transactions)
* Only Portal can trigger execution
* Receives tokens from solver before execution
* Returns unused tokens/ETH to solver after execution

## Access Control

```solidity theme={null}
modifier onlyPortal()
```

The executor can only be called by the Portal that deployed it. This prevents:

* Direct execution of arbitrary calls
* Bypassing intent validation
* Unauthorized token access

## Execution Flow

### Call Execution

```solidity theme={null}
function execute(Call[] calldata calls) 
    external payable onlyPortal returns (bytes[] memory)
```

**Process:**

1. Portal validates intent and transfers tokens to executor
2. Portal calls executor with intent's call array
3. Executor validates and executes each call sequentially
4. Executor returns results to Portal
5. Portal refunds unused ETH to solver

### Single Call

```solidity theme={null}
function execute(Call calldata call) internal returns (bytes memory)
```

Each call is executed with:

* **Target**: Contract address to call
* **Value**: Native token amount to send
* **Data**: Encoded function call data

**Validation:**

* Checks if call targets EOA with calldata (security check)
* Uses low-level `call()` for execution
* Reverts entire transaction if any call fails

## Security Features

### EOA Protection

```solidity theme={null}
function _isCallToEoa(Call calldata call) internal view returns (bool)
```

Prevents calls to externally owned accounts (EOAs) when calldata is present:

```solidity theme={null}
if (target.code.length == 0 && calldata.length > 0) {
    revert CallToEOA(target);
}
```

**Why this matters:**

* EOAs cannot execute code, so calldata is meaningless
* Calldata to EOA might indicate misconfiguration
* Prevents potential phishing where calldata appears to be a function call

**Allowed:**

* Native token transfers to EOAs (zero calldata)
* Calls to contracts (non-zero code length)

**Blocked:**

* Calls to EOAs with calldata (suspicious pattern)

### Storage Isolation

The Executor has no state variables except `immutable portal`:

* Cannot be manipulated through reentrancy
* No persistent state to corrupt
* Fresh execution context for each intent

### Atomic Execution

All calls execute atomically:

* If any call fails, entire transaction reverts
* No partial execution of intent
* Solver either succeeds completely or loses only gas

## Token Handling

### Token Flow

```
Solver (approves tokens) 
    ↓
Portal (validates and transfers)
    ↓
Executor (receives tokens)
    ↓
Executor (executes calls using tokens)
    ↓
Destination contracts (swaps, transfers, etc.)
```

### Native Tokens

The Executor accepts ETH via:

```solidity theme={null}
receive() external payable {}
```

Portal transfers `route.nativeAmount` to Executor before execution:

```solidity theme={null}
executor.execute{value: route.nativeAmount}(route.calls);
```

### ERC20 Tokens

Portal transfers ERC20s to Executor before execution:

```solidity theme={null}
for (TokenAmount memory token : route.tokens) {
    IERC20(token.token).safeTransferFrom(
        solver,
        address(executor),
        token.amount
    );
}
```

Executor then uses these tokens during call execution.

## Call Structure

```solidity theme={null}
struct Call {
    address target;   // Contract to call
    uint256 value;    // Native tokens to send
    bytes data;       // Encoded function call
}
```

### Example Calls

**Token Swap:**

```solidity theme={null}
Call({
    target: uniswapRouter,
    value: 0,
    data: abi.encodeWithSelector(
        IUniswapRouter.swapExactTokensForTokens.selector,
        amountIn,
        amountOutMin,
        path,
        recipient,
        deadline
    )
})
```

**Native Token Transfer:**

```solidity theme={null}
Call({
    target: recipientAddress,
    value: 1 ether,
    data: ""  // Empty for simple transfer
})
```

**Contract Interaction:**

```solidity theme={null}
Call({
    target: dappContract,
    value: 0.1 ether,
    data: abi.encodeWithSelector(
        IDapp.deposit.selector,
        amount,
        params
    )
})
```

## Error Handling

### CallToEOA

```solidity theme={null}
error CallToEOA(address target)
```

Thrown when attempting to call an EOA with calldata.

**Resolution:**

* Remove calldata for simple transfers
* Use correct contract address if calling contract
* Verify target address is not an EOA

### CallFailed

```solidity theme={null}
error CallFailed(Call call, bytes result)
```

Thrown when any call execution fails.

**Contains:**

* Full call data (target, value, data)
* Revert reason from failed call

**Common Causes:**

* Insufficient token balance in executor
* Reverted function on target contract
* Out of gas
* Invalid function selector

### NonPortalCaller

```solidity theme={null}
error NonPortalCaller(address caller)
```

Thrown when non-Portal address attempts execution.

**Resolution:**

* Only Portal can call executor
* Use Portal's fulfill methods instead of direct executor calls

## Execution Patterns

### Simple Transfer

Intent that sends tokens to recipient:

```solidity theme={null}
Call[] memory calls = new Call[](1);
calls[0] = Call({
    target: recipientAddress,
    value: 0,
    data: ""
});
```

Executor transfers tokens that were sent to it by Portal.

### Swap and Transfer

Intent that swaps tokens and sends result:

```solidity theme={null}
Call[] memory calls = new Call[](2);

// 1. Swap on DEX
calls[0] = Call({
    target: dexRouter,
    value: 0,
    data: abi.encodeWithSelector(
        IRouter.swap.selector,
        tokenIn,
        tokenOut,
        amountIn,
        address(executor)  // Swap result to executor
    )
});

// 2. Transfer swapped tokens
calls[1] = Call({
    target: tokenOut,
    value: 0,
    data: abi.encodeWithSelector(
        IERC20.transfer.selector,
        finalRecipient,
        amountOut
    )
});
```

### Multi-Step DeFi

Intent executing complex DeFi operations:

```solidity theme={null}
Call[] memory calls = new Call[](4);

// 1. Approve DEX
calls[0] = Call({
    target: token,
    value: 0,
    data: abi.encodeWithSelector(
        IERC20.approve.selector,
        dexRouter,
        amount
    )
});

// 2. Swap
calls[1] = Call({
    target: dexRouter,
    value: 0,
    data: /* swap calldata */
});

// 3. Add liquidity
calls[2] = Call({
    target: liquidityPool,
    value: 0,
    data: /* addLiquidity calldata */
});

// 4. Transfer LP tokens
calls[3] = Call({
    target: lpToken,
    value: 0,
    data: /* transfer calldata */
});
```

## Gas Considerations

### Per-Call Overhead

Each call incurs:

* **Call validation**: \~2,000 gas (EOA check)
* **Low-level call**: \~2,600 gas base, plus execution
* **Result handling**: \~1,000 gas

### Batch Optimization

Multiple calls in one intent are more efficient than separate intents:

* Shared validation costs
* Single token transfer from solver
* Atomic execution reduces failure risk

### Failed Call Costs

When a call fails:

* Gas consumed up to failure point
* Full transaction reverts (no state changes)
* Solver pays gas but receives no reward

## Integration Patterns

### For Intent Creators

**Encoding Calls:**

```solidity theme={null}
// Create calls for route
Call[] memory calls = new Call[](2);

calls[0] = Call({
    target: tokenAddress,
    value: 0,
    data: abi.encodeWithSelector(
        IERC20.transfer.selector,
        recipient,
        amount
    )
});

// Include in intent
Intent memory intent = Intent({
    // ...
    route: Route({
        // ...
        calls: calls
    })
});
```

**Testing Calls:**

```solidity theme={null}
// Test call execution offchain
(bool success, bytes memory result) = target.call{value: value}(data);
require(success, "Call would fail");
```

### For Solvers

**Pre-Execution Validation:**

```solidity theme={null}
// Before fulfilling, validate calls won't fail
for (Call memory call : route.calls) {
    // Simulate each call
    (bool success, ) = call.target.call{value: call.value}(call.data);
    require(success, "Call simulation failed");
}

// Now fulfill intent
portal.fulfill(intentHash, route, rewardHash, claimant);
```

**Token Preparation:**

```solidity theme={null}
// Approve tokens for Portal before fulfillment
for (TokenAmount memory token : route.tokens) {
    IERC20(token.token).approve(
        address(portal),
        token.amount
    );
}
```

## Security Best Practices

### For Intent Creators

1. **Test Thoroughly**: Simulate all calls before publishing intent
2. **Minimal Privileges**: Only include necessary calls in route
3. **Verify Targets**: Ensure all target addresses are correct contracts
4. **Amount Validation**: Verify all token amounts and values are correct
5. **Deadline Safety**: Set appropriate route deadline for execution window

### For Solvers

1. **Validate Before Fulfilling**: Simulate execution to avoid wasted gas
2. **Check Token Balances**: Ensure sufficient tokens to provide route amounts
3. **Gas Estimation**: Estimate execution costs accurately
4. **Slippage Protection**: Account for price changes in DEX calls
5. **Revert Analysis**: Understand why calls might fail before executing

### For Protocol Integrators

1. **Immutable Executor**: Executor address never changes per Portal
2. **No Direct Calls**: Never call executor directly, always through Portal
3. **Result Handling**: Process execution results appropriately
4. **Error Recovery**: Handle call failures gracefully in UI/SDK

## Limitations

### No Persistent State

Executor cannot:

* Store data between transactions
* Accumulate tokens across intents
* Maintain allowances or permissions

Each execution starts fresh.

### No Token Retention

After execution:

* All tokens should be transferred to final destinations
* Executor should have zero balance
* Any remaining tokens are stuck until next execution

**Best Practice:** Include cleanup call to sweep any dust.

### Sequential Execution

Calls execute in order:

* Cannot parallelize
* Later calls depend on earlier ones
* Any failure reverts all

**Design Consideration:** Order calls carefully for dependencies.

## Comparison to Alternatives

### Direct Portal Execution

```
❌ Portal executes calls directly
Risk: Malicious calls access Portal storage
```

### Executor Pattern (Current)

```
✅ Executor executes calls in isolation
Benefit: Portal storage protected
```

### External Executor

```
❌ Separate executor deployed per intent
Cost: Higher gas for deployment
Complexity: Address management
```

The single stateless executor provides optimal balance of security, gas efficiency, and simplicity.
