用於帳戶功能的擴展執行交易架構

用於帳戶功能的擴展執行交易架構

Ethereum Magicians·大約 5 小時前

EXEC_TX 引入了一種基於鉤子執行模型的新型交易類型,旨在簡化帳戶抽象與新功能的實現,而無需不斷更改協議層。透過將驗證與執行邏輯委派給合約鉤子,該架構解決了現有帳戶功能開發中的碎片化問題,並透過二維 Nonce 設計實現了交易的並行處理。

EXEC_TX 引入了一種基於 Hook 執行(hook-based execution) 的新型交易類型。我們不為帳戶抽象、隱私、贊助和明年的新功能添加新的 EIP,而是提供一個協議層的外殼(envelope),由 Hook 來實現各項功能的邏輯。協議負責處理枯燥的部分(Nonce、Gas、多階段路由);Hook 則負責處理有趣的部分(驗證、託管、證明、策略)。


問題所在

目前,每一項新的帳戶功能都需要:

  • 新的交易類型或獨立的基礎設施(EIP-4337 捆綁器、中繼器)

  • 新的錢包簽名邏輯

  • 新的客戶端驗證規則

  • 新的內存池(mempool)邏輯

  • 新的 dApp 集成

這造成了摩擦、碎片化,並減緩了功能開發的速度。

解決方案:一種可擴展的交易類型,其功能邏輯存在於合約中,而非共識層。


Hook 模型:核心設計

什麼是 Hook?

Hook 是一個智能合約,協議會在 EXEC_TX 執行流程中的特定點調用它。協議不再由協議代碼處理驗證、託管、證明,而是將其委託給 Hook。

Hook 接口

interface IExecHook {
  // PRE_VALIDATION:確定性驗證(強制執行驗證模式)
  // 返回值:0=失敗,1=成功。Revert → 交易被拒絕。
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256);

  // PRE_EXECUTION:設置/託管(完整 EVM 環境)
  // 返回值:0=失敗(交易回滾),1=成功。Revert → 交易回滾。
  function preExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256);

  // POST_EXECUTION:清理/釋放(完整 EVM 環境,僅在核心調用成功時執行)
  // 返回值:0=失敗(整筆交易回滾),1=成功。Revert → 核心執行結果回滾。
  function postExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256);
}

Hook 語義

  • 必須僅返回 0(失敗)或 1(成功)

  • Revert(異常)= 該階段執行失敗(參見生命週期)

  • txData = ABI 編碼的 EXEC_TX;hookData = 應用程序特定數據

  • Hook 通過 phaseMask 或內部邏輯檢測當前階段

Hook 生命周期

EXEC_TX 執行流程:

  1. 基礎驗證
    ├─ 解碼 EXEC_TX
    ├─ 驗證簽名 (EOA) 或調用 isAuthorizedExecHook (合約帳戶)
    └─ 加載 Hook 合約

  2. PRE_VALIDATION(強制執行驗證模式)
    ├─ 協議調用 hook.preValidation(txData, hookData)
    ├─ Hook 邏輯:驗證簽名、檢查證明、驗證策略
    ├─ 限制:禁止寫入狀態,禁止讀取區塊相關數據
    └─ Hook 返回 0(失敗,交易被拒絕)或 1(成功,繼續)

  3. PRE_EXECUTION(如果 phaseMask 第 1 位已設置)
    ├─ 協議調用 hook.preExecution(txData, hookData)
    ├─ Hook 邏輯:鎖定資金、預留容量、發出中間狀態事件
    ├─ 限制:無(可完整訪問 EVM)
    └─ Hook 返回 0(失敗,交易回滾)或 1(成功,繼續)

  4. 核心調用 (Core CALL)
    ├─ 協議執行 CALL(to, data, value)
    ├─ 不涉及 Hook
    └─ 如果回滾,整筆交易回滾(跳過 POST_EXECUTION)

  5. POST_EXECUTION(如果 phaseMask 第 2 位已設置且核心調用成功)
    ├─ 協議調用 hook.postExecution(txData, hookData)
    ├─ Hook 邏輯:釋放託管資金、發出作廢號(nullifiers)、結算帳戶
    ├─ 限制:無(可完整訪問 EVM)
    └─ 如果 Hook 回滾,整筆交易回滾(核心調用結果被撤銷)

  6. 結算
    └─ 向支付者收取實際消耗的 Gas(或回退至 from 地址)

為什麼採用單一 Hook?

