Skip to content

Commit

Permalink
[docs] Cosmetic changes to the objects tutorial (MystenLabs#2387)
Browse files Browse the repository at this point in the history
  • Loading branch information
awelc authored Jun 2, 2022
1 parent 276b0eb commit 3c04a59
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 15 deletions.
4 changes: 2 additions & 2 deletions doc/src/build/programming-with-objects/ch1-object-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Now `ColorObject` represents a Sui object type and can be used to create Sui obj
Now that we have learned how to define a Sui object type, how do we create/instantiate a Sui object? In order to create a new Sui object from its type, we must assign an initial value to each of the fields, including `id`. The only way to create a new unique `VersionedID` for a Sui object is to call `TxContext::new_id`. The `new_id` function takes the current transaction context as an argument to generate unique IDs. The transaction context is of type `&mut TxContext` and should be passed down from an [entry function](../move.md#entry-functions) (a function that can be called directly from a transaction). Let's look at how we may define a constructor for `ColorObject`:
```rust
/// TxContext::Self represents the TxContext module, which allows us call
/// functions in the module, such as the `sender` function.
/// functions in the module, such as the `new_id` function.
/// TxContext::TxContext represents the TxContext struct in TxContext module.
use Sui::TxContext::{Self, TxContext};

Expand Down Expand Up @@ -154,7 +154,7 @@ $ wallet active-address
```
This will tell you the current wallet address.

First, we need to publish the code on-chain. Assuming the path to the root of the repository is $ROOT:
First, we need to publish the code on-chain. Assuming the path to the root of the repository containing Sui source code is $ROOT:
```
$ wallet publish --path $ROOT/sui_programmability/examples/objects_tutorial --gas-budget 10000
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ TestScenario::next_tx(scenario, &owner);
### Pass objects by value
Objects can also be passed by value into an entry function. By doing so, the object is moved out of Sui storage (a.k.a. deleted). It is then up to the Move code to decide where this object should go.

> :books: Since every [Sui object struct type](./ch1-object-basics.md#define-sui-object) must include `VersionedID` as a field, and the [VersionedID struct](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/ID.move) does not have the `drop` ability, the Sui object struct type [must not](https://github.com/move-language/move/blob/main/language/documentation/book/src/abilities.md#drop) have `drop` ability either. Hence, any Sui object cannot be arbitrarily dropped and must be either consumed or unpacked.
> :books: Since every [Sui object struct type](./ch1-object-basics.md#define-sui-object) must include `VersionedID` as a field, and the [VersionedID struct](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/ID.move) does not have the `drop` ability, the Sui object struct type [must not](https://github.com/move-language/move/blob/main/language/documentation/book/src/abilities.md#drop) have `drop` ability either. Hence, any Sui object cannot be arbitrarily dropped and must be either consumed (e.g., transferred to another owner) or deleted by [unpacking](https://move-book.com/advanced-topics/struct.html#destructing-structures), as described below.
There are two ways we can deal with a pass-by-value Sui object in Move:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ TestScenario::next_tx(scenario, &sender2);
```
To show that this object is indeed not owned by anyone, we start the next transaction with `sender2`. As explained earlier, we used `take_immutable` and subsequently `borrow` to obtain a read-only reference to the object. It succeeded! This means that any sender will be able to take an immutable object. In the end, to return the object, we also need to call a new API: `return_immutable`.

Next let's examine if this object is indeed immutable. To test this, let's first introduce a function that would mutate a `ColorObject`:
In order to examine if this object is indeed immutable, let's introduce a function that would mutate a `ColorObject` (we will use this function when describing [on-chain interactions](#on-chain-interactions)):
```rust
public(script) fun update(
object: &mut ColorObject,
Expand Down
6 changes: 3 additions & 3 deletions doc/src/build/programming-with-objects/ch4-object-wrapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ struct Bar has key, store {
value: u64,
}
```
Now `Bar` is also a Sui object type. When we put a Sui object of type `Bar` into a Sui object of type `Foo`, the Sui object of type `Bar` is said to be **wrapped** by `Foo` (which we call the **wrapper** object).
Now `Bar` is also a Sui object type. When we put a Sui object of type `Bar` into a Sui object of type `Foo`, the Sui object of type `Bar` is said to be **wrapped** by `Foo` (which we call the **wrapper** object or the **wrapping** object).

> :bulb: In Move code, it is also possible to put a Sui object as a field of a non-Sui object struct type. For example, in the above code sample, we can define `Foo` to not have `key` but `Bar` to have `key, store`. However, this case can happen only temporarily in the middle of a Move execution and cannot be persisted on-chain. This is because a non-Sui object cannot flow across the Move-Sui boundary, and one must unpack the non-Sui object at some point and deal with the Sui object fields in it.
There are some interesting consequences of wrapping an Sui object into another. When an object is wrapped, this object no longer exists independently on-chain. We will no longer be able to look up this object by its ID. This object becomes part of the data of the object that wraps it. Most importantly, *we can no longer pass the wrapped object as an argument in any way in Move calls*. The only access point is through the wrapping object.
There are some interesting consequences of wrapping a Sui object into another. When an object is wrapped, this object no longer exists independently on-chain. We will no longer be able to look up this object by its ID. This object becomes part of the data of the object that wraps it. Most importantly, *we can no longer pass the wrapped object as an argument in any way in Move calls*. The only access point is through the wrapping object.

>:bulb: The fact that you can no longer use a wrapped Sui object means that it's impossible to create circular wrapping behavior, where A wraps B, B wraps C, and C also wraps A.
Expand Down Expand Up @@ -102,7 +102,7 @@ Finally, let's define the function that the service operator can call in order t
```rust
public(script) fun execute_swap(wrapper1: ObjectWrapper, wrapper2: ObjectWrapper, ctx: &mut TxContext);
```
Where `wrapper1` and `wrapper2` are two wrapped objects that were sent from different object owners to the service operator. (Hence, the service operator owns both.) Both wrapped objects are passed by value because they will eventually need to be unpacked.
Where `wrapper1` and `wrapper2` are two wrapped objects that were sent from different object owners to the service operator. (Hence, the service operator owns both.) Both wrapped objects are passed by value because they will eventually need to be [unpacked](https://move-book.com/advanced-topics/struct.html#destructing-structures).
We first check that the swap is indeed legit:
```rust
assert!(wrapper1.to_swap.scarcity == wrapper2.to_swap.scarcity, 0);
Expand Down
19 changes: 11 additions & 8 deletions doc/src/build/programming-with-objects/ch5-child-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ In the previous chapter, we walked through various ways of wrapping an object in

Fortunately, Sui provides another way to represent object relationships: *an object can own other objects*. In the first chapter, we introduced libraries for transferring objects to an account address. In this chapter, we will introduce libraries that allow you transfer objects to other objects.

### Create hild objects
### Create child objects

There are two ways of creating child objects which we describe in the following sections.

#### transfer_to_object
Assume we own two objects in our account address. To make one object own the other object, we can use the following API in the [`Transfer`](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/Transfer.move) library:
```rust
Expand All @@ -22,7 +25,7 @@ The first argument `obj` will become a child object of the second argument `owne

The function returns a special struct `ChildRef<T>` where `T` matches the type of the child object. It represents a reference to the child object. Since `ChildRef` is a struct type without `drop` ability, Move ensures the return value cannot be dropped. This ensures the caller of the function must put the reference somewhere and cannot forget about it.

This is very important because later on if we attempt to delete the parent object, the existence of the child references force us to take care of them. Otherwise, we may end up in a situation where we deleted the parent object, but there are still some child objects; and these child objects will be locked forever, as we will explain in latter sections. In the last section, we will also see how this reference is used to move around child objects and prevent making mistakes.
This is very important because later on if we attempt to delete the parent object, the existence of the child references forces us to take care of them. Otherwise, we may end up in a situation where we deleted the parent object, but there are still some child objects; and these child objects will be locked forever, as we will explain in latter sections. In the last section, we will also see how this reference is used to move around child objects and to prevent making mistakes.

Let's look at some code. The full source code can be found in [ObjectOwner.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-core/src/unit_tests/data/object_owner/sources/ObjectOwner.move).

Expand Down Expand Up @@ -104,15 +107,15 @@ public(script) fun create_another_parent(child: Child, ctx: &mut TxContext) {
In the above function, we need to first create the ID of the new parent object. With the ID, we can then transfer the child object to it by calling `transfer_to_object_id`, thereby obtaining a reference `child_ref`. With both `id` and `child_ref`, we can create an object of `AnotherParent`, which we would eventually transfer to the sender's account.

### Use Child Objects
We have explained in the first chapter that, in order to use an owned object, the object owner must be the transaction sender. What about objects owned by objects? We require that the object's owner object must also be passed as an argument in the Move call. For example, if object A owns object B, and object B owns object C, to be able to use C when calling a Move entry function, one must also pass B in the argument; and since B is in the argument, A must also be in the argument. This essentially mean that to use an object, its entire ownership ancestor chain must be included, and the account owner of the root ancestor must match the sender of the transaction.
We have explained in the first chapter that, in order to use an owned object, the object owner must be the transaction sender. What about objects owned by objects? We require that the object's owner object must also be passed as an argument in the Move call. For example, if object A owns object B, and object B owns object C, to be able to use C when calling a Move entry function, one must also pass B as an argument; and since B is an argument, A must also be an argument. This essentially means that to use an object, its entire ownership ancestor chain must be included, and the account owner of the root ancestor must match the sender of the transaction.

Let's look at how we could use the child object created earlier. Let's define two entry functions:
```rust
public(script) fun mutate_child(_child: &mut Child, _ctx: &mut TxContext) {}
public(script) fun mutate_child_with_parent(_child: &mut Child, _parent: &mut Parent, _ctx: &mut TxContext) {}
```
The first function requires only one object argument, which is an `Child` object. The second function requires two arguments, a `Child` object and a `Parent` object. Both functions are made empty since what we care about here is not the mutation logic, but whether you are able to make a call to them at all.
Both functions will compile successfully, because object ownership relationships are dynamic properties and the compiler cannot forsee.
The first function requires only one object argument, which is a `Child` object. The second function requires two arguments, a `Child` object and a `Parent` object. Both functions are made empty since what we care about here is not the mutation logic, but whether you are able to make a call to them at all.
Both functions will compile successfully, because object ownership relationships are dynamic properties and the compiler cannot forsee them.

Let's try to interact with these two entry functions on-chain and see what happens. First we publish the sample code:
```
Expand Down Expand Up @@ -185,7 +188,7 @@ In this section, we will introduce a few more APIs that will allow us safely mov

There are two ways to transfer a child object:
1. Transfer it to an account address, thus it will no longer be a child object after the transfer.
2. Transfer it to another object, thus it will still be a child object but the parent object changed.
2. Transfer it to another object, thus it will still be a child object but with the parent object changed.

#### transfer_child_to_address
First of all, let's look at how to transfer a child object to an account address. The [Transfer](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/Transfer.move) library provides the following API:
Expand All @@ -196,9 +199,9 @@ public fun transfer_child_to_address<T: key>(
recipient: address,
);
```
`transfer_child_to_address` transfers a currently child object to an account address. This function takes 3 arguments: `child` is the child object we wish to transfer, `child_ref` is the reference to it that was obtained when we previously transferred it to its current parent, and `recipient` is the recipient account address. After the transfer, the `recipient` account address now owns this object.
`transfer_child_to_address` transfers an object that is currently a child to an account address. This function takes 3 arguments: `child` is the child object we wish to transfer, `child_ref` is the reference to it that was obtained when we previously transferred it to its current parent, and `recipient` is the recipient account address. After the transfer, the `recipient` account address now owns this object.
There are two important things worth mentioning:
1. Requiring `child_ref` as an argument ensures that the old parent won't have an out-of-dated reference to the child object, and this reference is properly destroyed by the library during the transfer.
1. Requiring `child_ref` as an argument ensures that the old parent won't have an out-of-date reference to the child object, and this reference is properly destroyed by the library during the transfer.
2. This function has no return value. We no longer need a `ChildRef` because the object is no longer a child object.

To demonstrate how to use this API, let's implement a function that removes a child object from a parent object and transfer it back to the account owner:
Expand Down

0 comments on commit 3c04a59

Please sign in to comment.