IOTA 学习笔记(十一):共享对象与多用户交互

发布时间:2026/6/4 20:27:19

IOTA 学习笔记(十一):共享对象与多用户交互 上一期我们讲了 IOTA 中的交易与 PTB也就是可编程交易块。PTB 的重点在于一笔交易不一定只做一次转账或一次函数调用而是可以把对象拆分、对象转移、Move 函数调用等多个步骤组合起来形成一次原子执行。这一期继续往对象模型深入讲一个非常重要的概念共享对象也就是 Shared Object。在前面的 Counter 合约中我们创建的是一个归属于某个地址的对象。这个对象有明确的 owner通常只有对象所有者可以以可变方式操作它。这类对象比较容易理解。但在真实应用中很多链上状态并不只属于某一个用户而是需要被多个用户共同访问。例如市场、投票系统、公共计数器、游戏房间、流动性池、注册表等。这些场景就需要共享对象。简单来说Owned Object 适合表达个人资产或个人状态Shared Object 适合表达多个用户共同访问的链上状态。1. 先回顾 Owned ObjectOwned Object 是归某个地址所有的对象。例如上一期 Counter 示例中用户调用 create 函数后会得到一个 Counter 对象Address A owns Counter Object Counter.value 0之后Address A 可以调用 increment 函数修改这个 CounterAddress A 调用 increment Counter.value 1这个过程比较简单因为 Counter 明确属于 Address A。其他地址通常不能直接以可变方式修改它。Owned Object 的特点可以总结为有明确所有者 通常只能由所有者操作 适合个人状态或个人资产 交易处理路径相对简单例如用户自己的 Coin 用户自己的 NFT 用户自己的 Counter 用户自己的凭证对象 用户自己的配置对象这些都比较适合作为 Owned Object。2. 为什么需要 Shared Object但是并不是所有链上状态都适合归某一个用户所有。假设我们要写一个公共计数器。任何用户都可以调用 increment使计数器加 1。如果这个计数器只归 Address A 所有那么 Address B、Address C 就不能自然地修改它。再比如一个链上商店 Shop。所有用户都可以购买商品。如果 Shop 对象只归店主所有那么普通用户调用购买函数时就会遇到对象访问问题。类似场景很多公共计数器 链上商店 投票系统 订单市场 流动性池 多人游戏状态 公共注册表 共享配置对象这些对象的共同特点是不是某一个用户私有 多个用户都需要访问 状态可能被不同交易持续修改这时就需要 Shared Object。Shared Object 可以被多个用户访问。也就是说它不再只属于某一个地址而是作为全网可访问的共享状态存在。可以简单理解为Owned Object Address A owns Object X Shared Object Object X is shared and can be accessed by multiple users3. Shared Object 的核心特点Shared Object 有几个重要特点。第一它可以被多个用户访问。这是共享对象和地址拥有对象最直接的区别。第二它仍然是链上对象。共享对象也有 Object ID、类型、版本和数据不是普通全局变量。第三它通常需要keyability。因为它要作为链上对象被识别和操作。第四它一旦共享通常就不再回到普通 owned object 的模式。也就是说共享是一个非常重要的状态变化设计时要谨慎。第五涉及共享对象的交易通常需要共识排序。因为多个用户可能同时操作同一个共享对象系统必须确定交易顺序。这几个特点决定了 Shared Object 既强大也更复杂。可以概括为Shared Object 可被多个用户访问的链上对象但还要补一句共享不等于没有规则 共享对象的具体访问权限仍然由 Move 函数控制这是初学者很容易混淆的地方。4. 共享不等于任何人都能随便修改很多人看到 shared就会以为“所有人都能随便改”。这个理解不准确。Shared Object 的意思是这个对象可以作为多个用户交易中的输入对象。但用户能不能成功修改它还要看 Move 函数怎么写。例如一个共享商店对象 Shop 可以允许所有用户购买商品buy(shop: mut Shop, payment: CoinIOTA)但它可以只允许店主提取收益collect_profits(shop: mut Shop, cap: ShopOwnerCap)这里的ShopOwnerCap就是一种权限对象。只有持有这个权限对象的人才能调用提取收益函数。所以Shared Object 的正确理解是对象可以被多个用户访问 但具体操作仍然受函数参数、权限对象和合约逻辑约束这和现实世界也很像。一个商店是公共的所有人都可以进店买东西。但只有店主可以收银、改价格、取走利润。因此写共享对象合约时权限设计非常重要。5. 如何创建 Shared Object在 Move 中创建共享对象通常分成两步先创建对象 再把对象 share 出去以一个共享计数器为例可以写成类似这样module hello::shared_counter { use iota::object::{Self, UID}; use iota::transfer; use iota::tx_context::TxContext; public struct SharedCounter has key { id: UID, value: u64, } fun init(ctx: mut TxContext) { let counter SharedCounter { id: object::new(ctx), value: 0, }; transfer::share_object(counter); } public entry fun increment(counter: mut SharedCounter) { counter.value counter.value 1; } public fun value(counter: SharedCounter): u64 { counter.value } }这段代码的核心是transfer::share_object(counter);它把新创建的 counter 对象变成共享对象。和普通 transfer 不同普通对象创建后通常会转移给某个地址transfer::transfer(counter, sender);而共享对象创建后会被放到全网可访问的共享状态中transfer::share_object(counter);可以这样对比transfer::transfer 把对象转移给某个地址 transfer::share_object 把对象变成共享对象这就是 Owned Object 和 Shared Object 在创建方式上的关键区别。6. 共享计数器示例怎么理解上面的 SharedCounter 合约可以帮助我们理解共享对象的基本流程。合约发布后init函数会创建一个 SharedCounter 对象并把它共享出去。初始状态可以理解为SharedCounter Object value 0 owner shared之后用户 A 可以调用increment(shared_counter)执行后value 1用户 B 也可以调用increment(shared_counter)执行后value 2用户 C 再调用increment(shared_counter)执行后value 3这个过程中SharedCounter 不属于用户 A也不属于用户 B而是一个共享对象。多个用户都可以把它作为参数传入交易。这就是共享对象的基本意义。7. 为什么共享对象需要共识排序Shared Object 最大的复杂性来自并发。假设 SharedCounter 当前值是 0。现在用户 A 和用户 B 几乎同时提交交易用户 Aincrement(counter) 用户 Bincrement(counter)这两笔交易都想修改同一个共享对象。如果没有统一排序不同节点可能得到不同结果。例如节点 1 认为顺序是A 先执行B 后执行节点 2 认为顺序是B 先执行A 后执行对于简单加 1 来说最终值可能都是 2看起来影响不大。但对于复杂应用顺序可能非常关键。例如第一个买家买走唯一商品 第一个出价者获得拍卖资格 第一个提交者占用某个名称 第一个交易改变价格 第一个交易清空库存这些场景中交易顺序会直接影响结果。因此涉及共享对象的交易需要进入共识排序。网络必须先确定这些交易的全局顺序再让验证者按照相同顺序执行。这样所有节点才能得到一致状态。可以简单理解为Owned Object 只涉及某个所有者的对象处理路径较快 Shared Object 多个用户可能同时修改需要共识确定顺序这就是共享对象比 owned object 更复杂的根本原因。8. Owned Object 和 Shared Object 的交易路径差异可以用一个简单对比理解两类对象的交易路径。对于 Owned Object用户提交交易 验证对象所有权 锁定对象版本 执行交易 更新对象版本如果交易只涉及 owned objects系统可以更快处理因为对象有明确 owner不需要全网对所有并发访问进行统一排序。对于 Shared Object用户提交交易 识别共享对象输入 进入共识排序 确定交易顺序 按顺序执行交易 更新共享对象版本区别的核心在于Owned Object 重点检查所有权 Shared Object 重点处理并发顺序所以如果一个应用不需要多人共同修改同一个对象就不应该轻易使用 Shared Object。共享对象功能强大但也会带来更高的复杂度和执行成本。9. 对象版本在共享对象中怎么变化共享对象也有版本。每次共享对象被交易修改后都会产生新的版本。例如SharedCounter 初始版本是 1Version 1 value 0用户 A 调用 increment 后Version 2 value 1用户 B 调用 increment 后Version 3 value 2用户 C 调用 increment 后Version 4 value 3看起来和 owned object 类似但共享对象版本的确定更依赖交易调度和共识排序。因为多个用户可能同时提交交易系统必须先确定顺序再决定每笔交易读写的是哪个状态版本。可以简单理解为共享对象的版本变化 共识排序后的状态演进结果这也是为什么查询共享对象时版本信息很重要。它能反映这个共享对象经历了多少次状态更新。10. 共享对象和权限对象共享对象经常和权限对象配合使用。因为共享对象可以被多个用户访问但并不是所有操作都应该开放给所有人。例如一个 DonutShop 共享对象可以允许任何用户购买甜甜圈但只有店主可以提取收益。可以设计两个对象DonutShop共享对象 ShopOwnerCap店主权限对象购买函数public entry fun buy(shop: mut DonutShop, payment: CoinIOTA) { // 所有人都可以调用 }提取收益函数public entry fun collect(shop: mut DonutShop, cap: ShopOwnerCap) { // 只有持有 ShopOwnerCap 的人才能调用 }这里的ShopOwnerCap就像一把钥匙。共享对象负责承载公共状态。权限对象负责限制敏感操作。这种设计非常常见。可以总结为共享对象让多人可以访问公共状态 权限对象限制谁能执行敏感操作没有权限设计的共享对象很容易出问题。尤其是涉及价格、余额、收益、管理员操作、状态重置时必须清楚区分普通用户操作和管理员操作。11. 共享对象适合哪些场景Shared Object 适合表达多人共同访问和修改的状态。典型场景包括链上商店 交易市场 流动性池 投票系统 公共注册表 多人游戏房间 拍卖合约 共享计数器 公共配置状态这些场景都有一个共同点多个用户需要访问同一个对象并且该对象的状态会随着不同用户交易不断变化。例如交易市场中很多用户都可能挂牌、购买、取消订单。市场对象本身就不适合归某一个普通用户所有。再比如流动性池中很多用户都可能添加流动性、移除流动性、进行 swap。池子状态必须作为共享对象存在。因此Shared Object 的核心适用条件是同一个链上状态需要被多个用户共同访问或修改12. 哪些场景不适合使用共享对象Shared Object 虽然强大但不应该滥用。如果一个对象只属于某个用户不需要其他用户共同修改就更适合使用 Owned Object。例如个人 NFT 个人凭证 个人配置 个人计数器 个人钱包中的 Coin 个人游戏道具这些对象没有必要设计成共享对象。滥用共享对象可能带来几个问题第一交易路径更复杂。涉及共享对象的交易通常需要共识排序。第二权限设计更困难。共享对象能被多人访问必须认真设计哪些函数开放、哪些函数受限。第三并发冲突更多。多个用户同时操作同一个共享对象容易出现顺序依赖。第四状态膨胀风险更高。公共对象如果承载太多状态会影响合约可维护性。因此设计对象时可以先问一个问题这个对象是否真的需要被多个用户共同修改如果答案是否定的就优先使用 Owned Object。13. Shared Object 的常见错误初学共享对象时容易遇到一些错误。第一个错误忘记 share_object。只创建了对象但没有把它共享出去。结果其他用户无法作为共享对象访问。第二个错误把本应私有的对象设计成共享对象。这会增加不必要的复杂度。第三个错误没有权限控制。共享对象的敏感函数如果不需要任何权限对象可能导致任何人都能修改关键状态。第四个错误误以为共享对象没有 owner 就没有安全边界。共享对象的安全边界主要由 Move 类型系统、函数签名和权限对象共同实现。第五个错误并发顺序没有考虑清楚。如果多个用户同时操作共享对象交易顺序可能影响结果合约逻辑必须能正确处理。第六个错误用共享对象保存过多用户私有状态。如果每个用户的数据都塞进一个共享对象后续维护和扩展会很困难。更好的方式可能是共享对象保存公共索引用户状态使用 owned object 或动态字段组织。14. 如何判断该用 Owned Object 还是 Shared Object可以用几个问题判断。第一个问题这个对象是否只归某个用户使用如果是使用 Owned Object。第二个问题是否有多个用户需要直接修改同一个对象如果是考虑 Shared Object。第三个问题这个对象是否代表公共系统状态如果是考虑 Shared Object。第四个问题是否需要权限对象控制管理操作如果是可以采用 Shared Object Capability 的设计。第五个问题是否可以把公共状态和用户私有状态拆开如果可以通常不要把所有状态都放进一个共享对象。可以用一个简单表格总结适合 Owned Object 个人资产 个人凭证 个人配置 个人计数器 个人道具 适合 Shared Object 市场 商店 投票系统 流动性池 拍卖 公共注册表 多人游戏状态这套判断方式对后面设计复杂 Move 合约很有帮助。15. 共享对象和 PTB 的关系上一期讲过 PTB。PTB 可以把多个对象操作组合成一笔交易。当 PTB 中涉及共享对象时就需要注意交易排序问题。例如一个购买流程可能是1. SplitCoins拆出付款 Coin 2. MoveCall调用 shop::buy(shared_shop, payment) 3. TransferObjects把返回的商品对象转给用户这里的 shared_shop 是共享对象因为所有用户都可以从同一个 shop 中购买商品。这类 PTB 的特点是PTB 内部可以组合多个步骤 但只要涉及共享对象就需要进入共识排序所以PTB 和共享对象经常一起出现。复杂应用通常既需要共享对象表达公共状态也需要 PTB 组合多个交易步骤。可以这样理解Shared Object 解决多人访问同一状态的问题 PTB 解决一笔交易中组合多个操作的问题两者结合起来就能表达很多真实应用场景。16. 一个共享商店的简化理解为了进一步理解可以用一个共享商店作为例子。链上有一个 DonutShop 对象DonutShop price 10 balance 0这个对象是共享的因此所有用户都可以调用 buy。用户 A 买一个甜甜圈输入 - Shared DonutShop - Coin 10 执行 - 检查付款金额 - 增加 shop.balance - 创建 Donut 对象 - 转移 Donut 给用户 A用户 B 也可以买输入 - Shared DonutShop - Coin 10 执行 - 增加 shop.balance - 创建 Donut 对象 - 转移 Donut 给用户 B店主提取收益时需要 ShopOwnerCap输入 - Shared DonutShop - ShopOwnerCap 执行 - 检查权限 - 提取 shop.balance - 转移收益给店主这个例子体现了共享对象设计的三个核心DonutShop 是共享对象承载公共状态 Donut 是用户购买后获得的 owned object ShopOwnerCap 是权限对象限制店主操作这就是共享对象在实际应用中的典型组合。17. 小结这一期主要讲了 IOTA 中的共享对象。Owned Object 适合表达个人状态或个人资产Shared Object 适合表达多个用户共同访问和修改的公共状态。共享对象可以通过share_object创建创建后可以被多个用户作为交易输入。不过共享对象也更复杂。因为多个用户可能同时修改同一个共享对象所以涉及共享对象的交易通常需要通过共识排序确保所有验证者按照相同顺序执行交易从而得到一致状态。设计共享对象时需要重点考虑三个问题这个对象是否真的需要共享 哪些函数可以开放给所有用户 哪些敏感操作需要权限对象控制可以用一句话总结本期内容Shared Object 让 IOTA 可以表达多人共同访问的链上状态但它也要求开发者认真处理并发顺序、权限控制和状态设计。下一期我们会继续讲 IOTA EVM 与 MoveVM。重点回答IOTA 为什么同时存在 EVM 和 MoveVMSolidity 开发者和 Move 开发者分别应该如何理解这两套体系

相关新闻