-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fad1883
commit f2bf36b
Showing
1 changed file
with
327 additions
and
0 deletions.
There are no files selected for viewing
327 changes: 327 additions & 0 deletions
327
i18n/zh/docusaurus-plugin-content-docs/current/object.md/rooch-object.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |