[ERC6982] ガス効率よくNFTをロックする仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、ロック可能な**ERC721**トークンへのガス効率の良いアプローチを提案している規格である**ERC6982**についてまとめていきます！

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

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

:::note warn
**6982**は現在（2023年10月19日）では「**Review**」段階です。
:::

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

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

# 概要

この提案は、**ERC721**トークンのロック可能なインターフェースを導入し、不要なイベントを省いてガスの使用効率を向上させるものです。
このインターフェースは、ロック可能な**ERC721**トークンの作成と管理の基盤を提供します。
具体的には、コントラクトをデプロイする際に`DefaultLocked(bool locked)`イベントを発行し、すべてのトークンの初期ロック状態を設定します。
その後、個々のトークンのステータス変更に関しては、`Locked(uint256 indexed tokenId, bool locked)`イベントを使用します。
また、このインターフェースには、`view`関数である`locked(uint256 tokenId)`が含まれており、トークンの現在のロック状態を取得するために使用できます。
さらに、新しく生成されたトークンのデフォルトのステータスをクエリするためのビュー関数`defaultLocked()`も提供されています。

この**ERC721**コントラクトは、トークンのロックとアンロックを管理するためのガス効率の良い方法を提供します。
デプロイメント時にデフォルトのロック状態を設定し、その後のトークンのステータス変更には`Locked(uint256 indexed tokenId, bool locked)`イベントを使用します。
`locked(uint256 tokenId)`関数を使用して、トークンの現在のロック状態を取得し、`defaultLocked()`関数を使用して新しく生成されたトークンのデフォルトのロック状態を調べることができます。
これにより、トークンのロックとアンロックを効率的に管理できます。

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

https://chaldene.net/erc721

# 動機

従来のロック可能なトークン提案では、トークンがミントされるたびにイベントを発行することが一般的でした。
これは、特にトークンが永久にロックされる場合（例：ソウルバウンドや移動不可のバッジなど）に、不必要なガス消費を引き起こします。
この提案は、よりガス効率の良い解決策を提供し、コントラクトがデプロイされる時点と個々のトークンのステータスが変更される時点でのみイベントを発行する方法を提案しています。

:::note info
通常のトークン提案では、トークンが作成されるたびにイベントが発生し、これによりガスが無駄に消費されます。
しかし、この提案ではコントラクトがデプロイされる時と個々のトークンのステータスが変更される時にのみイベントを発行します。
これにより、トークンが永久にロックされる場合など、不必要なガスの浪費を削減できます。
:::

# 仕様

インターフェイスは以下のように定義されています。

```javascript
// ERC165 interfaceId 0x6b61a747
interface IERC6982 {
  // This event MUST be emitted upon deployment of the contract, establishing 
  // the default lock status for any tokens that will be minted in the future.
  // If the default lock status changes for any reason, this event 
  // MUST be re-emitted to update the default status for all tokens.
  // Note that emitting a new DefaultLocked event does not affect the lock 
  // status of any tokens for which a Locked event has previously been emitted.
  event DefaultLocked(bool locked);

  // This event MUST be emitted whenever the lock status of a specific token 
  // changes, effectively overriding the default lock status for this token.
  event Locked(uint256 indexed tokenId, bool locked);

  // This function returns the current default lock status for tokens.
  // It reflects the value set by the latest DefaultLocked event.
  function defaultLocked() external view returns (bool);

  // This function returns the lock status of a specific token.
  // If no Locked event has been emitted for a given tokenId, it MUST return 
  // the value that defaultLocked() returns, which represents the default 
  // lock status.
  // This function MUST revert if the token does not exist.
  function locked(uint256 tokenId) external view returns (bool);
}
```

**ERC165**で使用するインターフェースIDは`0x6b61a747`です。

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

https://qiita.com/cardene/items/541184977a5cba51b759

# 補足

