Rooch Object
Rooch 中的 Object 是一种箱子(Box)模式的 Object。创建一个 Object 相当于在状态空间中创建了一个箱子,可以将类型 T
的实例封装在箱子中,而 ObjectID
是这个箱子的地址。此外,这个箱子还支持动态添加状态,即 Object 的动态字段特性。
如果我们将智能合约的状态空间比作程序的堆内存,Object 类似于一个智能指针,提供了对状态的引用和操作,以及生命周期的管理。
Object 的所有权
Object
的 owner
代表 Object 属于哪个账户地址,通过 owner
可以将 Object 分为两种:
SystemOwnedObject
:owner
为0x0
的 Object。Object 创建后默认为SystemOwnedObject
。UserOwnedObject
:owner
为非0x0
的 Object。Object 被转让给某个用户后,owner
会被设置为该用户的地址。
Object 的生命周期
创建 Object
通过调用 object::new
方法可以创建出类型为 T
的 Object。
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。
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
:
module moveos_std::object {
public fun transfer<T: key + store>(self: Object<T>, new_owner: address);
}
owner
将属于自己的 Object 通过 object_id
拿出来
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
的所有权。
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 的引用:
- 通过
entry
方法传递进来。
entry fun my_entry(obj: &Object<MyStruct>, obj_mut: &mut Object<MyStruct>) {
// do something
}
- 通过
borrow_object
和borrow_mut_object
方法获取。
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>
引用。
给开发者的扩展方法:
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
。
module moveos_std::object {
public fun to_shared<T: key>(self: Object<T>);
}
要获取 SharedObject 的可变引用,需要通过 entry
参数传递或者 object
提供的方法:
module moveos_std::object {
public fun borrow_mut_object_shared<T: key>(object_id: ObjectID): &mut Object<T>;
}
通过以下方法可以将 Object<T>
变为 FrozenObject
。
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
等容器中。
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:
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
的数据。
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
{
"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 (opens in a new tab) 或 Bag (opens in a new tab) 来使用。
Rooch object 的提供了两种类型的动态字段:常规类型和Object类型。
常规类型的动态字段是指任何具有 store
ability 的类型存放在 object 下;Object 类型的动态字段是将子 Object 对象存放在 object 下。
注意:由于 Object 类型本身也具有 store
ability,那把整个 Obejct<T>
作为一个普通的字段存放在 object 下和使用 Object 类型字段有什么区别?
- 如果通过
new_with_parent
创建的子 object,是属于父 object 的子对象,与父 object 在同一个 SMT 子树下。这对于整个父 object 的状态迁移,查询等管理都很便捷。 - 如果在全局创建的 object,即使通过
add_field
放到 object 的动态字段中了,它实际上也是属于 global object,它的状态树处于 Root 根下。
常规类型动态字段相关方法列表
方法 | 说明 |
---|---|
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 通过只读引用,可变引用以及实例来区分不同的权限。
TODO: This part of this document needs to be improved