[ERC7654] リクエストメソッドをもとにコントラクトの構造を管理する仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、リクエストメソッドを使用して、コントラクトで各リクエストごとの情報を管理する仕組みを提案している**ERC7654**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

この規格では、クライアントとコントラクト間の通信の標準を統一することを提案しています。
POST、GET、PUTリクエストが特定の操作にマッピングされ、それに応じてコントラクトのデータの追加、読み取り、変更を行います。
リクエストパラメータやレスポンスの値もカスタマイズできます。

:::note info
ここでいうクライアントとは、コントラクトとやり取りするアプリケーションなどです。
:::

# 動機

この規格は、クライアントが異なるコントラクトの異なる関数を呼び出すための標準的な方法を提案しています。
コントラクトごとに異なる機能を持っているため、クライアントが一貫した方法で各コントラクトの関数を呼び出すことは難しいです。
そのため、コントラクトのリクエストメソッドを再定義し、異なるコントラクトの異なる関数を一貫したルールとプロトコルで呼び出せるようにすることを提案しています。

- POST
    - 新しいデータの作成。
    - 例えば、新しいユーザーの登録や新しいトークンの発行など。
- GET
    - 既存のデータを読み取る。
    - 例えば、ユーザー情報の取得やトークンの詳細を確認するなど。
- PUT
    - 既存のデータを更新する。
    - 例えば、ユーザー情報の更新やトークンのステータス変更など。

上記のように各リクエストメソッドが明確な操作タイプを示しているため、データへのアクセスと操作が整理されて制限が容易になります。
例えば、POSTリクエストはデータの作成に限定され、GETリクエストはデータの読み取りに限定されるため、誤操作を防ぎやすくなります。

また、一貫したルールとプロトコルにより、クライアントが異なるコントラクトの関数を呼び出す時の複雑さが減ります。
これにより、コントラクトの機能と階層構造を理解しやすくなります。

他にも、各関数のリクエストおよびレスポンスのパラメータデータ型が標準化されているため、操作の予測可能性が向上します。
これにより、クライアントとコントラクト間でのデータ交換が一貫性を持ち、信頼性が高まります。


# 仕様

この規格は4つのリクエストメソッドタイプから構成されます。

- GET
    - コントラクトにレコードの取得をリクエスト。
- POST
    - コントラクトに新しいレコードの作成をリクエスト。
- PUT
    - コントラクトにレコードの更新をリクエスト。
- Option
    - サポートされているリクエストメソッドの対応。

以下のようなフローで実行されます。

1. `option`を呼び出してサポートされているリクエストメソッドタイオプを取得
    - コントラクトがサポートしているリクエストメソッドの種類を確認。
2. `getMethods`を呼び出してリクエストメソッド名を取得
    - 利用可能なリクエストメソッドの名前を取得。
3. `getMethodReqAndRes`を呼び出してリクエストパラメータのデータ型とレスポンス値のデータ型を取得
    - 各メソッドに必要なリクエストパラメータのデータ型と、レスポンスのデータ型を確認。
4. リクエストパラメータをエンコードして`get`、`post`、`put`を呼び出す
    - 取得したデータ型情報に基づいてリクエストパラメータを適切にエンコードし、対応するメソッドを呼び出す。
5. レスポンス値をデコード
    - コントラクトからのレスポンスをデコードし、利用可能な形式に変換。

## インターフェース

### `IRequestMethodTypes.sol`