この標準は、ガス消費を最小限に抑えるために、イベントの頻度を最適化することを目指しています。
`DefaultLocked`イベントは、すべてのトークンのロック状態を確立するために設計されており、新しいトークンがミントされるたびにイベントを発行する必要がありません。
重要なのは、`DefaultLocked`イベントはいつでも発行でき、`Locked`イベントが発行される前だけでなく、その後でも発行できることです。

トークンは特定の状況下でその振る舞いを変更することがあります（たとえば、リビール後など）、新しいデフォルトのステータスを反映するために`DefaultLocked`イベントを再発行することがあります。
ここでの主要な目標は、デフォルトのステータスが変更された場合に、各トークンごとに`Locked`イベントを発行する必要を回避することにより、ガスの使用を節約することです。

`Locked`イベントは、個々のトークンのロック状態の変更を記録するために使用されます。

`defaultLocked`関数は、トークンの現在のデフォルトのロック状態を返します。
この関数は他のコントラクトとのやり取りを容易にし、最終段階にある**ERC5192**との潜在的な衝突を避けるのに役立ちます。

:::note info
**ERC5192**と**ERC6982**は、Ethereumの非代替トークン（NFT）のロック機能に関する異なるアプローチを提供しています。
それぞれの提案は、トークンのロック状態を制御するための機能を提供するが、実装と目的が異なります。

### **ERC5192: Soulbound NFTs**
**ERC5192**は、**EIP721**を拡張し、特定のトークンを一つのアカウントに「**結びつける**」（soulbound）ための最小のインターフェースを提案しています。
この提案では、`locked(uint256 tokenId)`という名前の関数を使ってトークンのロック状態を照会できます。
トークンがロックされている場合、すべての**EIP721**関数はトークンの転送を拒否する必要があります。

### **ERC-6982: Efficient Default Lockable Tokens**
**ERC6982**は、**EIP721**トークンのロック可能なインターフェースを導入し、不必要なイベントを削除することでガス使用量を最適化します。
このインターフェースは、`DefaultLocked(bool locked)`イベントを発行することで、すべてのトークンの初期ロック状態を設定し、個々のトークンのロック状態の変更を`Locked(uint256 indexed tokenId, bool locked)`イベントで処理します。
さらに、`defaultLocked()`という名前のview関数を提供して、新しく発行されたトークンのデフォルトのロック状態を照会できます。

### **潜在的な衝突**
**ERC6982**の`defaultLocked()`関数は、他のコントラクトとのやり取りを促進し、**ERC5192**との潜在的な衝突を回避することを目的としています。
これは、`defaultLocked()`関数が他のコントラクトとのやり取りを容易にし、**ERC5192**のようにトークンのロック状態を異なる方法で制御する提案との間に潜在的な衝突を回避するのに役立つためです。
:::

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

https://qiita.com/cardene/items/01b2463cb87f391498a8

`locked`関数は、特定のトークンの現在のロック状態を提供し、他のコントラクトとのやり取りを助けます。
特定のトークンIDに変更が加えられていない場合、この関数は`defaultLocked`関数が提供する値を返すべきです。

トークンが「**ロックされた**」と指定されていることは、必ずしもそのトークンが完全に譲渡できないことを意味しません。
特定の条件下では、トークンがロックされていても譲渡できる場合があります。
主に、ロック状態はマーケットプレイスや外部取引所でのトークンの譲渡可能性に関連しています。

例として、Crunaプロトコルを考えてみましょう。
このシステムでは、NFTの所有者は「**イニシエータ**」と呼ばれるものをアクティベートできます。
これは、キー取引を開始する特権を持つサブウォレットです。イニシエータを設定すると、トークンのステータスが「**ロック**」されます。
しかし、指定されたイニシエータからの`transfer`の開始があれば、トークンの譲渡は妨げられません。

# 後方互換性

この標準は、既存の**ERC721**コントラクトと完全に後方互換性があります。
この標準では、既存のコントラクトに新しい機能を追加するためのシームレスな方法を提供します。
言い換えれば、既存のトークンや契約に変更を加えることなく、**ERC7507**を導入できるということです。

