Write Contracts

基于Aptos Move V2标准生成安全的智能合约,支持NFT、DAO及去中心化应用。

已扫描
适合谁
区块链开发人员、Aptos生态项目开发者
不适合谁
无编程基础的普通用户、非Aptos平台的开发者
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @iskysun96/write-contracts

Skill 说明

命令、参数、文件名以原文为准

Write Contracts Skill

核心规则

数字资产(NFTs)⭐ 重要

  1. 所有与 NFT 相关的合约(如集合、市场、铸造)必须使用数字资产(DA)标准
  2. 始终导入 aptos_token_objects::collectionaptos_token_objects::token 模块
  3. 始终使用 Object<AptosToken> 来引用 NFT(不要使用通用的 Object<T>
  4. 绝不使用已弃用的 TokenV1 标准或 aptos_token::token 模块
  5. 参考 ../../../patterns/move/DIGITAL_ASSETS.md 获取完整的 NFT 设计模式

对象模型

  1. 始终使用 Object<T> 来表示所有对象引用(绝不要使用原始地址)
  2. 在构造函数销毁前,生成所有所需的引用(TransferRef、DeleteRef)
  3. 构造函数返回 Object<T>(绝不要返回 ConstructorRef)
  4. 通过以下方式验证所有权object::owner(obj) == signer::address_of(user)
  5. 使用 object::generate_signer(&constructor_ref) 来获取对象签名者
  6. 为单例对象使用命名对象object::create_named_object(creator, seed)

安全性

  1. 在入口函数中始终验证签名者权限

assert!(signer::address_of(user) == expected, E_UNAUTHORIZED)

  1. 始终验证输入参数:非零金额、地址有效性、字符串长度检查
  2. 绝不暴露 &mut 引用给公共函数
  3. 绝不跳过入口函数中的签名者验证

现代语法

  1. 使用内联函数和 lambda 表达式进行迭代操作
  2. 使用接收者风格的方法调用obj.is_owner(user)(将第一个参数定义为 self
  3. 使用向量索引语法vector[index] 而不是 vector::borrow()
  4. 直接使用命名地址@marketplace_addr(不要使用辅助函数)

必需的设计模式

  1. 使用 init_module 在部署时初始化合约
  2. 对所有重要操作发出事件(创建、转移、更新、删除)
  3. 定义清晰的错误常量,并使用描述性名称(如 E_NOT_OWNER、E_INSUFFICIENT_BALANCE)

可测试性

  1. 为结构体字段添加访问器函数——测试模块无法直接访问结构体字段
  2. **使用 #[view] 注解**标记只读访问器函数
  3. 访问器函数返回元组以支持多字段访问:(seller, price, timestamp)
  4. **将 #[view] 放在文档注释之前**——若 /// comment 写在 #[view] 前面,会导致编译警告。应先写 #[view],再写 ///

快速工作流程

  1. 创建模块结构 → 定义结构体、事件、常量、init_module
  2. 实现对象创建 → 使用正确的构造函数模式,确保所有引用在构造函数销毁前生成
  3. 添加访问控制 → 验证所有权并校验所有输入
  4. 安全检查 → 部署前使用 security-audit 技能进行审查

关键示例:对象创建模式

struct MyObject has key {
    name: String,
    transfer_ref: object::TransferRef,
    delete_ref: object::DeleteRef,
}

// 错误常量
const E_NOT_OWNER: u64 = 1;
const E_EMPTY_STRING: u64 = 2;
const E_NAME_TOO_LONG: u64 = 3;

// 配置常量
const MAX_NAME_LENGTH: u64 = 100;

/// 使用正确模式创建对象
public fun create_my_object(creator: &signer, name: String): Object<MyObject> {
    // 1. 创建对象
    let constructor_ref = object::create_object(signer::address_of(creator));

    // 2. 在构造函数销毁前生成所有需要的引用
    let transfer_ref = object::generate_transfer_ref(&constructor_ref);
    let delete_ref = object::generate_delete_ref(&constructor_ref);

    // 3. 获取对象签名者
    let object_signer = object::generate_signer(&constructor_ref);

    // 4. 将数据存入对象
    move_to(&object_signer, MyObject {
        name,
        transfer_ref,
        delete_ref,
    });

    // 5. 返回类型化的对象引用(ConstructorRef 自动销毁)
    object::object_from_constructor_ref<MyObject>(&constructor_ref)
}

/// 更新操作,包含所有权验证
public entry fun update_object(
    owner: &signer,
    obj: Object<MyObject>,
    new_name: String
) acquires MyObject {
    // ✅ 始终:验证所有权
    assert!(object::owner(obj) == signer::address_of(owner), E_NOT_OWNER);

    // ✅ 始终:验证输入
    assert!(string::length(&new_name) > 0, E_EMPTY_STRING);
    assert!(string::length(&new_name) <= MAX_NAME_LENGTH, E_NAME_TOO_LONG);

    // 安全继续
    let obj_data = borrow_global_mut<MyObject>(object::object_address(&obj));
    obj_data.name = new_name;
}

关键示例:用于测试的访问器函数

struct ListingInfo has store, drop, copy {
    seller: address,
    price: u64,
    listed_at: u64,
}

/// 访问器函数 - 测试无法直接访问结构体字段
/// 多字段访问使用元组返回
#[view]
public fun get_listing_details(nft_addr: address): (address, u64, u64) acquires Listings {
    let listings = borrow_global<Listings>(get_marketplace_address());
    assert!(table::contains(&listings.items, nft_addr), E_NOT_LISTED);
    let listing = table::borrow(&listings.items, nft_addr);
    (listing.seller, listing.price, listing.listed_at)
}

/// 单字段访问器,仅需一个值时使用
#[view]
public fun get_staked_amount(user_addr: address): u64 acquires Stakes {
    let stakes = borrow_global<Stakes>(get_vault_address());
    if (table_with_length::contains(&stakes.items, user_addr)) {
        table_with_length::borrow(&stakes.items, user_addr).amount
    } else {
        0
    }
}

模块结构模板

module my_addr::my_module {
    // ============ Imports ============
    use std::signer;
    use std::string::String;
    use aptos_framework::object::{Self, Object};
    use aptos_framework::event;

    // ============ Events ============
    #[event]
    struct ItemCreated has drop, store {
        item: address,
        creator: address,
    }

    // ============ Structs ============
    // 定义你的数据结构

    // ============ Constants ============
    const E_NOT_OWNER: u64 = 1;
    const E_UNAUTHORIZED: u64 = 2;

    // ============ Init Module ============
    fun init_module(deployer: &signer) {
        // 初始化全局状态、注册表等
    }

    // ============ Public Entry Functions ============
    // 用户可见的函数

    // ============ Public Functions ============
    // 可组合的函数

    // ============ Private Functions ============
    // 内部辅助函数
}

存储类型选择

⚠️ 当用户提到存储相关需求(“store”、“track”、“registry”、“mapping”、“list”、“collection”):

1. 提出 2-3 个问题(参考 references/storage-decision-tree.md

  • 访问模式?(顺序访问 vs 键值对访问 vs 两者兼有)
  • 预期规模?(小规模 vs 大规模 vs 不确定)
  • **是否需要 .length()?**(条件性判断)

2. 根据模式推荐(参考 references/storage-patterns.md

模式推荐的存储类型
用户注册表Table<address, UserInfo>
质押记录Table<address, StakeInfo>
排行榜BigOrderedMap<u64, address>
交易日志SmartVector<TxRecord>Vector
白名单(少于100)Vector<address>
投票记录TableWithLength<address, bool>
配置项(少于50)OrderedMap<String, Value>
DAO提案BigOrderedMap<u64, Proposal>
资产集合Vector<Object<T>>SmartVector

3. 包含简要的Gas成本说明

示例推荐:

  • “对于质押场景,建议使用 Table<address, StakeInfo>,因为用户数量可能无上限且存在并发操作(独立槽位支持并行访问)”
  • “对于排行榜,建议使用 BigOrderedMap<u64, address>,因为需要有序遍历(时间复杂度 O(log n),生产环境请使用 allocate_spare_slots)”

可用的存储类型

  • Vector - 小规模顺序存储(少于100项)
  • SmartVector - 大规模顺序存储(100项以上)
  • Table - 无序键值查找
  • TableWithLength - 带长度统计的表
  • OrderedMap - 小规模有序映射(少于100项)
  • BigOrderedMap - 大规模有序映射(100项以上)

⚠️ 禁止使用 SmartTable(已弃用,应使用 BigOrderedMap)

详细信息: 参见 references/ 目录中的决策树、类型对比及Gas优化指南。

反模式

  1. 永远不要使用旧版 TokenV1 标准 或导入 aptos_token::token
  2. 永远不要使用资源账户(应使用命名对象替代)
  3. 永远不要从公开函数返回 ConstructorRef
  4. 永远不要跳过 signer 验证 在入口函数中
  5. 永远不要跳过输入验证(金额、地址、字符串等)
  6. 永远不要在未达到100%测试覆盖率的情况下部署
  7. 永远不要创建仅返回命名地址的辅助函数
  8. 永远不要跳过重要操作的事件发出
  9. 永远不要使用旧语法,当 V2 语法可用时
  10. 永远不要跳过 init_module,对于需要初始化的合约
  11. 永远不要在代码中硬编码真实私钥或敏感信息 —— 应使用 @my_addr 命名地址和 "0x..." 占位符
  12. ❌ **永远不要读取 .env~/.aptos/config.yaml** —— 这些文件包含私钥

边界情况处理

场景检查条件错误码
零金额assert!(amount > 0, E_ZERO_AMOUNT)E_ZERO_AMOUNT
金额过大assert!(amount <= MAX, E_AMOUNT_TOO_HIGH)E_AMOUNT_TOO_HIGH
空向量assert!(vector::length(&v) > 0, E_EMPTY_VECTOR)E_EMPTY_VECTOR
空字符串assert!(string::length(&s) > 0, E_EMPTY_STRING)E_EMPTY_STRING
字符串过长assert!(string::length(&s) <= MAX, E_STRING_TOO_LONG)E_STRING_TOO_LONG
零地址assert!(addr != @0x0, E_ZERO_ADDRESS)E_ZERO_ADDRESS
溢出assert!(a <= MAX_U64 - b, E_OVERFLOW)E_OVERFLOW
下溢assert!(a >= b, E_UNDERFLOW)E_UNDERFLOW
除零assert!(divisor > 0, E_DIVISION_BY_ZERO)E_DIVISION_BY_ZERO
未经授权访问assert!(signer == expected, E_UNAUTHORIZED)E_UNAUTHORIZED
非所有者操作assert!(object::owner(obj) == user, E_NOT_OWNER)E_NOT_OWNER

参考资料

详细模式说明(references/ 文件夹):

  • references/storage-decision-tree.md - ⭐ 存储类型选择框架(当提及存储时使用)
  • references/storage-patterns.md - ⭐ 使用场景模式与智能默认值
  • references/storage-types.md - 所有6种存储类型的详细对比
  • references/storage-gas-optimization.md - 存储相关的Gas优化策略
  • references/object-patterns.md - 命名对象、集合、嵌套对象模式
  • references/access-control.md - RBAC 和权限控制系统
  • references/safe-arithmetic.md - 溢出/下溢防护
  • references/initialization.md - init_module 模式与注册表创建
  • references/events.md - 事件发出的最佳实践
  • references/v2-syntax.md - Move V2 的现代特性(方法调用、索引、匿名函数)
  • references/complete-example.md - 完整注释的NFT集合合约示例

模式文档(patterns/ 文件夹):

  • ../../../patterns/move/DIGITAL_ASSETS.md - 数字资产(NFT)标准 —— 对 NFT 至关重要
  • ../../../patterns/move/OBJECTS.md - 完整的对象模型指南
  • ../../../patterns/move/SECURITY.md - 安全检查清单与设计模式
  • ../../../patterns/move/MOVE_V2_SYNTAX.md - 现代语法示例

官方文档:

  • 数字资产标准:https://aptos.dev/build/smart-contracts/digital-asset
  • 对象模型:https://aptos.dev/build/smart-contracts/object
  • 安全指南:https://aptos.dev/build/smart-contracts/move-security-guidelines

相关技能:

  • search-aptos-examples - 在 aptos-core 中查找类似示例(可选)
  • generate-tests - 为合约编写测试(在编写合约后使用)
  • security-audit - 部署前对合约进行安全审计
I
@iskysun96

已收录 1 个 Skill

相关推荐