Move 基础语法 | 共学课优秀学习笔记

  • 李大狗
  • 更新于 2023-01-03 12:40
  • 阅读 2022

一份优质的学习总结

撰写人背景:Web3 萌新,无 Rust 经验,无 Solidity 经验,没搞过 NFT,没做过 Dapp ref: https://mp.weixin.qq.com/s/4ucdWPhOuJagRGi6GiLcyg

在线调试:

https://playground.pontem.network/

共学课链接:

Web2 to 3 第一站,共学最新合约语言 Move | 706 Creators × NonceGeek

0x00 Aptos

Aptos 是全新的 Layer 1 公链

ff: “diem 前身是 libra ,libra因为监管问题 更名为diem,之后 diem 卖给了meta 然后那伙人就出来搞了 aptos 和 sui 。“ 狗哥:”这种 Web3 狗血剧情请来一百集“

0x01 Why Move ?

  • 面向资源编程
  • 原生态避免双花
  • 安全和形式化验证

Move 认为 Token 资产是一种很特殊且重要的数据,不应该用普通的数值类型来定义和表示,所以单独创建了 Resource 来定义链上资产。这种方式呈现出三个特性:

  1. Resource 在 Move 中依然是以一个数值的形式存在,可以作为数据结构被储存,也可以作为参数被传递和返回。
  2. Resource 可以安全的表示数字资产,它的特殊在于不能被复制,丢弃或重用,但是它却可以被安全地存储和转移,并且 Resource 类型的值只能由定义该类型的模块创建和销毁,所以其实现了资产的含义而非数字。
  3. Resource 适配了区块链应用特性,如与账户进行绑定。Resource 数据必须要存储在账户下面,所以只有分配了账户后才会存在对应的 Resource 资产,以及 Resource 只要取出后就必须被“使用”,用内置的 Move_form 方法将资产从账户中取出后,要么将其作为返回值传递即必须要流向一个地方,要么直接将其销毁,这意味着资产取多少就用多少。还记得 Solidity 是如何操作的吗?它将一个地址的余额减少,再去另外一个地址增加,然后通过代码使得减少和增加的数字是一致的,所以在 Solidity 是完全靠代码逻辑强硬的实现了资产使用,但是 Resource 则是在底层将资产的概念进行了封装而非加减法,避免了资产凭空产生和随意访问,极大的提高了安全性,可以将 Move 的 Token 移动看作是搬砖,从一个地方搬到另一处,而 Solidity 则是加减法,一处减了,另一处加上。

综上所述,Move 是一种更加原生且贴合的专用于发行数字资产的编程语言,它实现了程序与数字资产的直接集成。

0x02 Move的结构

  • Modules

    • 结构化定义
    • 函数功能
    • 全局存储
  • Scripts

    • 暂时的代码片段
    • 可以调用Modules的函数
    • 1 个 Script 内只能有 1 个函数

About —— public(script)

  • public fun : 方法可以在任何模块中被调用。
  • public(script) fun:script function 是模块中的入口方法,表示该方法可以通过控制台发起一个交易来调用,就像本地执行脚本一样
  • 下个版本的 Move 会public entry fun 替代 public(script) fun
  • Self 则是代表自身module。
  public(script) fun init_counter(account: signer){  
        Self::init(&account)  
     }  

     public(script) fun incr_counter(account: signer)  acquires Counter {  
        Self::incr(&account)  
     }

0x03 Variables & First demo

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);
  }
}

脚本执行:

  1. 左侧边栏找到 "Run Script"
  2. 键入 main()
  3. console outputs "debug:1024",and Gas cost.”

数值、bool、address