每個 EXEC_TX 僅限一個 Hook,而非多個。原因如下:

  • 最小化共識規則:協議不定義 Hook 的順序、依賴關係或衝突解決。由應用程序決定。

  • 成熟模式:EIP-4337 捆綁器邏輯證明了調度器(dispatcher)模式是成熟且經過測試的。

  • 不限制組合性:單個 Hook 可以調度至內部模塊。下文示例展示了如何通過一個 Hook 同時實現 2-of-3 驗證和限額策略。

Hook 授權

合約帳戶必須實現:

function isAuthorizedExecHook(address hookTarget) external view returns (bool);

協議在 PRE_VALIDATION 之前調用此函數:

  • 如果返回 false → 交易被拒絕

  • 如果 Revert → 交易被拒絕

  • Gas 限制:5,000 gas(固定)

EOA:簽名即為授權;不調用 isAuthorizedExecHook。

Revert 處理機制

發生時機行為結果
isAuthorizedExecHook Revert立即失敗交易被拒絕
PRE_VALIDATION Revert階段失敗交易被拒絕;收取驗證 Gas
PRE_EXECUTION Revert階段失敗交易回滾;收取所有 Gas
核心調用 Revert執行失敗交易回滾;跳過 POST_EXECUTION
POST_EXECUTION Revert階段失敗核心結果回滾;整筆交易回滾

Hook 數據編碼

Hook 邏輯在 hook.data 中傳遞(任意字節)。編碼方式由應用程序特定,而非協議強制。

// 簡單:單一用途
hook.data = abi.encode(signature1, signature2)

// 複雜:調度器路由
hook.data = abi.encode(phase0Data, phase1Data, phase2Data)

Hook 通過 phaseMask 檢測階段或解碼特定階段的數據。

簽名與 Gas 語義

簽名(針對 EOA):對 EXEC_TX 哈希進行標準 EIP-191 簽名(字段順序:chainId, nonceKey, nonceSeq, from, to, value, data, gasLimit, maxFeePerGas, payer, payerData, hook, v, r, s)。

合約帳戶:無簽名。Hook 通過 isAuthorizedExecHook 驗證授權。

Gas 預算

MAX_HOOK_GAS = gasLimit - MIN_CORE_GAS
MIN_CORE_GAS = 21_000
Intrinsic = 21_000 + calldata_bytes × 16 (非零) / 4 (零)

結算:charge = (intrinsic + 實際 hook_gas + 實際 core_gas) × maxFeePerGas


執行模型:多階段設計

phaseMask:階段選擇

一個 8 位(8-bit)值,位控制執行哪些階段:

phaseMask & 0x1 → 運行 PRE_VALIDATION (bit 0)
phaseMask & 0x2 → 運行 PRE_EXECUTION (bit 1)
phaseMask & 0x4 → 運行 POST_EXECUTION (bit 2)

示例:

  • phaseMask = 1:僅 PRE_VALIDATION

  • phaseMask = 3:PRE_VALIDATION + PRE_EXECUTION

  • phaseMask = 7:所有三個階段

  • phaseMask = 0:無 Hook;僅核心調用(行為類似傳統交易)

語義:如果 Hook 不為空,則必須運行 PRE_VALIDATION(必須設置 bit 0)。

為什麼需要四個階段?

階段時機Hook 可執行的操作協議保證
PRE_VALIDATION核心前驗證簽名、檢查證明、驗證策略禁止寫入狀態;確定性;內存池安全
PRE_EXECUTION核心前鎖定託管、預留容量完整 EVM 訪問;與核心調用原子化
核心調用用戶意圖(無 Hook)與傳統交易一致;可執行任何操作
POST_EXECUTION核心後 (成功時)釋放託管、發出作廢號完整 EVM 訪問;與核心原子化;若 Revert 則全部回滾

為什麼採用這種結構?

  • PRE_VALIDATION:在接觸狀態前進行確定性檢查。內存池節點可以在本地驗證而不會產生分歧。

  • PRE_EXECUTION:核心調用前的設置(鎖定資金、預留)。核心調用可以假設資源已就緒。

  • 核心調用:與傳統交易相同。用戶實際的交易內容。

  • POST_EXECUTION:核心成功後的清理。託管會計是原子化的:要麼核心成功且資金釋放,要麼兩者都不發生。

驗證模式:為何限制 PRE_VALIDATION?

