Github CopilotでSolidityのコードをICP上のRustに変換する

## はじめに
SolidityというEVM系チェーンで使われるスマートコントラクト用のソースコードをICP上で動くRustのコードに変換してみます。

## verify-media/contracts
今回の変換対象は、[FoxのVerify](https://docs.verifymedia.com/)という[フェイクニュース対策用のスマートコントラクト](https://github.com/verify-media/contracts)です。

まず、下記のコマンドで`git clone`します。

```bash
git clone https://github.com/verify-media/contracts
```

contractsフォルダをVS CodeのFile -> Open Folderで開きます。

## IdentityRegistry.sol
最初の変換対象は[IdentityRegistry.sol](https://github.com/verify-media/contracts/blob/main/src/identity/IdentityRegistry.sol)です。

このファアイルを開きます。

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

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol";

/**
 * @title IdenityRegistry
 * @author Blockchain Creative Labs
 * @notice A registry of named root signing key pairs and their intermediate keypairs.
 */
contract IdentityRegistry is OwnableUpgradeable, EIP712Upgradeable {
    mapping(address => bool) public registered;
    mapping(address => string) public rootName;
    mapping(string => address) public nameToRoot;
    mapping(address => uint256) public nonces;
    mapping(address => bool) public used;

    mapping(address => address) intermediateToRoot;
    mapping(address => mapping(address => uint256)) registryExpiry;

    error NotRegistered();
    error InvalidSignature();
    error InvalidParams();
    error RegistryExpired();
    error SignatureExpired();
    error AlreadyRegistered();

    modifier onlyRegistered(address user) {
        if (!registered[user]) {
            revert NotRegistered();
        }
        _;
    }

    bytes32 emptyString = keccak256(abi.encode(""));

    /**
     * @dev Used to initialize the values through the transparent proxy upgrade pattern.
     * https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies
     */
    function initialize(string memory name_, string memory version_) public initializer {
        __Ownable_init(msg.sender);
        __EIP712_init(name_, version_);
    }

    /**
     * @notice Registers a given root address to a user facing name, only callable from the protocol wallet
     * @param root The address to register.
     * @param name The user facing name of the address, Org or real world entity.
     */
    function registerRoot(address root, string memory name) external onlyOwner {
        if (root == address(0) || (keccak256(abi.encode(name)) == emptyString)) revert InvalidParams();
        if (used[root] || (nameToRoot[name] != address(0))) revert AlreadyRegistered();
        used[root] = true;
        registered[root] = true;
        rootName[root] = name;
        nameToRoot[name] = root;
    }

    /**
     * @notice Unregisters an address from the registry. Only Callable from the protocol wallet.
     * @param root The address to unregister.
     */
    function unregisterRoot(address root) external onlyOwner {
        nameToRoot[rootName[root]] = address(0);
        registered[root] = false;
        rootName[root] = "";
    }

    /**
     * @notice Registers an address as a intermediate identity of a registered root identity using a signature from the root identity keypair.
     * Will only register intermediate identities that have not been registered before. Can be used to extend a existing registry during only
     * while the existing signature has not expired.
     * @param signature The signature from the root identity.
     * @param root the address of the root identity.
     * @param intermediate the address of the intermediate identity to register
     * @param expiry the uint256 timestamp of the expiry of the intermediate identity acting on behalf of the root.
     * @param chainID the id of the chain which this signature is for, used to avoid replay signatures between testnet/mainnet enviorments.
     * @param deadline the uint256 timestamp of the deadline to use the signature by.
     */
    function register(
        bytes memory signature,
        address root,
        address intermediate,
        uint256 expiry,
        uint256 chainID,
        uint256 deadline
    ) external onlyRegistered(root) {
        if (block.timestamp > deadline) revert SignatureExpired();
        if (used[intermediate]) {
            if (intermediateToRoot[intermediate] != root) revert AlreadyRegistered();
            if (block.timestamp > registryExpiry[root][intermediate]) revert RegistryExpired();
        }
        if (chainID != block.chainid) revert InvalidParams();
        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(
                    keccak256(
                        "register(address root,address intermediate,uint256 expiry,uint256 nonce,uint256 chainID,uint256 deadline)"
                    ),
                    root,
                    intermediate,
                    expiry,
                    nonces[root],
                    chainID,
                    deadline
                )
            )
        );
        address signer = ECDSA.recover(digest, signature);
        if (signer != root) revert InvalidSignature();
        ++nonces[root];
        _register(root, intermediate, expiry);
    }

    /**
     * @dev Internal function which stores the signature and registers the intermediate identity address to the root identity address.
     */
    function _register(address root, address intermediate, uint256 expiry) internal {
        used[intermediate] = true;
        registryExpiry[root][intermediate] = expiry;
        intermediateToRoot[intermediate] = root;
    }

    /**
     * @notice Unregisters an intermediate identity address from a root identity using a signature from the root identity.
     * @param signature The signature from the root identity.
     * @param root the address of the root identity.
     * @param intermediate the address of the intermediate identity to unregister.
     * @param chainID the id of the chain which this signature is for, used to avoid replay signatures between testnet/mainnet enviorments.
     * @param deadline the uint256 timestamp of the deadline to use the signature by.
     */
    function unregister(bytes memory signature, address root, address intermediate, uint256 chainID, uint256 deadline)
        external
        onlyRegistered(root)
    {
        if (intermediateToRoot[intermediate] != root) revert InvalidParams();
        if (block.timestamp > deadline) revert SignatureExpired();
        if (chainID != block.chainid) revert InvalidParams();
        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(
                    keccak256(
                        "unregister(address root,address intermediate,uint256 nonce,uint256 chainID,uint256 deadline)"
                    ),
                    root,
                    intermediate,
                    nonces[root],
                    chainID,
                    deadline
                )
            )
        );
        address signer = ECDSA.recover(digest, signature);
        if (signer != root) revert InvalidSignature();
        ++nonces[root];
        _unregister(root, intermediate);
    }

    /**
     * @dev Internal function to expire a signature to end association between intermediate identity address and root identity.
     */
    function _unregister(address root, address intermediate) internal {
        registryExpiry[root][intermediate] = 0;
    }

    /**
     * @notice Returns the corresponding root identity of an intermediate identity while the stored signature is valid. Address(0) means no association exists
     * @param identity The address of an intermediate identity to lookup.
     */
    function whoIs(address identity) external view returns (address root) {
        root = intermediateToRoot[identity];
        if (registered[root]) {
            if (block.timestamp > registryExpiry[root][identity]) root = address(0);
        } else {
            return address(0);
        }
    }
}
```

`コマンド(CTL)+i`でインラインチャットを開いて、`ICPで動くRustに変換して`と入力します。

下記のようなコードが作られたので、コピーして、Rustのソースコードに貼り付けます。

```rust
use ic_cdk::export::candid::{CandidType, Deserialize};
use ic_cdk::export::Principal;
use ic_cdk_macros::*;
use std::collections::BTreeMap;

