DAOrayaki:Aptos&Move実践解説
DAOrayaki DAOリサーチボーナスプール:
ファンディングアドレス: DAOrayaki.eth
投票の進行状況: DAO レビューアー 3/0 可決
研究タイプ: Aptos、Layer1
作者:FF@DoraFactory
この記事では主にaptos cliとaptos sdkの動作について説明します
Clone Aptos-core Repo
# Clone the Aptos repo.
git clone
# cd into aptos-core directory.
cd aptos-core
# Run the scripts/dev_setup.sh Bash script as shown below. This will prepare your developer environment.
./scripts/dev_setup.sh
# Update your current shell environment.
source ~/.cargo/env
# Skip this step if you are not installing an Aptos node.
git checkout --track origin/devnet
Aptos ローカル チェーンを開始する
Using CLI to Run a Local Testnet | Aptos Docs[1]
ローカルチェーンを開始する
ps: このメソッドによって開始されたローカル チェーンとデータは、このコマンドが開始された現在のフォルダーに保存され、.aptos/ ファイルに存在します。
aptos node run-local-testnet --with-faucet
正常に開始されました:
Completed generating configuration:
Log file: "/Users/greg/.aptos/testnet/validator.log"
Test dir: "/Users/greg/.aptos/testnet"
Aptos root key path: "/Users/greg/.aptos/testnet/mint.key"
Waypoint: 0:74c9d14285ec19e6bd15fbe851007ea8b66efbd772f613c191aa78721cadac25
ChainId: TESTING
REST API endpoint: 0.0.0.0:8080
FullNode network: /ip4/0.0.0.0/tcp/6181
Aptos is running, press ctrl-c to exit
Faucet is running. Faucet endpoint: 0.0.0.0:8081
起動に成功すると、Rest API とフォーセット API のアドレスの入力を求められます。後で、これら 2 つの情報を aptos cli 環境で構成する必要があります。
aptos cli 環境を構成する
コマンド ラインからローカル テスト チェーンにアクセスして呼び出すには、上記の展開情報に従って aptos cli の設定を構成する必要があります。
PROFILE=local
aptos init --profile $PROFILE --rest-url --faucet-url
実行中に、次の出力が得られます。秘密キーを入力するか、デフォルトでランダムに生成するかを選択できます。
Configuring for profile local
Using command line argument for rest URL
Using command line argument for faucet URL
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]
確認後、アカウントが作成され、デフォルトのトークン数が入金されます。
No key given, generating key...
Account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1 doesn't exist, creating it and funding it with 10000 coins
Aptos is now set up for account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1! Run `aptos help` for more information about commands
{
"Result": "Success"
}
今後は、コマンドに --profile local を追加することで、ローカル テストネット上で実行できるようになります。
ps: ここでの --profile は、k8s の kube-config のようなもので、異なるプロファイル環境を設定し、異なるネットワークを制御できます。
プロファイル構成では、エグゼキューターのアドレス、node-rest-api、およびfake-apiの情報が設定されます。
# cli によって制御されるすべてのアカウントを一覧表示します
aptos account list
# アカウントに資金を投入します:
aptos account fund --profile $PROFILE --account $PROFILE
# 新しいリソースアカウントを作成する
aptos account create-resource-account --profile $PROFILE --seed 1
# 引っ越し契約書を作成する
aptos move compile --package-dir hello_blockchain
# コントラクトをデプロイする
aptos move publish --package-dir hello_blockchain --named-addresses basecoin= --profile local
# 契約を呼び出す
aptos move run --function-id :::: --profile local
# 指定したアカウントのモジュール/リソース情報を一覧表示します
aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile local
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile local
# 契約のアップグレード
aptos move publish --upgrade-policy
`arbitrary`, `compatible`,「immutable」は 0、1、2 に対応します
0 はチェックを行わず、コードを強制的に置き換えます。
1 互換性チェックを実行します (同じパブリック関数は既存のリソースのメモリ レイアウトを変更できません)
2 バージョンアップの禁止
公開されるたびに、チェーン上のポリシーとこの公開のポリシーが比較されます (デフォルトは 1)。
契約のアップグレードは、今回のポリシーがチェーン上のポリシーよりも小さい場合にのみ許可されます。
単純な Move コントラクトをデプロイする
module MyCounterAddr::MyCounter {
use std::signer;
struct Counter has key, store {
value:u64,
}
public fun init(account: &signer){
move_to(account, Counter{value:0});
}
public fun incr(account: &signer) acquires Counter {
let counter = borrow_global_mut(signer::address_of(account));
counter.value = counter.value + 1;
}
public entry fun init_counter(account: signer){
Self::init(&account)
}
public entry fun incr_counter(account: signer) acquires Counter {
Self::incr(&account)
}
}
MyCounter ソースコード分析
モジュールは、特定のアドレスで公開される関数と構造のパッケージ化されたセットです。スクリプトを使用する場合は、公開されているモジュールまたは標準ライブラリで実行する必要があり、標準ライブラリ自体は0x1アドレスで公開されているモジュール群です。
モジュール MyCounterAddr::MyCounter{ } は、MyCounterAddr アドレス (MyCounterAddr = に対応) の下にあります。"0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf") モジュールを作成します。
use std::signer は、標準ライブラリの署名者モジュールを使用することです。署名者は、トランザクション送信者のアドレスを含む、リソースと同様のコピー不可能なネイティブ型です。署名者タイプを導入した理由の 1 つは、送信者のアクセス許可を必要とする機能とそうでない機能を明確にすることでした。したがって、関数はユーザーをだましてそのリソースへの不正アクセスを取得させることはできません。詳細についてはソースコード[2]を参照してください。
module std::signer {
// Borrows the address of the signer
// Conceptually, you can think of the `signer` as being a struct wrapper arround an
// address
// ```
// struct signer has drop { addr: address }
// ```
// `borrow_address` borrows this inner field
native public fun borrow_address(s: &signer): &address;
// Copies the address of the signer
public fun address_of(s: &signer): address {
*borrow_address(s)
}
/// Return true only if `s` is a transaction signer. This is a spec function only available in spec.
spec native fun is_txn_signer(s: signer): bool;
/// Return true only if `a` is a transaction signer address. This is a spec function only available in spec.
spec native fun is_txn_signer_addr(a: address): bool;
}
Struct & Abilities
struct Counter has key, store {
value:u64,
}
structを使ってCounterという構造体を定義すると同時に、keyとstoreという2種類のリミッターで修飾します。
Move のタイプ システムは柔軟で、各タイプは 4 つの能力を定義できます。
これらは、型の値をコピー、破棄、保存できるかどうかを定義します。
4 つの能力修飾子は、コピー、ドロップ、ストア、キーです。
それらの機能は次のとおりです。
コピー - 値をコピーできます。
ドロップ - スコープの最後で値をドロップできます。
キー - 値は、「グローバル ストレージ操作」によってキーとしてアクセスできます。
保存 - 値をグローバル状態に保存できます。
ここでキーで変更して保存すると、コピー、破棄、再利用はできませんが、安全に保存および転送できることを意味します。
能力の構文
プリミティブ型と組み込み型の機能は事前定義されており、不変です。整数、ベクトル、アドレス、およびブール型の値には、本質的にコピー、ドロップ、および保存の機能があります。
ただし、次の構文に従って構造の機能を追加できます。
struct NAME has ABILITY [, ABILITY] { [FIELDS] }
簡単なライブラリの例:
module Library {
// each ability has matching keyword
// multiple abilities are listed with comma
struct Book has store, copy, drop {
year: u64
}
// single ability is also possible
struct Storage has key {
books: vector
}
// this one has no abilities
struct Empty {}
}
リソースとは
リソースの概念については、Move ホワイト ペーパーで詳しく説明されています。当初はリソースという構造体タイプとして実装されていましたが、アビリティ導入以降はKeyとStoreという2つのアビリティを持つ構造体として実装されています。リソースはデジタル資産を安全に表すことができ、コピーしたり、破棄したり再利用したりすることはできませんが、安全に保存および転送することができます。
リソースの定義
リソースは、キーとストア機能が制限された構造体です。
module M {
struct T has key, store {
field: u8
}
}リソース制限
コードでは、リソース タイプにはいくつかの大きな制限があります。
リソースはアカウントの下に保存されます。したがって、アカウントが割り当てられている場合にのみ存在し、そのアカウントを介してのみアクセスできます。
アカウントは一度に特定の種類のリソースを 1 つだけ保持できます。
リソースはコピーできません。それに対応するのは特別な種類のリソースです。これは、ジェネリックスの章で紹介したコピー可能とは異なります。 (これはRustの所有権に抽象化できます)
リソースを使用する必要があります。つまり、新しく作成したリソースはアカウントの下に移動する必要があり、アカウントから移動したリソースは分解するか、別のアカウントの下に保存する必要があります。
たった今の件
struct Counter has key, store {
value:u64,
}
したがって、usdcなどのethで新しい資産を発行する必要がある場合、ここがソリッドティとの違いです。次に、この資産は契約内のマップに記録されます。しかし、移動の場合は異なり、資産はユーザーのアドレスの下にリソースとして保存されます。
関数を定義する
public fun init(account: &signer){
move_to(account, Counter{value:0});
}
public fun incr(account: &signer) acquires Counter {
let counter = borrow_global_mut(signer::address_of(account));
counter.value = counter.value + 1;
}
public entry fun init_counter(account: signer){
Self::init(&account)
}
public entry fun incr_counter(account: signer) acquires Counter {
Self::incr(&account)
}
定義形式は次のとおりです。
public fun 関数名 (パラメータ: パラメータの型) { }
move 関数はデフォルトではプライベートであり、それが定義されているモジュール内でのみアクセスできます。キーワード public は、関数のデフォルトの可視性を変更し、関数をパブリックにします。つまり、外部からアクセスできるようにします。
init メソッドのパラメータは &signer です。つまり、このメソッドはアカウントが合法的に署名された後にのみ呼び出すことができます。move_to は move のプリミティブで、署名者のアドレスにカウンター リソースを公開および追加するために使用されます。 Move のアカウント モデルでは、コードとデータはアカウント アドレスの下に保存されます。
以下は一般的に使用されるプリミティブのリストです。
move_to< T >(&signer,T): タイプ T のリソースを公開し、署名者のアドレスに追加します。
move_from< T >(addr: address): T - タイプ T のリソースをアドレスから削除し、このリソースを返します。
borrow_global< T >(addr: address): &T - アドレスにあるタイプ T のリソースへの不変参照を返します。
borrow_global_mut< T >(addr: address): &mut T - アドレスにあるタイプ T のリソースへの変更可能な参照を返します。
exists< T >(アドレス): bool: アドレスの下にタイプ T のリソースがあるかどうかを判断します。
incr メソッドのパラメータも &signer です。これは、メソッドを呼び出す前に、アカウントによって合法的に署名される必要があることを意味します。
関数の戻り値の後に置かれるキーワード取得は、この関数によって取得されるすべてのリソースを明示的に定義するために使用されます。
Signer::address_of(account) 署名者からアドレスを取得します
上で述べたように、borrow_global_mut では、変数はアドレスの下のリソース Counter を借用し、Counter 構造体の値に対して +1 演算を実行します。
以下の2つのメソッドはスクリプトメソッドですが、上記の2つの機能との違いは何でしょうか?
public fun : メソッドはどのモジュールでも呼び出すことができます。
public(script) fun / publicentry fun: スクリプト関数はモジュール内のエントリ メソッドです。つまり、スクリプトをローカルで実行するのと同じように、コンソールからトランザクションを開始することでメソッドを呼び出すことができます。
Move の次のバージョンでは、パブリック (スクリプト) ファンがパブリック エントリ ファンに置き換えられます。
Self は独自のモジュールを表します。
Aptos Cli を使用してコントラクトをコンパイル、デプロイし、呼び出します
# 新しいテスト環境を作成する
aptos init --profile devtest --rest-url --faucet-url
# 引っ越し契約書を作成する
aptos move compile --package-dir my-counter
# コントラクトをデプロイする
# 例: aptos move public --package-dir my-counter --named-addressesbasecoin=0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664 --profile devtest
aptos move publish --package-dir my-counter --named-addresses basecoin= --profile devtest
# 契約を呼び出す
# 例えば:
# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::init_counter --profile devtest
# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::incr_counter --profile devtest
aptos move run --function-id :::: --profile devtest
# 指定したアカウントのモジュール/リソース情報を一覧表示します
aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile devtest
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile devtest
Aptos SDK は Move コントラクトを呼び出します
コントラクトをコンパイルした後、SDK を通じてコントラクトを呼び出すことができます。
SDK を介してコントラクトをデプロイするか、SDK を介して移動コントラクトを呼び出すかを選択できます。
SDKを介してコントラクトをデプロイする
コンパイルすると、移動コントラクトフォルダーの下に build/ フォルダーが生成されます。
my-counter/build/Examples/bytecode_modules/MyCounter.mv ファイルを SDK スクリプトにコピーする必要があります。
aptos move compile --package-dir my-counter
cp MyCounter.mv my-counter-sdk-demo/
コントラクトに関連するSDKコードをデプロイする
/** Publish a new module to the blockchain within the specified account */
export async function publishModule(accountFrom: AptosAccount, moduleHex: string): Promise
const moudleBundlePayload = new TxnBuilderTypes.TransactionPayloadModuleBundle(
new TxnBuilderTypes.ModuleBundle([new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())]),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
moudleBundlePayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
SDK経由でトランザクションを送信
ここでは、例として my-counter コントラクトの init_counter と incr_counter を取り上げます。
クライアントが init と incr を呼び出す機能を実現するには、これら 2 つのメソッドを呼び出す 2 つのメソッドを構築します。
async function initCounter(contractAddress: string, accountFrom: AptosAccount): Promise
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural(
`${contractAddress}::MyCounter`,// 契約アドレス::契約名
"init_counter",// スクリプト関数のメソッド
[],
[],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
async function incrCounter(contractAddress: string, accountFrom: AptosAccount): Promise
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural(
`${contractAddress}::MyCounter`,
"incr_counter",
[],
[],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
SDK を通じてアカウント内のリソース情報を取得します。
リソースは、それが属するアカウント アドレスの下に保存され、アカウント アドレスに従って関連するリソース情報をクエリできます。
getCounter() メソッドは実際には、my-counter の下にある **Counter** リソースを取得します。
async function getCounter(contractAddress: string, accountAddress: MaybeHexString): Promise
try {
const resource = await client.getAccountResource(
accountAddress.toString(),
`${contractAddress}::MyCounter::Counter`,
);
return (resource as any).data["value"];
} catch (_) {
return "";
}
}
実際、この効果は SDK の効果と似ています。
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf
最終的なメイン関数
async function main() {
assert(process.argv.length == 3, "Expecting an argument that points to the helloblockchain module");
const contractAddress = "0x173d51b1d50614b03d0c18ffcd958309042a9c0579b6b21fc9efeb48cdf6e0b0"; // 以前にデプロイされたコントラクトのアドレスを指定します
const bob = new AptosAccount(); // テストアドレスを作成します Bob
console.log("\n=== Addresses ===");
console.log(`Bob: ${bob.address()}`);
await faucetClient.fundAccount(bob.address(),5_000); // 5000 個のテスト トークンをボブのアドレスにエアドロップします
console.log("\n=== Initial Balances ===");
console.log(`Bob: ${await accountBalance(bob.address())}`);
await new Promise
readline.question(
"Update the module with Alice's address, build, copy to the provided path, and press enter.",
() => {
resolve();
readline.close();
},
);
});
const modulePath = process.argv[2];
const moduleHex = fs.readFileSync(modulePath).toString("hex");
console.log('Init Counter Moudle.');
let txHash = await initCounter(contractAddress,bob); // bob の下の Counter リソースを初期化します。この時点では、bob の下の Counter の値は 0 です。
await client.waitForTransaction(txHash);
console.log("\n=== Testing Bob Get Counter Value ===");
console.log(`Initial value: ${await getCounter(contractAddress, bob.address())}`);
console.log('========== Incr Counter Value, 1th ==========');
txHash = await incrCounter(contractAddress,bob); // bob は incrCounter メソッドを 1 回呼び出しますが、この時点の Counter は 1 です。
console.log(txHash);
await client.waitForTransaction(txHash);
await Sleep(100);
console.log(`New value: ${await getCounter(contractAddress,bob.address())}`); // ボブのアドレスの下のカウンタ値を取得して出力します。
console.log('========== Incr Counter Value, 2th ==========');
txHash = await incrCounter(contractAddress,bob); // bob は incrCounter メソッドを 1 回呼び出し、Counter は 2 です。
console.log(txHash);
await client.waitForTransaction(txHash);
await Sleep(100);
console.log(`New value: ${await getCounter(contractAddress,bob.address())}`); // ボブのアドレスの下のカウンタ値を取得して出力します。
console.log('========== Incr Counter Value, 3th ==========');
txHash = await incrCounter(contractAddress,bob); // bob は incrCounter メソッドを 1 回呼び出し、Counter は 3 です。
console.log(txHash);
await client.waitForTransaction(txHash);
await Sleep(100);
console.log(`New value: ${await getCounter(contractAddress,bob.address())}`); // ボブのアドレスの下のカウンタ値を取得して出力します。
}
if (require.main === module) {
main().then((resp) => console.log(resp));
}
実行効果
実行は成功しました。ここでは、SDK を通じて、ランダムに生成されたアカウントが Counter リソース (Counter=0) で開始され、その後 3 回増加するため、Counter の最終値は 3 になります。
image-20220831200516865
参考文献
参考文献
[1]Using CLI to Run a Local Testnet | Aptos Docs: https://aptos.dev/nodes/local-testnet/using-cli-to-run-a-local-testnet
[2] ソースコード: https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/move-stdlib/sources/signer.move


