[ERC2771] ガス代を他のアドレスに負担させるメタトランザクションの仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、ガス代を他のアドレスに支払ってもらいコントラクトの処理を実行するメタトランザクションについて提案している**ERC2771**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-2771

# 概要

このEIPは、新しい方法でEthereumのコントラクト間で通信する仕組みを提案しています。
具体的には、**メタトランザクション**と呼ばれるもので、コントラクトが別のコントラクトに対して操作を実行できるようになります。

これまでの通信方法では、呼び出し元のアカウント（EOA）がトランザクションのガス費用を支払う必要がありました。

:::note info
EOAアカウントとは、Metamaskなどのウォレットのことです。
:::

しかし、この提案では、ガス費用を支払えない外部所有アカウントからの操作も可能になります。
これを実現するために、信頼できる**Forwarder**コントラクトが介在します。

具体的には、外部所有アカウントが操作を行いたいとき、**Forwarder**コントラクトを通じてメタトランザクションを送信します。
**Forwarder**コントラクトは、ガス費用を支払えるためのETHを持っているため、操作を実行するためのガスを提供します。
そして、その操作が実際に行われるコントラクト（**Recipient**コントラクト）に対して、特別な情報を付加した形で通知を行います。
この特別な情報には、操作を行ったアカウント（`msg.sender`）や操作の内容（`msg.data`）が含まれます。
これによって、**Recipient**コントラクトは正しく操作を実行することができます。

重要なのは、既存のプロトコルを大きく変更せずに、新しい仕組みを追加するところです。
これによって、ガス費用を持たないアカウントでもコントラクトの機能を利用できるようになります。

:::note info
メタトランザクションについては以下でも説明しています。
:::

https://chaldene.net/erc20permit#st-toc-h-7

# 動機
Ethereumのコントラクトが、ガスの支払いに必要なETHを持たない外部所有アカウントからの呼び出しを受け入れる仕組みへの関心が高まっています。
ガスコストを支払うのに代わりに第三者が支払うことを可能にする解決策は、メタトランザクションと呼ばれます。
このEIPの観点では、メタトランザクションとは、トランザクション署名者によって承認され、ガス代を支払うための信頼できない第三者によって中継されたトランザクションを指します（ガスリレーとも呼ばれます）。
このプロセスにより、ETHを持っていないユーザーもコントラクトの機能を利用できるようになります。

:::note info
以下のようにユーザーはガス代を考慮せずに処理の実行を仲介者にお願いできます。
:::

![34-1024x576.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/a5faa9ab-307e-ac3e-c38d-a2a31bcb5b2b.png)

# 仕様

## 用語の定義

### トランザクション署名者（Transaction Signer）
トランザクション署名者は、トランザクションに署名を行い、その署名済みトランザクションをガスリレーに送信する役割を果たします。

### ガスリレー（Gas Relay）
ガスリレーは、トランザクション署名者からオフチェーンで署名済みのリクエストを受け取り、ガス代を支払って有効なトランザクションに変換し、信頼できる**Forwarder**を介して処理されるようにします。

### 信頼されたForwarder（Trusted Forwarder）
信頼された**Forwarder**は、**Recipient**によって信頼されており、トランザクション署名とnonce（トランザクションの一意の識別子）を正しく検証し、トランザクション署名者からのリクエストを**Recipient**に送付する前に検証するコントラクトです。

:::note info
「**信頼された**」とは、以下のゲームの例えで言えば**ゲーム運営**という存在を信頼して署名を預けたことを指します。
:::

**Forwarder**がトランザクションの起点となります。
そして、渡された署名付きのトランザクションの実行者（`meg.sender`）をユーザーに設定することができます。
ただ、あくまでトランザクションを起こすのは**Forwarder**であるということを注意してください。

### 受信者（Recipient）
受信者（**Recipient**）は、信頼された**Forwarder**を介してメタトランザクションを受け入れるコントラクトです。
これによって、ガスリレーを経由して送信されたトランザクションが受信者（**Recipient**）のコントラクトで実行されます。

このプロセスは、トランザクション署名者が署名したリクエストをガスリレーが受け取り、ガス代を支払ってトランザクションに変換し、信頼された**Forwarder**を介して受信者（**Recipient**）に送付されることで、メタトランザクションを実現します。
受信者（**Recipient**）は信頼された**Forwarder**通じてメタトランザクションを受け入れることで、ガス代を支払えないユーザーでもコントラクトの機能を利用できるようになります。

:::note info
以下のゲームで例えると、ユーザーが**トランザクション署名者**になります。
そして、ゲームを通じて裏側で署名を行い運営に渡している**署名情報**が**ガスリレー**の説明部分になります。
**署名情報**を受け取った運営（この場合はEOAアカウントではなく、コントラクトアカウントになります）が、**信頼されたForwarder**となり、署名を検証してガス代を負担して処理を実行してくれます。
最後に運営が**受信者（Recipient）** となり、コントラクトを呼び出して具体的な処理を実行してくれます。
:::

