Skip to content

Commit

Permalink
add zh doc of object
Browse files Browse the repository at this point in the history
  • Loading branch information
geometryolife committed Sep 9, 2024
1 parent fad1883 commit f2bf36b
Showing 1 changed file with 327 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
# Rooch Object

Rooch 中的 Object 是一种箱子(Box)模式的 Object。创建一个 Object 相当于在状态空间中创建了一个箱子,可以将类型 `T` 的实例封装在箱子中,而 `ObjectID` 是这个箱子的地址。此外,这个箱子还支持动态添加状态,即 Object 的动态字段特性。

如果我们将智能合约的状态空间比作程序的堆内存,Object 类似于一个智能指针,提供了对状态的引用和操作,以及生命周期的管理。

## Object 的所有权

`Object``owner` 代表 Object 属于哪个账户地址,通过 `owner` 可以将 Object 分为两种:

1. `SystemOwnedObject`: `owner``0x0` 的 Object。Object 创建后默认为 `SystemOwnedObject`
2. `UserOwnedObject`: `owner` 为非 `0x0` 的 Object。Object 被转让给某个用户后,`owner` 会被设置为该用户的地址。

## Object 的生命周期

### 创建 Object

通过调用 `object::new` 方法可以创建出类型为 `T` 的 Object。

```move
module moveos_std::object {
#[private_generics(T)]
public fun new<T: key>(value: T): Object<T>;
}
```

* 该方法受 `private_generics(T)` 保护,所以只有 `T` 所在的模块才能调用该方法。`T` 模块的开发者可以决定是否提供将 `T` 封装到 `Object` 中的方法。
* `T` 必须拥有 `key` ability。
* 该 Object 的 ObjectID 是系统自动分配的全局唯一 ID。

同时还提供了两种特殊的 Object 创建方式,不自动分配 ID,而是通过预先确定的算法来生成 ID,叫做 Named Object。

```move
module moveos_std::object {
#[private_generics(T)]
public fun new_named_object<T: key>(value: T): Object<T>;
#[private_generics(T)]
public fun new_account_named_object<T: key>(account: address, value: T): Object<T>;
}
```

* NamedObject: 用类型 `T` 的类型名来生成 ObjectID。生成的公式为 `sha3(type_name<T>())`。一般用于全局唯一的 Object,比如 `0x2::timestamp::Timestamp`
* Account NamedObject: 是用 account 地址和 `T` 类型名来生成 ObjectID。生成的公式为 `sha3(account + type_name<T>())`。一般用于每个用户只有一个的 Object,比如 `0x3::coin_store::CoinStore<CoinType>`

### 操作 Object

#### 所有权转让

将 Object 转让给 `new_owner`

```move
module moveos_std::object {
public fun transfer<T: key + store>(self: Object<T>, new_owner: address);
}
```

`owner` 将属于自己的 Object 通过 `object_id` 拿出来

```move
module moveos_std::object {
public fun take_object<T: key + store>(owner: &signer, object_id: ObjectID): Object<T>;
}
```

> 注意:当 Object 被拿出来后,`owner` 会被设置为 `0x0`,这时候 Object 就变成了 `SystemOwnedObject`
以上方法的 `T` 都必须拥有 `key + store` ability,我们可以把这种类型的 Object 称为 `PublicObject`,用户可以自己转让 `PublicObject` 的所有权。

如果是只有 `key` ability 的 Object,我们可以称为 `PrivateObject`,用户无法直接转让 `PrivateObject` 的所有权,需要借助 `T` 所在的模块提供的接口来转让 `PrivateObject` 的所有权。

```move
module moveos_std::object {
#[private_generics(T)]
public fun take_object_extend<T: key>(object_id: ObjectID): (address, Object<T>);
}
```

`take_object_extend` 方法受 `private_generics(T)` 保护,只有 `T` 所在的模块才能调用,开发者可以决定是否提供将 `PrivateObject` 转让给其他用户的方法。

#### Object 的引用

`Object<T>` 有两种引用方式,一种是只读引用 `&Object<T>`,一种是可变引用 `&mut Object<T>`

我们可以通过 `object::borrow(&Object<T>)` 方法获取到 `&T`,通过 `object::borrow_mut(&mut Object<T>)` 获取到 `&mut T`。至于获取到 `&T``&mut T` 后,可以进行哪些操作,这个由 `T` 的模块来定义。

有两种方法获取 Object 的引用:

1. 通过 `entry` 方法传递进来。

```move
entry fun my_entry(obj: &Object<MyStruct>, obj_mut: &mut Object<MyStruct>) {
// do something
}
```

2. 通过 `borrow_object``borrow_mut_object` 方法获取。

```move
module moveos_std::object {
public fun borrow_object<T: key>(object_id: ObjectID): &Object<T>;
public fun borrow_mut_object<T: key>(owner: &signer, object_id: ObjectID): &mut Object<T>;
}
```

