Aptos

2025年09月06日更新 4 人订阅
原价: ¥ 2.2 限时优惠
专栏简介 Aptos 开发实战:从环境搭建到第一个 Hello World Aptos 开发指南:在 JetBrains 编辑器中配置运行、编译、测试与发布部署,实现更高效开发 Aptos 区块链智能合约入门:使用 Move 实现消息存储与检索 Aptos Move 语言中的变量管理与内存所有权机制详解 Aptos Move 编程语言中的四大基础类型解析:UINT、STRING、BOOL 与 ADDRESS 深入解读 APTOS-MOVE 中的 Vector 向量核心特性与操作 深入理解APTOS-MOVE中的函数修饰符:核心概念与应用 深入解读 Aptos Move 的 Struct 特性与四大能力 Aptos Move 控制流解析:IF、WHILE与LOOP的深入解读 Aptos Move 模块的特性与实操指南:模块引用与作用域管理 Aptos Move 模块的发布与交互:完整指南 深入理解 Aptos Move 中的 Object 创建与管理 深入探索 Aptos Move:Object 配置与实操指南 使用 Aptos Move 实现随机数生成:从 AIP-41 到实战操作 Aptos Move 实践指南:构建并部署同质化代币水龙头 (FA Faucet) Aptos Move NFT 项目实操指南:从开发到部署全流程解析 Aptos Move 开发入门:从环境搭建到合约部署全流程实录 Aptos Move 入门:从零到一的合约开发与测试实战 Move 语言核心:布尔逻辑与地址类型的实战精解 深入 Aptos Move:从public到friend,函数可见性详解 Aptos Move 编程:for、while 与 loop 循环的实战详解 Aptos Move 安全编程:abort 与 assert! 错误处理实战 Aptos Move 实战:基础运算与比较逻辑的实现与测试 Aptos Move 性能优化:位运算与移位操作实战 Aptos Move 实战:as 关键字与整数类型转换技巧 Aptos Move DeFi 实战:从零构建流动性池兑换逻辑 Aptos Move 实战:用 signer 实现合约所有权与访问控制 Aptos Move 核心安全:& 与 &mut 引用机制详解 Aptos Move 实战:全面掌握 SimpleMap 的增删改查 Aptos Move 入门:掌握链上资源(Resource)的增删改查

Aptos Move 入门:掌握链上资源(Resource)的增删改查

AptosMove入门:掌握链上资源(Resource)的增删改查智能合约的终极目的,是管理那些永久存在于区块链上的状态(State)。在AptosMove中,这种链上状态是通过其独一无二的资源(Resource)模型来管理的。所谓资源,就是被赋予了key能力的结构体,它们像真实的物

Aptos Move 入门:掌握链上资源(Resource)的增删改查

智能合约的终极目的,是管理那些永久存在于区块链上的状态(State)。在 Aptos Move 中,这种链上状态是通过其独一无二的资源(Resource)模型来管理的。所谓资源,就是被赋予了 key 能力的结构体,它们像真实的物理资产一样,被直接、安全地存放在用户的账户地址之下。

那么,我们如何创建、读取、更新和销毁这些珍贵的链上资产呢?

这篇实战教程将带你走完一次链上资源的完整生命周期之旅。我们将从零开始,学习如何使用 move_to 创建一个资源,用 borrow_globalborrow_global_mut 读取和修改它,并最终用 move_from 将其安全地移除。掌握这套“增删改查”(CRUD)流程,是每一位 Aptos Move 开发者的核心基本功。

实操

Aptos Move Storage Operations

示例一

module net2dev_addr::StorageDemo {
    struct StakePool has key {
        amount: u64
    }

    fun add_user(account: &signer) {
        let amount: u64 = 0;
        move_to(account, StakePool { amount })
    }

    fun read_pool(account: address): u64 acquires StakePool {
        borrow_global<StakePool>(account).amount
    }

    #[test_only]
    use std::debug::print;

    #[test_only]
    use std::signer;

    #[test_only]
    use std::string::utf8;

