# 0x11 | Patterns 设计模式

By [Demian](https://paragraph.com/@demian-2) · 2023-01-14

---

### Patterns

> 设计模式 - [https://move-by-example.com/core-move/patterns/index.html#patterns](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_mut` functions.
        
    *   Objects in the global storage can only be accessed in the module where it is defined.
        

#### Capability 模式

> [https://move-by-example.com/core-move/patterns/capability.html#capability](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_to` the receiptor.
    
*   定义一个 Capability 的 type，比如 ownerCapability 表示是一个 module 的拥有者，把这个资源对象发送给其他人，别人有了这个资源对象，就相当于有了权限操作 module 里的内容。
    

注意，是一个凭证，相当于是一个权限证明，不能 Copy, 不能 Drop 丢弃！ 要不就会有安全问题！

1.  下面的例子：将 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 ...  
      }
    }
    

1.  将 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 能力（为全局存储操作的键值）

#### Offer 模式

> [https://move-by-example.com/core-move/patterns/offer.html#offer](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);
        }
    }
    

#### Wrapper 模式

> [https://move-by-example.com/core-move/patterns/wrapper.html#wrapper](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?**

1.  Limited by that an account can only have one resource of a given type.
    
    1.  一个账户只能拥有一个给定类型的资源。
        
2.  Limited by that one can't `move_to` or `move_from` a resource outside the module.
    
    1.  不能 `move_to` 或 `move_from` 模块外的资源，使用了 Wrapper 后，可以再包一层，这样就能在 module 之外操作别人的 Resource 了.......
        

**How to use?**

1.  Use a `vector` or a `table` to store the objects.
    
    1.  使用 `vector` 或者 `table` 数据结构来存储多个 Object，来分发给多个地址。
        
2.  Wrapper an object in a new struct directly.
    

**Applications:**

*   `NFTGalley::NFTGalley` in StarCoin framework .
    
*   `token::TokenStore` in 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 模式

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)
            )
        }
    }
    

### phantom

在上面的例子中，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

One Time Witness (OTW) 是 Witness 模式的一个子模式，我们利用模块 `init` 函数来确保只创建一个 witness 资源实例（因此类型 A 保证是单例）。

在 Sui Move 中，如果一个类型的定义具有以下属性，则该类型被认为是一个 OTW：

*   类型以模块命名，但大写
    
    *   `module witness::peace`
        
    *   `struct 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](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 想要攻击这个合约 ：

1.  Hacker 知道 Publisher 还没来得及 `publish_coin` ，黑客想利用这个时间差抢先发币：
    
2.  Hacker 想利用 `examples::framework` 的 `publish_coin()` 和 `examples::xcoin` 的 `X` 类型来发行货币
    
3.  但是得益于 Move 的特性 —— 类型实例化的时候，只能在定义这个类型的 Module 里面进行实例化。
    
    1.  也就是说，publish() 中的类型 T 在 Module `examples::xcoin` 中被实例化为 `X`，其只能在 examples Module 中被实例化(应该是)， 所以外部 Module 无法调用 `X` 进行实例化
        
4.  `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 模式

> 烫手山芋，笑死 🤣

*   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;
        }
    }

---

*Originally published on [Demian](https://paragraph.com/@demian-2/0x11-patterns)*