* 注意,Rooch 中的所有 Object 都是读公开的,任何人都可以通过 `ObjectID` 获取到任意的 `&Object<T>`
* Object 的所有者可以通过 `object::borrow_mut_object` 获取 `&mut Object<T>` 引用。

给开发者的扩展方法:

```move
module moveos_std::object {
#[private_generics(T)]
public fun borrow_mut_object_extend<T: key>(object_id: ObjectID): &mut Object<T>;
}
```

* `T` 所在的模块可以通过 `ObjectID` 获取任意 `&mut Object<T>` 引用,除非该 Object 被冻结。

#### 共享的(Shared)和冻结的(Frozen) Object

SystemOwnedObject `Object<T>` 有两种状态,一种是 `shared`,一种是 `frozen`

* `SharedObject`:任何人都可以直接获取到 `&mut Object<T>` 引用。
* `FrozenObject`:任何人都无法获取到 `&mut Object<T>` 引用,包括 `T` 所在的模块。

通过以下方法可以将 `Object<T>` 变为 `SharedObject`

```move
module moveos_std::object {
public fun to_shared<T: key>(self: Object<T>);
}
```

要获取 SharedObject 的可变引用,需要通过 `entry` 参数传递或者 `object` 提供的方法:

```move
module moveos_std::object {
public fun borrow_mut_object_shared<T: key>(object_id: ObjectID): &mut Object<T>;
}
```

通过以下方法可以将 `Object<T>` 变为 `FrozenObject`

```move
module moveos_std::object {
public fun to_frozen<T: key>(self: Object<T>);
}
```

> 注意:一旦 Object 变为 `frozen` 或者 `shared`,Object 就自动归属于 `SystemOwnedObject`,任何人都无法直接获取到 `Object<T>` 的实例,只能通过引用来操作 Object。
#### 嵌套的 Object

`Object<T>` 本身拥有 `store` ability,所以可以嵌套在另外的结构体中作为字段,或者保存到 `vector``Table` 等容器中。

```move
struct Avatar has key {
head: Object<Head>,
body: Object<Body>,
}
```

上面的例子中,`Object<Head>``Object<Body>``Avatar` 结构体的字段,这两个对象归属于 `Avatar` 这个结构体,如果 `Object<Avatar>` 被转让给其他用户,那么 `Object<Head>``Object<Body>` 也会随着 `Object<Avatar>` 一起转让给其他用户。

* 被嵌套的 Object 依然会在 Object Storage 中,可以通过前面描述的引用获取方法来获取 `Object<T>` 的引用。
* 被嵌套的 Object 一定是 `SystemOwnedObject`

#### 删除 Object

通过以下方法可以删除 Object:

```move
module moveos_std::object {
#[private_generics(T)]
public fun remove<T: key>(self: Object<T>): T;
}
```

删除 Object 后会返回 Object 中封装的数据,只有 `T` 所在的模块才能调用该方法。

综上所述,针对不同状态的 Object 的操作,不同的用户具有不同的权限。以下是合约开发者和普通用户对不同类型 Object,使用 `moveos_std::object` 提供的方法可以进行的操作:

- 合约开发者

| object | owner | value abilities | transfer | borrow mut | take value | remove |
|---------|-------------------|-----------------|----------|------------|------------|--------|
| shared | SystemOwnedObject | not required | × || × | × |
| frozen | SystemOwnedObject | not required | × | × | × | × |
| public | UserOwnedObject | key, store |||||
| private | UserOwnedObject | key |||||

- 普通用户

| object | owner | value abilities | transfer | borrow mut | take value | remove |
|---------|-------------------|-----------------|----------|------------|------------|--------|
| shared | SystemOwnedObject | not required | × || × | × |
| frozen | SystemOwnedObject | not required | × | × | × | × |
| public | UserOwnedObject | key, store |||| × |
| private | UserOwnedObject | key | × || × | × |

### Object RPC

通过 `rooch_getState` RPC 接口可以获取到 `ObjectEntity` 的数据。

```bash
curl -H "Content-Type: application/json" -X POST \
--data '{"jsonrpc":"2.0","method":"rooch_getStates","params":["/object/0x2::timestamp::Timestamp", {"decode":true}],"id":1}' \
https://dev-seed.rooch.network
```

