- Go back to TOP(インデックスページへ)
- Flow Network Token
- Fungible Tokens on the Flow Emulator
- Decentralizing Ownership
- Intuiting Ownership with Resources
- Interacting with the Fungible Token in the Flow Playground
- Ensuring Security in Public: Capability Security
- Adding Interfaces to Our Fungible Token
- Create, Store, and Publish Capabilities and References to a Vault
このチュートリアルでは、fungible トークンのデプロイ、保存、転送を行っていきます。
💡 TIP
このチュートリアルのスターターコードを Flow Playground で開きます。
https://play.flow.com/65b44962-32c8-49c4-8a69-e96475d5a780
チュートリアルでは、このコードとやり取りするためにさまざまな操作を行うよう求められます。
ⓘ ACTION
ユーザーにアクションを要求する指示は、常にこのような吹き出しボックス内に記載されています。ハイライトされたアクションは、コードを実行するために必要なこと全てですが、言語の設計を理解するためには、残りの部分を読むことも必要です。
今日、ブロックチェーン上で最も人気のあるスマートコントラクト クラスは、fungible tokens(交換可能なトークン)です。これらのスマートコントラクトは、他のユーザーに譲渡でき、通貨として使用できる同種のトークンを作成します(例えば、イーサリアムのERC-20)。
従来のソフトウェアおよびスマートコントラクトでは、各ユーザーの残高は、ディクショナリのような中央台帳(central ledger)によって追跡されます。
// DO NOT USE THIS CODE FOR YOUR PROJECT
contract LedgerToken {
// Tracks every user's balance
access(contract) let balances: {Address: UFix64}
// Transfer tokens from one user to the other
// by updating their balances in the central ledger
access(all)
fun transfer(from: Address, to: Address, amount: UFix64) {
balances[from] = balances[from] - amount
balances[to] = balances[to] + amount
}
}
Cadenceでは、新しいリソース指向のパラダイムを使用して代替可能なトークンを実装し、中央台帳の使用を避けています。なぜなら、中央台帳を使用することには固有の問題があり、詳細は以下のthe Fungible Tokensのセクションで説明しています。
Flow Network Token
Flowでは、ネイティブネットワークトークン(FLOW)は、このチュートリアルで使用されているものと同様のスマートコントラクトを使用して、通常のfungibleトークンのスマートコントラクトとして実装されています。
特別なトランザクションとhooksがあり、トランザクション実行手数料、ストレージ手数料、ステーキングに使用することができますが、それ以外は、開発者やユーザーは、ネットワーク上の他のトークンと同様に扱うことができます。
⚠️ WARNING
このチュートリアルでは、実際に機能するfungibleトークンを実装していますが、教育目的のために簡略化されており、実際のプロジェクトで使用すべきものではないことを覚えておくことが重要です。標準インターフェースと実装例については、Flow Fungible Tokenスタンダードを参照してください。また、fungibleトークンスマートコントラクトの実用版を作成する方法については、Fungible Token開発者ガイドを参照してください。
fungible token に慣れていただくために、以下の手順を実行してみましょう。
- fungible token スマートコントラクトをアカウント
0x06
にデプロイします。 - fungible token オブジェクトを作成し、アカウントのストレージに保存します。
- 他のユーザーがトークンを送信できるように、トークンへの参照(reference)を作成します。
- 同様の方法で、もう1つのアカウントを設定します。
- トークンを1つのアカウントから別のアカウントに転送します。
- スクリプト(Script)を使用して、アカウントの残高を読み取ります。
このチュートリアルを進める前に、言語の基本とプレイグラウンド(playground)を学ぶために、Getting StartedとHello, World!の手順に従うことをお勧めします。
Fungible Tokens on the Flow Emulator
ⓘ ACTION
まず、このリンクを開きFungibleトークンスマートコントラクト、トランザクション、スクリプトが事前にロードされたプレイグラウンド(playground)を開きます。
https://play.flow.com/65b44962-32c8-49c4-8a69-e96475d5a780
ⓘ ACTION
アカウント0x06
タブを開くと、BasicToken.cdc
というファイルが表示されます。BasicToken.cdc
には、アカウントにfungibleトークンを保存し、他のユーザーにトークンを転送したり、他のユーザーからのトークンを受け取ったりするためのコア機能を提供するfungibleトークンの完全なコードが含まれています。
Cadenceで代替トークン(fungible token)を実装する際に必要となる概念は、最初は理解しにくいかもしれません。前のチュートリアルを完了しておらず、理解していない場合は、先にそちらを完了させてください。このチュートリアルでは、それらのチュートリアルで説明されている概念の多くを理解していることを前提としています。
この機能とコードの詳細については、次のセクションをお読みください。
または、すぐにデプロイしてプレイグラウンド(playground)で使用したい場合は、このチュートリアルの「Interacting with the Fungible Token」セクションから読み進めてください。
Fungible Tokens: An In-Depth Exploration
Flowが代替トークン(fungible token)を実装する方法は、他のプログラミング言語とは異なります。その結果、
- オーナーシップは分散型とされ、中央台帳に依存しません
- バグや悪用によるユーザーへのリスクや攻撃者への機会が減少します
- 整数アンダーフローやオーバーフローのリスクがありません
- 資産の重複は不可能であり、紛失、盗難、破棄される可能性は非常に低いです
- コードは構成可能(composable)です
- ルールは不変です
- コードが意図せず公開されることはありません
Decentralizing Ownership
中央台帳システムを使用する代わりに、Flowは資産所有の新しいパラダイムを通じて、各アカウントに所有権(オーナーシップ)を結び付けます。以下の例では、Solidity(Ethereumブロックチェーン用のスマートコントラクト言語)が代替可能なトークン(fungible token)を実装する方法を示しています。簡潔にするため、トークンの保存と転送用のコードのみを示しています。
contract ERC20 {
// Maps user addresses to balances, similar to a dictionary in Cadence
mapping (address => uint256) private _balances;
function _transfer(address sender, address recipient, uint256 amount) {
// ensure the sender has a valid balance
require(_balances[sender] >= amount);
// subtract the amount from the senders ledger balance
_balances[sender] = _balances[sender] - amount;
// add the amount to the recipient’s ledger balance
_balances[recipient] = _balances[recipient] + amount
}
}
ご覧の通り、Solidityでは、交換可能なトークンに対して中央台帳システムを使用しています。トークンの状態を管理するスマートコントラクトが1つあり、ユーザーがトークンに対して何らかの操作を行いたい場合は、必ずERC20の中央コントラクトとやりとりを行い、その機能を利用して残高を更新する必要があります。このスマートコントラクトは、すべての機能に対するアクセス制御を処理し、独自の正確性チェックをすべて実装し、すべてのユーザーに対するルールを強制します。
中央集権型の台帳システムを使用する代わりに、Flowはスマートコントラクトの開発者やユーザーに、より高い安全性、セキュリティ、明瞭性を提供するために、いくつかの異なるコンセプトを活用しています。このセクションでは、代替可能なトークンの例を通じて、Flowのリソース、インターフェース、その他の機能がどのように使用されているかを示します。
Intuiting Ownership with Resources
Cadenceにおける重要な概念はResourcesであり、これLinear Type(リニアタイプ)す。リソース(resource)は、独自の定義済みフィールドと関数を持つ複合型(構造体のような)です。リソースオブジェクトには、コピーや損失(lost)を防ぐ特別なルールがある点が異なります。リソースは、資産所有の新しいパラダイムです。トークン所有権を中央台帳スマートコントラクトで表すのではなく、各アカウントは、アカウントストレージ(account storage)にトークンの所有数を記録しているリソース(resource)オブジェクトを所有します。これにより、ユーザー同士が取引(トランザクション)を行いたい場合、中央のトークンスマートコントラクトとやり取りすることなく、ピアツーピアで取引を行うことができます。トークンを互いに移転するには、中央のtransfer
関数ではなく、自身のリソースオブジェクトと他のユーザーのリソースに対してtransfer
関数(または同等のもの)を呼び出します。
(補足: 各アカウントはストレージを保有しており、その中にリソースというスマートコントラクトによって生成されたインスタンスを所有します。そこからスマートコントラクトの関数を呼ぶのですが、実際にはインスタンスの関数なのでストレージ内のリソースを通じて呼び出していることになります)
このアプローチでは、アクセス制御が簡素化されます。なぜなら、中央のスマートコントラクトが関数呼び出しの送信者をチェックする必要がないため、ほとんどの関数呼び出しはユーザーのアカウントに保存されたリソースオブジェクト上で実行され、各ユーザーは自分のアカウントのリソース上で関数を呼び出せるユーザーを制御できるからです。この概念は「Capabilityベースのセキュリティ」と呼ばれ、後のセクションで詳しく説明します。
このアプローチは潜在的なバグからの保護にも役立ちます。Solidityスマートコントラクトにすべてのロジックとステートが含まれている場合、脆弱性が存在すると、すべてのユーザが影響を受ける可能性が高いです。
Cadenceでは、リソース(resource)ロジックにバグがある場合、攻撃者は各トークンホルダーのアカウントで個別にバグを悪用する必要があり、中央台帳システムよりもはるかに複雑で時間がかかります。
以下は、fungible token vaultのリソースの例です。これらのトークンを所有するすべてのユーザーは、このリソースをアカウントに保存することになります。各アカウントには、Vault
リソースのコピーのみ(補足: つまり、クラスから生成されたインスタンスです。)が保存され、ExampleToken
スマートコントラクトのコピーは保存されないことに留意することが重要です。ExampleToken
スマートコントラクトは、トークンの定義を管理する初期アカウント(補足: =管理者アカウント)にのみ保存する必要があります。
access(all)
resource Vault: Provider, Receiver {
// Balance of a user's Vault
// we use unsigned fixed point numbers for balances
// because they can represent decimals and do not allow negative values
access(all) var balance: UFix64
init(balance: UFix64) {
self.balance = balance
}
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
self.balance = self.balance - amount
return <-create Vault(balance: amount)
}
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
}
このコードは教育目的であり、包括的なものではありません。しかし、トークン用のリソースがどのように機能するかを示しています。
Token Balances and Initialization
トークンリソースオブジェクトにはそれぞれ残高があり、関連する機能(deposit
、withdraw
など)があります。ユーザーがこれらのトークンを使用したい場合、アカウントストレージにこのリソースの残高ゼロのインスタンスを保存します。言語では、初期化関数init
は一度だけ実行され、すべてのメンバー変数を初期化する必要があります。
// Balance of a user's Vault
// we use unsigned fixed-point integers for balances because they do not require the
// concept of a negative number and allow for more clear precision
access(all) var balance: UFix64
init(balance: UFix64) {
self.balance = balance
}
init
関数をExampleToken
スマートコントラクトから削除すると、残高(balance)フィールドが初期化されなくなるため、エラーが発生します。
Deposit
(init処理を実行)すれば、トークンを転送する先としての任意アカウント上でdeposit関数が利用可能になります。
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
Transferring Tokens
アカウントがトークンを別のアカウントに送信したい場合、送信元のアカウントはまず自身のwithdraw関数を呼び出します。これにより、リソースの残高からトークンが差し引かれ、この残高(balance)を保持する新しいリソースオブジェクトが一時的に作成されます。
// Withdraw tokens from the signer's stored vault
let sentVault <- vaultRef.withdraw(amount: amount)
送金側アカウントは次に、受取側アカウントのdeposit関数を呼び出します。これは文字通り、リソースインスタンスを他方のアカウントに移動し、残高に追加し、使用済みのリソースを破棄します。
// Deposit the withdrawn tokens in the recipient's receiver
receiverRef.deposit(from: <-sentVault)
リソースは破棄する必要があります。これは、Cadenceがリソースのやり取りに関して
(Linear Typeによる)厳格なルールを適用しているためです。リソースをコード内に残しておくことはできません。明示的に破棄するか、アカウントのストレージに保存する必要があります。
リソースをやり取りする際には、@
記号を使用して型を指定し、リソースを移動する場合には、特別な「移動演算子」<-
を使用します。
access(all) fun withdraw(amount: UInt64): @Vault {
この@
記号は、フィールド、引数、または戻り値のリソース**タイプ(型)を指定する際に必要となります。移動演算子<-
は、リソースが変数等に割り当て(assignment)*、関数のパラメータ、または戻り値に使用される場合、新しい場所に移動され、古い場所の値は無効になることを明確にします。これにより、リソースは常に1つの場所にのみ存在することが保証されます。
リソースがアカウントのストレージから移動された場合、アカウントのストレージに移動するか、明示的に破棄する必要があります。
destroy from
このルールにより、実質的な価値を持つことが多いリソースが、コーディングエラーによって失われることがないようにしています。
算術演算が実際にはオーバーフローやアンダーフローに対して明示的に保護されていないことに気づくでしょう。
self.balance = self.balance - amount
Solidityでは、これは整数オーバーフローまたはアンダーフローのリスクとなり得ますが、Cadenceにはオーバーフローおよびアンダーフローの保護機能が組み込まれているため、リスクにはなりません。また、この例では符号なし数値を使用しているため、前述の通り、Vaultの残高が0を下回ることはありません。
さらに、トークンのリソース型のインスタンスをアカウントのストレージに含んでいるという要件により、資金が誤ったアドレスに送信されて失われることがないようになっています。
アドレス(=アカウント)に正しいリソース型がインポートされていない(保存されていない)場合、トランザクションは取り消され、誤ったアドレスに送信されるトランザクションによって失われることはありません。
重要:この保護機能は、Flowネットワーク通貨には適用されません。なぜなら、Flowアカウントはすべて、ストレージ料金と取引手数料の支払いのために、デフォルトのFlowトークンvaultで初期化されているからです。(補足: Flowでは全てのアカウントが生成時からFlow Tokenのインスタンスを保有しています。なぜならそのトークンを使ってトランザクション費用を支払うからです)
Function Parameters
withdraw
の行で新しいVault
(トークンの保管庫)を作成する場合、関数呼び出し時にパラメータbalance
を指定します。
return <-create Vault(balance: amount)
これも、Cadenceがコードの明瞭性を向上させるために使用する機能です。開発者が関数宣言時に要件を特に上書きしていない限り、すべての関数呼び出しでは、送信する引数の名前を指定する必要があります。
Interacting with the Fungible Token in the Flow Playground
fungibleトークンの仕組みについてお読みいただいたので、あなたのアカウントにそのベーシックバージョンをデプロイし、いくつかのトランザクションを送信して、それ(fungible token)とやり取りすることができます。
ⓘ ACTION
このページの上部にあるリンクから、fungibleトークンのテンプレートをプレイグラウンド(playground)で開いていることを確認してください。Account0x06
が開かれており、以下のコードが表示されているはずです。
/// BasicToken.cdc
///
/// The BasicToken contract is a sample implementation of a fungible token on Flow.
///
/// Fungible tokens behave like everyday currencies -- they can be minted, transferred or
/// traded for digital goods.
///
/// This is a basic implementation of a Fungible Token and is NOT meant to be used in production
/// See the Flow Fungible Token standard for real examples: https://github.com/onflow/flow-ft
access(all) contract BasicToken {
access(all) entitlement Withdraw
access(all) let VaultStoragePath: StoragePath
access(all) let VaultPublicPath: PublicPath
/// Vault
///
/// Each user stores an instance of only the Vault in their storage
/// The functions in the Vault are governed by the pre and post conditions
/// in the interfaces when they are called.
/// The checks happen at runtime whenever a function is called.
///
/// Resources can only be created in the context of the contract that they
/// are defined in, so there is no way for a malicious user to create Vaults
/// out of thin air. A special Minter resource or constructor function needs to be defined to mint
/// new tokens.
///
access(all) resource Vault {
/// keeps track of the total balance of the account's tokens
access(all) var balance: UFix64
/// initialize the balance at resource creation time
init(balance: UFix64) {
self.balance = balance
}
/// withdraw
///
/// Function that takes an integer amount as an argument
/// and withdraws that amount from the Vault.
///
/// It creates a new temporary Vault that is used to hold
/// the money that is being transferred. It returns the newly
/// created Vault to the context that called so it can be deposited
/// elsewhere.
///
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
pre {
self.balance >= amount:
"BasicToken.Vault.withdraw: Cannot withdraw tokens! "
.concat("The amount requested to be withdrawn (").concat(amount.toString())
.concat(") is greater than the balance of the Vault (")
.concat(self.balance.toString()).concat(").")
}
self.balance = self.balance - amount
return <-create Vault(balance: amount)
}
/// deposit
///
/// Function that takes a Vault object as an argument and adds
/// its balance to the balance of the owners Vault.
///
/// It is allowed to destroy the sent Vault because the Vault
/// was a temporary holder of the tokens. The Vault's balance has
/// been consumed and therefore can be destroyed.
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
}
/// createVault
///
/// Function that creates a new Vault with an initial balance
/// and returns it to the calling context. A user must call this function
/// and store the returned Vault in their storage in order to allow their
/// account to be able to receive deposits of this token type.
///
access(all) fun createVault(): @Vault {
return <-create Vault(balance: 30.0)
}
/// The init function for the contract. All fields in the contract must
/// be initialized at deployment. This is just an example of what
/// an implementation could do in the init function. The numbers are arbitrary.
init() {
self.VaultStoragePath = /storage/CadenceFungibleTokenTutorialVault
self.VaultPublicPath = /public/CadenceFungibleTokenTutorialReceiver
// create the Vault with the initial balance and put it in storage
// account.save saves an object to the specified `to` path
// The path is a literal path that consists of a domain and identifier
// The domain must be `storage`, `private`, or `public`
// the identifier can be any name
let vault <- self.createVault()
self.account.storage.save(<-vault, to: self.VaultStoragePath)
}
}
ⓘ ACTION
(Playgroundの)エディタの右上にあるdeployボタンをクリックしてコードをデプロイします。
このデプロイ実施では、基本的なfungibleトークンのスマートコントラクトを、選択したアカウント(アカウント0x06
)に保存します。これにより、そのスマートコントラクトをトランザクション実施時にインポートできるようになります。
スマートコントラクトのinit
関数は、スマートコントラクトが作成されたときに実行され、その後は決して実行されることはありません。この例では、この関数はVault
オブジェクトのインスタンスを初期残高(initial balance)30で保存しています。
// create the Vault with the initial balance and put it in storage
// account.save saves an object to the specified `to` path
// The path is a literal path that consists of a domain and identifier
// The domain must be `storage` or `public`
// the identifier can be any string
let vault <- self.createVault()
self.account.save(<-vault, to: self.VaultStoragePath)
この行は、新しい@Vault
オブジェクトをストレージに保存します。アカウントのストレージは、ドメインと識別子からなるpathでインデックス化されます。/domain/identifier
。パス名には2つのドメインのみ使用できます。
storage
: すべてのオブジェクトが保存される場所。アカウントの所有者のみがアクセスできます。
public
: ストレージ内のオブジェクトへのリンクを保存します。ネットワーク上の誰でもアクセスできます。
スマートコントラクトは、デプロイ先のアカウントのself.account
を使用し、プライベートな&Account
オブジェクトにアクセスできます。このオブジェクトには、ストレージをさまざまな方法で変更できるメソッドがあります。呼び出し可能なすべてのメソッドの一覧については、アカウントのドキュメントを参照してください。
この行では、storage.save
メソッドを呼び出してリソースのインスタンスをストレージに保存します。
最初の引数は保存する値で、2番目の引数は値を保存する先のパスです。storage.save
の場合は、パスは/storage/
ドメインでなければなりません。
これで、交換可能なトークン(fungible token)を使うトランザクションを実行する準備ができました!
Perform a Basic Transfer
前述の通り、トークンをリソースとともに転送することは、単なる台帳の更新ではありません。Cadenceでは、まずトークンを保管庫から引き出し、次に転送先の保管庫に預け入れなければなりません。ここでは、トークンを保管庫から引き出し、同じ保管庫に預け入れるという単純なトランザクションを開始します。
ⓘ ACTION
Basic Transfer
という名前のトランザクションを開きます。
Basic Transfer
には、保管された保管庫からの引き出しと預け入れを行うための以下のコードが含まれているはずです。
// Basic Transfer
import BasicToken from 0x06
// This transaction is used to withdraw and deposit tokens with a Vault
transaction(amount: UFix64) {
prepare(signer: auth(BorrowValue) &Account) {
// Get a reference to the signer's stored vault
let vaultRef = signer.storage.borrow<auth(BasicToken.Withdraw) &BasicToken.Vault>
(from: BasicToken.VaultStoragePath)
?? panic("Could not borrow a vault reference to 0x06's BasicToken.Vault"
.concat(" from the path ")
.concat(BasicToken.VaultStoragePath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an BasicToken Vault."))
// Withdraw tokens from the signer's stored vault
sentVault <- vaultRef.withdraw(amount: amount)
// Deposit the withdrawn tokens in the recipient's receiver
vaultRef.deposit(from: <-sentVault)
log("Withdraw/Deposit succeeded!")
}
}
ⓘ ACTION
アカウント0x06
のみを署名者として選択します。
転送するトークンの数量には、30.0未満の任意の数値を入力できます。
Send
ボタンをクリックして、トランザクションを送信します。
このトランザクションでは、main vaultからトークンをwithdrawし、それを再びmain vaultにdepositします。
このトランザクションは、アカウント内での送金の基本的な例です。トークンをメインの保管庫(Vault)から引き出し、メインの保管庫(Vault)に戻します。これは、送金がどのように機能するのかという基本的な機能を説明するためのものです。
このトランザクションでは、ストレージ内のオブジェクトから参照を直接借用できることがわかります。
// Borrow a Withdraw reference to the signer's vault
// Remember to always have descriptive error messages!
let vaultRef = signer.storage.borrow<auth(BasicToken.Withdraw) &BasicToken.Vault>
(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow a vault reference to 0x06's BasicToken.Vault"
.concat(" from the path ")
.concat(BasicToken.VaultStoragePath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an BasicToken Vault."))
これにより、よりコストのかかるやりとりであるオブジェクトのロードを行うことなく、ストレージ内のオブジェクトに効率的にアクセスすることができます。
このコードでは、参照を通じてwithdraw機能にアクセスするために、エンタイトルメント(auth(BasicToken.Withdraw)
)も使用しています。エンタイトルメントがなければ、参照は具体的な参照型にダウンスローディングできるため、特権機能はすべてパブリック機能を通じてアクセス可能になります。したがって、特権機能を持つ関数(例えば、withdraw()
)は、安全を確保するためにエンタイトルメントを持つ必要があります。
本番コードでは、おそらくトークンを他のアカウントに転送することになるでしょう。 Capabilityにより、これを安全に実行することができます。
Ensuring Security in Public: Capability Security
Cadenceのもう一つの重要な特徴は、Capabilityベースのセキュリティ(Capability-Based Security)を採用していることです。
Cadenceのセキュリティモデルでは、アカウントのストレージに保存されたオブジェクトは、そのオブジェクトを所有するアカウントのみがアクセスできることを保証します。ユーザーが保存したオブジェクトへのアクセスを他のユーザーに許可したい場合、ユーザーは「API」のような公開Capabilityをリンクすることができます。これにより、他のユーザーはユーザーのオブジェクト上の指定された関数を呼び出すことができます。
アカウントは、明示的にそれらのフィールドやメソッドへのアクセスを許可するオブジェクトに対するCapabilityを保持している場合のみ、異なるアカウントのオブジェクトのフィールドやメソッドにアクセスできます。
オブジェクトの所有者のみが、そのオブジェクトに対するCapabilityを作成でき、また、所有者のみがCapabilityに権限(エンタイトルメント)を追加できます。
したがって、ユーザーが自分のアカウントでVaultを作成すると、リソース上のaccess(all)
で宣言されたフィールドと関数を公開するCapabilityが保存されます。ここでは、それらはbalance
およびdeposit()
です。
withdraw関数は、所有者だけが呼び出せる関数として非公開にしておくことができます。
これにより、アクセス制御の目的で関数呼び出しを行ったアカウントのアドレス(Ethereumではmsg.sender
)を確認する必要がなくなります。なぜなら、この機能はプロトコルと言語の強力な静的型システムによって処理されるからです。オブジェクトの所有者でない場合、またはその所有者によって作成された有効な参照を持っていない場合は、オブジェクトに一切アクセスできません。
Using Pre and Post-Conditions to Secure Implementations
Cadenceにおける次の重要な概念は、設計によるコントラクト(design-by-contract)でありこれは、プログラムによって状態(state)が変化したことをプログラム的に警告するために、事前条件と事後条件を使用します。これらの条件は通常、型が定義されたり、動作する方法に関してルールを強制したりするインターフェースで指定されます。
この例では、単純化のためインターフェースは使用していませんが、上記のVault
リソース用のインターフェースの例を以下に示します。
// Interface that enforces the requirements for withdrawing
// tokens from the implementing type
//
access(all) resource interface Provider {
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
post {
// `result` refers to the return value
result.balance == amount:
"FungibleToken.Provider.withdraw: Cannot withdraw tokens!"
.concat("The balance of the withdrawn tokens (").concat(result.balance.toString())
.concat(") is not equal to the amount requested to be withdrawn (")
.concat(amount.toString()).concat(")")
}
}
}
// Interface that enforces the requirements for depositing
// tokens into the implementing type
//
access(all) resource interface Receiver {
// There aren't any meaningful requirements for only a deposit function
// but this still shows that the deposit function is required in an implementation.
access(all) fun deposit(from: @Vault)
}
// Balance
//
// Interface that specifies a public `balance` field for the vault
//
access(all) resource interface Balance {
access(all) var balance: UFix64
}
本番コードでは、Vault
リソースがこれら3つのインターフェースすべてを実装します。これらのインターフェースにより、特定のフィールドと関数がリソースの実装に存在し、関数の引数、リソースのフィールド、およびすべての戻り値が実行前および/または実行後に有効な状態であることが保証されます。
これらのインターフェースはオンチェーンにデプロイすれば、他のコントラクトやリソースにインポートされることができ、これらの要件が、ヒューマンエラーの影響を受けない不変の真実のソースによって強制されるようにします(enforced by an immutable source of truth that is not susceptible to human error)。
実際のfungibleトークン実装で使用されるインターフェースについては、Flow Fungible Token standardを参照してください。
Adding Interfaces to Our Fungible Token
次に、これらのインターフェースを、minterリソースとともにFungibleトークンに追加します。
ExampleToken
スマートコントラクトを開きます。 BasicToken
スマートコントラクトに含まれているものに加えて、Provider
、Receiver
、およびBalance
インターフェースをインターフェースを私たちが追加しました。
ExampleToken.Vault
型はこれらのインターフェースを実装することを宣言しており、それらのフィールドと関数が必要となります。また、それぞれの関数が呼び出されるたびに、事前条件と事後条件も評価されます。
さらに、ExampleToken
はcreateVault()
をcreateEmptyVault()
に変更し、トークンの発行を新たに追加されたVaultMinter
リソースに制限します。これは、Cadenceリソースのもう一つの強力な機能を示しています。スマートコントラクトがminterのアドレスのリストを管理する代わりに、ミントにしたいアカウントに、トークン発行の権限を直接付与する特別なリソースを与えることができます。この認証方法は、さまざまな方法で使用でき、スマートコントラクトの管理をさらに分散化できます。
また、VaultMinter
オブジェクトを/storage/
にinit()
関数内で保管しますが、保管場所はVaultとは異なります。
self.account.storage.save(<-create VaultMinter(), to: /storage/CadenceFungibleTokenTutorialMinter)
ここで、アカウントストレージはスマートコントラクトによって名前空間化(namespaced)されていないことを思い出していただくことが重要です。つまり、パス名が潜在的に競合する可能性があるということです。これが、他のプロジェクトのパスと競合する可能性が非常に低いように、ここで私たちがしたように、パスに一意の名前を選択することが重要である理由です。
Create, Store, and Publish Capabilities and References to a Vault
まだまだ続きますが、私は翻訳もうギブアップです。読みたい方はこちらからどうぞ👇
翻訳元->https://cadence-lang.org/docs/tutorial/fungible-tokens