:::note info
**ERC7507**は既存のトークンやアプリケーションに対して、新しい機能やガス効率の向上をもたらす可能性を秘めています。
この標準を使用することで、既存のトークンと連携しながら、新しい要件やニーズに対応できます。
そして、それを行う時には、既存のトークンやコントラクトに問題や混乱をもたらすことなく、スムーズに統合できます。
:::

# 参考実装

以下のGithubには、特定の使用ケースを解決するための具体的な実装例が含まれています。

https://github.com/ethereum/EIPs/tree/master/assets/eip-6982

この実装の主な目的は、トークンの所有者がアセットをプールにステーキングする時に、通常の状況では所有権を失わないようにすることです。
通常、トークンをステーキングすると、そのトークンの所有権が移動し、所有者が取り戻すことが難しくなります。
しかし、この実装では、プールがアセットをロックすることができ、同時にトークンの所有者に所有権を保持させることができます。

具体的な手順やコントラクトのコンパイルおよびテスト方法については、`README`ファイルに詳細が記載されています。
この実装は、所有者とプールの間でトークンのやり取りを管理し、特定の使用ケースにおいてトークンの流動性と所有権を維持するのに役立つものです。

```javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// Authors: Francesco Sullo <francesco@sullo.co>

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./IERC721Lockable.sol";
import "./IERC6982.sol";

// This is an example of lockable ERC721 using IERC6982 as basic interface

contract ERC721Lockable is IERC6982, IERC721Lockable, Ownable, ERC721, ERC721Enumerable {
  using Address for address;

  mapping(address => bool) private _locker;
  mapping(uint256 => address) private _lockedBy;

  bool internal _defaultLocked;

  modifier onlyLocker() {
    require(_locker[_msgSender()], "Not a locker");
    _;
  }

  constructor(
    string memory name,
    string memory symbol,
    bool defaultLocked_
  ) ERC721(name, symbol) {
    updateDefaultLocked(defaultLocked_);
  }

  function defaultLocked() external view override returns (bool) {
    return _defaultLocked;
  }

  function updateDefaultLocked(bool defaultLocked_) public onlyOwner {
    _defaultLocked = defaultLocked_;
    emit DefaultLocked(defaultLocked_);
  }

  function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId,
    uint256 batchSize
  ) internal override(ERC721, ERC721Enumerable) {
    require(
      // during minting
      from == address(0) ||
        // later
        !locked(tokenId),
      "Token is locked"
    );
    super._beforeTokenTransfer(from, to, tokenId, batchSize);
  }

  function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) {
    return
      interfaceId == type(IERC6982).interfaceId ||
      interfaceId == type(IERC721Lockable).interfaceId ||
      super.supportsInterface(interfaceId);
  }

  function locked(uint256 tokenId) public view virtual override returns (bool) {
    require(_exists(tokenId), "Token does not exist");
    return _lockedBy[tokenId] != address(0);
  }

  function lockerOf(uint256 tokenId) public view virtual override returns (address) {
    return _lockedBy[tokenId];
  }

  function isLocker(address locker) public view virtual override returns (bool) {
    return _locker[locker];
  }

  function setLocker(address locker) external virtual override onlyOwner {
    require(locker.isContract(), "Locker not a contract");
    _locker[locker] = true;
    emit LockerSet(locker);
  }

  function removeLocker(address locker) external virtual override onlyOwner {
    require(_locker[locker], "Not an active locker");
    delete _locker[locker];
    emit LockerRemoved(locker);
  }

  function hasLocks(address owner) public view virtual override returns (bool) {
    uint256 balance = balanceOf(owner);
    for (uint256 i = 0; i < balance; i++) {
      uint256 id = tokenOfOwnerByIndex(owner, i);
      if (locked(id)) {
        return true;
      }
    }
    return false;
  }

  function lock(uint256 tokenId) external virtual override onlyLocker {
    // locker must be approved to mark the token as locked
    require(isLocker(_msgSender()), "Not an authorized locker");
    require(getApproved(tokenId) == _msgSender() || isApprovedForAll(ownerOf(tokenId), _msgSender()), "Locker not approved");
    _lockedBy[tokenId] = _msgSender();
    emit Locked(tokenId, true);
  }

  function unlock(uint256 tokenId) external virtual override onlyLocker {
    // will revert if token does not exist
    require(_lockedBy[tokenId] == _msgSender(), "Wrong locker");
    delete _lockedBy[tokenId];
    emit Locked(tokenId, false);
  }

  // emergency function in case a compromised locker is removed
  function unlockIfRemovedLocker(uint256 tokenId) external virtual override {
    require(locked(tokenId), "Not a locked tokenId");
    require(!_locker[_lockedBy[tokenId]], "Locker is still active");
    require(ownerOf(tokenId) == _msgSender(), "Not the asset owner");
    delete _lockedBy[tokenId];
    emit ForcefullyUnlocked(tokenId);
  }

  // manage approval

  function approve(address to, uint256 tokenId) public virtual override(IERC721, ERC721) {
    require(!locked(tokenId), "Locked asset");
    super.approve(to, tokenId);
  }

  function getApproved(uint256 tokenId) public view virtual override(IERC721, ERC721) returns (address) {
    if (locked(tokenId) && lockerOf(tokenId) != _msgSender()) {
      return address(0);
    }
    return super.getApproved(tokenId);
  }

  function setApprovalForAll(address operator, bool approved) public virtual override(IERC721, ERC721) {
    require(!approved || !hasLocks(_msgSender()), "At least one asset is locked");
    super.setApprovalForAll(operator, approved);
  }

  function isApprovedForAll(address owner, address operator) public view virtual override(IERC721, ERC721) returns (bool) {
    if (hasLocks(owner)) {
      return false;
    }
    return super.isApprovedForAll(owner, operator);
  }
}
```