內存池節點在將交易納入區塊前會在本地驗證。如果 PRE_VALIDATION 代碼讀取區塊狀態:

節點 A 在區塊 1000:BALANCE 檢查通過(支付者有 1 ETH)
節點 B 在區塊 1001:支付者資金被抽乾,BALANCE 檢查失敗
結果:同一筆交易在 A 有效,在 B 無效 → 導致內存池分區。

解決方案:驗證模式禁止:

  • SLOAD, SSTORE(狀態讀/寫)

  • TIMESTAMP, BLOCKHASH, BASEFEE, BLOBBASEFEE, PREVRANDAO, COINBASE, GASLIMIT(區塊相關)

  • 例外:BALANCE, SELFBALANCE(支付者流動性檢查至關重要;TOCTOU 風險通過 PRE_EXECUTION 的資金鎖定來緩解)

這確保了 PRE_VALIDATION 是確定性的:無論區塊高度如何,同一筆交易在所有節點上的結果始終一致。


二維 Nonce:並行化設計

線性 Nonce 的問題

傳統 Nonce 強制線性順序:nonce=100, 101, 102, ...。一次失敗會阻塞所有後續交易。

發送 nonce=100 的交易:待處理
發送 nonce=101 的交易:待處理(等待 100)
發送 nonce=102 的交易:待處理(等待 100 和 101)
nonce=100 的交易失敗
結果:101 和 102 被永久阻塞

解決方案:帶有獨立通道的 2D Nonce

struct Nonce {
  uint64 nonceKey;   // 通道標識符
  uint64 nonceSeq;   // 通道內的序列號
}

通道是相互獨立的

通道 0:nonce_seq=100, 101, 102, ... (支付交易)
通道 1:nonce_seq=100, 101, 102, ... (治理投票)
通道 2:nonce_seq=100, 101, 102, ... (意圖響應)

所有三個通道並行執行。通道 0 的失敗不會阻塞通道 1 或 2。

為什麼要在協議層實現?

Hook 能否自行管理 Nonce 序列?不行:

  • 共識分歧:兩個擁有相同 Hook 代碼但狀態不同的節點可能會接受/拒絕衝突的交易。

  • 內存池攻擊:攻擊者利用 Hook 狀態差異進行雙花或死鎖。

  • 協議不變量:重放保護必須是共識保證,而非應用邏輯。

協議強制執行:“在每個通道內,nonceSeq 必須等於下一個預期序列。” 應用程序可以在此之上疊加策略(例如,“通道 0 僅用於支付”)。

EOA 與合約帳戶的 Nonce 處理

帳戶類型協議強制執行應用程序責任
EOA嚴格:nonceSeq == next[from][nonceKey]
合約帳戶順序:nonceSeq <= next[from][nonceKey]Hook 必須防止重複

Nonce 狀態更新時機:在執行成功(POST_EXECUTION 完成)後,協議增加 next[from][nonceKey] = max(next[from][nonceKey], nonceSeq) + 1

風險:如果合約 Hook 不檢查 Nonce 的唯一性,同一筆 EXEC_TX 可能會執行兩次(重放攻擊)。


交易格式 (EIP-2718)

EXEC_TX 是交易類型 0x08(待定最終化)。

RLP 結構

0x08 || RLP([
  chainId, nonceKey, nonceSeq, from, to, value, data,
  gasLimit, maxFeePerGas, payer, payerData,
  hook.target, hook.phaseMask, hook.gasLimit, hook.data,
  v, r, s  // 僅限 EOA;合約帳戶為 0
])

字段說明

  • nonceKey, nonceSeq:2D Nonce(各為 uint256)

  • payer:若無贊助則為 address(0)

  • payerData:字節數據(若 payer 為 address(0) 則為空)

  • hook.target:若無 Hook 則為 address(0)

  • 所有其他字段:與傳統交易相同


實例分析:Hook 的實際應用

示例 1:簡單授權(僅 PRE_VALIDATION)

// 2-of-3 多簽合約帳戶發送代幣

EXEC_TX {
  from: safe_multisig_account,
  to: token_contract,
  data: transfer(recipient, amount),
  hook: {
    target: multisig_validator,  // 部署一次,供所有 2-of-3 帳戶複用
    phaseMask: 1,  // 僅 PRE_VALIDATION
    gasLimit: 100_000,
    data: abi.encode([sig1, sig2])  // 2-of-3 簽名
  }
}

Hook 執行

