
トークンのスワップ、流動性の追加、報酬のステーキングをまとめて行う、複雑なオールインワンのアトミックトランザクションを実行しようとしているとしましょう。ところが、普通の Ethereum ウォレットである EOA では、バッチトランザクションができません。そうなると、3 つの別々のトランザクションに署名して、ガス代を 3 回払う必要があります。これはありえませんよね。しかも、ステップの合間に MEV ボットにサンドイッチされないよう祈るしかありません。
これは最近の EVM エコシステムで白熱している議論の 1 つです。普通のウォレットに、すべてを一度に壊すことなく、いったいどうやってスーパーパワーを与えればいいのでしょうか?
この課題から 2 つの EIPs が生まれました。EIP-3074 と、その後に続いた EIP-7702 です。どちらもシンプルな EOA と smart contract accounts のあいだのギャップを埋めようとするものですが、権限の委任に対するアプローチが異なります。一方は永続的な権限を与えるもので、事態がひどく悪化してウォレットが空にされる可能性があります。もう一方は、永続的な委任と一時的な委任の両方を提供でき、一時的な委任ならトランザクションがオンチェーンで実行された瞬間に消えてなくなることもあります。
この違いがなぜ重要なのか、そして Vitalik の提案が、潜在的なセキュリティ上の悪夢から Ethereum をどう救ったのかもしれないのかを話していきましょう。
委任の話に入る前に、これらすべてを可能にしている重要なインフラがあります。それが EIP-2718 です。トランザクションのための、Ethereum の輸送用コンテナシステムのようなものだと考えてください。
// before eip-2718 all transactions looked the same
const legacyTx = rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]);
// after eip-2718 transactions can now be typed
const typedTx = TransactionType || TransactionPayload;EIP-2718 は Typed Transaction Envelopes を導入しました。TransactionType || TransactionPayload が有効なトランザクションになります。TransactionType はトランザクションの形式を識別し、Payload はトランザクションの中身を持ちます。このラッパーの仕組みのおかげで、既存の機能を壊すことなく、まったく新しいトランザクションの振る舞いを導入できるようになりました。
エンベロープ型のトランザクションタイプを導入することで、既存のトランザクションとの後方互換性さえ確保すればよくなり、それ以降は TransactionTypes のあいだで番号が衝突しないようにするという、はるかに単純な問題を解くだけで済みます。だからこそ、EIP-7702 が一時的な委任の仕組みを導入する必要があったときも、例えば 0x04 のような新しいトランザクションタイプを定義するだけでよく、EVM そのものには何の変更も必要ありませんでした。
EIP-3074 は、委任に対して大胆なアプローチを取りました。AUTH と AUTHCALL という 2 つの新しい EVM opcodes を使って、EOAs がスマートコントラクトに権限を永続的に委任できるようにしたのです。
仕組みはシンプルです
// user signs an auth message
bytes32 commit = keccak256(abi.encode(invokerAddress, batchActions));
bytes memory signature = signMessage(userPrivateKey, commit);
// invoker contract calls AUTH opcode
AUTH(commit, signature.v, signature.r, signature.s);
// This sets the 'authorized' context variable to user's address
// now invoker can make calls as the user
AUTHCALL(gasLimit, targetContract, 0, 0, calldata, calldataLength, 0, 0);AUTH は commit と、署名の yParity、r、s という 4 つのパラメータを受け取ります。署名が有効で、署名者のアドレスが authority と一致すれば、コンテキスト変数 authorized にその authority がセットされます。そのうえで AUTHCALL によって、承認された invoker コントラクトがトランザクションを送信できるようになり、そのとき CALLER には invoker ではなく承認されたアドレスがセットされます。
では、バッチトランザクションを処理できる invoker コントラクトを想像してみてください
contract BatchInvoker {
struct BatchCall {
address target;
bytes data;
uint256 value;
}
function executeBatch(
BatchCall[] calldata calls,
bytes32 commit,
uint8 v, bytes32 r, bytes32 s
) external {
// authorize the user
AUTH(commit, v, r, s);
// Execute all calls as the user
for (uint i = 0; i < calls.length; i++) {
AUTHCALL(
gasleft(),
calls[i].target,
calls[i].value,
0, // valueExt (must be 0 for now)
// ... calldata parameters
);
}
}
}ワクワクする話ではありますが、この 2 つの opcodes は EVM コミュニティにいくつものセキュリティ上の懸念をもたらしました。コンテキスト変数 authorized は、コントラクトの 1 つの実行フレーム全体を通して保持されますが、DELEGATECALL を含むどの呼び出しにも引き継がれません。これは基本的に、承認がトランザクションのスコープ全体で有効だということです。
本当の問題は、トランザクション内での一時的な性質ではありません。広く使われる invoker コントラクトがあるときに何が起きるか、です。いずれ誰もがスマートアカウントを使うようになるのなら、AUTH と AUTHCALL の opcodes は大きな技術的負担になり、EVM そのものを複雑にしかねません。
上のような人気の invoker コントラクトにバグがあったらどうなるでしょうか? そのコントラクトを一度でも承認したことのあるすべてのユーザーが、突然リスクにさらされるかもしれません。悪意のあるアップデートが 1 つ、見落とされたエッジケースが 1 つ、整数オーバーフローが 1 つあれば、それで終わりです。大規模な資金流出が始まります。
EVM エコシステムは、これが奇妙な力学を生むことに気づきました。EIP-3074 のセキュリティモデルは、invoker コントラクトに大きく依存しています。複数のユーザーが同じ invoker のロジックに頼る可能性があるため、1 つの invoker の脆弱性やエクスプロイトによって、多くのユーザーの資産や権限が一度に危険にさらされかねません。これが、EIP-3074 が取り下げられた理由です。
さて、2024 年 5 月に Vitalik が提案した EIP-7702 は、2025 年 5 月に pectra upgrade によって mainnet に導入されました。これは新しく洗練されたアプローチを導入するもので、EOAs が 1 つのトランザクションの間だけ一時的にスマートコントラクトのように機能できるようにします。これは大きな出来事です!
新しい opcodes の代わりに、EIP-7702 は EIP-2718's のトランザクションエンベロープの仕組みを活用して、新しいトランザクションタイプを導入します
interface EIP7702Transaction {
chainId: number;
nonce: number;
maxPriorityFeePerGas: bigint;
maxFeePerGas: bigint;
gasLimit: bigint;
to: string;
value: bigint;
data: Uint8Array;
accessList: AccessList;
authorizationList: Authorization[];
}
interface Authorization {
chainId: number;
address: string; // the address of the contract code to delegate to
nonce: number;
yParity: 0 | 1;
r: Uint8Array;
s: Uint8Array;
}各 authorization オブジェクトについて、委任インジケーター (0xef0100 || address) が、承認したアカウントのコードに書き込まれます。コードを実行するすべての操作は、その委任が指すコードを読み込んで実行しなければなりません。
ここで多くの人が混乱します。EIP-7702 は最初のドラフトから進化してきました。当初、この提案は EOAs をトランザクション 1 つの間だけ一時的にスマートにするように設計されていました。しかし最終版では、委任がどれくらい続くかを自分でコントロールできるようになっています。
function processEIP7702Transaction(tx) {
// verify all auth signatures
for (const auth of tx.authorizationList) {
const signer = recoverSigner(auth);
if (signer !== getAddressFromAuth(auth)) {
throw new Error("Invalid authorization");
}
}
// set delegation pointers (these persist after transaction)
for (const auth of tx.authorizationList) {
const eoaAddress = getAddressFromAuth(auth);
// set delegation pointer - 0xef0100 || contract_address
const delegationPointer = concat(['0xef0100', auth.address]);
setCode(eoaAddress, delegationPointer);
// nowm this delegation PERSISTS unless explicitly cleared
}
// execute the transaction normally
executeTransaction(tx);
// no auto cleanup, delegation stays active
}注意: 7702 トランザクションの実行が失敗しても、処理済みの委任インジケーターはロールバックされません。後続の 7702 トランザクションで委任先アドレスを 0x0 に設定して明示的にクリアしない限り、そのまま残ります。
つまり、委任のパターンは 3 つあります:
Batching Contract に委任するような場合ですが、委任そのものは、ユーザーがクリアするまで残り続けます。EIP-7702 の良いところは、選択肢を与えてくれることです。1 回限りの複雑なトランザクションのために一時的に委任したいですか? それもできます。EOA を永続的にアップグレードして、スマートコントラクトウォレットのように動かしたいですか? それもできます。
一時的な委任の例はこちらです
// one off delegation
const tempDelegationTx = {
type: 0x04,
authorizationList: [{
address: batchExecutorContract,
// ... auth details
}],
to: userAddress,
data: encodeBatchCall([
// execute your complex operation
swapTokens(),
addLiquidity(),
stakeRewards(),
])
};そして、永続的な委任の例はこちらです
// upgrade EOA to smart wallet
const persistentDelegationTx = {
type: 0x04,
authorizationList: [{
address: smartWalletImplementation,
// ... auth details
}],
to: userAddress,
data: encodeCall("initializeSmartFeatures", [])
// this delegation persists for future txns
};
// later txns can now use smart wallet features
const futureTransaction = {
to: userAddress, // your EOA will now execute smart wallet code
data: encodeCall("batchExecute", [multipleOperations])
// no new auth needed
};EIP-3074 と EIP-7702 の重要な違いは、単に一時的か永続的かということではありません。リスクの分散とコントロールの問題なのです。EIP-3074 は共有された単一障害点を作り出し、人気のあるコントラクト 1 つの問題が何千人ものユーザーに影響しかねません。EIP-7702 は各トランザクションを分離するので、バグが影響するのはその特定のトランザクションだけです。EIP-7702 は既存の ERC-4337 と完全に互換性があり、新しい opcodes も必要ありません。つまり、ウォレットは既存のシステムを何も壊すことなく、段階的にこれを採用できるということです。
重要なのは、委任を管理するのは dApps ではなく wallets だという点です。そのため、委任の管理はウォレットにだけ許可するというのが一般的な合意になっています。これはつまり:
EIP-7702 をサポートするアプリケーションを作っているなら、技術的に注意すべき点がいくつかあります
// old: simple EOA transaction
const tx = {
to: contractAddress,
data: encodedCall,
// ... standard fields
};
// new way with eip-7702 delegation
const tx = {
type: 0x04,
to: userAddress, // calling user's own address
data: encodeBatchCall([
contract.interface.encodeFunctionData("swap", [tokenA, tokenB, amount]),
contract.interface.encodeFunctionData("addLiquidity", [lpTokens]),
contract.interface.encodeFunctionData("stake", [lpAmount])
]),
authorizationList: [{
address: batchExecutorImplementation,
// ... authorization signature
}],
// ... standard fields
};0x0 に戻すために、もう 1 つ 7702 トランザクションを送る必要があります。つまり、ガスの見積もりでは次の点を考慮しなければなりませんEIP-7702 によって、EOAs で EIP-1271 の署名検証が使えるようになります。つまり、アップグレードされた EOA は、後方互換性を保ちながら、passkeys のような別の方法でメッセージに署名できるのです。// your EOA can now implement smart signature verification
function isValidSignature(bytes32 hash, bytes signature) external view returns (bytes4) {
// can verify passkey signatures, multisig, etc.
if (verifyPasskeySignature(hash, signature)) {
return 0x1626ba7e; // EIP-1271 magic value
}
return 0xffffffff;
}最新の記事をメールでお届けします。