一份优质的学习总结
撰写人背景:Web3 萌新,无 Rust 经验,无 Solidity 经验,没搞过 NFT,没做过 Dapp ref: https://mp.weixin.qq.com/s/4ucdWPhOuJagRGi6GiLcyg
在线调试:
共学课链接:
Web2 to 3 第一站,共学最新合约语言 Move | 706 Creators × NonceGeek
Aptos 是全新的 Layer 1 公链
ff: “diem 前身是 libra ,libra因为监管问题 更名为diem,之后 diem 卖给了meta 然后那伙人就出来搞了 aptos 和 sui 。“ 狗哥:”这种 Web3 狗血剧情请来一百集“
Move 认为 Token 资产是一种很特殊且重要的数据,不应该用普通的数值类型来定义和表示,所以单独创建了 Resource 来定义链上资产。这种方式呈现出三个特性:
综上所述,Move 是一种更加原生且贴合的专用于发行数字资产的编程语言,它实现了程序与数字资产的直接集成。
Modules
Scripts
About —— public(script)
public fun
: 方法可以在任何模块中被调用。public(script) fun
:script function 是模块中的入口方法,表示该方法可以通过控制台发起一个交易来调用,就像本地执行脚本一样public entry fun
替代 public(script) fun
public(script) fun init_counter(account: signer){
Self::init(&account)
}
public(script) fun incr_counter(account: signer) acquires Counter {
Self::incr(&account)
}
Move 里中文注释会报错,so write comments in English.
script{
use 0x1::Debug // import other modules, 0x1 is an offical modules library.
fun main(){
let num:u64 = 1024;
Debug::print(&num);
}
}
脚本执行:
Move 不支持字符串类型,只支持 ”数值、bool、和 address “ .
整形(无符号整形)
bool : true or false
address :
原生数据类型,只有一种 ability :drop
struct signer has drop { a: address }
Signer 代表发送交易的人
不能在代码中创建,必须通过脚本调用传递
use StarcoinFramework::Signer
,是使用标准库下的 Signer module,Signer 是一种原生的类似 Resource 的不可复制的类型,它包含了交易发送者的地址。引入 signer 类型的原因之一是要明确显示哪些函数需要发送者权限,哪些不需要。因此,函数不能欺骗用户未经授权访问其 Resource。具体可参考源码。
API:
address_of ( &Signer ): address
返回 signer 中的地址borrow_address(&Signer): &address
返回 SIgner 中地址的引用script {
use 0x1::Signer;
use 0x1::Debug;
fun main_signer( sn: signer) { // if multi params, place signer first
// let a: signer = @0x42; // wrong, no constructor for signer
let addr = Signer::address_of(&sn);
Debug::print(&addr);
}
}
Signer
是 librarysigner
是数据类型public fun init(account: &signer){
move_to(account, Counter{value:0});
}
如上,init
方法参数是一个 &signer
, 意味着该方法必须是一个账户合法签名过后才可以调用
API :
name | description | abort |
---|---|---|
move_ro<T>(&Signer, T) |
将 Resource T 发布给 Signer | if Signer 早已具有了 T |
move_from<T>(address): T |
删除 Signer 的 T 资源并返回 | if Signer 没有 T |
borrow_global_mut<T>(address): &mut T |
返回 Signer 下 T 的可变引用 | if Signer 没有 T |
borrow_global(address): &T |
返回 Signer 下 T 的不可变引用 | if Signer 没有 T |
exists <T>(address): bool |
返回 address 下的是否具有 T 类型的资源 | Never |
① move_to<T>(&signer, T)
:发布、添加类型为 T 的资源到 signer 的地址下。② exists<T>(address): bool
:判断地址下是否有类型为 T 的资源。。③ move_from<T>(addr: address): T
—— 从地址下删除类型为 T 的资源并返回这个资源。④ borrow_global< T >(addr: address): &T
—— 返回地址下类型为 T 的资源的不可变引用。⑤ borrow_global_mut< T >(addr: address): &mut T
—— 返回地址下类型为 T 的资源的可变引用。
// resouces/collection.move
module 0x1::collection{
use 0x1::Vector;
use 0x1::Signer;
struct Item has store, drop {}
// define resouce,
// abilities of resouce only store & kkry
struct Collection has store, key {
items: vector<Item>, // vector not Vector
}
// move resource to account.
public fun start_collection(account: &signer){
move_to<Collection>(account, Collection{
items: Vector::empty<Item>()
})
}
// judge exists ?
public fun exists_account(at: address): bool {
exists<Collection>(at)
}
}
vector<Item>
表示容器里面存放的是 Item
类型;// scripts/test-resource.move
script {
use 0x1::collection as cl;
use 0x1::Debug;
use 0x1::Signer;
fun test_resource(account: signer) {
cl::start_collection(&account);
let addr = Signer::address_of(&account);
let isExists = cl::exists_account(addr);
Debug::print(&isExists);
}
}
module 0x1::collection{
use 0x1::Vector;
use 0x1::Signer;
struct Item has store, drop {}
// define resouce,
// abilities of resouce only store & kkry
struct Collection has store, key {
items: vector<Item>, // vector not Vector
}
// move resource
public fun start_collection(account: &signer){
move_to<Collection>(account, Collection{
items: Vector::empty<Item>()
})
}
// judge exists ?
public fun exists_account(at: address): bool {
exists<Collection>(at)
}
// modify
// acquires return resource list
public fun add_item(account: &signer) acquires Collection{
// get the resource mutable quote
let addr = Signer::address_of(account);
let collection = borrow_global_mut<Collection>(addr);
Vector::push_back(&mut collection.items, Item{});
}
// return resources length
public fun size(account: &signer): u64 acquires Collection {
let addr = Signer::address_of(account);
let collection = borrow_global<Collection>(addr);
Vector::length(&collection.items)
}
}
测试:
① 取消 cl::start_collection(&account);
的注释,先创建
② 注释掉 cl::start_collection(&account);
,执行后续代码
script {
use 0x1::collection as cl;
use 0x1::Debug;
use 0x1::Signer;
fun test_resource(account: signer) {
cl::start_collection(&account);
// let addr = Signer::address_of(&account);
// let isExists = cl::exists_account(addr);
// Debug::print(&isExists);
let addr = Signer::address_of(&account);
cl::add_item(&account);
let lsize = cl::size(&account);
Debug::print(&lsize);
}
}
module 0x1::collection{
use 0x1::Vector;
use 0x1::Signer;
struct Item has store, drop {}
// ...
// Destroy
public fun destroy(account: &signer) acquires Collection{
let addr = Signer::address_of(account);
// remove collection from address
let collection = move_from<Collection>(addr);
// DESTROY:
let Collection{ items: _ } = collection;
}
}
是一种新的数据类型,可以指定 “能力 ability ”
struct Name [has ${ability}] { // 首字母大写
filed1: TYPE1,
filed2: TYPE2,
filed3: TYPE3,
}
举例—— 学生类型:
// sources/student.move
address 0x1 {
module student {
struct Empty {}
struct Student {
id: u64,
age: u8,
sex: bool,
}
public fun newStudent(id: u64, age: u8, sex: bool): Student { //
return Student {
id: id, // 或者直接写 id, 跟 js 一样
age: age, // age: age, means age ,
sex: sex, // sex
}
}
}
}
// scripts/05-struct.move
script {
use 0x1::student;
use 0x1::Debug;
fun main() {
let stu1 = student::newStudent(10001, 24, true);
let id = student::getId(stu1);
Debug::print(&id);
}
}
return 的那一句 不要加分号 ;
函数原型:
[public] fun funcName(param1: Type, ... ): ReturnType{
// body ...
}
[public]
是函数的访问权限,无 Public 则仅限于 module /ˈmɒdjuːl/ 内访问。在 Address 处定义一个地址空间 Sender,后续编译时,会自动将地址空间转换为地址 0x1。
// module 0x01::Math{
module Sender::Math{
public fun sum(a:u64, b:u64): u64 {
// return a+b // it's ok .
a + b // `return` can be omitted
}
}
script{
use 0x1::Debug;
// use 0x1::Math;
use Sender::Math;
fun testSum(a:u64,b:u64){
let c:u64 = Math::sum(a, b);
Debug::print(&c);
}
}
我发现传入的参数是 (a:u8, b:u8) , return 的也需要是 u8 类型
if 、 else
public fun max(a:u64, b:u64): u64 {
// a>b ? a : b
if (a >= b){
a
} else {
b
}
}
// 03-text-func.move
script {
use 0x1::Debug;
use:Sender::Math as MM;
fun textMax(a:u64, b:u64) {
Debug::print(&MM::max(a, b));
}
}
while :
// 计算 1 +3 +5 ...
public fun sum99() :u64 {
let idx:u64 = 0;
let res:u64 = 0;
while ( idx <= 99) {
idx = idx + 1; // not support `+=` ?
if ( idx % 2 == 0) continue;
res = res + idx
}; // Attention `;`
return res
}
// sources/Math.move
module Sender::Math {
public fun sum(a:u64, b:u64):u64 {
a + b // `+` has higher priority than `as`
}
public fun sum2(a:u64, b:u8):u64 {
a + (b as u64) // `+` has higher priority than `as`
}
}
// scripts/02-test-as.move
script {
use 0x1::Debug;
use Sender::Math;
fun testSum(a:u64, b:u8) {
let c:u64 = Math::sum2(a, b);
Debug::print(&c)
}
}
as 还可用于 module 令命名:
💡 use Sender::Math as SM;
script {
use 0x1::Debug;
use Sender::Math as SM;
fun testSum(a:u64, b:u8) {
let c:u64 = SM::sum2(a, b);
Debug::print(&c)
}
}
作用域和生命周期
全局常量:
const
module内 或 script 内
首字母 A-Z
局部变量:
地址类型,如果放到表达式里面,必须用 @
符号
script {
use 0x1::Debug;
use Sender::Math as SM;
fun testSum() {
// let c:u64 = SM::sum2(a, b);
//Debug::print(&SM::sum99());
let dd:address = @Sender;
Debug::print(&dd);
}
}
abort 断言( abort v. 退出, 舍弃)
用法 :abort 0
打断当前脚本的执行,打断完成后,脚本会恢复初始的状态。
assert 断言
assert!(condition, code)
如下示例,Debug::print(&temp);
没有在 abort(1)
后接着执行。
let temp:u8 = 10;
if (temp == 10) abort(1);
Debug::print(&temp);
使用 assert 同样可达到如上的效果:
loginStatus == false
, 则抛出 code == 401 Unauthorized
// let loginStatus:bool = false;
assert!(loginStatus, 401);
Debug::print(&loginStatus);
元组:
let (a, _) = (1, false);
// _ is 占位符 // let (a:u8, b:bool) = (1, false); // Error 不需要写类型... 迷惑 ...
let (a, b) = (1, false);
C++ 经常用
&
引用去替代指针*
。 因为指针会直接暴露地址,但是 quote 不会。
引用 quote :
Move 的 2 种引用方式:
不可变引用 & ( 只读,不可修改 —— 安全!
可变引用 &mut
&mut
*
代表解除引用,指向引用对应的值使用 quote 实现交换数值:
最大用处:间接赋值
// modules/Math.move
module Sender::Math {
// ..
public fun swap(a:&mut u64, b:&mut u64) { // no return
let temp = *a;
*a = *b;
*b = temp;
}
}
// scripts/02-test.move
script {
use 0x1::Debug;
use Sender::Math as SM;
fun testSum() {
let (a, b) = (1, 10);
SM::swap(&mut a, &mut b);
Debug::print(&a);
Debug::print(&b);
}
}
&mut a
将 a 解构为 可变引用fun swap
使用 ( a:&mut u64, ... )
来接受可变引用
*a
解除引用,此时 *a
指向引用对应的值即 1**fun funcName<Type>(x: Type)**
下面实现一个比较 low 的 show 函数,打印不同类型:
module Sender::Math {
use 0x1::Debug;
// ..
public fun show<T>(a: T) {
Debug::print(&a);
}
}
执行后发现报错:
The type 'T' does not have the ability 'drop’
加上 drop ability,Compile passed :
public fun show<T:drop>(a: T) {
Debug::print(&a);
}
后续使用结构体泛型实现进阶版的能力。
// modules/user.move
address 0x1 {
module user {
struct Student has drop {
id: u64,
age: u8,
sex: bool,
}
struct User<T1, T2> has drop {
id: T1,
age: T2,
}
public fun newUser<T1, T2>(id: T1, age: T2): User<T1, T2> {
return User { id , age }
}
}
}
// scripts/test.move
script {
use 0x1::user;
use 0x1::Debug;
fun main() {
// let user1 = user::newUser(10001, 23); // auto cognize
let user1 = user::newUser(10001: u64, 23: u8); // good
Debug::print(&user1)
}
}
vector 是 Move 提供的泛型容器
let str_ = b"hello"; // cast "hello" as Vector of Ascii
let v2 = Vector::empty<u64>();
Vector::push_back(&mut v2, 10);
let str_ = b"Hello World";
Debug::print(&str_);
SM::show(str_); // Attention No &str_ , but str_ , why ?
script {
use 0x1::Debug;
use Sender::Math as SM;
use 0x1::Vector; // maybe lowcase as 'vector', need test ?
fun testSum() {
let v2 = Vector::empty<u64>();
Vector::push_back<u64>(&mut v2, 1);
Vector::push_back<u64>(&mut v2, 10);
Debug::print(&v2);
}
}
终止:即会不会产生 abort 类型的终止异常
empty<T>(): vector<T> |
|
---|---|
singleton<T>(t: T):vector<T> |
|
borrow<T>(v: &vector<T>, i:u64): &T |
返回在 index i 处对 T 的不可变引用 |
Move 类型的几种能力
用 key,store 修饰,则表示它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。
struct Counter has key, store {
value:u64,
}
蓝色:整数、布尔等原生类型天然具有 copy、drop、store 能力。
public fun show<T:drop>(val: T) {
Debug::print(&val);
}
在调用 show 函数时, val 这个变量就归属于这个函数了, 函数执行完毕后,val 这个变量的生命周期就结束了,就需要被丢弃掉,所以编译器会要求 show 传递的类型 T 具有被 drop 的能力。
每个变量都有所有权
所有权的主人(属主)是 作用域
fun main() {
let tmp = 10;
Math::show(move tmp);
Math::show(tmp);
main 函数将 tmp 的属主转移给了 show 函数, main 就没有了变量的所有权。
后面 Math::show(tmp);
再次调用就会报错。
如下这 2 种写法都是没问题的。
fun main() {
let tmp = 10;
Math::show(move tmp);
Math::show(tmp);
let tmp = 10;
Math::show(copy tmp);
Math::show(copy tmp);
即 https://playground.pontem.network/ 的示例代码。
scripts
文件夹包含使用存储模块的示例。
Storage.move
利用 Move 语言泛型将用户帐户下的任何类型的数据存储为资源(resource)。
module Sender::Storage {
use Std::Signer;
//The resource would store `T` (generic) kind of data under `val` field.
struct Storage<T: store> has key {
val: T,
}
// Store the `val` under user account in `Storage` resource.
// `signer` - transaction sender.
// `val` - the value to store.
public fun store<T: store>(account: &signer, val: T){
// Get address of `signer` by utilizing `Signer` module of Standard Library
let addr = Signer::address_of(account);
// Check if resource isn't exists already, otherwise throw error with code 101.
assert!(!exists<Storage<T>>(addr), 101);
// Create `Storage` resource contains provided value.
let to_store = Storage{
val,
};
// 'Move' the Storage resource under user account,
// so the resource will be placed into storage under user account.
move_to(account, to_store);
// Get stored value under signer account.
// `signer` - transaction sender, which stored value.
public fun get<T: store>(account: &signer): T acquires Storage{
let addr = Signer::address_of(account);
assert!(exists<Storage<T>>(addr), 102);
let Storage { val } = move_from<(Storage)<T>>(addr);
return val
}
}
public fun store :
use Std::Signer;
struct Storage
:
public fun store
:
T: store
:要求 T 类型拥有 store 的能力。signer
是交易的发送者, val 是待存储的数据;Signer
模块获取 signer
的地址,let to_store = Storage{ val, };
即 {val: val ,}
跟 js 的 Object 一样move_to(account, to_store);
资源存储。
public fun get
acquires
关键字let Storage { val }
主要不是 let { val }
测试一下上面写的 Storage.move
在 ./scripts/store_bytes.move
中,这个脚本可以存储 bytes 数据:
script {
use Sender::Storage;
// Script to store `vector<u8>` (bytes).
fun store_bytes(account: signer, val: vector<u8>) {
Storage::store(&account, val);
}
}
这一个在没有 Aptos 框架的情况下,在 Move 中实现 Balance 和 Coin 逻辑的示例
module Sender::Coins {
use Std::Signer;
// In Move, all struct objects can have "abilities" from a hardcoded set of {copy, key, store, drop}.
struct Coins has store { val: u64 }
// resource object which is marked by `key` ability. It can be added to the Move Storage directly.
struct Balance has key {
// It contains an amount of `Coins` inside.
coins: Coins
}
// Error Code Definition
// 1. when `Balance` doesn't exist on account.
const ERR_BALANCE_NOT_EXISTS: u64 = 101;
// 2. Error when `Balance` already exists on account.
const ERR_BALANCE_EXISTS: u64 = 102;
// In Move you cannot directly create an instance of `Coin` from script,
// Instead you need to use available constructor methods. In general case, those methods could have some permission
// restrictions, i.e. `mint(acc, val: u64)` method would require `&signer` of coin creator as the first argument
// which is only available in transactions signed by that account.
//
// In the current example anyone can mint as many coins as want, but usually you can add restrictions (MintCapabilities),
// for details look at standard library (links in the herd of the file).
public fun mint(val: u64): Coins {
let new_coin = Coins{ val };
new_coin
}
/// If struct object does not have `drop` ability, it cannot be destroyed at the end of the script scope,
/// and needs explicit desctructuring method.
public fun burn(coin: Coins) {
let Coins{ val: _ } = coin;
}
public fun create_balance(acc: &signer) {
let acc_addr = Signer::address_of(acc);
assert!(!balance_exists(acc_addr), ERR_BALANCE_EXISTS);
let zero_coins = Coins{ val: 0 };
move_to(acc, Balance { coins: zero_coins});
}
// Check if `Balance` resource exists on account.
public fun balance_exists(acc_addr: address): bool {
exists<Balance>(acc_addr)
}
// Create `Balance` resource to account.
// In Move to store resource under account you have to provide user signature (`acc: &signer`).
// So before starting work with balances (use `deposit`, `withdraw`), account should add Balance resource
// on it's own account.
public fun create_balance(acc: &signer) {
let acc_addr = Signer::address_of(acc);
assert!(!balance_exists(acc_addr), ERR_BALANCE_EXISTS);
let zero_coins = Coins{ val: 0 };
move_to(acc, Balance { coins: zero_coins});
}
// Check if `Balance` resource exists on account.
public fun balance_exists(acc_addr: address): bool {
exists<Balance>(acc_addr)
}
// Deposit coins to user's balance (to `acc` balance).
public fun deposit(acc_addr: address, coin: Coins) acquires Balance {
assert!(balance_exists(acc_addr), ERR_BALANCE_NOT_EXISTS);
let Coins { val } = coin;
let balance = borrow_global_mut<Balance>(acc_addr);
balance.coins.val = balance.coins.val + val;
}
// Withdraw coins from user's balance (withdraw from `acc` balance).
public fun withdraw(acc: &signer, val: u64): Coins acquires Balance {
let acc_addr = Signer::address_of(acc);
assert!(balance_exists(acc_addr), ERR_BALANCE_NOT_EXISTS);
let balance = borrow_global_mut<Balance>(acc_addr);
balance.coins.val = balance.coins.val - val;
Coins{ val }
}
// Get balance of an account.
public fun balance(acc_addr: address): u64 acquires Balance {
borrow_global<Balance>(acc_addr).coins.val
}
}
在 Move 中,所有 struct object 都可以从 hardcoded set 的{copy, key, store, drop}
中获得 “abilities” 。
Balance Resource
代表存储在帐户下的用户余额,由 key
能力标记。它可以直接添加到移动存储中。
可以看到, Balance struct 里面调用了其上一步生成的 Coin( 可能是为了拓展性?)
fun burn
: 如果 struct 对象没有 drop
能力,它就不能在脚本范围结束时被销毁,所以需要显式的解构方法。
let Coins{ val: _} = coin
:这里用 _
来接受原来的 coin 信息,也就变相地对原来的 Coin 进行了销毁。create_balance
:创建 Balance
资源到帐户,在帐户下存储资源时,您必须提供user signature(acc: &signer
),所以在使用 deposit
, withdraw
等方法之前,account should add Balance resource on it's own account.
move_to(acc, Balance { coins: Coins{ val: 0 }});
:创建余额 即把一个余额为 0 的对象添加进去,完成创建余额的操作。public fun deposit
:存款,
borrow_global_mut<Balance>(acc_addr);
取回一个可变对象做修改—— 增加余额。public fun withdraw
:提现(取款)
return Coins{ val }
:提取的金额需要返回。对比 deposit 和 withdraw :
deposit(acc_addr: address)
传入的是一个 addresswithdraw(acc: &signer, val: u64)
传入的是一个 signermint.move
script {
use Std::Signer;
use Sender::Coins;
// Mint new coins and deposit to account.
fun mint(acc: signer, amount: u64) {
let acc_addr = Signer::address_of(&acc);
let coins = Coins::mint(amount);
if (!Coins::balance_exists(acc_addr)) {
Coins::create_balance(&acc);
};
Coins::deposit(acc_addr, coins);
assert!(Coins::balance(acc_addr) == amount, 1);
}
}
转账:从 acc 账号 withdraw 出 Coins,然后 deposit 存到 recipient 账号中去。
script{
use Sender::Coins;
// Script to mint coins and deposit coins to account.
// Recipient should has created `Balance` resources on his account.
fun mint_and_deposit(acc: signer, recipient: address, amount: u64){
let coins = Coins::withdraw(&acc, amount);
Coins::deposit(recipient, coins);
}
}
测试函数:mint_and_deposit(0x41, 0x51, 25)
但是贸然这样执行会报错:
因为 0x51 还没有余额 Balance。
mint_coins(0x51, 0)
添加余额
然后就可以执行转账了:
mint_and_deposit(0x41, 0x51, 25)
sources/AptosCoin.move
有点难,先不搞了
球星卡信息 - 功能:
address 0x1 {
module football {
use Std::Signer;
// error code
const STAR_ALREADY_EXISTS: u64 = 100;
const STAR_NOT_EXISTS: u64 = 101;
struct FootBallStar has key {
name: vector<u8>,
country: vector<u8>,
position: u8,
value: u64
}
public fun newStar(
name: vector<u8>,
country: vector<u8>,
position: u8
): FootBallStar {
FootBallStar {
name, country, position,
value: 0
}
}
public fun mint(recipient: &signer, card: FootBallStar) {
let addr = Signer::address_of(recipient);
assert!(!exists<FootBallStar>(addr), STAR_ALREADY_EXISTS);
move_to<FootBallStar>(recipient, card);
}
// Query, dont need signer, address is ok.
public fun get(owner: address, ): (vector<u8>, u64) acquires FootBallStar {
// query dont need assert
let card = borrow_global<FootBallStar>(owner);
(card.name, card.value) // return a tuple
}
// modify need to get the real data, so acquires ..
public fun setPrice(owner: address, price: u64) acquires FootBallStar {
assert!(exists<FootBallStar>(owner), STAR_NOT_EXISTS);
let card = borrow_global_mut<FootBallStar>(owner);
card.value = price;
}
// in every transaction, price will go rise $20
public fun transfer(owner: address, recipient: &signer) acquires FootBallStar {
assert!(exists<FootBallStar>(owner), STAR_NOT_EXISTS); // is owner hold it ?
let card = move_from<FootBallStar>(owner);
card.value = card.value + 20;
let reci_address = Signer::address_of(recipient);
assert!(!exists<FootBallStar>(owner), STAR_ALREADY_EXISTS); // is recipient hold it ?
move_to<FootBallStar>(recipient, card);
}
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!