#[derive(Clone, CandidType, Deserialize)]
struct IdentityRegistry {
    registered: BTreeMap<Principal, bool>,
    root_name: BTreeMap<Principal, String>,
    name_to_root: BTreeMap<String, Principal>,
    nonces: BTreeMap<Principal, u64>,
    used: BTreeMap<Principal, bool>,
    intermediate_to_root: BTreeMap<Principal, Principal>,
    registry_expiry: BTreeMap<Principal, BTreeMap<Principal, u64>>,
}

#[init]
fn init() -> IdentityRegistry {
    IdentityRegistry {
        registered: BTreeMap::new(),
        root_name: BTreeMap::new(),
        name_to_root: BTreeMap::new(),
        nonces: BTreeMap::new(),
        used: BTreeMap::new(),
        intermediate_to_root: BTreeMap::new(),
        registry_expiry: BTreeMap::new(),
    }
}

#[update]
fn register_root(&mut self, root: Principal, name: String) {
    if root == Principal::anonymous() || name.is_empty() {
        panic!("Invalid parameters");
    }
    if self.used.contains_key(&root) || self.name_to_root.contains_key(&name) {
        panic!("Already registered");
    }
    self.used.insert(root.clone(), true);
    self.registered.insert(root.clone(), true);
    self.root_name.insert(root.clone(), name.clone());
    self.name_to_root.insert(name.clone(), root.clone());
}

#[update]
fn unregister_root(&mut self, root: Principal) {
    if let Some(name) = self.root_name.get(&root) {
        self.name_to_root.remove(name);
    }
    self.registered.insert(root.clone(), false);
    self.root_name.insert(root.clone(), "".to_string());
}