---

### `_locker`

```javascript
mapping(address => bool) private _locker;
```

**概要**
アドレスごとにブール値（真偽値）を関連付ける配列。

**詳細**
- `_locker` マッピングは、各アドレスに対して、そのアドレスが「**ロッカー**」として機能するかどうかを示すブール値を関連付けます。
- `_locker[_msgSender()]`は`_msgSender()`関数が返す現在の呼び出し元アドレスに対応するブール値を返します。
- `_locker`マッピングはプライベートなので、外部からは直接アクセスできません。

**パラメータ**
- `address`
    - アドレスとそのロッカーの状態（真偽値）を関連付けるためのキーと値のペアが含まれます。

---

### `_lockedBy`

```javascript
mapping(uint256 => address) private _lockedBy;
```

**概要**
トークンIDごとにロッカーのアドレスを関連付ける配列。
特定のトークンがどのアドレスによってロックされているかを追跡します。

**詳細**
- `_lockedBy`マッピングは、各トークンIDに対して、そのトークンをロックしたアドレスを関連付けます。
- `_lockedBy[トークンID]`は、指定したトークンIDに対応するロッカーのアドレスを返します。
- `_lockedBy`マッピングもプライベートなので、外部からは直接アクセスできません。

**パラメータ**
- `uint256`
    - トークンIDとそのロックを実行したアドレスが関連付けられます。

---

### `_defaultLocked`

```javascript
bool internal _defaultLocked;
```

**概要**
デフォルトのロック状態を示すブール値（真偽値）。
デフォルトでトークンがロックされているかどうかを示します。

**詳細**
- `_defaultLocked`ブール値は、コントラクト内でデフォルトのロック状態を定義します。
- トークンがデプロイされたときの初期状態を表します。
- `_defaultLocked`が`true`の場合、デフォルトでトークンはロックされた状態となり、`false`の場合はロックされていない状態となります。
- この変数は`internal`なので、同じコントラクト内で継承されたコントラクトからアクセスできます。

**パラメータ**
- `bool`
    - デフォルトのロック状態を示すブール値です。
    - `true`はロック状態を示し、`false`はロック解除状態を示します。