contract MultisigValidator {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    // 解碼簽名
    (bytes memory sig1, bytes memory sig2) = abi.decode(hookData, (bytes, bytes));
    
    // 從 EXEC_TX 簽名哈希中恢復簽名者
    address signer1 = recoverFromSignature(txData, sig1);
    address signer2 = recoverFromSignature(txData, sig2);
    
    // 驗證兩位簽名者是否都在此帳戶的 2-of-3 集合中
    Safe safe = Safe(msg.sender);  // msg.sender 是 EXEC_TX 的 from 地址
    require(safe.isOwner(signer1) && safe.isOwner(signer2), "Invalid signers");
    
    return 1;  // 成功
  }
}

意義:合約帳戶首次擁有了原生的基礎層簽名驗證。無需捆綁器。同一個驗證器合約可被 100,000 個 Safe 錢包複用。

示例 2:帶有 ZK 的隱私保護(僅 PRE_VALIDATION)

// 使用 ZK 證明的隱私代幣轉帳

EXEC_TX {
  from: user,
  to: privacy_token_contract,
  data: [commitment_hash, encrypted_transfer_params],
  hook: {
    target: zk_verifier,
    phaseMask: 1,  // 僅 PRE_VALIDATION
    gasLimit: 200_000,
    data: zk_proof  // 公開證明
  }
}

Hook 執行

contract ZKVerifier {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    // 解碼 calldata 以提取 commitment_hash
    (bytes32 commitment, ) = abi.decode(txData, (bytes32, bytes));
    
    // 從 hook.data 解碼證明
    bytes memory proof = hookData;
    
    // 驗證證明(確定性,無狀態讀取)
    require(verifyProof(proof, commitment), "Invalid proof");
    
    return 1;  // 成功
  }
}

後續執行

  • 核心:隱私代幣合約接收加密參數,更新屏蔽池(shielded pool)狀態

  • 用戶在鏈下保留私鑰;可在未來交易提現時解密

運作原理:證明驗證(公開、確定性)與狀態變更(加密)解耦。隱私邏輯在合約中演進,無需更改共識。

示例 3:帶有調度器的多模塊組合(所有階段)

// 2-of-3 多簽 + 金額限制 + 贊助 Gas

EXEC_TX {
  from: safe_multisig_account,
  to: uniswap,
  data: swap_calldata,
  payer: relayer_sponsor,
  hook: {
    target: dispatcher,  // 路由至 AuthModule, PolicyModule, EscrowModule
    phaseMask: 7,  // 所有三個階段
    gasLimit: 500_000,
    data: abi.encode(
      [sig1, sig2],                    // 用於 AuthModule (階段 0)
      {amount: 100e18, recipient: "0x..."},  // 用於 PolicyModule (階段 1)
      relayer_address                  // 用於 EscrowModule (階段 2)
    )
  }
}

Hook 執行

contract Dispatcher {
  AuthModule authModule;
  PolicyModule policyModule;
  EscrowModule escrowModule;

  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    (bytes[] memory sigs, , ) = abi.decode(hookData, (bytes[], bytes32, address));
    return authModule.verify(txData, sigs) ? 1 : 0;
  }

  function preExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (, bytes32 constraints, address relayer) = abi.decode(hookData, (bytes[], bytes32, address));
    
    // 驗證策略
    require(policyModule.validateConstraints(constraints, txData), "Policy violation");
    
    // 鎖定 Gas 贊助的託管資金
    require(escrowModule.lockEscrow(relayer, tx.gasprice * 500_000), "Escrow lock failed");
    
    return 1;
  }

  function postExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (, , address relayer) = abi.decode(hookData, (bytes[], bytes32, address));
    
    // 釋放託管資金(計算實際消耗的 Gas,退還差額)
    uint256 actualGasCost = (initialGas - gasleft()) * tx.gasprice;
    escrowModule.releaseEscrow(relayer, actualGasCost);
    
    return 1;
  }
}

執行時間線

  • PRE_VALIDATION:Dispatcher.preValidation → AuthModule.verify → 2-of-3 簽名檢查(約 8K gas)

  • PRE_EXECUTION:Dispatcher.preExecution → PolicyModule.validate (金額限制) → EscrowModule.lockEscrow(約 10K gas)

  • 核心:Uniswap 交易執行(約 100K gas,取決於市場)

  • POST_EXECUTION(僅在核心成功時):釋放託管,退款給中繼器(約 5K gas)

  • 結算:向中繼器收取實際 Gas;若中繼器資金耗盡則回退至 Safe 帳戶

架構優勢:單個 Hook(調度器)路由至 3 個內部模塊。協議不關心存在多少模塊;調度器負責組合。路由開銷約 1K gas(可忽略不計)。

示例 4:意圖結算 Hook(後量子安全)

// 用戶提交意圖:“以 10 ETH 換取至少 15,000 USDC”
// 意圖解算器返回匹配的交易及證明

EXEC_TX {
  from: user,
  to: uniswap_router,
  data: swap_with_minimum_check,
  hook: {
    target: intent_settlement,
    phaseMask: 1,  // 僅 PRE_VALIDATION
    gasLimit: 150_000,
    data: abi.encode(intent_hash, solver_proof, is_post_quantum_sig)
  }
}

Hook 執行

contract IntentSettlement {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    (bytes32 intent, bytes memory proof, bool usePostQuantum) = 
      abi.decode(hookData, (bytes32, bytes, bool));
    
    Intent memory original = intentRegistry[intent];
    require(block.timestamp <= original.expiry, "Intent expired");
    require(verifySolverProof(proof, original.minOut), "Solver mismatch");
    
    if (usePostQuantum) {
      require(verifyDilithiumProof(proof, intent), "PQ sig failed");
    }
    return 1;
  }
}

意義:意圖鏈現在可以進行透明結算,無需鏈下排序基礎設施。解算器競爭在鏈上進行。後量子帳戶只需更改 Hook 即可升級,無需主網分叉。

示例 5:委託授權(無需重新驗證)

// 被委託的中繼器可以代表委託人提交交易
// 委託證明嵌入在 hook.data 中

EXEC_TX {
  from: delegator,
  to: dapp,
  data: user_intent,
  payer: relayer,
  hook: {
    target: delegation_verifier,
    phaseMask: 1,
    gasLimit: 80_000,
    data: abi.encode(delegation_proof, relayer_address, expiry_time)
  }
}

Hook 驗證

contract DelegationVerifier {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    (bytes memory delegProof, address relayer, uint256 expiry) = 
      abi.decode(hookData, (bytes, address, uint256));
    
    require(block.timestamp <= expiry, "Delegation expired");
    (, address to, , bytes calldata data, ) = decodeTx(txData);
    require(verifyDelegation(delegProof, relayer, to, expiry), "Invalid delegation");
    return 1;
  }
}

高級模式:組合與優化

模式 1:用於重試邏輯的並行通道

使用 2D Nonce 通道解耦重試:

EXEC_TX (通道 0) {
phaseMask: 7,
data: primary_operation
}

EXEC_TX (通道 1) {
phaseMask: 1,
data: fallback_operation // 若通道 0 失敗則執行
}

兩者同時待處理且互不阻塞。主操作成功 → 備用操作取消。

模式 2:通過調度器實現批量原子性

通過基於 Hook 的調度器實現多操作原子性:

contract BatchDispatcher {
  function preExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (Operation[] memory ops) = abi.decode(hookData, (Operation[]));
    require(lockEscrow(sumCosts(ops)), "Escrow failed");
    return 1;
  }
  
  function postExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (Operation[] memory ops) = abi.decode(hookData, (Operation[]));
    for (uint i = 0; i < ops.length; i++) {
      releaseEscrow(ops[i].actualCost);
    }
    return 1;
  }
}

單個核心調用在內部進行調度。要麼全部成功,要麼全部回滾。

模式 3:用於並行工作流的 2D Nonce 通道

為不同交易類型設置不同通道:

通道 0:支付(順序執行)
通道 1:意圖響應(並行執行)
通道 2:治理投票(並行執行)

用戶同時提交:

交易 1:lane=0, seq=100 (支付)
交易 2:lane=2, seq=50 (投票)
交易 3:lane=1, seq=25 (意圖)

所有交易共同待處理。通道 0 的失敗不會阻塞通道 1 或 2。


詳細 Gas 計費示例

考慮示例 3(多模塊)的實際 Gas 成本:

場景:2-of-3 多簽 + 策略限制 + 託管贊助

區塊上下文:
base_fee = 50 gwei
payer_balance = 100 ETH
relayer_credit_limit = 5 ETH

提交的 EXEC_TX:
gasLimit = 500_000
maxFeePerGas = 100 gwei