```json
{
"jsonrpc": "2.0",
"result": [
{
"value": "0x711ab0301fd517b135b88f57e84f254c94758998a602596be8ae7ba56a0d14b3000000000000000000000000000000000000000000000000000000000000000004002db02e34050600",
"value_type": "0x2::object::ObjectEntity<0x2::timestamp::Timestamp>",
"decoded_value": {
"abilities": 0,
"type": "0x2::object::ObjectEntity<0x2::timestamp::Timestamp>",
"value": {
"flag": 4,
"id": "0x3a7dfe7a9a5cd608810b5ebd60c7adf7316667b17ad5ae703af301b74310bcca",
"owner": "0x0000000000000000000000000000000000000000000000000000000000000000",
"value": {
"abilities": 8,
"type": "0x2::timestamp::Timestamp",
"value": {
"milliseconds": "1694571540000000"
}
}
}
}
}
],
"id": 1
}
```

### Object 相关的方法列表

`object` 模块提供以下函数,可以对 `Object` 进行操作:

| Object 函数 | `#[private_generics<T>]` | 说明 |
| -------------------------------------------------------------------------------------- | -------------------------- | ---------------------------------------------- |
| `object::new<T: key>(T): Object<T>` | true | 创建 `Object`,将 `T` 封装到 `Object` 中,返回 `Object<T>` |
| `object::new_named_object<T: key>(T): Object<T>` | true |`Object``ObjectID``T` 类型生成 |
| `object::new_account_named_object<T: key>(address, T): Object<T>` | true |`Object``ObjectID` 由 address 和 `T` 类型生成 |
| `object::borrow_object<T: key>(ObjectID): &Object<T>` | false | 通过 ID 借用 `Object<T>` 的只读引用 |
| `object::borrow_mut_object<T: key>(&signer, ObjectID): &mut Object<T>` | false | owner(&signer) 通过 ID 借用 `Object<T>` 的可变引用 |
| `object::borrow_mut_object_shared<T: key>(ObjectID): &mut Object<T>` | false | 通过 ID 借用共享 `Object<T>` 的可变引用 |
| `object::borrow_mut_object_extend<T: key>(ObjectID): &mut Object<T>` | true | 给开发者的扩展方法,`T` 所在的模块可以通过 ObjectID 获取任意 `&mut Object<T>` |
| `object::exists_object(ObjectID): bool` | false | 通过 ObjectID 检测 Object 是否存在 |
| `object::id<T>(&Object<T>): ObjectID` | false | 获取 ObjectID |
| `object::owner<T: key>(&Object<T>): address` | false | 获取拥有者的地址 |
| `object::borrow<T: key>(&Object<T>): &T` | false | `&Object` 借用 `T` 的只读引用 |
| `object::borrow_mut<T: key>(&mut Object<T>): &mut T` | false | 通过 `&mut Object` 借用 `T` 的可变引用 |
| `object::transfer<T: key + store>(Object<T>, address)` | false |`Object<T>` 所有权转移给 `address` |
| `object::transfer_extend<T: key>(Object<T>, address)` | true | 给开发者的扩展方法,将 `Object<T>` 所有权转移给 `address` |
| `object::to_shared<T: key>(Object<T>)` | false |`Object<T>` 变为 `SharedObject`,任何人都可以直接获取到 `&mut Object<T>` |
| `object::is_shared<T: key>(&Object<T>): bool` | false | 判断 `Object<T>` 是否为 `SharedObject` |
| `object::to_frozen<T: key>(Object<T>)` | false |`Object<T>` 变为 `FrozenObject`,任何人都无法获取到 `&mut Object<T>` |
| `object::is_frozen<T: key>(&Object<T>): bool` | false | 判断 `Object<T>` 是否为 `FrozenObject` |
| `object::remove<T: key>(Object<T>): T` | true | 删除 `Object<T>`,并返回其中的 `T`,只有 `T` 所在的模块才能删除 `Object<T>` |

以上函数中,如果 `#[private_generics<T>]` 列为 `true`,表明只有 `T` 所在的模块才能调用。

## Object 的 dynamic fields

