您的第一个 Move 模块
本教程详细介绍了如何编写、编译、测试、发布以及与 Aptos 区块链上的 Move 模块交互。步骤如下:
  • 编写、编译和测试 Move 模块
  • 将 Move 模块发布到 Aptos 区块链
  • 初始化并与 Move 模块的资源交互
请从下面的代码仓库中 clone 最新代码!
本教程以“您的第一笔交易”为基础,并借用该代码作为本示例的库。 在下面下载示例代码:
Python
Rust
Typescript
对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。
你可以在这里找到 python 项目。
对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。
你可以在这里找到 Rust 项目。
对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。
你可以在这里找到 typescript 项目。

Step 1) 编写和测试 Move 模块

Step 1.1) 下载 Aptos-core

为了简化本练习,Aptos-core 有一个 move-examples 目录,可以轻松构建和测试 Move 模块,而无需下载其他资源。 随着时间的推移,我们将扩展本节以描述如何利用 Move 工具进行开发。
现在,下载并准备 Aptos-core:
git clone https://github.com/aptos-labs/aptos-core.git
cd aptos-core
./scripts/dev_setup.sh
source ~/.cargo/env

Step 1.2) 查看模块

在上面的终端窗口中,将目录更改为 aptos-move/move-examples。 在本教程的其余部分保留此终端窗口 - 后面我们将它称为“Move 窗口”。 本节的其余部分将查看文件 `sources/HelloBlockchain.move`。
该模块使用户可以在其帐户下创建 `String` 资源并进行设置。 用户只能设置自己的资源,不能设置他人的资源。
module HelloBlockchain::Message {
use Std::ASCII;
use Std::Errors;
use Std::Signer;
struct MessageHolder has key {
message: ASCII::String,
}
public(script) fun set_message(account: signer, message_bytes: vector<u8>)
acquires MessageHolder {
let message = ASCII::string(message_bytes);
let account_addr = Signer::address_of(&account);
if (!exists<MessageHolder>(account_addr)) {
move_to(&account, MessageHolder {
message,
})
} else {
let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
old_message_holder.message = message;
}
}
}
在前面的代码中,两个重要的部分是 MessageHolder 结构体和函数 set_messageset_message 是一个 script 函数,允许事务直接调用它。调用它时,该函数将确定当前帐户是否具有 MessageHolder 资源,如果不存在,则创建并存储该 message。 如果资源存在,则覆盖 MessageHolder 中的 message

Step 1.3) 测试模块

Move 允许内联测试,因此我们添加了 get_message 以方便获取 message,并添加了一个测试方法 sender_can_set_message 来验证端到端流。 这可以通过运行 cargo test 来验证。 在 sources/HelloBlockchainTest.move 下还有另一个测试,演示了另一种编写测试的方法。
注意:sender_can_set_message 是一个 script 函数,以便调用脚本函数 set_message
const ENO_MESSAGE: u64 = 0;
public fun get_message(addr: address): ASCII::String acquires MessageHolder {
assert!(exists<MessageHolder>(addr), Errors::not_published(ENO_MESSAGE));
*&borrow_global<MessageHolder>(addr).message
}
#[test(account = @0x1)]
public(script) fun sender_can_set_message(account: signer) acquires MessageHolder {
let addr = Signer::address_of(&account);
set_message(account, b"Hello, Blockchain");
assert!(
get_message(addr) == ASCII::string(b"Hello, Blockchain"),
0
);
}

Step 2) 发布并与 Move 模块交互

现在我们回到我们的应用程序来部署 Atpos 区块链上的模块并与之交互。 如前所述,本教程建立在早期教程的基础上并共享通用代码。 因此,本教程仅讨论该库的新功能,包括发布、发送 set_message 交易和读取 MessageHolder::message 的能力。 发布模块和提交交易的唯一区别是负载(payload)类型。 请参阅以下内容:

Step 2.1) 发布 Move 模块

Python
Rust
Typescript
class HelloBlockchainClient(RestClient):
def publish_module(self, account_from: Account, module_hex: str) -> str:
"""Publish a new module to the blockchain within the specified account"""
payload = {
"type": "module_bundle_payload",
"modules": [
{"bytecode": f"0x{module_hex}"},
],
}
txn_request = self.generate_transaction(account_from.address(), payload)
signed_txn = self.sign_transaction(account_from, txn_request)
res = self.submit_transaction(signed_txn)
return str(res["hash"])
pub struct HelloBlockchainClient {
pub rest_client: RestClient,
}
impl HelloBlockchainClient {
/// Represents an account as well as the private, public key-pair for the Aptos blockchain.
pub fn new(url: String) -> Self {
Self {
rest_client: RestClient::new(url),
}
}
/// Publish a new module to the blockchain within the specified account
pub fn publish_module(&self, account_from: &mut Account, module_hex: &str) -> String {
let payload = serde_json::json!({
"type": "module_bundle_payload",
"modules": [{"bytecode": format!("0x{}", module_hex)}],
});
let txn_request = self
.rest_client
.generate_transaction(&account_from.address(), payload);
let signed_txn = self.rest_client.sign_transaction(account_from, txn_request);
let res = self.rest_client.submit_transaction(&signed_txn);
res.get("hash").unwrap().as_str().unwrap().to_string()
}
class HelloBlockchainClient extends RestClient {
/** Publish a new module to the blockchain within the specified account */
async publishModule(accountFrom: Account, moduleHex: string): Promise<string> {
const payload = {
"type": "module_bundle_payload",
"modules": [
{"bytecode": `0x${moduleHex}`},
],
};
const txnRequest = await this.generateTransaction(accountFrom.address(), payload);
const signedTxn = await this.signTransaction(accountFrom, txnRequest);
const res = await this.submitTransaction(accountFrom, signedTxn);
return res["hash"];
}

Step 2.2) 读取资源

该模块在一个地址发布。 这是下面的 contract_address。 这类似于前面的示例,其中 TestCoin0x1地址下。 contract_address 将与发布它的地址相同。
Python
Rust
Typescript
def get_message(self, contract_address: str, account_address: str) -> Optional[str]:
""" Retrieve the resource Message::MessageHolder::message """
resources = self.account_resources(account_address)
for resource in resources:
if resource["type"] == f"0x{contract_address}::Message::MessageHolder":
return resource["data"]["message"]
return None
/// Retrieve the resource Message::MessageHolder::message
pub fn get_message(&self, contract_address: &str, account_address: &str) -> Option<String> {
let module_type = format!("0x{}::Message::MessageHolder", contract_address);
self.rest_client
.account_resources(account_address)
.as_array()
.unwrap()
.iter()
.find(|x| {
x.get("type")
.map(|v| v.as_str().unwrap() == module_type)
.unwrap_or(false)
})
.and_then(|coin| {
coin.get("data")
.unwrap()
.get("message")
.unwrap()
.as_str()
.and_then(|s| Some(s.to_string()))
})
}
/** Retrieve the resource Message::MessageHolder::message */
async getMessage(contractAddress: string, accountAddress: string): Promise<string> {
const resources = await this.accountResources(accountAddress);
for (const key in resources) {
const resource = resources[key];
if (resource["type"] == `0x${contractAddress}::Message::MessageHolder`) {
return resource["data"]["message"];
}
}
}

Step 2.3) 修改资源

