{"content":{"title":"Move中的设计模式(4)——Witness","body":"# Witness\r\n\r\n>我翻译并补充了英文版的Patterns，欢迎以下链接查看\r\n>原文链接：https://blog.chrisyy.top/move-patterns/index.html\r\n>仓库链接（欢迎提Issue和Pr）：https://github.com/chrisyy2003/move-patterns\r\n\r\n|||\r\n|-|-|\r\n| **Name** | Witness |\r\n| **Origin** | [FastX](https://github.com/MystenLabs/sui/blob/c1ed7cc451119432d32d8eacf471f314e0ce5275/fastx_programmability/sources/Coin.move#L85) / [Sam Blackshear](https://github.com/sblackshear) |\r\n| **Example** | [Sui Move by Example](https://examples.sui.io/patterns/witness.html) / [Damir Shamanaev](https://github.com/damirka) |\r\n| **Depends on** | None |\r\n| **Known to work on** | Move |\r\n\r\n## 概述\r\n\r\nwitness是一种临时资源，相关资源只能被使用一次，资源在使用后被丢弃，确保不能重复使用相同的资源来初始化任何其他结构，通常用来确认一个类型的的所有权。\r\n\r\nwitness得益于Move中的类型系统。一个类型实例化的时候，它只能在定义这个类型的模块中创建。\r\n\r\n一个简单的例子，在framework里面定义了coin合约用来定义token标准，如果想要注册token那么合约会调用`publish_coin`。\r\n\r\n```solidity\r\nmodule framework::coin {\r\n    /// The witness patameter ensures that the function can only be called by the module defined T.\r\n    public fun publish_coin<T: drop>(_witness: T) {\r\n        // register this coin to the registry table\r\n    }\r\n}\r\nmodule examples::xcoin {\r\n    use framework::coin;\r\n    /// The Witness type.\r\n    struct X has drop {}\r\n    /// Only this module defined X can call framework::publish_coin<X>\r\n    public fun publish() {\r\n        coin::publish_coin<X>(X {});\r\n    }\r\n}\r\nmodule hacker::hack {\r\n    use framework::coin;\r\n    use examples::xcoin::X;\r\n\r\n    public fun publish() {\r\n        // Illegal, X can not be constructed here.\r\n        coin::publish_coin<X>(X {}); \r\n    }\r\n}\r\n```\r\n\r\n那么如果此时有一个hacker想要抢先注册这个token，那么需要构造模块中的x提前调用`publish_coin`函数，但是由于Move中的类型系统限制了这种情况的发生，因为模块外部是不能构造其他模块的结构体资源。\r\n\r\n```solidity\r\n// Move编译器报错\r\n┌─ /sources/m.move:25:31\r\n   │\r\n25 │         coin::publish_coin<X>(X {}); \r\n   │                               ^^^^ Invalid instantiation of '(examples=0x1)::xcoin::X'.\r\nAll structs can only be constructed in the module in which they are declared\r\n```\r\n\r\n## 如何使用\r\n\r\nwitness在Sui中与其他Move公链有一些区别。\r\n\r\n如果结构类型与定义它的模块名称相同且是大写，并且没有字段或者只有一个布尔字段，则意味着它是一个one-time witness类型。该类型只会在模块初始化时使用，在合约中验证是否是one-time witness类型，可以通过sui framwork中[types::is_one_time_witness](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/types.move#L10)来验证。\r\n\r\n例如在sui的coin库中，如果需要注册一个coin类型，那么需要调用[create_currency](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/coin.move#L176)函数。函数参数则就需要一个one-time witness类型。为了传递该类型参数，需要在模块初始化`init`函数参数中第一个位置传递，即：\r\n\r\n```solidity\r\n// 注册一个M_COIN类型的通用Token\r\nmodule examples::m_coin {\r\n    use sui::coin;\r\n    use sui::transfer;\r\n    use sui::tx_context::{Self, TxContext};\r\n\r\n\t\t// 必须是模块名大写字母\r\n    struct M_COIN has drop{}\r\n\r\n\t\t// 第一个位置传递\r\n    fun init (witness: M, ctx: &mut TxContext) {\r\n        let cap = coin::create_currency(witness, 8, ctx);\r\n        transfer::transfer(cap, tx_context::sender(ctx));\r\n    }\r\n}\r\n```\r\n\r\nsui中的初始化函数只能有一个或者两个参数，且最后的参数一定是`&mut TxContext`类型，one-time witness类型同样是模块初始化时自动传递的。\r\n\r\n> `init`函数如果传递除了上述提到的以外的参数，Move编译器能够编译通过，但是部署时Sui的验证器会报错。此外如果第一个传递的参数不是one-time witness类型，同样也只会在部署时Sui验证才会报错。\r\n> \r\n\r\n## 总结\r\n\r\nwitness模式通常其他模式一同使用，例如Wrapper和capability模式。\r\n\r\n除了在sui的coin标准库中使用到了wintess，以下例子同样也有使用到：\r\n\r\n- [Liquidity pool](https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/defi/sources/pool.move)\r\n- [Regulated coin](https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/fungible_tokens/sources/regulated_coin.move)"},"author":{"user":"https://learnblockchain.cn/people/6415","address":"0xd69a77bE38f2Fb35De1aCC3AC3626E6d6c8d6928"},"history":null,"timestamp":1668271786,"version":1}