Move 不支持字符串类型,只支持 ”数值、bool、和 address “ .

  • 整形(无符号整形)

    • u8 : unsigned 8 bits (2^8 = 256
    • u64 : unsigned 64 bits (2^64 = 18,446,744,073,709,551,616)
    • u128
  • bool : true or false

  • address :

    • Std / 0x1 / Sender 等都是 address 类型

signer 签署者

  • 原生数据类型,只有一种 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 是 library
  • signer 是数据类型
  • 如果要穿多个参数,把 signer 类型放在第一个。
  • signer 不能创建(没有构造函数),只能通过传递值调用。
public fun init(account: &signer){  
    move_to(account, Counter{value:0});  
}

如上,init 方法参数是一个 &signer, 意味着该方法必须是一个账户合法签名过后才可以调用

Resource 资源

  • Resource 是被限制了 ability 的 struct
  • Resource 只具有 key 和 store 2 种 ability
  • Resource 必须存储在账户下面。
  • 一个账户同一时刻只能容纳一个资源

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

create、move、Query Resource

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);
  }
}

Modify Resource

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);
  }
}

Destroy Resource

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;
  }
}

struct

是一种新的数据类型,可以指定 “能力 ability ”

struct Name [has ${ability}] {   // 首字母大写
  filed1: TYPE1,
  filed2: TYPE2,
  filed3: TYPE3,
}
  • 字段个数可以从 0~65535
  • 类型可以是原生类型 、自定义类型,但是不允许类型递归。

举例—— 学生类型:

//  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);
  }
}
  • address 0x1 是另一种(配置 address)的写法
  • struct Empty {} 说明可以建一个空的 Struct , ps:后面不需要加分号 ;

0x04 Function ( fun ~)

return 的那一句 不要加分号 ;

函数原型:

[public] fun funcName(param1: Type, ... ): ReturnType{
  // body ...
}
  • [public] 是函数的访问权限,无 Public 则仅限于 module /ˈmɒdjuːl/ 内访问。
  • 返回值可以使多个,要用 () wrap 起来

在 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 类型

0x05 Loop & break

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 :

  • continue ; 跳出本次循环
  • break; 直接挑出整个循环
// 计算 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
}

0x06 as 类型转换

//  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)
  }
}

0x07 Scope and Lifecycle

作用域和生命周期

全局常量:

  • const

  • module内 或 script 内

  • 首字母 A-Z

    • Constant names must start with 'A...Z’

局部变量:

  • let 、 函数内部 、 首字母 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);
  }
}

图片

0x08 Abort Assert Control

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);

0x09 Tuple & quote 元组和引用

元组:

  1. 用小括号去组合多个变量,例如 (x, y) (10, false)
  2. 函数多个返回值的定义和接受
  3. 多个变量的同时定义 let (a, _) = (1, false); // _ is 占位符
 // let (a:u8, b:bool) = (1, false);  // Error 不需要写类型...  迷惑 ...
    let (a, b) = (1, false);

C++ 经常用 & 引用去替代指针 * 。 因为指针会直接暴露地址,但是 quote 不会。

引用 quote :

  • quote 定义变量的别名
  • quote 可以避免直接暴露地址

Move 的 2 种引用方式:

  1. 不可变引用 & ( 只读,不可修改 —— 安全!

  2. 可变引用 &mut

    1. 左值用于赋值 ( 需要 &mut
    2. 右值用于读取
    3. * 代表解除引用,指向引用对应的值

使用 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);
  }
}
  1. &mut a 将 a 解构为 可变引用
  2. fun swap 使用 ( a:&mut u64, ... ) 来接受可变引用
  3. *a 解除引用,此时 *a 指向引用对应的值即 1
  4. 交换,结束。

0x0A generices 泛型

  • 泛型逻辑与类型无关;
  • 泛型允许 coder 在强类型设计语言中使用一些“以后” 才指定的类型,在实例化时作为参数指明这些类型,
  • 也就是说,类型没有提前确定,而是在执行阶段才知道类型是什么,所以存在不确定、不安全性
**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);
  }

后续使用结构体泛型实现进阶版的能力。

泛型结构体 struct

// 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)
  }
}

0x0B Vector

vector 是 Move 提供的泛型容器

let str_ = b"hello";             // cast "hello" as Vector of Ascii
let v2 = Vector::empty<u64>();
Vector::push_back(&mut v2, 10);
  1. 打印 “字符串”
    let str_ = b"Hello World";
    Debug::print(&str_);
    SM::show(str_);   // Attention No &str_ , but str_ , why ?
  1. 向 Vector Push Value:
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);
  }
}

