cross-contract-callを実装しよう
⚔️ cross contract call
を実装しよう
これまでのレッスンの中で
アカウントが他のアカウントにftを転送するにはftコントラクト
のメソッドを呼び出せばよいことがわかりました。
そして本プロジェクトではいくつか必要な機能がまだ残っています。
そのうちの1つが
bikeコントラクト
からバイクの点検をしてくれたユーザへ報酬として ft を支払う
という機能です。
この場合の処理の流れを整理します。
- バイクを点検中のユーザがバイクの返却を
bikeコントラクト
に申請する。 bikeコントラクト
はユーザのアカウントIDを照合し報酬としてftをユーザへ転送。bikeコントラクト
はftの転送後、バイクの返却手続きを進める。
これを実現するには以下の処理を同期的に行う必要があります。
- ユーザが
bikeコントラクト
のバイク返却メソッドを呼ぶ。 bikeコントラクト
がftコントラクト
のft転送メソッドを呼ぶ。- ft の転送が成功していれば
bikeコントラクト
の返却処理を進める。
cross contract call
とcallback
関数という機能を使用してこの機能を実装します。
cross contract call
はあるコントラクトから別のコントラクトのメソッドを呼び出す仕組みです。
callback
関数はcross contract call
の結果に対応した処理を行う際に使用します。
まずはコントラクト内に下記のコードを追加してください!
// lib.rs
use near_sdk::{
borsh::{self, BorshDeserialize, BorshSerialize},
env, ext_contract, log, near_bindgen, AccountId, Gas, Promise, PromiseResult,
};
const FT_CONTRACT_ACCOUNT: &str = "sub.ft_account.testnet"; // <- あなたのftコントラクトをデプロイしたアカウントに変更してください!
const AMOUNT_REWARD_FOR_INSPECTIONS: u128 = 15;
/// 外部コントラクト(ftコントラクト)に実装されているメソッドをトレイトで定義
#[ext_contract(ext_ft)]
trait FungibleToken {
fn ft_transfer(&mut self, receiver_id: String, amount: String, memo: Option<String>);
}
const DEFAULT_NUM_OF_BIKES: usize = 5;
/// バイクの状態遷移を表します。
// ...
/// コントラクトを定義します
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct Contract {
bikes: Vec<Bike>,
}
/// デフォルト処理を定義します。
// ...
/// メソッドの実装です。
#[near_bindgen]
impl Contract {
// ...
pub fn inspect_bike(&mut self, index: usize) {
// ...
}
// バイク 使用中or点検中 -> 使用可
pub fn return_bike(&mut self, index: usize) {
let user_id = env::predecessor_account_id();
log!("{} returns bike", &user_id);
match &self.bikes[index] {
Bike::Available => panic!("Bike is already available"),
Bike::InUse(user) => {
assert_eq!(user.clone(), user_id, "Fail due to wrong account");
self.bikes[index] = Bike::Available
}
Bike::Inspection(inspector) => {
assert_eq!(inspector.clone(), user_id, "Fail due to wrong account");
Self::return_inspected_bike(index); // <- 関数実行に変更!
}
};
}
/// 点検中から返却に変更する際の挙動を定義します。
/// 点検をしてくれたユーザに報酬(ft)を支払い、コールバックで返却処理をします。
pub fn return_inspected_bike(index: usize) -> Promise {
let contract_id = FT_CONTRACT_ACCOUNT.parse().unwrap();
let amount = AMOUNT_REWARD_FOR_INSPECTIONS.to_string();
let receiver_id = env::predecessor_account_id().to_string();
log!(
"{} transfer to {}: {} FT",
env::current_account_id(),
&receiver_id,
&amount
);
// cross contract call (contract_idのft_transfer()メソッドを呼び出す)
ext_ft::ext(contract_id)
.with_attached_deposit(1)
.ft_transfer(receiver_id, amount, None)
.then(
// callback (自身のcallback_return_bike()メソッドを呼び出す)
Self::ext(env::current_account_id())
.with_static_gas(Gas(3_000_000_000_000))
.callback_return_bike(index),
)
}
/// cross contract call の結果を元に処理を条件分岐します。
// #[private]: predecessor(このメソッドを呼び出しているアカウント)とcurrent_account(このコントラクトのアカウント)が同じことをチェックするマクロです.
// callbackの場合、コントラクトが自身のメソッドを呼び出すことを期待しています.
#[private]
pub fn callback_return_bike(&mut self, index: usize) {
assert_eq!(env::promise_results_count(), 1, "This is a callback method");
match env::promise_result(0) {
PromiseResult::NotReady => unreachable!(),
PromiseResult::Failed => panic!("Fail cross-contract call"),
// 成功時のみBikeを返却(使用可能に変更)
PromiseResult::Successful(_) => self.bikes[index] = Bike::Available,
}
}
}
// ...
変更点を見ていきましょう。
cross contract call
を使用するには、呼び出す外部コントラクトのメソッドをトレイトで定義しておきます。
今回はftコントラクトのft_transfer
メソッドを呼び出すので