MOVE 中的重要概念 —— 资源
本系列将以 Starcoin 为例,讲解 Move 语言以及 Move dApp 的开发,及其背后的计算机原理。
本系列的全文更新中,见:
https://github.com/WeLightProject/Web3-dApp-Camp/tree/main/move-dapp
同步的打卡任务:
https://github.com/WeLightProject/Web3-dApp-Camp/discussions/categories/projects-others
「资源」是 Move 语言中最关键的概念。本篇将以 Library 图书馆 Demo 为例,讲解在 Rust 下是如何操作资源的。
本文的 Demo 源码包含 smart contract
、react dApp
、script.sh
三部分,资源链接如下:
https://github.com/WeLightProject/Web3-dApp-Camp/tree/main/move-dapp/my-library
https://move-book.com/advanced-topics/types-with-abilities.html#types-with-abilities
https://move-book.com/cn/advanced-topics/types-with-abilities.html
Move 的类型系统非常灵活,每种类型都可以定义四种能力(abilities)。它们决定了类型的值是否可以被「使用、丢弃和存储」。
这四种 abilities 能力分别是: Copy, Drop, Store 和 Key。
它们的功能分别是:
在上一篇中,我们已经初步接触到了 Abilities。在本实例中,我们将进一步的通过 Play with Abilities 掌握其原理。
基本类型和内建类型的 abilities 是预先定义好的并且不可改变: integers, vector, addresses 和 boolean 类型的值先天具有 copy、drop 和 store ability。
然而,结构体的 ability 可以按照下面的语法进行添加:
struct NAME has ABILITY [, ABILITY] { [FIELDS] }
一个图书馆的Struct
例子:
module Library {
// each ability has matching keyword
// multiple abilities are listed with comma
struct Book has store, copy, drop {
year: u64
}
// single ability is also possible
struct Storage has key {
books: vector<Book>
}
// this one has no abilities
struct Empty {}
}
合约源码见:
对应的脚本合集如下(脚本中的路径、地址等信息需自行调整):
# deploy
dev deploy [path to blob] -s [addr] -b
dev deploy /Users/cjf/Documents/bc/Web3-dApp-Camp/move-dapp/my-library/release/my_library.v0.0.3.blob -s 0x07ffe973c72356c25e623e2470172a69 -b
# call function init library
account execute-function --function 0x07Ffe973C72356C25e623E2470172A69::MyLibrary::init_library -s 0x07Ffe973C72356C25e623E2470172A69 -b
# get library
state get resource 0x07Ffe973C72356C25e623E2470172A69 0x07Ffe973C72356C25e623E2470172A69::MyLibrary::Library
# add book
account execute-function --function 0x07Ffe973C72356C25e623E2470172A69::MyLibrary::s_add_book --arg b"web3" --arg b"github.com" -s 0x07Ffe973C72356C25e623E2470172A69 -b
# update book at index
account execute-function --function 0x07Ffe973C72356C25e623E2470172A69::MyLibrary::s_update_book_at_id --arg 0 --arg b"atest" --arg b"noncegeek.com" -s 0x07Ffe973C72356C25e623E2470172A69 -b
# delete book at index
account execute-function --function 0x07Ffe973C72356C25e623E2470172A69::MyLibrary::s_add_book --arg b"web3" --arg b"github.com" -s 0x07Ffe973C72356C25e623E2470172A69 -b
通过如下命令在starcoin console
中进行合约部署:
starcoin% dev deploy [path to blob] -s [deployer addr] -b
初始化 Library:
starcoin% account execute-function --function [deployer addr]::MyLibrary::init_library -s [deployer addr] -b
查看 Library:
starcoin% state get resource [caller addr] [deployer addr]::MyLibrary::Library
此时因为还没有藏书,所以图书馆是空的:
插入一本 Book:
starcoin% account execute-function --function [deployer addr]::MyLibrary::s_add_book --arg b"web3" --arg b"github.com" -s [caller addr] -b
此时再执行get resource
,会发现多了一个 item:
更新(Update
)和删除(Delete
)的操作同理,你自己来动手试试吧:)!
Move 合约包含Struct
、func
和script
三个部分。
其中,Struct
定义数据结构、func
是一般函数、script
是暴露被外部调用的脚本。
module MyAddr::MyLibrary {
use StarcoinFramework::Signer;
use StarcoinFramework::Vector;
// each ability has matching keyword
// multiple abilities are listed with comma
struct Book has store, copy, drop {
id: u64,
name: vector<u8>,
link: vector<u8>
}
// single ability is also possible
struct Library has key {
books: vector<Book>
}
public fun create_library(account: &signer){
move_to<Library>(account, Library{books: Vector::empty<Book>()});
}
//because the script function cannot have return value,
//query only can be done by: state get resource Addr Addr::MyLibraryV4::Library
public fun addBook(account: &signer,name:vector<u8>, link: vector<u8>) acquires Library {
let lib = borrow_global_mut<Library>(Signer::address_of(account));
let id = Vector::length(&lib.books);
Vector::push_back(&mut lib.books, Book{id:id,name:name,link:link});
}
public fun updateBookAtId(account: &signer,id:u64,name:vector<u8>, link: vector<u8>) acquires Library {
let lib = borrow_global_mut<Library>(Signer::address_of(account));
let book = Vector::borrow_mut<Book>(&mut lib.books,id);
book.name = name;
book.link = link;
}
public fun deleteBookAtId(account: &signer,id:u64) acquires Library {
let lib = borrow_global_mut<Library>(Signer::address_of(account));
Vector::remove(&mut lib.books, id);
}
public(script) fun init_library(account: signer){
Self::create_library(&account)
}
public(script) fun s_add_book(account: signer, name:vector<u8>, link: vector<u8>) acquires Library {
Self::addBook(&account,name, link)
}
public(script) fun s_update_book_at_id(account: signer, id:u64,name:vector<u8>, link: vector<u8>) acquires Library {
Self::updateBookAtId(&account,id,name,link)
}
public(script) fun s_delete_book_at_id(account: signer, id:u64) acquires Library {
Self::deleteBookAtId(&account,id)
}
}
还记得 signer 吗? 现在你可以看看它是如何运作的! 要将资源移动到帐户,您有内置函数 move_to,它将
signer
作为第一个参数,Collection
作为第二个参数。 move_to 函数的签名可以表示为:native fun move_to<T: key>(account: &signer, value: T);
这导致两个结论:
- 您只能将资源放在您的帐户下。 您无法访问另一个帐户的 signer value,因此无法将资源放在那里。
- 一个地址下只能存储一种单一类型的资源。 两次执行相同的操作会导致丢弃现有资源——这种情况绝不能发生(想象您存储了您的硬币,并且由于不准确的操作,您通过推空余额丢弃了所有储蓄!)。 第二次尝试创建现有资源将失败并出现错误。
—— https://move-book.com/resources/resource-by-example/storing-new-resource.html
在本 demo 中,我们通过 move_to 函数创建了Library
资源:
public fun create_library(account: &signer){
move_to<Library>(account, Library{books: Vector::empty<Book>()});
}
见:
Vector 是 Rust 中的一种数据类型,其允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。Vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
在本 Demo 中,我们使用vector<u8>
来统一处理字符串,相当于 Python 中的 b"something",或 Elixir 中的 <<1,2,3>>。
此外,Library 中存放了很多书籍,我们能用vector<Book>
表示,相当于其他语言中的List
类型:
struct Library has key {
books: vector<Book>
}
要了解更多关于 Vector 的内容,可以见starcoin-framework
:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!