[ERC7629] ERC20トークンとERC721トークンを変換できる仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、**ERC20**と**ERC721**の両方のトークンタイプの機能を実装し、相互変換できる仕組みを提案している**ERC7629**についてまとめていきます！

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

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

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

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

# 概要

この規格では、Ethereumブロックチェーン上で**ERC20**（代替性トークン）と**ERC721**（非代替性トークン）の両方を管理できるインターフェースを提案しています。
両方のトークンタイプに適用可能な共通機能セットを定義し、開発者が単一のインターフェースを使用して**ERC20**トークンと**ERC721**トークンを操作できるようになります。

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

https://chaldene.net/erc20

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

https://chaldene.net/erc721

# 動機

この提案では、**ERC20**トークンの流動性と**ERC721**トークンの独自性を組み合わせたトークンの需要に対応することを目的としています。
現在の標準ではどちらの機能を選択する必要があります。

:::note info
**DN404**（**ERC404**）などの規格でも、**ERC20**トークンと**ERC721**の仕組みを組み合わせています。
:::

**ERC20**と**ERC721**の統一されたインターフェースを提供することで、それぞれのトークンに切り替えることができます。

# 仕様

- **ERC20**と**ERC721**の両方の機能を組み合わせたコントラクトを導入します。
- **ERC20**と**ERC721**の相互移行をサポートし、「**FT ←→ NFT**」の変換を容易にします。
- トークンの送付や**ERC20**と**ERC721**の変換、データの取得などの関数とイベントを定義しています。
- **ERC20**トークンを効率的に実装し、通常の**ERC20**トークンの`transfer`と同等のガスコストを維持します。

この規格に準拠するコントラクトは以下のインターフェースを実装する必要があります。