#[update]
fn register(
    &mut self,
    signature: Vec<u8>,
    root: Principal,
    intermediate: Principal,
    expiry: u64,
    chain_id: u64,
    deadline: u64,
) {
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if self.used.contains_key(&intermediate) {
        if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
            if root_identity != &root {
                panic!("Already registered");
            }
            if ic_cdk::api::time() > self.registry_expiry[root_identity][&intermediate] {
                panic!("Registry expired");
            }
        }
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&expiry.to_le_bytes());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._register(root, intermediate, expiry);
}

#[update]
fn unregister(&mut self, signature: Vec<u8>, root: Principal, intermediate: Principal, chain_id: u64, deadline: u64) {
    if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
        if root_identity != &root {
            panic!("Invalid parameters");
        }
    }
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._unregister(root, intermediate);
}

#[query]
fn who_is(&self, identity: Principal) -> Option<Principal> {
    if let Some(root) = self.intermediate_to_root.get(&identity) {
        if let Some(expiry_map) = self.registry_expiry.get(root) {
            if ic_cdk::api::time() > expiry_map[&identity] {
                return None;
            }
        }
        return Some(root.clone());
    }
    None
}

impl IdentityRegistry {
    fn _register(&mut self, root: Principal, intermediate: Principal, expiry: u64) {
        self.used.insert(intermediate.clone(), true);
        self.registry_expiry
            .entry(root.clone())
            .or_insert_with(BTreeMap::new)
            .insert(intermediate.clone(), expiry);
        self.intermediate_to_root.insert(intermediate, root);
    }

    fn _unregister(&mut self, root: Principal, intermediate: Principal) {
        self.registry_expiry
            .entry(root.clone())
            .and_modify(|expiry_map| {
                expiry_map.remove(&intermediate);
            });
    }
}
```

## ステート変数
Solidityのcontractのステート変数は、[Stable structures](https://github.com/dfinity/stable-structures)に格納します。

Copilotが下記のようにSolidityのcontractをRustのstructに変換していますが、
```rust
#[derive(Clone, CandidType, Deserialize)]
struct IdentityRegistry {
    registered: BTreeMap<Principal, bool>,
    root_name: BTreeMap<Principal, String>,
    name_to_root: BTreeMap<String, Principal>,
    nonces: BTreeMap<Principal, u64>,
    used: BTreeMap<Principal, bool>,
    intermediate_to_root: BTreeMap<Principal, Principal>,
    registry_expiry: BTreeMap<Principal, BTreeMap<Principal, u64>>,
}
```

これをThreadLocalを使ったStable Structuresを使うように変更します。
先に、`ic-stable-structures`クレートを追加しておきましょう。

```bash
cargo add ic-stable-structures
```

一旦下記のように`struct`を`thread_local!`に変更します。
```rust
thread_local! {
    registered: BTreeMap<Principal, bool>,
    root_name: BTreeMap<Principal, String>,
    name_to_root: BTreeMap<String, Principal>,
    nonces: BTreeMap<Principal, u64>,
    used: BTreeMap<Principal, bool>,
    intermediate_to_root: BTreeMap<Principal, Principal>,
    registry_expiry: BTreeMap<Principal, BTreeMap<Principal, u64>>,
}
```
`root_name`、 `name_to_root`、 `nonces`, `used`は、コードを見る限り必要ないので、削除します。

`registered`、`intermediate_to_root`, `registry_expiry`をStableBTreeMapを使うように書きえます。
```rust
use std::cell::RefCell;

use candid::Principal;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};

