---
title: "Yulのコントラクト呼び出しの仕組み"
description: "Yulスマートコントラクトプログラミング言語におけるコントラクト呼び出しの仕組みを解説"
---

# Yulのコントラクト呼び出しの仕組み

[Yul](http://www.alchemy.com/overviews/what-is-yul)は、[スマートコントラクト](https://www.alchemy.com/overviews/solidity-smart-contract)内でアセンブリ言語の一種を記述するために使用できる中間プログラミング言語です。構文、ストレージ、Yulにおけるメモリの仕組みを理解したところで、次はコントラクトの呼び出し方を見ていきましょう。

## **Yulにおけるコントラクト呼び出しの仕組み**

このシリーズの最終セクションでは、Yulにおけるコントラクト呼び出しの仕組みを見ていきます。いくつかの例に入る前に、いくつかのYulオペレーションについて学ぶ必要があります。見ていきましょう。‍

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Instruction", dataType: "object" },
      { key: "2", width: 200, title: "Explanation", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>gas()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>実行に使用可能な残りのガス量</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>gasPrice()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>トランザクションのガス価格</p>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>address()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>現在のコントラクトのアドレス</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>balance(a)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>アドレスaが保有するether残高（wei単位）</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>selfbalance()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>balance(address())を呼び出すのと同じだが、ややコストが低い</p>", tooltip: "", icon: "" },
        id: 4,
      },
      {
        "1": { title: "<p>caller()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>コントラクトを呼び出したアドレス（msg.sender）</p>", tooltip: "", icon: "" },
        id: 5,
      },
      {
        "1": { title: "<p>origin()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>トランザクションを発行したアドレス（tx.origin）</p>", tooltip: "", icon: "" },
        id: 6,
      },
      {
        "1": { title: "<p>callvalue()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>コントラクト呼び出しで送金されたether量（wei単位、msg.value）</p>", tooltip: "", icon: "" },
        id: 7,
      },
      {
        "1": { title: "<p>calldataload(p)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>位置pから始まるcalldata（32バイト分のみ）</p>", tooltip: "", icon: "" },
        id: 8,
      },
      {
        "1": { title: "<p>calldatacopy(t, f, s)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>calldataをメモリ位置tにコピーする。calldata内の位置fから開始し、sバイト分のデータをコピーする。</p>", tooltip: "", icon: "" },
        id: 9,
      },
      {
        "1": { title: "<p>extcodesize(a)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>アドレスaにあるコードのサイズ</p>", tooltip: "", icon: "" },
        id: 10,
      },
      {
        "1": { title: "<p>returndatasize()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>直近のreturndataのサイズ</p>", tooltip: "", icon: "" },
        id: 11,
      },
      {
        "1": { title: "<p>returndatacopy(t, f, s)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>return data内の位置fからsバイトをメモリの位置tにコピーする</p>", tooltip: "", icon: "" },
        id: 12,
      },
      {
        "1": { title: "<p>timestamp()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>現在のブロックのタイムスタンプ（秒単位）</p>", tooltip: "", icon: "" },
        id: 13,
      },
      {
        "1": { title: "<p>number()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>現在のブロック番号</p>", tooltip: "", icon: "" },
        id: 14,
      },
      {
        "1": { title: "<p>call(g, a, v, in, insize, out, outsize)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>アドレスaにあるコントラクトを、ガスgで呼び出し、v weiをmsg.valueとして渡し、メモリ位置in〜insizeからtx.dataを渡し、返り値をメモリ位置out〜outsizeに格納する。呼び出しが成功した場合は1を、そうでない場合は0を返す。</p>", tooltip: "", icon: "" },
        id: 15,
      },
      {
        "1": { title: "<p>delegatecall(g, a, in, insize, out, outsize)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>call()と似ているが、主な違いはdelegatecall()が呼び出し元コントラクトの状態変数を更新する点である。プロキシコントラクトでよく使用される。意図しないストレージ変数の上書きを避けるため、呼び出し先コントラクトとストレージレイアウトが一致している必要があることに注意。パラメータvが存在しない点にも注意。delegatecall()ではetherを送金できない。</p>", tooltip: "", icon: "" },
        id: 16,
      },
      {
        "1": { title: "<p>staticcall(g, a, in, insize, out, outsize)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>call()と似ているが、ブロックチェーンの状態を変更するコントラクト（つまりpureおよびview以外の関数）を呼び出すことはできない。パラメータvが存在しない点に注意。staticcall()ではetherを送金できない。</p>", tooltip: "", icon: "" },
        id: 17,
      },
    ],
  }}
