コントラクトへトークンを安全に転送する方法
このチュートリアルでは、コントラクト経由でトークンを扱う方法2つをご紹介していきます。
Casper Networkの本来の手法ではないですし、カスタムコードを使うことになります。以下の2つのシナリオにて、開発者向けのフレームワークと各例題のPros&Consをお伝えします。開発者は、各々のニーズに応じて適切なものを選択してください。
Scenario 1 – Throw-Away パース(お財布)の作成
まず初めのシナリオでは、単独利用でのthrow-away パースの使い方についてとなります。呼び出し元の人(Caller)は、URefを呼び出し先の人(Callee)に渡す前に、個々のメインパースの作成と資金調達をします。
この例題では、スマートコントラクトはパースへのアクセス権をフルで持っており、Callerによる再利用に対するセキュリティ面への懸念を生んでいます。例題ではお伝えしていないが、更には、Callerは使い捨てのパースへのフルアクセス権を取得することが可能です。コントラクトは、問題を避ける為にパースからトークンを取り除き、コントロール下の他のパースへ転送する必要があります。
このシナリオの複雑性は高くはないが、2つ目のシナリオより無駄が多いです。このやり方で作成されたパースは、初期運用後は使用されないまま永遠に残ります。
Casper Mainnet上では、パースの作成にも2.5 CSPRが掛かります。
#[no_mangle]
pub extern "C" fn call() {
let amount: U512 = runtime::get_named_arg("amount");
// This is demonstrating the most direct case, wherein you pass in the contract_hash and
// the entry_point_name of the target contract as args.
// With prior setup having been done, this can also be simplified.
let contract_hash = runtime::get_named_arg("contract_hash");
let entry_point_name = runtime::get_named_arg("entry_point_name");
// This creates a new empty purse that the caller will use just this one time.
let new_purse = system::create_purse();
// Transfer from the caller's main purse to the new purse that was just created.
// Note that transfer is done safely by the host logic.
system::transfer_from_purse_to_purse(account::get_main_purse(), new_purse, amount, None)
.unwrap_or_revert();
// Pass the newly created purse to the smart contract with full access rights;
// the called contract should receive the new purse, extract the token from it, and then do
// whatever else it is meant to do if a valid amount was transferred to it. Note that the
// caller's main purse is never exposed to the called contract; the newly created purse
// is provided instead.
runtime::call_contract(contract_hash, entry_point_name, runtime_args! {
// The arg names are defined by the contract that you are calling,
// there is no canonical name. The contract you are calling may have other
// runtime args that it requires.
"????" => new_purse
});
}Scenario 1 – 高度なバリエーション
このシナリオの高度なバリエーションは、例題特有の無駄を減らしてくれます。Callerが名前付きパースを個々のメインパースに作成する場合、コントラクトと紐づけることができます。この方法は、同じパースをコントラクトへの資金調達に繰り返し使用できます。
この例では、その考えに基づいたフレームワークを提供しているが、開発者の用途に応じて修正は必要となるでしょう。
Scenario 2 – コントラクトのロジック内でのパース再利用の保持
2つ目のシナリオは、パースの再利用を可能にする内部ロジックがより複雑になります。コントラクト自体は、内部のブックキーパーとしてCallerに紐づいたパースのトラッキングをしています。
シナリオ1では、新しく作成されたパースはCallerからCalleeへのトークン転送を純粋に意味するものでした。それに反して、シナリオ2では、Callerのアドレスに紐づいた内部のパースを保持するものでした。シナリオ1の高度なバリエーションとの違いは、問題のパースがCallerではなくコントロール下のコントラクトであるところです。
シナリオ2は、コントラクトへのトークン転送の無駄の少ない手段を提供しますが、内部の複雑性は増すことになります。この2つのシナリオから選択する際、プロジェクトのスコープとニーズを評価した上で選択する必要があります。
// Scenario 2: with this style, the contract being called has some internal accounting
// to keep track of a reusable purse associated to the calling account; this avoids
// wasteful creation of one time purses but requires the smart contract being called
// to have more sophisticated internal logic.
#[no_mangle]
pub extern "C" fn call() {
let amount: U512 = runtime::get_named_arg("amount");
// This is demonstrating the most direct case, wherein you pass in the contract_hash and
// the entry_point_names of the target contract as args.
// With prior setup having been done, this can also be simplified.
let contract_hash = runtime::get_named_arg("contract_hash");
// the name of the entry point on the contract that returns a purse uref to receive token at
// the actual name of the entry point is up to the smart contract authors
let deposit_point_name = runtime::get_named_arg("deposit_point_name");
// whatever entry point on the smart contract does the actual work if token has been transferred
// the actual name of which is up to the smart contract authors.
let other_entry_point_name = runtime::get_named_arg("other_entry_point_name");
// The smart contract returns a purse URef of a deposit purse (with ADD access rights only)
// for the caller to transfer to.
let deposit_purse: URef = runtime::call_contract(contract_hash, deposit_point_name, runtime_args! {});
// transfer from the caller's purse to the purse provided by the contract; the transfer is handled
// safely by the host and the caller's purse is never exposed to the called smart contract.
system::transfer_from_purse_to_purse(account::get_main_purse(), deposit_purse, amount, None)
.unwrap_or_revert();
// The contract being interacted with looks up the associated purse, checks its balance, etc.
// within its logic. That side of it is entirely up to the smart contract authors to code; the caller
// merely calls the logic. Also, the entry point might require one or more runtime arguments.
// In all cases some discovery of the API of the contract you are calling is necessary.
runtime::call_contract(contract_hash, other_entry_point_name, runtime_args! {});
}Scenario 2 – 高度なバリエーション
シナリオ2では、質問のコントラクトがそれぞれの紐づいたCallerのパースを維持しています。高度なバリエーションが、各Callerの残高を記録する内部Ledgerを構築します。コントラクトは、dictionary(辞書)アイテムとして各Callerの情報を記録し、それに応じて対応することができます。この様にして、1つのパースがコントラクトへアクセスする全てのCallerのモーツを保存することができます。
この構造であれば、コントラクトの内部会計処理を提供となりますが、はるかに高い複雑性が初期設定時に求められます。