Cadence と Solidity の比較:オンチェーンのトークン送付

Ara
Flow Japan
Published in
33 min readFeb 4, 2023

※本記事はこちらの英語記事を日本語に翻訳し、いくつかの注釈を加えたものです。

ERC-20 と ERC-721 は、今日最も広く採用されているスマートコントラクト標準であり、Ethereum の成功に重要な役割を果たしました。それにもかかわらず、Flow の Cadence スマートコントラクト言語は全く異なるやり方を選びました。

2 つの記事のうちの最初の記事では、Solidity と Cadence のトークン送付のアプローチの根本的な違いを説明するために、ERC-20 / ERC-721 と Flow の標準を比較します。それぞれのスマートコントラクト言語がトークン送付にどう影響するか、そして Cadence の設計が新しい言語とセキュリティ機能をどのように提供し、ビルダーとユーザーの両方に利益をもたらすかをみていきます。読者は Solidity や他のスマートコントラクト言語を使った分散型アプリ開発の基本的な知識を持っていることを前提にしています。

リファレンスとなるコントラクト

Solidity のリファレンスとして、OpenZeppelin プロジェクトが提供する ERC-20ERC-721 のよく使われている優れた実装を用います。

ハイレベルでの比較

Cadence と、Fungible Token(FT)/ Non-Fungible Token(NFT)標準の設計は、ERC-20 / ERC-721 の実体験に基づいています。(実際、言語と標準の両方のメイン設計者の 1 人は、ERC-721 の作者でした)。それぞれの言語と標準は、ユーザー間のトークン交換を簡単にするというコア機能において論理的に比較できます。その比較はインターフェースとコードレベルで決着がつきます。しかしそれ以上に、2 つの言語間の機能と設計の違い、その性質は、アプリケーション設計・セキュリティ・コンポーザビリティに影響を与え、ビルダーにとって重要な意味を持ちます。

トークンを送付する仕組みの比較

Solidity のコントラクトの特徴は、各トークンのコントラクトが残高や NFT の所有、アクセスの情報を保存・管理することです。さらに、Solidity はアドレス・ベースのアクセス/権限モデルに依存しており、特権的な機能へのアクセスは、重要な関数の実行前にアドレスを確認することでそれが誰であるかを検証するパブリック・メソッドを介して行われます。一方、Cadence では、FT / NFT 標準を使うすべてのトークンは、プログラム内にあるリソースと呼ばれる言語の第一級の型であって、それが一意であることが言語によって管理されています。アクセスとセキュリティは、暗号化キーに似たプログラム内のオブジェクトである Capability ベースのアクセス制御によって管理されており、それが所有されたときに、オブジェクトにアクセスするためのスコープ付きの権限が付与されます。これから説明するように、リソースと Capability は、Flow のアカウントモデルとともに、新しいプリミティブと他とはまったく異なる機能を提供して、複雑さを開発者に押し付けるのではなく吸収し、分散型アプリ開発をこれまで以上に簡単にします。

ERC-20 の移譲された transferFrom()

移譲された(delegated)トークン送付のパターンは、EVM ベースのアプリでよく見られるものであり、アドレス・ベースのアクセス・モデルの課題を説明する好例です。この図は、GIBBON トークンという ERC-20 コントラクトの例で、誰かが Dapp / サービスのコントラクトから移譲された transferFrom() を実行した場合の典型的なプログラム・フローを示しています。

MetaMask ではおなじみの approve() は、ほとんどの Dapps が EVM ベースのチェーンで動作する前提条件として要求する処理です。ユーザーは、GIBBON コントラクト上でアプリのコントラクトを(送付額を指定して)承認(approve)したあと、アプリのコントラクトとやり取りします。そして、アプリのコントラクトは、送信者を指定して transferFrom() を呼び出し、送信者から自分自身への 10 GIB の送付を完了させます。

完了したとき、GIBBON トークンのコントラクト内の _balances というマッピング変数から、ユーザー・アカウントのトークン残高を指定された量(ただし、aprove で指定された量以下)減らし、Dapp コントラクトのアドレスのトークン残高を同じ量だけ増やします。これは基本的にコントラクトが中央集権的な台帳として機能し、銀行口座と同様に所有者とその残高をマッピングして記録するためです。