Gas 拆解(執行順序):

  1. 固有開銷 (Intrinsic):21_000 + calldata_cost

    • Calldata 約 400 字節:~4,800
    • 小計:25_800
  2. isAuthorizedExecHook (固定 5,000)

    • 小計:30_800
  3. 通過調度器執行 PRE_VALIDATION

    • AuthModule.verify (簽名恢復):6,000
    • 小計:36_800
  4. 通過調度器執行 PRE_EXECUTION

    • PolicyModule.validate (金額檢查):3,500
    • EscrowModule.lockEscrow (SSTORE):20_000
    • 小計:60_300
  5. 核心調用 (Uniswap swap)

    • Uniswap 邏輯:95_000
    • 小計:155_300
  6. 通過調度器執行 POST_EXECUTION

    • EscrowModule.releaseEscrow (退款計算 + 轉帳):12_000
    • 小計:167_300
  7. 最終結算

    • gas_used = 167_300
    • cost = 167_300 × 100 gwei = 16.73 ETH

支付者扣費:

  • 支付者為中繼器,現有 100 ETH
  • 扣除:16.73 ETH
  • 剩餘:83.27 ETH
  • 執行後退款:escrowModule 計算實際成本為 16.73 ETH,無需退款

若支付者僅有 10 ETH:

  • PRE_EXECUTION 將失敗 (lockEscrow 失敗)
  • 整筆交易回滾
  • POST_EXECUTION 不執行
  • 交易被拒絕,報錯 "Escrow lock failed"
  • 回退至 from 地址扣費... (但 Safe 多簽帳戶無 ETH)
  • 回退失敗,交易在執行前失敗

驗證模式:常見陷阱與強制執行

驗證模式中禁用的操作碼

嚴格禁用(9 個操作碼):
SLOAD 0x54 (狀態讀取)
SSTORE 0x55 (狀態寫入)
TIMESTAMP 0x42 (block.timestamp)
BLOCKHASH 0x40 (歷史區塊)
GASLIMIT 0x45 (區塊 Gas 限制)
BLOBBASEFEE 0x4a (EIP-4844)
PREVRANDAO 0x44 (隨機性)
COINBASE 0x41 (礦工地址)
BASEFEE 0x48 (基礎費用)

儘管有表面風險但仍允許的操作碼:
BALANCE 0x31 (支付者餘額檢查)
SELFBALANCE 0x19 (支付者餘額檢查)

為什麼允許 BALANCE:

  • 對支付者流動性檢查至關重要
  • TOCTOU 競爭通過 PRE_EXECUTION 的資金鎖定來緩解
  • 如果餘額在 PRE_VALIDATION 和 PRE_EXECUTION 之間發生變化,lockEscrow 會捕捉到

客戶端強制執行

每個 EVM 客戶端必須以相同方式實現驗證模式。實現方式:

  • 使用追蹤器(tracer)檢測禁用的操作碼(SLOAD, SSTORE, TIMESTAMP 等)

  • 檢查 PRE_VALIDATION 返回值(必須為 0 或 1)

  • 若檢測到禁用操作碼則拒絕交易

客戶端測試:使用故意設計的惡意 Hook 對 EXEC_TX 進行模糊測試。所有客戶端必須以相同方式拒絕。這對內存池的一致性至關重要。


安全考量

合約帳戶的 Nonce 唯一性

協議強制執行順序;合約帳戶必須防止重放:

易受攻擊的示例

contract VulnerableHook {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    return isValidSignature(txData, hookData) ? 1 : 0;  // 無 Nonce 檢查!
  }
}

同一筆交易可以執行兩次。正確模式

contract SafeHook {
  mapping(address => mapping(uint64 => bool)) executed;
  
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    (address from, uint64 nonceSeq, ) = decodeTx(txData);
    require(!executed[from][nonceSeq], "Nonce already used");
    require(isValidSignature(txData, hookData), "Bad signature");
    return 1;
  }
  
  function preExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (address from, uint64 nonceSeq, ) = decodeTx(txData);
    executed[from][nonceSeq] = true;  // 在核心調用前標記,與其原子化
    return 1;
  }
}

在 PRE_EXECUTION 而非 POST 中標記:確保如果核心調用失敗,整筆交易回滾,Nonce 標誌也會回退。

支付者回退風險

如果支付者在 PRE_VALIDATION 和執行之間資金被抽乾,lockEscrow 將失敗且整筆交易回滾。如果回退地址(from)也沒有餘額,交易將失敗。