```js
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0;
import "./Types.sol";
interface IRequestMethodTypes{

    /**
     * Requested method type.
     * GET, POST, PUT, OPTIONS
     */
    enum MethodTypes{
        GET,
        POST,
        PUT,
        OPTIONS
    }

    /**
     * Response data event.
     * @param _response is the response value of the post request or put request.
     */
    event Response(bytes _response);

    /**
     * Get method names based on request method type.
     * @param _methodTypes is the request method type.
     * @return Method names.
     */
    function getMethods(MethodTypes _methodTypes)external view returns (string[] memory);

    /**
     * Get the data types of request parameters and responses based on the requested method name.
     * @param _methodName is the method name.
     * @return Data types of request parameters and responses.
     */
    function getMethodReqAndRes(string memory _methodName) external view returns(Types.Type[] memory ,Types.Type[] memory );

    /**
     * Request the contract to retrieve records.
     * @param _methodName is the method name.
     * @param _methodReq is the method type.
     * @return The response to the get request.
     */
    function get(string memory _methodName,bytes memory _methodReq)external view returns(bytes memory);

    /**
     * Request the contract to create a new record.
     * @param _methodName is the method name.
     * @param _methodReq is the method type.
     * @return The response to the post request.
     */
    function post(string memory _methodName,bytes memory _methodReq)external returns(bytes memory);

    /**
     * Request the contract to update a record.
     * @param _methodName is the method name.
     * @param _methodReq is the method type.
     * @return The response to the put request.
     */
    function put(string memory _methodName,bytes memory _methodReq)external returns(bytes memory);

    /**
     * Supported request method types.
     * @return Method types.
     */
    function options()external returns(MethodTypes[] memory);
}
```

### ライブラリ

`Types.sol`というライブラリには、`IRequestMethodTypes`で使用されるSolidityのタイプが一覧になっています。

<details><summary>Types.sol</summary>