    #[test(user = @0x123)]
    fun test_function(user: signer) acquires StakePool {
        add_user(&user);
        assert!(borrow_global<StakePool>(@0x123).amount == 0, 0);
        assert!(read_pool(@0x123) == 0, 1);
        assert!(read_pool(signer::address_of(&user)) == 0, 2);
        print(&borrow_global<StakePool>(@0x123).amount);
        print(&read_pool(@0x123));
        print(&utf8(b"User Added Successfully!"));
    }
}

这段 Aptos Move 代码通过一个名为 StorageDemo 的模块,演示了使用资源(resources)*进行*链上数据存储的基础模式。它定义了一个带有 key*能力的 StakePool 结构体,使其可以直接存储在用户的账户地址下。该模块展示了与这种资源交互的两种主要操作:add_user 函数使用 **move_to*\ 指令为一个交易签名者\创建并存储一个新的、空的 StakePool;而 read_pool 函数则使用 borrow_global 从任意指定地址读取**已存在的 StakePool 中的数据。整个流程由一个单元测试进行验证,该测试模拟了一个用户(@0x123)先存储资源,然后读取数据以确认写入成功的完整过程。

测试

➜ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my-dapp
Running Move unit tests
[debug] 0
[debug] 0
[debug] "User Added Successfully!"
[ PASS    ] 0x48daaa7d16f1a59929ece03157b8f3e4625bc0a201988ac6fea9b38f50db5ef3::StorageDemo::test_function
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
  "Result": "Success"
}

这个测试结果表明,你的 my-dapp 项目已成功通过其全部单元测试。在成功编译项目后,测试框架执行了 StorageDemo 模块中定义的唯一一个测试函数 test_function。日志中的 [debug] 输出清晰地展示了测试过程:两个 0 分别是通过直接 borrow_global 和调用 read_pool 函数从链上存储中成功读取回来的初始质押数量,而 "User Added Successfully!" 消息则标志着测试逻辑的顺利执行。最终的 [ PASS ] 状态,结合 Test result: OK 的总结陈述,共同确认了该测试用例顺利完成,证明了你代码中通过 move_to 写入数据到链上存储,再读取出来的整个流程是完全正确的。

示例二

module net2dev_addr::StorageDemo {
    struct StakePool has key {
        amount: u64
    }

    fun add_user(account: &signer) {
        let amount: u64 = 0;
        move_to(account, StakePool { amount })
    }

    fun read_pool(account: address): u64 acquires StakePool {
        borrow_global<StakePool>(account).amount
    }

    fun stake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry += 100;
    }

    #[test_only]
    use std::debug::print;

    #[test_only]
    use std::signer;

    #[test_only]
    use std::string::utf8;

    #[test(user = @0x123)]
    fun test_function(user: signer) acquires StakePool {
        add_user(&user);
        assert!(borrow_global<StakePool>(@0x123).amount == 0, 0);
        assert!(read_pool(@0x123) == 0, 1);
        assert!(read_pool(signer::address_of(&user)) == 0, 2);
        print(&borrow_global<StakePool>(@0x123).amount);
        print(&read_pool(@0x123));
        print(&utf8(b"User Added Successfully!"));

        stake(@0x123);
        assert!(read_pool(@0x123) == 100, 3);
        stake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 200, 4);
        print(&utf8(b"User Staked Successfully!"));
    }
}

这段 Aptos Move 代码扩展了 StorageDemo 模块,以演示一个链上资源的完整生命周期:创建、读取和修改。在原有的创建 (add_user 函数) 和读取 (read_pool 函数) StakePool 资源的功能之上,新增了一个 stake 函数。该函数的核心是使用 borrow_global_mut 来获取一个指向链上数据的可变引用,从而能够直接修改已存储的 amount 字段。其单元测试完整地验证了这一流程:首先创建并读取初始状态(金额 0),然后调用两次 stake 函数,并在每次调用后都进行断言,确认存储的金额被正确地依次更新为 100200

测试

➜ aptos move test                                                                                            
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my-dapp
Running Move unit tests
[debug] 0
[debug] 0
[debug] "User Added Successfully!"
[debug] "User Staked Successfully!"
[ PASS    ] 0x48daaa7d16f1a59929ece03157b8f3e4625bc0a201988ac6fea9b38f50db5ef3::StorageDemo::test_function
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
  "Result": "Success"
}