```js
pragma solidity ^0.8.0;
/**
 * @title ERC-7629 Unify Token Interface
 * @dev This interface defines the ERC-7629 Unify Token, which unifies ERC-721 and ERC-20 assets.
 */
interface IERC7629  is IERC165 {
    // ERC-20 Transfer event
    event ERC20Transfer(
        address indexed from,
        address indexed to,
        uint256 amount
    );

    // ERC-721 Transfer event
    event ERC721Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );

    // ERC-721 Transfer event
    event Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );

    // Approval event for ERC-20 and ERC-721
    event Approval(
        address indexed owner,
        address indexed approved,
        uint256 indexed tokenId
    );

    // Approval event for ERC-20 and ERC-721
    event Approval(
        address indexed owner,
        address indexed approved,
        uint256 indexed tokenId
    );

    // Approval event for ERC-20
    event ERC20Approval(
        address indexed owner,
        address indexed approved,
        uint256 indexed tokenId
    );

    // ApprovalForAll event for ERC-721
    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );

    // ERC-20 to ERC-721 Conversion event
    event ERC20ToERC721(address indexed to, uint256 amount, uint256 tokenId);

    // ERC-721 to ERC-20 Conversion event
    event ERC20ToERC721(address indexed to, uint256 amount, uint256[] tokenIds);

    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the number of decimals used in the token.
     */
    function decimals() external view returns (uint8);

    /**
     * @dev Returns the total supply of the ERC-20 tokens.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the balance of an address for ERC-20 tokens.
     * @param owner The address to query the balance of.
     */
    function balanceOf(address owner) external view returns (uint256);

    /**
     * @dev Returns the total supply of ERC-20 tokens.
     */
    function erc20TotalSupply() external view returns (uint256);

    /**
     * @dev Returns the balance of an address for ERC-20 tokens.
     * @param owner The address to query the balance of.
     */
    function erc20BalanceOf(address owner) external view returns (uint256);

    /**
     * @dev Returns the total supply of ERC-721 tokens.
     */
    function erc721TotalSupply() external view returns (uint256);

    /**
     * @dev Returns the balance of an address for ERC-721 tokens.
     * @param owner The address to query the balance of.
     */
    function erc721BalanceOf(address owner) external view returns (uint256);

    /**
     * @notice Get the approved address for a single NFT
     * @dev Throws if `tokenId` is not a valid NFT.
     * @param tokenId The NFT to find the approved address for
     * @return The approved address for this NFT, or the zero address if there is none
     */
    function getApproved(uint256 tokenId) external view returns (address);

    /**
     * @dev Checks if an operator is approved for all tokens of a given owner.
     * @param owner The address of the token owner.
     * @param operator The address of the operator to check.
     */
    function isApprovedForAll(
        address owner,
        address operator
    ) external view returns (bool);

    /**
     * @dev Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner.
     * @param owner The address of the token owner.
     * @param spender The address of the spender.
     */
    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);

    /**
     * @dev Returns the array of ERC-721 token IDs owned by a specific address.
     * @param owner The address to query the tokens of.
     */
    function owned(address owner) external view returns (uint256[] memory);

    /**
     * @dev Returns the address that owns a specific ERC-721 token.
     * @param tokenId The token ID.
     */
    function ownerOf(uint256 tokenId) external view returns (address erc721Owner);

    /**
     * @dev Returns the URI for a specific ERC-721 token.
     * @param tokenId The token ID.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);

    /**
     * @dev Approve or disapprove the operator to spend or transfer all of the sender's tokens.
     * @param spender The address of the spender.
     * @param amountOrId The amount of ERC-20 tokens or ID of ERC-721 tokens.
     */
    function approve(
        address spender,
        uint256 amountOrId
    ) external returns (bool);

    /**
     * @dev Set or unset the approval of an operator for all tokens.
     * @param operator The address of the operator.
     * @param approved The approval status.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Transfer ERC-20 tokens or ERC-721 token from one address to another.
     * @param from The address to transfer ERC-20 tokens or ERC-721 token from.
     * @param to The address to transfer ERC-20 tokens or ERC-721 token to.
     * @param amountOrId The amount of ERC-20 tokens or ID of ERC-721 tokens to transfer.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amountOrId
    ) external returns (bool);
    
    /**
     * @notice Transfers the ownership of an NFT from one address to another address
     * @dev Throws unless `msg.sender` is the current owner, an authorized
     *  operator, or the approved address for this NFT. Throws if `_rom` is
     *  not the current owner. Throws if `_to` is the zero address. Throws if
     *  `tokenId` is not a valid NFT. When transfer is complete, this function
     *  checks if `to` is a smart contract (code size > 0). If so, it calls
     *  `onERC721Received` on `to` and throws if the return value is not
     *  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
     * @param from The current owner of the NFT
     * @param to The new owner
     * @param tokenId The NFT to transfer
     * @param data Additional data with no specified format, sent in call to `to`
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external payable;

    /**
     * @notice Transfers the ownership of an NFT from one address to another address
     * @dev This works identically to the other function with an extra data parameter,
     *  except this function just sets data to "".
     * @param from The current owner of the NFT
     * @param to The new owner
     * @param tokenId The NFT to transfer
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external payable;

    /**
     * @dev Transfer ERC-20 tokens to an address.
     * @param to The address to transfer ERC-20 tokens to.
     * @param amount The amount of ERC-20 tokens to transfer.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Retrieves the unit value associated with the token.
     * @return The unit value.
     */
    function getUnit() external view returns (uint256);

    /**
     * @dev Converts ERC-721 token to ERC-20 tokens.
     * @param tokenId The unique identifier of the ERC-721 token.
     */
    function erc721ToERC20(uint256 tokenId) external;

    /**
     * @dev Converts ERC-20 tokens to an ERC-721 token.
     * @param amount The amount of ERC-20 tokens to convert.
     */
    function erc20ToERC721(uint256 amount) external;
}
```

### `getUnit`

```js
/**
 * @dev Retrieves the unit value associated with the token.
 * @return The unit value.
 */
function getUnit() external view returns (uint256);
```

**ERC20**トークンが単一の**ERC721**トークンと幾つで交換できるかの変換レートを返す関数です。

### `erc721ToERC20`