/>

では、これらの例で使用する新しいコントラクトを見ていきましょう。まず、これから呼び出す対象のコントラクトを見てみます。

このコントラクトには、それぞれストレージスロット1と2に格納されているvar1とvar2という2つのストレージ変数があります。

関数a\(\)は、ユーザーがコントラクトに少なくとも1 ether送金することを要求しており、そうでない場合はrevertします。

続いて、関数a\(\)はvar1とvar2を更新し、それらを返します。

関数b\(\)は単にvar1とvar2を読み取り、それらを返します。

コントラクトCallMeを呼び出す私たちのコントラクトに進む前に、少し時間を取ってfunction selector（関数セレクタ）を理解しておく必要があります。

トランザクション`0x773d45e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002`の以下のcalldataを見てみましょう。

calldataの最初の4バイトは、function selector（0x773d45e0）と呼ばれるものです。これはEVMがどの関数を呼び出したいのかを知る方法です。function selectorは、関数シグネチャの文字列のハッシュの最初の4バイトを取得することで導出されます。

したがって、関数a\(\)のシグネチャはa\(uint256,uint256\)になります。

この文字列のハッシュを取ると、次のようになります: `0x773d45e097aa76a22159880d254a5f1db8365bc2d0f0987a82bda7dfd3b9c8aa`。

最初の4バイトを見ると、0x773d45e0であることがわかります。

シグネチャにスペースがないことに注目してください。これは重要な点で、スペースを追加すると全く異なるハッシュになってしまいます。今回のコード例ではselectorを取得する心配はありません。私が提供します。

ストレージレイアウトから見ていきましょう。

var1とvar2がコントラクトCallMeと同じレイアウトになっていることに注目してください。delegatecall\(\)が正しく機能するためには、レイアウトが他方のコントラクトと同じである必要があると述べたのを覚えているかもしれません。

新しい変数（selectorAとselectorB）を末尾に追加する限り、この条件を満たしつつ他の変数を持つことができます。これにより、ストレージの衝突を防ぐことができます。

これで最初のコントラクト呼び出しを行う準備が整いました。

### **Yulでstaticcall\(\)を使う方法**

まずは簡単なもの、staticcall\(\)から始めましょう。以下が私たちの関数です:

**行われていることは以下の通りです:**

- スロット2をロードして、ストレージからb\(\)のfunction selectorを取得する（両方のselectorが1つのスロットにパックされている）
- selectorBを取り出すために4バイト（32ビット）右にシフトする
- function selectorをメモリのscratch spaceに格納する
- staticcallを実行する
- gas\(\)を渡す（ガス量を指定することも可能）
- コントラクトアドレスとしてパラメータ\_callMeを渡す
- 関数呼び出しが成功したか確認し、失敗した場合はデータなしでreturnする
- メモリからデータをreturnし、値1と2を確認する

**注:** 0x1cと0x20は、格納した値の最後の4バイトをscratch spaceに渡したいことを表しています。staticcall\(\)の最後の2つのパラメータは、返り値をメモリ位置0x80〜0xcoに格納したいことを指定しています。

### **Yulでcall\(\)を使う方法**

