在线调试:
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 来定义链上资产。这种方式呈现出三个特性:
- Resource 在 Move 中依然是以一个数值的形式存在,可以作为数据结构被储存,也可以作为参数被传递和返回。
- Resource 可以安全的表示数字资产,它的特殊在于不能被复制,丢弃或重用,但是它却可以被安全地存储和转移,并且 Resource 类型的值只能由定义该类型的模块创建和销毁,所以其实现了资产的含义而非数字。
- 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。
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);
}
}
脚本执行:
- 左侧边栏找到 “Run Script”
- 键入 main()
- 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
是 librarysigner
是数据类型- 如果要穿多个参数,把 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 元组和引用
元组:
- 用小括号去组合多个变量,例如 (x, y) (10, false)
- 函数多个返回值的定义和接受
- 多个变量的同时定义
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 种引用方式:
- 不可变引用 & ( 只读,不可修改 —— 安全!
- 可变引用
&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- 交换,结束。
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 ?
向 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)
传入的是一个 addresswithdraw(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);
}
}
- Mint Coins
- 给账户创建 Balance
- 向账户 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);
}
}
}
以上内容均转载自互联网,不代表AptosNews立场,不是投资建议,投资有风险,入市需谨慎,如遇侵权请联系管理员删除。