はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、他のコントラクトを呼び出す方法の1つであるDELEGATECALL
の仕組みを提案しているEIP7についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
DELEGATECALL
については以下の記事を参考にしてください。
概要
- Block >= 1,150,000 on Mainnet
- Block >= 494,000 on Morden
- Block >= 0 on future testnets
各チェーンで、このアップデートが有効になるタイミングがまとめられています。
メインネットでは1,150,000
ブロック目以降、Mordenでは494,000
ブロック目以降にこのアップデートが適用されます。
また、未来のテストネットにおいては、ブロック番号が0
からこのアップデートが有効になることが示されています。
このテキストは、ブロックチェーンのスマートコントラクトにおける新しい命令(オペコード)「DELEGATECALL
」について説明しています。
このオペコードは、0xf4
という特定のコードに配置されます。
DELEGATECALL
は、既存のオペコードである「CALLCODE
」と似た機能を持ちますが、大きなな違いがあります。
CALLCODE
は、スマートコントラクトから別のコントラクトのコードを実行する時に使われますが、送信者とvalue
は新しいコールで新たに設定されます。
一方、DELEGATECALL
は、親スコープ(元のコールを行ったコントラクト)の送信者とvalue
を子スコープ(新しく呼び出されるコントラクト)にそのまま伝播させます。
これにより、新しく作成されるコールは、元のコールと同じ送信者(コントラクトのアドレス)と送信されるvalue
(たとえば、送金される通貨の量)を保持します。
この変更は、スマートコントラクトが他のコントラクトを呼び出す時の挙動に影響を与え、より柔軟な設計が可能になります。
特に、異なるコントラクト間での情報や価値の伝達を、より直接的かつ透明に行うことができるようになります。
CALLCODE
コントラクトAがコントラクトBの関数をCALLCODE
を使って呼び出す場合、コントラクトBのコードはコントラクトAのストレージ(データ)を使って実行されます。
しかし、この際の「送信者」(msg.sender
)はコントラクトAのアドレスですが、コントラクトBは自分自身が直接呼び出されたわけではないため、コントラクトBのアドレスは使われません。
例えば、コントラクトAが5 ETH
をコントラクトBに送る場合、実際にはコントラクトAから5 ETH
が引き出され、コントラクトBのバランスは変わりません。
DELEGATECALL
コントラクトAがコントラクトBの関数をDELEGATECALL
を使って呼び出す場合、こちらもコントラクトBのコードはコントラクトAのストレージを使って実行されます。
しかし、この場合「送信者」(msg.sender
)と「value
」は親スコープ(コントラクトA)から子スコープ(コントラクトB)にそのまま伝播されます。
「送信者」(msg.sender
)は、呼び出し元のEOAアドレスになります。
つまり、コントラクトBの関数を実行しているのはあたかもコントラクトA自身のように見えます。
同じく、コントラクトAが5 ETH
をコントラクトBに送る場合、DELEGATECALL
を使うと、コントラクトBが直接5 ETH
を受け取ることになり、コントラクトBのバランスが増えます。
要するに、CALLCODEは親コントラクトのコンテキストでコードを実行しますが、送信者は変わりません。一方、DELEGATECALLは親コントラクトのコンテキストでコードを実行しつつ、送信者と価値も親コントラクトから引き継ぎます。これにより、DELEGATECALLはより柔軟なインタラクションと、コントラクト間のより緊密な統合を可能にします。
仕様
DELEGATECALL
命令は、スマートコントラクト内で別のコントラクトを呼び出す際に使用される命令です。
この命令は、以下の6つのパラメーターを必要とします。
-
gas
- 実行時に消費できる最大ガス量。
- ガスは、ブロックチェーン上の操作にかかるコストを表します。
-
to
- 実行するコードがある宛先アドレス。
- これは通常、他のスマートコントラクトのアドレスです。
-
in_offset
- 入力データのメモリ内開始位置を指定します。
-
in_size
- 入力データのサイズ(バイト単位)。
-
out_offset
- 出力データのメモリ内開始位置。
-
out_size
- 出力データのためのメモリ領域のサイズ。
ガスに関する重要な点は、基本的なガス支給はなく、呼び出された側が受け取るガスの量が全額となることです。
また、CALLCODE
と同じように、新しいアカウントの作成は発生せず、ガスの前払いコストは常に一定です。
未使用のガスは通常どおり返金されます。
送信者(CALLER
)と送信される値(VALUE
)に関しては、呼び出された環境と呼び出し元の環境で完全に同じように動作します。
これは、DELEGATECALL
が呼び出し元の環境を保持するため重要です。
最後に、ブロックチェーンの深さ制限(1024
)はDELEGATECALL
を使用しても変わらず適用されます。
これは、スマートコントラクトがあまりに深い呼び出しを行わないようにするための措置です。
補足
DELEGATECALL
を使用することで、親コントラクトの送信者(msg.sender
)と送信値(msg.value
)を子コントラクトにそのまま伝えることができる点が重要です。
これにより、特定の操作や機能を子コントラクトに「委任」することが容易になります。
~calldatacopy(0, 0, ~calldatasize())
if ~calldataload(0) < 2**253:
~delegate_call(msg.gas - 10000, $ADDR1, 0, ~calldatasize(), ~calldatasize(), 10000)
~return(~calldatasize(), 10000)
elif ~calldataload(0) < 2**253 * 2:
~delegate_call(msg.gas - 10000, $ADDR2, 0, ~calldatasize(), ~calldatasize(), 10000)
~return(~calldatasize(), 10000)
...
上記では、コントラクトが入力データに基づいて異なるアドレスにDELEGATECALL
を行う方法を示しています。
これにより、1つのコントラクトが複数の異なるタスクを処理できるようになり、ガスの使用量を分散し、3M
ガスの制限を回避できます。
入力データの最初の部分を確認し、特定の範囲内で条件分岐を行い、対応するアドレスにDELEGATECALL
を実行するプロセスが含まれています。
if ~calldataload(0) / 2**224 == 0x12345678 and self.owner == msg.sender:
self.delegate = ~calldataload(4)
else:
~delegate_call(msg.gas - 10000, self.delegate, 0, ~calldatasize(), ~calldatasize(), 10000)
~return(~calldatasize(), 10000)
上記では、コントラクトがその「delegate
」アドレス(コードを実行するために使用されるアドレス)を動的に変更することができます。
これにより、コントラクトはその挙動を更新し、異なる機能を持つコードを実行することが可能になります。
また、この例では、特定の条件(例えば、特定の関数が呼び出され、適切な送信者がいる場合)が満たされると、デリゲートアドレスが更新されます。
これらの例から、DELEGATECALL
がコントラクトの柔軟性と機能の範囲を大幅に拡張することがわかります。
特に、親コントラクトのコンテキスト(送信者と送信値)を保持することで、より複雑な相互作用と分散化されたアーキテクチャを実現できます。
反論の可能性
送信者の情報をコールデータの最初の20
バイトに直接含める方法でDELEGATECALL
の機能を模倣することが可能である点に反論される可能性があります。
しかし、この方法には大きな問題があります。
送信者の情報をコールデータに組み込むためには、スマートコントラクトのコードを特別にコンパイルする必要があります。
これは、コントラクトが委任された環境(他のコントラクトからのDELEGATECALL
を介して実行される場合)と、生の環境(直接実行される場合)の両方で同じコードを使用することができないことを意味します。
この代替手段を使う場合、同じコントラクトが異なる実行コンテキストで異なるバージョンのコードを必要とする可能性があります。
これは、開発の複雑さを増加させるだけでなく、コントラクトの柔軟性と再利用性を損なうことになります。
一方、DELEGATECALL
を使用すると、コントラクトは特別な変更を加えることなく、送信者と値を維持しながら他のコントラクトのコードを実行できるため、より効率的で柔軟な解決策となります。
引用
Vitalik Buterin (@vbuterin), "EIP-7: DELEGATECALL," Ethereum Improvement Proposals, no. 7, November 2015. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7.
最後に
今回は「他のコントラクトを呼び出す方法の1つであるDELEGATECALL
の仕組みを提案しているEIP7」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!