[ERC7066] NFTをロックする仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、**ERC721**規格のNFTをロックする仕組みのインターフェイスを提案している規格である**ERC7066**についてまとめていきます！

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

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

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

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

# 概要

提案されているこの拡張された**ERC721**規格は、NFTに新しい機能を追加するものです。
主なポイントは以下になります。

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

https://chaldene.net/erc721

### 所有者によるロック
NFTの所有者は、そのNFTにロックをかけることができます。
これを行うには、所有者は特定のアドレス（EOAアカウントまたはコントラクトアカウント）をロッカーとして指定します。
このロッカーアドレスのみがNFTのロックを解除できる権限を持ちます。

### `tokenId`の承認
NFTの所有者は、特定の`tokenId`について承認を与えることができます。
この承認を受けたアドレスは、NFTの所有権が自分にある間そのNFTをロックする権限を持ちます。

### 承認された者によるロック
NFTに対して承認を受けた人は、自分自身をロッカーとして指定しそのNFTをロックすることができます。

### 譲渡時の権限リセット
NFTが他の人に譲渡されると、これらのロックに関する権限はすべてリセットされます。

このように、**ERC721**のこの拡張はNFTをより安全に管理し、特定の条件下でのみ使用や譲渡ができるようにするためのものです。
これにより、所有者はNFTをより制御しやすくなり、不正な取引や売却を防ぐことができます。

# 動機

**ERC721**によってNFT市場は大きな成長を遂げましたが、所有者のウォレット内でNFTが流動化しないという二次流動性の問題があります。
これを解決するためのプロジェクトが存在しますが、所有者がNFTをプロジェクトのコントラクトに移転する必要があるため、以下のような問題が生じます。

### ユーティリティの喪失
NFTがエスクロー口座（第三者の管理口座）に移転されると、所有者の直接管理から離れるため、NFTの実用性が低下します。

### コンポーザビリティの不足
NFT所有者がローンや資産の賃貸など複数の金融ツールを活用できれば、市場の流動性はさらに向上します。
しかし、現在はそのような組み合わせ可能性が不足しています。

### スマートコントラクトの脆弱性
スマートコントラクトにはバグや脆弱性が存在する可能性があり、これによりNFTが失われたり盗まれたりするリスクがあります。

これらの問題に対処するために、**ERC721**規格の改善として、ネイティブなロッキングメカニズムを導入する提案があります。
このメカニズムでは、NFTをスマートコントラクトに移転する代わりに、所有者のウォレット内で安全に保管しつつロックします。
ロック期間中はNFTの移転が制限されますが、他の特性はそのまま維持されます。
これにより、NFT所有者はNFTのユーティリティを利用したり配布したりする能力を保持できます。

例えば、ローンの担保としてNFTを使用しても、所有者のウォレット内に残す必要がある場合があります。
これにより、Discordサーバーへのアクセス権を付与したり、P2Eゲーム内でNFTを利用したりする自由が保たれます。
不動産の所有者が抵当に入れた家に住み続けたり、ローンを受けたり、賃貸で収入を得るように、NFT所有者も同様の機能を享受できるようになることで、NFT市場への投資が促進されることが期待されます。

ロック可能なNFTは、次のような様々な用途に活用できます。

### NFT担保付きローン
通常のローンではNFTをレンディングプロトコルのコントラクトにロックする必要がありますが、ロック可能なNFTを使えば、所有者のウォレット内でNFTをロックするだけで、そのユーティリティを維持しながらローンを利用できます。

### 担保不要のNFTレンタル
大きな担保を必要とせず、手数料を支払ってNFTを借りることができます。
借り手はNFTを使用できますが、移転することはできません。
これにより貸し手の安全が保証され、借用期間が終了するとNFTは自動的に貸し手に返されます。

### Buy Now Pay Later (BNPL)
このシステムでは、購入者はロックされたNFTを受け取った後すぐに使用を開始できますが、全ての分割払いが完了するまではNFTを売却することができません。
支払いが完了しない場合、NFTは売り手に返却されます。

### コンポーザビリティ
複数の金融ツールにアクセスすることで、NFTの流動性を最大化します。
例えば、NFTを担保にしてローンを受け、同時にレンタルして受動収入を得ることが可能です。