```js
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0;
library Types {
    enum Type {
        BOOL,
        INT8,
        INT16,
        INT24,
        INT32,
        INT40,
        INT48,
        INT56,
        INT64,
        INT72,
        INT80,
        INT88,
        INT96,
        INT104,
        INT112,
        INT120,
        INT128,
        INT136,
        INT144,
        INT152,
        INT160,
        INT168,
        INT176,
        INT184,
        INT192,
        INT200,
        INT208,
        INT216,
        INT224,
        INT232,
        INT240,
        INT248,
        INT256,
        UINT8,
        UINT16,
        UINT24,
        UINT32,
        UINT40,
        UINT48,
        UINT56,
        UINT64,
        UINT72,
        UINT80,
        UINT88,
        UINT96,
        UINT104,
        UINT112,
        UINT120,
        UINT128,
        UINT136,
        UINT144,
        UINT152,
        UINT160,
        UINT168,
        UINT176,
        UINT184,
        UINT192,
        UINT200,
        UINT208,
        UINT216,
        UINT224,
        UINT232,
        UINT240,
        UINT248,
        UINT256,
        ADDRESS,
        BYTES1,
        BYTES2,
        BYTES3,
        BYTES4,
        BYTES5,
        BYTES6,
        BYTES7,
        BYTES8,
        BYTES9,
        BYTES10,
        BYTES11,
        BYTES12,
        BYTES13,
        BYTES14,
        BYTES15,
        BYTES16,
        BYTES17,
        BYTES18,
        BYTES19,
        BYTES20,
        BYTES21,
        BYTES22,
        BYTES23,
        BYTES24,
        BYTES25,
        BYTES26,
        BYTES27,
        BYTES28,
        BYTES29,
        BYTES30,
        BYTES31,
        BYTES32,
        BYTES,
        STRING,
        INT8_ARRAY,
        INT16_ARRAY,
        INT24_ARRAY,
        INT32_ARRAY,
        INT40_ARRAY,
        INT48_ARRAY,
        INT56_ARRAY,
        INT64_ARRAY,
        INT72_ARRAY,
        INT80_ARRAY,
        INT88_ARRAY,
        INT96_ARRAY,
        INT104_ARRAY,
        INT112_ARRAY,
        INT120_ARRAY,
        INT128_ARRAY,
        INT136_ARRAY,
        INT144_ARRAY,
        INT152_ARRAY,
        INT160_ARRAY,
        INT168_ARRAY,
        INT176_ARRAY,
        INT184_ARRAY,
        INT192_ARRAY,
        INT200_ARRAY,
        INT208_ARRAY,
        INT216_ARRAY,
        INT224_ARRAY,
        INT232_ARRAY,
        INT240_ARRAY,
        INT248_ARRAY,
        INT256_ARRAY,
        UINT8_ARRAY,
        UINT16_ARRAY,
        UINT24_ARRAY,
        UINT32_ARRAY,
        UINT40_ARRAY,
        UINT48_ARRAY,
        UINT56_ARRAY,
        UINT64_ARRAY,
        UINT72_ARRAY,
        UINT80_ARRAY,
        UINT88_ARRAY,
        UINT96_ARRAY,
        UINT104_ARRAY,
        UINT112_ARRAY,
        UINT120_ARRAY,
        UINT128_ARRAY,
        UINT136_ARRAY,
        UINT144_ARRAY,
        UINT152_ARRAY,
        UINT160_ARRAY,
        UINT168_ARRAY,
        UINT176_ARRAY,
        UINT184_ARRAY,
        UINT192_ARRAY,
        UINT200_ARRAY,
        UINT208_ARRAY,
        UINT216_ARRAY,
        UINT224_ARRAY,
        UINT232_ARRAY,
        UINT240_ARRAY,
        UINT248_ARRAY,
        UINT256_ARRAY,
        ADDRESS_ARRAY,
        BYTES1_ARRAY,
        BYTES2_ARRAY,
        BYTES3_ARRAY,
        BYTES4_ARRAY,
        BYTES5_ARRAY,
        BYTES6_ARRAY,
        BYTES7_ARRAY,
        BYTES8_ARRAY,
        BYTES9_ARRAY,
        BYTES10_ARRAY,
        BYTES11_ARRAY,
        BYTES12_ARRAY,
        BYTES13_ARRAY,
        BYTES14_ARRAY,
        BYTES15_ARRAY,
        BYTES16_ARRAY,
        BYTES17_ARRAY,
        BYTES18_ARRAY,
        BYTES19_ARRAY,
        BYTES20_ARRAY,
        BYTES21_ARRAY,
        BYTES22_ARRAY,
        BYTES23_ARRAY,
        BYTES24_ARRAY,
        BYTES25_ARRAY,
        BYTES26_ARRAY,
        BYTES27_ARRAY,
        BYTES28_ARRAY,
        BYTES29_ARRAY,
        BYTES30_ARRAY,
        BYTES31_ARRAY,
        BYTES32_ARRAY,
        BYTES_ARRAY,
        STRING_ARRAY
    }
}
```
</details>

# 補足

## Type of Request Method

クライアントが標準化された予測可能な方法でコントラクトを操作できるように、`GET`、`POST`、`PUT`の3つのリクエストメソッド対応が設定されています。
コントラクトの呼び出し元がリクエストに必要が情報を理解して処理できるように、3つのタイプの機能を定義する必要があります。

また、`DELETE`タイプは存在しません。
これは、コントラクトでデータを削除することが非効率的な操作だからです。
代わりに、開発者は`PUT`リクエストメソッドを追加し、データを有効・無効に設定することができます。
そして、GETメソッドでは有効なデータのみを返すようにします。

#### Request Method Parameter Type

各リクエストメソッドタイプにはいくつかの関数が定義されており、それぞれの関数にはリクエストパラメータのデータ型とレスポンスパラメータのデータ型が含まれています。
これらのデータ型はコンストラクタで設定され、メソッド名を介して `getMethodReqAndRes` を使って取得されます。

- **データ型の定義**
    - パラメータのデータ型はデータ型の列挙体によって定義されます。
- **リクエストパラメータの処理**
    - リクエストパラメータを処理する際には、`abi.decode` を使用して、リクエストパラメータの型と値に基づいてデコードを行います。
- **レスポンスの返却**
    - レスポンスを返す時には、`abi.encode` を使用して、レスポンスの値とレスポンスパラメータの型に基づいてエンコードを行います。

