设计模式 - https://move-by-example.com/core-move/patterns/index.html#patterns)
These patterns are closely related to the following Move language features :
Resource oriented(资源导向)
Resource can only be stored, transfered, destroyed or dropped restricted to the abilities.
Resource can only be created, readed field, modified field in the module where it is defined.(资源只能在定义它的模块中创建、读取字段、修改字段)
The global storage mechanism(全局存储机制)
The global storage can only be accessed(访问) through the
move_to,move_from,borrow_global,borrow_global_mutfunctions.Objects in the global storage can only be accessed in the module where it is defined.
https://move-by-example.com/core-move/patterns/capability.html#capability)
The Capability pattern can be used for access controll. It is the most widely .used pattern in Move smart contracts. Capability 模式可用于访问控制,它是 Move smart contracts 中使用最广泛的模式
Why this pattern?
Limited by that `no storage for modules to save data.``
因为 module (合约)是不能储存数据的,所有的数据都被以资源的形式储存在 account 账户下面
How to use?
construct a capability resource and
move_tothe receiptor.定义一个 Capability 的 type,比如 ownerCapability 表示是一个 module 的拥有者,把这个资源对象发送给其他人,别人有了这个资源对象,就相当于有了权限操作 module 里的内容。
注意,是一个凭证,相当于是一个权限证明,不能 Copy, 不能 Drop 丢弃! 要不就会有安全问题!
下面的例子:将 Capability 存储到用户账户下的 Storage
module examples::capability {
use std::signer;
const ENotPublisher: u64 = 0;
const ENotOwner: u64 = 1;
struct OwnerCapability has key, store {}
public entry fun init(sender: signer) {
assert!(signer::address_of(&sender) == @examples, ENotPublisher);
move_to(&sender, OwnerCapability{} );
}
// Only user with Capability can call this function.
public entry fun grant_role() acquires OwnerCapability {
let cap = borrow_global<OwnerCapability>(signer::address_if(&signer));
// do s.th. with the capability ...
}
}
将 struct 传递给其他合约,其他合约可以通过这个对象 struct 来调用响应的函数。
module examples::capability {
// same as above ...
// The returned `OwnerCapability` can be stored in other modules.
public entry fun get_owner_cap(sender: signer): OwnerCapability {
assert!(signer::address_of(&sender) == @examples, ENotPublisher);
OwnerCapability {} // retured !
}
// module with `OwnerCapability` can call this Function !!
public fun grant_role_with_cap(to: address, _cap: &OwnerCapability) acquires {
// do s.th. ....
}
官网例子(仔细看注释)
module examples::capability {
use std::signer;
const ENotPublisher: u64 = 0;
const ENotOwner: u64 = 1; // Error Code.
struct OwnerCapability has key, store {}
/// init and publish an OwnerCapability to the module owner.
/// 初始化并向模块所有者发布 OwnerCapability
public entry fun init(sender: signer) {
// 该函数的执行人需要是模块 owner,否则 Error NotPublisher
assert!(signer::address_of(&sender) == @examples, ENotPublisher);
// 将该资源 move_to 到 sender 地址下
move_to(&sender, OwnerCapability {})
}
/// mint to `_to` with the OwnerCapability.
public fun mint_with_capability(_amount: u64, _to: address, _cap: &OwnerCapability) {
// mint and deposit to `_to` , mint and 存款
}
/// mint entry function. Only signer with OwnerCapability can call this function. mint 入口函数,只有拥有 OwnerCapability 的签名者才能调用此函数。
public entry fun mint(sender: &signer, to: address, amount: u64) acquires OwnerCapability {
// 如果调用者 sender 不具备 OwnerCapability ,则 Error Not Owner
assert!(exists<OwnerCapability>(signer::address_of(sender)), ENotOwner);
// 接受 borrow_global 的返回值,borrow_global 会从全局状态中暂借出 owner 的 OwnerCapability 能力。
let cap = borrow_global<OwnerCapability>(signer::address_of(sender));
mint_with_capability(amount, to, cap);
}
}
OwnerCapability:定义了(属主 owner 有 ?) key、store 能力(为全局存储操作的键值)
https://move-by-example.com/core-move/patterns/offer.html#offer)
The Offer pattern is used to transfer new objects to others, such as capability delegation, etc. Offer 模式用于将新对象传递给其他对象,例如能力委托等。
This pattern is intraoduced because the restriction that in Move, move_to requires a signer, which means that an account cannot directly send an object to another account. So we need to use the Offer pattern to achieve this. 这种模式是内部引入的,因为在 Move 中,move_to 需要 signer 签署,这意味着 A 帐户不能直接无许可地将对象发送到 B 帐户(因为需要 move_to 到 B 账户下面去,而 move_to 这个动作需要 B 账户来 sign 签署)。所以我们需要使用 Offer pattern 来实现这一点。
发送者将要发送的对象放在一个 Offer 里面,然后 receptor 接收 Offer 对象,再放到自己的账户下面。
例子 1 : 小伦想发送给小明一个 AdminCapability,但是小伦不知道小明什么时候在家 (小明无法实时 sign 签署),所以不能贸然寄给小明。所以他们俩约定了一个流程:
小伦将
AdminCapability包裹在Offer里,里面写上了小明的address(receipt: to)目标接收人(小明)如果想获取
AdminCapability的话,需要自己去申请打开 Offer 包裹,即 claim 调用accept_role,然后合约会检查小明的地址 (address_of(sender)) 是不是 Offer 包裹里写入的 address . ( 即:只有小明才能打开这个写着小明 address 的 Offer 包裹)
module examples::offer {
struct Offer<T: key+store> has key, store {
receipt: address,
offer: T,
}
/// The owner can grant the `admin` role to another address `to`:
public entry fun grant_role_offer(sender: &signer, to: address) acquires OwnerCapability {
assert!(exists<OwnerCapability>(signer::address_of(sender)), ENotOwner);
move_to<Offer<AdminCapability>>(sender,
Offer<AdminCapability> {
receipt: to,
offer: AdminCapability {},
}
);
}
/// The receiptor accept an `offer` from `grantor` and save it in his account.
public entry fun accept_role(sender: &signer, grantor: address) acquires Offer {
assert!(exists<Offer<AdminCapability>>(grantor), ENotGrantor);
let Offer<AdminCapability> { receipt, offer: admin_cap = move_from<Offer<AdminCapability>>(grantor);
assert!(receipt == signer::address_of(sender), ENotReceipt); // Attention.
move_to<AdminCapability>(sender, admin_cap);
}
}
真正的 Wrapper —— 把别的合约里的资源 Wrapper 起来自己用。
module examples::coin {
struct Coin has key, store {
value: u64,
}
struct MintCapability has key, store {}
public fun mint(amount: u64, _cap: &MintCapability): Coin {
Coin { value: amount }
}
}
module examples::wrapper {
use examples::coin::{Self, Coin, MintCapability};
struct MintCapabilityWrapper has key {
mint_cap: MintCapability,
}
public entry fun mint(sender: &signer, amount: u64) acquires MintCapabilityWrapper {
// check if the sender has can mint with own policy
// 这边省略了一些检查—— 检查 sender 是否符合政策 —— 在可 mint 的白名单里
// ..
// 符合 : 发放 mint 能力:
let wrapper = borrow_global<MintCapabilityWrapper>(@examples);
let coin = coin::mint(amount, &wrapper.mint_cap);
}
}
官网例子,和上面的差不多。
module examples::offer {
use std::signer;
const ENotPublisher: u64 = 0; // Error codes.
const ENotOwner: u64 = 1;
const ENotReceipt: u64 = 2;
const ENotGrantor: u64 = 3;
struct OwnerCapability has key, store {}
struct AdminCapability has key, store {}
// 限制 offer struct 传入的 T 需要有 key + store abilities (个人理解)
struct Offer<T: key + store> has key, store {
receipt: address,
offer: T,
}
// 和上面 Capability 一样,初始化并向模块所有者发布 OwnerCapability
public entry fun init(sender: signer) {
assert!(signer::address_of(&sender) == @examples, ENotPublisher);
move_to(&sender, OwnerCapability {})
}
// 将 capability 授予给 `to`,即 receipt,
// return 一个 Offer struct with AdminCapability.
public fun grant_role_with_capability(to: address, _cap: &OwnerCapability): Offer<AdminCapability> {
Offer<AdminCapability> {
receipt: to,
offer: AdminCapability {},
}
}
/// The owner can grant the admin role to another address `to`
// owner 可以将 AdminCapability (admin role) 授予另一个地址`to`
public entry fun grant_role_offer(sender: &signer, to: address) acquires OwnerCapability {
assert!(exists<OwnerCapability>(signer::address_of(sender)), ENotOwner);
// 1. 签名者把自己的 OwnerCapability 揪出来
let cap = borrow_global<OwnerCapability>(signer::address_of(sender));
// 2. 将 OwnerCapability 放入,取回一个 AdminCapability 能力类型的 offer
let offer = grant_role_with_capability(to, cap);
// 3. 将 offer ( AdminCapability能力) 塞入自己的地址(move_to )
move_to<Offer<AdminCapability>>(sender, offer);
}
/// Entry function for `receiptor` to accept an offer from `grantor` and save it in their account.
// `receiptor` 接受来自 `grantor` 的 offer 并将其保存在他们的帐户中
public entry fun accept_role(receiptor: &signer, grantor: address) acquires Offer {
assert!(exists<Offer<AdminCapability>>(grantor), ENotGrantor);
let Offer<AdminCapability> { receipt, offer: admin_cap } = move_from<Offer<AdminCapability>>(grantor); // 移出
assert!(receipt == signer::address_of(receiptor), ENotReceipt);
move_to<AdminCapability>(receiptor, admin_cap);
}
}
https://move-by-example.com/core-move/patterns/wrapper.html#wrapper
如上 Offer 类型,其局限是某个地址下只能存储一个资源,无法分发给很多人,所以实际中更常用的是 Wrapper 模式。
The Wrapper pattern is used to store an arbitrary number of a given type, or store objects defined in other modules.
Wrapper 模式用于存储任意数量的给定类型,或存储其他模块中定义的对象
Why this pattern?
Limited by that an account can only have one resource of a given type.
一个账户只能拥有一个给定类型的资源。
Limited by that one can't
move_toormove_froma resource outside the module.不能
move_to或move_from模块外的资源,使用了 Wrapper 后,可以再包一层,这样就能在 module 之外操作别人的 Resource 了.......
How to use?
Use a
vectoror atableto store the objects.使用
vector或者table数据结构来存储多个 Object,来分发给多个地址。
Wrapper an object in a new struct directly.
Applications:
NFTGalley::NFTGalleyin StarCoin framework .token::TokenStorein Aptos framework.
如下示例:
offers: Table<address, T>: offers 里可以存放多个 address ,也就是说: 可以把比如AdminCapability对象分发给多个地址。重复调用
grant_admin_offer(to: address), 将address, AdminCapability {}添加到&offer_store.offers里去address 来 claim 调用 accept_role 申请
AdminCapability时, 合约会判断这个 address 在不在&store.offers的名单里,如果在名单里,就table::remove(&mut store.offers, to);弹出一个AdminCapability{}给 claimer
module examples::wrapper {
use std::signer;
use extensions::table::{Self, Table};
const ENotPublisher: u64 = 0;
const ENotOwner: u64 = 1;
const ENotReceipt: u64 = 2;
const ENotGrantor: u64 = 3;
const EOfferExisted: u64 = 4;
struct OwnerCapability has key, store {}
struct AdminCapability has key, store {}
/// The wrapper pattern.
struct OfferStore<phantom T> has key, store {
offers: Table<address, T>,
}
public entry fun init(sender: signer) {
assert!(signer::address_of(&sender) == @examples, ENotPublisher);
move_to(&sender, OwnerCapability {})
}
/// The owner can grant the admin role to another address `to`
public entry fun grant_admin_offer(sender: &signer, to: address) acquires OfferStore
{
assert!(exists<OwnerCapability>(signer::address_of(sender)), ENotOwner);
if (!exists<OfferStore<AdminCapability>>(signer::address_of(sender))) {
move_to<OfferStore<AdminCapability>>(sender, OfferStore<AdminCapability> {
offers: table::new(),
});
};
let offer_store = borrow_global_mut<OfferStore<AdminCapability>>(signer::address_of(sender));
assert!(!table::contains<address, AdminCapability>(&offer_store.offers, to), EOfferExisted);
table::add(&mut offer_store.offers, to, AdminCapability {});
}
/// Entry function for `sender` to accept an offer from `grantor` and save it in their account.
public entry fun accept_role(sender: &signer, grantor: address)
acquires OfferStore {
assert!(exists<OfferStore<AdminCapability>>(grantor), ENotGrantor);
let to = signer::address_of(sender);
let store = borrow_global_mut<OfferStore<AdminCapability>>(grantor);
assert!(table::contains<address, AdminCapability>(&store.offers, to), EOfferExisted);
let admin_cap = table::remove(&mut store.offers, to);
move_to<AdminCapability>(sender, admin_cap);
}
}
Witness is a design pattern used to prove that the a resource or type in question , A , can be initiated only once after the ephemeral witness resource has been consumed. Witness 模式通常用于证明资源或类型 A 只能在短命的 Witness 资源被消耗后初始化一次。 witness 资源必须在使用后立即被 drop,确保它不能被重复用于创建 A 的多个实例。
举个栗子 : 为了确保 Coin 只能发行一次, 不能重复发行多次, 我们修建了一个铸币法阵, 这个法阵只能用天选之子小明的血液为媒介才能开启 , 此时小明被祭司挑选为 Witness 见证人 , 某天, 祭司们需要开启法阵来铸币, 就把小明抓过来放血, 放完血之后, 短命的小明就被吃掉了(drop), 这个法阵从此以后就再也不能开启了, 因为唯一的血媒小明已经被吃掉了 ( 被 drop 了 ).
在下面的示例中,witness resource (小明) 是 PEACE,而我们要控制实例化的类型 A 是 Guardian。 (Guardian 只能被初始化一次 )
witness 资源类型必须有 drop 关键字,以便此资源被传参后可以被删除。我们看到 PEACE 资源的实例被传递到 create_guardian 方法并被删除(注意 witness 之前的下划线),确保只能创建一个 Guardian 实例。
/// 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`.
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 {}
/// 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: 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)
)
}
}
在上面的例子中,Guardian 具有 key 和 store ability,这样它就是一种资源 asset,可转移(transferrable) 并可以persists in global storage. (持久化在区块链全局存储中)
我们还想把 Witness resource PEACE 传入Guardian,但是 PEACE 只有 drop ability 。回想一下我们之前关于能力约束和内部类型的讨论,规则暗示 PEACE 也应该有 key 和 value ,因为外部类型 Guardian 有。但在这种情况下,我们不想为我们的 Witness 类型添加不必要的能力,因为这样做可能会导致不良行为和漏洞。
我们可以使用关键字 phantom 来绕过这种情况。当一个类型参数要么没有在结构体定义内部使用,要么只用作另一个幻像类型参数的参数时,我们可以使用幻像关键字来请求 Move 类型系统放宽对内部类型的能力约束规则。我们看到 Guardian 没有在其任何字段中使用 T 类型,因此我们可以安全地将 T 声明为幻像类型。
One Time Witness (OTW) 是 Witness 模式的一个子模式,我们利用模块 init 函数来确保只创建一个 witness 资源实例(因此类型 A 保证是单例)。
在 Sui Move 中,如果一个类型的定义具有以下属性,则该类型被认为是一个 OTW:
类型以模块命名,但大写
module witness::peacestruct PEACE
该类型只有 drop 能力
要获得此类型 (PEACE)的实例,您需要将其作为第一个参数添加到 Module 的 init 函数,如上例所示。 The Sui runtime will then generate the OTW struct automatically at module publish time.
Sui runtime 将在 module 发布时自动生成 OTW 结构。
https://move-by-example.com/core-move/patterns/witness.html#witness)
Witness is a pattern that is used for confirming the ownership of a type. To do so, one passes a drop instance of a type. Coin relies on this implementation. Witness 是一种用于确认类型所有权(the ownership of a type)的模式。 为此,需要传递一个类型的 drop 实例。 Coin 依赖于这个实现。
Why this pattern?
Benifit from the Move type system, that a type can only be created by the module that defines it.
类型 type 只能由定义它的模块 module 创建。
得益于 Move 的特性 —— 一个类型实例化的时候,只能在定义这个类型的 Module 里面进行实例化。
Witness 对象一定是创建对象的合约或者是授权的合约才能获取这个实例。
How to use?
Define a public function with generic type argument, and a function argument of that type.
定义具有泛型类型参数的公共函数,以及该类型的函数参数。
Security programming
No copy ability and public constructor function for the Witness type.
Witness 类型没有 copy 能力和 public 构造函数。
Applications
A third party library can provide public functions but limit the invoking.
第三方库可以提供公共功能但限制调用。
In an open game, a hero module authorization other modules to increase a hero's expericence.
在一个开放游戏中,一个英雄模块授权(某个可信任可约)其他模块来增加一个英雄的经验。
如下例子,Hacker 想要攻击这个合约 :
Hacker 知道 Publisher 还没来得及
publish_coin,黑客想利用这个时间差抢先发币:Hacker 想利用
examples::framework的publish_coin()和examples::xcoin的X类型来发行货币但是得益于 Move 的特性 —— 类型实例化的时候,只能在定义这个类型的 Module 里面进行实例化。
也就是说,publish() 中的类型 T 在 Module
examples::xcoin中被实例化为X,其只能在 examples Module 中被实例化(应该是), 所以外部 Module 无法调用X进行实例化
publish_coin和publish_coin_v2传值/传引用,可以达到相同的效果。
module examples::framework {
/// Phantom parameter T can only be initialized in the `create_guardian`
/// function. But the types passed here must have `drop`.
// Phantom param `T` 只能在 `create_guardian()` 中初始化, 这里传递的类型必须有 `drop`。
struct Coin<phantom T: drop> has key, store {
value: u128,
}
/// The first argument of this function is an actual instance of the
/// type T with `drop` ability. It is dropped as soon as received.
// 该函数的第一个参数是具有“drop”能力的类型 T 的实际实例。 它一收到就被丢弃。
public fun publish_coin<T: drop>(_witness: T) {
// register this coin to the registry table
}
public fun publish_coin_v2<T: drop>(_witness: &T) {
// register this coin to the registry table, it's also work well.
}
}
/// Custom module that makes use of the `guardian`.
module examples::xcoin {
use examples::framework; // Use the `guardian` as a dependency.
struct X has drop {}
/// Only this module defined X can call framework::publish_coin<X>
// 只有这个模块定义的 X 可以调用 framework::publish_coin<X>
public fun publish() {
framework::publish_coin<X>(X {});
}
}
// Hack it !
module hacker::hacker {
use examples::framework; // Use the `guardian` as a dependency.
use examples::xcoin::X;
public fun publish() {
coin::publish_coin<X>( X { } ); // Illegal, X can not be constructed here.
}
}
烫手山芋,笑死 🤣
Hot Potato is a name for a struct that has no abilities, hence it can only be packed and unpacked in its module.
struct with no abilities, 它只能在其 module 中打包和解包。
In this struct, you must call function B after function A in the case where function A returns a potato and function B consumes it.
在这个结构中,你必须在函数 A 之后调用函数 B,以防 A 返回一个 potato 而 B 消费它。
该 Design Pattern 实现的逻辑:
可以控制函数们的执行顺序:
比如说我做了一个基础库,在不知道调用者会干什么的时候,通过 Hot Potato 模式,让别人在预先设定的顺序下去调用我现有的函数。
如下例子:
定义了一个圆圆的土豆,它既不能直接吃,又不能存起来(马铃薯会发芽),只能传递给下一个人 (函数)。
如果调用
get_potato函数,该函数返回一个Potato {}对象,用户是没法处理的所以用户必须在其后调用
consume_potato来消费这个Potato {}对象如此,就实现了
get_potato->consume_potato这样的一个函数执行顺序。
module examples::hot_potato {
/// Without any capability, the `sender` can only call `consume_potato`.
struct Potato {}
/// When calling this function, the `sender` will receive a `Potato` object.
/// The `sender` can do nothing with the `Potato` such as store, drop, except
/// passing it to `consume_potato` function.
public fun get_potato(_sender: &signer): Potato {
Potato {}
}
public fun consume_potato(_sender: &signer, potato: Potato) {
// do nothing
let Potato {} = potato;
}
}
