1. 区块链基础概念
1. 区块链平台:区块链平台是一个分布式数据库,,它由许多计算机节点组成。这些计算机节点共同维护着一个共享的账本,称为区块链。(比如sui)
2. 智能合约: 运行在区块链平台上的计算机程序,智能合约主要执行3件事:创建资产,管理资产,权限控制
3. move语言:智能合约编程语言,可以类比于c语言这种计算机编程语言
4. 区块链资产(Blockchain Asset):区块链网络上记录和存储的数字化资产,比如加密货币(比特币),稳定币(加密货币的一种,但和法定货币直接挂钩,如usdt),NFT(数字资产,艺术品),Coin代币(代表各种东西,如公司股票)
5. 区块链账户(Blockchain Account):是在区块链网络中用于存储和管理用户资产和交易记录的数字账户。每个参与特定区块链网络的用户都可以拥有一个或多个区块链账户。(实际上就是公私钥对,公钥做地址,私钥做身份认证)
6. 资产所有权(Ownership):资产所有权是指对资产的占有、使用、收益和处分的权利。
资产所有权可以分为以下几种类型:
独有权:私有权是指个人资产的专有所有权。
共有权:共有权是指两个人或两个人以上共同对资产的所有权。
7. 区块链钱包(Blockchain Wallet):是一种用于存储、管理和交换加密货币的数字钱包。它是在区块链技术基础上开发的工具,允许用户安全地保存其加密货币资产(包括生成区块链账号),并进行交易和管理。
区块链钱包的类型有很多,包括:
热钱包:热钱包是指连接互联网的区块链钱包。热钱包比较方便使用,但安全性较低。
冷钱包:冷钱包是指不连接互联网的区块链钱包。冷钱包比较安全,但使用起来不太方便。
硬件钱包:硬件钱包是一种物理设备,它可以用来存储和管理区块链资产。硬件钱包非常安全,但价格比较昂贵。
8. 区块链浏览器(Blockchain Explorer):一种用于浏览和查询区块链上数据的工具。它提供了一种用户友好的界面,使用户能够浏览区块链上的交易、区块、地址和其他相关信息。
2. 环境搭建
2.1 sui(区块链平台)
sui可以通过brew快速安装。
# 下载安装脚本
git clone --depth=1 https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/install.git brew-install
# 执行安装脚本
./brew-install/install.sh
# 将brew添加到环境变量,路径为/home/your_user/.bashrc
echo >> /home/wsxk/.bashrc
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/wsxk/.bashrc
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
# 用brew安装sui
brew install sui
2.2 Sui Wallet(区块链钱包)
其实可配可不配。
Sui的区块链钱包有多种,基本功能是一致的(存储、管理资产),不同的区块链钱包提供了不同的功能。
这里我安装了Sui Wallet(slush)
在chrome浏览器上安装插件即可。
https://chrome.google.com/webstore/detail/opcgpfmipidbgpenhmajoajpbobppdil
安装步骤比较简单,无需多言
2.3 suiscan(区块链浏览器)
区块链浏览器用于查看区块链,这里提供两个区块链浏览器:
https://suiscan.xyz/
https://suivision.xyz/
2.4 faucet(测试网sui)
sui(区块链平台)中有很多张区块链网络,当然作为学习用途,我们肯定不能直接花钱买币。所以为了能白嫖币供我们使用,我们需要连接测试网,俗称水龙头https://faucet.sui.io/
获取sui token的办法https://docs.sui.io/guides/developer/getting-started/get-coins
2.5 VScode move插件
参考https://docs.sui.io/references/ide/move
要想写move语言,ide必不可少:我使用VScode来编写,但是需要安装相应插件,安装步骤如下:
sudo apt-get install curl git-all cmake gcc libssl-dev pkg-config libclang-dev libpq-dev build-essential
sudo snap install rustup --classic
rustup default stable
cargo install --git https://github.com/MystenLabs/sui.git sui-move-lsp
随后在VSCODE插件发布市场里搜索mysten.move
并安装即可
安装完成后,在terminal执行命令sui move new hello_move
,并抄抄代码:
module hello_move::hello ;
use std::ascii::{String, string};
use sui::object::{Self, UID};
use sui::transfer::transfer;
use sui::tx_context::{TxContext, sender};
public struct Hello has key {
id: UID,
say: String
}
fun init(ctx: &mut TxContext) {
let hello_move = Hello {
id: object::new(ctx),
say: string(b"move"),
};
transfer(hello_move, sender(ctx));
}
2.6 发布项目
使用/home/wsxk/.sui/sui_config/client.yaml
文件配置区块链
配置完成后,它会自动生成新的地址和私钥。
接下来使用sui client faucet
命令获取sui tokens
接着使用
sui client gas
查看结果:
接着再运行一次
sui client publish
发布成功后,可以到区块链浏览器上查看详情:
其中,Transaction Effects中的Digest字段的值,可以让我们在区块链浏览器中找到我们发布事件
另外,若想看到sui替你生成的钱包地址和私钥,可以查看~/.sui/sui_config/client.yaml
和~/.sui/sui_config/sui.keystore
2.7 cli调用函数的方法
sui client publish //发布上链
sui client call --package package_id --module module_name --function func_name --args ....
//如果参数中需要用到地址,需要记住参数的object id
2.8 本地调试
上节讲的是将所写代码发布到测试网testnet
然后尝试调用某个公开的function
,在实际编写move代码时,为了能够更有效的查看代码结果,发布在testnet上还是不方便。
目前比较好用的还是单元测试,在项目的代码中添加:
#[test]
public fun test_a_greater_than_b() {
debug::print(&string::utf8(b"test start!"));
}
然后terminal
中执行sui move test
即可
2.9 远程调用合约
https://learnblockchain.cn/article/7623里面写的比较详细
2.10 查看链上数据
通常情况下,在区块链浏览器上是可以搜到对象的内容的,但是里面的dynamic-field内容无法查看,这时候可以在区块链浏览器找到有dynamic-field
的对象的id,如:
接着调用
sui client dynamic-field 0x7dd04814002d50a2f2a585aa2bba04967d0ea307e2e199926994c54516f67602
即可看到
接下来,要查看里面具体的值,就需要调用
sui client object 0x7dd04814002d50a2f2a585aa2bba04967d0ea307e2e199926994c54516f67602
查看具体内容:
3. Move语言基本概念
其实只要了解Move语言中比较关键的部分,其余部分和诸如c语言等编程语言是类似的。
3.1 package、module
package是指同一个合约地址包含的全部代码的集合,由很多模块组成,比如一个项目sui move new hello_move
module就是package中的其中一个模块,比如hello_move项目中的hello_move.move文件
3.2 方法的访问权限控制
方法是module中的函数,类比与c的函数
跟c语言不同的是,方法前面可以添加关键字来确定这个方法的访问权限,即这个方法能被谁调用
一般访问权限控制由如下几种模式:
1. fun call(): 表示只有module内可见,只能在module内被调用
2. public(package) fun call(): 表示只对当前package内可见,可以在package内被调用
3. public fun call(): 表示对任意package可见,其他package都可以调用,包括 sui client call
4. entry fun call(): 表示能被链下其他事务调用(即dapp(RPC)调用)
5. public entry fun call(): 表示任意package,链下其他事务都能调用。
3.3 object和资产和资产所有权
object
由struct
演变而来:
//寻常struct
public struct NAME {
FIELD1: TYPE1,
FIELD2: TYPE2,
...
}
//每个struct最多有4个能力
//key:在全局存储操作中值可以被用为key键
//store:值可以在全局存储中被存储
//copy:值可以被复制
//drop:作用域范围结束后可以被丢弃
//object,加个 has key,且结构体的第一个成员变量一定是UID类型,就会变成object(对象),move中这种object就代表一个资产。
public struct ColorObject has key {
id: UID,
red: u8,
green: u8,
blue: u8,
}
资产是有所有权的(你也不想你的钱也是别人的钱吧)
move语言中,所有权分为独享所有权和共享所有权,有如下表现形式:
1. 对于只有key属性的独享所有权对象,要进行所有权转移,需要使用transfer方法
2. 对于有key+store属性的独享所有权对象,要进行所有权转移,需要使用public_transfer方法
3. 对于只有key的共享所有权对象,在使用时必须进行share_object方法来设置对象
4. 对于有key+store的共享所有权对象,在使用时必须进行public_share_object来设置对象
3.4 函数 参数传递和删除object
需要注意的是,函数在使用这些所有权对象时,访问权限会发生转移:
fun f(consume: T, write: &mut T, read: &T)
T: transfer, delete, write, read //这种场景下相当于访问权限从父函数传递到了f函数,父函数之后不能再使用,除非f函数又将T返回
&mut T: write, read //父函数还是拥有先前权限
&T: read//父函数还是拥有先前权限
函数的访问权限和对象所有权是有区别的,需要注意。
3.5 Capibility设计模式
本质上是一种权限的管控,如下:
// Type that marks the capability to create, update, and delete transcripts
public struct TeacherCap has key {
id: UID
}
//只有拥有TeacherCap能力的调用者,才能完成调用
public entry fun create_wrappable_transcript_object(_: &TeacherCap, history: u8, math: u8, literature: u8, ctx: &mut TxContext) {
let wrappableTranscript = WrappableTranscript {
id: object::new(ctx),
history,
math,
literature,
};
transfer::transfer(wrappableTranscript, tx_context::sender(ctx))
}
3.6 witness设计模式
witness
是一种设计模式,用于证明有关的一个资源或类型 A,在短暂的 witness 资源被消耗后只能启动一次。witness
资源在使用后必须立即被消耗或丢弃,确保它不能被重复使用以创建 A 的多个实例。
通常会在init
阶段使用。(通常只用一次,也叫OTW(one-time witness))
/// Module that defines a generic type `Guardian<T>` which can only be
/// instantiated with a witness.
module witness::peace {
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
/// Phantom parameter T can only be initialized in the `create_guardian`
/// function. But the types passed here must have `drop`.
public struct Guardian<phantom T: drop> has key, store {
id: UID
}
/// This type is the witness resource and is intended to be used only once.
struct PEACE has drop {}//这就是witness变量,witness必须有drop能力。
/// The first argument of this function is an actual instance of the
/// type T with `drop` ability. It is dropped as soon as received.
public fun create_guardian<T: drop>( //witness虽然没有被用到变量当中,但是却表示了这个结构体是唯一的。
_witness: T, ctx: &mut TxContext
): Guardian<T> {
Guardian { id: object::new(ctx) }
}
/// Module initializer is the best way to ensure that the
/// code is called only once. With `Witness` pattern it is
/// often the best practice.
fun init(witness: PEACE, ctx: &mut TxContext) {
transfer::transfer(
create_guardian(witness, ctx),
tx_context::sender(ctx)
)
}
}
3.7 coin
coin
是move语言中代币非常重要的结构体,用于发行你自己的货币。其定义如下:
public struct Coin<phantom T> has key, store {
id: UID,
balance: Balance<T>
}
public struct Balance<phantom T> has store {
value: u64
}
coin
类型的代币,通过create_currency
来创建,其定义如下:
public fun create_currency<T: drop>(
witness: T,
decimals: u8,
symbol: vector<u8>,
name: vector<u8>,
description: vector<u8>,
icon_url: Option<Url>,
ctx: &mut TxContext
): (TreasuryCap<T>, CoinMetadata<T>) {
// Make sure there's only one instance of the type T, OTW模式
assert!(sui::types::is_one_time_witness(&witness), EBadWitness);
// Emit Currency metadata as an event.
event::emit(CurrencyCreated<T> {
decimals
});
(
TreasuryCap {
id: object::new(ctx),
total_supply: balance::create_supply(witness)
},
CoinMetadata {
id: object::new(ctx),
decimals,
name: string::utf8(name),
symbol: ascii::string(symbol),
description: string::utf8(description),
icon_url
}
)
}
它返回两个对象,一个是TreasuryCap
资源,另一个是CoinMetadata
资源
TreasuryCap
其实就是存放这个代币的发行总额,所以只能有一个单例来记录。
/// Capability allowing the bearer to mint and burn
/// coins of type `T`. Transferable
public struct TreasuryCap<phantom T> has key, store {
id: UID,
total_supply: Supply<T>
}
/// A Supply of T. Used for minting and burning.
/// Wrapped into a `TreasuryCap` in the `Coin` module.
struct Supply<phantom T> has store {
value: u64
}
CoinMetadata
用于记录这个代币的一些基本信息,创建出来后通常都会使用freeze_object
来冻结,使其只读。
3.8 同类集合和异构结合
同类集合其实指的是集合中存放的类型都是一样的,异构结合指的是集合中可以存放的类型可以不一样。
move语言中的三个集合类型,分别为vector
(同类集合),table
(同类集合),bag
(异构结合)
3.8.1 vector
vector
本质上和c++中的vector
是一样的:
module collection::vector {
use std::vector;
public struct Widget {
}
// Vector for a specified type
public struct WidgetVector {
widgets: vector<Widget>
}
// Vector for a generic type
public struct GenericVector<T> {
values: vector<T>
}
// Creates a GenericVector that hold a generic type T
public fun create<T>(): GenericVector<T> {
GenericVector<T> {
values: vector::empty<T>()
}
}
// Push a value of type T into a GenericVector
public fun put<T>(vec: &mut GenericVector<T>, value: T) {
vector::push_back<T>(&mut vec.values, value);
}
// Pops a value of type T from a GenericVector
public fun remove<T>(vec: &mut GenericVector<T>): T {
vector::pop_back<T>(&mut vec.values)
}
// Returns the size of a given GenericVector
public fun size<T>(vec: &mut GenericVector<T>): u64 {
vector::length<T>(&vec.values)
}
}
3.8.2 table
table
是一个映射类的集合,可以动态存储键值对。但与传统的映射集合不同,它的键和值不存储在 Table 值中,而是使用 Sui 的对象系统存储。该 Table 结构仅充当对象系统的句柄以检索这些键和值。(3.9节分析这句话)
table要求键必须有copy+store+drop能力。值必须有store能力
module collection::table {
use sui::table::{Table, Self};
use sui::tx_context::{TxContext};
// Defining a table with specified types for the key and value
public struct IntegerTable {
table_values: Table<u8, u8>
}
// Defining a table with generic types for the key and value
public struct GenericTable<phantom K: copy + drop + store, phantom V: store> {
table_values: Table<K, V>
}
// Create a new, empty GenericTable with key type K, and value type V
public fun create<K: copy + drop + store, V: store>(ctx: &mut TxContext): GenericTable<K, V> {
GenericTable<K, V> {
table_values: table::new<K, V>(ctx)
}
}
// Adds a key-value pair to GenericTable
public fun add<K: copy + drop + store, V: store>(table: &mut GenericTable<K, V>, k: K, v: V) {
table::add(&mut table.table_values, k, v);
}
/// Removes the key-value pair in the GenericTable `table: &mut Table<K, V>` and returns the value.
public fun remove<K: copy + drop + store, V: store>(table: &mut GenericTable<K, V>, k: K): V {
table::remove(&mut table.table_values, k)
}
// Borrows an immutable reference to the value associated with the key in GenericTable
public fun borrow<K: copy + drop + store, V: store>(table: &GenericTable<K, V>, k: K): &V {
table::borrow(&table.table_values, k)
}
/// Borrows a mutable reference to the value associated with the key in GenericTable
public fun borrow_mut<K: copy + drop + store, V: store>(table: &mut GenericTable<K, V>, k: K): &mut V {
table::borrow_mut(&mut table.table_values, k)
}
/// Check if a value associated with the key exists in the GenericTable
public fun contains<K: copy + drop + store, V: store>(table: &GenericTable<K, V>, k: K): bool {
table::contains<K, V>(&table.table_values, k)
}
/// Returns the size of the GenericTable, the number of key-value pairs
public fun length<K: copy + drop + store, V: store>(table: &GenericTable<K, V>): u64 {
table::length(&table.table_values)
}
}
3.8.3 bag
bag
本质上和table
类似,只不过可以放不同类型的对象:
module collection::bag {
use sui::bag::{Bag, Self};
use sui::tx_context::{TxContext};
// Defining a table with generic types for the key and value
public struct GenericBag {
items: Bag
}
// Create a new, empty GenericBag
public fun create(ctx: &mut TxContext): GenericBag {
GenericBag{
items: bag::new(ctx)
}
}
// Adds a key-value pair to GenericBag
public fun add<K: copy + drop + store, V: store>(bag: &mut GenericBag, k: K, v: V) {
bag::add(&mut bag.items, k, v);
}
/// Removes the key-value pair from the GenericBag with the provided key and returns the value.
public fun remove<K: copy + drop + store, V: store>(bag: &mut GenericBag, k: K): V {
bag::remove(&mut bag.items, k)
}
// Borrows an immutable reference to the value associated with the key in GenericBag
public fun borrow<K: copy + drop + store, V: store>(bag: &GenericBag, k: K): &V {
bag::borrow(&bag.items, k)
}
/// Borrows a mutable reference to the value associated with the key in GenericBag
public fun borrow_mut<K: copy + drop + store, V: store>(bag: &mut GenericBag, k: K): &mut V {
bag::borrow_mut(&mut bag.items, k)
}
/// Check if a value associated with the key exists in the GenericBag
public fun contains<K: copy + drop + store>(bag: &GenericBag, k: K): bool {
bag::contains<K>(&bag.items, k)
}
/// Returns the size of the GenericBag, the number of key-value pairs
public fun length(bag: &GenericBag): u64 {
bag::length(&bag.items)
}
}
3.9 动态字段
上文提到:table
是一个映射类的集合,可以动态存储键值对。但与传统的映射集合不同,它的键和值不存储在 Table 值中,而是使用 Sui 的对象系统存储。该 Table 结构仅充当对象系统的句柄以检索这些键和值。bag和table是类似的。
如何理解table和bag集合呢,其实table本身的定义如下:
struct Table<phantom K, phantom V> has key, store {
id: UID, // 相当于“主键”
size: u64, // 只是计数
}
添加对象时,做的事情其实是:
field::add(&mut table.id, k, v); // 在 table.id 这棵“父对象”下挂一条动态字段
table.size += 1;
动态字段有两种类型:
1. 动态字段:可以存储任何具有store能力的值,但是存储在这种字段中的对象将被视为被包装过(例如一个带有key能力的全局对象被嵌套进另一个结构体中),无法通过其直接访问通过外部工具(浏览器、钱包等)访问存储的 ID。添加时使用的是 field::add(&mut parent.id, name, child);
2. 动态对象字段:值必须是 Sui 对象(具有 key 和 store 能力,以及 id: UID 作为第一个字段),但仍然可以通过它们的对象 ID 直接访问被附上。添加是使用的是 ofield::add(&mut parent.id, name, child);
4. move进阶!
4.1 dapp
dapp即Decentralization Application
,去中心化应用。即DApp = 前端界面 + 智能合约 + 链上数据
其实我们在第二章说的安装都只安装了后端程序,只写了智能合约,发布上链有了链上数据,但作为成熟的应用,没有前端是不行的。
为了能写一个完整的dapp,需要安装一个前端,这里推荐安装node和npm
随后在某个目录下执行npm i @mysten/sui
即可开始编写js代码!
编写js代码后可通过node xxx.js
执行。