IOTA 学习笔记(五):对象模型是理解 IOTA 的关键 前几期我们先从 IOTA 的历史讲起理解了 Tangle、DAG、Coordinator、Coordicide、Stardust 和 Rebased。上一期开始进入当前 IOTA 架构提到了网络层、共识层、执行层、状态层和开发工具链。从这一期开始我们要真正接触当前 IOTA 开发中最重要的概念之一对象模型。如果说 Tangle 是理解早期 IOTA 的关键那么对象模型就是理解当前 IOTA 的关键。因为 Rebased 之后IOTA 不再只是围绕早期 DAG 账本思想展开而是进一步采用 MoveVM 和对象模型来支撑智能合约、链上资产和复杂状态管理。简单来说在当前 IOTA 中很多链上状态都可以被理解为对象。代币是对象合约包是对象开发者自定义的数据结构也可以成为对象。理解对象就等于理解当前 IOTA 中“链上状态到底是怎么存在的”。1. 为什么要理解对象模型很多人刚开始学习区块链时会先形成一种账户模型的直觉。比如在以太坊中我们经常说某个账户有多少余额某个合约地址有一片存储空间合约内部有一些变量。用户调用合约本质上是修改合约账户下的状态。这种理解方式可以简单表示为账户地址 → 账户余额 合约地址 → 合约存储但是在 IOTA 当前架构中更重要的理解方式是对象模型。对象模型的直觉是链上状态不是只挂在某个账户下面的一组变量而是由一个个独立对象组成。每个对象都有自己的唯一标识、类型、所有者和数据内容。可以简单表示为地址 A → 拥有 Object 1 地址 A → 拥有 Object 2 地址 B → 拥有 Object 3 链上系统 → 存在 Shared Object这种方式更接近现实世界中“物品”的概念。比如一枚代币是一个对象 一个 NFT 是一个对象 一个合约包是一个对象 一个用户创建的计数器是一个对象 一个共享状态也可以是一个对象所以学习 IOTA 不能只问“某个账户里有什么变量”而要进一步问当前交易操作了哪些对象这些对象属于谁对象是否可以被修改对象是否可以被转移对象是否是共享对象这就是对象模型的重要性。2. 什么是 ObjectObject 可以翻译为“对象”。在 IOTA 中对象是链上状态的基本单位。一个对象通常包含几个核心信息Object ID对象的唯一标识 Object Type对象的类型 Owner对象的所有者 Version对象版本 Data对象内部保存的数据 Previous Transaction产生或修改该对象的上一笔交易可以把对象想象成链上的一个独立状态单元。它不是临时变量而是可以被链上系统追踪、查询和操作的状态实体。例如一个 Counter 对象可以表示一个计数器Object ID: 0xabc... Type: Counter Owner: 0x123... Value: 10 Version: 3这表示链上有一个 Counter 类型的对象它属于某个地址当前计数值是 10版本号是 3。如果用户调用 increment 函数把 value 从 10 改成 11那么这个对象不会只是“原地无痕变化”而是会产生新的对象版本。也就是说IOTA 可以追踪对象随着交易不断变化的历史。因此对象不仅表示链上状态也承担了状态追踪和权限控制的功能。3. Object ID对象的唯一身份每个对象都有一个 Object ID。Object ID 可以理解为对象在链上的唯一身份。只要知道对象 ID开发者就可以通过 CLI、SDK 或浏览器查询这个对象的状态。这和现实世界中的编号很像。比如一张身份证有身份证号一辆车有车架号一个快递有运单号。Object ID 就是链上对象的唯一编号。在后面使用 IOTA CLI 时经常会看到这样的对象 ID0x8a7f... 0x25c3... 0xb91d...这些 ID 很长但不用害怕。它们的作用就是告诉系统我要查询或操作的是哪一个具体对象。例如当我们调用某个 Move 函数时如果这个函数需要修改一个 Counter 对象那么命令中通常就需要传入这个 Counter 对象的 Object ID。可以简单理解为函数知道怎么修改 Counter Object ID 告诉函数要修改哪一个 Counter所以Object ID 是开发者和链上对象交互时最常用的标识之一。4. Version为什么对象有版本对象不仅有 ID还有版本。版本的作用是记录对象状态变化。每当对象被交易修改后都会产生新的版本。例如一个 Counter 对象最开始 value 0可以看作版本 1Counter Object Version: 1 Value: 0执行一次 increment 后value 变成 1对象版本也更新Counter Object Version: 2 Value: 1再执行一次 incrementCounter Object Version: 3 Value: 2这样做的好处是系统可以清楚知道对象状态经历了哪些变化也可以避免并发交易同时修改同一个对象时造成混乱。版本号在交易验证中也很重要。因为交易操作对象时通常不是只说“我要操作这个对象 ID”还需要对应具体版本。这样可以防止某笔交易基于旧状态执行导致状态冲突。可以这样理解Object ID 决定“是哪一个对象”Version 决定“是哪一个状态版本”。这对区块链系统尤其重要因为链上状态必须可验证、可追踪、不可随意篡改。5. Owner对象属于谁对象模型中另一个非常重要的概念是 Owner也就是对象所有者。在 IOTA 中对象不是孤立存在的它通常会有一个所有权模型。所有权决定这个对象能被谁访问、谁能修改、谁能转移以及是否需要经过共识排序。常见的所有权类型可以先理解为以下几类Owned Object归某个地址所有 Shared Object共享对象可以被多个用户访问 Immutable Object不可变对象 Wrapped Object被包裹在另一个对象内部的对象这几类对象在交易中的行为是不一样的。Owned Object 比较容易理解。它属于某个地址通常只有所有者可以把它作为可变对象传入交易中。Shared Object 则更复杂。它不是只属于某一个用户而是可以被多个用户访问或修改。因为多个用户可能同时操作它所以涉及共享对象的交易通常需要经过共识排序。Immutable Object 是不可变对象。它一旦创建后状态不会再被修改。智能合约 package 通常就可以理解为不可变对象因为合约发布后代码本身不能随意被修改。Wrapped Object 则表示一个对象被放进另一个对象内部。这个概念后面学习更复杂的 Move 设计时会遇到入门阶段先知道它存在即可。6. Owned Object最容易理解的对象Owned Object 是最容易理解的一类对象。它属于某个具体地址。这个地址的拥有者可以在交易中操作这个对象比如转移、修改或作为参数传给 Move 函数。例如用户 A 拥有一个 Counter 对象Address A owns Counter Object Counter.value 0用户 A 调用 increment 函数后Address A owns Counter Object Counter.value 1这个过程比较直接因为对象归用户 A 所有用户 A 发起交易修改自己的对象。Owned Object 的特点是有明确所有者 通常只能由所有者操作 适合表示个人资产或个人状态 并发冲突相对容易处理例如普通代币对象、用户自己的 NFT、个人创建的计数器都可以看作 Owned Object 的典型例子。学习 IOTA Move 合约时刚开始最好先从 Owned Object 入手。因为它的所有权关系最直观也最容易通过 CLI 查询和调用。7. Shared Object为什么共享对象更复杂Shared Object 是共享对象。它不只属于某一个地址而是可以被多个用户访问。共享对象适合表示多人共同交互的链上状态。比如一个公共交易池 一个共享计数器 一个链上投票系统 一个市场合约 一个多人游戏状态这些状态不是某一个用户自己的私有对象而是多个用户都可能参与操作。共享对象的问题在于如果多个用户同时修改同一个对象系统必须决定这些操作的执行顺序。例如有一个共享计数器 SharedCounter当前值是 0SharedCounter.value 0用户 A 和用户 B 同时调用 increment用户 A: increment 用户 B: increment如果两个交易都有效最终值应该是 2。但是系统必须明确谁先执行、谁后执行。否则不同节点可能得到不同状态。因此涉及共享对象的交易通常需要通过共识来排序。这就是 Shared Object 和 Owned Object 的重要区别Owned Object对象归某个地址所有操作路径相对简单 Shared Object多个用户都可能访问需要共识确定顺序所以后面如果你看到某些交易比普通对象交易更复杂很可能就是因为它涉及共享对象。8. Immutable Object不可变对象Immutable Object 是不可变对象。顾名思义它一旦形成就不能再被修改。不可变对象适合表示不应该被随意更改的链上内容。最典型的例子就是 Package。在 IOTA 中Move 合约发布之后会形成链上的 Package 对象。这个 Package 保存了合约模块和函数。一般来说发布后的 Package 是不可变的。开发者可以调用其中的函数但不能随意修改已经发布的代码内容。这很好理解。智能合约之所以可信一个重要原因就是代码发布后不能被随便改。如果合约发布者可以随时修改代码那么用户就很难信任这个合约。因此Package 作为不可变对象能够保证链上代码的稳定性和可验证性。可以简单理解为Package Object 发布到链上的 Move 合约代码 Immutable 发布后不能随意修改当然实际开发中可能存在 package upgrade 机制用于在一定规则下升级合约。但从入门角度看先把 Package 理解成不可变合约对象是比较合适的。9. Package合约也是对象在 IOTA 中合约发布后会形成 Package 对象。这一点很重要。很多人会把合约理解成某个“程序文件”但在 IOTA 链上发布后的合约本身也被表示为对象。一个 Package 中可以包含多个 Move module。每个 module 中又可以定义 struct 和 function。可以简单表示为Package ├── module A │ ├── struct │ └── function └── module B ├── struct └── function当开发者发布一个 Move package 后链上会生成一个 Package ID。之后调用这个合约中的函数时就需要用到这个 Package ID。例如我们发布了一个 Counter 合约得到Package ID: 0xabc...然后调用其中的函数时可能需要指定0xabc::counter::create 0xabc::counter::increment这里的0xabc就是 Package IDcounter是模块名create或increment是函数名。所以Package ID 对开发者来说非常重要。它相当于链上合约的地址。可以这样理解Object ID 用来定位对象 Package ID 用来定位合约包 函数路径用来定位具体调用逻辑10. Coin代币也是对象在对象模型下代币也可以被理解为对象。很多传统账户模型中余额像是账户里的一个数字。例如Address A balance 100但在对象模型中余额可以由多个 Coin 对象组成。一个地址可能拥有多个 Coin 对象每个 Coin 对象代表一笔具体的代币金额。例如Address A owns Coin Object 1: 30 Address A owns Coin Object 2: 50 Address A owns Coin Object 3: 20这三个 Coin 对象加起来用户 A 的总余额就是 100。这种设计一开始可能不如账户余额直观但它有很强的表达能力。因为每个 Coin 都是独立对象可以被拆分、合并、转移或作为 gas 使用。比如一个 Coin 对象可以被拆成两个Coin 100 → Coin 40 Coin 60两个 Coin 对象也可以合并Coin 40 Coin 60 → Coin 100这也是为什么在 IOTA CLI 操作中经常会看到 gas object、coin object、object ID 等概念。因为你的余额不是一个抽象数字而是由具体对象组成的。11. 对象和交易的关系对象模型只有和交易放在一起才真正容易理解。一笔交易通常会读取或操作一些对象。交易执行后这些对象的状态可能发生变化也可能产生新的对象。可以把交易看成对象状态转换器交易前 Object A Object B Gas Object 交易执行 Move 函数调用 权限检查 Gas 计算 对象读写 交易后 Object A Object B New Object C Updated Gas Object也就是说交易不是简单地“写一条记录”而是根据合约逻辑改变链上对象状态。例如调用 Counter 的 increment 函数输入Counter Object 执行value value 1 输出Counter Object 的新版本再比如调用 create 函数输入TxContext 执行创建 Counter Object 输出新的 Counter Object因此学习 IOTA 交易时最重要的是看清楚这笔交易输入了哪些对象 这些对象是 owned 还是 shared 函数是否需要 mutable reference 交易执行后产生了哪些对象 哪些对象被修改了 哪些对象被转移了 Gas object 发生了什么变化这些问题比单纯记命令更重要。12. 对象模型和 Move 的关系IOTA 引入 MoveVM 后对象模型和 Move 语言紧密结合。在 Move 中开发者可以定义 struct。某些 struct 可以成为链上对象。通常一个可以成为链上对象的 struct 会包含 UID。可以用简化方式理解public struct Counter has key { id: UID, value: u64, }这里的 Counter 是一个结构体。它有key能力说明它可以作为链上对象存在。id: UID则表示它具有链上对象身份。后面编写合约时我们会经常看到类似结构。例如public fun create(ctx: mut TxContext) { let counter Counter { id: object::new(ctx), value: 0, }; transfer::transfer(counter, tx_context::sender(ctx)); }这段逻辑可以简单理解为创建一个新的 Counter 对象 给它分配唯一 ID 把它转移给交易发送者这就是对象模型和 Move 合约结合的典型方式。因此学习 Move 时不要只看语法而要一直追问这个 struct 会不会成为对象这个函数创建了什么对象对象转移给了谁对象是否可变对象能不能被共享13. 对象模型和账户模型的区别现在可以系统对比一下对象模型和账户模型。账户模型更像这样账户 A: balance 100 storage {...} 合约 C: variable1 ... variable2 ...对象模型更像这样Address A owns: Coin Object 1 Coin Object 2 NFT Object Counter Object Shared: Market Object Registry Object Immutable: Package Object账户模型强调“账户下面有什么状态”。对象模型强调“链上有哪些对象这些对象属于谁”。这会影响开发者的思维方式。在账户模型中你可能会问这个账户余额是多少 这个合约变量是多少在对象模型中你更应该问这个地址拥有哪些对象 这个对象的 ID 是什么 这个对象的 owner 是谁 这个对象能不能被修改 这个对象是否是 shared 这个对象由哪笔交易创建或修改这就是 IOTA 当前开发方式和很多传统智能合约平台的不同之处。14. 初学者最容易混淆的几个点学习对象模型时初学者容易混淆几个问题。第一个问题Object ID 和 Package ID 是不是一回事它们本质上都是链上对象的 ID但用途不同。普通 Object ID 用来定位具体链上对象例如 Counter、Coin、NFT。Package ID 用来定位发布后的合约包。第二个问题地址是不是对象地址本身不是普通对象。地址是账户身份或所有者标识对象可以归属于地址。可以说地址拥有对象但不要把地址本身简单等同于对象。第三个问题Coin 为什么也是对象因为在对象模型下代币余额可以由多个 Coin 对象组成。每个 Coin 对象都有自己的 ID 和金额可以被拆分、合并或转移。第四个问题Shared Object 是不是所有人都能随便改不是。Shared Object 可以被多个用户访问但具体谁能改、怎么改仍然取决于 Move 合约函数中的权限逻辑。共享不等于无权限控制。第五个问题Package 发布后还能不能改入门阶段可以先理解为 Package 是不可变对象。实际开发中可能涉及升级机制但升级也需要遵守特定规则不是随意修改链上代码。15. 小结这一期主要讲了 IOTA 的对象模型。在当前 IOTA 中对象是链上状态的基本单位。每个对象都有自己的 Object ID、类型、所有者、版本和数据。对象可以归某个地址所有也可以是共享对象、不可变对象或者被包裹在其他对象内部。对象模型改变了我们理解链上状态的方式。传统账户模型更关注“账户下面有什么状态”而对象模型更关注“链上有哪些对象、对象属于谁、对象如何被交易操作”。这也是为什么后面学习 IOTA Move 合约时必须先理解对象模型。因为 Move 合约并不是孤立执行的代码它总是在创建、读取、修改、转移或共享对象。可以用一句话总结本期内容当前 IOTA 的链上世界不是由账户变量简单堆起来的而是由一个个具有身份、所有权和版本的对象组成的。下一期我会继续讲 Move 语言入门。重点回答Move 是什么module、struct、function 分别是什么为什么 Move 很适合和对象模型结合