参考资料:


变量

变量这一章比较简单, 轻松入门sui: https://reference.sui-book.com/packages.html 中写得也非常详细!

let _a = 10u32;//下划线开头,表示后续没有使用

包-模块-方法

:包是同一个合约地址包含的全部代码的集合,由很多模块组成,也就是sui move new <name>生成的文件夹

a_move_package
├── Move.toml (必需)
├── Move.lock (生成的)
├── sources (必需)
├── doc_templates (可选)
├── examples (可选,测试和开发模式)
└── tests (可选,测试模式)

有关包下具体的配置信息,可以参见

模块-module:代码模块是代码划分访问权限和代码的组织方式

创建格式:

module <address>::<name>{
use sui::tx_context::TxContext;
fun init(ctx: &mut TxContext) {
}
}

方法访问权限控制

我把这里的方法理解为其他语言中的函数

方法签名 调用范围 返回值
fun call() 只能模块内调用 可以有
public fun call() 全部合约能调用 可以有
public entry fun call() 全部合约和Dapp(RPC)能调用
entry fun call() 只能Dapp(RPC)调用
public(package) fun call() 只能当前的包能调用 可以有
fun a(){
}
//全局可调用
public fun b(){
}
//只能声明package的包可调用(当前包也可调用)
public(package) fun c(){}
//全部合约和Dapp(RPC)能调用
public entry fun d(){
}

public fun f(a:u32, b:u32): bool{
a > b
}

init 方法

只能是私有的

会在发布合约的是时候自动调用一次

只有两种形式

  • fun init (ctx: &mut TxContext){}

  • fun init (witness: Struct, ctx: &mut TxContext) {}

引用-注释

mut: 可变引用

&:不可变引用

let mut a: u32 = 32;//mut表示 后续会改变它
a = 64;

控制流

这里比较简单,与其他语言差不多,结合代码学习很快就能上手

if-条件语句

if (condition) true_branch // implied default: else ()
if (condition) true_branch else ()


// x and y must be u64 integers
//这里意思为 x>y时,返回x; x<y时,返回y
let maximum: u64 = if (x > y) x else y;

// ERROR! branches different types
let z = if (maximum < 10) 10u8 else 100u64;

while-循环语句

fun sum(n: u64): u64 {
let mut sum = 0;
let mut i = 1;
while (i <= n) {
sum = sum + i;
i = i + 1
};

sum
}

break-跳出循环

fun min_factor(n: u64): u64 {
let mut i = 2;
while (i <= n) {
if (n % i == 0) break;
i = i + 1
};

i
}

continue-跳过当前条件的循环,直接进入下一个应该进入循环的值

fun sum(n: u64): u64 {
let mut sum = 0;
let mut i = 0;
while (i < n) {
i = i + 1;
if (i % 20 == 0) continue;
sum = sum + i;
};

sum
}

loop-只有遇到break时才跳出循环

相当于 c语言中的while(1),这里等价于while(true)

fun sum(n: u64): u64 {
let mut sum = 0;
let mut i = 0;
loop {
i = i + 1;
if (i > n) break;
sum = sum + i
};
sum
}

结构体-所有权-对象

struct-结构体

结构体是自定义类型,由字段组成,可以简单地理解成”key-value”存储,其中 key 是字段的名称,而 value 是存储的内容,使用关键字 struct 定义,可以为空。

注:结构体只能在模块内部定义,并且以关键字 public struct 开头,结构体名称首字母需要大写

 public struct NAME {
FIELD1: TYPE1,
FIELD2: TYPE2,
...
}

*UTXO

每个交易产生一个或多个UTXO,每一个UTXO都对应者一个ID,代表未花费的金额,可以简单理解为你的零钱。

sui基于UTXO模型

object-对象

对象在Sui上存储,维护了一个全局的Map数据结构 Map<ID,object>, id 是唯一的,通过这个唯一的id 查找到object。

sui上的资产都是对象,对象可以相互嵌套,所有的对象都是全局存储。

对象的定义

  • 必须有 key 能力

  • 必须第一个字段 是id,而且类型为sui::object::UID