![35-1024x576.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/dc667140-dba0-3da8-e5bf-887dc4d3eaf2.png)

:::note warn
上記の説明の場合、**信頼されたForwarder**と**受信者（Recipient）**が同じコントラクトに見えますが、実際は運営が2つのコントラクトを所有していると認識してもらえるとわかりやすいです。
:::

## サンプルフロー

![example-flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/e624edb7-241f-49e6-37c8-ef91695193ca.png)
引用: https://eips.ethereum.org/EIPS/eip-2771


## トランザクション署名者のアドレス抽出

**信頼されたForwarderによるアドレス追加**

信頼された**Forwarder**は、受信者（**Recipient**）コントラクトを呼び出す責任を持ち、呼び出しデータの末尾にトランザクション署名者のアドレス（20バイトのデータ）を追加する必要があります。

```javascript
(bool success, bytes memory returnData) = to.call.value(value)(abi.encodePacked(data, from));
```

### トランザクション署名者アドレス抽出

受信者（**Recipient**）コントラクト、次の3つの操作を実行することでトランザクション署名者のアドレスを抽出できます。

1. 信頼された**Forwarder**かどうかをチェックします。
    - これがどのように実装されるかは、提案の範囲外です。
2. 呼び出しデータの最後の`20`バイトからトランザクション署名者のアドレスを抽出し、それをトランザクションの元の送信者として使用します（`msg.sender`の代わりに）。
3. `msg.sender`が信頼された**Forwarder**でない場合（または`msg.data`が`20`バイト未満の場合）、元の`msg.sender`をそのまま返します。

### 信頼されたForwarderの確認

受信者（**Recipient**）は、アドレスデータが信頼されていないコントラクトから追加されないように、信頼された**Forwarder**をチェックする必要があります。
これによって、偽造されたアドレスが生成されるリスクを回避します。

## プロトコルサポートの発見メカニズム

特定のフロントエンドで受信者（**Recipient**）コントラクトがネイティブメタトランザクションをサポートしていることを知っている場合を除いて、ユーザーにメタトランザクションを選択してコントラクトとやり取りする選択肢を提供することはできません。
したがって、受信者（**Recipient**）がメタトランザクションをサポートしていることを世界に伝えるためのメカニズムが必要です。

特に、Web3ウォレットレベルでメタトランザクションをサポートする場合、ウォレットはユーザーが対話したい受信者（**Recipient**）コントラクトについて必ずしも知る必要がありません。

異なるインターフェースと機能（トランザクションバッチング、異なるメッセージ署名フォーマットなど）を持つ信頼された**Forwarder**が存在する可能性があるため、ウォレットが信頼された**Forwarder**を発見できるようにする必要があります。

このために、受信者（**Recipient**）コントラクトは以下の関数を実装する必要があります。

```javascript
function isTrustedForwarder(address forwarder) external view returns(bool);
```

`isTrustedForwarder`関数は、**Forwarder**が受信者（**Recipient**）によって信頼されている場合に`true`を返し、それ以外の場合は`false`を返す必要があります。
`isTrustedForwarder`関数は`revert`してはなりません。

内部的には、受信者（**Recipient**）は**Forwarder**からのリクエストを受け入れる必要があります。

`isTrustedForwarder`関数はオンチェーンで呼び出される可能性があり、そのためガス制限が必要です。
`50,000`ガスを超えることは避けるべきです。

背景としての論理を詳しく説明します。

# 補足

この提案の目的は、最も簡単なコントラクトインターフェースを標準化することによって、コントラクト開発者がメタトランザクションのサポートを簡単に追加できるようにすることです。
受信者（**Recipient**）コントラクトがメタトランザクションをサポートしていない場合、外部所有アカウントは受信者（**Recipient**）コントラクトとメタトランザクションを使用して対話することはできません。

### 標準契約インターフェースの重要性

標準のコントラクトインターフェースがない場合、クライアントが受信者（**Recipient**）がメタトランザクションをサポートしているかどうかを判別する標準的な方法は存在しません。
また、標準のコントラクトインターフェースがない場合、受信者（**Recipient**）にメタトランザクションを送信する標準的な方法も存在しません。

### 信頼されたForwarderの利用

信頼された**Forwarder**を利用できない場合、各受信者（**Recipient**）コントラクトはメタトランザクションを安全に受け入れるために必要なロジックを内部的に実装しなければなりません。

### 発見プロトコルの重要性