thread_local! {
    static MEM_MGR: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static REGISTERED: RefCell<StableBTreeMap<Principal, bool, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(0))))
    );

    static INTERMEDIATE_TO_ROOT: RefCell<StableBTreeMap<Principal, Principal, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(1))))
    );

    static REGISTRY_EXPIRY: RefCell<StableBTreeMap<(Principal, Principal), u64, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(2))))
    );
}
```

init()は必要ないので消しておきます。

## rootとintermediate
このシステムにおける`root`とは、記事を登録する会社です。`root`は、このCanisterの所有者が登録します。

`intermediate`は、実際に記事を登録する編集者です。`intermediate`は、`root`が登録します。

## register_root
`Github Copilot`は`register_root()`を下記のように変換してました。

```rust
#[update]
fn register_root(&mut self, root: Principal, name: String) {
    if root == Principal::anonymous() || name.is_empty() {
        panic!("Invalid parameters");
    }
    if self.used.contains_key(&root) || self.name_to_root.contains_key(&name) {
        panic!("Already registered");
    }
    self.used.insert(root.clone(), true);
    self.registered.insert(root.clone(), true);
    self.root_name.insert(root.clone(), name.clone());
    self.name_to_root.insert(name.clone(), root.clone());
}
```

割と良い線行ってます。`Github Copilot`は、Canister(Ethereumでいうスマートコントラクト)のステートを更新するときは、`update`属性を使うということも知っているようです。

Solidityのerror定義は、`panic!`で置き換えられていますね。本来は、Rustのenumで置き換えたいところですが、できるだけ早くコンパイルエラーを無くしたいので、`panic!`については目をつぶります。

```rust
#[update]
```
を
```rust
#[ic_cdk::update]
```
に修正します。

```rust
fn register_root(&mut self, root: Principal, name: String)
```
を
```rust
fn register_root(root: Principal) {
```
に修正します。

`name_to_root`、`used`、`root_name`は使っていないので、使っている部分を削除します。

`name`も必要ないので、`name`を使っている部分も削除します。

下記のコードになりました。
```rust
#[ic_cdk::update]
fn register_root(root: Principal) {
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if self.used.contains_key(&root) {
        panic!("Already registered");
    }
    self.registered.insert(root.clone(), true);
}
```
`used`は削除したので、
```rust
self.used.contains_key(&root)
```
は
```rust
self.registered.contains_key(&root)
```
に置き換えます。

エラーマークのついているselfにカーソルを当てて、インラインチャットを呼び出し、下記のプロンプトを入力します。
```
/fix REGISTEREDを使うように修正して
```
正しく変換する提案をしてくれたので、Acceptで反映させます。

修正後のコードは下記のようになりました。

```rust
#[ic_cdk::update]
fn register_root(root: Principal) {
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Already registered");
    }
    REGISTERED.with_borrow_mut(|registered| registered.insert(root.clone(), true));
}
```

この関数を呼び出せるのは、Canisterの所有者だけというチェックが未実装ですが、あとで実装することにしましょう。

## unregister_root
`unregister_root()`の修正前のコードは下記の通りです。
```rust
#[update]
fn unregister_root(&mut self, root: Principal) {
    if let Some(name) = self.root_name.get(&root) {
        self.name_to_root.remove(name);
    }
    self.registered.insert(root.clone(), false);
    self.root_name.insert(root.clone(), "".to_string());
}
```
先程と同様、`update属性`を変更し、`引数のself`を削除します。

`root_name`、`name_to_root`を使っている部分を削除します。

`Solidity`の`mapping`と違い、StableBTreeMapでエントリを削除するには、`remove()`を使うので、
```rust
self.registered.insert(root.clone(), false);
```
の`insert`にカーソルを当てて、インラインチャットを起動し、下記のプロンプトを入力します。
```
/fix insertではなくremoveを使って
```
下記の変更が提案されたので、Acceptで受け入れます。
```rust
self.registered.remove(&root);
```
エラーマークのついている`self`にカーソルを当てて、インラインチャットを呼び出し、下記のプロンプトを入力します。
```
/fix REGISTEREDを使って
```
下記の変更が提案されたので、Acceptで受け入れます。
```rust
REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
```

修正後のコードは下記のようになりました。
```rust
#[ic_cdk::update]
fn unregister_root(root: Principal) {
    REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
}
```

この関数を呼び出せるのは、Canisterの所有者だけというチェックが未実装ですが、あとで実装することにしましょう。

また、`intermediate`が登録されている`root`は削除できないというチェックも必要ですが、あとで実装することにしましょう。

## register
`register()`は、`root`が`intermediate`を登録する関数です。登録の時に、`intermediate`の有効期限として`expiry`も登録します。

元の`Verify`では、`register()`のシグネチャは下記の通りです。
```solidity
function register(
        bytes memory signature,
        address root,
        address intermediate,
        uint256 expiry,
        uint256 chainID,
        uint256 deadline
    ) external onlyRegistered(root)
```

元の`Verify`では、この関数を呼び出しているのが、確かに`root`だということを検証するのに、`signature`、`chainID`、`deadline`を使っていますが、ICPでは、`ic_cdk::caller()`で、誰が関数を呼び出しているのか特定できるので、これらの引数は必要ありません。Solidityでも`msg.sender`を使えばいい気がしますが。

`register()`でチェックしなければいけない項目は下記の通りです。
- `ic_cdk::caller()`が`REGISTERED`に登録されていること
- 引数の`expiry`が未来であること
- `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること

上記のチェックがOKなら、`INTERMEDIATE_TO_ROOT`, `REGISTRY_EXPIRY`に登録します。

`Github Copilot`は、`register()`を下記のように変換していました。
```rust
#[update]
fn register(
    &mut self,
    signature: Vec<u8>,
    root: Principal,
    intermediate: Principal,
    expiry: u64,
    chain_id: u64,
    deadline: u64,
) {
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if self.used.contains_key(&intermediate) {
        if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
            if root_identity != &root {
                panic!("Already registered");
            }
            if ic_cdk::api::time() > self.registry_expiry[root_identity][&intermediate] {
                panic!("Registry expired");
            }
        }
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&expiry.to_le_bytes());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._register(root, intermediate, expiry);
}
```

update属性を修正し、引数の`self`、`signature`、`root`、`chain_id`、`deadline`を削除します。関数の中身は全く役に立たないので、削除してしまいましょう。

関数の中身を削除したら、なんと`Gihtub Copilot`が中身を提案してくれました。一部、間違っているけど、Acceptで受け入れます。

まずは、下記のようになりました。かなり良い線行ってます。
```rust
#[ic_cdk::update]
fn register(intermediate: Principal, expiry: u64) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    if INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.contains_key(&intermediate))
    {
        panic!("Already registered");
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.insert(intermediate.clone(), root.clone())
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}
```
`intermediate`が`INTERMEDIATE_TO_ROOT`に登録済みならエラーにする部分が間違っているので、消して、下記のコメントを書きます。
```rust
// `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること
```
下記のコードが提案されました。間違っているけど、一旦受け入れます。
```rust
if let Some(expiry_map) = REGISTRY_EXPIRY.with_borrow(|registry_expiry| registry_expiry.get(&root)) {
        if let Some(expiry) = expiry_map.get(&intermediate) {
            if ic_cdk::api::time() > *expiry {
                panic!("Already registered");
            }
        }
    }
```
下記のように修正します。
```rust
if let Some(expiry) = REGISTRY_EXPIRY
        .with_borrow(|registry_expiry| registry_expiry.get(&(root.clone(), intermediate.clone())))
    {
        if ic_cdk::api::time() > expiry {
            panic!("Intermediate expired");
        }
    }