---

### `onlyLocker`

```javascript
modifier onlyLocker() {
    require(_locker[_msgSender()], "Not a locker");
    _;
}
```

**概要**
特定の条件を満たす呼び出し元にのみアクセスを許可する修飾子。
呼び出し元が「**ロッカー**」として設定されている場合にのみ、関数やコードブロックにアクセスできます。

**詳細**
- `onlyLocker`修飾子は、トランザクションが実行される前に特定の条件をチェックします。
- 条件として、`_locker[_msgSender()]`が`true`（つまり、呼び出し元がロッカーである）であるかどうかを確認します。
- もし条件が満たされない場合、`require`ステートメントによりエラーメッセージ「**Not a locker**」が表示され、トランザクションが中断されます。
- 条件が満たされると、修飾子が付いた関数やコードブロックが実行されます。

---

### `defaultLocked`

```javascript
function defaultLocked() external view override returns (bool) {
    return _defaultLocked;
}
```

**概要**
デフォルトのロック状態が設定されているか取得する関数。

**詳細**
- デフォルトのロック状態が設定されているかどうかを外部から取得するために使用されます。
- 関数の修飾子として`external view override`が指定されており、外部から読み取り専用でアクセス可能です。
- 関数は`_defaultLocked`変数の値を返します。

**戻り値**
- `bool`
  - デフォルトのロック状態。
  - `true`はロックされていることを示し、`false`はロック解除状態を示します。

---

### `updateDefaultLocked`

```javascript
function updateDefaultLocked(bool defaultLocked_) public onlyOwner {
    _defaultLocked = defaultLocked_;
    emit DefaultLocked(defaultLocked_);
}
```

**概要**
デフォルトのロック状態を更新する関数。
オーナーによって呼び出されます。

**詳細**
- `updateDefaultLocked`関数は、デフォルトのロック状態を指定された値に更新し、それに応じて `DefaultLocked`イベントを発行します。
- 関数は`_defaultLocked`変数の値を更新します。
- この関数はオーナーによってのみ呼び出すことができます。

**引数**
- `defaultLocked_`
  - デフォルトのロック状態を指定するブール値。
  - `true`はロックされていることを示し、`false`はロック解除状態を示します。

**戻り値**
- なし。

---

### `_beforeTokenTransfer`

```javascript
function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId,
    uint256 batchSize
) internal override(ERC721, ERC721Enumerable) {
    require(
        // during minting
        from == address(0) ||
        // later
        !locked(tokenId),
        "Token is locked"
    );
    super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
```

**概要**
トークンの`transfer`が行われる前に実行される前処理を担当する関数。
トークンがロックされている場合、トークンの`transfer`を拒否します。

**詳細**
- トークンの`transfer`が行われる前に呼び出され、`transfer`の条件を検証します。
- 条件として、`from`アドレスからの`transfer`の場合（ミント中）、またはトークンがロックされていない場合に`transfer`を許可します。
- それ以外の場合、エラーメッセージ「**Token is locked**」が表示されて`transfer`が中断されます。
- この関数は**ERC721**や**ERC721Enumerable**の関数をオーバーライドしています。
- 最後に、親クラスの`_beforeTokenTransfer`関数を呼び出します。

**引数**
- `from`
  - トークンの送信元アドレス。
- `to`
  - トークンの送信先アドレス。
- `tokenId`
  - トークンのID。
- `batchSize`
  - バッチ処理の際のバッチサイズ。

---

### `supportsInterface`

```javascript
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) {
    return
        interfaceId == type(IERC6982).interfaceId ||
        interfaceId == type(IERC721Lockable).interfaceId ||
        super.supportsInterface(interfaceId);
}
```

**概要**
指定されたインターフェースがサポートされているかを確認する関数。
複数のインターフェースに対応しています。

