-
Notifications
You must be signed in to change notification settings - Fork 93
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
[book] transfer to object #106
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 |
---|---|---|
@@ -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 | ||
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. | ||
|
||
</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. | ||
|
||
Comment on lines
+42
to
+43
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 so-so on the phrase "the owner object needs to implement..." because it sounds like interface implementation which is not a concept we have in Move. It also puts the focus on the custom receive function, and I'm not sure that's the focus for the object doing the receiving (the only thing that matters for the receiving object is that it exposes its 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. Yeah, I also had this feeling, and I don't like it. The biggest blocker I faced on every attempt to write this page. |
||
```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>): { | ||
/* ... */ | ||
} | ||
``` | ||
|
||
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 | ||
[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. | ||
|
||
```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, | ||
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. | ||
public fun receive<T: key>(po: &mut PostOffice, to_receive: Receiving<T>): T { | ||
transfer::receive(&mut po.id, to_receive) | ||
} | ||
``` | ||
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. | ||
|
||
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 | ||
[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. |
||
|
||
## 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Technically they are split into four types:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch, will expand!