图片

Vector API

终止:即会不会产生 abort 类型的终止异常

empty<T>(): vector<T>
singleton<T>(t: T):vector<T>
borrow<T>(v: &vector<T>, i:u64): &T 返回在 index i 处对 T 的不可变引用

图片图片

0x0C 类型能力

Move 类型的几种能力

  • Copy 被修饰的值可以被复制。
  • Drop 被修饰的值在作用域结束时可以被丢弃。
  • Key 被修饰的值可以作为键值对全局状态进行访问。(对应另外一个 value 即 store , key-store 是一对
  • Store 被修饰的值可以被存储到全局状态。

用 key,store 修饰,则表示它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。

struct Counter has key, store {  
 value:u64,  
}

蓝色:整数、布尔等原生类型天然具有 copy、drop、store 能力。

Drop

public fun show<T:drop>(val: T) {
  Debug::print(&val);
}

在调用 show 函数时, val 这个变量就归属于这个函数了, 函数执行完毕后,val 这个变量的生命周期就结束了,就需要被丢弃掉,所以编译器会要求 show 传递的类型 T 具有被 drop 的能力

0x0D 所有权问题

  • 每个变量都有所有权

  • 所有权的主人(属主)是 作用域

    • move 操作:主人(属主)转移操作
    • copy 拷贝值
  fun main() {
    let tmp = 10;
    Math::show(move tmp);
    Math::show(tmp);

main 函数将 tmp 的属主转移给了 show 函数, main 就没有了变量的所有权。

后面 Math::show(tmp); 再次调用就会报错。

如下这 2 种写法都是没问题的。

  • Debug::print(&id); : 把 id 作为一个不可更改的引用传过去的。可以避免复制。
  • 使用 move 可以控制资源。
  • move 设计理念:一个资产设计出来,是不能被复制的,只能被传递。
  fun main() {
    let tmp = 10;
    Math::show(move tmp);
    Math::show(tmp);

let tmp = 10;
    Math::show(copy tmp);
    Math::show(copy tmp);

0x0F Storage 分析

https://playground.pontem.network/ 的示例代码。

scripts 文件夹包含使用存储模块的示例。

sources/Storage.move

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;

    • 使用标准库下的 Signer 数据类型
  • struct Storage :

    • 该资源将在 “val” 字段下存储 “T” 泛型的数据。
    • 泛型结构体拥有 key - 存储的能力。
  • public fun store :

    • 因为数据是要存储到 account 上的,一个账户下只能存储一个资源,所以如果 account 下已经有资源了,就要跑出错误码。
    • 将用户帐户下的 “val” 存储在 “Storage” 结构体资源中。
    • T: store :要求 T 类型拥有 store 的能力。
    • signer 是交易的发送者, val 是待存储的数据;
    • 利用标准库的 Signer 模块获取 signer 的地址,
    • 检查资源是否已经不存在,否则抛出代码 101 错误。
    • let to_store = Storage{ val, };{val: val ,} 跟 js 的 Object 一样
  • move_to(account, to_store); 资源存储。

public fun get

  • 如果一函数想要拿出资源返回,就需要使用 acquires 关键字
  • let Storage { val } 主要不是 let { val }

Test it

测试一下上面写的 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);
  }
}

sources/Coins.move

这一个在没有 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) 传入的是一个 address
  • withdraw(acc: &signer, val: u64) 传入的是一个 signer

Test it!

mint.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);
    }
}
  1. Mint Coins
  2. 给账户创建 Balance
  3. 向账户 Balance 内添加余额。

sources/Transfer.move

转账:从 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)

图片

0x10 AptosCoin.move 分析

sources/AptosCoin.move

有点难,先不搞了

0x11 实例 - 球星卡

球星卡信息 - 功能:

  • 创建
  • 空投
  • 查看
  • 设置价格 (想卖的话)
  • 转账 (结合Coins)
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);
        }
    }
}
点赞 1
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
李大狗
李大狗
0x73c7...6A1d
面向炫酷编程