**詳細**
- 指定されたインターフェースIDがサポートされているかを確認します。
- サポートされているインターフェースには**IERC6982**や**IERC721Lockable**が含まれます。
- また、親クラスの**ERC721**や**ERC721Enumerable**のインターフェースもサポートされています。
- 関数は`interfaceId`を受け取り、それに対応するインターフェースがサポートされている場合に`true`を返します。

**引数**
- `interfaceId`
  - インターフェースIDを表すバイト列。

**戻り値**
- `bool`
  - 指定されたインターフェースがサポートされている場合は`true`、それ以外は`false`を返します。

---

### `locked`

```javascript
function locked(uint256 tokenId) public view virtual override returns (bool) {
    require(_exists(tokenId), "Token does not exist");
    return _lockedBy[tokenId] != address(0);
}
``

`

**概要**
指定されたトークンがロックされているかを確認する関数。

**詳細**
- `locked`関数は、指定されたトークンIDに対して、そのトークンがロックされているかどうかを確認します。
- トークンが存在しない場合はエラーメッセージ「**Token does not exist**」を表示します。
- ロックされている場合、対応するアドレスが空ではないかを確認します。
- 非空であればトークンはロックされています。

**引数**
- `tokenId`
  - 確認するトークンのID。

**戻り値**
- `bool`
  - 指定されたトークンがロックされている場合は`true`、それ以外は`false`を返します。

---

### `lockerOf`

```javascript
function lockerOf(uint256 tokenId) public view virtual override returns (address) {
    return _lockedBy[tokenId];
}
```

**概要**
指定されたトークンのロッカー（ロックを実行したアドレス）を取得する関数。

**詳細**
- 指定されたトークンIDに対応するロッカーのアドレスを取得します。
- トークンが存在しない場合でも、対応するアドレスを返しますが、このアドレスは通常トークンがロックされていない場合には空のアドレスとなります。

**引数**
- `tokenId`
  - ロッカーを取得するトークンのID。

**戻り値**
- `address`
  - 指定されたトークンのロッカーのアドレス。

---

### `isLocker`

```javascript
function isLocker(address locker) public view virtual override returns (bool) {
    return _locker[locker];
}
```

**概要**
指定されたアドレスがロッカーであるか確認する関数。

**詳細**
- 指定されたアドレスがロッカーであるかどうかを確認します。
- ロッカーである場合、`true`を返し、そうでない場合は`false`を返します。

**引数**
- `locker`
  - 確認するアドレス。

**戻り値**
- `bool`
  - 指定されたアドレスがロッカーである場合は`true`、それ以外は`false`を返します。

---

### `setLocker`

```javascript
function setLocker(address locker) external virtual override onlyOwner {
    require(locker.isContract(), "Locker not a contract");
    _locker[locker] = true;
    emit LockerSet(locker);
}
```

**概要**
ロッカー（コントラクト）を設定する関数。

**詳細**
- 指定されたアドレスをロッカーとして設定します。
- ロッカーとして設定するアドレスがコントラクトであることを確認します。
- ロッカーが正常に設定された場合、関数は`_locker`マッピングを更新し、`LockerSet`イベントを発行します。
- コントラクトの所有者のみが実行できます。

**引数**
- `locker`
  - 設定するロッカー（コントラクト）のアドレス。

---

### `removeLocker`

```javascript
function removeLocker(address locker) external virtual override onlyOwner {
    require(_locker[locker], "Not an active locker");
    delete _locker[locker];
    emit LockerRemoved(locker);
}
```

**概要**
アクティブなロッカーを削除する関数。

**詳細**
- 指定されたアドレスがアクティブなロッカーであることを確認し、それを削除します。
- アクティブなロッカーであるかどうかを`_locker`マッピングを通じて確認します。
- ロッカーが正常に削除された場合、関数は`_locker`マッピングからロッカーを削除し、`LockerRemoved`イベントを発行します。
- コントラクトの所有者のみが実行できます。

**引数**
- `locker`
  - 削除するアクティブなロッカーのアドレス。

---

### `hasLocks`

