Move 与 Aptos 开发笔记

Move 是面向数字资产的智能合约语言。它通过资源类型、所有权和能力约束减少资产被复制、重复使用或意外销毁的风险。Aptos Move 在基础 Move 语言之上提供账户、对象、事件和框架模块。

语言核心思想

  • 资源安全:具有 key 能力的资源可进入全局存储,不能被隐式复制或丢弃。
  • 所有权转移:值被移动后,原变量不能继续使用。
  • 显式能力:copydropstorekey 控制类型允许执行的操作。
  • 模块隔离:资源只能由定义它的模块直接创建和破坏。
  • 静态验证:大量错误在编译阶段被拒绝,便于形式化分析。
1
2
3
4
5
6
7
flowchart LR
A[源代码] --> B[解析与类型检查]
B --> C[借用与能力检查]
C --> D[编译为 Move 字节码]
D --> E[字节码验证]
E --> F[虚拟机执行]
F --> G[更新链上资源]

模块、入口函数与脚本

模块用于定义结构体、函数、常量和事件,是长期部署的代码单元。入口函数可由交易直接调用。脚本是一次性交易逻辑,可以调用公开模块函数,但新项目通常优先使用模块中的 entry 函数组织入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module lesson::counter {
use std::signer;

struct Counter has key {
value: u64,
}

public entry fun create(account: &signer) {
move_to(account, Counter { value: 0 });
}

public entry fun increase(account: &signer) acquires Counter {
let address = signer::address_of(account);
let counter = borrow_global_mut<Counter>(address);
counter.value = counter.value + 1;
}

#[view]
public fun value_of(owner: address): u64 acquires Counter {
borrow_global<Counter>(owner).value
}
}

能力说明

能力 作用
copy 允许按值复制
drop 允许值在作用域结束时被丢弃
store 允许作为字段存入其他结构体
key 允许作为顶层资源进入全局存储

普通整数和布尔值通常可复制与丢弃。资产类结构体不应随意拥有 copydrop,否则可能破坏唯一性约束。

值移动与借用

1
2
3
4
5
6
7
8
9
10
11
12
13
module lesson::ownership {
struct Wallet has store {
balance: u64,
}

public fun demo() {
let wallet = Wallet { balance: 100 };
let wallet2 = wallet;
// 此处不能再读取 wallet,因为所有权已经移动到 wallet2。
let Wallet { balance } = wallet2;
assert!(balance == 100, 1);
}
}

不可变引用使用 &T,可变引用使用 &mut T。同一数据在存在可变借用时不能再被其他引用访问,这一约束用于避免并发修改和悬空引用。

基础类型与字符串

Move 常用基础类型包括 boolu8u16u32u64u128u256addressvector<T>。Move 不提供普通浮点数,金融计算通常采用整数定点数。

1
2
3
4
5
6
use std::string::{Self, String};

let enabled: bool = true;
let amount: u64 = 1024;
let target: address = @0x2a;
let message: String = string::utf8(b"hello world");

不同整数位宽之间不能隐式运算,需要显式转换并确认目标类型能够容纳原值。

Windows 与 WSL 环境

Windows 可在管理员 PowerShell 中安装 WSL,并将 WSL 2 作为默认版本:

1
2
3
wsl --install
wsl --set-default-version 2
wsl --status

系统需要启用硬件虚拟化与虚拟机平台。安装完成后,可在 WSL 中运行 Linux 工具链,也可直接使用 Windows 版本的 Aptos CLI。具体安装包与命令应以所用 CLI 版本的官方文档为准。

WSL 原始安装记录

Aptos CLI 与项目初始化

aptos 可执行文件所在目录加入 PATH 后,先验证命令:

1
2
aptos help
aptos --version

初始化 Move 包与本地配置:

1
2
3
4
aptos move init --name lesson
aptos init
aptos move compile
aptos move test

常见项目结构如下:

1
2
3
4
5
6
7
lesson/
├── Move.toml
├── sources/
│ └── counter.move
├── tests/
└── .aptos/
└── config.yaml

Move.toml 保存包名、命名地址和依赖。sources 存放模块源码。.aptos/config.yaml 可能包含私钥和网络配置,不应提交到公开仓库。

Move.toml 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[package]
name = "lesson"
version = "1.0.0"

[addresses]
lesson = "_"

[dev-addresses]
lesson = "0x42"

[dependencies.AptosFramework]
git = "https://github.com/aptos-labs/aptos-core.git"
rev = "mainnet"
subdir = "aptos-move/framework/aptos-framework"

命名地址写为 _ 时,编译或发布命令必须从开发地址、配置文件或 --named-addresses 参数获得实际值。若地址已经写成固定的十六进制值,通常无需再次提供。

1
2
aptos move compile --named-addresses lesson=0x42
aptos move test --named-addresses lesson=0x42

依赖下载故障

首次编译可能需要拉取 Git 依赖。出现网络失败时,应依次检查代理、DNS、Git 是否可用、依赖仓库地址和分支是否存在。已有完整缓存时,可尝试跳过最新依赖获取:

1
aptos move compile --skip-fetch-latest-git-deps

该参数不能修复缺失或损坏的缓存。若需使用本地依赖,应在 Move.toml 中明确配置本地路径,并固定经过验证的版本。不要直接在依赖缓存目录中执行破坏性重置,因为缓存可能被多个项目共享。

单元测试

1
2
3
4
5
6
7
8
9
10
11
#[test_only]
module lesson::counter_tests {
use lesson::counter;

#[test(account = @0x42)]
fun create_and_increase(account: signer) {
counter::create(&account);
counter::increase(&account);
assert!(counter::value_of(@0x42) == 1, 1001);
}
}

测试应覆盖正常路径、重复初始化、资源不存在、权限不足、整数边界和中止码。发布前还应执行格式化、静态检查和本地网络交互测试。

发布流程

1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram
participant Dev as 开发者
participant CLI as Aptos CLI
participant Node as Aptos 节点
participant VM as Move VM
Dev->>CLI: 编译与测试
CLI->>CLI: 生成字节码与元数据
Dev->>CLI: 发起发布交易
CLI->>Node: 签名并提交交易
Node->>VM: 验证并执行字节码
VM-->>Node: 写入模块与资源
Node-->>Dev: 返回交易结果

发布前必须确认网络、发布账户、命名地址、Gas 预算和依赖版本。模块升级还需要检查兼容性规则,避免修改已发布资源的存储布局。

原始操作截图