module book::obj {
use sui::object::UID;
public struct Obj has key {
id:UID,
}
}

所有权

定义资产

资产也就是个人拥有所有权的物品合集

常见资产:银行余额,支付宝微信余额,房产等。

资产所有权

资产所有权可以分为:独有资产和共有资产,拥有所有权,则可以改变、删除、增加资产内容。

在Object中可以用关键字来标记所有权的类型,也就是能力,具体在下一章

分为:

  • key
  • copy
  • drop
  • store

所有权在函数之间的三种传递方式

fun f(consume: T, write: &mut T, read: &T)
T: transfer, delete, write, read//权限最高
&mut T: write, read
&T: read
//示例
public fun del(dog:Dog){
let Dog{id,gender,age} = dog;
object::delete(id);
}

public fun transfer(dog: &mut Dog,age:u8){
dog.age = 18
}

public fun view_dog(dog:Dog){
dog.age
}

所有权的方法

方法 生成的方法 属性
transfer 独享对象 key
public_transfer 独享对象 key + store
freeze_object 共享对象 - 常量 key
public_freeze_object 共享对象 - 常量 key + store
share_object 共享对象 key
public_share_object 共享对象 key + store

能力-Event-常量错误处理

能力

四种能力可以相互组合

  • key - 被值修饰的键可以对全局进行访问。
  • copy - 被修改的值可以被复制。
  • drop - 被成员函数在作用域结束时被丢弃。
  • store被修改的建议 存储在紧急情况下

没有任何能力:只能存活在同一个交易中

只有key:对象,自定义转移规则,对象有全局ID,可以被全局存储和查找(实现灵魂绑定)

只有drop:被修饰的值在离开作用域的时候会被自动解构 (删除),基本数据类型默认实现了drop

只有store:没法使用所有权,可以通过放在其他结构体中来实现所有权的使用,实现结构体的嵌套

key + store:对象,可以被任意转移,不被转移规则限定,对象有全局ID,可以被全局存储和查找

  • key 和 dorp 不能同时存在,也就是对资产进行操作后,不会销毁资产
  • key 和 copy不能同时存在,也就是资产不可复制

Event

打印日志:copy+drop

常量

不会更改的量即为常量,创建常量const Name : Type = Value

错误处理

  • abort 配合if语句来终止程序

  • assert!(num>10,ErrMustGet10) 断言,不满足条件时报错

  • debug 调试代码

    module std::debug {
    //打印数值
    native public fun print<T>(x: &T);
    //打印当前堆栈
    native public fun print_stack_trace();
    }

泛型

泛型是具体类型或其他属性的抽象替代品,使得在编写 Move 代码时提供更强的灵活性,并避免逻辑重复。

我理解的泛型,就是在起初定义结构体/方法时不定义其类型,在后续使用时再定义类型。这样一个语句就能被多次使用,从而避免了重复定义类似的结构体。

结构体泛型示例:

module generics::obj_generics {
public struct Box<T> {
value: T
}

//定义多个泛型
public struct Box<T,Y,X> {
value1: T,
value2: Y,
}
}

//使用泛型
fun init(ctx:&mut TxContext){

let box = Box<u8,u16>{
value1:23,
value2:45,
};
}

方法泛型示例:

module generics::generics {

public struct Box<T> {
value: T
}

public fun create_box<T>(value: T): Box<T> {
Box<T> { value }
}
//伪代码
let box:Box<u32> = create_box<u32>{value:1u32}
let box:Box<u32> = create_box<_>{value:1u32}

}

泛型的能力限制

通过store、drop、key、copy,对泛型进行约束(区别一下泛型的约束和能力的约束)

public struct Box3<T: store + drop,Y:store> has key, store {
id:UID,
value: T,
value2:X,
}

泛型如何传参

sui client call --package $PACKAGE --module $MODULE --function "create_box" --args $OBJECT_ID --type-args "0x2::coin::Coin<0x2::sui::SUI>" --gas-budget 100000000

phanton 泛型

申明一个类型参数但并不使用它,用于区分或者约束

(这个我还不是很清晰,后续会补充一下)