```js
/**
 * @dev Converts ERC-721 token to ERC-20 tokens.
 * @param tokenId The unique identifier of the ERC-721 token.
 */
function erc721ToERC20(uint256 tokenId) external;
```

指定された**ERC721**トークンを**ERC20**トークンに変換する関数。
変換対象となる**ERC721**トークンの`tokenId`を引数で受け取ります。

### `erc20ToERC721`

```js
/**
 * @dev Converts ERC-20 tokens to an ERC-721 token.
 * @param amount The amount of ERC-20 tokens to convert.
 */
function erc20ToERC721(uint256 amount) external;
```

指定された量**ERC20**トークンを**ERC721**トークンに変換する関数。
変換対象となる**ERC20**トークンの数量である`amount`を引数で受け取ります。

# 補足

### 統一インターフェース

**ERC20**と**ERC721**の統一されたインターフェースを導入します。
これにより、開発者は別々のロジックを実装することなく、両方のトークンタイプとやり取りできるようになります。

### `transfer`機能

トークンをアドレス間で移動させるための `transferFrom` 関数を実装しています。
これは、**ERC20**と**ERC721**の両方の標準においてコアとなる機能です。
これにより、トークンの移動がシームレスに行えます。

### `mint`と`burn`

トークンの発行（`mint`）と`burn`関数を実装しています。
これらの関数があることで、新しいトークンを作成したり、不要なトークンを`burn`することができます。

### 残高と所有権のクエリ

トークンの残高や所有権情報を取得するための関数を実装しています。
`balanceOf`関数は特定のアドレスのトークン残高を返し、`ownerOf`関数はトークンの所有者を返します。

### 互換性と拡張性

既存の**ERC20**と**ERC721**の実装と互換性を保つことで、移行へのハードルを下げます。
これにより将来の拡張を考慮し、追加の関数やイベントを組み込むことが可能になります。

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

リエントランシー攻撃やオーバーフローなどを防ぐためのメカニズムを実装し、統一インターフェースのセキュリティを確保します。
例えば、リエントランシー攻撃を防ぐためのガードや、オーバーフローを防ぐためのチェックを導入しています。

:::note info
リエントランシーガードは以下のOpenzeppelinの機能があります。
:::

https://docs.openzeppelin.com/contracts/5.x/api/utils#ReentrancyGuard

:::note info
オーバーフローなどはSolidityのバージョン0.8.20移行であればデフォルトで組み込まれています。
:::

:::note info
リエントランシー攻撃やオーバーフローなどについては以下の記事を参考にしてください。
:::

https://chaldene.net/solidity-reentrancy-attack

https://chaldene.net/solidity-overflow-underflow

# 互換性

互換性については問題が生じています。
例えば、`balanceOf`は**ERC20**と**ERC721**の両方に実装されている機能になります。
そのため、この提案では`erc20BalanceOf`と`erc721BalanceOf`と異なる関数を定義しているため、互換性は失われています。
この機能を汎用的な`balanceOf`関数として実装するかを検討しているようです。

# セキュリティ

**ERC20**と**ERC721**の両方の機能が、どのように統合されるかの認識の違いによって開発者間で異なる解釈が生じやすいです。
そのため、明確な設計ドキュメントや仕様書を作成し、チーム間や開発者間で認識を共有できるようにすることが重要です。

**ERC20**と**ERC721**を切り替える時にセキュリティリスクの懸念があります。
例えば、切り替え時に資産が不正に操作されたり、盗まれる可能性があるため、セキュリティ監査を行うことが重要です。

# 引用

0xZeus1111 (@0xZeus1111), Nvuwa (@Nvuwa), "ERC-7629: ERC-20/ERC-721 Unified Token Interface [DRAFT]," Ethereum Improvement Proposals, no. 7629, February 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7629.

# 最後に

今回は「**ERC20**と**ERC721**の両方のトークンタイプの機能を実装し、相互変換できる仕組みを提案している**ERC7629**」についてまとめてきました！
いかがだったでしょうか？

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