はじめに

初めまして。 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)を子コントラクトにそのまま伝えることができる点が重要です。 これにより、特定の操作や機能を子コントラクトに「委任」することが容易になります。

js
~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を実行するプロセスが含まれています。

js
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などからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!