```javascript
function hasLocks(address owner) public view virtual override returns (bool) {
    uint256 balance = balanceOf(owner);
    for (uint256 i = 0; i < balance; i++) {
        uint256 id = tokenOfOwnerByIndex(owner, i);
        if (locked(id)) {
            return true;
        }
    }
    return false;
}
```

**概要**
所有者がロックされたトークンを持っているかどうか確認する関数。

**詳細**
- 指定された所有者がロックされたトークンを持っているかどうかを確認します。
- 所有者のトークン数を取得し、それぞれのトークンがロックされているかどうかを確認します。
- ロックされたトークンがある場合は`true`を返し、すべてのトークンがロック解除されている場合は`false`を返します。

**引数**
- `owner`
  - ロックされたトークンを持つ所有者のアドレス。

**戻り値**
- `bool`
  - 所有者がロックされたトークンを持っている場合は`true`、それ以外は`false`を返します。

---

### `lock`

```javascript
function lock(uint256 tokenId) external virtual override onlyLocker {
    require(isLocker(_msgSender()), "Not an authorized locker");
    require(getApproved(tokenId) == _msgSender() || isApprovedForAll(ownerOf(tokenId), _msgSender()), "Locker not approved");
    _lockedBy[tokenId] = _msgSender();
    emit Locked(tokenId, true);
}
```

**概要**
トークンをロックする関数。

**詳細**
- 指定されたトークンをロックし、トークンの所有者にロック権限を与えます。
- ロッカーが正当な権限を持っていることを確認し、トークンがロッカーによって承認されているかどうかも確認します。
- トークンが正常にロックされた場合、関数は`_lockedBy`マッピングを更新し、`Locked`イベントを発行します。

**引数**
- `tokenId`
  - ロックするトークンのID。

---

### `unlock`

```javascript
function unlock(uint256 tokenId) external virtual override onlyLocker {
    require(_lockedBy[tokenId] == _msgSender(), "Wrong locker");
    delete _lockedBy[tokenId];
    emit Locked(tokenId, false);
}
```

**概要**
トークンのロックを解除する関数。

**詳細**
- 指定されたトークンのロックを解除します。
- ロックを解除する権限を持つロッカーであるかどうかを確認し、正当なロッカーであることを確認します。
- トークンが正常にロック解除された場合、関数は`_lockedBy`マッピングからロック情報を削除し、`Locked`イベントを発行します。

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

---

### `unlockIfRemovedLocker`

```javascript
function unlockIfRemovedLocker(uint256 tokenId) external virtual override {
    require(locked(tokenId), "Not a locked tokenId");
    require(!_locker[_lockedBy[tokenId]], "Locker is still active");
    require(ownerOf(tokenId) == _msgSender(), "Not the asset owner");
    delete _lockedBy[tokenId];
    emit ForcefullyUnlocked(tokenId);
}
```

**概要**
ロッカーが削除された場合にトークンのロックを強制的に解除する関数。

**詳細**
- 指定されたトークンのロックを強制的に解除します。
- トークンがロックされていることを確認し、ロッカーがまだアクティブでないこと、および呼び出し元がアセットの所有者であることを確認します。
- トークンが正常に強制的にロック解除された場合、関数は`_lockedBy`マッピングからロック情報を削除し、`ForcefullyUnlocked`イベントを発行します。

**引数**
- `tokenId`
  - 強制的にロック解除するトークンのID。

---

# セキュリティ考慮事項

このEIPは、既知のセキュリティ上の考慮事項を導入していません。
しかし、他のスマート・コントラクト標準と同様に、このインターフェースの実装には厳格なセキュリティ対策を実装することが重要です。

# 引用

Francesco Sullo (@sullof), Alexe Spataru (@urataps), "ERC-6982: Efficient Default Lockable Tokens [DRAFT]," Ethereum Improvement Proposals, no. 6982, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6982.

# 最後に

今回は「ロック可能な**ERC721**トークンへのガス効率の良いアプローチを提案している規格である**ERC6982**」についてまとめてきました！
いかがだったでしょうか？

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