-
Notifications
You must be signed in to change notification settings - Fork 125
[book] transfer to object #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,21 +31,27 @@ Transactions consist of: | |
|
||
## Inputs | ||
|
||
Transaction inputs are the arguments for the transaction and are split between 2 types: | ||
Transaction inputs are the arguments for the transaction and are split between 3 types: | ||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like the term "PTB" should be mentioned somewhere on this page. |
||
|
||
- Pure arguments: These are mostly [primitive types](../move-basics/primitive-types.html) with some | ||
extra additions. A pure argument can be: | ||
- [`bool`](../move-basics/primitive-types.html#booleans). | ||
- [unsigned integer](../move-basics/primitive-types.html#integer-types) (`u8`, `u16`, `u32`, `u64`, `u128`, `u256`). | ||
- [`address`](../move-basics/address.html). | ||
- [`std::string::String`](../move-basics/string.html), UTF8 strings. | ||
- [`std::ascii::String`](../move-basics/string.html#ascii-strings), ASCII strings. | ||
- [`vector<T>`](../move-basics/vector.html), where `T` is a pure type. | ||
- [`std::option::Option<T>`](../move-basics/option.html), where `T` is a pure type. | ||
- [`std::object::ID`](../storage/uid-and-id.html), typically points to an object. See also [What is an Object](../object/object-model.html). | ||
extra additions. A pure argument can be: | ||
- [`bool`](../move-basics/primitive-types.html#booleans). | ||
- [unsigned integer](../move-basics/primitive-types.html#integer-types) (`u8`, `u16`, `u32`, | ||
`u64`, `u128`, `u256`). | ||
- [`address`](../move-basics/address.html). | ||
- [`std::string::String`](../move-basics/string.html), UTF8 strings. | ||
- [`std::ascii::String`](../move-basics/string.html#ascii-strings), ASCII strings. | ||
- [`vector<T>`](../move-basics/vector.html), where `T` is a pure type. | ||
- [`std::option::Option<T>`](../move-basics/option.html), where `T` is a pure type. | ||
- [`std::object::ID`](../storage/uid-and-id.html), typically points to an object. See also | ||
[What is an Object](../object/object-model.html). | ||
- Object arguments: These are objects or references of objects that the transaction will access. An | ||
object argument needs to be either a shared object, a frozen object, or an object that the | ||
transaction sender owns, in order for the transaction to be successfull. | ||
For more see [Object Model](../object/index.html). | ||
object argument needs to be either a shared object, a frozen object, or an object that the | ||
transaction sender owns, in order for the transaction to be successfull. For more see | ||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[Object Model](../object/index.html). | ||
- Receiving object argument: a special argument that is used to receive an object transferred to | ||
another object. We cover this in more detail in the | ||
[Transfer to Object](../storage/transfer-to-object.html) section. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to have an example and/or more detail on how you actually create a receiving object, either on this page or that one |
||
|
||
## Commands | ||
|
||
|
@@ -95,6 +101,7 @@ The result of the executed transaction consists of different parts: | |
status of the transaction, updates to objects and their new versions, the gas object used, the gas | ||
cost of the transaction, and the events emitted by the transaction; | ||
- Events - the custom [events](./../programmability/events.md) emitted by the transaction; | ||
- Object Changes - the changes made to the objects, including the _change of ownership_; | ||
- Object Changes - the changes made to the objects, including the | ||
[_change of ownership_](../storage/storage-functions.md); | ||
- Balance Changes - the changes made to the aggregate balances of the account involved in the | ||
transaction; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,4 +1,132 @@ | ||||||
# Transfer to Object? | ||||||
# Transfer to Object | ||||||
|
||||||
The `transfer::transfer` call takes the receiver `address` as the second argument, and while in most | ||||||
of the cases it is an account address, it can also be an address of an object. In this case, | ||||||
Previously, we have explained how [transfer](./storage-functions.md#transfer) works in relation to | ||||||
accounts. However, there is an additional behaviour allowed by the | ||||||
[Sui Object Model](./../object/object-model.md) - transferring objects to other objects. If certain | ||||||
conditions are met (which we explain in this section), objects can be sent to and _received_ from | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
other objects. | ||||||
|
||||||
<div class="warning"> | ||||||
|
||||||
Warning: recipient object must be implemented in a way that allows receiving. Attempt to send an | ||||||
object to an object that does not implement receiving can result in asset loss. | ||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
</div> | ||||||
|
||||||
## Background | ||||||
|
||||||
Every object in Sui has its own `UID` which is represented by the | ||||||
[address type](./../move-basics/address.md). This address can be used to query the object's data, | ||||||
and to perform account queries, such as getting the list of owned objects. This property of the | ||||||
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may be a bit confusing to folks, because depending on context, an object's ID can be represented by a Here seems like the wrong place to discuss this concept in detail, but it seems like something we should document somewhere. |
||||||
system lays the foundation for the transfer to object feature. | ||||||
|
||||||
> In this section, by _object address_ we mean the address value stored in the `UID` of the object. | ||||||
|
||||||
## Transfer to Object | ||||||
|
||||||
An object can be transferred to another object by calling the `transfer` function, or its public | ||||||
version - `public_transfer`. The behaviour is identical to the transfer to account. | ||||||
|
||||||
```move | ||||||
// File: sui-framework/sources/transfer.move | ||||||
module sui::transfer; | ||||||
|
||||||
public fun transfer<T: key>(object: T, recipient: address) {} | ||||||
public fun public_transfer<T: key + store>(object: T, recipient: address) {} | ||||||
``` | ||||||
|
||||||
## Receiving Objects | ||||||
|
||||||
While objects _owned by accounts_ can be used in the transaction directly (given that the sender of | ||||||
the transaction is the owner), objects transferred to other objects first need to be _received_ | ||||||
through their owner. The owner object needs to implement a custom function that will call the | ||||||
`receive` function from the `transfer` module. | ||||||
|
||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
```move | ||||||
// File: sui-framework/sources/transfer.move | ||||||
module sui::transfer; | ||||||
|
||||||
/// Special type which cannot be constructed in Move but can be passed as a | ||||||
/// special input to a PTB. | ||||||
public struct Receiving<phantom T: key> has drop { | ||||||
id: ID, | ||||||
version: u64, | ||||||
} | ||||||
|
||||||
/// Private receiving function for `key`-only objects. | ||||||
public fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T { /* ... */ } | ||||||
|
||||||
/// Public receiving function for `key + store` objects. | ||||||
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): { | ||||||
/* ... */ | ||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
``` | ||||||
|
||||||
The `Receiving` type is a special type that references an object-owned object. It cannot be | ||||||
constructed in Move, and can only be passed as a special input to a | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think including this part in the example would be very helpful - I don't yet see how the end-to-end process of transferring works. e.g. if Alice wants to transfer an object to a PostOffice owned by Bob, does Bob have to execute a PTB that constructs a |
||||||
[Programmable Transaction Block (PTB)](../concepts/what-is-a-transaction.md). The `receive` (or | ||||||
`public_receive`) function must be called in order to exchange the `Receiving` instance for the | ||||||
actual object. | ||||||
|
||||||
While this may seem overwhelming, we will demonstrate the feature in a simple example. | ||||||
|
||||||
## Example | ||||||
|
||||||
To demonstrate the feature, let's consider a simple `PostOffice` object that can receive any objects | ||||||
transferred to it. Given that the transfer itself can be performed on the PTB level, or in a Move | ||||||
function, we will focus on the receiving part. | ||||||
|
||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
```move | ||||||
module book::post_office; | ||||||
|
||||||
// While the `sui::transfer` module is implicitly imported, the `Receiving` type | ||||||
// is not. We need to import it explicitly. | ||||||
use sui::transfer::Receiving; | ||||||
|
||||||
/// The `PostOffice` object that can receive any objects transferred to it via | ||||||
/// a custom `receive` function. | ||||||
public struct PostOffice has key { | ||||||
id: UID, | ||||||
} | ||||||
|
||||||
/// Voila! The `receive` function can now be called on the `PostOffice` object | ||||||
/// to receive any objects transferred to it. | ||||||
public fun public_receive<T: key + store>( | ||||||
po: &mut PostOffice, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it would be clearer if this function wasn't named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry to chime in an internal discussion, but I got sort of the same question. For an object to be able to receive other objects it HAS TO have a struct method called Also not sure where to mention this, but I feel like it would be really good to compare this to DOFs. My best guess right now is that dofs are for attaching specific objects to others in the module defining the outer object, while transfer to object will be for the developer allowing anyone who's able to obtain a mutable reference to the object to deposit anything on it. Very different purposes, but since both end up with an object wrapped into another I really feel it's worth clarifying. |
||||||
to_receive: Receiving<T> | ||||||
): T { | ||||||
transfer::public_receive(&mut po.id, to_receive) | ||||||
} | ||||||
|
||||||
/// For objects without `store` (though, they would require special handling), we | ||||||
/// can implement the non-public version of the `receive` function. | ||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
public fun receive<T: key>(po: &mut PostOffice, to_receive: Receiving<T>): T { | ||||||
transfer::receive(&mut po.id, to_receive) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm very confused about the non-public receive here. I don't understand the connection between whether or not something has |
||||||
} | ||||||
``` | ||||||
Comment on lines
+101
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work? I would expect it to fail because of custom transfer rules (the non- |
||||||
|
||||||
In this example, we have defined a `PostOffice` object that can receive any objects transferred to | ||||||
it. We omitted the creation of the `PostOffice` for brevity; additionally, an implementation like | ||||||
this would require some authorization to prevent unauthorized claims. We talk about authorization | ||||||
patterns in the [Advanced Programmability](./../programmability/) chapter. | ||||||
|
||||||
## Limitations | ||||||
|
||||||
Transfer to Object must be used with caution, as the recipient object must be implemented in a way | ||||||
that allows receiving. Worth noting, that objects owned by other objects must be taken by value, and | ||||||
cannot be borrowed as references, unlike objects owned by accounts. | ||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
Transfer to Object is not the only way to create a relationship between objects. There are more | ||||||
flexible and feature-rich ways, such as the | ||||||
[Dynamic Fields](./../programmability/dynamic-fields.md), which we explore in the | ||||||
damirka marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
[Advanced Programmability](./../programmability/) chapter. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not know if this is the place for this comment, however as dynamic fields are referenced here, here it is:
One major difference is that one does not need to have ownership of the owner-object in order to transfer an object to it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Been thinking about it a lot and still don't have an answer. Technically, in the book, it is too early to mention dynamic fields (they come mid next chapter), yet dynamic fields are too far from transfer to object. Very good point, and very much worth discussing! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my mind it's not really an either/or of TTO vs dynamic fields. TTO is about transfer, dynamic fields are about ownership -- i.e. it's perfectly valid to have a protocol where you use TTO to transfer something to an object, and then when it is received, it is turned into a dynamic field on the object it was received on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amnn I agree that they're not replacing each other (though, in some situations they do), but more of a guide which one is suited for which purpose. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## Summary | ||||||
|
||||||
- Objects can be transferred to other objects by calling the `transfer` function. | ||||||
- The recipient object must be able to `receive` transferred objects, otherwise the objects may be | ||||||
lost. | ||||||
- The `Receiving` type is a special type that references an object-owned object. It is passed as a | ||||||
special input to a transaction and cannot be constructed dynamically. | ||||||
- Once received, the object can be used normally, including being transferred again, even to the | ||||||
same parent object. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the "including being transferred again" part was discussed above, so it probably shouldn't be mentioned in the summary. |
Uh oh!
There was an error while loading. Please reload this page.