使用场景:

  • 泛型未被使用

  • 容器能力规则不满足

public struct Box <phanton T> has store{
value: u64
}

设计模式

这一节主要结合代码学习:https://github.com/404ll/letsmove/tree/main/tutorial/bootcamp/08_design_pattern

我没有将过多的代码放上来,建议自己手搓学,多写注释。

Capability 权限设计模式

public struct AdminCap has key { id: UID }

当你需要对结构体进行一些操作时,必须由传入这个结构体的实例来验证你是不是有这个权限,这个权限一般来说是一个有key能力的object,同时可以适当加上store能力,可以多次使用。

实际上就是实施权限控制,有权限的人才可以调用该操作

具体示例:

module design_pattern::capability {
use std::string::{Self,String};
use sui::transfer
use sui::object::{Self, UID};
use sui::tx_context::{Self,TxContext};

//生成管理员权限结构体
public struct AdminCap has key { id: UID }
//类似于NFT的类型
public struct Item has key, store { id: UID, name: String }
//创建一个管理员权限,并传递给发行者
fun init(ctx: &mut TxContext) {
let my_address = ctx.sender();
let addmin_cap = AdminCap {
id: object::new(ctx)
};
//将权限转移transfer::transfer(addmin_cap, my_address);
let addmin_cap2 = AdminCap {
id: object::new(ctx)
}; transfer::transfer(addmin_cap2, @0x1111);

}
//运行示例
public fun create_and_send(
//检验
_: &AdminCap, name: vector<u8>, to: address, ctx: &mut TxContext
) {
transfer::transfer(Item {
id: object::new(ctx),
name: name.to_string()
}, to)
}
}

witness 见证者设计模式

public struct Name has drop {}

简单理解为 这个结构体(资源)创建出来的实例是为了见证另一个资源的创建,类似于做标记。

:此结构体没有字段,只有drop(销毁)能力,实例只能使用一次

示例代码:


module design::guardian {

public struct Guardian<phantom T: drop> has key, store {
id: UID
}

public fun create_guardian<T: drop>(
//做标记,见证Guardian资源的创建
_witness: T, ctx: &mut TxContext
): Guardian<T> {
Guardian { id: object::new(ctx) }
}
//结束后删除
}

module design::peace_guardian {
use design::guardian;

public struct PEACE has drop {}

fun init(ctx: &mut TxContext) {
//生成对应的示例
let peace = PEACE{};
transfer::public_transfer(
guardian::create_guardian(peace, ctx),
ctx.sender()
)
}
}

one-time-witness 见证者模式

public struct OTW has drop {}
types::is_one_time_witness(&witness)

该结构体是一个特殊的见证者:同一个包下面的同一个结构体,只能创建出来一个实例来做 ‘见证’ ,同一个结构体只能用一次,不然会报错。例如创建一个Coin,一条链对应一个Coin

:名称必须与包的名字完全相同,并且全部大写;没有字段,只有drop(销毁)能力;通过fun init (witness: Struct, ctx: &mut TxContext) {} 传入

Transferable Witness 可以转移见证者模式

public struct WITNESS has store, drop {}
public struct WitnessBox has key { id: UID, witness: WITNESS }

这见证者结构体可以创建后放在一个容器里面,随着容器转移所有权,需要用到的时候在取出来做见证

: 结构体没有字段,只有drop(销毁)和store(实现存储)和能力,需要一个object的容器来包装,存储在链上。

hot-potato 设计模式

public struct Receipt { price: u64 }

public fun create(xx:XX,...):(Receipt{},Coin<x>)

public fun burn(rece:Receipt,...)

用这个结构体public struct Receipt { price: u64 }来检测是否符合交易的条件,防止别人盗走资源。

简单理解就是烫手的山芋,你拿到手里肯定处理不了,所以你只能还回去

:结构体没有任何能力,提供对外方法来创建结构体和销毁结构体。

hot-potato具体例子-闪电贷

闪电贷的特点

  • 在一个交易里面必须完成借和还

  • 无需抵押

  • 不还款一定会报错

  • 主要用于套利和加杠杆