### 具体例

- **リクエストパラメータの設定**

```js
struct RequestParameter {
  string paramName;
  uint256 paramValue;
}
```

- **リクエストパラメータのデコード**

```js
function decodeRequest(bytes memory encodedRequest) public pure returns (RequestParameter memory) {
  return abi.decode(encodedRequest, (RequestParameter));
}
```

- **レスポンスのエンコード**:

```js
struct Response {
  bool success;
  string message;
}

function encodeResponse(Response memory response) public pure returns (bytes memory) {
  return abi.encode(response);
}
```

# 参考実装

```js
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0;
import "./Types.sol";
import "./IRequestMethodTypes.sol";
contract RequestMethodTypes is IRequestMethodTypes{

    //@dev define the data type of this component
    struct Profiles{
        string name;
        uint256 age;
    }

    mapping (address=>Profiles) users;

    //@dev Types contains all data types in solidity
    mapping (string=>Types.Type[]) methodRequests;
    mapping (string=>Types.Type[]) methodResponses;
    mapping (MethodTypes=>string[]) methods;

    constructor(){
        Types.Type[] memory getReqArray = new Types.Type[](1);
        getReqArray[0] = Types.Type.ADDRESS;
        Types.Type[] memory dataTypeArray = new Types.Type[](2);
        dataTypeArray[0] = Types.Type.STRING;
        dataTypeArray[1] = Types.Type.UINT256;
        Types.Type[] memory putReqArray = new Types.Type[](2);
        putReqArray[0] = Types.Type.ADDRESS;
        putReqArray[1] = Types.Type.STRING;
        // @dev initialize get, post, put request parameter data types and response data types
        setMethod("getUser",MethodTypes.GET,getReqArray,dataTypeArray);
        setMethod("createUser",MethodTypes.POST,dataTypeArray,new Types.Type[](0));
        setMethod("updateUserName",MethodTypes.PUT,putReqArray,new Types.Type[](0));
    }

    function setMethod(string memory _methodName,MethodTypes _methodType,Types.Type[] memory _methodReq,Types.Type[] memory _methodRes)  private  {
        methods[_methodType].push(_methodName);
        methodRequests[_methodName]=_methodReq;
        methodResponses[_methodName]=_methodRes;
    }

    function getMethodReqAndRes(string memory _methodName)public view returns(Types.Type[] memory ,Types.Type[] memory ){
        return(
            methodRequests[_methodName],
            methodResponses[_methodName]
        );
    }
    
    function getMethods(MethodTypes _methodTypes)public view returns (string[] memory){
        return methods[_methodTypes];
    } 

    function get(string memory _methodName,bytes memory _methodReq)public view returns(bytes memory){
        if(compareStrings(_methodName,"getUser")){
            address user=abi.decode(_methodReq, (address));
            bytes memory userData=abi.encode(users[user].name,users[user].age);
            return userData;
        }else{
            return abi.encode("");
        }  
    }

    function post(string memory _methodName,bytes memory _methodReq)public returns(bytes memory){
        if(compareStrings(_methodName,"createUser")){
            (string memory name,uint256 age)=abi.decode(_methodReq, (string,uint256));
            users[msg.sender]=Profiles(name,age);
            
        }
        return abi.encode("");
    }

    function put(string memory _methodName,bytes memory _methodReq)public returns(bytes memory){
        if(compareStrings(_methodName,"updateUserName")){
            (address userAddress,string memory name)=abi.decode(_methodReq, (address,string));
            require(userAddress==msg.sender);
            users[userAddress].name=name;
        }
        return abi.encode("");
    }
    
    function options()public pure returns(MethodTypes[] memory){
        MethodTypes[] memory methodTypes=new MethodTypes[](4);
        methodTypes[0]=MethodTypes.GET;
        methodTypes[1]=MethodTypes.POST;
        methodTypes[2]=MethodTypes.PUT;
        methodTypes[3]=MethodTypes.OPTIONS;
        return methodTypes;
    }

    //@dev compares two strings for equality
    function compareStrings(string memory _a, string memory _b) private  pure returns (bool) {
        return keccak256(abi.encodePacked(_a)) == keccak256(abi.encodePacked(_b));
    }


}
```

