Move Basic Grammar | Outstanding Study Notes for Co-Learning Classes

在线调试:

https://playground.pontem.network/

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
    • 结构化定义
    • 函数功能
    • global storage
  • Scripts
    • 暂时的代码片段
    • 可以调用Modules的函数
    • 1 个 Script 内只能有 1 个函数

About  —— public(script)

  • public fun : 方法可以在任何模块中被调用。
  • public(script) fun:script function 是模块中的入口方法,表示该方法可以通过控制台发起一个交易来调用,就像本地执行脚本一样
  • 下个版本的 Move 会expense or outlay public entry fun 替代 public(script) fun
  • Self 则是代表自身module。
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 类型
Move Basic Grammar | Outstanding Study Notes for Co-Learning Classes

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);
  }
}
Move Basic Grammar | Outstanding Study Notes for Co-Learning Classes
  • 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 :

namedescriptionabort
move_ro<T>(&Signer, T)将 Resource T 发布给 Signerif 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);

打印 “字符串”

    let str_ = b"Hello World";
    Debug::print(&str_);
    SM::show(str_);   // Attention No &str_ , but str_ , why ?
Move Basic Grammar | Outstanding Study Notes for Co-Learning Classes

向 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 的不可变引用
Move Basic Grammar | Outstanding Study Notes for Co-Learning Classes

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 的能力The

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, };  assume (office) {val: val ,} 跟 js 的 Object 一样
  • move_to(account, to_store);  资源存储。

public fun get

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

Test it

测试一下上面写的 Storage.move

exist ./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),所以在使用 depositwithdraw 等方法之前,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 } :提取的金额需要返回The

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

The above content are reproduced from the Internet, does not represent the position of AptosNews, is not investment advice, investment risk, the market need to be cautious, in case of infringement, please contact the administrator to delete.

Like (0)
Donate WeChat Sweep WeChat Sweep Alipay Sweep Alipay Sweep
Previous July 21, 2023
Next July 23, 2023

Related posts

Leave a Reply

Please Login to Comment
WeChat Sweep
Baidu Sweep

Subscribe to AptosNews

Subscribe to AptosNews to stay on top of Aptos.


This will close in 25 seconds

This site has no investment advice, Investment risk, Enter the market with caution.