発見プロトコルがない場合、クライアントが特定のフォワーダーをサポートする受信者（**Recipient**）を発見するメカニズムがありません。

### コントラクトインターフェースの標準化の利点

コントラクトインターフェースを信頼された**Forwarder**の内部実装の詳細から切り離すことで、受信者（**Recipient**）コントラクトがコードの変更なしに複数の**Forwarder**をサポートすることが可能になります。

### msg.senderの問題とセキュリティ

`msg.sender`は、トランザクションに署名したユーザーを特定するためにコントラクトによって検査できるトランザクションのパラメータです。
しかし、メタトランザクションを保護するには、`msg.sender`のセキュリティだけでは不十分です。
問題は、メタトランザクションに対応していないコントラクトの場合、トランザクションの`msg.sender`がガスリレーからのものに見え、トランザクション署名者からのものには見えないということです。
メタトランザクションを安全に受け入れるためのセキュアなプロトコルは、ガスリレーがトランザクション署名者によるリクエストを偽造、変更、複製することを防止する必要があります。

# 参考実装

```javascript
contract RecipientExample {

    function purchaseItem(uint256 itemId) external {
        address sender = _msgSender();
        // ... perform the purchase for sender
    }

    address immutable _trustedForwarder;
    constructor(address trustedForwarder) internal {
        _trustedForwarder = trustedForwarder;
    }

    function isTrustedForwarder(address forwarder) public returns(bool) {
        return forwarder == _trustedForwarder;
    }

    function _msgSender() internal view returns (address payable signer) {
        signer = msg.sender;
        if (msg.data.length>=20 && isTrustedForwarder(signer)) {
            assembly {
                signer := shr(96,calldataload(sub(calldatasize(),20)))
            }
        }    
    }

}
```

このコントラクトは、メタトランザクションをサポートするための受信者（**Recipient**）コントラクトの例です。
受信者（**Recipient**）コントラクトは、外部所有アカウントがメタトランザクションを使用して商品の購入を行うことを可能にします。

### purchaseItem
外部から呼び出される関数で、指定された商品IDに基づいて商品の購入処理を行います。

### _trustedForwarder
信頼された**Forwarder**のアドレスを保持する不変の変数。

### constructor
コントラクトのデプロイ時に信頼された**Forwarder**のアドレスを受け取り、`_trustedForwarder`変数に設定します。

### isTrustedForwarder
渡されたアドレスが信頼された**Forwarder**かどうかをチェックする関数。

### _msgSender
メタトランザクションからの呼び出しの場合、正しい`msg.sender`アドレスを返す関数。
信頼された**Forwarder**のアドレスが検出された場合、アセンブリコードを使用して正しい署名者アドレスを取得します。

# セキュリティ上の考慮事項

悪意のある**Forwarder**は、`_msgSender()`の値を偽装して、実際には任意のアドレスからトランザクションを送信することができます。
そのため、受信者（**Recipient**）コントラクトは**Forwarder**を信頼する際に非常に注意が必要です。
もし**Forwarder**がアップグレード可能である場合、そのコントラクトが悪意のあるアップグレードを行わないことも信頼する必要があります。

さらに、信頼される**Forwarder**を変更することを制限する必要があります。
攻撃者が自分のアドレスを「**信頼**」することで、トランザクションを偽造できる可能性があるためです。
信頼される**Forwarder**のリストを不変（変更不可能）にすることをお勧めします。
これが実現不可能な場合は、信頼されたコントラクト所有者のみがそれを変更できるようにすることが望ましいです。

要するに、信頼された**Forwarder**を選ぶ際には慎重さが必要であり、不正な操作や偽造を防ぐために信頼性の確認と制限を行うことが重要です。

# 引用

Ronan Sandford (@wighawag), Liraz Siri (@lirazsiri), Dror Tirosh (@drortirosh), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Hadrien Croubois (@Amxx), Sachin Tomar (@tomarsachin2271), Patrick McCorry (@stonecoldpat), Nicolas Venturo (@nventuro), Fabian Vogelsteller (@frozeman), Gavin John (@Pandapip1), "ERC-2771: Secure Protocol for Native Meta Transactions," Ethereum Improvement Proposals, no. 2771, July 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2771.

# 最後に

今回は「ガス代を他のアドレスに支払ってもらいコントラクトの処理を実行するメタトランザクションについて提案している**ERC2771**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

# 採用強化中！

CryptoGamesでは一緒に働く仲間を大募集中です。

https://www.wantedly.com/companies/cryptogames

この記事で書いた自分の経験からもわかるように、裁量権を持って働くことができて一気に成長できる環境です。
「**ブロックチェーンやWeb3、NFTに興味がある**」、「**スマートコントラクトの開発に携わりたい**」など、少しでも興味を持っている方はまずはお話ししましょう！