### 初期セール
NFTを部分的に支払い、コレクションの進捗に満足した場合に残りの金額を支払うことができます。

### ソウルバウンド
組織は自分でロッカーを指定しNFTを`mint`してユーザーに送り、その資産をロックすることができます。

### 安全性
ロック可能なNFTを使用すると、高価なNFTを安全かつ便利に使用できます。
所有者はNFTをロックし、安全なコールドウォレットをアンロッカーとして指定できます。
これにより、MetaMaskなどのウォレットにNFTを保管し、簡単に使用することができます。
万が一ハッカーがMetaMaskアカウントにアクセスしても、コールドウォレットにアクセスできない限りNFTを移転することはできず、その安全性が保証されます。

この提案は、NFTのロック機能を改善するための新しいアプローチを提案しており、他の提案とは異なる特徴を持っています。

### 最小限の実装
この提案では、ロックとアンロックの機能を最小限に抑えています。
例えば、時間に基づく条件などは良いアイデアですが、これらは特別な実装を作成せずとも、スマートコントラクト自体で実現可能です。
ロックとアンロックは、返済や期限切れなどの様々な条件に基づいて設定できます。

### ロッカーと承認者のロールの分離
トークンのロックに関して、ロッカー（ロックをかける者）と承認者（ロックを解除できる者）のロールを分けています。
これにより、トークンは承認を受けた上でロックされ、レンタルや貸出、BNPL（後払い）などの新しい使い方が可能になります。
また、トークンをロックした状態で取り消し権を持たない設定も可能で、必要に応じてアンロックできます（例えば、アカウントに紐づけられたNFTなど）。

### `transferAndLock`の実装
この提案では、`transferAndLock`という機能を導入します。
これにより、トークンを移転しながらロックし、必要に応じて承認も付与できます。
これは、移転後にも取り消しが可能となるため、より柔軟な管理が実現できます。

### ERC721規格の拡張
**ERC721**規格を拡張することで、NFTの基盤資産の管理をより安全かつ便利に行えるようになります。
この新規格は、NFTの金融化（NFTFi）におけるステーキング、貸出、レンタルなどの主要な用途を直接サポートします。
これにより、NFT所有者がNFTFiプロジェクトにより積極的に参加し、NFTエコシステム全体の活性化が期待されます。

# 仕様

## 概要

この提案は、**ERC721**に準拠したコントラクトが提供するNFTのロックとアンロックの標準的な方法に関するものです。

### トークンのロック
トークンの所有者は、`lock(uint256 tokenId, address _locker)`関数を使ってトークンをロックし、ロックを解除できるアドレス（ロッカー）を指定します。
この関数はロッカーを`_locker`に設定します。
また、所有者または承認された者は`lock(uint256 tokenId)`関数を使ってトークンをロックできますが、この場合ロッカーは関数を呼び出したアドレス（`msg.sender`）になります。

### トークンのアンロック
ロッカーは`unlock`関数を使ってトークンをアンロックできます。
この関数はロッカーの情報を削除し、デフォルトのアドレス（`address(0)`）に設定します。

### ロッカーの確認
`lockerOf`関数は、ロックされているトークンの場合、ロックを解除できるアドレス（ロッカー）を返します。
ロックされていないトークンの場合は、`address(0)`を返します。

### 制限事項
トークンが既にロックされている場合に`lock`関数を使用すると失敗します。
同様に、ロックされていないトークンに`unlock`関数を使用すると失敗します。
ロックされているトークンには、**ERC721**の`approve`関数や所有権を移転する関数も失敗します。
ただし、関数を呼び出したアドレスが承認されており、ロッカーでもある場合はこの限りではありません。
トークン移転の関数が呼ばれた後、ロッカーと承認されたアドレスの情報は削除されます。

### トークンの移転とロック
`transferAndLock`関数を使用すると、トークンを移転しながらロックし、ロッカーに承認も与えることができます。
これは、トークンの移転後に取り消しが必要な場合に推奨される方法です。

## インターフェース

