您的第一个 Move 模块

本教程详细介绍了如何编写、编译、测试、发布以及与 Aptos 区块链上的 Move 模块交互。步骤如下:

  • 编写、编译和测试 Move 模块

  • 将 Move 模块发布到 Aptos 区块链

  • 初始化并与 Move 模块的资源交互

请从下面的代码仓库中 clone 最新代码!

本教程以“您的第一笔交易”为基础,并借用该代码作为本示例的库。 在下面下载示例代码:

对于本教程,将重点关注 hello_blockchain.py 并重用上一教程中的 first_transaction.py 库。

你可以在这里找到 python 项目。

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 模块

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"])
    

Step 2.2) 读取资源

该模块在一个地址发布。 这是下面的 contract_address。 这类似于前面的示例,其中 TestCoin0x1地址下。 contract_address 将与发布它的地址相同。

    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
    

Step 2.3) 修改资源

Move 模块必须公开用于初始化和操作资源的 script 函数。 然后可以从交易中调用该 script

注意:虽然 REST 接口可以显示字符串,但由于 JSON 和 Move 的限制,它无法确定参数是字符串还是十六进制编码的字符串。 所以交易参数总是假设后者。 因此,在此示例中,消息被编码为十六进制字符串。

    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"])
    

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

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 接口或区块浏览器来验证数据:

Last updated