diff --git a/mover/ctianming/co-learn-2411/images/lesson_4.png b/mover/ctianming/co-learn-2411/images/lesson_4.png new file mode 100644 index 000000000..97840ffc4 Binary files /dev/null and b/mover/ctianming/co-learn-2411/images/lesson_4.png differ diff --git a/mover/ctianming/co-learn-2411/images/meeting_4.png b/mover/ctianming/co-learn-2411/images/meeting_4.png new file mode 100644 index 000000000..3a69a4b87 Binary files /dev/null and b/mover/ctianming/co-learn-2411/images/meeting_4.png differ diff --git a/mover/ctianming/co-learn-2411/images/note_share_11_12_13.png b/mover/ctianming/co-learn-2411/images/note_share_11_12_13.png new file mode 100644 index 000000000..cf2366af5 Binary files /dev/null and b/mover/ctianming/co-learn-2411/images/note_share_11_12_13.png differ diff --git a/mover/ctianming/co-learn-2411/images/note_share_8_9_10.png b/mover/ctianming/co-learn-2411/images/note_share_8_9_10.png new file mode 100644 index 000000000..465f54161 Binary files /dev/null and b/mover/ctianming/co-learn-2411/images/note_share_8_9_10.png differ diff --git a/mover/ctianming/co-learn-2411/images/readme.md b/mover/ctianming/co-learn-2411/images/readme.md index 7ced025bb..105277c20 100644 --- a/mover/ctianming/co-learn-2411/images/readme.md +++ b/mover/ctianming/co-learn-2411/images/readme.md @@ -47,4 +47,19 @@ b站关注 第三周学课程学习 ![alt text](Screenshot_2024-12-02-20-05-18-395_com.tencent.we.jpg) -12.3会议 \ No newline at end of file +12.3会议 + +![alt text](image.png) +第八、九、十篇笔记分享 + +![alt text](image.png) +第十一、十二、十三篇笔记分享 + +![alt text](image.png) +第四周课程学习 + +![alt text](image.png) +12.9会议 + +![alt text](image.png) +推特关注截图 \ No newline at end of file diff --git a/mover/ctianming/co-learn-2411/images/x_follow.png b/mover/ctianming/co-learn-2411/images/x_follow.png new file mode 100644 index 000000000..e223a1bc2 Binary files /dev/null and b/mover/ctianming/co-learn-2411/images/x_follow.png differ diff --git a/mover/ctianming/co-learn-2411/project/move_coin/Move.lock b/mover/ctianming/co-learn-2411/project/move_coin/Move.lock index 37fa667ad..103c39d8e 100644 --- a/mover/ctianming/co-learn-2411/project/move_coin/Move.lock +++ b/mover/ctianming/co-learn-2411/project/move_coin/Move.lock @@ -21,7 +21,7 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.37.3" +compiler-version = "1.38.3" edition = "2024.beta" flavor = "sui" diff --git a/mover/ctianming/co-learn-2411/project/move_coin/sources/move_coin.move b/mover/ctianming/co-learn-2411/project/move_coin/sources/move_coin.move index 84435daa5..ea35b59a2 100644 --- a/mover/ctianming/co-learn-2411/project/move_coin/sources/move_coin.move +++ b/mover/ctianming/co-learn-2411/project/move_coin/sources/move_coin.move @@ -3,53 +3,51 @@ module move_coin::move_coin; */ module move_coin::my_coin { - use sui::coin::{Self, Coin, TreasuryCap}; - use std::debug; - use std::ascii::string; - - public struct MY_COIN has drop {} - - fun init(witness: MY_COIN, ctx: &mut TxContext) { - let (treasury, metadata) = coin::create_currency( - witness, - 6, - b"MOON", - b"CTIANMING_MY_COIN", - b"MOON_COIN", - option::none(), - ctx); - debug::print(&string(b"init MY_COIN")); - transfer::public_freeze_object(metadata); - transfer::public_transfer(treasury, ctx.sender()) - } - - public entry fun mint( - treasury_cap: &mut TreasuryCap, - amount: u64, - recipient: address, - ctx: &mut TxContext, - ) { - debug::print(&string(b"my_coin mint")); - let coin = coin::mint(treasury_cap, amount, ctx); - transfer::public_transfer(coin, recipient) - } - - public entry fun burn( - treasury_cap: &mut TreasuryCap, - coin: Coin - ) { - debug::print(&string(b"burn")); - coin::burn(treasury_cap, coin); - } - - #[test_only] + use std::ascii::string; + use std::debug; + use sui::coin::{Self, Coin, TreasuryCap}; + + public struct MY_COIN has drop {} + + fun init(witness: MY_COIN, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 6, + b"MOON", + b"CTIANMING_MY_COIN", + b"MOON_COIN", + option::none(), + ctx, + ); + debug::print(&string(b"init MY_COIN")); + transfer::public_freeze_object(metadata); + transfer::public_transfer(treasury, ctx.sender()) + } + + public entry fun mint( + treasury_cap: &mut TreasuryCap, + amount: u64, + recipient: address, + ctx: &mut TxContext, + ) { + debug::print(&string(b"my_coin mint")); + let coin = coin::mint(treasury_cap, amount, ctx); + transfer::public_transfer(coin, recipient) + } + + public entry fun burn(treasury_cap: &mut TreasuryCap, coin: Coin) { + debug::print(&string(b"burn")); + coin::burn(treasury_cap, coin); + } + + #[test_only] use sui::test_scenario as ts; #[test] fun test_self_mint() { let admin = @0xA; let mut scenario = ts::begin(admin); - + // init ts::next_tx(&mut scenario, admin); { @@ -100,7 +98,7 @@ module move_coin::my_coin { }; // mint 100 coin => user1 - ts::next_tx(&mut scenario, admin); // if change to user1, failed! + ts::next_tx(&mut scenario, admin); // if change to user1, failed! { let mut treasurycap = ts::take_from_sender>(&scenario); mint(&mut treasurycap, 100, user1, scenario.ctx()); @@ -127,53 +125,51 @@ module move_coin::my_coin { } module move_coin::faucet_coin { - use sui::coin::{Self, Coin,TreasuryCap}; - use std::debug; - use std::ascii::string; - - public struct FAUCET_COIN has drop {} - - fun init(witness: FAUCET_COIN, ctx: &mut TxContext) { - let (treasury, metadata) = coin::create_currency( - witness, - 6, - b"MOON", - b"CTIANMING_FAUCET_COIN", - b"MOON_COIN", - option::none(), - ctx); - debug::print(&string(b"init FAUCET_COIN")); - transfer::public_freeze_object(metadata); - transfer::public_share_object(treasury) - } - - public entry fun mint( - treasury_cap: &mut TreasuryCap, - amount: u64, - recipient: address, - ctx: &mut TxContext, - ) { - debug::print(&string(b"faucet_coin mint")); - let coin = coin::mint(treasury_cap, amount, ctx); - transfer::public_transfer(coin, recipient) - } - - public entry fun burn( - treasury_cap: &mut TreasuryCap, - coin: Coin - ) { - debug::print(&string(b"burn")); - coin::burn(treasury_cap, coin); - } - - #[test_only] + use std::ascii::string; + use std::debug; + use sui::coin::{Self, Coin, TreasuryCap}; + + public struct FAUCET_COIN has drop {} + + fun init(witness: FAUCET_COIN, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 6, + b"MOON", + b"CTIANMING_FAUCET_COIN", + b"MOON_COIN", + option::none(), + ctx, + ); + debug::print(&string(b"init FAUCET_COIN")); + transfer::public_freeze_object(metadata); + transfer::public_share_object(treasury) + } + + public entry fun mint( + treasury_cap: &mut TreasuryCap, + amount: u64, + recipient: address, + ctx: &mut TxContext, + ) { + debug::print(&string(b"faucet_coin mint")); + let coin = coin::mint(treasury_cap, amount, ctx); + transfer::public_transfer(coin, recipient) + } + + public entry fun burn(treasury_cap: &mut TreasuryCap, coin: Coin) { + debug::print(&string(b"burn")); + coin::burn(treasury_cap, coin); + } + + #[test_only] use sui::test_scenario as ts; #[test] fun test_faucet_coin() { let admin = @0xCAFE; let user0 = @0xFECA; - let user1 = @0xABCD; + let user1 = @0xABCD; let mut scenario = ts::begin(admin); // init @@ -214,7 +210,7 @@ module move_coin::faucet_coin { ts::return_to_sender>(&scenario, coin); }; - // query [user1] coin + // query [user1] coin ts::next_tx(&mut scenario, user1); { let coin = ts::take_from_sender>(&scenario); @@ -240,7 +236,7 @@ module move_coin::faucet_coin { ts::return_shared>(treasurycap); }; - // burn [user1] coin,共享所有权,可以分别对自己的coin操作! + // burn [user1] coin,共享所有权,可以分别对自己的coin操作! ts::next_tx(&mut scenario, user1); { let coin = ts::take_from_sender>(&scenario); @@ -252,4 +248,4 @@ module move_coin::faucet_coin { ts::end(scenario); // pass } -} \ No newline at end of file +} diff --git a/mover/ctianming/co-learn-2411/readme.md b/mover/ctianming/co-learn-2411/readme.md index 9f67ab1b9..3a8e84342 100644 --- a/mover/ctianming/co-learn-2411/readme.md +++ b/mover/ctianming/co-learn-2411/readme.md @@ -2,7 +2,9 @@ ## b站,推特关注 -- [x] b站,推特关注截图: ![关注截图](./images/b_follow.png) +- [x] b站关注截图: ![关注截图](./images/b_follow.png) +- [x] b站关注截图: ![关注截图](./images/x_follow.png) + ## 为共学营宣传(在朋友圈或者群聊中转发海报/文章) @@ -13,14 +15,14 @@ - [x] 第一周:![学习记录截图](./images/lesson_1.png) - [x] 第二周:![学习记录截图](./images/lesson_2.png) - [x] 第三周:![学习记录截图](./images/lesson_3.png) -- [] 第四周:![学习记录截图](./images/你的图片地址) +- [x] 第四周:![学习记录截图](./images/lesson_4.png) ## 参加直播答疑 - [x] 第一周:![学习记录截图](./images/meeting_1.jpg) - [x] 第二周:![学习记录截图](./images/meeting_2.png) - [x] 第三周:![学习记录截图](./images/meeting_3.jpg) -- [] 第四周:![学习记录截图](./images/你的图片地址) +- [x] 第四周:![学习记录截图](./images/meeting_4.png) ## 群里分享学习笔记 @@ -31,6 +33,12 @@ - [x] 第五篇笔记: ![第五篇笔记分享](./images/note_share_5.png) - [x] 第六篇笔记: ![第六篇笔记分享](./images/note_share_6.png) - [x] 第七篇笔记: ![第七篇笔记分享](./images/note_share_7.png) +- [x] 第八篇笔记: ![第八篇笔记分享](./images/note_share_8_9_10.png) +- [x] 第九篇笔记: ![第九篇笔记分享](./images/note_share_8_9_10.png) +- [x] 第十篇笔记: ![第十篇笔记分享](./images/note_share_8_9_10.png) +- [x] 第十一篇笔记: ![第十一篇笔记分享](./images/note_share_11_12_13.png) +- [x] 第十二篇笔记: ![第十二篇笔记分享](./images/note_share_11_12_13.png) +- [x] 第十三篇笔记: ![第十三篇笔记分享](./images/note_share_11_12_13.png) ## 对外输出学习笔记 @@ -41,6 +49,12 @@ - [x] 第五篇笔记【https://learnblockchain.cn/article/9969】 - [x] 第六篇笔记【https://learnblockchain.cn/article/10021】 - [x] 第七篇笔记【https://learnblockchain.cn/article/10171】 +- [x] 第八篇笔记【https://learnblockchain.cn/article/10199】 +- [x] 第九篇笔记【https://learnblockchain.cn/article/10201】 +- [x] 第十篇笔记【https://learnblockchain.cn/article/10202】 +- [x] 第十一篇笔记【https://learnblockchain.cn/article/10215】 +- [x] 第十二篇笔记【https://learnblockchain.cn/article/10216】 +- [x] 第十三篇笔记【https://learnblockchain.cn/article/10220】 ## 在HOH社区公众号发布自己的技术文章 diff --git a/mover/ctianming/notes/call_by_interface.png b/mover/ctianming/notes/call_by_interface.png new file mode 100644 index 000000000..718c73df5 Binary files /dev/null and b/mover/ctianming/notes/call_by_interface.png differ diff --git a/mover/ctianming/notes/image-3.png b/mover/ctianming/notes/image-3.png new file mode 100644 index 000000000..04f236888 Binary files /dev/null and b/mover/ctianming/notes/image-3.png differ diff --git "a/mover/ctianming/notes/sui-move\345\237\272\347\241\200\357\274\210\344\270\211\357\274\211\357\274\232letsmove-task2.md" "b/mover/ctianming/notes/sui-move\345\237\272\347\241\200\357\274\210\344\270\211\357\274\211\357\274\232letsmove-task2.md" index e92a2a58f..087da6c63 100644 --- "a/mover/ctianming/notes/sui-move\345\237\272\347\241\200\357\274\210\344\270\211\357\274\211\357\274\232letsmove-task2.md" +++ "b/mover/ctianming/notes/sui-move\345\237\272\347\241\200\357\274\210\344\270\211\357\274\211\357\274\232letsmove-task2.md" @@ -153,7 +153,7 @@ public fun create_currency( ``` 我们来逐一解释一下该函数的参数: -- witness 是一个类型 `T` 的一次性证明者,确保货币只被创建一次。通过 sui::types::is_one_time_witness 进行检查。具体参考:https://move.sui-book.com/programmability/witness-pattern.html +- witness 是一个类型 `T` 的一次性见证者,确保货币只被创建一次。通过 sui::types::is_one_time_witness 进行检查。具体参考:https://move.sui-book.com/programmability/witness-pattern.html - decimals 指定货币支持的小数位数,定义其精度。 - symbol 是一个字节向量,表示货币的符号,例如 "USD" 或 "BTC"。 - name 指定货币的名称。 diff --git "a/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232Phantom type Parameter.md" "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232Phantom type Parameter.md" new file mode 100644 index 000000000..b1161bccc --- /dev/null +++ "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232Phantom type Parameter.md" @@ -0,0 +1,218 @@ +# sui-move进阶:Move Phantom Type Parameter + +在 Move 编程语言中,Phantom Type Parameter 是一个强大且灵活的特性,允许开发者通过类型参数区分逻辑类型,而无需为这些类型参数引入不必要的能力(Abilities)。 + +本教程将详细讲解Phantom Type Parameter的定义、用法及最佳实践。 + +*注: 考虑到目前针对`Phantom Type Parameter`没有一个统一的翻译,我在这里维持原文,但你愿意的话,或许可以在阅读时将其翻译为:`Phantom Type Parameter`* + +## Phantom Type Parameter的核心概念 + +Phantom Type Parameter是未在结构体主体中使用,或仅在其他Phantom Type Parameter中使用的类型参数。它们的主要目的是提供逻辑上的区分,而不会影响结构体的能力推导。 + +核心特点: + +- 不参与能力推导: + +Phantom Type Parameter不会影响结构体能力的派生结果。 + +- 仅作逻辑区分: + +提供类型区分,用于逻辑处理,而不需要额外的能力约束。 + +示例:带有Phantom Type Parameter的结构体 + +```move + +public struct Coin has store { + value: u64 +} +``` + +在上述定义中: + +`Currency` 是一个Phantom Type Parameter,仅用作逻辑标识,不参与能力推导。 +`Coin` 的能力仅由其 `value: u64` 决定,而与 `Currency` 无关。 + +## 为什么需要Phantom Type Parameter? + +Move 的类型系统要求泛型类型的能力由其所有类型参数的能力决定。然而,在某些情况下,类型参数仅用作逻辑区分,而非真正参与结构体功能。此时引入Phantom Type Parameter可以避免不必要的能力声明。 + +常见问题:非Phantom Type Parameter带来的问题 + +假设我们定义一个 Coin 结构,但未使用Phantom Type Parameter: + +```move +public struct Coin has store { + value: u64 +} +``` + +如果 Currency 没有 store 能力,则: + +无法将 `Coin` 存储在全局状态中。 +为了满足 `store` 能力,我们可能会错误地为 `Currency` 添加虚假的能力声明: + +```move +public struct Currency has store {} // 可能导致安全漏洞 +``` + +这样的做法会: + +- 弱化类型安全:类型 Currency 本不需要 store,却被赋予了该能力。 + +- 增加复杂性:泛型函数也需要处理这些额外的约束。 + +使用Phantom Type Parameter可以解决这一问题。 + +## Phantom Type Parameter的声明 + +在结构体定义中,可以通过在类型参数前添加 phantom 关键字将其声明为Phantom Type Parameter。 + +声明格式: +```move +public struct StructName { + // 定义内容 +} +``` + +示例: + +```move +public struct Coin has store { + value: u64 +} +``` + +Move 的类型检查器会确保: + +Phantom Type Parameter未在结构体中使用,或仅作为其他Phantom Type Parameter的参数。 +如果不符合上述规则,则编译会报错。 + +## 使用规则 + +合法用法: +Phantom Type Parameter可以: + +完全未出现在结构体定义中。 +仅出现在其他Phantom Type Parameter的类型参数中。 +```move +复制代码 +public struct S1 { f: u64 } +// ^^^^^^^ 合法,T1 未在结构体中使用 + +public struct S2 { f: S1 } +// ^^^^^^^ 合法,T1 出现在phantom位置 +``` + +非法用法: + +Phantom Type Parameter如果直接作为字段类型或非Phantom Type Parameter的参数,会导致编译错误。 + +```move +复制代码 +public struct S1 { f: T } +// ^^^^^^^ 错误!T 未出现在phantom位置 + +public struct S2 { f: T } +public struct S3 { f: S2 } +// ^^^^^^^ 错误!T 未出现在phantom位置 +``` + +## Phantom Type Parameter的能力推导 + +在实例化结构体时,Phantom Type Parameter不会参与能力推导。例如: + +```move +复制代码 +public struct S has copy { + f: T1 +} + +public struct NoCopy {} +public struct HasCopy has copy {} +``` + +如果实例化为 `S`: + +- T1 = HasCopy,具有 copy 能力。 + +- T2 = NoCopy,但由于 T2 是Phantom Type Parameter,它的能力不会影响 S 的能力推导。 + +因此,`S` 仍具有 copy 能力。 + +## 实例分析:Coin + +```move +/// 表示一种特定类型的代币 `T` +public struct Coin has key, store { + id: UID, + balance: Balance, +} +``` + +关键点解析 + +phantom T: + +类型参数 T 被声明为Phantom Type Parameter,只用于逻辑区分不同的代币类型,而不会影响结构体的能力推导。 + +例如:`Coin` 和 `Coin` 是不同的类型,分别表示美元和欧元的代币。 + +能力 has key, store: + +- Coin 可以存储在全局状态中(store)。 + +- Coin 具有唯一标识符(key),可通过 UID 管理。 +字段解析: + +- id: UID: 代币的唯一标识符。 +- balance: `Balance`: 代币的余额,依赖于类型参数 T。 + +2. Phantom Type Parameter的作用 + +逻辑区分 + +通过Phantom Type Parameter,Coin 可以表示不同类型的代币,而无需为这些类型声明额外的能力。例如: + +```move +let coin_usd = Coin { id: ..., balance: ... }; +let coin_eur = Coin { id: ..., balance: ... }; +``` + +即使 USD 和 EUR 都没有声明能力,Coin 的能力依然由 key 和 store 决定。 + +避免能力传播 +假如 T 不是Phantom Type Parameter,那么 T 的能力将直接影响 `Coin` 的能力推导。例如: + +如果 T 没有 store 能力,则 `Coin` 无法存储在全局状态中。 + +使用 phantom 关键字可以避免这些问题,使 T 仅用于逻辑区分。 + +## 注意事项 + +编译器警告: + +如果一个类型参数可以是Phantom Type Parameter却未标记为 phantom,编译器会发出警告。 +避免滥用能力: + +使用Phantom Type Parameter可以避免为类型参数添加不必要的能力声明,从而提升系统的安全性和健壮性。 +配合类型系统设计: + +Phantom Type Parameter适合逻辑区分的场景,例如不同货币类型、标识符等。 + +## 总结与最佳实践 +Phantom Type Parameter提供了一种简洁、高效的方式来处理逻辑区分,同时确保类型系统的安全性和灵活性。使用Phantom Type Parameter时,建议遵循以下最佳实践: + +仅在必要时使用: + +如果类型参数直接参与功能实现,不应标记为 phantom。 + +合理设计结构: + +将Phantom Type Parameter用于区分逻辑类型,例如资产、资源或身份标识符。 +结合能力系统: + +利用Phantom Type Parameter避免滥用能力声明,从而提高系统的健壮性。 + +通过对Phantom Type Parameter的灵活使用,可以构建更加安全、高效的区块链应用。 \ No newline at end of file diff --git "a/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232coin.move\346\272\220\347\240\201\345\210\206\346\236\220.md" "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232coin.move\346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 000000000..4f2a85e7a --- /dev/null +++ "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232coin.move\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,307 @@ +# sui-move进阶:coin.move源码分析 + +`coin.move` 是 Sui Move 中实现可替代代币(fungible tokens)的核心模块(实际上,因为sui"一切皆对象"和所有权的设计,也自然而然地可以用来实现NFT)。它提供了创建、管理和操作代币的基础工具,包括代币的生成、分割、合并、供应量管理以及监管功能。 + +在本教程中,我将部分解析 `coin.move` 的设计与功能。 + +--- + +## 模块概述 + +`coin.move` 旨在提供一套通用的代币操作接口,并支持高级功能如: +- 代币分割与合并 +- 总供应量管理 +- 元数据存储与更新 +- 受监管的代币功能(如地址限制、全局暂停) + +--- + +## 核心结构体 + +### Coin +`Coin` 是代币的核心结构,用于表示某种类型 `T` 的代币及其余额。 + +```move +public struct Coin has key, store { + id: UID, + balance: Balance, +} +``` + +字段解析: + +- id: 每个 Coin 实例的唯一标识符。 +- balance: 表示代币的余额,使用 Balance 类型。 +功能解析 +- phantom T 确保 Coin 类型之间互不干扰。 +- key, store 能力允许 Coin 存储在全局状态中。 + +### CoinMetadata + +`CoinMetadata` 用于存储代币的元数据信息,如名称、符号、小数位数等。 + +```move +public struct CoinMetadata has key, store { + id: UID, + decimals: u8, + name: string::String, + symbol: ascii::String, + description: string::String, + icon_url: Option, +} +``` + +字段解析: + +- decimals: 小数位数,用于显示格式化的代币余额。 +- name: 代币的名称,如 "US Dollar"。 +- symbol: 代币符号,如 "USD"。 +- description: 对代币的描述。 +- icon_url: 可选的图标 URL。 + +### TreasuryCap + +`TreasuryCap` 用于管理代币的总供应量。 + +```move +public struct TreasuryCap has key, store { + id: UID, + total_supply: Supply, +} +``` + +功能解析: + +- 提供安全的供应量管理。 +- 确保某种类型的代币只能由其唯一的 TreasuryCap 控制。 + +### DenyCapV2 + +`DenyCapV2` 为受监管代币提供高级功能,如地址限制和全局暂停。 + +```move +public struct DenyCapV2 has key, store { + id: UID, + allow_global_pause: bool, +} +``` + +功能解析: + +- allow_global_pause: 允许启用全局暂停,禁止所有地址操作该代币。 + +## 核心功能解析 + +### 基础代币操作 + +#### 查询余额 + +通过 value 获取 Coin 的余额: + +```move +public fun value(self: &Coin): u64 { + self.balance.value() +} +``` + +#### 分割代币 + +将一个代币分割为两部分: + +```move +public fun split(self: &mut Coin, split_amount: u64, ctx: &mut TxContext): Coin { + take(&mut self.balance, split_amount, ctx) +} +``` + +#### 合并代币 + +将两个代币合并: + +```move +public entry fun join(self: &mut Coin, c: Coin) { + let Coin { id, balance } = c; + id.delete(); + self.balance.join(balance); +} +``` + +#### 生成零余额代币 + +创建一个余额为 0 的占位代币: + +```move +public fun zero(ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance: balance::zero() } +} +``` +### 总供应量管理 + +#### 查询总供应量 + +```move +public fun total_supply(cap: &TreasuryCap): u64 { + balance::supply_value(&cap.total_supply) +} +``` + +#### 铸造代币 + +通过 TreasuryCap 创建新的代币: + +```move +public fun mint(cap: &mut TreasuryCap, value: u64, ctx: &mut TxContext): Coin { + Coin { + id: object::new(ctx), + balance: cap.total_supply.increase_supply(value), + } +} +``` + +#### 销毁代币 + +减少代币的总供应量: + +```move +public entry fun burn(cap: &mut TreasuryCap, c: Coin): u64 { + let Coin { id, balance } = c; + id.delete(); + cap.total_supply.decrease_supply(balance) +} +``` +### 监管代币 + +#### 地址限制 + +添加地址到禁止列表: + +```move +public fun deny_list_v2_add( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCapV2, + addr: address, + ctx: &mut TxContext, +) { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_add(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} +``` + +#### 启用全局暂停 + +立即禁止所有地址使用代币: + +```move +public fun deny_list_v2_enable_global_pause( + deny_list: &mut DenyList, + deny_cap: &mut DenyCapV2, + ctx: &mut TxContext, +) { + assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_enable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) +} +``` + +### 元数据管理 + +#### 更新元数据 + +支持动态更新代币的元数据: + +```move +public entry fun update_name( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + name: string::String, +) { + metadata.name = name; +} +``` + +## 示例:创建和管理代币 + +```move +public fun create_currency( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + ctx: &mut TxContext, +): (TreasuryCap, CoinMetadata) { + ( + 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, + }, + ) +} +``` + +注意这个`witness`,这里使用了`一次性见证者`的设计模式,我将在下一篇教程中进行讲述与探讨。 + +而我们同样可以创建被管理的代币: + +```move +public fun create_regulated_currency_v2( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + allow_global_pause: bool, + ctx: &mut TxContext, +): (TreasuryCap, DenyCapV2, CoinMetadata) { + let (treasury_cap, metadata) = create_currency( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + let deny_cap = DenyCapV2 { + id: object::new(ctx), + allow_global_pause, + }; + transfer::freeze_object(RegulatedCoinMetadata { + id: object::new(ctx), + coin_metadata_object: object::id(&metadata), + deny_cap_object: object::id(&deny_cap), + }); + (treasury_cap, deny_cap, metadata) +} +``` + +源代码对其的解释是: + +>通过调用 `create_currency` 创建一种新的货币类型,并附加了一项额外功能, +> +>允许将特定地址的代币冻结。当一个地址被添加到禁止列表时, +它将立即无法将该货币的代币用作交易的输入对象。 +> +>此外,从下一个纪元开始,这些地址将无法接收该货币的代币。 +`allow_global_pause` 标志启用了一个额外的 API,可禁止所有地址操作该货币的代币。 +> +>需要注意的是,这不会影响禁止列表中针对单个地址的条目, +也不会更改 "contains" API 的返回结果。 + +另外,如果需要使用上述代币管理的相关功能,在创建代币时,我们必须调用`create_regulated_currency_v2`创建可被管理的代币。 + +## 总结 + +Coin是sui链的基本资产,因此,理解和使用Coin也是sui move学习最重要的一部分。 + +还有一些额外的功能与特性我在本篇文章没有提到,希望大家主动去阅读源码,甚至编写用例进行调试。 \ No newline at end of file diff --git "a/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\345\233\233\347\247\215\350\203\275\345\212\233\345\217\212\345\205\266\347\273\204\345\220\210.md" "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\345\233\233\347\247\215\350\203\275\345\212\233\345\217\212\345\205\266\347\273\204\345\220\210.md" new file mode 100644 index 000000000..0edc8f8ad --- /dev/null +++ "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\345\233\233\347\247\215\350\203\275\345\212\233\345\217\212\345\205\266\347\273\204\345\220\210.md" @@ -0,0 +1,233 @@ +# sui-move进阶:四种能力及其组合 + +在 Sui Move 中,**能力(Abilities)** 是一种类型系统特性,用于约束资源或结构体的行为。能力声明定义了某种类型的可操作性,如存储、复制或丢弃。Sui Move 提供了四种能力:`store`、`key`、`copy` 和 `drop`。本教程将深入解析这四种能力的用途、规则以及它们在智能合约设计中的应用。 + +--- + +## 什么是能力? + +能力是一种类型属性,用于描述资源或结构体在 Move 中的操作约束。例如: +- 某些类型可以被存储在全局状态中。 +- 某些类型可以被复制或丢弃。 + +Move 的能力系统通过显式声明这些行为限制,确保资源的安全性和操作的合理性。 + +--- + +## Sui Move 的四种能力 + +### `store` 能力 + +**`store`** 表示一种类型可以存储在全局状态中。例如,区块链上的资源通常需要存储,因此它们需要具备 `store` 能力。 + +规则: + +- 只有具备 `store` 能力的类型,才能存储在全局状态中。 + +- 使用 `has store` 显式声明。 + +示例: + +```move +module example::StoreExample { + public struct StorableResource has store { + id: u64, + value: u64, + } + + public fun save_resource(resource: StorableResource) { + move_to(@0x1, resource); + } +} +``` + +应用场景: + +- 代币(Coin):需要存储到链上的全局状态中。 + +- 智能合约配置:如参数和状态信息。 + +### key 能力 + +key 表示一种类型可以通过唯一标识符存储在全局状态中。带有 key 能力的类型必须具备一个 UID 字段,用于唯一标识该对象。 + +规则: + +- 必须具备 key 能力才能使用 move_to 将资源存储到全局状态。 + +- 带有 key 能力的类型通常会包含一个 UID 字段。 + +示例: + +```move +module example::KeyExample { + use sui::object::UID; + + public struct KeyedResource has key { + id: UID, + value: u64, + } + + public fun save_keyed_resource(resource: KeyedResource) { + move_to(@0x1, resource); + } +} +``` + +应用场景: + +- 唯一资源:如 NFT。 + +- 账户标识:需要通过 UID 唯一标识的资源。 + +### copy 能力 + +copy 表示一种类型的值可以被复制。与资源(如代币)不同,某些数据类型(如整数)需要频繁复制以简化操作,这种情况下需要具备 copy 能力。 + +规则: + +- 默认情况下,基本数据类型(如 u8、bool)具备 copy 能力。 + +- 使用 has copy 显式声明后,自定义类型也可以支持复制。 + +示例: + +```move +module example::CopyExample { + public struct CopyableResource has copy { + value: u64, + } + + public fun duplicate(resource: CopyableResource): (CopyableResource, CopyableResource) { + (resource, resource) // 允许复制 + } +} +``` + +应用场景: + +- 临时变量:如配置、计数器。 + +- 轻量级对象:不需要严格的所有权管理。 + +### drop 能力 + +drop 表示一种类型的值可以被丢弃。某些类型在生命周期结束时必须显式销毁(如资源类型),但带有 drop 能力的类型可以自动丢弃。 + +规则: + +- 默认情况下,基本数据类型(如 u8、bool)具备 drop 能力。 + +- 自定义类型需通过 has drop 显式声明支持丢弃。 + +示例: + +```move +module example::DropExample { + public struct DroppableResource has drop { + value: u64, + } + + public fun discard(resource: DroppableResource) { + // 自动丢弃,无需显式销毁 + } +} +``` + +应用场景: + +- 临时数据:如日志信息。 + +- 非关键性资源:无需严格管理生命周期的类型。 + +## 能力的组合 + +能力可以组合使用,以满足不同场景的需求。例如: + +```move +module example::CombinedAbilities { + use sui::object::UID; + + /// 具备 key 和 store 能力的资源 + public struct ManagedResource has key, store { + id: UID, + value: u64, + } +} +``` + +- key, store:该资源可以通过唯一标识符存储到链上的全局状态中。 + +- 一般而言key 与store 的组合是最常见的,而copy 和drop 可以根据需要进行组合。 + +## 能力的约束与推导 + +在使用泛型和结构体时,能力可以被约束或推导: + +能力约束: 使用泛型类型时,可以指定能力约束: + +```move +public fun example(resource: T) { + move_to(@0x1, resource); +} +``` + +能力推导: 类型的能力可以根据其字段的能力自动推导。例如: + +```move +public struct DerivedResource { + value: T, +} +``` + +如果 T 具备 store 能力,则 `DerivedResource` 也具备 store 能力。 + +## 实践:设计具备能力的智能合约 +代币实现示例 +结合以上能力设计一个基本的代币结构: + +```move +module example::Token { + use sui::object::UID; + + /// 定义代币资源 + public struct Token has key, store { + id: UID, + balance: u64, + } + + /// 创建新代币 + public fun mint(id: UID, amount: u64): Token { + Token { id, balance: amount } + } + + /// 销毁代币 + public fun burn(token: Token) { + token.id.delete(); // 显式销毁 UID + } +} +``` +6. 总结 + +- 能力的选择: + - 根据类型的用途合理选择能力,避免不必要的能力传播。 + +- 能力的限制: + - 利用能力限制类型行为,例如禁止复制或丢弃关键资源。 + +- 结合类型系统设计: + - 使用能力约束设计泛型函数,提升代码的灵活性和安全性。 + +Sui Move目前的四种能力已经足够为目前实践中的项目提供便利和保障。而目前sui move 并不允许开发者自定义能力以满足一些额外的需求。 + +当然,肯定有学过rust的朋友联想起了trait,并疑惑: + +为什么sui move 不允许自定义能力呢? + +答:为了安全。在区块链的世界里,再怎么谨慎也不为过了。 + +但也不必灰心,据内部消息,Sui 官方正有允许自定义能力的计划,或许就在明年也说不定。 + + + + diff --git "a/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\345\274\225\347\224\250.md" "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\345\274\225\347\224\250.md" new file mode 100644 index 000000000..00993413a --- /dev/null +++ "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\345\274\225\347\224\250.md" @@ -0,0 +1,181 @@ +# sui-move进阶:引用 + +在 Move 中,*引用(References)* 是一种高效且安全的资源访问方式。引用有两种类型:*不可变引用*和*可变引用*。Move 的类型系统通过强制执行所有权规则,防止引用错误,并确保资源的安全使用。 + +--- + +## 引用类型 + +在Move中,引用分为可变引用(mutable reference)和不可变引用(imutable reference): + +### 不可变引用(`&`) +- 只读访问底层值。 + +### 可变引用(`&mut`) + +- 允许通过引用修改底层值。 + +## 引用操作符 + +Move 提供了一系列操作符,用于创建、扩展引用以及将可变引用转换为不可变引用。 + +| **语法** | **类型** | **描述** | +|------------|----------------------------------|-----------------------------------------------| +| `&e` | `&T`,其中 `e: T` 且 `T` 不是引用类型 | 创建 `e` 的不可变引用 | +| `&mut e` | `&mut T`,其中 `e: T` 且 `T` 不是引用类型 | 创建 `e` 的可变引用 | +| `&e.f` | `&T`,其中 `e.f: T` | 创建结构体 `e` 的字段 `f` 的不可变引用 | +| `&mut e.f` | `&mut T`,其中 `e.f: T` | 创建结构体 `e` 的字段 `f` 的可变引用 | +| `freeze(e)`| `&T`,其中 `e: &mut T` | 将可变引用 `e` 转换为不可变引用 | + +--- + +与引用(reference)操作符相对的还有解引用(dereference)操作符,我将在后面介绍。 + +### 创建与扩展引用 + +#### 创建引用 + +以下是如何创建引用的示例: + +```move +let x = 10; +let ref_x: &u64 = &x; // 不可变引用 +let mut y = 20; +let ref_y: &mut u64 = &mut y; // 可变引用 +``` + +需要注意的是,需要资源本身是可变的,才能创建其可变引用;相应的,也可以创建它的不可变引用。 + +而如果资源本身是不可变的,则只能创建不可变引用。 + +#### 扩展引用 + +扩展引用支持访问结构体的字段: + +```move +struct S { f: u64 } +let s = S { f: 10 }; +let f_ref1: &u64 = &s.f; // 直接创建字段的引用 +let s_ref: &S = &s; +let f_ref2: &u64 = &s_ref.f; // 从结构体的引用扩展引用 +``` + +#### 多字段引用 + +如果两个结构体在同一模块中,可以通过多层引用访问嵌套字段: + +```move +public struct A { b: B } +public struct B { c: u64 } +fun get_nested_field(a: &A): &u64 { + &a.b.c // 访问嵌套字段的引用 +} +``` +### 禁止引用的引用 + +Move 不允许引用的引用,这种设计确保了引用的简单性和安全性。例如: + +```move +let x = 7; +let y: &u64 = &x; +let z: &&u64 = &y; // 错误!Move 不支持引用的引用 +``` + +## 解引用(dereference) + +可变和不可变引用都可以被解引用以产生被引用值的副本。 + +与引用操作符`&`相对的,解引用操作符是`*`,这或许很像c语言中的指针操作符,但从根本上存在很大不同。 + +只有可变引用可以被写入。写入`*x = v`会丢弃之前存储在x中的值,并用v更新它。 + +### 解引用操作符 + +| 语法 | 类型 | 描述 | +|-----------|-------------------------------|--------------------| +| `*e` | `T`, 其中 `e` 是 `&T` 或 `&mut T` | 读取 `e` 指向的值 | +| `*e1 = e2`| `()`, 其中 `e1: &mut T` 且 `e2: T` | 用 `e2` 更新 `e1` 中的值 | + +### 解引用的内涵 + +另,为了进一步了解解引用,我们尤其需要知道,解引用的具体情况是什么: + +以此代码为例: +```move +let a = 10; +let b = &a; +*b = 12; //不允许,a 是不可变的 + +let mut c = 10; +let mut d = &mut c; +*d = 12; //允许,c可变,且d是可变引用 + +let e = *b; +let f = *d; +``` + +我们可以看到,在上述代码中,b是a的不可变引用,d是c的可变引用。 + +而通过解引用操作符`*`,`*b`和`*d`实际上试图修改的分别是a和c的值,在示例代码中,`*b`进行的操作是不合法的,`*d`进行的操作是合法的。 + +`*b=12;`这一行代码将会报错,而我们如果在`*d=12;`这一行之后打印`c`的值,将会发现它的值变为了12。 + +而在这之后的两行代码,是为了展示解引用操作同样可以用于给变量赋值。 + +### 解引用操作的限制 + +为了能读取引用,底层类型必须具有copy能力,因为读取引用会创建值的新副本。这条规则防止了资产被复制: + +```move +fun copy_coin_via_ref_bad(c: Coin) { + let c_ref = &c; + let counterfeit: Coin = *c_ref; // 不允许! +} +``` + +相对地:为了能写入引用,底层类型必须具有drop能力,因为写入引用会丢弃(或"删除")旧值。换言之,不期望被销毁的资源不应当为其赋予drop能力。这条规则防止了资源值被销毁: + +```move +fun destroy_coin_via_ref_bad(mut ten_coins: Coin, c: Coin) { + let ref = &mut ten_coins; + *ref = c; // 错误! 不允许--会销毁10个硬币! +} +``` + +## 引用的作用域 + +### Move对于引用的宽松检查 + +可变和不可变引用都可以随时被复制和扩展,即使存在同一引用的现有副本或扩展: + +```move +public struct S { + f: u32, +} + +fun reference_copies(s: &mut S) { + let s_copy1 = s; // 可以 + let s_extension = &mut s.f; // 也可以 + let s_copy2 = s; // 仍然可以 + let s_extension_2 = &mut s.f; // 居然还是可以 + *s_extension = 1; + *s_extension_2 = 2; //甚至可以存在多个解引用 + let out_extension = &s_extension; + //但是不允许存在引用的引用,这里会报错:Expected a single non-reference type, but found: '&mut u32' +} + +``` + +这可能会让熟悉Rust所有权系统的程序员感到惊讶,Rust会拒绝上面的代码。Move的类型系统在处理复制时更加宽松,但在写入前确保可变引用的唯一所有权方面同样严格。 + +实质上,这种特性应该来源于Move中的引用需要进行复制的特殊性质。如图: + +![alt text](image-3.png) + +熟悉Rust语言的朋友应该知道,Rust中的引用可以被理解为指向某一资源的指针,所以不允许多个指针指向同一资源是自然而然的,这可能会导致资源争用。 + +而在Move中,由于每次进行引用都会进行复制,所以对同一资源的多次引用是允许的。 + +### 引用不能被存储 + +引用和元组是**唯一**不能作为结构体字段值存储的类型,这也意味着它们不能存在于存储或对象中。在程序执行期间创建的所有引用都会在Move程序终止时被销毁;它们完全是短暂的。这也适用于所有没有store能力的类型:任何非store类型的值必须在程序终止前被销毁。但请注意引用和元组更进一步,从一开始就不允许存在于结构体中。 diff --git "a/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\347\224\237\345\221\275\345\221\250\346\234\237.md" new file mode 100644 index 000000000..ae01c02b1 --- /dev/null +++ "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -0,0 +1,321 @@ +# sui-move进阶:生命周期 + +对于许多语言的初学者而言,很可能缺乏生命周期的概念,例如通过C语言入门编程的朋友们。但在例如Move和Rust这样的注重安全的编程语言中,生命周期是一个非常重要的概念,甚至许多时候编译器会要求你注意变量的生命周期。 + +在 Sui Move 编程中,生命周期用于描述变量和资源的存在周期及其作用范围。Move 的生命周期模型通过静态类型系统强制执行所有权和引用规则,确保资源的安全性,防止常见的内存管理错误,如悬挂引用或双重释放。 + +本教程将详细讲解 Sui Move 的生命周期管理,包括生命周期的基本概念、作用范围、引用与所有权的交互,以及常见的生命周期规则。 + +> `遮蔽`与`Move和Copy`两节引用了sui move reference:https://reference.sui-book.com + +--- + +## 生命周期的基本概念 + +生命周期指的是变量或资源从创建到销毁的整个过程。在 Move 中,生命周期的管理依赖于以下原则: + +1. 所有权规则:资源类型具有唯一的所有权。 + +2. 作用范围:变量的生命周期受其作用域限制。 + +3. 静态验证:生命周期在编译时被静态验证,避免运行时错误。 + + +## 生命周期与作用范围 + +### 局部变量的生命周期 + +局部变量的生命周期从声明开始,到其所在的代码块结束为止。例如: + +```move +fun main() { + let x = 10; // x 的生命周期开始 + let y = x + 1; // x 和 y 在作用域内 +} // x 和 y 的生命周期结束 +``` + +一个局部变量的声明周期仅在其声明的代码块内(如果不作其他如复制的操作的话),如果尝试在代码块外使用局部变量,将会报错: + +```move +let x = 0; +{ + let y = 1; +}; +x + y // 错误! +// ^ 未绑定的局部变量 'y' +``` + +一般而言,Move的基本类型都具有drop能力,所以当变量超出作用范围时,Move 会自动清理该变量,并释放相关资源。 + +### 资源类型的生命周期 + +资源类型(如 has key 或 has store 的结构体)具有特殊的生命周期规则: + +- 资源的所有权不可复制:资源类型的生命周期由所有权决定,不能通过赋值操作复制资源。 + +- 资源的销毁:资源超出作用范围后,必须显式移动或销毁。(除非它具有drop能力) + +还是以Coin为例: + +```move +public struct Coin has key, store { + id: UID, + balance: Balance, +} + +``` + +Coin具有key和store能力,这代表它在链上唯一存在,并具有相应的所有权。在创建和使用完Coin之后,我们必须显式地(如果没有,就是你所调用的函数替你做了这一工作)声明移动或是销毁它: + +```move + public entry fun mint( + treasury_cap: &mut TreasuryCap, + amount: u64, + recipient: address, + ctx: &mut TxContext, + ) { + debug::print(&string(b"my_coin mint")); + let coin = coin::mint(treasury_cap, amount, ctx); + // 显式地移动Coin + transfer::public_transfer(coin, recipient) + } + + public entry fun burn(treasury_cap: &mut TreasuryCap, coin: Coin) { + debug::print(&string(b"burn")); + // 显式地销毁Coin + coin::burn(treasury_cap, coin); + } +``` + + + +## 生命周期与引用 + +### 引用的生命周期 + +引用的生命周期受其创建位置和作用范围的限制: + +- 引用的生命周期不能超过被引用值的生命周期。 + +- Move 不允许引用的引用,以简化生命周期管理。 + +示例:合法引用 +```move +fun main() { + let x = 10; + let ref_x = &x; // ref_x 的生命周期与 x 相同 + let value = *ref_x; // 解引用 ref_x +} // x 和 ref_x 生命周期结束 +``` +示例:非法引用 + +```move +fun illegal() { + let x = 10; + let ref_x = &x; + let ref_ref_x = &ref_x; // 错误!Move 不支持引用的引用 +} +``` + +## 生命周期与资源的所有权转移 + +在 Move 中,资源的所有权可以通过以下方式转移: + +- 显式移动:通过函数调用将资源的所有权从一个变量转移到另一个变量。 + +- 资源返回值:将资源作为函数的返回值,转移所有权。 + +一般而言,我们调用transfer.move中的函数来进行所有权的转移。 + +所有权转移源代码: + +```move +/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, +/// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient +/// address represents an object ID, the `obj` sent will be inaccessible after the transfer +/// (though they will be retrievable at a future date once new features are added). +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `transfer` is invoked. Use +/// `public_transfer` to transfer an object with `store` outside of its module. +public fun transfer(obj: T, recipient: address) { + transfer_impl(obj, recipient) +} + +/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, +/// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient +/// address represents an object ID, the `obj` sent will be inaccessible after the transfer +/// (though they will be retrievable at a future date once new features are added). +/// The object must have `store` to be transferred outside of its module. +public fun public_transfer(obj: T, recipient: address) { + transfer_impl(obj, recipient) +} +``` + +概括一下,就是将`obj`的所有权发送给`recipient`,而当资源只有key能力时使用tansfer,当资源还具有store能力时使用public_transfer函数。 + +有趣的是,它们底层都是调用这样一个函数: + +```move +public(package) native fun transfer_impl(obj: T, recipient: address); +``` + +遗憾的是没有找到这一函数的底层实现,如果有了解的,欢迎赐教。 + + +## 生命周期规则总结 + +- 规则 1:变量的生命周期受作用范围限制 + + 变量的生命周期从声明开始,到其作用域结束为止。 + +- 规则 2:资源的所有权是唯一的 + + 资源类型具有唯一的所有权,所有权的转移必须显式进行。 + +- 规则 3:引用的生命周期不得超出被引用值 + + 引用的生命周期不能超过被引用变量的生命周期,Move 静态验证引用关系。 + +- 规则 4:禁止引用的引用 + + Move 不允许引用的引用,简化了生命周期管理。 + +## 遮蔽 + +如果 let 引入的局部变量与作用域中已有的变量同名,那么之前的变量在此作用域后将无法访问。这称为 遮蔽。 + +```move +let x = 0; +assert!(x == 0, 42); + +let x = 1; // x 被遮蔽 +assert!(x == 1, 42); +当一个局部变量被遮蔽时,它不需要保留之前的类型。 + +let x = 0; +assert!(x == 0, 42); + +let x = b"hello"; // x 被遮蔽 +assert!(x == b"hello", 42); +``` + +局部变量被遮蔽后,其值仍然存在,但将不再可访问。这点在处理没有 drop 能力 的类型的值时尤为重要,因为该值的所有权必须在函数结束前转移。 + +``` +module 0x42::example { + public struct Coin has store { value: u64 } + + fun unused_coin(): Coin { + let x = Coin { value: 0 }; // 错误! +// ^ 此局部变量仍包含没有 `drop` 能力的值 + x.value = 1; + let x = Coin { value: 10 }; + x +// ^ 返回无效 + } +} +``` + +当局部变量在作用域内被遮蔽时,遮蔽仅在该作用域内有效。一旦作用域结束,遮蔽就消失了。 + +```move +let x = 0; +{ + let x = 1; + assert!(x == 1, 42); +}; +assert!(x == 0, 42); +``` +请记住,局部变量在被遮蔽时可以改变类型。 + +```move +let x = 0; +{ + let x = b"hello"; + assert!(x = b"hello", 42); +}; +assert!(x == 0, 42); +``` + +## Move和Copy + +在 Move 中,所有局部变量可以通过 move 或 copy 两种方式使用。如果没有明确指定其中一种,Move 编译器可以推断出应该使用 copy 还是 move。这意味着在上述所有例子中,编译器会插入 move 或 copy。 + +从其他编程语言过来的人会更熟悉 copy,因为它会创建变量内部值的新副本以供表达式使用。使用 copy,局部变量可以多次使用。 + +```move +let x = 0; +let y = copy x + 1; +let z = copy x + 2; +``` + +任何具有 copy 能力 的值都可以以此方式复制,并且除非指定了 move,否则会自动复制。 + +move 将值从局部变量中取出,而不复制数据。一旦发生 move,该局部变量将不再可用,即使值的类型具有 copy 能力 也是如此。 + +```move +let x = 1; +let y = move x + 1; +// ------ 局部变量在此处被移动 +let z = move x + 2; // 错误! +// ^^^^^^ 无效使用局部变量 'x' +y + z +``` + +### 安全性 + +Move 的类型系统将阻止在值移动后继续使用该值。这与let声明中描述的安全检查相同,防止局部变量在分配值之前被使用。 + +### 推断 + +如上所述,如果未指定 copy 或 move,Move 编译器会推断出应该使用 copy 还是 move。该算法非常简单: + +- 任何具有 copy 能力 的值被视为 copy。 + +- 任何引用(可变 &mut 和不可变 &)被视为 copy。 + + - 除了在特殊情况下,为了可预测的借用检查错误而被视为 move。这将在引用不再使用时发生。 + +- 其他任何值被视为 move。 + +给定以下结构体 + +```move +public struct Foo has copy, drop, store { f: u64 } +public struct Coin has store { value: u64 } +``` + +我们有以下示例 + +```move +let s = b"hello"; +let foo = Foo { f: 0 }; +let coin = Coin { value: 0 }; +let coins = vector[Coin { value: 0 }, Coin { value: 0 }]; + +let s2 = s; // copy +let foo2 = foo; // copy +let coin2 = coin; // move +let coins2 = coin; // move + +let x = 0; +let b = false; +let addr = @0x42; +let x_ref = &x; +let coin_ref = &mut coin2; + +let x2 = x; // copy +let b2 = b; // copy +let addr2 = @0x42; // copy +let x_ref2 = x_ref; // copy +let coin_ref2 = coin_ref; // copy +``` + +## 总结 +Sui Move 的生命周期管理以静态验证为核心,确保资源和引用的安全性: + +- 作用范围限制:变量的生命周期由作用域决定。 + +- 资源的唯一所有权:资源的生命周期由所有权规则严格控制。 + +- 安全引用:Move 静态验证引用生命周期,防止悬挂引用和数据竞争。 diff --git "a/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\350\256\276\350\256\241\346\250\241\345\274\217\342\200\224\342\200\224\350\247\201\350\257\201\350\200\205\344\270\216\344\270\200\346\254\241\346\200\247\350\247\201\350\257\201\350\200\205.md" "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\350\256\276\350\256\241\346\250\241\345\274\217\342\200\224\342\200\224\350\247\201\350\257\201\350\200\205\344\270\216\344\270\200\346\254\241\346\200\247\350\247\201\350\257\201\350\200\205.md" new file mode 100644 index 000000000..620d92203 --- /dev/null +++ "b/mover/ctianming/notes/sui-move\350\277\233\351\230\266\357\274\232\350\256\276\350\256\241\346\250\241\345\274\217\342\200\224\342\200\224\350\247\201\350\257\201\350\200\205\344\270\216\344\270\200\346\254\241\346\200\247\350\247\201\350\257\201\350\200\205.md" @@ -0,0 +1,230 @@ +# sui-move进阶:设计模式——见证者与一次性见证者 + +在上一个教程中,我们提到了`witness`,并说它:使用了`一次性见证者`的设计模式。 + +在本教程中,我们将对其作进一步的讨论。 + +而在介绍`一次性见证者`之前,我们需要先介绍一下`设计模式——见证者`。 + +--- + +## 什么是见证者模式 + +### 介绍 + +见证者模式的核心是利用特定的值来证明某个属性的真实性。在 Move 中,这种模式通过模块提供的结构体和函数,确保只有定义模块才能创建特定类型的实例。 + +### 特点 + +- **安全性**:模块内的结构体只能由模块创建,防止外部滥用。 +- **泛型支持**:见证者允许泛型类型的受限实例化。 +- **类型所有权验证**:通过特定值证明模块对类型的所有权。 + + +## Move 中的见证者模式 + +### 示例 + +```move +module book::witness { + /// 需要见证者才能创建的结构体。 + public struct Instance { t: T } + + /// 使用提供的 T 创建 `Instance` 的新实例。 + public fun new(witness: T): Instance { + Instance { t: witness } + } +} +``` +解析 +`Instance` 是一个需要见证者才能创建的泛型结构体。 +new 函数接受类型 T 的见证者 witness,并返回 `Instance` 的实例。 +只有提供类型 T 的实例,才能调用 new 创建对应的 `Instance`。 + +### 实现示例 + +见证者的功能通常通过其他模块中的实现来体现。以下是一个完整的模块示例: + +```move +module book::witness_source { + use book::witness::{Self, Instance}; + + /// 作为见证者使用的结构体。 + public struct W {} + + /// 创建 `Instance` 的新实例。 + public fun new_instance(): Instance { + witness::new(W {}) + } +} +``` + +#### 功能解析 + +W 是一个定义在 book::witness_source 模块中的结构体。 +通过 new_instance 函数,模块创建并传递 W 的实例,生成 `Instance`。 + +这一模式证明了 book::witness_source 对 W 的所有权。 + +### 泛型类型实例化 + +见证者模式在 Move 中的一个重要应用是泛型类型的受限实例化。以下是一个示例,展示了如何通过见证者模式为泛型类型提供实例化和扩展能力: + +#### 供应量管理示例 + +##### 提供供应 + +```move +/// 供应类型为 T。用于铸造和销毁。 +public struct Supply has key, store { + id: UID, + value: u64 +} + +/// 使用提供的见证者为类型 T 创建新的供应。 +public fun create_supply(_w: T): Supply { + Supply { id: object::new(), value: 0 } +} + +/// 获取 `Supply` 的值。 +public fun supply_value(supply: &Supply): u64 { + supply.value +} +``` + +解析: + +`Supply` 是一个泛型结构体,用于管理某种类型 T 的供应量。 + +create_supply 函数需要提供类型 T 的见证者 _w,确保只能通过见证者来创建 `Supply`。 + +##### 扩展供应 + +`Supply` 可以用于创建代币余额 `Balance`。以下是相关代码: + +```move +/// 可存储的余额。 +struct Balance has store { + value: u64 +} + +/// 增加供应量 `value` 并创建具有此值的新 `Balance`。 +public fun increase_supply(self: &mut Supply, value: u64): Balance { + assert!(value < (18446744073709551615u64 - self.value), EOverflow); + self.value = self.value + value; + Balance { value } +} +``` + +功能解析: + +increase_supply 允许模块通过修改 `Supply` 的值,铸造新的 `Balance`。 + +同时泛型参数 T 确保了类型安全,避免跨类型的误操作。 + +## 一次性见证者(one time witness) + +### 介绍 + +我将重点放在一次性见证者中,实际上,这一种设计模式在我们之前做task2的时候便已经使用过了。 + +一次性见证者用于某些需要确保某些类型的实例只能被创建一次的场景,它确保结构体的唯一性。 + +### 定义 + +一次性见证(OTW)(one time witness)是一种特殊类型的见证,只能使用一次。它不能手动创建,且每个模块中拥有唯一的实例。Sui 适配器将类型视为 OTW,如果满足以下规则: + +- 只具有 drop 能力。 +- 没有字段。 +- 不是泛型类型。 +- 模块名称为大写字母。 + +以下是 OTW 的示例: + +```move +module book::one_time { + /// The OTW for the `book::one_time` module. + /// Only `drop`, no fields, no generics, all uppercase. + public struct ONE_TIME has drop {} + + /// Receive the instance of `ONE_TIME` as the first argument. + fun init(otw: ONE_TIME, ctx: &mut TxContext) { + // do something with the OTW + } +} +``` +OTW 不能手动构造,任何试图这样做的代码都会导致编译错误。 + +OTW 可以作为模块初始化器的第一个参数进行接收。由于 init 函数每个模块只调用一次,因此 OTW 保证只被实例化一次。 + +### 示例 + +我们直接使用我们task2的代码作为示例: + +```move +// 这是一个OTW +public struct MY_COIN has drop {} +``` + +MY_COIN结构体: + + 1. 无字段 + 2. 只具有drop能力 + 3. 名称全部大写(如果你试图违反的话,编译器会报错) + 4. 不是泛型类型 + +它完美符合我们OTW的定义。 + +而在我们编写过的函数init中: + +```move +fun init(witness: MY_COIN, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 6, + b"MOON", + b"CTIANMING_MY_COIN", + b"MOON_COIN", + option::none(), + ctx); + debug::print(&string(b"init MY_COIN")); + transfer::public_freeze_object(metadata); + transfer::public_transfer(treasury, ctx.sender()) + } +``` + +witness我们传入了MY_COIN作为参数,我再来看看coin.move库文件对create_currency的描述: + +``` +/// Create a new currency type `T` as and return the `TreasuryCap` for +/// `T` to the caller. Can only be called with a `one-time-witness` +/// type, ensuring that there's only one `TreasuryCap` per `T`. +``` + +翻译一下,就是: +```move +/// 创建一种新的货币类型 `T` 并返回 `T` 的 `TreasuryCap` 给调用者。 +/// 只能使用 `one-time-witness` 类型调用,确保每种 `T` 只有一个 `TreasuryCap`。 +``` + +很明显,一次性见证则模式保证`MY_COIN`只具有一个`TreasuryCap`,这是权限控制所必须的。 + +一般而言,我们使用标准库时,需要使用一次性见证者模式的函数都有相应描述,所以: + +看源码!看源码!看源码! + +### 为什么是*一次性见证者* + +那么我们来思考一个问题,为什么`一次性见证者`是一次性的? + +很显然,仍然以`MY_COIN`为例,它只具有`drop`能力,这使得它在生命周期结束时将自动销毁。 + +而我们调用create_currency使用了它,而没有对它进行任何的额外处理,它在那之后便自动销毁了,不在有人可以使用它。 + +也就是说,它是唯一的,也是一次性的。 + +## 总结 + +- 见证者模式:通过模块控制类型实例的创建,用于泛型实例化和权限控制。 + +- 一次性见证者:确保某些类型的实例只能被创建一次,用于关键资源的唯一性管理。 \ No newline at end of file