这个测试结果表明,你的 my-dapp 项目已成功通过其全部单元测试。在成功编译项目后,测试框架执行了 StorageDemo 模块中定义的唯一一个测试函数 test_function。日志中的 [debug] 输出清晰地展示了测试的完整流程:先是打印出初始状态 0 和 "User Added Successfully!" 消息,确认了数据创建的成功;随后打印出 "User Staked Successfully!" 消息,标志着后续的数据修改(stake)步骤也已执行。最终的 [ PASS ] 状态,结合 Test result: OK 的总结陈述,共同确认了该测试用例顺利完成,证明了你代码中对链上资源进行创建、读取和多次修改的完整逻辑流是完全正确的。

示例三

module net2dev_addr::StorageDemo {
    struct StakePool has key {
        amount: u64
    }

    fun add_user(account: &signer) {
        let amount: u64 = 0;
        move_to(account, StakePool { amount })
    }

    fun read_pool(account: address): u64 acquires StakePool {
        borrow_global<StakePool>(account).amount
    }

    fun stake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry += 100;
    }

    fun unstake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry = 0;
    }

    #[test_only]
    use std::debug::print;

    #[test_only]
    use std::signer;

    #[test_only]
    use std::string::utf8;

    #[test(user = @0x123)]
    fun test_function(user: signer) acquires StakePool {
        add_user(&user);
        assert!(borrow_global<StakePool>(@0x123).amount == 0, 0);
        assert!(read_pool(@0x123) == 0, 1);
        assert!(read_pool(signer::address_of(&user)) == 0, 2);
        print(&borrow_global<StakePool>(@0x123).amount);
        print(&read_pool(@0x123));
        print(&utf8(b"User Added Successfully!"));

        stake(@0x123);
        assert!(read_pool(@0x123) == 100, 3);
        stake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 200, 4);
        print(&utf8(b"User Staked Successfully!"));

        unstake(@0x123);
        assert!(read_pool(@0x123) == 0, 5);
        unstake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 0, 6);
        print(&utf8(b"User Unstaked Successfully!"));
    }
}

这段 Aptos Move 代码在 StorageDemo 模块中进一步完善了链上资源(resource)的生命周期管理,在原有的创建、读取和质押(增加金额)功能之上,新增了一个 unstake 函数。该函数同样利用 borrow_global_mut 获取对链上 StakePool 资源的可变引用,但其操作是amount 字段直接重置为 0,以此来模拟用户取回所有质押资金的“解押”操作。扩展后的单元测试则完整地验证了这一整套流程:从创建资源(amount为0),到两次质押使其逐步增加到 200,再到最终调用 unstake 函数使其回归到 0,并通过 assert! 在每一步都确认了链上状态的正确性。

测试

➜ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my-dapp
Running Move unit tests
[debug] 0
[debug] 0
[debug] "User Added Successfully!"
[debug] "User Staked Successfully!"
[debug] "User Unstaked Successfully!"
[ PASS    ] 0x48daaa7d16f1a59929ece03157b8f3e4625bc0a201988ac6fea9b38f50db5ef3::StorageDemo::test_function
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
  "Result": "Success"
}

这个测试结果表明,你的 my-dapp 项目已成功通过其全部单元测试。在成功编译项目后,测试框架执行了 StorageDemo 模块中定义的唯一一个测试函数 test_function。日志中的 [debug] 输出清晰地展示了测试成功执行了创建、质押、解押的完整流程,这一点由 "User Added Successfully!"、"User Staked Successfully!" 和 "User Unstaked Successfully!" 这三条消息得到确认。最终的 [ PASS ] 状态,结合 Test result: OK 的总结陈述,证明了测试中的所有 assert! 断言均已满足,验证了你代码中对链上资源进行创建、多次修改、再到最终重置的完整生命周期管理逻辑是完全正确的。

示例四

module net2dev_addr::StorageDemo {
    use std::signer;