借款人通过智能合约请求贷款,在同一交易中利用这笔贷款进行各种操作,如投资、交易或其他金融活动,同时借款人必须在同一交易中将贷款金额与利息(gas fee)还清。如果借款人未能按时还款或不按协议还款,整个交易将被取消,贷款金额将被返还给贷款提供方,这时借款人仍然需要支付gas fee

闪电贷的具体研究可以看看这篇文章:

[闪电贷详解]: https://academy.binance.com/zh/articles/what-are-flash-loans-in-defi#how-does-a-flash-loan-work “什么是defi中的闪电贷?”

Sui_framework

建议去官方的库中查看学习

Sui_framework,是Sui-move编程功能的合集,有很多相关的库

  • deepbook
  • move-stdlib 标准库
  • sui-framework
  • sui-system

Balance/Coin/Token-定义及特点

  • Balance:一个通用的余额可存储处理程序。在Coin 模块中用于允许余额操作,并可用于实现具有Supply 和 Balance 的自定义货币。
  • Coin:定义了 Coin类型-表示可互换的令牌和货币的平台范围内的表示。Coin 可以被描述为围绕Balance 类型的安全包装器。
  • Token: Token 模块实现了一个可配置的闭环令牌系统。该策略由一组规则定义,必须满足这些规则才能对令牌执行操作。

Token的产生是由Coin抽象出来,限制Coin的自由转发

Module Main type Capability Abilities
sui::balance Balance Supply store
sui::coin Coin TreasuryCapT> key + store
sui:: token Token TreasuryCap key

display standard -NFT

  • name(名称) - 对象的名称。用户查看对象时显示此名称。

  • description(描述) - 对象的描述。用户查看对象时显示此描述。

  • link(链接) - 用于应用程序中的对象链接。

  • image_url(图片链接) - 对象的图像链接,可以是URL或者图像的二进制数据。

  • thumbnail_url(缩略图链接) - 用作钱包、浏览器和其他产品中的预览的小图像的URL。

  • project_url(项目链接) - 与对象或创建者相关联的网站链接。

  • creator(创建者) - 表示对象创建者的字符串信息。

NFT = Object + Display

Sui Object Display 是一种模板引擎,可以实现对类型的链上管理与链下表示(显示)的模板化。通过它,你可以将对象的数据替换为模板字符串。该标准不限制你可以设置的字段。你可以使用{property}语法访问所有对象属性,然后将它们作为模板字符串的一部分插入其中。

Kiosk

具体介绍:https://docs.sui.io/standards/kiosk

Unit Test(单元测试)

  • #[test]-只跑一遍某个函数
  • #[test_only]-工具函数,只在测试的时候才被编译
  • #[expect_failure(abort_code = test_sui_hello ::my_coin::ENotlmplemented)]-测试指定函数,报出指定的错误,可以理解为用错误条件来检验程序是否正确

使用前文的Print()来调试

Coin协议

Coin有两种所有权:

  • 独有所有权:public_transfer(treasury_cap,sender(ctx))
  • 共享所有权: public_share_object(treasury_cap)

ps: 代码来自官方标准库

生成一个Coin

module examples::my_coin {
use sui::coin::{Self, TreasuryCap};

public struct MY_COIN has drop {}
//采用一次见证
fun init(witness: MY_COIN, ctx: &mut TxContext) {
let (treasury, metadata) = coin::create_currency(witness, 6, b"MY_COIN", b"", b"", option::none(), ctx);
//所有权共享 不可变共享
transfer::public_freeze_object(metadata);
//向合约发布者共享所有权
transfer::public_transfer(treasury, ctx.sender())
}

接下来我们一步一步的认识这个函数

使用coin::create_currency时,创建硬币的智能合约的发布者会收到一个TreasuryCap对象和Coin元数据。该TreasuryCap对象是铸造新硬币或销毁现有硬币所必需的。

同时TreasuryCap对象是可转让的,因此如果您转让该对象,第三方可以接管您创建的代币的管理TreasuryCap。但是,在转让该功能后,您将无法再自行铸造和销毁代币。

还有很多其他功能:这里就不一一列举了,建议用文档学习