```javascript
// SPDX-License-Identifier: CC0-1.0

pragma solidity >=0.7.0 <0.9.0;

/// @title Lockable Extension for ERC721
/// @dev Interface for the Lockable extension
/// @author StreamNFT 

interface IERC7066{

    /**
     * @dev Emitted when tokenId is locked
     */
    event Lock (uint256 indexed tokenId, address _locker);

    /**
     * @dev Emitted when tokenId is unlocked
     */
    event Unlock (uint256 indexed tokenId);

    /**
     * @dev Lock the tokenId if msg.sender is owner or approved and set locker to msg.sender
     */
    function lock(uint256 tokenId) external;

    /**
     * @dev Lock the tokenId if msg.sender is owner and set locker to _locker
     */
    function lock(uint256 tokenId, address _locker) external;

    /**
     * @dev Unlocks the tokenId if msg.sender is locker
     */
    function unlock(uint256 tokenId) external;

    /**
     * @dev Tranfer and lock the token if the msg.sender is owner or approved. 
     *      Lock the token and set locker to caller
     *      Optionally approve caller if bool setApprove flag is true
     */
    function transferAndLock(uint256 tokenId, address from, address to, bool setApprove) external;

    /**
     * @dev Returns the wallet, that is stated as unlocking wallet for the tokenId.
     *      If address(0) returned, that means token is not locked. Any other result means token is locked.
     */
    function lockerOf(uint256 tokenId) external view returns (address);
}
```

# イベント

---

### Lock

```javascript
event Lock (uint256 indexed tokenId, address _locker);
```

**概要**

トークンがロックされたときに発行されるイベント。

**詳細**

このイベントは、特定のトークンIDのトークンがロックされたときに発行されます。
ロックの操作が行われた際に、このイベントによってロックが行われたことが外部に通知されます。

**パラメータ**

- `uint256 indexed tokenId`
    - ロックされたトークンのID。
- `address _locker`
    - トークンをロックしたアドレス（ロッカー）。

---

### Unlock

```javascript
event Unlock (uint256 indexed tokenId);
```

**概要**

トークンがアンロックされたときに発行されるイベント。

**詳細**

このイベントは、特定のトークンIDのトークンがアンロックされたときに発行されます。
アンロックの操作が行われた際に、このイベントによってアンロックが行われたことが外部に通知されます。

**パラメータ**

- `uint256 indexed tokenId`
    - アンロックされたトークンのID。

---

# 関数

---

### lock

```javascript
function lock(uint256 tokenId) external;
```

**概要**

トークンIDをロックする関数。

**詳細**

この関数は、`msg.sender`がトークンの所有者または承認されている場合に、指定されたトークンIDをロックし、ロッカーを`msg.sender`に設定します。

**引数**

- `uint256 tokenId`
    - ロックするトークンのID。

---

### lock

```javascript
function lock(uint256 tokenId, address _locker) external;
```

**概要**

トークンIDを特定のアドレスでロックする関数。

**詳細**

この関数は、`msg.sender`がトークンの所有者である場合に限り、指定されたトークンIDをロックし、ロッカーを`_locker`アドレスに設定します。

**引数**

- `uint256 tokenId`
    - ロックするトークンのID。
- `address _locker`
    - ロッカーとして設定するアドレス。

---

### unlock

```javascript
function unlock(uint256 tokenId) external;
```

**概要**

トークンIDのロックを解除する関数。

**詳細**

この関数は、`msg.sender`がロッカーである場合に限り、指定されたトークンIDのロックを解除します。

**引数**

- `uint256 tokenId`
    - ロックを解除するトークンのID。

---

### transferAndLock

```javascript
function transferAndLock(uint256 tokenId, address from, address to, bool setApprove) external;
```

**概要**

トークンを移転しロックする関数。

**詳細**

この関数は、`msg.sender`がトークンの所有者または承認されている場合に、トークンを移転し、受け取り手のアドレスでロックします。
`setApprove`フラグが`true`の場合は、呼び出し元に承認を設定します。

**引数**

- `uint256 tokenId`
    - 移転およびロックするトークンのID。
- `address from`
    - トークンの移転元アドレス。
