[EIP7681] ERC20とERC1155を組み合わせたERC404のような仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、代替可能トークンである**ERC20**とマルチトークンである**ERC1155**を組み合わせて、**ERC404**や**DN404**に近い仕組みを提案している**EIP7681**についてまとめていきます！

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

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

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

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

# 概要

この規格では、**ERC20**（代替可能トークン）と**ERC1155**（マルチトークン）を統合して、単一のコントラクト内で2つのトークンを管理できる仕組みを提案しています。

:::note info
**ERC20**については以下の記事を参考にしてください。
:::

https://chaldene.net/erc20

:::note info
**ERC1155**については以下の記事を参考にしてください。
:::

https://chaldene.net/erc1155

# 動機

**ERC20**と**ERC721**を統合した規格は、既に**ERC7631**として提案されています。

:::note info
**ERC7631**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/feef82cecbacf9a043bb

ただ、**ERC7631**の課題点として、`Transfer`イベントが重複してしまうということがありました。
**ERC20**にも**ERC721**にも`Transfer`イベントが存在するため、完全な互換性を持つことが難しかったです。

:::note info
**ERC20**の`Transfer`イベントは以下です。
:::

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol

```js
event Transfer(address indexed from, address indexed to, uint256 value);
```

:::note info
**ERC721**の`Transfer`イベントは以下です。
:::

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol

```js
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
```

:::note info
**ERC1155**の`Transfer`系イベントは以下です。
:::

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC1155/IERC1155.sol

```js
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(
    address indexed operator,
    address indexed from,
    address indexed to,
    uint256[] ids,
    uint256[] values
);
```

確かに**ERC1155**だけ異なりますね。

**ERC20**と**ERC1155**を組み合わせることで、**ERC20**トークンを取得するとその保有量に比例して**ERC1155**トークンを発行する仕組みを実装でき、NFTの分割所有をネイティブにサポートできるようになります。

:::note info
**ERC404**や**DN404**と呼ばれる規格と同じ仕組みです。
簡単な例で説明すると以下になります。

- **ERC20**を10個持っていると、**ERC1155**を1個`mint`される。
- **ERC1155**を1個`transfer`されると、**ERC20**が10個`mint`される。
- **ERC1155**を1個`transfer`すると、**ERC20**が10個`burn`される。

このように、**ERC20**10個につき**ERC1155**1個などの設定値に基づいて、2つのトークンを管理する仕組みです。

**ERC404**については以下の記事で詳しく説明しています。
:::

https://zenn.dev/heku/books/ae7f350cca6e46

また、**ERC20**と**ERC1155**の個数同期プロセスにおいて、**ERC1155**の`mint`や`transfer`を除外することもできます。

# 仕組み

**ERC7681**に準拠するコントラクトは、**ERC20**と**ERC721**の両方を実装する必要があります。

## ERC7681 Interface

**ERC20**コントラクトは以下のインターフェースを実装する必要があります。

```js
interface IERC7681 /* is IERC20, IERC1155 */ {
    /// The contract MUST contain the following events
    /// ERC20 related events
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    /// The contract MUST contain the following events
    /// ERC1155 related events
    event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
    event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    event URI(string _value, uint256 indexed _id);

    /// The contract MAY contain the following functions
    /// ERC20 related functions
    function name() public view returns (string);
    function symbol() public view returns (string);
    function decimals() public view returns (uint8);

    /// The contract MUST contain the following functions
    /// ERC20 related functions
    function totalSupply() public view returns (uint256);
    function balanceOf(address _owner) public view returns (uint256);
    function transfer(address _to, uint256 _value) public returns (bool);
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
    function approve(address _spender, uint256 _value) public returns (bool);
    function allowance(address _owner, address _spender) public view returns (uint256);

    /// The contract MUST contain the following functions
    /// ERC1155 related functions
    function balanceOf(address _owner, uint256 _id) external view returns (uint256);
    function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
    function setApprovalForAll(address _operator, bool _approved) external;
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
    function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
}
```