Move 模块必须公开用于初始化和操作资源的 script 函数。 然后可以从交易中调用该 script
注意:虽然 REST 接口可以显示字符串,但由于 JSON 和 Move 的限制,它无法确定参数是字符串还是十六进制编码的字符串。 所以交易参数总是假设后者。 因此,在此示例中,消息被编码为十六进制字符串。
Python
Rust
Typescript
def set_message(self, contract_address: str, account_from: Account, message: str) -> str:
""" Potentially initialize and set the resource Message::MessageHolder::message """
payload = {
"type": "script_function_payload",
"function": f"0x{contract_address}::Message::set_message",
"type_arguments": [],
"arguments": [
message.encode("utf-8").hex(),
]
}
txn_request = self.generate_transaction(account_from.address(), payload)
signed_txn = self.sign_transaction(account_from, txn_request)
res = self.submit_transaction(signed_txn).json()
return str(res["hash"])
/// Potentially initialize and set the resource Message::MessageHolder::message
pub fn set_message(
&self,
contract_address: &str,
account_from: &mut Account,
message: &str,
) -> String {
let message_hex = hex::encode(message.as_bytes());
let payload = serde_json::json!({
"type": "script_function_payload",
"function": format!("0x{}::Message::set_message", contract_address),
"type_arguments": [],
"arguments": [message_hex]
});
let txn_request = self
.rest_client
.generate_transaction(&account_from.address(), payload);
let signed_txn = self.rest_client.sign_transaction(account_from, txn_request);
self.rest_client
.submit_transaction(&signed_txn)
.get("hash")
.unwrap()
.as_str()
.unwrap()
.to_string()
}
/** Potentially initialize and set the resource Message::MessageHolder::message */
async setMessage(contractAddress: string, accountFrom: Account, message: string): Promise<string> {
let payload: { function: string; arguments: string[]; type: string; type_arguments: any[] };
payload = {
"type": "script_function_payload",
"function": `0x${contractAddress}::Message::set_message`,
"type_arguments": [],
"arguments": [
Buffer.from(message, "utf-8").toString("hex")
]
};
const txnRequest = await this.generateTransaction(accountFrom.address(), payload);
const signedTxn = await this.signTransaction(accountFrom, txnRequest);
const res = await this.submitTransaction(accountFrom, signedTxn);
return res["hash"];
}
}

Step 3) 初始化并与 Move 模块交互

Python
Rust
Typescript
For Python3:
  • 下载示例项目
  • 打开您喜欢的终端并导航到您下载上述示例项目的位置
  • 安装要求的依赖包:pip3 install -r requirements.txt.
  • 执行示例命令:python3 hello_blockchain.py Message.mv
  • 片刻之后,它会输出 "Update the module with Alice's address, build, copy to the provided path, and press enter."
  • 在 "Move 窗口" 的终端中,对于我们之前看过的 Move 文件:
    • 编辑 Move.toml 并在 [addresses] 下将 0xe110 地址替换为 Alice 的地址,在提示上方输出。
    • 构建 cargo run -- sources
    • 拷贝 build/Examples/bytecode_modules/Message.mv 到与本教程项目代码相同的文件夹中
  • 返回到您的另一个终端窗口,并在提示符处按下“Enter”键以继续执行其余代码
输出的结果如下:
=== Addresses ===
Alice: a52671f10dc3479b09d0a11ce47694c0
Bob: ec6ec14e4abe10aaa6ad53b0b63a1806
=== Initial Balances ===
Alice: 10000000
Bob: 10000000
Update the module with Alice's address, build, copy to the provided path, and press enter.
=== Testing Alice ===
Publishing...
Initial value: None
Setting the message to "Hello, Blockchain"
New value: Hello, Blockchain
=== Testing Bob ===
Initial value: None
Setting the message to "Hello, Blockchain"
New value: Hello, Blockchain
结果表明,Alice 和 Bob 从没有资源变成了一个 message 设置为“Hello, Blockchain”的地址。
可以通过访问 REST 接口或区块浏览器来验证数据:
Copy link
On this page
Step 1) 编写和测试 Move 模块​
Step 2) 发布并与 Move 模块交互​