次にcall\(\)を見ていきましょう。CallMeの関数a\(\)を呼び出します。コントラクトに少なくとも1 ether送金することを忘れないでください！この例では、\_var1と\_var2として3と4を渡します。

**コードは以下の通りです:**

前回の例と同様、slot2をロードする必要があります。ただし今回は、selectorAを取り出すためにselectorBをマスクします。

次に、selectorを0x80に格納します。

calldataからパラメータが必要なため、calldatacopy\(\)を使用します。calldatacopy\(\)に対して以下を指示します:

- calldataをメモリ位置0xa0に格納する
- 最初の36バイトをスキップする
- calldataのサイズから36バイトを引いたサイズを格納する

**注:** スキップする最初の4バイトはcallA\(\)のfunction selectorで、続く32バイトはcallMeのアドレスです。

これでコントラクト呼び出しを行う準備が整いました！

前回と同様、gas\(\)と\_callMeを渡します。ただし今回は、0x9c（0x80メモリ系列の最後の4バイト）〜0xe0のcalldataを渡し、データをメモリ位置0x100〜0x120に格納します。

再び、呼び出しが成功したか確認し、出力をreturnします。コントラクトCallMeを確認すると、値が正常に3と4に更新されていることがわかります。

何が起きているかをより明確にするため、returnする直前のメモリレイアウトを以下に示します:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Memory Location", dataType: "object" },
      { key: "2", width: 200, title: "Value Stored", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>0x00</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Scratch Space (空)</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>0x20</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Scratch Space (空)</p>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>0x40</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x80（Yulでは全ての操作を手動で行うため、Free Memory Pointerを更新していません）</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>0x60</p>", tooltip: "", icon: "" },
        "2": { title: "<p>空</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>0x80</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x0000000000000000000000000000000000000000000000000000000000773d45e0（Function Selector）</p>", tooltip: "", icon: "" },
        id: 4,
      },
      {
        "1": { title: "<p>0xa0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x0000000000000000000000000000000000000000000000000000000000000003（_var1）</p>", tooltip: "", icon: "" },
        id: 5,
      },
      {
        "1": { title: "<p>0xc0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x0000000000000000000000000000000000000000000000000000000000000004（_var2）</p>", tooltip: "", icon: "" },
        id: 6,
      },
      {
        "1": { title: "<p>0xe0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>空</p>", tooltip: "", icon: "" },
        id: 7,
      },
      {
        "1": { title: "<p>0x100</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x0000000000000000000000000000000000000000000000000000000000000003（最初の返り値）</p>", tooltip: "", icon: "" },
        id: 8,
      },
      {
        "1": { title: "<p>0x120</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x0000000000000000000000000000000000000000000000000000000000000004（2番目の返り値）</p>", tooltip: "", icon: "" },
        id: 9,
      },
      {
        "1": { title: "<p>0x140</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Free Memory Pointer（空）</p>", tooltip: "", icon: "" },
        id: 10,
      },
    ],
  }}
/>

### **Yulでdelegatecallを使う方法**

最後のセクションでは、delegatecall\(\)を見ていきます。コードはcall\(\)とほぼ同一で、変更点は1つだけです。

変更したのはcall\(\)をdelegatecall\(\)に変えたことと、callvalue\(\)を削除したことだけです。

callvalue\(\)が不要な理由は、delegatecallがCallMeのコードを自身の状態内で実行するためです。したがって、a\(\)内のrequire\(\)文は、私たちのCallerコントラクトにetherが送金されたかどうかをチェックしています。CallMe内のvar1とvar2を確認すると、変更がないことがわかります。一方、Callerコントラクト内のvar1とvar2は正常に更新されています。

これでコントラクト呼び出しに関するセクションは終わりです！次は、Yulにおける[Memory](http://www.alchemy.com/overviews/yul-memory)、[Storage](http://www.alchemy.com/overviews/yul-storage)、[Smart Contract Calls](http://www.alchemy.com/overviews/yul-contract-calls)の仕組みについて学びましょう。
