コントラクトへトークンを安全に転送する方法

このチュートリアルでは、コントラクト経由でトークンを扱う方法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のモーツを保存することができます。

この構造であれば、コントラクトの内部会計処理を提供となりますが、はるかに高い複雑性が初期設定時に求められます。

      次ページ