```
修正後の`register()`は下記のようになりました。
```rust
#[ic_cdk::update]
fn register(intermediate: Principal, expiry: u64) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    // `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること
    if let Some(expiry) = REGISTRY_EXPIRY
        .with_borrow(|registry_expiry| registry_expiry.get(&(root.clone(), intermediate.clone())))
    {
        if ic_cdk::api::time() > expiry {
            panic!("Intermediate expired");
        }
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.insert(intermediate.clone(), root.clone())
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}
```

## unregister
`Github Copilot`は、`unregister()`を下記のように変換していました。
```rust
#[update]
fn unregister(
    &mut self,
    signature: Vec<u8>,
    root: Principal,
    intermediate: Principal,
    chain_id: u64,
    deadline: u64,
) {
    if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
        if root_identity != &root {
            panic!("Invalid parameters");
        }
    }
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._unregister(root, intermediate);
}
```
`register()`と同様に、update属性を修正し、引数の`self`、`signature`、`root`、`chain_id`、`deadline`を削除します。関数の中身は全く役に立たないので、削除してしまいましょう。

期待通りに、`Github Copilot`が関数の中身を提案してくれました。先ほど間違えていた`REGISTRY_EXPIRY`のキーの処理もバッチリです。Acceptで受け入れます。今回は、修正する必要はありません。

修正後の`unregister()`は下記のようになりました。
```rust
#[ic_cdk::update]
fn unregister(intermediate: Principal) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.remove(&intermediate);
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.remove(&(root, intermediate)));
}
```

## who_is
`who_is()`は、`intermediate`の`root`を返す関数です。ただし、有効期限を過ぎている場合は、`None`を返します。

`Github Copilot`は、`who_is()`を下記のように変換していました。
```rust
#[query]
fn who_is(&self, identity: Principal) -> Option<Principal> {
    if let Some(root) = self.intermediate_to_root.get(&identity) {
        if let Some(expiry_map) = self.registry_expiry.get(root) {
            if ic_cdk::api::time() > expiry_map[&identity] {
                return None;
            }
        }
        return Some(root.clone());
    }
    None
}
```
いつものように、query属性を修正し、引数の`self`を削ります。`identity`という引数名は、`intermediate`にリファクタリングしておきましょう。

エラーマークのついているselfにカーソルを当てて、インラインチャットを呼び出し、次のプロンプトを実行します。
```
/fix INTERMEDIATE_TO_ROOTを使って
```
うまく修正できませんでした。`self`を引数に追加しようとしています。`IdentityRegistry`を`struct`として認識しているので、`impl IdentityRegistry`を削って、インラインチャットで`/fix`とだけ入力して修正させてみました。今度は、下記のようにきちんと修正してくれました。
```rust
if let Some(root) = INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))
```

`registry_expiry`も`REGISTRY_EXPIRY`を使うように修正してくれたのですが、キーを`root`と`intermediate`の`tuple`を使うところが、まだ間違えて、`root`のみを使っています。

エラーマークのついている`root`にカーソルを当てて、インラインチャットに`/fix tuple`と入力しました。今度はちゃんと修正してくれました。`expiry_map`の部分は直接修正します。

修正後の`who_is()`は下記のようになりました。
```rust
#[ic_cdk::query]
fn who_is(intermediate: Principal) -> Option<Principal> {
    if let Some(root) = INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))
    {
        if let Some(expiry) = REGISTRY_EXPIRY
            .with_borrow(|registry_expiry| registry_expiry.get(&(root.clone(), intermediate)))
        {
            if ic_cdk::api::time() > expiry {
                return None;
            }
        }
        return Some(root.clone());
    }
    None
}
```

## lib.rsの修正
`lib.rs`の`ic_cdk::export_candid!();`がエラーになっています。`use candid::Principal;`を支えていうメッセージが出ているので、それに従います。

明確に修正箇所がわかっている場合は、わざわざ`Github Copilot`を使う必要はありません。

## clippyさんの警告の修正
全部のエラーが消えたら、`clippy`さんが7つほど警告を出しています。`Principal`は`Copy`トレイトを実装しているので、`clone()`は必要ないとのことです。素直に従います。

これで、警告も全部消えました。

## identity_registry.rs
`identity_registry.rs`の最終的なコードは下記のようになりました。
```rust
use std::cell::RefCell;

use candid::Principal;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};

thread_local! {
    static MEM_MGR: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static REGISTERED: RefCell<StableBTreeMap<Principal, bool, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(0))))
    );

    static INTERMEDIATE_TO_ROOT: RefCell<StableBTreeMap<Principal, Principal, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(1))))
    );

    static REGISTRY_EXPIRY: RefCell<StableBTreeMap<(Principal, Principal), u64, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(2))))
    );
}

#[ic_cdk::update]
fn register_root(root: Principal) {
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Already registered");
    }
    REGISTERED.with_borrow_mut(|registered| registered.insert(root, true));
}

#[ic_cdk::update]
fn unregister_root(root: Principal) {
    REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
}

#[ic_cdk::update]
fn register(intermediate: Principal, expiry: u64) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    // `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること
    if let Some(expiry) =
        REGISTRY_EXPIRY.with_borrow(|registry_expiry| registry_expiry.get(&(root, intermediate)))
    {
        if ic_cdk::api::time() > expiry {
            panic!("Intermediate expired");
        }
    }
    INTERMEDIATE_TO_ROOT
        .with_borrow_mut(|intermediate_to_root| intermediate_to_root.insert(intermediate, root));
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}

#[ic_cdk::update]
fn unregister(intermediate: Principal) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.remove(&intermediate);
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.remove(&(root, intermediate)));
}

#[ic_cdk::query]
fn who_is(intermediate: Principal) -> Option<Principal> {
    if let Some(root) = INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))
    {
        if let Some(expiry) = REGISTRY_EXPIRY
            .with_borrow(|registry_expiry| registry_expiry.get(&(root, intermediate)))
        {
            if ic_cdk::api::time() > expiry {
                return None;
            }
        }
        return Some(root);
    }
    None
}
```

まだ、未実装部分は残っていますが、一旦ここまでとします。

`Github Copilot`にコミットメッセージを作ってもらいpushしておきます。