緩解措施

  • 錢包 UI:突出顯示支付者風險

  • Hook 預檢查:在提交前驗證支付者餘額

  • 託管合約:使用長期託管而非臨時的 EOA 支付者


集成指南:從 EIP-4337 遷移

對於目前使用 EIP-4337 UserOp 的團隊:

UserOp (4337)EXEC_TX
senderfrom
noncenonceKey + nonceSeq
initCode無對應項
callDatato + data
callGasLimithook.gasLimit
verificationGasLimitN/A (協議處理)
preVerificationGasintrinsic
maxFeePerGasmaxFeePerGas
maxPriorityFeePerGasN/A
paymasterpayer
paymasterDatapayerData
signaturehook.data (取決於驗證方案)
factoryN/A (無初始化)

遷移的好處:
✅ 原生基礎層支持(無需捆綁器)
✅ 無需獨立的 UserOp 內存池(使用交易內存池)
✅ 2D Nonce 並行化(4337 是線性的)
✅ 原子化贊助(無需單獨的 Paymaster 調用)
✅ 執行後 Hook(託管結算,4337 無法做到)

遷移時間線

  • 將您的驗證器合約 Hook 化(將 onValidate 適配為 preValidation)

  • 更新錢包:使用 EXEC_TX 編碼而非 UserOp 編碼

  • 更新捆綁器/中繼器:提交至普通交易內存池,而非 UserOp 內存池

  • 廢棄 Paymaster 合約(託管現在是 Hook 的責任)


性能與內存池效率

Gas 開銷(與傳統交易相比):

  • 協議增加:5,000 gas (isAuthorizedExecHook 調用,固定)

  • 典型應用 Hook:50–200K gas (驗證、策略、託管)

  • 總計:對於高價值交易,開銷約為 0.1–0.5%

內存池優化(通過驗證模式的確定性):

  • 緩存 PRE_VALIDATION 結果(以 hook.target + hook.data 為鍵)

  • 費用估算:無需執行 Hook 即可準確估算

  • 並行驗證:獨立的 CPU 通道(無共享狀態)

  • 優先級:按 maxFeePerGas 排序(標準內存池邏輯)


未來方向

主網上線後(無需共識變更):

  • Hook 註冊表:用於錢包 UX 的鏈下元數據映射

  • 標準化模塊:AuthModule, EscrowModule, PolicyModule 共享庫

  • Hook Gas 分析:用於準確預測成本的工具

  • 2D Nonce UX 標準:錢包如何向用戶展示通道

潛在的協議 v2(未來 EIP):

  • 用於鏈下協調的 Hook 元數據字段

  • 條件執行:“僅當 state[addr] == value 時執行”

  • 帶有順序保證的多 Hook 支持


Hook 設計如何解決問題

從“為每個功能升級”到“部署一個 Hook”

舊模式 (EIP-4337, 捆綁器中繼器)

  • 需要新功能 → 新 EIP 提案 → 客戶端共識 → 主網升級 → 錢包集成 → Dapp 集成

  • 每個功能耗時數月至數年

  • 每個功能都會導致基礎設施碎片化

新模式 (EXEC_TX + Hooks)

  • 需要新功能 → 編寫 Hook 合約 → 部署 → Dapp 集成

  • 每個功能耗時數週

  • 單一統一的基礎設施 (EXEC_TX)

未來 Hook 示例(無需更改協議)

  • 風險管理:風險 Hook 根據用戶的風險概況驗證交易

  • 意圖解算:意圖 Hook 驗證鏈上狀態是否符合用戶意圖

  • 隱私:隱私 Hook 根據承諾驗證證明

  • 量子安全驗證:Hook 實現後量子簽名驗證

  • 社交恢復:Hook 實現社交恢復機制

  • 委託:Hook 驗證委託授權證明

  • 批量操作:Hook 驗證批量結構和依賴關係

  • MEV 抗性:Hook 實現加密 calldata + 揭示機制

所有這些都可以在不更改任何協議的情況下實現,因為 Hook 接口是通用的。


Hook 模型的核心特性

使其運作的特性

  • 確定性 PRE_VALIDATION:所有節點對有效性達成一致。無內存池分區風險。

  • 原子化階段:PRE_EXECUTION 和 POST_EXECUTION 與核心原子化。託管不變量得以維持。

  • 授權委託:合約定義有效性 (isAuthorizedExecHook)。協議強制執行結構。

  • 應用層快速迭代:新 Hook = 無共識變更。如果需要,可以每週部署和測試。

  • 通過調度器組合:單個 Hook 支持 100 多個內部模塊。該模式已在 EIP-4337 規模下得到驗證。