mapping(address => uint256) private _balances;

なぜ移譲が必要なのか?

一歩下がって、GIBBON コントラクトに approve() が必要な理由は、コントラクト トークンそのものであり、オーナーと残高のマッピング・データも格納しているからです。GIBBON コントラクトが approve されるのは、署名済みの銀行の小切手を渡された人が、その人から自分に残高を引き出すことを許可されているようなものです。その結果、approve は Solidity のセキュリティ構造の重要な部分となっており、それが必要なのは正当な理由です。しかし、一歩下がってみると、approve パターンが存在する理由は、Solidity のアドレス中心の性質に起因しています。Solidity コントラクトとのやり取りは、コントラクトとやり取りするアカウントが関数を実行することを想定しています。ランタイムのコンテキストは msg.sender プロパティを介して呼び出し元コントラクトのアドレスを提供します。関数呼び出し中に権限を与えられているのはそのアドレスだけです。

function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}

したがって、Dapp コントラクトから GIBBON コントラクトへのやり取りでは、msg.sender は Dapp コントラクトのアドレスとなります。送信者の代理で処理を行う唯一の方法は、あらかじめ権限を与えられていることです。このため、transferFrom() メソッドでは、変更を加える前にこのチェックを内部で行っています。OpenZeppelin のベース・コントラクトの場合は、_spendAllowance() と allowance() 関数で行われています。

function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}

function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}

Cadence は違います!でも、どう違う?

先ほども触れましたが、Cadence は新しいコンセプトを導入しており、トークン譲渡の解決方法が大きく異なります。Cadence のトークン譲渡の仕組みを理解するためには、次のことを知る必要があります。

アカウントモデル

Cadence における アカウントモデル は、アカウントに関連付く鍵およびコード(スマートコントラクト)のためのストレージと、アカウントが所有するアセットのためのストレージを持っています。そうです。Cadence では、あなたのトークンはスマートコントラクトではなく、あなたのアカウントに保管されます。もちろん、スマートコントラクトは依然としてこれらアセットとその振る舞いを定義しますが、アセットはリソースの魔法によってユーザーのアカウントに安全に保管できます。

リソース(Resources)

リソースは一意な 線形型 であり、コピーや暗黙的な破棄はできず、アカウント間の移動のみが可能です。もし開発中に、ある関数がアカウントから取得したリソースを関数スコープ内で格納できなかった場合、セマンティック・チェックでエラーになります。ランタイムは、許可された操作に関して同様の厳密なルールを強制します。つまり、スコープ内でリソースを適切に処理しないコントラクトの関数は停止(abort)し、リソースは元のストレージに戻されます(revert されます)。このような特徴から、リソースは、Fungible Token / Non-Fungible Token 両方のトークンを表現するのに適しています。「所有」は保管場所によって決まり、アセットは複製されたり、誤って失われたりすることはありません。言語自体がその正しさを強制します。

ケイパビリティ(Capabilities)

格納されているオブジェクトへのアクセスは、Capability を介して移譲することができます。つまり、あるアカウントが、他のアカウントに格納されているオブジェクトにアクセスしたい場合、そのオブジェクトに対する有効な Capability が提供される必要があります。Capability はパブリックまたはプライベートにすることができます。アカウントは、他のすべてのアカウントにアクセスを許可したい場合、パブリックな Capability を共有します。(例えば、あるアカウントがパブリックな Capability を介して誰からでも互換性のあるトークンを受け取れるようにすることが一般的です)。または、アカウントは、特定のアカウントにプライベートな Capability を渡して、制限された関数へのアクセスを提供することもできます。例えば、NFT のプロジェクトでは、特定のアカウントに新しいトークンを mint する権限を付与する「管理者の Capability」を通じて、mint を制御することがよくあります。

コントラクト標準

