はじめに
SolidityというEVM系チェーンで使われるスマートコントラクト用のソースコードをICP上で動くRustのコードに変換してみます。
verify-media/contracts
今回の変換対象は、FoxのVerifyというフェイクニュース対策用のスマートコントラクトです。
まず、下記のコマンドでgit clone
します。
git clone https://github.com/verify-media/contracts
contractsフォルダをVS CodeのFile -> Open Folderで開きます。
IdentityRegistry.sol
最初の変換対象はIdentityRegistry.solです。
このファアイルを開きます。
// 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のソースコードに貼り付けます。
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に格納します。
Copilotが下記のようにSolidityのcontractをRustのstructに変換していますが、
#[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
クレートを追加しておきましょう。
cargo add ic-stable-structures
一旦下記のようにstruct
をthread_local!
に変更します。
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を使うように書きえます。
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()
を下記のように変換してました。
#[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!
については目をつぶります。
#[update]
を
#[ic_cdk::update]
に修正します。
fn register_root(&mut self, root: Principal, name: String)
を
fn register_root(root: Principal) {
に修正します。
name_to_root
、used
、root_name
は使っていないので、使っている部分を削除します。
name
も必要ないので、name
を使っている部分も削除します。
下記のコードになりました。
#[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
は削除したので、
self.used.contains_key(&root)
は
self.registered.contains_key(&root)
に置き換えます。
エラーマークのついているselfにカーソルを当てて、インラインチャットを呼び出し、下記のプロンプトを入力します。
/fix REGISTEREDを使うように修正して
正しく変換する提案をしてくれたので、Acceptで反映させます。
修正後のコードは下記のようになりました。
#[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()
の修正前のコードは下記の通りです。
#[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()
を使うので、
self.registered.insert(root.clone(), false);
のinsert
にカーソルを当てて、インラインチャットを起動し、下記のプロンプトを入力します。
/fix insertではなくremoveを使って
下記の変更が提案されたので、Acceptで受け入れます。
self.registered.remove(&root);
エラーマークのついているself
にカーソルを当てて、インラインチャットを呼び出し、下記のプロンプトを入力します。
/fix REGISTEREDを使って
下記の変更が提案されたので、Acceptで受け入れます。
REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
修正後のコードは下記のようになりました。
#[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()
のシグネチャは下記の通りです。
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()
を下記のように変換していました。
#[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で受け入れます。
まずは、下記のようになりました。かなり良い線行ってます。
#[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
に登録済みならエラーにする部分が間違っているので、消して、下記のコメントを書きます。
// `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること
下記のコードが提案されました。間違っているけど、一旦受け入れます。
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");
}
}
}
下記のように修正します。
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()
は下記のようになりました。
#[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()
を下記のように変換していました。
#[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()
は下記のようになりました。
#[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()
を下記のように変換していました。
#[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
とだけ入力して修正させてみました。今度は、下記のようにきちんと修正してくれました。
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()
は下記のようになりました。
#[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
の最終的なコードは下記のようになりました。
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しておきます。