    struct StakePool has key {
        amount: u64
    }

    fun add_user(account: &signer) {
        let amount: u64 = 0;
        move_to(account, StakePool { amount })
    }

    fun read_pool(account: address): u64 acquires StakePool {
        borrow_global<StakePool>(account).amount
    }

    fun stake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry += 100;
    }

    fun unstake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry = 0;
    }

    fun remove_user(account: &signer): u64 acquires StakePool {
        let entry = move_from<StakePool>(signer::address_of(account));
        let StakePool { amount } = entry;
        amount
    }

    fun confirm_user(account: address): bool {
        exists<StakePool>(account)
    }

    #[test_only]
    use std::debug::print;

    #[test_only]
    use std::string::utf8;

    #[test(user = @0x123)]
    fun test_function(user: signer) acquires StakePool {
        add_user(&user);
        assert!(borrow_global<StakePool>(@0x123).amount == 0, 0);
        assert!(read_pool(@0x123) == 0, 1);
        assert!(read_pool(signer::address_of(&user)) == 0, 2);
        print(&borrow_global<StakePool>(@0x123).amount);
        print(&read_pool(@0x123));
        print(&utf8(b"User Added Successfully!"));

        stake(@0x123);
        assert!(read_pool(@0x123) == 100, 3);
        stake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 200, 4);
        print(&utf8(b"User Staked Successfully!"));

        unstake(@0x123);
        assert!(read_pool(@0x123) == 0, 5);
        unstake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 0, 6);
        print(&utf8(b"User Unstaked Successfully!"));

        let amount = remove_user(&user);
        assert!(amount == 0, 7);
        assert!(!confirm_user(@0x123), 8);
        assert!(confirm_user(signer::address_of(&user)) == false, 9);
        print(&amount);
        print(&utf8(b"User Removed Successfully!"));
    }
}

这段 Aptos Move 代码在 StorageDemo 模块中完整地演示了一个链上资源从创建到销毁的全生命周期(CRUD)。在之前创建 (add_user)、读取 (read_pool) 和更新 (stake/unstake) 的功能基础上,新增了两个关键函数:confirm_user 函数使用 exists 指令来检查指定地址下是否存在资源;而 remove_user 函数则使用 move_from 指令来将资源从链上存储中彻底移除。最终,单元测试 test_function 完整地串联了所有操作:它首先创建、质押、解押资源,并在最后调用 remove_user 将其销毁,再通过 confirm_user 函数断言该资源已不再存在,从而完整地验证了 Move 语言中关于链上资源增、删、改、查的所有核心操作。

测试

➜ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my-dapp
Running Move unit tests
[debug] 0
[debug] 0
[debug] "User Added Successfully!"
[debug] "User Staked Successfully!"
[debug] "User Unstaked Successfully!"
[debug] 0
[debug] "User Removed Successfully!"
[ PASS    ] 0x48daaa7d16f1a59929ece03157b8f3e4625bc0a201988ac6fea9b38f50db5ef3::StorageDemo::test_function
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
  "Result": "Success"
}

这个测试结果表明,你的 my-dapp 项目已成功通过了其全部单元测试。在成功编译项目后,测试框架执行了 StorageDemo 模块中定义的唯一一个测试函数 test_function。日志中的 [debug] 输出清晰地展示了测试成功执行了创建、质押、解押、并最终移除的完整流程,这一点由 "User Added Successfully!" 直到 "User Removed Successfully!" 的四条连续消息得到确认。最终的 [ PASS ] 状态,结合 Test result: OK 的总结陈述,证明了测试中的所有 assert! 断言均已满足,验证了你代码中对链上资源进行创建、修改、重置和销毁的完整生命周期管理逻辑是完全正确的。

示例五

module net2dev_addr::StorageDemo {
    use std::signer;

    struct StakePool has key, drop {
        amount: u64
    }

    fun add_user(account: &signer) {
        let amount: u64 = 0;
        move_to(account, StakePool { amount })
    }

    fun read_pool(account: address): u64 acquires StakePool {
        borrow_global<StakePool>(account).amount
    }