### `Profiles`
ユーザーのプロフィール情報を保存。
`name` (string) と `age` (uint256) のフィールドを持ちます。

### `users`

ユーザーのアドレスに対してプロフィール情報を保存。

```js
// ユーザーアドレスと対応するプロフィール
mapping(address => Profiles) users;

// 例:
users[0x123...abc] = Profiles("Alice", 25);
users[0x456...def] = Profiles("Bob", 30);
```

### `methodRequests`

各メソッド名に対するリクエストパラメータのデータ型を保存します。

```js
// メソッド名と対応するリクエストパラメータのデータ型
mapping(string => Types.Type[]) methodRequests;

// 例:
methodRequests["getUser"] = [Types.Type.ADDRESS];
methodRequests["createUser"] = [Types.Type.STRING, Types.Type.UINT256];
methodRequests["updateUserName"] = [Types.Type.ADDRESS, Types.Type.STRING];
```

### `methodResponses`

各メソッド名に対するレスポンスパラメータのデータ型を保存します。

```js
// メソッド名と対応するレスポンスパラメータのデータ型
mapping(string => Types.Type[]) methodResponses;

// 例:
methodResponses["getUser"] = [Types.Type.STRING, Types.Type.UINT256];
methodResponses["createUser"] = [];
methodResponses["updateUserName"] = [];
```

### `methods`

各メソッドタイプ（GET、POST、PUT）に対するメソッド名のリストを保存します。

```js
// メソッドタイプと対応するメソッド名のリスト
mapping(MethodTypes => string[]) methods;

// 例:
methods[MethodTypes.GET] = ["getUser"];
methods[MethodTypes.POST] = ["createUser"];
methods[MethodTypes.PUT] = ["updateUserName"];
```

### `setMethod`
メソッド名、メソッドタイプ、リクエストパラメータ、レスポンスパラメータを設定する関数。

### `getMethodReqAndRes`
メソッド名に対するリクエストおよびレスポンスのデータ型を返す関数。

### `getMethods`
指定されたメソッドタイプ（GET、POST、PUT）に対するメソッド名のリストを返す関数。

### `get`
GETリクエストを処理する関数。
`getUser` メソッドの場合、ユーザーのアドレスをデコードし、そのユーザーのプロフィール情報をエンコードして返す。

### `post`
POSTリクエストを処理します。
`createUser`メソッドの場合、名前と年齢をデコードし、新しいユーザーを作成。

### `put`
PUTリクエストを処理する関数。
`updateUserName` メソッドの場合、ユーザーのアドレスと新しい名前をデコードし、ユーザーの名前を更新。

### `options`
サポートされているリクエストメソッドタイプ（GET、POST、PUT、OPTIONS）のリストを返す関数。

### `compareStrings`
2つの文字列を比較し、等しいかどうかをチェックする関数。


# セキュリティ

コントラクトのリクエストメソッドは安全なメソッドとそうでないメソッドに分けられます。

:::note info
簡単にまとめると、読み取り専用のメソッドが安全で、データを変更するメソッドが安全ではないということです。
:::

- 安全なメソッド
    - `GET`
    - `OPTIONS`
- 安全でないメソッド
    - `POST`
    - `PUT`

# 引用

Rickey (@HelloRickey), "ERC-7654: Request Method Types [DRAFT]," Ethereum Improvement Proposals, no. 7654, March 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7654.

# 最後に

今回は「リクエストメソッドを使用して、コントラクトで各リクエストごとの情報を管理する仕組みを提案している**ERC7654**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4