Cadence の FT コントラクト標準は、ERC-20 と同様に実装されるコントラクトのインターフェースを定義しています。しかし ERC-20 と違ってこの規格は、リソース、リソース・インターフェース、その他の型、などのサブタイプに継承されます。したがって、この規格は、FT の実装が違反できない動作や条件を定義して、制限することができます。具体的には、FT 標準は Vault(金庫)というリソースのサブタイプを定義しています。これは、標準のすべての実装が、同じインターフェースと関数を持つ、同じ Vault リソース型を実装しなければならないことを意味します。Provider、Receiver、Balance というリソース・インタフェースは、Vault の相互作用を分離し、後で説明するように、きめ細かいセキュリティ制御に使われます。FT 標準の Valut の関数で指定されている関数の事前条件と事後条件は、FT 実装の実行時に強制的に適用されます。この標準では、mint やリソース生成などその他のことは強制していません。それらはプロジェクトによって異なる可能性があるためです。以下のクラス図は、FLOW トークンがどのように FT コントラクト標準を実装しているかを示しています。

msg.sender からの解放!

これらの新しいコンセプトがなぜ重要なのか理解するために、さっそく歴史を振り返ってみましょう。言語としての Cadence の開発は、Solidity の限界に長い間苦しんでいた Dapper Labs チームによって 2018 年に始まりました。分散型アプリケーションをエンジニアリングする上で最もフラストレーションがたまるのは、コントラクトのコンポーザビリティを非常に難しくするアドレス・ベースのアクセスまでさかのぼります。Web3 におけるコンポーザビリティとは、コントラクトがレゴブロックの役割を果たし、その周りに他のコントラクトを構築して機能を追加的に組み合わせられるという考え方です。たとえば、ゲームのコントラクトがゲームの勝敗結果をオンチェーンに保存する場合、勝敗データを使ってリーダーボードでトッププレーヤーを表示する別のコントラクトを作れます。さらに別のコントラクトでは、勝敗履歴を利用して、プレイヤーの将来のゲームに賭けるオッズを計算できるかもしれません。しかし、アドレス・ベースのアクセスであるため、コントラクトの呼び出しは、それが誰であるかしっかりとゲートで検査されています。ユーザーが両方のコントラクトにアクセスできる場合でも、最初のコントラクトが 2 番目のコントラクトにアクセスできなければ、2 つのコントラクトが相互作用することは不可能になります。

Solidity でのアクセス制御がカバーできる範囲は、保護された関数から権限が与えられた対象までです。その結果、コントラクトは認可情報を内部で保持し、保護された関数にアクセスするすべてのアドレスを検査することになります。

Cadence は、Capability でアクセスベースのパラダイムを覆しました。Capability を取得すると、対象から保護された関数(またはリソース)へのアクセスが許可されるため、コントラクトはアドレスを認可する必要がなくなります。これは、保護されたオブジェクトへのアクセスは、borrow() を使う Capability を通してのみ可能であるためです。さようなら、msg.sender!

権限移譲の考え方からドメイン駆動型のコーディングへ

FT トークンのコントラクトと直接やり取りするコードではなく、最初のステップは、アカウントとやり取りして特定の FT トークンの型の Capability を取得することです。図では、public path の引数(getCapability の引数)からわかるように、Capability が FLOW トークンの Vault リソース用であることがわかります(そして、アカウントが持つパブリックな Capability を一覧で確認できます)。返された Capability インスタンスは、それが参照している Vault リソース(この場合は FLOW の Vault)を borrow() することを可能にします。この例では、戻り値の型は FT の Receiver リソース・インターフェースに制限されています。これは、Vault の参照に対して、deposit() 関数だけ呼び出せるように厳しく制限します。

後述しますが、このコードは直感的で、オブジェクト指向であり、型は明らかに当該のトークン送付のドメインに関連しています。

トランザクションの必須要素

Capability ベースのアクセスと FT 標準を理解した上で、送付処理に必要な基本的な要素を説明します。

1. 引き出すための Provider Vault を取得する