Rooch 为 object 提供了管理动态字段的能力。动态字段是指将 Resource 或者 Object 以 key, value 的形式储存在 Object 中。特别是,key 可以是异质的,即不受 key 类型的限制。更具体的说,Object 可以被当作 [Table](https://github.com/rooch-network/rooch/blob/main/frameworks/moveos-stdlib/sources/table.move)[Bag](https://github.com/rooch-network/rooch/blob/main/frameworks/moveos-stdlib/sources/bag.move) 来使用。

Rooch object 的提供了两种类型的动态字段:常规类型和Object类型。

常规类型的动态字段是指任何具有 `store` ability 的类型存放在 object 下;Object 类型的动态字段是将子 Object 对象存放在 object 下。

<Callout>
注意:由于 Object 类型本身也具有 `store` ability,那把整个 `Obejct<T>` 作为一个普通的字段存放在 object 下和使用 Object 类型字段有什么区别?
1. 如果通过 `new_with_parent` 创建的子 object,是属于父 object 的子对象,与父 object 在同一个 SMT 子树下。这对于整个父 object 的状态迁移,查询等管理都很便捷。
2. 如果在全局创建的 object,即使通过 `add_field` 放到 object 的动态字段中了,它实际上也是属于 global object,它的状态树处于 Root 根下。
</Callout>

### 常规类型动态字段相关方法列表

| 方法 | 说明
|---|---
| `add_field<T: key, K: copy + drop, V: store>(obj: &mut Object<T>, key: K, val: V)` | 添加一个动态字段到对象。如果已经存在相同的键,则会中止。字段本身不会存储在对象中,并且不能从对象中发现。
| `borrow_field<T: key, K: copy + drop, V: store>(obj: &Object<T>, key: K): &V` | 获取对象中键对应的值的不可变引用。如果没有对应的键,则会中止。
| `borrow_field_with_default<T: key, K: copy + drop, V: store>(obj: &Object<T>, key: K, default: &V): &V` | 获取对象中键对应的值的不可变引用。如果没有对应的键,则返回默认值。
| `borrow_mut_field<T: key, K: copy + drop, V: store>(obj: &mut Object<T>, key: K): &mut V` | 获取对象中键对应的值的可变引用。如果没有对应的键,则会中止。
| `borrow_mut_field_with_default<T: key, K: copy + drop, V: store + drop>(obj: &mut Object<T>, key: K, default: V): &mut V` | 获取对象中键对应的值的可变引用。如果没有对应的键,则插入键值对(`key`, `default`),然后返回对应的值的可变引用。
| `remove_field<T: key, K: copy + drop, V: store>(obj: &mut Object<T>, key: K): V` | 从对象中移除键对应的字段,并返回字段的值。如果没有对应的键,则会中止。
| `contains_field<T: key, K: copy + drop>(obj: &Object<T>, key: K): bool` | 如果对象中存在键对应的字段,则返回`true`,否则返回`false`
| `contains_field_with_type<T: key, K: copy + drop, V: store>(obj: &Object<T>, key: K): bool` | 如果对象中存在键对应的字段,并且字段的值类型为`V`,则返回`true`,否则返回`false`
| `upsert_field<T: key, K: copy + drop, V: store + drop>(obj: &mut Object<T>, key: K, value: V)` | 如果对象中存在键对应的字段,则更新字段的值。如果没有对应的键,则插入键值对(`key`, `value`)。
| `field_size<T: key>(obj: &Object<T>): u64` | 返回对象中字段的数量,即键值对的数量。

### Object 类型动态字段相关方法列表

| 方法 | 说明
|---|---
| `new_with_parent<P: key, T: key>(parent: &mut Object<P>, v: T): Object<T>` | 向对象添加一个新的子对象字段,返回新添加的子对象。只有共享对象可以添加子对象字段。
| `new_with_parent_and_id<P: key, ID:drop, T: key>(parent: &mut Object<P>, id: ID, v: T): Object<T>` | 使用自定义ID向对象添加一个新的子对象字段,返回新添加的子对象。只有共享对象可以添加子对象字段。

## Rooch Object, Sui Object, Aptos Object 的比较

### Sui Object

* Sui Object 是一种特殊的 `struct` 要求该 `struct` 必须拥有 `key` ability, 同时第一个字段必须是 `UID`,Object 是虚拟机和存储提供的,Move 中并不存在 Object 类型。Rooch 中的 Object 是在 Move 中定义的类型。
* Sui Object 由外部系统索引,合约内并没有提供通过 ID 获取 Object 的方法,只能通过参数传递。Rooch 同时提供两种方式。
* Sui Object 如果发生嵌套或者保存到其他容器中,Object 在全局 Object Storage 就不可见。而 Rooch 中的 Object 嵌套或者保存到其他容器中,Object 依然在全局 Object Storage 中可访问。

### Aptos Object

* Aptos Object 底层是一种特殊的账户,该账户的 `address``ObjectID`
* `Object<T>` 代表对 Object 的引用,可以 `copy``drop`,而 Rooch 中 `Object<T>` 只有一个实例,不可以 `copy``drop`
* Aptos Object 通过 `DeleteRef``ExtendRef``TransferRef` 来表达对 Object 不同的操作权限,而 Rooch Object 通过只读引用,可变引用以及实例来区分不同的权限。

## 参考链接

1. [Rooch Object API document](https://github.com/rooch-network/rooch/blob/main/frameworks/moveos-stdlib/doc/object.md)
2. [Rooch Object Source code](https://github.com/rooch-network/rooch/blob/main/frameworks/moveos-stdlib/sources/object.move)
3. [Sui Object](https://docs.sui.io/learn/objects)
4. [Aptos Object](https://aptos.dev/standards/aptos-object/)
5. [Storage Abstraction](./storage-abstraction)
6. [Hot Potato](https://examples.sui.io/patterns/hot-potato.html)

0 comments on commit f2bf36b

Please sign in to comment.