- `address to`
    - トークンの移転先アドレス。
- `bool setApprove`
    - 承認設定フラグ。

---

### lockerOf

```javascript
function lockerOf(uint256 tokenId) external view returns (address);
```

**概要**

トークンのロッカーアドレスを返す関数。

**詳細**

この関数は、指定されたトークンIDのロッカーアドレスを返します。
トークンがロックされていない場合は、`address(0)`を返します。

**引数**

- `uint256 tokenId`
    - ロッカーを確認するトークンのID。

**戻り値**

- `address`
    - ロッカーアドレス、またはトークンがロックされていない場合は`address(0)`です。

---

# 補足

この提案は、**ERC721**コントラクトで使用されるNFTのロックとアンロックの機能に関連しています。

### トークンのアンロック時の処理
トークンがアンロックされると、`locker[tokenId]`は`address(0)`に設定されます。
これは、`locker[tokenId]`のマッピングを削除し、ストレージのスペースを解放するためです。
この処理により、コントラクト内でトークンが現在ロックされているかどうかを判断することができます。

### `transferAndLock`関数の提供
この提案では、`transferAndLock(uint256 tokenId, address from, address to, bool setApprove)`関数が公開されています。
この関数は、トークンを受け取り手のアドレスに移転し、同時にロックするために使用されます。
加えて、`bool setApprove`という追加のパラメータがあり、これが`true`に設定されている場合は、ロッカー（ロックを解除できるアドレス）に承認を与えます。
これにより、ロッカーがトークンを取り消すことが可能になり、取り消し条件はコントラクトで定義されます。
この機能により、受け取り手はトークンの所有権を持ちますが、トークンを移転する権限はありません。

この提案により、**ERC721**トークンのより柔軟な管理と、条件付きの所有権の提供が可能になります。

# 後方互換性

この標準は、**ERC721**規格と互換性があります。
既存のアップグレード可能な**ERC721**コントラクトは、この新しい標準にアップグレードすることで、ロック機能を本質的に備えることができ、それによって基盤となる流動性の特徴を解放することが可能になります。
これにより、**ERC721**トークンをより効果的に管理し、新しい金融機能や用途を実現することができるようになります。

:::note info
アップグレーダブルなコントラクトについては以下を参考にしてください。
:::

https://qiita.com/cardene/items/339d9fd83955cda55d14

https://chaldene.net/upgradable-contracts

# テスト

以下にテストコードが格納されています。

https://eips.ethereum.org/assets/eip-7066/test/test.js

# 参考実装

インターフェースの参考実装は以下に格納されています。

https://eips.ethereum.org/assets/eip-7066/IERC7066.sol

コントラクトの参考実装は以下に格納されています。

https://eips.ethereum.org/assets/eip-7066/ERC7066.sol

# セキュリティ考慮事項

この**ERC721**標準に基づくロック機能の実装についてのセキュリティ上の考慮事項は、以下のように説明できます。

### ロック後のトークンの扱い
トークンが一度ロックされた後、それは承認や移転ができなくなります。
つまり、ロックが解除されるまでそのトークンは動かすことができなくなります。

### ロッカーと承認者が同一の場合
トークンがロックされている状態でも、そのロッカーがトークンの移転を承認している場合（つまり、ロッカーと承認者が同じである場合）は、そのトークンを移転することが可能です。

### 永久ロックのリスク
ロックされたトークンのロッカーがアクセス不可能なアカウントや未確認のコントラクトアドレスの場合、そのトークンは永久にロックされる恐れがあります。
これはトークンが取り返しのつかない状態になる可能性を意味します。

### MEVに関する考慮
このロック機能において、ロックとアンロックは承認された当事者だけが行えますので、MEV（最大抽出可能な価値）の問題は考慮する必要はありません。

以上のように、ロック機能を持つ**ERC721**コントラクトは、トークンのより効果的な管理を可能にしますが、同時に特定のセキュリティ上の注意が必要です。

＃引用

https://eips.ethereum.org/assets/eip-7066/ERC7066.sol

# 最後に

今回は「**ERC721**規格のNFTをロックする仕組みのインターフェイスを提案している規格である**ERC7066**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58


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