前のセクションで説明したように、最初のステップとして、資金が保管されているユーザーの Vault へのアクセスを取得する必要があります。このステップではユーザーは、資金を送り、トランザクションを実行する本人です。そのため、Capability を取得しなくても borrow() を使って自分の Provider Vault(withdraw() が許可されている Provider インターフェースを公開する Vault)に直接アクセスできます。

// 送付するトークンを保持する Vault リソース
let interimVault: @FungibleToken.Vault

prepare(provider: AuthAccount) {

// 送信者の FLOW Vault への参照を取得する
let vaultRef = provider.borrow<&FlowToken.Vault>(from: FlowToken.VaultStoragePath)
?? panic("provider の Vault への参照を取得できません!")

// 送信者の Vault からトークンを引き出す
self.interimVault <- vaultRef.withdraw(amount: amount)
}

トランザクションのコードの先頭で定義されている interimVault(一時的な Vault)という変数は、受信者に預け入れる前に、送信者から引き出したトークンを一時的に保持するためのものです。細かいことですが、移動演算子 <- を使った interimVault への代入(移動)の行では、引き出す額が利用可能か検証されます。これらのステップは両方とも、送信者のストレージからオブジェクトを読み込むための、トランザクションの prepare ブロックにて実行されます。送信者が十分なトークンを持っていない場合、あるいは何らかの理由で Vault が利用できない場合、トランザクションは停止(abort)します。

2. 預け入れ先となる受信者の Vault の参照を取得する

Cadence のトランザクションは 2 つのフェーズで行われます。そのうちの execute フェーズでは、事前に取得した送信者のリソース操作や他のアカウントとのやりとりが行われます。prepare フェーズが正常に完了した場合に、execute フェーズが実行されます。prepare フェーズでは、受信者アカウントのパブリックな Capability を介して、受信者の Vault リソースの参照を取得する必要があることに注意してください。