全て**ERC20**と**ERC1155**に存在する機能たちです。

## ERC7681 Option Interface

オプションで実装できるインターフェースで、**ERC20**と**ERC1155**の同期プロセス中に**ERC1155&&の`mint`や`transfer`をスキップする機能を提供しています。

```js
interface IERC7681Skippable {
    /// @dev Emitted when the skip ERC1155 token status of `owner` is changed by any mechanism.
    ///
    /// This initial skip ERC1155 token status for `owner` can be dynamically chosen to
    /// be true or false, but any changes to it MUST emit this event.
    event SkipTokenSet(address indexed owner, bool status);

    /// @dev Returns true if ERC-1155 mints and transfers to `owner` SHOULD be
    /// skipped during ERC-20 to ERC-1155 synchronization. Otherwise false.
    /// 
    /// This method MAY revert
    ///
    /// If this method reverts:
    /// - Interacting code SHOULD interpret `setSkipToken` functionality as
    ///   unavailable (and hide any functionality to call `setSkipToken`).
    /// - The skip ERC1155 token status for `owner` SHOULD be interpreted as undefined.
    ///
    /// Once a true or false value has been returned for a given `owner`,
    /// this method MUST NOT revert for the given `owner`.
    function getSkipToken(address owner) external view returns (bool);

    /// @dev Sets the caller's skip ERC1155 token status.
    ///
    /// This method MAY revert
    /// (e.g. insufficient permissions, method not supported).
    ///
    /// Emits a {SkipTokenSet} event.
    function setSkipToken(bool status) external;
}
```

### SkipTokenSet

**ERC1155**トークンのスキップステータスが変更されたときに発行されるイベントです。

### getSkipToken

特定のアドレスが**ERC1155**トークンの`mint`や`transfer`をスキップするかどうかを返す関数です。

### setSkipToken

`bool`値で、特定のアドレスが**ERC1155**トークンの`mint`や`transfer`をスキップするか設定する関数です。
権限が不足しているとエラーになります。
ここでの権限はアドレスが指定されていないので、単純に自分が保有しているアドレスのみ設定できるということだと思います。

# 補足

## 実装の柔軟性

特定の同期ロジックを定めたりせずに、様々な実装に対応できるように作成されています。
**ERC20**トークンの保有量に基づいて**ERC1155**トークンを同期させたり、1対1で同期させたりできます。
**ERC20**と**ERC1155**の両方のトークン規格の仕様に完全に準拠している限り、同期方法を自由に決められます。

## ERC1155トークンスキップ

**ERC20**トークンの保有者がコントラkつおの場合、デフォルトのスキップステータスを`true`にすることで、DEXやレンディングプロトコルでの取引において**ERC1155**トークンの発行を止めることができ、ガス代を削減できます。

## 互換性

**ERC20**と**ERC1155**と完全な互換性があります。

# セキュリティ

## ガス切れによるDos

**ERC20**トークンを`transfer`する時、自動的に複数の**ERC1155**トークンの`mint`、`transfer`、`burn`が実行されることがあります。
これは固定のガスコスト（ $O(1)$ ）ではなく、アクションの数に比例してガスコストが増加（ $O(n)$ ）します。
また、**ERC1155**のトークンIDの選択によりガスコストが増加する可能性もあります。

:::note info
**ERC1155**は、各`tokenId`が複数枚存在します。
そのため、**ERC721**と異なり、どの`tokenId`のトークンを発行するかなどを管理する必要があります。
:::

これにより、同期戦略はガスコストの増加を考慮して設計し、ガス切れによるサービス停止などが起きないように注意する必要があります。

# 引用

Sennett Lau (@sennett-lau), "ERC-7681: Dual Nature Multi Token Protocol [DRAFT]," Ethereum Improvement Proposals, no. 7681, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7681.

# 最後に

今回は「代替可能トークンである**ERC20**とマルチトークンである**ERC1155**を組み合わせて、**ERC404**や**DN404**に近い仕組みを提案している**EIP7681**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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