權衡

權衡點選擇理由
協議 vs 應用協議:Nonce/階段/Gas強制執行不變量。應用程序執行策略。
單一 vs 多個 Hook單一 Hook最小化共識規則。調度器模式已驗證。
Nonce 唯一性EOA:強制,合約:委託默認安全。為應用程序提供靈活性。
驗證嚴格性嚴格(禁用 9 個操作碼)內存池一致性。允許 BALANCE 用於支付者檢查。
支付者回退資金耗盡時回退原子性。緩解措施:錢包 UI 警告。

與替代方案的比較

我們並非要取代 EIP-4337、EIP-8130 或 EIP-7702。不同的工具,不同的用途。

  • EIP-4337(捆綁器、UserOp):鏈下基礎設施。將共存。EXEC_TX 是基礎層原生的。

  • EIP-8130(帳戶配置):規範性強(多所有者、範圍)。EXEC_TX 是最小外殼;由合約實現策略。

  • EIP-7702(EOA 委託):為每個交易的 EOA 添加代碼。EXEC_TX 針對合約帳戶,提供原生協議支持。

核心價值:EXEC_TX 讓未來的創新不再是協議層的問題。驗證、隱私、委託、MEV 保護、量子安全——這一切都變成了“另一個 Hook”。多年內無需更改共識。


社區開放問題

  • Hook 接口:標準 ABI 還是應用程序特定?

  • 驗證模式:客戶端分歧風險(geth, erigon, reth 的一致性)?

  • 階段上下文:Hook 應如何檢測當前階段?

  • 支付者回退:靜默處理 + 警告,還是嚴格失敗?

  • Nonce 安全:Hook 委託是否足夠?

  • Revert 數據:是否捕獲用於錢包錯誤消息?

  • 帶有可選階段的 MIN_CORE_GAS?

  • 2D Nonce UX:如何向用戶展示通道?

安全性:需要形式化驗證:驗證模式強制執行、Nonce 轉換、階段原子性、Gas 計量。


實現清單

EVM 客戶端

  • 解碼類型 0x08 交易外殼 (EIP-2718)

  • 實現驗證模式操作碼追蹤器

  • 通過各階段路由執行 (PRE_VALIDATION → PRE_EXECUTION → Core → POST_EXECUTION)

  • 管理 2D Nonce 狀態:next[from][nonceKey]

  • 計量並結算 Gas(實際使用量 + 固有開銷)

錢包

  • EXEC_TX 編碼和 EIP-191 簽名

  • 每個通道的 2D Nonce 追蹤

  • Hook 規範和 Gas 估算

  • 每個階段的錯誤處理

部署順序

  • 部署參考 Hook 實現(多簽、託管、策略)

  • 測試網升級 + 跨客戶端的驗證模式模糊測試

  • 主網軟分叉:支持類型 0x08(非共識)

  • 主網硬分叉:激活 EXEC_TX 規則

  • 6–12 個月內完成錢包集成(移動端、桌面端、硬件錢包)


更新日誌

外部審查

截至 2026-04-08 尚無。

待解決問題

  • Hook 合約 ABI 和接口標準化

  • 跨 EVM 客戶端的驗證模式實現一致性

  • Hook 的階段上下文傳遞機制

  • 支付者回退語義和 UX 緩解

  • 合約帳戶 Nonce 安全保證

  • Hook Revert 數據捕獲和錯誤消息

  • 可選階段的 Gas 計量

  • 2D Nonce 錢包 UX 設計

  • 調度器模式數據編碼標準化



為什麼是現在?

帳戶抽象已碎片化為捆綁器 (4337)、批量系統 (8130)、單筆交易委託 (7702)。EXEC_TX 將它們統一起來:一個協議外殼,可插拔的 Hook,由合約掌控安全性。


作者:Louis Liu (@louisliu2048) 及貢獻者

相關項目:EIP-155, EIP-1559, EIP-1153, EIP-2718, EIP-2929, EIP-4844, EIP-6780

        1 帖子 - 1 參與者

        [閱讀完整話題](https://ethereum-magicians.org/t/extensible-execution-transaction-for-account-capabilities/28168)
https://ethereum-magicians.org/t/extensible-execution-transaction-for-account-capabilities/28168