execute {

// 受信者のパブリック・アカウント・オブジェクトを取得する
let recipient = getAccount(to)

// 受信者の FLOW Vault Receiver への参照を取得する
let receiverRef = recipient.getCapability(FlowToken.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("受信者の Vault への Receiver インターフェースの参照を取得できません")

// 引き出したトークンを受信者の Receiver に預け入れる
receiverRef.deposit(from: <-self.interimVault)
}

アカウントから、FlowToken.ReceiverPublicPath のパスにあるパブリックな Capability を取得し、その Capability を介して FLOW トークンの Vault の Receiver インターフェースの参照を借用します。私たちは、移動演算子 <-を使って、受信者のトークン Vault に intermVault 変数に保持されている額を預け入れて、トランザクションを終了します。完了すると、トランザクションはコミットされます。いずれかのフェーズで panic が発生した場合、トランザクション全体が中断(abort)されます。

まとめ

上記のコードから推測されるように、Cadence のトランザクションは Solidity と異なり、単一の関数呼び出しだけでなく、任意のコードを含むことができます。以下の完全な例では、FT 標準と FLOW トークンのコントラクトのインポートが、必要な型をトランザクションのスコープに解決し、その後、これらの型とのやり取りが標準のオブジェクト指向プログラミングの慣例に従った形で確立されます。

import FungibleToken from 0xFUNGIBLETOKENADDRESS
import FlowToken from 0xTOKENADDRESS

transaction(amount: UFix64, to: Address) {

// 送付するトークンを保持する Vault リソース
let interimVault: @FungibleToken.Vault

prepare(provider: AuthAccount) {

// 送信者の FLOW Vault への参照を取得する
let vaultRef = provider.borrow<&FlowToken.Vault>(from: FlowToken.VaultStoragePath)
?? panic("provider の Vault への参照を取得できません!")

// 送信者の Vault からトークンを引き出す
self.interimVault <- vaultRef.withdraw(amount: amount)
}

execute {

// 受信者のパブリック・アカウント・オブジェクトを取得する
let recipient = getAccount(to)

// 受信者の FLOW Vault Receiver への参照を取得する
let receiverRef = recipient.getCapability(FlowToken.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("受信者の Vault の Receiver インターフェースの参照を取得できません")

// 引き出したトークンを受信者の Receiver に預け入れる
receiverRef.deposit(from: <-self.interimVault)
}
}

これはどう安全なのでしょうか?誰もスコープ内の Vault から全資金にアクセスできないのでしょうか?

一見すると、アカウントの Vault にアクセスを許可することは、安全でないと思われるかもしれません。しかし、アカウントのセットアップ処理では、パブリックにアクセスできる Capability はアクセス権をスコープダウンして、クライアントが完全な Vault インターフェイスにアクセスできないようにします。この例では、セットアップ処理でトークン Vault の Capability をアカウントにリンクする際に、FLOW トークンのコントラクトがどのようにアクセスをスコープダウンするのかを示しています。

// Receiver インターフェイスを通じて deposit 関数のみを公開する
// Vaultのパブリックな Capability を作る
signer.link<&FlowToken.Vault{FungibleToken.Receiver}>(
/public/flowTokenReceiver,
target: /storage/flowTokenVault
)

リンクされた Capability は、FlowToken.Vault{FungibleToken.Receiver} インターフェイスのみを公開し、アカウントの Vault への権限が制限された参照を返します。これにより、元のオブジェクトがより広い API アクセス領域を持つにもかかわらず、他の関数が Vault に対して呼び出されることがないように強制します。権限の制限によって、パブリックな Capability を介したアクセスをスコープダウンすることは、Cadence 独自のセキュリティ制御機構のひとつで、よく使われるものです。

Cadence における移譲の真実

鋭い洞察力をお持ちの方は、上記の送付の例が、制御を移譲していないことに気づかれたかもしれません。実は、Solidity と Cadence では、アドレス・ベースと Capability ベースというセキュリティ・モデルが異なるため、移譲された送付について比較することは不可能なのです。つまり、移譲は可能ですが、Solidityのような方法ではできません。

Capability による移譲

アクセスや制御を移譲する最も簡単な方法は、Capability を使うことです。もしアリスがボブにトークン送付を移譲して、彼が彼女の資金の一部を何かに使うことを許可しようと決めたら、彼女はボブにプライベートな Capability を発行して、彼女のトークン Vault を Provider リソース・インタフェースでアクセス許可するかもしれません。しかし、アリスのメインの FLOW Vault から無制限に資金を引き出せるアクセス権は、明らかに望ましくありません。Capability を使えば、ユースケースに最適な表現力豊かで創造的なソリューションを設計できます。ここでの例は、移譲を実装できる方法のほんの一部です。

  1. アリスのアカウントに新しい一時的な FLOW Vault を作り、アリスがボブに引き出すことを許可する限られた額を入れます。そして、その Vault にプライベートな Capability をリンクして、それをボブに提供します。
  2. Provider インタフェースを実装した新しいリソース(この例ではScopedProvider)を作成します(完全なコードはこの Gist を参照)。そのリソースに、アリスの Vault の Provider インターフェースの Capability を保存し、元の withlow() をラップしたメソッドで引き出し額を制限します。その後、ScopedProvider をアリスのアカウントに保存し、その Provider インターフェースの Capability をリンクしてボブに渡します。
/// 引き出しを制限するために Provider Capability をラップしたリソース
pub resource ScopedProvider : FungibleToken.Provider {
/// いくら引き出されたかを格納する
pub var withdrawn: UFix64
/// 引き出しの上限をセットする
pub let withdrawalLimit: UFix64
/// 元の Provider への Capability を保持する
pub let sourceProvider: Capability<&{FungibleToken.Provider}>

init(
_ withdrawalLimit: UFix64,
_ sourceProvider: Capability<&{FungibleToken.Provider}>
) {
pre {
sourceProvider.check(): "Provider Capability に問題があります!"
}
/* リソース変数を初期化する */
self.withdrawn = 0.0
self.withdrawalLimit = withdrawalLimit
self.sourceProvider = sourceProvider
}

/// 引出しを制限する Provider.withdraw メソッドの実装
pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
pre {
self.withdrawn + amount <= self.withdrawalLimit: "出金限度額を超えています!"
}
// withdwar 前に、出金額をインクリメントする
self.withdrawn = self.withdrawn + amount
// init 時に与えられた Provider への参照を取得する
let sourceProviderRef = self.sourceProvider.borrow()!
// 元の Provider withdraw された Vault を返す
return <-sourceProviderRef.withdraw(amount: amount)
}
}

3. ハイブリッド・カストディ!Flow 独自の画期的な機能で、間もなくリリースされます。ご期待ください!

Cadence のトランザクションはどうなってますか?

Cadence と Solidity のもう一つの大きな違いは、VM で実行されるコードが、デプロイされたコントラクトだけではないことです。Cadence はスクリプト、および、そのサブセットであるトランザクションを提供し、どちらも任意のコードが許可されています。スクリプトやトランザクションはオンチェーンにはデプロイされず、常にオフチェーンに存在しますが、実行ランタイムによって実行されるトップレベルのコード・ペイロードとなります(※訳注:トランザクションの場合は、トランザクションのペイロードとしてオンチェーンに記録されます)。クライアントは、Flow アクセス API の gRPC または REST エンドポイントを通じてスクリプトとトランザクションを送信し、該当する場合はクライアントに結果を返します。スクリプトとトランザクションは、より効率的で強力な方法で Dapps とブロックチェーンを統合できます。コントラクトはより純粋にサービスまたはコンポーネントとして考えることができ、スクリプトまたはトランザクションはチェーン相互作用のための Dapp 固有の API インターフェイスとなります。

スクリプトは読み取り専用で、メイン関数の宣言のみを必要とし、チェーンの状態に対してクエリーを実行します。

// このスクリプトは、アカウントの ExampleToken Balance の balance フィールドを読み取る
import FungibleToken from "../../contracts/FungibleToken.cdc"
import ExampleToken from "../../contracts/ExampleToken.cdc"

pub fun main(account: Address): UFix64 {
let acct = getAccount(account)
let vaultRef = acct.getCapability(ExampleToken.VaultPublicPath)
.borrow<&ExampleToken.Vault{FungibleToken.Balance}>()
?? panic("Vault の Balance インターフェースの参照を取得できません")

return vaultRef.balance
}

トランザクションは ACID(Atomic, Consistent, Isolated and Durable)特性を持ったスクリプトであり、prepare 関数と execute 関数から成ります。トランザクションは、完全に成功して記述通りにチェーンの状態を変更するか、失敗して何も変更されないか、どちらかの結果を引き起こします。また、事前条件と事後条件の設定もサポートしています。以下のトランザクションの例では、ExampleToken は入力されたマップのアドレスの各受信者の Vault に預け入れられます。

import FungibleToken from "../contracts/FungibleToken.cdc"
import ExampleToken from "../contracts/ExampleToken.cdc"

/// `addressAmountMap` パラメータで指定されたアドレスのリストにトークンを送付する
transaction(addressAmountMap: {Address: UFix64}) {

// 送付されるトークンを保持する Vault リソース
let vaultRef: &ExampleToken.Vault

prepare(signer: AuthAccount) {

// 署名者に格納されている Vault への参照を取得する
self.vaultRef = signer.borrow<&ExampleToken.Vault>(from: ExampleToken.VaultStoragePath)
?? panic("所有者の Vault への参照を取得できません!")
}

execute {

for address in addressAmountMap.keys {

// 署名者の Vault からトークンを引き出す
let sentVault <- self.vaultRef.withdraw(amount: addressAmountMap[address]!)

// 受信者のパブリック・アカウント・オブジェクトを取得する
let recipient = getAccount(address)

// 受信者の Receiver への参照を取得する
let receiverRef = recipient.getCapability(ExampleToken.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("受信者の Vault の Receiver インターフェースの参照を取得できません")

// 引き出したトークンを受信者の Receiver に預け入れる
receiverRef.deposit(from: <-sentVault)
}
}
}

トランザクションは、任意の数の withdraw() / deposit() 呼び出しや、複数の FT の利用、複数のアドレスへの送付、または、他のより複雑なバリエーションをつくることができます。それらは ACID 特性を持ち、全体として成功または失敗します。

トランザクション後の状態

Solidity と Cadence のそれぞれのトークン標準における、トークン送付のランタイムの仕組みを説明したので、実装と設計の違いを明確に理解できたと思います。しかし、これらのトランザクションが完了した後、オンチェーンに何が残るのかを解き明かすまでは、話は終わりません。

前述のとおり、_balances は Solidity コントラクトが台帳のマッピングへのエントリーを保持している場所です。ウォレットは、これらの残高をユーザーのためにわかりやすい単一のビューにまとめます。私が GIBBON トークンを使って transferFrom() を完了したとすると、私のウォレットは balanceOf() メソッドを呼び出して GIBBON トークンの残高を更新します。

アカウント 0x001 は複数のトークンを所有しているため、ウォレットはそれぞれの ERC-20 準拠のトークン・コントラクトに対して balanceOf() をクエリします。

予想しているとおり、Cadence はアカウントモデルを活用して、必要に応じて異なるトークンタイプごとに Vault を所有します。このため、ウォレット側がまとめるためのビューを作成する必要はありません。flow-cli や他のオンラインツールを使ってアカウントを調べることで、所有するすべてのトークンの残高を確認することも同様に可能です。

所有を形づくる

Solidity と Cadence でトークンの所有がどのように形づくられるかの違いは、おそらく両者の哲学の最も重要で対照的な側面です。リソース、アカウントモデル、Capability ベースのアクセスは、FT 標準によって強力に結合され、トークンの所有をドメイン中心のプリミティブとして実現し、推論しやすく、ビルダーのソリューション領域を拡張します。Solidity が銀行口座のように所有者の残高記録を保持するのに対し、Cadence の保管庫は、財布に現金を入れるようにトークンを所有することを保証します。コントラクトのインターフェースや関数へのアクセスは、コントラクトとやり取りする人々が所有する Capability によって管理されます。実際、ビルダーが順応しなければならない哲学は、おそらく「あなたが誰であるかではなく、あなたが何を持っているか」ということに集約され、Cadence でコントラクトのやり取りがどのように行われるかを定義しています。次回の記事で説明するように、NFT の所有も同じ原則に従っています。NFT ではさらに、NFT コレクションを管理するための豊富なセマンティクス、ネットワーク全体の発見可能性、出品とオファー、そしてすべてのメタデータが NFT とともにオンチェーンで生きていることの素晴らしさが提供されています。

次回は… NFT の送付とコンポーザビリティ

この記事では、Fungible Token の送付が各言語でどのように機能するか、また、データと計算がオンチェーンでどのように処理されるか、設計と考え方がどのように対照的であるかを探求しました。Solidity のシンプルなアクセスベースのモデルから生じる制限が問題となり、それがコンポーザビリティにどのように影響するか、そして Cadence が強力で魅力的な代替案を提供することを強調しました。業界は重要な転換点にあり、現実の実用性とコンポーザビリティのユースケースは、約束された未来にはまだ明らかに不足しています。Cadence は、分散アプリのエンジニアリングの最前線を押し進める、現実的かつ実用的な方法でビルダーを支援します。現在 Cadence を進化させているグローバルでオープンソースなエンジニアリングコミュニティは、現在そして将来の世代の Web3 ビルダーのために、最も目的に適った、効果的で安全なスマートコントラクト言語を作ることに重きを置いています。上記で紹介した新しい言語機能や、近日公開予定の画期的な新機能には、コミュニティからの多くの重要な貢献が含まれています。

Cadence についてもっと知りたい、コミュニティに参加したいという方は、ぜひ私たちの Discord に参加してください。Solidity 開発者向けの詳細なガイドと、その他開発者向けの多くのリソースをご確認ください。また、Twitter のハッシュタグ #onFlow で私たちをタグ付けしてください。

第 2 部では、ERC-721 とそれに対応する Cadence 標準の NFT 送付の比較について深く掘り下げます。お楽しみに!

--

--

Ara
Flow Japan

ソフトウェアエンジニア。生物学、民俗学、仏教、神道、メディアアート、博物学、フォント、ブロックチェーンなどに興味あり。