    fun stake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry += 100;
    }

    fun unstake(account: address) acquires StakePool {
        let entry = &mut borrow_global_mut<StakePool>(account).amount;
        *entry = 0;
    }

    fun remove_user(account: &signer) acquires StakePool {
        move_from<StakePool>(signer::address_of(account));

    }

    fun confirm_user(account: address): bool {
        exists<StakePool>(account)
    }

    #[test_only]
    use std::debug::print;

    #[test_only]
    use std::string::utf8;

    #[test(user = @0x123)]
    fun test_function(user: signer) acquires StakePool {
        add_user(&user);
        assert!(borrow_global<StakePool>(@0x123).amount == 0, 0);
        assert!(read_pool(@0x123) == 0, 1);
        assert!(read_pool(signer::address_of(&user)) == 0, 2);
        print(&borrow_global<StakePool>(@0x123).amount);
        print(&read_pool(@0x123));
        print(&utf8(b"User Added Successfully!"));

        stake(@0x123);
        assert!(read_pool(@0x123) == 100, 3);
        stake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 200, 4);
        print(&utf8(b"User Staked Successfully!"));

        unstake(@0x123);
        assert!(read_pool(@0x123) == 0, 5);
        unstake(signer::address_of(&user));
        assert!(read_pool(signer::address_of(&user)) == 0, 6);
        print(&utf8(b"User Unstaked Successfully!"));

        remove_user(&user);
        assert!(!confirm_user(@0x123), 8);
        assert!(confirm_user(signer::address_of(&user)) == false, 9);
        print(&utf8(b"User Removed Successfully!"));
    }
}

这段 Aptos Move 代码通过 StorageDemo 模块,完整地演示了一个链上资源从创建、读取、更新到最终销毁的全生命周期。此版本的一个关键改动是在 StakePool 结构体上新增了 drop 能力,这使得资源在被移除后可以被安全地丢弃。因此,remove_user 函数的实现得以简化,它现在仅需调用 move_from 指令将资源从账户存储中移除即可,而无需再手动解构并返回其内部数据。最终的单元测试 test_function 完整地串联了所有操作:从创建资源,到反复质押和解押,再到最终调用 remove_user 将其销毁,并通过 confirm_user 函数断言资源已不存在,从而全面验证了 Move 语言中关于链上资源增、删、改、查的所有核心操作。

测试

➜ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my-dapp
Running Move unit tests
[debug] 0
[debug] 0
[debug] "User Added Successfully!"
[debug] "User Staked Successfully!"
[debug] "User Unstaked Successfully!"
[debug] "User Removed Successfully!"
[ PASS    ] 0x48daaa7d16f1a59929ece03157b8f3e4625bc0a201988ac6fea9b38f50db5ef3::StorageDemo::test_function
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
  "Result": "Success"
}

这个测试结果表明,你的 my-dapp 项目已成功通过其全部单元测试。在成功编译项目后,测试框架执行了 StorageDemo 模块中定义的唯一一个测试函数 test_function。日志中的 [debug] 输出清晰地展示了测试成功执行了创建、质押、解押、并最终移除的完整流程,这一点由 "User Added Successfully!" 直到 "User Removed Successfully!" 的四条连续消息得到确认。最终的 [ PASS ] 状态,结合 Test result: OK 的总结陈述,证明了测试中的所有 assert! 断言均已满足,验证了你代码中对链上资源进行创建、修改、重置和销毁的完整生命周期管理逻辑是完全正确的。

总结

恭喜你!跟随本篇详尽的实战教程,你已经完整地掌握了 Aptos Move 中链上资源(Resource)的全生命周期管理。我们一起学习并实践了 Move 语言中最核心的几个全局存储指令:

  • move_to:将资源存入账户。
  • borrow_global / borrow_global_mut读取修改已存在的资源。
  • move_from:将资源从账户中移除
  • exists:检查资源是否存在

这套以“资源”为中心、直接与用户账户绑定的存储模型,正是 Move 语言安全性的根基所在。现在,你已经具备了在 Aptos 链上自信地管理状态的能力,真正准备好去构建安全、强大的去中心化应用了。

参考

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论