diff --git a/docs/content/concepts.mdx b/docs/content/concepts.mdx index 6b15e43a43510..d6ac21d735c5c 100644 --- a/docs/content/concepts.mdx +++ b/docs/content/concepts.mdx @@ -41,9 +41,9 @@ Sui introduces innovative approaches to blockchain architecture and development. ## Object Model - - - + + + ## Move diff --git a/docs/content/concepts/architecture.mdx b/docs/content/concepts/architecture.mdx index 43e38b34446f6..185e0ff9dc5ac 100644 --- a/docs/content/concepts/architecture.mdx +++ b/docs/content/concepts/architecture.mdx @@ -16,7 +16,7 @@ Sui is a layer 1 blockchain. Layer 1 networks consist of the following primary c On Sui specifically, other core components include: -1. [**Objects**](/concepts/object-model), the most basic unit of storage on Sui that are addressable on-chain by unique IDs. +1. [**Objects**](/guides/developer/objects/object-model), the most basic unit of storage on Sui that are addressable on-chain by unique IDs. 1. [**Move**](/concepts/sui-move-concepts), the programming language used to create smart contracts on Sui. @@ -88,7 +88,7 @@ The basic unit of storage in Sui is the object. Blocks on the chain are actually Every object has an owner field that dictates how you can use it in transactions. Objects can be owned by an address or party, or they can be immutable, shared, or wrapped. -Learn more about the [Sui Object Model](/concepts/object-model.mdx) or [Object Ownership](/concepts/object-ownership.mdx). +Learn more about the [Sui Object Model](/guides/developer/objects/object-model.mdx) or [Object Ownership](/guides/developer/objects/object-ownership.mdx). ## Move diff --git a/docs/content/concepts/data-access/data-serving.mdx b/docs/content/concepts/data-access/data-serving.mdx index 563b5a57e969b..32a6d5ad80461 100644 --- a/docs/content/concepts/data-access/data-serving.mdx +++ b/docs/content/concepts/data-access/data-serving.mdx @@ -4,7 +4,7 @@ description: Overview of the types of data access mechanisms available in Sui. keywords: [ data types, data access, data access interfaces, json RPC, custom indexers, indexers, gRPC, graphql RPC, general purpose indexer ] --- -You can access Sui network data like [Transactions](/concepts/transactions.mdx), [Checkpoints](/concepts/cryptography/system/checkpoint-verification.mdx), [Objects](/concepts/object-model.mdx), [Events](/guides/developer/sui-101/using-events.mdx), and more through the available interfaces. You can use this data in your application workflows, to analyze network behavior across applications or protocols of interest, or to perform audits on parts or the whole of the network. +You can access Sui network data like [Transactions](/concepts/transactions.mdx), [Checkpoints](/concepts/cryptography/system/checkpoint-verification.mdx), [Objects](/guides/developer/objects/object-model.mdx), [Events](/guides/developer/sui-101/using-events.mdx), and more through the available interfaces. You can use this data in your application workflows, to analyze network behavior across applications or protocols of interest, or to perform audits on parts or the whole of the network. This document outlines the interfaces that are currently available to access the Sui network data, along with an overview of how that's gradually evolving. Refer to the following definitions for release stages mentioned in this document: diff --git a/docs/content/concepts/gaming.mdx b/docs/content/concepts/gaming.mdx index c3e31a0895993..a77aad45594f7 100644 --- a/docs/content/concepts/gaming.mdx +++ b/docs/content/concepts/gaming.mdx @@ -86,7 +86,7 @@ Sui enables more use cases with the transfer to object feature, which allows an When an object is sent to another object, the metadata of the parent object remain the same, but its `id` has a new object. This feature can enable use cases such as on-chain wallets where a `Wallet` object is used to deposit other objects. Combining this with transfer to object can lead to complex and exciting use cases. -See [Transfer to Object](./transfers/transfer-to-object.mdx) for more details on how to effectively transfer objects to other objects on Sui. +See [Transfer to Object](/guides/developer/objects/transfers/transfer-to-object.mdx) for more details on how to effectively transfer objects to other objects on Sui. #### Deleting assets diff --git a/docs/content/concepts/object-model.mdx b/docs/content/concepts/object-model.mdx deleted file mode 100644 index 6f3206a89ddf3..0000000000000 --- a/docs/content/concepts/object-model.mdx +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Object Model -description: Everything on the Sui blockchain is an object, with metadata, type of ownership, and a referencing scheme. -keywords: [ object model, objects on sui, sui move package, sui move object, move objects, move packages, objects, packages, referring to objects, object metadata, transaction-objects, transaction objects, relating objects and transactions ] ---- - -The basic unit of storage in Sui is the object. In contrast to many other blockchains where storage is centered around accounts containing key-value stores, Sui's storage is centered around objects addressable on-chain by unique IDs. A smart contract is an object (called a Sui Move package), and these smart contracts manipulate objects on the Sui network: - -- Sui Move package: A set of Sui Move bytecode modules. Each module has a name that's unique within the containing package. The combination of the package's on-chain ID and the name of a module uniquely identify the module. When you publish smart contracts to Sui, a package is the unit of publishing. After you publish a package object, it is immutable and can never be changed or removed. A package object can depend on other package objects that were previously published to Sui. -- Sui Move object: Typed data governed by a particular Sui Move module from a Sui Move package. Each object value is a struct with fields that can contain primitive types (such as integers and addresses), other objects, and non-object structs. - -## Object metadata - -Each Sui object has the following metadata: - -- A 32-byte globally unique ID. An object ID is derived from the digest of the transaction that created the object and from a counter encoding the number of IDs generated by the transaction. -- An 8-byte unsigned integer version that monotonically increases with every transaction that modifies it (see [Object and Package Versioning](./versioning.mdx)). -- A 32-byte transaction digest indicating the last transaction that included this object as an output. -- A 32-byte owner field that indicates how this object can be accessed. See [Object Ownership](./object-ownership.mdx) for more information. - -In addition to common metadata, objects have a category-specific, variable-sized contents field containing a [Binary Canonical Serialization (BCS)](https://docs.rs/bcs/latest/bcs/)-encoded payload. - -- Move objects contain their Move type, whether the object can be transferred using `public_transfer`, and its fields, again encoded as BCS. -- Move packages contain the bytecode modules in the package, a table identifying which versions of a package introduced each of its types (the type origin table), and a table mapping each of its transitive dependencies to a specific version of that package to use (the linkage table). - -## Referring to objects - -There are a few different ways to concisely refer to an object without specifying its entire contents and metadata, each with slightly different use cases: -- **ID:** The globally unique ID of the object mentioned above. ID is a stable identifier for the object across time and is useful for querying the current state of an object or describing which object was transferred between two addresses. -- **Versioned ID:** An (ID, version) pair. Versioned ID describes the state of the object at a particular point in the object's history and is useful for asking what the value of the object was at some point in the past or determining how fresh some view of an object is now. -- **Object reference:** An (ID, version, object digest) triple. The object digest is the hash of the object's contents and metadata. An object reference provides an authenticated view of the object at a particular point in the object's history. Transactions require object inputs to be specified via object references to ensure the transaction's sender and a validator processing the transaction agree on the contents and metadata of the object. - -## The transaction-object DAG: Relating objects and transactions - -Transactions take objects as input, read/write/mutate these inputs, and produce mutated or freshly created objects as output. Each object knows the (hash of the) last transaction that produced it as an output. Thus, a natural way to represent the relationship between objects and transactions is a directed acyclic graph (DAG) where: - -- Nodes are transactions. -- Directed edges go from transaction `A` to transaction `B` if an output object of `A` is an input object of `B`. They are labeled by the reference of the object in question (which specifies the exact version of the object created by `A` and used by `B`). - -The root of this DAG is a genesis transaction that takes no inputs and produces the objects that exist in the system's initial state. The DAG can be extended by identifying mutable transaction outputs that have not yet been consumed by any committed transaction and sending a new transaction that takes these outputs (and optionally, immutable transaction outputs) as inputs. - -The set of objects that are available to be taken as input by a transaction are the live objects, and the global state maintained by Sui consists of the totality of such objects. The live objects for a particular Sui address A are all objects owned by A, along with all shared and immutable objects in the system. - -When this DAG contains all committed transactions in the system, it forms a complete (and cryptographically auditable) view of the system's state and history. In addition, you can use the scheme above to construct a DAG of the relevant history for a subset of transactions or objects (for example, the objects owned by a single address). - -## Limits on transactions, objects, and data - -Sui has some limits on transactions and data used in transactions, such as a maximum size and number of objects used. For more information on limits, see [Building against Limits](https://move-book.com/guides/building-against-limits.html) in The Move Book. - -The `ProtocolConfig` struct in the [`sui-protocol-config` crate](https://github.com/MystenLabs/sui/blob/main/crates/sui-protocol-config/src/lib.rs) itemizes these limits. Expand the following code to see the `ProtocolConfig` struct and the comments that explain each parameter. - -
- - -`lib.rs` - - - - - -
- -Select a network from the following tabs to see the currently configured limits and values. - - - -## Related links - - - - - - - \ No newline at end of file diff --git a/docs/content/concepts/object-ownership.mdx b/docs/content/concepts/object-ownership.mdx deleted file mode 100644 index 319c1d1673fd8..0000000000000 --- a/docs/content/concepts/object-ownership.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Object Ownership -description: Every object has an owner field that dictates how you can use it in transactions. Each object is either address-owned, dynamic fields, immutable, shared, or wrapped. -keywords: [ object ownership, how objects are owned, address-owned, address-owned objects, immutable objects, immutable, shared objects, shared, wrapped objects, wrapped ] ---- - -Every object has an owner field that dictates how you can use it in transactions. Objects can have the following types of ownership: - - - - -An address-owned object is owned by a specific 32-byte address that is either an account address (derived from a particular signature scheme) or an object ID. An address-owned object is accessible only to its owner and no others. - - -An immutable object is an object that can't be mutated, transferred, or deleted. Immutable objects have no owner, so anyone can use them. - - -A party object is an object that is transferred using the `0x2::transfer::party_transfer` or `0x2::transfer::public_party_transfer` function and is accessible by the `Party` to which it is transferred. Party objects can be singly owned, but unlike address-owned objects, they are versioned by consensus. - - -A shared object is an object that is shared using the `0x2::transfer::share_object` function and is accessible to everyone. Unlike owned objects, anyone can access shared ones on the network. - - -In Move, you can organize data structures by putting a field of `struct` type in another. - - \ No newline at end of file diff --git a/docs/content/concepts/object-ownership/address-owned.mdx b/docs/content/concepts/object-ownership/address-owned.mdx deleted file mode 100644 index 3e93fe702f28a..0000000000000 --- a/docs/content/concepts/object-ownership/address-owned.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Address-Owned Objects -description: Address-owned objects are owned by a Sui 32-byte address, which can either be an account address or an object ID. Learn how to create and access these objects. -keywords: [ address-owned objects, address-owned, owned objects, objects owned by address, accessing objects, accessing owned objects, address ownership ] ---- - -An address-owned object is owned by a specific 32-byte address that is either an account address (derived from a particular signature scheme) or an object ID. An address-owned object is accessible only to its owner and no others. - -As the owner of the address that holds an address-owned object, you can transfer that object to different addresses. Because only one owner can access an object, transactions must use different owned objects to run in parallel without having to go through consensus. - -## Creating address-owned objects - -Use these [transfer module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move) functions to create address-owned objects: - -```move -public fun transfer(obj: T, recipient: address) -public fun public_transfer(obj: T, recipient: address) -``` - -Use the `sui::transfer::transfer` function if you are defining [custom transfer rules](/concepts/transfers/custom-rules.mdx) for the object. Use the `sui::transfer::public_transfer` function to create an address-owned object if the object has the `store` capability. - -After you declare an object as address-owned, its ownership can change over the life of that object - either by adding it as a dynamic object field, transferring it to a different address, or making it immutable. Importantly though, after you create an object and set its ownership, it cannot be shared. - -## Accessing address-owned objects - -You can access address-owned objects in 2 different ways, depending on whether or not the address owner of the object corresponds to an object ID. - -If the address owner of the object corresponds to an object ID, then you must access and dynamically authenticate it during the execution of the transaction using the mechanisms defined in [Transfer to Object](/concepts/transfers/transfer-to-object.mdx). - -If the address owner of the object is a signature-derived address (an account address), then you can use and access it directly as an owned object during the execution of a transaction that address signs. Other addresses cannot access owned objects in any way in a transaction - even to just read the object. - -## When to use address-owned objects - -Use address-owned objects when you need: -- Single ownership at any time -- Better performance than shared objects -- Avoidance of consensus sequencing - -## Example - -An example of an object that is frequently address-owned is that of a [Coin object](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/coin.move). If address `0xA11CE` had a coin `C` with 100 SUI and wanted to pay address `0xB0B` 100 SUI, `0xA11CE` could do so by transferring `C` to `0xB0B`. - -```move -transfer::public_transfer(C, @0xB0B); -``` - -This results in `C` having a new address owner of `0xB0B`, and `0xB0B` can later use that 100 SUI coin. \ No newline at end of file diff --git a/docs/content/concepts/object-ownership/party.mdx b/docs/content/concepts/object-ownership/party.mdx deleted file mode 100644 index d17c24bcea0ea..0000000000000 --- a/docs/content/concepts/object-ownership/party.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Party Objects -description: Party objects are owned by a specified party at the time of transfer and versioned by consensus. Learn how to create and access these objects. ---- - -A party object is an object that is transferred using the `sui::transfer::party_transfer` or `sui::transfer::public_party_transfer` function. It is accessible to the `Party` to which it is transferred. - - -Party objects combine some properties of address-owned objects and shared objects. Like address-owned objects, they can be owned by a single address. Like shared objects, they are versioned by consensus. Unlike shared objects, they can be transferred to and from other ownership types and wrapped. - -:::info - -Currently, single ownership is the only supported mode for party objects. This topic will be updated if support for multiple owners or more granular permissions is added. - -::: - -## When to use party objects - -Use party objects when you want an object to be versioned by consensus, such as for operational convenience. If an object is only used with other party or shared objects, converting it to a party object has no additional performance cost. - -Party objects can be used by multiple inflight transactions at the same time. This contrasts with address-owned objects, which only allow a single inflight transaction. Many applications can benefit from the ability to pipeline multiple transactions on the same party object. - -:::info - -`Coin`s can be party objects, including `Coin`. However, you cannot use a party object `Coin` for gas payment. To use a party object `Coin` for gas, you must first transfer it back to address-owned. - -::: - -## Creating party objects - -Use one of the following functions (defined in the [transfer module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move)) to create party objects: - -```move -public fun party_transfer(obj: T, party: sui::party::Party) -public fun public_party_transfer(obj: T, party: sui::party::Party) -``` - -Use the `sui::transfer::party_transfer` function if you are defining a [custom transfer policy](../transfers/custom-rules.mdx) for the object. Use the `sui::transfer::public_party_transfer` function if the object has the `store` capability. - -A party object's ownership can change over its lifetime. For example, by adding it as a dynamic object field, transferring it to a different address or ownership type, or making it immutable. One exception: after you create an object and set its ownership, you cannot later share it. - -## Accessing party objects - -You can specify party objects as input to a transaction in the same way as shared objects. Sui validators ensure that the sender of the transaction can access the object. The validator might abort a transaction at execution time if the owner of an input party object has changed due to an earlier, conflicting transaction. - -Party objects whose owning address corresponds to an object ID are not supported for access through the [transfer to object](/concepts/transfers/transfer-to-object.mdx) mechanism. \ No newline at end of file diff --git a/docs/content/concepts/object-ownership/wrapped.mdx b/docs/content/concepts/object-ownership/wrapped.mdx deleted file mode 100644 index 251d861fa8333..0000000000000 --- a/docs/content/concepts/object-ownership/wrapped.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: Wrapped Objects -description: Wrapped objects are object data structures nested inside of another object data structure. Objects can be wrapped directly, through `Option`, or through `vector` fields. -keywords: [ wrapped objects, wrapping objects, unwrapping objects, direct wrapping, wrap directly, wrap object directly, wrap object option, option wrap, vector wrap, wrap object vector ] ---- - -You can nest structs to organize data structures in Move. This example shows a basic wrapper pattern: - -```move -public struct Foo has key { - id: UID, - bar: Bar, -} - -public struct Bar has store { - value: u64, -} -``` - -To embed a struct type in a Sui object struct (with a `key` ability), the struct type must have the `store` ability. - -In the preceding example, `Bar` is a normal struct, but it is not a Sui object since it doesn't have the `key` ability. - -The following code turns `Bar` into an object, which you can still wrap in `Foo`: - -```move -public struct Bar has key, store { - id: UID, - value: u64, -} -``` - -Now `Bar` is also a Sui object type. If you put a Sui object of type `Bar` into a Sui object of type `Foo`, the object type `Foo` wraps the object type `Bar`. The object type `Foo` is the wrapper or wrapping object. - -There are some interesting consequences of wrapping a Sui object into another. When an object is wrapped, the object no longer exists independently on-chain. You can no longer look up the object by its ID. The object becomes part of the data of the object that wraps it. Most importantly, you can no longer pass the wrapped object as an argument in any way in Sui Move calls. The only access point is through the wrapping object. - -It is not possible to create circular wrapping behavior, where A wraps B, B wraps C, and C also wraps A. - -At some point, you can then take out the wrapped object and transfer it to an address, modify it, delete it, or freeze it. This is called **unwrapping**. When an object is **unwrapped**, it becomes an independent object again, and can be accessed directly on-chain. There is also an important property about wrapping and unwrapping: the object's ID stays the same across wrapping and unwrapping. - -There are a few ways to wrap a Sui object into another Sui object, and their use cases are typically different. This section describes three different ways to wrap a Sui object with typical use cases. - -### Direct wrapping - -**Direct wrapping** occurs when a Sui object type contains another Sui object type as a direct field. The most important properties achieved through direct wrapping are: - -- The wrapped object cannot be extracted without destroying the wrapper -- Provides strong encapsulation guarantees -- Ideal for implementing object locking patterns -- Requires explicit contract calls to modify access - -The following example implementation of a trusted swap demonstrates how to use direct wrapping. Assume there is an NFT-style `Object` type that has `scarcity` and `style`. In this example, `scarcity` determines how rare the object is (presumably the more scarce the higher its market value), and `style` determines the object content/type or how it's rendered. If you own some of these objects and want to trade your objects with others, you want to make sure it's a fair trade. You are willing to trade an object only with another one that has identical `scarcity`, but want a different `style` (so that you can collect more styles). - -First, define such an object type: - -```move -public struct Object has key, store { - id: UID, - scarcity: u8, - style: u8, -} -``` - -In a real application, you might make sure that there is a limited supply of the objects, and there is a mechanism to mint them to a list of owners. For simplicity and demonstration purposes, this example simplifies creation: - -```move -public fun new(scarcity: u8, style: u8, ctx: &mut TxContext): Object { - Object { id: object::new(ctx), scarcity, style } -} - -``` - -You can also enable a swap or trade between your object and others' objects. For example, define a function that takes two objects from two addresses and swaps their ownership. This doesn't work in Sui. Only object owners can send a transaction to mutate the object. One person cannot send a transaction that would swap their own object with someone else's object. - -Another common solution is to send your object to a pool, such as an NFT marketplace or a staking pool, and perform the swap in the pool, either right away, or later when there is demand. Transactions using only owned objects are faster and less expensive (in terms of gas) than using shared objects, because they do not require consensus in Sui. - -To swap objects, the same address must own both objects. Anyone who wants to swap their object can send their objects to the third party, such as a site that offers swapping services, and the third party helps perform the swap and send the objects to the appropriate owner. To ensure that you retain custody of your objects (such as coins and NFTs) and not give full custody to the third party, use direct wrapping. To define a wrapper object type: - -```move -public struct SwapRequest has key { - id: UID, - owner: address, - object: Object, - fee: Balance, -} -``` - -`SwapRequest` defines a Sui object type, wraps the `object` to swap, and tracks the original `owner` of the object. You might need to also pay the third party some fee for this swap. To define an interface to request a swap by someone who owns an `Object`: - -```move -public fun request_swap( - object: Object, - fee: Coin, - service: address, - ctx: &mut TxContext, -) { - assert!(coin::value(&fee) >= MIN_FEE, EFeeTooLow); - - let request = SwapRequest { - id: object::new(ctx), - owner: ctx.sender(), - object, - fee: coin::into_balance(fee), - }; - - transfer::transfer(request, service) -} - -``` - -In the preceding function, you must pass the object by value so that it's fully consumed and wrapped into `SwapRequest` to request swapping an `object`. The example also provides a fee (in the type of `Coin`) and checks that the fee is sufficient. The example turns `Coin` into `Balance` when it's put into the `wrapper` object. This is because `Coin` is a Sui object type and used only to pass around as Sui objects (such as transaction inputs or objects sent to addresses). For coin balances that need to be embedded in other structs, use `Balance` instead because to avoid the overhead of carrying around an unnecessary `UID` field. - -The wrapper object is then sent to the service operator, with the address specified in the call as `service`. - -The function interface for the function that the service operator can call to perform a swap between two objects sent from two addresses resembles: - -```move -public fun execute_swap(s1: SwapRequest, s2: SwapRequest): Balance; -``` - -Where `s1` and `s2` are two wrapped objects that were sent from different object owners to the service operator. Both wrapped objects are passed by value because they eventually need to be [unpacked](https://move-book.com/move-basics/struct.html#destructing-structures). - -First, unpack the two object to obtain the inner fields: - -```move -let SwapRequest {id: id1, owner: owner1, object: o1, fee: fee1} = s1; -let SwapRequest {id: id2, owner: owner2, object: o2, fee: fee2} = s2; -``` - -Then, check that the swap is legitimate (the two objects have identical scarcity and different styles): - -```move -assert!(o1.scarcity == o2.scarcity, EBadSwap); -assert!(o1.style != o2.style, EBadSwap); -``` - -To perform the actual swap: - -```move -transfer::transfer(o1, owner2); -transfer::transfer(o2, owner1); -``` - -The preceding code sends `o1` to the original owner of `o2`, and sends `o2` to the original owner of `o1`. The service can then delete the wrapping `SwapRequest` objects: - -```move -id1.delete(); -id2.delete(); -``` - -Finally, the service merges together the `fee1` and `fee2`, and returns it. The service provider can then turn it into a coin, or merge it into some larger pool where it collects all fees: - -```move -fee1.join(fee2); -``` - -After this call, the 2 objects are swapped and the service provider takes the service fee. - -Because the contract defined only one way to deal with `SwapRequest` (`execute_swap`), there is no other way the service operator can interact with `SwapRequest` despite its ownership. - -Find the full source code in the [trusted_swap](https://github.com/MystenLabs/sui/blob/main/examples/move/trusted_swap) example. - - -### Wrapping through `Option` - -When Sui object type `Bar` is directly wrapped into `Foo`, there is not much flexibility: a `Foo` object must have a `Bar` object in it, and to take out the `Bar` object you must destroy the `Foo` object. However, for more flexibility, the wrapping type might not always have the wrapped object in it, and the wrapped object might be replaced with a different object at some point. - - -To demonstrate this use case, design a simple game character: A warrior with a sword and shield. A warrior might have a sword and shield, or might not have either. The warrior should be able to add a sword and shield, and replace the current ones at any time. To design this, define a `SimpleWarrior` type: - -```move -public struct SimpleWarrior has key { - id: UID, - sword: Option, - shield: Option, -} -``` - -Each `SimpleWarrior` type has an optional `sword` and `shield` wrapped in it, defined as: - -```move -public struct Sword has key, store { - id: UID, - strength: u8, -} - -public struct Shield has key, store { - id: UID, - armor: u8, -} -``` - -When you create a new warrior, set the `sword` and `shield` to `none` to indicate there is no equipment yet: - -```move -public fun create_warrior(ctx: &mut TxContext) { - let warrior = SimpleWarrior { - id: object::new(ctx), - sword: option::none(), - shield: option::none(), - }; - transfer::transfer(warrior, ctx.sender()) -} -``` - -You can then define functions to equip new swords or new shields: - -```move -public fun equip_sword(warrior: &mut SimpleWarrior, sword: Sword, ctx: &mut TxContext) { - if (warrior.sword.is_some()) { - let old_sword = warrior.sword.extract(); - transfer::transfer(old_sword, ctx.sender()); - }; - warrior.sword.fill(sword); -} -``` - -The function in the preceding example passes a `warrior` as a mutable reference of `SimpleWarrior`, and passes a `sword` by value to wrap it into the `warrior`. - -Note that because `Sword` is a Sui object type without `drop` ability, if the warrior already has a sword equipped, the warrior can't drop that sword. If you call `option::fill` without first checking and taking out the existing sword, an error occurs. In `equip_sword`, first check whether there is already a sword equipped. If so, remove it out and send it back to the sender. To a player, this returns an equipped sword to their inventory when they equip the new sword. - -Find the source code in the [simple_warrior](https://github.com/MystenLabs/sui/tree/main/examples/move/simple_warrior) example. - -To view a more complex example, see [hero](https://github.com/MystenLabs/sui/tree/main/examples/move/hero). - -### Wrapping through `vector` - -The concept of wrapping objects in a vector field of another Sui object is very similar to wrapping through `Option`: an object can contain 0, 1, or many of the wrapped objects of the same type. -Wrapping through vector resembles: - -```move -public struct Pet has key, store { - id: UID, - cuteness: u64, -} - -public struct Farm has key { - id: UID, - pets: vector, -} -``` - -The preceding example wraps a vector of `Pet` in `Farm`, and can be accessed only through the `Farm` object. diff --git a/docs/content/concepts/sui-architecture/consensus.mdx b/docs/content/concepts/sui-architecture/consensus.mdx index da68e93e0f5ba..6294f4a2ea861 100644 --- a/docs/content/concepts/sui-architecture/consensus.mdx +++ b/docs/content/concepts/sui-architecture/consensus.mdx @@ -30,7 +30,7 @@ A validator can handle 2 types of write requests: transactions and certificates. ### Transactions -When a validator receives a transaction from a client, it first performs transaction validity checks to verify the sender's signature. If the checks pass, the validator locks all [owned-objects](/concepts/object-ownership/address-owned.mdx) and signs the transaction bytes, then returns the signature to the client. The client repeats this process with multiple validators until it has collected signatures on its transaction from a quorum, thereby forming a certificate. +When a validator receives a transaction from a client, it first performs transaction validity checks to verify the sender's signature. If the checks pass, the validator locks all [owned-objects](/guides/developer/objects/object-ownership/address-owned.mdx) and signs the transaction bytes, then returns the signature to the client. The client repeats this process with multiple validators until it has collected signatures on its transaction from a quorum, thereby forming a certificate. The process of collecting validator signatures on a transaction into a certificate and the process of submitting certificates can be performed in parallel. The client can simultaneously multicast transactions and certificates to an arbitrary number of validators. Alternatively, a client can outsource either or both of these tasks to a third-party service provider. This provider must be trusted for liveness, as it can refuse to form a certificate, but not for safety, as it cannot change the effects of the transaction and does not need the user's secret key. diff --git a/docs/content/concepts/sui-architecture/epochs.mdx b/docs/content/concepts/sui-architecture/epochs.mdx index dbd3ad3c96d04..46b6a3ed046ab 100644 --- a/docs/content/concepts/sui-architecture/epochs.mdx +++ b/docs/content/concepts/sui-architecture/epochs.mdx @@ -10,7 +10,7 @@ Epoch values are included in the metadata of all [transactions](/concepts/transa ## Equivocation -On Sui, [object versioning](/concepts/versioning.mdx) ensures that each [object](/concepts/object-model.mdx) is referenced by a unique (`ObjectId`, `SequenceNumber`) pair, where `SequenceNumber` refers to the object's version. Only a single transaction can modify an object at a specific version. After a transaction modifies an object, the version is incremented. Only the latest version can be used in subsequent transactions. Object versioning tracks the state of objects across transactions and epochs. +On Sui, [object versioning](/guides/developer/objects/versioning.mdx) ensures that each [object](/guides/developer/objects/object-model.mdx) is referenced by a unique (`ObjectId`, `SequenceNumber`) pair, where `SequenceNumber` refers to the object's version. Only a single transaction can modify an object at a specific version. After a transaction modifies an object, the version is incremented. Only the latest version can be used in subsequent transactions. Object versioning tracks the state of objects across transactions and epochs. _Equivocation_ occurs when an owned object pair (`ObjectId`, `SequenceNumber`) is used concurrently in multiple non-finalized transactions. Any object used in multiple transactions can become a source of equivocation if not managed correctly. diff --git a/docs/content/concepts/sui-architecture/sui-security.mdx b/docs/content/concepts/sui-architecture/sui-security.mdx index 9709995a22635..4e23dd6c37c52 100644 --- a/docs/content/concepts/sui-architecture/sui-security.mdx +++ b/docs/content/concepts/sui-architecture/sui-security.mdx @@ -4,13 +4,13 @@ description: Assets on Sui, including coins and tokens, are types of objects, an keywords: [ security, security guarantees, asset security, asset owners, object security, security architecture ] --- -Sui is designed to provide high security guarantees to asset owners. Assets on Sui, including coins and tokens, are types of [_objects_](/concepts/object-model.mdx), and can only be used by their owners unless otherwise defined according to predefined logic in a smart contract. Every smart contract can be audited, and the network can process them correctly even if some [validators](/concepts/sui-architecture/consensus.mdx) do not follow the protocol correctly. This is known as _fault tolerance_. +Sui is designed to provide high security guarantees to asset owners. Assets on Sui, including coins and tokens, are types of [_objects_](/guides/developer/objects/object-model.mdx), and can only be used by their owners unless otherwise defined according to predefined logic in a smart contract. Every smart contract can be audited, and the network can process them correctly even if some [validators](/concepts/sui-architecture/consensus.mdx) do not follow the protocol correctly. This is known as _fault tolerance_. Sui's security features guarantee certain properties for objects: 1. Only the owner of an object can authorize a [transaction](/concepts/transactions.mdx) that operates on the object. Authorization is performed through the use of a private signature key that is known only to the object owner. -1. Everyone can operate on [shared](/concepts/object-ownership/shared.mdx) objects or [immutable](/concepts/object-ownership/immutable.mdx) objects, but additional access control logic can be implemented by a smart contract. +1. Everyone can operate on [shared](/guides/developer/objects/object-ownership/shared.mdx) objects or [immutable](/guides/developer/objects/object-ownership/immutable.mdx) objects, but additional access control logic can be implemented by a smart contract. 1. Transactions operate on objects according to predefined rules set by the smart contract creator that defined the object type. diff --git a/docs/content/concepts/sui-for-ethereum.mdx b/docs/content/concepts/sui-for-ethereum.mdx index c934ff569014e..42260a32ebfe9 100644 --- a/docs/content/concepts/sui-for-ethereum.mdx +++ b/docs/content/concepts/sui-for-ethereum.mdx @@ -32,7 +32,7 @@ There are some nuances to object ownership, but key types include: - **Address-owned objects:** These objects are owned by a single address. You can transfer or receive these objects without interacting with a smart contract. For example, currency, NFTs, or tokens gating access to certain functions. - **Shared objects:** Publicly accessible objects that anyone can use. Mutating the data stored in these objects typically involves defining rules in the smart contract. -For all types of ownership on Sui, see [Object Ownership](/concepts/object-ownership.mdx). +For all types of ownership on Sui, see [Object Ownership](/guides/developer/objects/object-ownership.mdx). ## Access control @@ -112,6 +112,6 @@ See [Programmable Transaction Blocks](/concepts/transactions/prog-txn-blocks.mdx ## Related links - + diff --git a/docs/content/concepts/sui-move-concepts.mdx b/docs/content/concepts/sui-move-concepts.mdx index 41c574ec00664..bd01d151c9f91 100644 --- a/docs/content/concepts/sui-move-concepts.mdx +++ b/docs/content/concepts/sui-move-concepts.mdx @@ -6,7 +6,7 @@ keywords: [ move on sui, move language, what is move, move objects, move smart c -You can use Move to define, create, and manage programmable Sui objects representing user-level assets. Sui's object system is implemented by adding new functionality to Move while also imposing additional restrictions. See [Object Model](./object-model.mdx) for more details. +You can use Move to define, create, and manage programmable Sui objects representing user-level assets. Sui's object system is implemented by adding new functionality to Move while also imposing additional restrictions. See [Object Model](/guides/developer/objects/object-model) for more details. ## Move on Sui diff --git a/docs/content/concepts/sui-move-concepts/derived-objects.mdx b/docs/content/concepts/sui-move-concepts/derived-objects.mdx index cf994541aa11b..06ff3ea08f9a1 100644 --- a/docs/content/concepts/sui-move-concepts/derived-objects.mdx +++ b/docs/content/concepts/sui-move-concepts/derived-objects.mdx @@ -12,7 +12,7 @@ To claim an ID using the `derived_object` module, pass the parent object and key :::info -Parent objects can be [shared, owned, party, or wrapped](../object-ownership.mdx). +Parent objects can be [shared, owned, party, or wrapped](/guides/developer/objects/object-ownership). ::: @@ -216,8 +216,8 @@ public fun receive_from_vault( ## Related links - - + + diff --git a/docs/content/concepts/sui-move-concepts/packages/upgrade.mdx b/docs/content/concepts/sui-move-concepts/packages/upgrade.mdx index c6337f3a67faa..1cade62c726de 100644 --- a/docs/content/concepts/sui-move-concepts/packages/upgrade.mdx +++ b/docs/content/concepts/sui-move-concepts/packages/upgrade.mdx @@ -4,7 +4,7 @@ description: Sui provides a method of upgrading your packages while still retain keywords: [ upgrading packages, upgrade considerations, how to upgrade a package, how to upgrade, update package, update, how to update ] --- -Sui smart contracts are immutable package objects consisting of a collection of Move modules. Because the packages are immutable, transactions can safely access smart contracts without full consensus (fastpath transactions). If someone could change these packages, they would become [shared objects](/concepts/object-ownership/shared.mdx#shared), which would require full consensus before completing a transaction. +Sui smart contracts are immutable package objects consisting of a collection of Move modules. Because the packages are immutable, transactions can safely access smart contracts without full consensus (fastpath transactions). If someone could change these packages, they would become [shared objects](/guides/developer/objects/object-ownership/shared.mdx#shared), which would require full consensus before completing a transaction. The inability to change package objects, however, becomes a problem when considering the iterative nature of code development. Builders require the ability to update their code and pull changes from other developers while still being able to reap the benefits of fastpath transactions. Fortunately, the Sui network provides a method of upgrading your packages while still retaining their immutable properties. diff --git a/docs/content/concepts/tokenomics/staking-unstaking.mdx b/docs/content/concepts/tokenomics/staking-unstaking.mdx index 1d6608bb54666..25c7f96c094ca 100644 --- a/docs/content/concepts/tokenomics/staking-unstaking.mdx +++ b/docs/content/concepts/tokenomics/staking-unstaking.mdx @@ -8,7 +8,7 @@ Sui uses a delegated proof of stake (DPoS) [consensus mechanism](/concepts/sui-a ## Staking -Stake SUI tokens by sending a transaction to the network that calls the staking function implemented as part of the system Move package. This transaction wraps the SUI tokens in a self-custodial stake [object](/concepts/object-model.mdx). This stake object contains information including the validator staking pool ID and the activation [epoch](/concepts/sui-architecture/epochs.mdx) of the stake. With the introduction of [SIP-6](https://blog.sui.io/liquid-staking-coming-sui/), you can participate in liquid staking protocols using your staked objects. +Stake SUI tokens by sending a transaction to the network that calls the staking function implemented as part of the system Move package. This transaction wraps the SUI tokens in a self-custodial stake [object](/guides/developer/objects/object-model.mdx). This stake object contains information including the validator staking pool ID and the activation [epoch](/concepts/sui-architecture/epochs.mdx) of the stake. With the introduction of [SIP-6](https://blog.sui.io/liquid-staking-coming-sui/), you can participate in liquid staking protocols using your staked objects. [Sui-compatible crypto wallets](https://slush.app/) typically have functionality to initiate staking and unstaking from your Sui address. See the respective documentation for these tools to begin staking your SUI. diff --git a/docs/content/concepts/transactions/prog-txn-blocks.mdx b/docs/content/concepts/transactions/prog-txn-blocks.mdx index 0315d3daa78ca..990cd826d8da4 100644 --- a/docs/content/concepts/transactions/prog-txn-blocks.mdx +++ b/docs/content/concepts/transactions/prog-txn-blocks.mdx @@ -53,7 +53,7 @@ The inputs and results can be seen as populating an array of values. For inputs, Input arguments to a PTB are broadly categorized as either objects or pure values. The direct implementation of these arguments is often obscured by transaction builders or SDKs. Each `Input` is either an object, `Input::Object(ObjectArg)`, which contains the necessary metadata to specify the object being used, or a pure value, `Input::Pure(PureArg)`, which contains the bytes of the value. -For object inputs, the metadata needed differs depending on the type of [ownership of the object](/concepts/object-ownership.mdx). The data for the `ObjectArg` enum follows: +For object inputs, the metadata needed differs depending on the type of [ownership of the object](/guides/developer/objects/object-ownership.mdx). The data for the `ObjectArg` enum follows: - If the object is owned by an address or it is immutable, then use `ObjectArg::ImmOrOwnedObject(ObjectID, SequenceNumber, ObjectDigest)`. The triple respectively specifies the object's ID, its sequence or version number, and the digest of the object's data. diff --git a/docs/content/concepts/transactions/transaction-lifecycle.mdx b/docs/content/concepts/transactions/transaction-lifecycle.mdx index d314fcbea2966..a307a66f29316 100644 --- a/docs/content/concepts/transactions/transaction-lifecycle.mdx +++ b/docs/content/concepts/transactions/transaction-lifecycle.mdx @@ -10,7 +10,7 @@ At a high level, the following diagram outlines the life cycle of a transaction The following steps align with those in the diagram: -1. A user with a private key creates and signs a user transaction to either mutate objects they own, [shared objects](/concepts/object-ownership/shared.mdx), or create new objects. +1. A user with a private key creates and signs a user transaction to either mutate objects they own, [shared objects](/guides/developer/objects/object-ownership/shared.mdx), or create new objects. 1. Sui sends the transaction to each [validator](/guides/operator/validator-index.mdx), often through a [full node](/guides/operator/sui-full-node.mdx). Validators perform a series of validity and safety checks, sign it, and return the signed transaction to the client. @@ -110,7 +110,7 @@ The network round trip of sending a transaction and receiving a validator signat After the supermajority of validators have executed the transaction and an effects certificate exists, the effects of the transaction (transfers, newly minted objects, and so on) have been implemented. At this point, the network can process transactions that depend on those effects. -For transactions that involve owned objects only, this happens before consensus in under 1/2 a second. If a transaction includes shared objects, it happens shortly after consensus, which can take a few seconds. At this point, the transaction reached settlement finality because now you can process more transactions on the same input objects. See [Object Ownership](/concepts/object-ownership.mdx) for more information. +For transactions that involve owned objects only, this happens before consensus in under 1/2 a second. If a transaction includes shared objects, it happens shortly after consensus, which can take a few seconds. At this point, the transaction reached settlement finality because now you can process more transactions on the same input objects. See [Object Ownership](/guides/developer/objects/object-ownership.mdx) for more information. ## Epoch change diff --git a/docs/content/concepts/transfers.mdx b/docs/content/concepts/transfers.mdx deleted file mode 100644 index 045f6fd78629b..0000000000000 --- a/docs/content/concepts/transfers.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Transfers -description: Everything on Sui is an object. To use objects, they must be transferred between owners, which can be an address or another object. -keywords: [ object transfers, transferring objects, custom transfer rules, transfer to an object, transfer objects ] ---- - -Everything on Sui is an object and your smart contracts are inevitably going to need to move those objects around the network, transferring them from one owner to another. The topics in this section explore the options you have on Sui around transferring objects on the network. - - - - - - - \ No newline at end of file diff --git a/docs/content/concepts/transfers/custom-rules.mdx b/docs/content/concepts/transfers/custom-rules.mdx deleted file mode 100644 index 4a50bafa557ae..0000000000000 --- a/docs/content/concepts/transfers/custom-rules.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Custom Transfer Rules -description: Custom transfer rules enable you to define a set of rules that must be met before Sui considers a transfer operation valid. ---- - -Every Sui object must have the `key` ability. The `store` ability, on the other hand, is an optional ability you can add to Sui objects. Objects with the `store` ability: - -- are transferable by anyone using the `transfer::public_transfer` function; and -- are able to be wrapped in other objects. - -Importantly for custom transfer rules, if the Sui object `Object` does not have the `store` ability, you cannot call the `sui::transfer::public_transfer` function to transfer it. The Move module that defines `Object` is the only entity that can transfer objects of that type using the `sui::transfer::transfer` function. Consequently, the module that defines the object `Object` can define a custom transfer function for `Object` that can take any number of arguments, and enforce any restrictions desired for performing a transfer operation (for example, a fee must be paid in order to transfer the object). - -## The store ability and transfer rules - -Custom transfer rules for objects enable you to define the transfer conditions that must be met for a valid transfer operation. You should be intentional about adding the `store` ability to an object because you are providing unrestricted access to that object without having to go through the module that defines it. After you enable public transfers on an object, there is no way of re-enabling custom transfer rules or any type of restrictions regarding the transfer of the object. - -## Example - -This example creates an object type `Object` that is transferrable only if the `unlocked` flag inside of it is set to `true`: - -```move -public struct Object has key { - id: UID, - // An `Object` object can only be transferred if this field is `true` - unlocked: bool, -} -``` - -Within the same module that defines the object `Object`, you can then define a custom transfer rule `transfer_unlocked` for `Object` that takes the object to transfer and the address to transfer it to, and verifies that the object is unlocked before transferring it to the specified address. - -```move -module examples::custom_transfer; - -// Error code for trying to transfer a locked object -const EObjectLocked: u64 = 0; - -public struct Object has key { - id: UID, - // An `Object` object can only be transferred if this field is `true` - unlocked: bool, -} - -// Check that `Object` is unlocked before transferring it -public fun transfer_unlocked(object: Object, to: address) { - assert!(object.unlocked, EObjectLocked); - transfer::transfer(object, to) -} -``` - -With custom transfer rules, you can define multiple different transfer rules for the same object. Each of these rules might have different restrictions that execution of the transaction can dynamically enforce. So, if you wanted to allow only locked objects to be transferred to a specific address you could add the following function to the previous module: - -```move -const EObjectNotLocked: u64 = 1; -const HOME_ADDRESS = @0xCAFE; - -public fun transfer_locked(object: Object) { - assert!(!object.unlocked, EObjectNotLocked); - transfer::transfer(object, HOME_ADDRESS) -} -``` - -With these rules in place, there are two different custom transfer rules for any object `Object`: either it's unlocked and anyone can transfer it, or it's locked, and it can only be transferred to `0xCAFE`. Importantly, these two ways of transferring `Object` are the only ways of transferring any object of type `Object`. In particular, because `Object` does not have the `store` ability, you cannot transfer it using the `sui::transfer::public_transfer` function. In fact, the only ways of transferring `Object` are using `examples::custom_transfer::transfer_unlocked` and `examples::custom_transfer::transfer_locked`. diff --git a/docs/content/concepts/versioning.mdx b/docs/content/concepts/versioning.mdx deleted file mode 100644 index 78eaa0fa7edad..0000000000000 --- a/docs/content/concepts/versioning.mdx +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Object and Package Versioning -description: Versioning provides the ability to upgrade packages and objects on the Sui network. -keywords: [ object versioning, object versions, versioning objects, package versioning, package versions, versioning packages ] ---- - -You reference every object stored on chain by an ID and version. When a transaction modifies an object, it writes the new contents to an on-chain reference with the same ID but a later version. This means that a single object (with ID `I`) might appear in multiple entries in the distributed store: - -``` -(I, v0) => ... -(I, v1) => ... # v0 < v1 -(I, v2) => ... # v1 < v2 -``` - -Despite appearing multiple times in the store, only one version of the object is available to transactions - the latest version (v2 in the previous example). Moreover, only one transaction can modify the object at that version to create a new version, guaranteeing a linear history (`v1` was created in a state where `I` was at `v0`, and `v2` was created in a state where `I` was at `v1`). - -Versions are strictly increasing and (ID, version) pairs are never re-used. This structure allows node operators to prune their stores of old object versions that are now inaccessible, if they choose. This is not a requirement, though, as node operators might keep prior object versions around to serve requests for an object's history, either from other nodes that are catching up, or from RPC requests. - -## Move objects - -Sui uses [Lamport timestamps](https://en.wikipedia.org/wiki/Lamport_timestamp) in its versioning algorithm for objects. The use of Lamport timestamps guarantees that versions never get re-used as the new version for objects touched by a transaction is one greater than the latest version among all input objects to the transaction. For example, a transaction transferring an object `O` at version 5 using a gas object `G` at version 3 updates both `O` and `G` versions to `1 + max(5, 3) = 6` (version 6). - -The following sections detail the relevance of Lamport versions for maintaining the "no (ID, version) re-use" invariant or for accessing an object as a transaction input changes depending on that object's ownership. - -### Address-owned objects - -You must reference address-owned transaction inputs at a specific ID and version. When a validator signs a transaction with an owned object input at a specific version, that version of the object is locked to that transaction. Validators reject requests to sign other transactions that require the same input (same ID and version). - -If `F + 1` validators sign one transaction that takes an object as input, and a different `F + 1` validators sign a different transaction that takes the same object as input, that object (and all the other inputs to both transactions) is equivocated, meaning they cannot be used for any further transactions in that epoch. This is because neither transaction can form a quorum without relying on a signature from a validator that has already committed the object to a different transaction, which it cannot get. All locks are reset at the end of the epoch, which frees the objects again. - -:::info - -Only an object's owner can equivocate it, but this is not a desirable thing to do. You can avoid equivocation by carefully managing the versions of address-owned input objects: never attempt to execute two different transactions that use the same object. If you don't get a definite success or failure response from the network for a transaction, assume that the transaction might have gone through, and do not re-use any of its objects for different transactions. - -::: - -### Immutable objects - -Like address-owned objects, you reference immutable objects at an ID and version, but they do not need to be locked as their contents and versions do not change. Their version is relevant because they could have started life as an address-owned object before being frozen. The given version identifies the point at which they became immutable. - -### Shared objects - -Specifying a shared transaction input is slightly more complex. You reference it by its ID, the version it was shared at, and a flag indicating whether it is accessed mutably. You don't specify the precise version the transaction accesses because consensus decides that during transaction scheduling. When scheduling multiple transactions that touch the same shared object, validators agree the order of those transactions, and pick each transaction's input versions for the shared object accordingly (one transaction's output version becomes the next transaction's input version, and so on). - -Shared transaction inputs that you reference immutably participate in scheduling, but don't modify the object or increment its version. - -### Wrapped objects - -You can't access wrapped objects by their ID in the object store, you must access them by the object that wraps them. Consider the following example that creates a `make_wrapped` function with an `Inner` object, wrapped in an `Outer` object, which is returned to the transaction sender. - -```move -module example::wrapped { - use sui::object::{Self, UID}; - use sui::transfer; - use sui::tx_context::{Self, TxContext}; - - struct Inner has key, store { - id: UID, - x: u64, - } - - struct Outer has key { - id: UID, - inner: Inner, - } - - entry fun make_wrapped(ctx: &mut TxContext) { - let inner = Inner { - id: object::new(ctx), - x: 42, - }; - - let outer = Outer { - id: object::new(ctx), - inner, - }; - - transfer::transfer(outer, tx_context::sender(ctx)); - } -} -``` - -The owner of `Outer` in this example must specify it as the transaction input and then access its `inner` field to read the instance of `Inner`. Validators refuse to sign transactions that directly specify wrapped objects (like the `inner` of an `Outer`) as inputs. As a result, you don't need to specify a wrapped object's version in a transaction that reads that object. - -Wrapped objects can eventually become unwrapped, meaning that they are once again accessible at their ID: - -```move -module example::wrapped { - // ... - - entry fun unwrap(outer: Outer, ctx: &TxContext) { - let Outer { id, inner } = outer; - object::delete(id); - transfer::transfer(inner, tx_context::sender(ctx)); - } -} -``` - -The `unwrap` function in the previous code takes an instance of `Outer`, destroys it, and sends the `Inner` back to the sender. After calling this function, the previous owner of `Outer` can access `Inner` directly by its ID because it is now unwrapped. Wrapping and unwrapping of an object can happen multiple times across its lifespan, and the object retains its ID across all those events. - -The Lamport timestamp-based versioning scheme ensures that the version that an object is unwrapped at is always greater than the version it was wrapped at, to prevent version re-use. - -- After a transaction, `W`, where object `I` is wrapped by object `O`, the `O` version is greater than or equal to the `I` version. This means one of the following conditions is true: - - `I` is an input so has a strictly lower version. - - `I` is new and has an equal version. -- After a later transaction unwrapping `I` out of `O`, the following must be true: - - The `O` input version is greater than or equal to its version after `W` because it is a later transaction, so the version can only have increased. - - The `I` version in the output must be strictly greater than the `O` input version. - -This leads to the following chain of inequalities for I's version before wrapping: - -- less than or equal to O's version after wrapping -- less than or equal to O's version before unwrapping -- less than I's version after unwrapping - -So the `I` version before wrapping is less than the `I` version after unwrapping. - -### Dynamic fields - -From a versioning perspective, values held in dynamic fields behave like wrapped objects: - -- They are only accessible via the field's parent object, not as direct transaction inputs. -- Based on the previous point, you do not need to supply their IDs or versions with the transaction inputs. -- Lamport timestamp-based versioning makes sure that when a field contains an object and a transaction removes that field, its value becomes accessible by its ID and the value's version has been incremented to a previously unused version. - -:::info - -One distinction dynamic fields have to wrapped objects is that if a transaction modifies a dynamic object field, its version is incremented in that transaction, where a wrapped object's version would not be. - -::: - -Adding a new dynamic field to a parent object also creates a `Field` object, responsible for associating the field name and value with that parent. Unlike other newly created objects, the ID for the resulting instance of `Field` is not created using `sui::object::new`. Instead, it is computed as a hash of the parent object ID and the type and value of the field name, so that you can use it to look-up the `Field` via its parent and name. - -When you remove a field, Sui deletes its associated `Field`, and if you add a new field with the same name, Sui creates a new instance with the same ID. Versioning using Lamport timestamps, coupled with dynamic fields being only accessible through their parent object, ensures that (ID, version) pairs are not reused in the process: - -- The transaction that deletes the original field increments the parent's version to be greater than the deleted field's version. -- The transaction that creates the new version of the same field creates the field with a version that is greater than the parent's version. - -So the version of the new `Field` instance is greater than the version of the deleted `Field`. - -## Packages - -Move packages are also versioned and stored on chain, but follow a different versioning scheme to objects because they are immutable from their inception. This means that you refer to package transaction inputs (for example, the package that a function is from for a Move call transaction) by just their ID, and are always loaded at their latest version. - -### User packages - -Every time you publish or upgrade a package, Sui generates a new ID. A newly published package has its version set to 1, whereas an upgraded package's version is one greater than the package it is upgrading. Unlike objects, older versions of a package remain accessible even after being upgraded. For example, imagine a package `P` that is published and upgraded twice. It might be represented in the store as: - -``` -(0x17fb7f87e48622257725f584949beac81539a3f4ff864317ad90357c37d82605, 1) => P v1 -(0x260f6eeb866c61ab5659f4a89bc0704dd4c51a573c4f4627e40c5bb93d4d500e, 2) => P v2 -(0xd24cc3ec3e2877f085bc756337bf73ae6976c38c3d93a0dbaf8004505de980ef, 3) => P v3 -``` - -In this example, all three versions of the same package are at different IDs. The packages have increasing versions but it is possible to call into `v1`, even though `v2` and `v3` exist on chain. - -### Framework packages - -Framework packages (such as the Move standard library at `0x1`,the Sui Framework at `0x2`, Sui System at `0x3` and Deepbook at `0xdee9`) are a special-case because their IDs must remain stable across upgrades. The network can upgrade framework packages while preserving their IDs via a system transaction, but can only perform this operation on epoch boundaries because they are considered immutable like other packages. New versions of framework packages retain the same ID as their predecessor, but increment their version by one: - -``` -(0x1, 1) => MoveStdlib v1 -(0x1, 2) => MoveStdlib v2 -(0x1, 3) => MoveStdlib v3 -``` - -The prior example shows the on-chain representation of the first three versions of the Move standard library. - -### Package versions - -Sui smart contracts are organized into upgradeable packages and, as a result, multiple versions of any given package can exist on chain. Before someone can use an on-chain package, you must publish its first, original version. When you upgrade a package, you create a new version of that package. Each upgrade of a package is based on the immediately preceding version of that package in the versions history. In other words, you can upgrade the `nth` version of a package from only the `nth - 1` version. For example, you can upgrade a package from version 1 to 2, but afterwards you can upgrade that package only from version 2 to 3; you're not allowed to upgrade from version 1 to 3. - -There is a notion of versioning in package manifest files, existing in both the package section and in the dependencies section. For example, consider the manifest code that follows: - -```toml -[package] -name = "some_pkg" -version = "1.0.0" - -[dependencies] -another_pkg = { git = "https://github.com/another_pkg/another_pkg.git" , version = "2.0.0"} -``` - -At this point, the version references in the manifest are used only for user-level documentation as the `publish` and `upgrade` commands do not leverage this information. If you publish a package with a certain package version in the manifest file and then modify and re-publish the same package with a different version (using `publish` command rather than `upgrade` command), the two are considered different packages, rather than on-chain versions of the same package. You cannot use any of these packages as a dependency override to stand in for the other one. While you can specify this type of override when building a package, it results in an error when publishing or upgrading on chain. diff --git a/docs/content/guides.mdx b/docs/content/guides.mdx index 9458cdc6270aa..4094c5b33cb86 100644 --- a/docs/content/guides.mdx +++ b/docs/content/guides.mdx @@ -27,7 +27,7 @@ Install tooling, setup your environment, and deploy a "Hello, World!" Move packa Follow these guides to learn about essential Sui concepts. - + diff --git a/docs/content/guides/developer/advanced/graphql-rpc.mdx b/docs/content/guides/developer/advanced/graphql-rpc.mdx index 7b672812dd09a..0228ffeab6c53 100644 --- a/docs/content/guides/developer/advanced/graphql-rpc.mdx +++ b/docs/content/guides/developer/advanced/graphql-rpc.mdx @@ -568,7 +568,7 @@ fragment MoveValueFields on MoveValue { ### Fetch all dynamic fields on an object -Paginate over the dynamic fields of an object. This works even when the object in question is [wrapped](/concepts/object-ownership/wrapped.mdx) by using the owner query. It can be used for iterating over the elements of on-chain data structures, like tables and bags. See [The Move Book](https://move-book.com/programmability/dynamic-collections.html) to learn more about dynamic collections available in Move. +Paginate over the dynamic fields of an object. This works even when the object in question is [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) by using the owner query. It can be used for iterating over the elements of on-chain data structures, like tables and bags. See [The Move Book](https://move-book.com/programmability/dynamic-collections.html) to learn more about dynamic collections available in Move. :::info diff --git a/docs/content/guides/developer/app-examples/coin-flip.mdx b/docs/content/guides/developer/app-examples/coin-flip.mdx index 2c1dc58064e4f..577508eb094aa 100644 --- a/docs/content/guides/developer/app-examples/coin-flip.mdx +++ b/docs/content/guides/developer/app-examples/coin-flip.mdx @@ -23,10 +23,10 @@ Source code locations for the smart contracts and frontend: ## What the guide teaches -- **Shared objects:** The guide teaches you how to use [shared objects](concepts/object-ownership/shared.mdx), in this case to create a globally accessible `HouseData` object. +- **Shared objects:** The guide teaches you how to use [shared objects](/guides/developer/objects/object-ownership/shared), in this case to create a globally accessible `HouseData` object. - **One-time witnesses:** The guide teaches you how to use [one-time witnesses](concepts/sui-move-concepts.mdx#one-time-witness) to ensure only a single instance of the `HouseData` object ever exists. - **Asserts:** The guide teaches you how to use [asserts](https://move-book.com/move-basics/assert-and-abort.html?highlight=asserts#assert) to abort functions due to certain conditions not being met. -- **Address-owned objects:** The guide teaches you how to use [address-owned objects](concepts/object-ownership/address-owned.mdx) when necessary. +- **Address-owned objects:** The guide teaches you how to use [address-owned objects](/guides/developer/objects/object-ownership/address-owned) when necessary. - **Events:** The guide teaches you how to emit events in your contracts, which can be used to track on-chain activity. For more information on events, see [Using Events](../sui-101/using-events.mdx) for practical usage of events on Sui or [Events in The Move Book](https://move-book.com/programmability/events.html) to learn about event structure and how to emit them in Move. - **Storage rebates:** The guide shows you best practices regarding [storage fee rebates](concepts/tokenomics.mdx#storage-fund-rewards). - **MEV attack protection:** The guide introduces you to [MEV attacks](https://github.com/MystenLabs/satoshi-coin-flip?tab=readme-ov-file#mev-attack-resistant-single-player-satoshi-smart-contract-flow), how to make your contracts MEV-resistant, and the trade-offs between protection and user experience. @@ -44,7 +44,7 @@ To follow along with this guide, set your new Move package to `satoshi_flip`. ### `house_data` module -This example uses several modules to create a package for the Satoshi Coin Flip game. The first module is `house_data.move`. You need to store the game's data somewhere, and in this module you create a [shared object](concepts/object-ownership/shared.mdx) for all house data. +This example uses several modules to create a package for the Satoshi Coin Flip game. The first module is `house_data.move`. You need to store the game's data somewhere, and in this module you create a [shared object](/guides/developer/objects/object-ownership/shared) for all house data. Create a new file in the `sources` directory with the name `house_data.move` and populate the file with the following code: @@ -318,7 +318,7 @@ This code also adds a `count` function that returns the current count, and a tes ### `single_player_satoshi` module -Lastly, you need a game module and object that can create a new game, distribute funds after the game, and potentially cancel games. Because this is a one-player game, create an [address-owned object](concepts/object-ownership/address-owned.mdx) rather than a [shared object](/concepts/object-ownership/shared.mdx). +Lastly, you need a game module and object that can create a new game, distribute funds after the game, and potentially cancel games. Because this is a one-player game, create an [address-owned object](/guides/developer/objects/object-ownership/address-owned) rather than a [shared object](/guides/developer/objects/object-ownership/shared.mdx). Create the game module. In the `sources` directory, create a new file called `single_player_satoshi.move` and populate with the following: diff --git a/docs/content/guides/developer/app-examples/e2e-counter.mdx b/docs/content/guides/developer/app-examples/e2e-counter.mdx index f8ff44cc78bae..f23ac71d0cd8e 100644 --- a/docs/content/guides/developer/app-examples/e2e-counter.mdx +++ b/docs/content/guides/developer/app-examples/e2e-counter.mdx @@ -22,7 +22,7 @@ The guide is split into two parts: ## What the guide teaches -- **Shared objects:** The guide teaches you how to use [shared objects](../../../concepts/object-ownership/shared.mdx), in this case to create globally accessible `Counter` objects. +- **Shared objects:** The guide teaches you how to use [shared objects](/guides/developer/objects/object-ownership/shared.mdx), in this case to create globally accessible `Counter` objects. - **Programmable transaction blocks (PTBs):** You learn how to use PTBs to interact with your Move module from your frontend. ## Directory structure @@ -80,7 +80,7 @@ Add the `Counter` struct and elements described in the following sections to the -In the `create` function, a new `Counter` object is created and [shared](../../../concepts/object-ownership/shared.mdx). +In the `create` function, a new `Counter` object is created and [shared](/guides/developer/objects/object-ownership/shared.mdx). ### Incrementing and resetting `Counter` diff --git a/docs/content/guides/developer/app-examples/recaptcha.mdx b/docs/content/guides/developer/app-examples/recaptcha.mdx index e0d08fd407a3b..26f6e85a9d944 100644 --- a/docs/content/guides/developer/app-examples/recaptcha.mdx +++ b/docs/content/guides/developer/app-examples/recaptcha.mdx @@ -94,7 +94,7 @@ fun init(ctx: &mut TxContext) { - The `Interaction` struct is used to define the data that is emitted as an event when a user successfully interacts with the smart contract. The `Interaction` event has two fields: the sender's address and the timestamp in milliseconds. The sender's address is the account that initiated the interaction, and the timestamp is the current time when the event is triggered. - The `Registry` struct stores the mapping of the user’s address to the expiration time of the eligibility to interact with the smart contract. It also has a `window` field that specifies the length of the time window in milliseconds. The time window is the period of time that the user is eligible to interact with the smart contract after passing the reCAPTCHA test. -- The [`init` function](../../../concepts/sui-move-concepts.mdx) creates a [shared object](concepts/object-ownership/shared.mdx) for the `Registry`. The function is called when the smart contract is deployed to the blockchain. The function creates a new registry object with a unique ID and sets the time window to the constant value. +- The [`init` function](../../../concepts/sui-move-concepts.mdx) creates a [shared object](/guides/developer/objects/object-ownership/shared) for the `Registry`. The function is called when the smart contract is deployed to the blockchain. The function creates a new registry object with a unique ID and sets the time window to the constant value. So far, you've set up the data structures within the module. Now, create a function that verifies the message diff --git a/docs/content/guides/developer/app-examples/reviews-rating.mdx b/docs/content/guides/developer/app-examples/reviews-rating.mdx index a53b5ce4509f5..ff6dc396515a7 100644 --- a/docs/content/guides/developer/app-examples/reviews-rating.mdx +++ b/docs/content/guides/developer/app-examples/reviews-rating.mdx @@ -97,14 +97,14 @@ public fun register_service(db: &mut Dashboard, service_id: ID) { } ``` -A `Dashboard` is a [shared object](../../../concepts/object-ownership/shared.mdx), so any service owner can register their service to a dashboard. +A `Dashboard` is a [shared object](/guides/developer/objects/object-ownership/shared.mdx), so any service owner can register their service to a dashboard. A service owner should look for dashboards that best match their service attribute and register. A dynamic field stores the list of services that are registered to a dashboard. Learn more about dynamic fields in [The Move Book](https://move-book.com/programmability/dynamic-fields.html). A service may be registered to multiple dashboards at the same time. For example, a Chinese-Italian fusion restaurant may be registered to both the Chinese and Italian dashboards. :::info -See [Object Ownership Basics](../sui-101/object-ownership.mdx) for more information on the differences between object types. +See [Object Ownership Basics](/guides/developer/objects/object-ownership) for more information on the differences between object types. ::: @@ -148,7 +148,7 @@ fun calculate_total_score(rev: &Review): u64 { In addition to the content of a review, all the elements that are required to compute total score are stored in a `Review` object. -A `Review` is a [shared object](../../../concepts/object-ownership/shared.mdx), so anyone can cast a vote on a review and update its `total_score` field. +A `Review` is a [shared object](/guides/developer/objects/object-ownership/shared.mdx), so anyone can cast a vote on a review and update its `total_score` field. After `total_score` is updated, the [`update_top_reviews`](#casting-votes) function can be called to update the `top_reviews` field of the `Service` object. ### `service.move` diff --git a/docs/content/guides/developer/app-examples/tic-tac-toe.mdx b/docs/content/guides/developer/app-examples/tic-tac-toe.mdx index cf1dc7ce585b9..47f62b0a91b69 100644 --- a/docs/content/guides/developer/app-examples/tic-tac-toe.mdx +++ b/docs/content/guides/developer/app-examples/tic-tac-toe.mdx @@ -23,8 +23,8 @@ You can view the [complete source code for this app example](https://github.com/ ## What the guide teaches -- **Owned objects:** The guide teaches you how to use [owned objects](../../../concepts/object-ownership/address-owned.mdx), in this case to act as the game board in the centralized and multisig version of tic-tac-toe. Owned objects are objects that are owned by a single account and can only be modified by that account. In this case, the game board is owned by a game admin, who is responsible for updating the board with each player's move. -- **Shared objects:** The guide teaches you how to use [shared objects](../../../concepts/object-ownership/shared.mdx), in this case to act as the game board in the more decentralized version of tic-tac-toe. Shared objects are objects that can be modified by multiple accounts. In this case, the game board is shared between the two players, allowing them to update the board directly. +- **Owned objects:** The guide teaches you how to use [owned objects](/guides/developer/objects/object-ownership/address-owned.mdx), in this case to act as the game board in the centralized and multisig version of tic-tac-toe. Owned objects are objects that are owned by a single account and can only be modified by that account. In this case, the game board is owned by a game admin, who is responsible for updating the board with each player's move. +- **Shared objects:** The guide teaches you how to use [shared objects](/guides/developer/objects/object-ownership/shared.mdx), in this case to act as the game board in the more decentralized version of tic-tac-toe. Shared objects are objects that can be modified by multiple accounts. In this case, the game board is shared between the two players, allowing them to update the board directly. - **Multisig accounts:** The guide teaches you how to use [multisig accounts](https://sdk.mystenlabs.com/typescript/cryptography/multisig) to share ownership of the game board between two players. Multisig accounts are accounts that require a certain threshold of signatures to authorize a transaction. In this case, the game board is owned by a 1-of-2 multisig account. - **Dynamic object fields:** The guide teaches you how to use dynamic object fields, in this case to transfer the actions of the players to the game board, which will be retrieved by the game admin. See [The Move Book](https://move-book.com/programmability/dynamic-object-fields.html to learn more about dynamic object fields. diff --git a/docs/content/guides/developer/app-examples/trustless-swap.mdx b/docs/content/guides/developer/app-examples/trustless-swap.mdx index 3e6a4e859e342..51d2c1a96ec3f 100644 --- a/docs/content/guides/developer/app-examples/trustless-swap.mdx +++ b/docs/content/guides/developer/app-examples/trustless-swap.mdx @@ -19,7 +19,7 @@ There are three main sections to this guide: ## What the guide teaches -- **Shared objects:** The guide teaches you how to use [shared objects](../../../concepts/object-ownership/shared.mdx), in this case to act as the escrow between two Sui users wanting to trade. Shared objects are a unique concept to Sui. Any transaction and any signer can modify it, given the changes meet the requirements set forth by the package that defined the type. +- **Shared objects:** The guide teaches you how to use [shared objects](/guides/developer/objects/object-ownership/shared.mdx), in this case to act as the escrow between two Sui users wanting to trade. Shared objects are a unique concept to Sui. Any transaction and any signer can modify it, given the changes meet the requirements set forth by the package that defined the type. - **Composability:** The guide teaches you how to design your Move code in a way that enables full composability. In this app, the Move code that handles trading is completely unaware of the code that defines the objects it is trading and vice versa. The guide also shows how to build an app that: @@ -131,7 +131,7 @@ Together, they ensure that a lock and key cannot have existed before the lock op :::tip Additional resources - [Move Package](https://move-book.com/concepts/packages.html) defined in The Move Book. -- Concepts: [Wrapped Objects](../../../concepts/versioning.mdx#wrapped-objects) +- Concepts: [Wrapped Objects](/guides/developer/objects/versioning.mdx#wrapped-objects) ::: @@ -272,9 +272,9 @@ Finally, you need to add a function to allow the first party to complete the tra :::tip Additional resources - [Full source code](https://github.com/MystenLabs/sui/blob/705ee1ed3ce8cfadc4597c6facb6769d7dfb5896/examples/trading/contracts/escrow/sources/shared.move) -- Concepts: [Shared Objects](../../../concepts/object-ownership/shared) +- Concepts: [Shared Objects](/guides/developer/objects/object-ownership/shared) - Concepts: [Shared Object Deletion](https://blog.sui.io/ephemeral-shared-objects/) -- Concepts: [PTBs](../../../concepts/transactions/prog-txn-blocks) +- Concepts: [PTBs](/concepts/transactions/prog-txn-blocks) ::: diff --git a/docs/content/guides/developer/app-examples/weather-oracle.mdx b/docs/content/guides/developer/app-examples/weather-oracle.mdx index 57da1da9178ab..54846d63e579e 100644 --- a/docs/content/guides/developer/app-examples/weather-oracle.mdx +++ b/docs/content/guides/developer/app-examples/weather-oracle.mdx @@ -98,7 +98,7 @@ fun init(otw: WEATHER, ctx: &mut TxContext) { - The first struct, `AdminCap`, is a capability. - The second struct, `WEATHER`, is a one-time witness that ensures only a single instance of this `Weather` ever exists. See [One Time Witness](https://move-book.com/programmability/one-time-witness.html) in The Move Book for more information. - The `WeatherOracle` struct works as a registry and stores the `geoname_id`s of the `CityWeatherOracle`s as dynamic fields. See [The Move Book](https://move-book.com/programmability/dynamic-fields.html) to learn more about dynamic fields. -- The `init` function creates and sends the `Publisher` and `AdminCap` objects to the sender. Also, it creates a [shared object](concepts/object-ownership/shared.mdx) for all the `CityWeatherOracle`s. See [Module Initializer](https://move-book.com/programmability/module-initializer.html) in The Move Book for more information. +- The `init` function creates and sends the `Publisher` and `AdminCap` objects to the sender. Also, it creates a [shared object](/guides/developer/objects/object-ownership/shared) for all the `CityWeatherOracle`s. See [Module Initializer](https://move-book.com/programmability/module-initializer.html) in The Move Book for more information. So far, you've set up the data structures within the module. Now, create a function that initializes a `CityWeatherOracle` and adds it as dynamic fields to the `WeatherOracle` object: diff --git a/docs/content/guides/developer/getting-started/hello-world.mdx b/docs/content/guides/developer/getting-started/hello-world.mdx index 334349cea24c7..20afee48f4853 100644 --- a/docs/content/guides/developer/getting-started/hello-world.mdx +++ b/docs/content/guides/developer/getting-started/hello-world.mdx @@ -8,7 +8,7 @@ pagination_next: null You'll build a "Hello, World!" program to learn the fundamentals of programming on Sui. You create programs on Sui by writing and deploying smart contracts to the network. -The most basic unit of storage on Sui is an [_object_](/concepts/object-model.mdx). Other blockchains typically structure storage using key-value stores. Sui centers storage around objects with unique ID addresses on-chain. Every Sui smart contract is an object that manipulates other objects. +The most basic unit of storage on Sui is an [_object_](/guides/developer/objects/object-model.mdx). Other blockchains typically structure storage using key-value stores. Sui centers storage around objects with unique ID addresses on-chain. Every Sui smart contract is an object that manipulates other objects. Objects can be immutable or mutable: diff --git a/docs/content/guides/developer/getting-started/sui-install.mdx b/docs/content/guides/developer/getting-started/sui-install.mdx index 9c55d2baf2560..38d25ac2c2255 100644 --- a/docs/content/guides/developer/getting-started/sui-install.mdx +++ b/docs/content/guides/developer/getting-started/sui-install.mdx @@ -10,7 +10,7 @@ pagination_next: null Sui is a scalable and performant layer-1 blockchain that is home to a complete stack of native primitives ideal for building decentralized applications. Such primitives, such as those for [encryption](https://seal-docs.wal.app/UsingSeal/), [data storage](https://docs.wal.app/usage/setup.html), verification, and access control, provide developers with every piece of the application stack without needing to use layer-2 chains or off-chain solutions. -In contrast to other chains, Sui uses an [object-centric model](/concepts/object-model.mdx), where every item on the network is an object. [Transactions](/concepts/transactions.mdx) use objects as input, which mutate an existing object or create new objects. Each object has a unique on-chain ID. +In contrast to other chains, Sui uses an [object-centric model](/guides/developer/objects/object-model.mdx), where every item on the network is an object. [Transactions](/concepts/transactions.mdx) use objects as input, which mutate an existing object or create new objects. Each object has a unique on-chain ID. To create objects, submit transactions, and start building an application on Sui, first you must install Sui. This installation includes the [Sui CLI](/references/cli.mdx), a tool that creates and manages address balances, builds and publishes smart contracts, and queries information from the network. diff --git a/docs/content/guides/developer/nft/nft-rental.mdx b/docs/content/guides/developer/nft/nft-rental.mdx index 7ba1d5eb4cf36..bb69b73c057ed 100644 --- a/docs/content/guides/developer/nft/nft-rental.mdx +++ b/docs/content/guides/developer/nft/nft-rental.mdx @@ -15,7 +15,7 @@ The NFT Rental example satisfies the following project requirements: - Immutable access is read-only. - Mutable, the lender should consider downgrade and upgrade operations and include them in the renting fee. - After the renting period has finished, the item can be sold normally. -- Creator-defined royalties are respected by encompassing [transfer policy rules](../../../concepts/transfers/custom-rules.mdx). +- Creator-defined royalties are respected by encompassing [transfer policy rules](/guides/developer/objects/transfers/custom-rules.mdx). ## Use cases @@ -652,4 +652,4 @@ rect rgba(191, 223, 255, 0.2) - + diff --git a/docs/content/guides/developer/objects/object-model.mdx b/docs/content/guides/developer/objects/object-model.mdx new file mode 100644 index 0000000000000..1924ef2a54247 --- /dev/null +++ b/docs/content/guides/developer/objects/object-model.mdx @@ -0,0 +1,110 @@ +--- +title: Object Model +description: Everything on the Sui blockchain is an object that has metadata, a type of ownership, and a referencing scheme. +keywords: [ object model, objects on sui, sui move package, sui move object, move objects, move packages, objects, packages, referring to objects, object metadata, transaction-objects, transaction objects, relating objects and transactions ] +--- + +An _object_ is a fundamental unit of storage on the network. Every resource, asset, or piece of data on-chain is an object. Many other blockchains structure storage around accounts containing key-value stores. Sui's storage is structured around objects addressable by unique IDs on-chain. + +Every object has the following components: + +- A globally unique ID. + +- An owner, which might be an address or an object. + +- A version number that increments every time a change is made to the object. + +- Metadata, such as the digest of the last transaction that used the object. + +## Object types + +There are 3 types of objects: + +- **Sui object:** Every resource, asset, or piece of data. Sui objects are what transactions interact with, and are the building blocks for all on-chain state. + +- **Sui Move object:** An object defined using the Move language. To create a Move object, you must use `sui::object::new` to create a struct. This struct must have the `key` ability and include a field `id: UID` as the first defined field. The struct can also contain primitive types, such as integers and addresses, other objects, and non-object structs. This struct can be stored on-chain as a Sui object. + +- **Sui Move package:** A smart contract that can manipulate other objects. Each Move package is a set of Sui Move bytecode modules. The modules within the package each have a name that's unique within the containing package. The combination of the package's on-chain ID and the name of a module uniquely identifies the module. When you publish smart contracts to Sui, a package is the unit of publishing. After you publish a package object, it is immutable and can never be changed or removed. A package object can depend on other package objects that were previously published to Sui. + +## Referring to objects + +You can refer to objects by their: + +- **ID:** The globally unique ID of the object. It is a stable identifier for the object that does not change and is useful for querying the current state of an object or describing which object was transferred between two addresses. + +- **Versioned ID:** An (ID, version) pair. It describes the state of the object at a particular point in the object's history and is useful for asking what the value of the object was at some point in the past or determining how recently an object was updated. + +- **Object reference:** An (ID, version, object digest) triple. The object digest is the hash of the object's contents and metadata. An object reference provides an authenticated view of the object at a particular point in the object's history. Transactions require object inputs to be specified through object references to ensure the transaction's sender and a validator processing the transaction agree on the contents and metadata of the object. + +## Object ownership + +Every object has an owner field that dictates who can use it in transactions. This field can contain one of the following options: + +- **[Address owned](/guides/developer/objects/object-ownership/address-owned.mdx):** Owned by a specific 32-byte address that is either an account address derived from a particular signature scheme or an object ID. + +- **[Immutable](/guides/developer/objects/object-ownership/immutable.mdx):** Not owned by anyone, so anyone can use it. It cannot be mutated, transferred, or deleted. + +- **[Party](/guides/developer/objects/object-ownership/party.mdx):** Transferred using the [`sui::transfer::party_transfer`](/references/framework/sui_sui/transfer#sui_transfer_party_transfer) or [`sui::transfer::public_party_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_party_transfer) function and is accessible by the `Party` to which it is transferred. Party objects can be singly owned, but unlike address-owned objects, they are versioned by consensus. + +- **[Shared](/guides/developer/objects/object-ownership/shared.mdx):** Ownership is shared using the [`sui::transfer::share_object`](/references/framework/sui_sui/transfer#sui_transfer_share_object) function and is accessible to everyone. + +- **[Wrapped](/guides/developer/objects/object-ownership/wrapped.mdx):** A data structure containing one `struct` type nested in another. + +## Object metadata + +Every object has the following metadata: + +- **A 32-byte globally unique ID:** Derived from the digest of the transaction that created the object and a counter encoding the number of IDs generated by the transaction. + +- **An 8-byte unsigned integer version:** Monotonically increases with every transaction that [modifies it](/guides/developer/objects/versioning.mdx). + +- **A 32-byte transaction digest:** Indicates the last transaction that included this object as an output. + +- **A 32-byte owner field:** Indicates how this object can be [accessed](/guides/developer/objects/object-ownership.mdx). + +In addition to common metadata, objects have a category-specific, variable-sized contents field containing a [Binary Canonical Serialization (BCS)](https://docs.rs/bcs/latest/bcs/)-encoded payload: + +- Move objects contain their Move type, whether the object can be transferred using `public_transfer`, and its fields. + +- Move packages contain the bytecode modules in the package, a table identifying which versions of a package introduced each of its types (type origin table), and a table mapping each of its transitive dependencies to a specific version of that package to use (linkage table). + + +## The transaction-object DAG: Relating objects and transactions + +Transactions take objects as input, read, write, or mutate these inputs, then produce mutated or freshly created objects as output. Each object knows the hash of the last transaction that produced it as an output. A natural way to represent the relationship between objects and transactions is a [directed acyclic graph (DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph) where nodes are transactions and directed edges go from transaction `A` to transaction `B` if an output object of `A` is an input object of `B`. They are labeled by the reference of the object in question, which specifies the exact version of the object created by `A` and used by `B`. + +The root of this DAG is a genesis transaction that takes no inputs and produces the objects that exist in the system's initial state. You can extend the DAG by identifying mutable transaction outputs that have not yet been consumed by any committed transaction and sending a new transaction that takes these outputs and optionally, immutable transaction outputs as inputs. + +Live objects are available as input for a transaction. The global state maintained by Sui consists of the totality of such objects. The live objects for a particular Sui address `A` are all objects owned by `A`, along with all shared and immutable objects in the system. + +When this DAG contains all committed transactions in the system, it forms a complete and cryptographically auditable view of the system's state and history. In addition, you can use the scheme above to construct a DAG of the relevant history for a subset of transactions or objects, for example, the objects owned by a single address. + +## Limits on transactions, objects, and data + +Sui has some limits on transactions and data used in transactions, such as a [maximum size and number of objects used](https://move-book.com/guides/building-against-limits.html). + +The `ProtocolConfig` struct in the [`sui-protocol-config` crate](https://github.com/MystenLabs/sui/blob/main/crates/sui-protocol-config/src/lib.rs) itemizes these limits: + +
+ + +`lib.rs` + + + + + +
+ +Select a network from the following tabs to see the currently configured limits and values. + + + +## Related links + + + + + + + \ No newline at end of file diff --git a/docs/content/guides/developer/objects/object-ownership.mdx b/docs/content/guides/developer/objects/object-ownership.mdx new file mode 100644 index 0000000000000..e340c2a13341f --- /dev/null +++ b/docs/content/guides/developer/objects/object-ownership.mdx @@ -0,0 +1,219 @@ +--- +title: Object Ownership +description: On Sui, object ownership can be represented in different ways. Weigh the benefits of each to decide the best approach for your project. +keywords: [ shared objects, owned objects, object types, locked, key, types of objects, shared, owned, address-owned ] +--- + +Every object has an owner that determines who can use it in transactions. If an object is [address owned](/guides/developer/objects/object-ownership/address-owned.mdx), then it is owned by either an account address derived from a particular signature scheme or an object ID. [Immutable](/guides/developer/objects/object-ownership/immutable.mdx) objects are not owned by anyone and are publicly available to be used, however they cannot be mutated, transferred, or deleted. + +[Party](/guides/developer/objects/object-ownership/party.mdx) objects are accessible by the `Party` to which it is transferred. They objects can be singly owned, but unlike address-owned objects, they are [versioned](/guides/developer/objects/versioning.mdx) by [consensus](/concepts/sui-architecture/consensus.mdx). + +[Shared](/guides/developer/objects/object-ownership/shared.mdx) objects can be mutated and are accessible to everyone on the network. They can be used by different transactions concurrently. + +Lastly, [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) are not necessarily an ownership type, but rather a data structure containing a `struct` type nested within another. + +## Object versioning paths + +Objects on Sui can be [versioned](/guides/developer/objects/versioning.mdx) either through the [fastpath](#fastpath-objects) or through [consensus](/concepts/sui-architecture/consensus.mdx). The choice between these paths affects your options for object ownership. Applications can be built using a solution that either uses consensus objects or only uses fastpath objects, with trade-offs for each. + +### Fastpath objects + +:::tip + +It is recommended to use [party objects](/guides/developer/objects/object-ownership/party.mdx) rather than fastpath objects. + +::: + +Fastpath objects can only be [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) or [immutable](/guides/developer/objects/object-ownership/immutable.mdx). This makes it complicated to write applications that require multiple parties to access the same object. Transactions that use fastpath benefit from low latency and fast finality. + +Every transaction using a fastpath object must lock the object's current version in the transaction input, then use the output version as the reference for the next transaction's input. If a fastpath object is frequently used by multiple senders, there is a risk of equivocating or causing the object to be locked until the end of the epoch. Therefore, you must coordinate off-chain access to fastpath objects. + +Applications that are extremely sensitive to latency or gas costs, that do not need to handle complex multi-party transactions, or that already require an off-chain service might benefit from a design that only uses fastpath objects. + +### Consensus objects + +Consensus objects can be [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx), owned by a [party](/guides/developer/objects/object-ownership/party.mdx), or [shared](/guides/developer/objects/object-ownership/shared.mdx). Transactions that access 1 or more consensus objects require consensus to sequence the reads and writes to those objects. This makes version management simpler, especially for frequently accessed objects, but results in higher gas cost and latency. + +Transactions that access multiple consensus objects, or particularly popular objects, might have increased latency because of contention. However, the advantage of using consensus objects is the flexibility of allowing multiple addresses to access the same object in a coordinated manner. + +Applications that require coordination between multiple parties typically benefit from using [shared](/guides/developer/objects/object-ownership/shared.mdx) objects through consensus. + +## Example + +This example demonstrates the trade-offs between consensus objects and fastpath objects by implementing the same application both ways. Both variations of the example implement a service that enables a trustless swap of objects between 2 addresses, with the service holding those objects in escrow. + +View the example's [complete code](https://github.com/MystenLabs/sui/blob/93e6b4845a481300ed4a56ab4ac61c5ccb6aa008/examples/move/escrow/sources/lock.move). + +### `Locked` and `Key` + + +Both implementations use a primitive for locking values, which offers the following interface: + +```move +module escrow::lock { + public fun lock(obj: T, ctx: &mut TxContext): (Locked, Key); + public fun unlock(locked: Locked, key: Key): T +} +``` + +Any `T: store` can be locked to get a `Locked` and a corresponding `Key`, and conversely, the locked value and its corresponding key can be consumed to get back the wrapped object. + +This interface provides locked values that cannot be modified unless they are unlocked first and later relocked. Because unlocking consumes the key, tampering with a locked value can be detected by monitoring the ID of the key that it was locked with. This prevents situations where a party in a swap changes the object they are offering. + +### Address-owned objects (fastpath) + +
+ +`owned.move` + + + + +
+ +Swapping through escrow using fastpath objects starts with both parties locking their respective objects. Fastpath objects must be [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) or [immutable](/guides/developer/objects/object-ownership/immutable.mdx). This proves that the object has not been tampered with after the swap has been agreed to. If either party doesn't want to proceed at this stage, they just unlock their object. + +```mermaid +flowchart TD + SR[Locked<fa:fa-wrench S>, fa:fa-key key_s] + BR[Locked<fa:fa-coins B>, fa:fa-key key_b] + + subgraph Seller + a2(fa:fa-wrench S)--escrow::lock-->SR + end + + subgraph Buyer + a1(fa:fa-coins B)--escrow::lock-->BR + end +``` + +Assuming both parties are happy to continue, the next step requires both parties to swap the keys. + +```mermaid +flowchart LR + Buyer--fa:fa-key key_b-->Seller + Seller--fa:fa-key key_s-->Buyer +``` + +A third party acts as the custodian and holds the objects that are waiting for their counterparts to arrive. When they arrive, it matches them up to complete the swap. + + + +```mermaid +flowchart TB + S["fa:fa-key key_s, + Locked<fa:fa-wrench S>, + exchange_key: fa:fa-key key_b, + recipient: Buyer + "] + B["fa:fa-key key_b, + Locked<fa:fa-coins B>, + exchange_key: fa:fa-key key_s, + recipient: Seller + "] + + id1(Escrow<fa:fa-coins B>)-->Third_Party + id2(Escrow<fa:fa-wrench S>)-->Third_Party + subgraph Buyer + direction TB + B--create-->id1 + end + + subgraph Seller + direction TB + S--create-->id2 + end +``` + +The `create` function prepares the `Escrow` request and sends it to the custodian. The object being offered is passed in, locked with its key, and the object being requested is identified by the ID of the key it was locked with. While preparing the request, the offered object is unlocked while referencing the ID of its key. + +Although the custodian is trusted to preserve liveness, all other properties are maintained in Move. Even though the custodian owns both objects being swapped, the only valid action they are permitted to take is to match them up with their correct counterpart to finish the swap or to cancel the swap and return them: + +```mermaid +flowchart TB + + subgraph Third_Party + direction TB + id1(fa:fa-wrench S, fa:fa-coins B) + id2(Escrow<fa:fa-coins B>, Escrow<fa:fa-wrench S>) + id2--swap-->id1 + end + + Third_Party--fa:fa-wrench S-->Buyer + Third_Party--fa:fa-coins B-->Seller +``` + + + +The `swap` function checks that senders and recipients match and that each party wants the object that the other party is offering by comparing their respective key IDs. If the custodian tries to match together 2 unrelated escrow requests to swap, the transaction does not succeed. + +### Shared objects (consensus) + +
+ +`shared.move` + + + + +
+ +The protocol in the consensus object case is less symmetric but still starts with the first party locking the object they want to swap. Consensus objects can be [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx), owned by a [party](/guides/developer/objects/object-ownership/party.mdx), or [shared](/guides/developer/objects/object-ownership/shared.mdx). This example uses a shared object. + +```mermaid +flowchart TB + B["Locked<fa:fa-coins B>, fa:fa-key key_b"] + + subgraph Buyer + direction TB + a1(fa:fa-coins B)--escrow::lock-->B + end +``` + +The second party can then view the object that was locked. If they decide they want to swap with it, they indicate their interest by creating a swap request: + +```mermaid +flowchart TB + S["fa:fa-wrench S, + exchange_key: fa:fa-key key_b, + recipient: Buyer + "] + + id1(Shared Object)-->id2(Escrow<fa:fa-wrench S>) + + subgraph Seller + direction TB + S--create-->id2 + end +``` + + + +The `create` request accepts the object being escrowed directly, meaning it is not locked, and creates a shared `Escrow` object. The request remembers the address that sent it, who is allowed to reclaim the object if the swap hasn't already happened. The intended recipient is then expected to continue the swap by providing the object they initially locked: + +```mermaid +flowchart TB + + subgraph Buyer + direction TB + id1(Escrow<fa:fa-wrench S>,\n fa:fa-key key_b,\n Locked<fa:fa-coins B>) + id2(fa:fa-wrench S) + id1-->swap-->id2 + end + + swap--fa:fa-coins B-->Seller +``` + + + +Even though the `Escrow` object is a [shared](/guides/developer/objects/object-ownership/shared.mdx) object that is accessible by anyone, the Move interface ensures that only the original sender and the intended recipient can successfully interact with it. `swap` checks that the locked object matches the object that was requested when the `Escrow` was created and assumes that the intended recipient wants the escrowed object. + +Assuming all checks pass, the object held in `Escrow` is extracted, its wrapper is deleted, and it is returned to the first party. The locked object offered by the first party is also unlocked and sent to the second party, completing the swap. + +### Comparison + +In both examples there is a point at which the first party has made a request and the other has not responded. At this point, both parties might want to access the `Escrow` object either to cancel the swap or to complete it. + +In some cases, the protocol uses only [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) objects but requires a custodian to act as an intermediary. This has the advantage of avoiding the costs and latencies of consensus, but involves more steps and requires trusting a third party for liveness. + +In the other case, the object is in custody on-chain in a [shared](/guides/developer/objects/object-ownership/shared.mdx) object. This requires consensus but involves fewer steps and no third party. \ No newline at end of file diff --git a/docs/content/guides/developer/objects/object-ownership/address-owned.mdx b/docs/content/guides/developer/objects/object-ownership/address-owned.mdx new file mode 100644 index 0000000000000..53ffb64624c72 --- /dev/null +++ b/docs/content/guides/developer/objects/object-ownership/address-owned.mdx @@ -0,0 +1,85 @@ +--- +title: Address-Owned Objects +description: Address-owned objects are owned by a Sui 32-byte address, which can either be an account address or an object ID. Learn how to create and access these objects. +keywords: [ address-owned objects, address-owned, owned objects, objects owned by address, accessing objects, accessing owned objects, address ownership ] +--- + +Address-owned objects are owned by a 32-byte address. 32-byte addresses are either an account address derived from a particular signature scheme or an object ID. An address-owned object is only accessible to its owner. You can transfer objects that you own to different addresses. + +## Create address-owned objects + +Use these [`transfer` module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move) functions to create address-owned objects: + +- Use the [`sui::transfer::transfer`](/references/framework/sui_sui/transfer#sui_transfer_transfer) function if you are defining [custom transfer rules](/guides/developer/objects/transfers/custom-rules.mdx) for the object. + +- Use the [`sui::transfer::public_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_transfer) function to create an address-owned object if the object has the `store` capability. + +``` +public fun transfer(obj: T, recipient: address) +public fun public_transfer(obj: T, recipient: address) +``` + +An object's ownership can change over the life of that object, either by adding it as a [dynamic object field](/concepts/dynamic-fields.mdx), transferring it to a different address, or making it [immutable](/guides/developer/objects/object-ownership/immutable.mdx). However, after you create an object and set its ownership, it cannot be [shared](/guides/developer/objects/object-ownership/shared.mdx). + + + +## When to use address-owned objects + +Use address-owned objects when you need: + +- Single ownership + +- Better performance than shared objects + +- Avoidance of consensus sequencing + +## Interact with address-owned objects + +You can access address-owned objects in 2 different ways depending on whether the address owner of the object corresponds to an address or an object ID. + +If the address owner of the object is an account address, then you can use and access it directly as an owned object during the execution of a transaction signed by that address. Other addresses cannot access owned objects in any way. + +If the address owner of the object corresponds to an object ID, then you must access and dynamically authenticate it during the execution of the transaction using the mechanisms defined in [Transfer to Object](/guides/developer/objects/transfers/transfer-to-object.mdx). + +To interact with an address-owned object through the CLI, first view the objects you own: + +```sh +$ export ADDR=`sui client active-address` +``` +```sh +$ sui client objects $ADDR +``` + +You can see [`sui::transfer::public_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_transfer) function used in the [`color_object` example module](https://github.com/MystenLabs/sui/blob/main/examples/move/color_object/sources/example.move) tests. The test creates a new address-owned `ColorObject` object, then calls `public_transfer` to transfer it to the owner's address. + +Save the [`color_object` example code](https://github.com/MystenLabs/sui/blob/main/examples/move/color_object/sources/example.move), then publish the `ColorObject` code on-chain using the Sui CLI: + + + +```sh +$ sui client publish $ROOT/examples/move/color_object --gas-budget +``` + +Set the package object ID to the `$PACKAGE` environment variable, if you have it set. Then create a new `ColorObject`: + +```sh +$ sui client call --gas-budget --package $PACKAGE --module "color_object" --function "create" --args 0 255 0 +``` + +Set the newly created object ID to `$OBJECT`. To view the objects in the current active address: + +```sh +$ sui client objects $ADDR +``` + +You can see that it is now owned by your address by querying the object information and viewing the `Owner` field in the output: + +```sh +$ sui client object $OBJECT +``` + +## Test address-owned objects + +The following test creates an address-owned object, transfers it to the owner, then verifies that the owner field is correct: + + \ No newline at end of file diff --git a/docs/content/concepts/object-ownership/immutable.mdx b/docs/content/guides/developer/objects/object-ownership/immutable.mdx similarity index 66% rename from docs/content/concepts/object-ownership/immutable.mdx rename to docs/content/guides/developer/objects/object-ownership/immutable.mdx index 2a6e7e6d10468..74aee7787ce3e 100644 --- a/docs/content/concepts/object-ownership/immutable.mdx +++ b/docs/content/guides/developer/objects/object-ownership/immutable.mdx @@ -4,33 +4,25 @@ description: Immutable objects cannot be changed, transferred, or deleted. Immut keywords: [ immutable objects, objects immutable, create immutable object, convert object, use immutable object, test immutable object ] --- -Objects in Sui have different ownership types, with 2 main categories: immutable objects and mutable objects. An immutable object cannot be mutated, transferred, or deleted. These objects have no owner and are freely accessible to everyone. +Objects on Sui can either be immutable or mutable. An immutable object cannot be mutated, transferred, or deleted. These objects have no owner and are freely accessible to everyone. -## Create immutable object +## Create immutable objects -To make an object immutable, call the `public_freeze_object` function from the [transfer module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move): +To make an object immutable, call the [`sui::transfer::public_freeze_object`](/references/framework/sui_sui/transfer#sui_transfer_public_freeze_object) function from the [transfer module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move): ```move public native fun public_freeze_object(obj: T); ``` -This call permanently makes the object immutable. The operation cannot be reversed. Only freeze objects when you are certain they will never need modification. +This call permanently makes the object immutable. The operation cannot be reversed. Only freeze objects when you are certain they never need modification. -You can see this function use in one of the [color_object example module](https://github.com/MystenLabs/sui/blob/main/examples/move/color_object/sources/example.move) tests. The test creates a new (owned) `ColorObject`, then calls `public_freeze_object` to turn it into an immutable object. +You can see this function used in the [`color_object` example module](https://github.com/MystenLabs/sui/blob/main/examples/move/color_object/sources/example.move) tests. The test creates a new address-owned `ColorObject` object, then calls `public_freeze_object` to turn it into an immutable object. -```move -{ - ts.next_tx(alice); - // Create a new ColorObject - let c = new(255, 0, 255, ts.ctx()); - // Make it immutable. - transfer::public_freeze_object(c); -}; -``` + In this test, you must own a `ColorObject` initially. After freezing, the object becomes immutable and ownerless. -The `transfer::public_freeze_object` function requires that you pass the object by value. If you are allowed to pass the object by a mutable reference, you could still mutate the object after the `public_freeze_object` call. This contradicts the fact that it should have become immutable. +The [`sui::transfer::public_freeze_object`](/references/framework/sui_sui/transfer#sui_transfer_public_freeze_object) function requires that you pass the object by value. If you are allowed to pass the object by a mutable reference, you could still mutate the object after the [`sui::transfer::public_freeze_object`](/references/framework/sui_sui/transfer#sui_transfer_public_freeze_object) call. This contradicts the fact that it should have become immutable. Alternatively, you can also provide an API that creates an immutable object at creation: @@ -43,12 +35,13 @@ public fun create_immutable(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { This function creates a new `ColorObject` and immediately makes it immutable before it has an owner. -## Use immutable object +## When to use immutable objects After an object becomes immutable, the rules of who can use this object in Sui Move calls change: -You can only pass an immutable object as a read-only, immutable reference to Sui Move entry functions as `&T`. -All network participants can access immutable objects. +1. You can only pass an immutable object as a read-only, immutable reference to Sui Move entry functions as `&T`. + +2. All network participants can access immutable objects. Consider a function that copies the value of one object to another: @@ -56,66 +49,9 @@ Consider a function that copies the value of one object to another: public fun copy_into(from: &ColorObject, into: &mut ColorObject); ``` -In this function, anyone can pass an immutable object as the first argument, `from`, but not the second argument. -Because you can never mutate immutable objects, there is no data race, even when multiple transactions are using the same immutable object at the same time. Hence, the existence of immutable objects does not pose any requirement on consensus. - -## Test immutable object - -You can interact with immutable objects in unit tests using `test_scenario::take_immutable` to take an immutable object wrapper from global storage, and `test_scenario::return_immutable` to return the wrapper back to the global storage. - -The `test_scenario::take_immutable` function is required because you can access immutable objects solely through read-only references. The `test_scenario` runtime keeps track of the usage of this immutable object. If the compiler does not return the object through `test_scenario::return_immutable` before the start of the next transaction, the test stops. - -To see it work in action: - -```move -let sender1 = @0x1; -let scenario_val = test_scenario::begin(sender1); -let scenario = &mut scenario_val; -{ - let ctx = test_scenario::ctx(scenario); - color_object::create_immutable(255, 0, 255, ctx); -}; -scenario.next_tx(sender1); -{ - // has_most_recent_for_sender returns false for immutable objects. - assert!(!test_scenario::has_most_recent_for_sender(scenario)) -}; -``` - -This test submits a transaction as `sender1`, which tries to create an immutable object. - -The `has_most_recent_for_sender` function no longer returns `true`, because the object is no longer owned. To take this object: - -```move -// Any sender can work. -let sender2 = @0x2; -scenario.next_tx(sender2); -{ - let object = test_scenario::take_immutable(scenario); - let (red, green, blue) = color_object::get_color(object); - assert!(red == 255 && green == 0 && blue == 255) - test_scenario::return_immutable(object); -}; -``` - -To show that this object is indeed not owned by anyone, start the next transaction with `sender2`. Note that it used `take_immutable` and succeeded. This means that any sender can take an immutable object. To return the object, call the `return_immutable` function. - -To verify immutability, create a function attempting to modify a `ColorObject`: - -```move -public fun update( - object: &mut ColorObject, - red: u8, green: u8, blue: u8, -) { - object.red = red; - object.green = green; - object.blue = blue; -} -``` - -As you have learned, the function fails when the `ColorObject` is immutable. +In this function, anyone can pass an immutable object as the first argument, `from`, but not the second argument. Because you can never change immutable objects, there is no data race, even when multiple transactions are using the same immutable object at the same time. The existence of immutable objects does not pose any requirement on consensus. -## On-chain interactions +## Interact with immutable objects First, view the objects you own: @@ -126,7 +62,7 @@ $ export ADDR=`sui client active-address` $ sui client objects $ADDR ``` -Publish the `ColorObject` code on-chain using the Sui Client CLI: +Publish the `ColorObject` code on-chain using the Sui CLI: @@ -176,4 +112,58 @@ If you try to mutate it: $ sui client call --gas-budget --package $PACKAGE --module "color_object" --function "update" --args \"$OBJECT\" 0 0 0 ``` -The response indicates that you cannot pass an immutable object to a mutable argument. \ No newline at end of file +The response indicates that you cannot pass an immutable object to a mutable argument. + +## Test immutable objects + +You can interact with immutable objects in unit tests using `test_scenario::take_immutable` to take an immutable object wrapper from global storage, and `test_scenario::return_immutable` to return the wrapper back to the global storage. + +The `test_scenario::take_immutable` function is required because you can access immutable objects solely through read-only references. The `test_scenario` runtime keeps track of the usage of this immutable object. If the compiler does not return the object through `test_scenario::return_immutable` before the start of the next transaction, the test stops. + +```move +let sender1 = @0x1; +let scenario_val = test_scenario::begin(sender1); +let scenario = &mut scenario_val; +{ + let ctx = test_scenario::ctx(scenario); + color_object::create_immutable(255, 0, 255, ctx); +}; +scenario.next_tx(sender1); +{ + // has_most_recent_for_sender returns false for immutable objects. + assert!(!test_scenario::has_most_recent_for_sender(scenario)) +}; +``` + +This test submits a transaction as `sender1`, which tries to create an immutable object. + +The `has_most_recent_for_sender` function no longer returns `true`, because the object is no longer owned. + +To show that this object is indeed not owned by anyone, start the next transaction with `sender2`. It uses `take_immutable` and succeeds. This means that any sender can take an immutable object. To return the object, call the `return_immutable` function. + +```move +// Any sender can work. +let sender2 = @0x2; +scenario.next_tx(sender2); +{ + let object = test_scenario::take_immutable(scenario); + let (red, green, blue) = color_object::get_color(object); + assert!(red == 255 && green == 0 && blue == 255) + test_scenario::return_immutable(object); +}; +``` + +To verify immutability, create a function attempting to modify a `ColorObject`: + +```move +public fun update( + object: &mut ColorObject, + red: u8, green: u8, blue: u8, +) { + object.red = red; + object.green = green; + object.blue = blue; +} +``` + +The function fails because the `ColorObject` is immutable. \ No newline at end of file diff --git a/docs/content/guides/developer/objects/object-ownership/party.mdx b/docs/content/guides/developer/objects/object-ownership/party.mdx new file mode 100644 index 0000000000000..ba3c57e2bc3f1 --- /dev/null +++ b/docs/content/guides/developer/objects/object-ownership/party.mdx @@ -0,0 +1,51 @@ +--- +title: Party Objects +description: Party objects are owned by a specified party at the time of transfer and versioned by consensus. Learn how to create and access these objects. +--- + +A party object combines properties of both address-owned objects and shared objects. Like address-owned objects, party objects can be owned by a single address. Like shared objects, they are versioned by consensus. Unlike shared objects, they can be transferred to and from other ownership types and wrapped. + +Party objects are transferred using the [`sui::transfer::party_transfer`](/references/framework/sui_sui/transfer#sui_transfer_party_transfer) or [`sui::transfer::public_party_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_party_transfer) function. It is accessible to the `Party` to which it is transferred. + +:::info + +Only single ownership is supported for party objects. + +::: + +## Create party objects + +Use these [`transfer` module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move) functions to create party objects: + +``` +public fun party_transfer(obj: T, party: sui::party::Party) +public fun public_party_transfer(obj: T, party: sui::party::Party) +``` + +- Use the [`sui::transfer::party_transfer`](/references/framework/sui_sui/transfer#sui_transfer_party_transfer) function if you are defining a [custom transfer policy](/guides/developer/objects/transfers/custom-rules.mdx) for the object. + +- Use the [`sui::transfer::public_party_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_party_transfer) function if the object has the `store` capability. + +A party object's ownership can change over its lifetime, either by adding it as a [dynamic object field](/concepts/dynamic-fields.mdx), transferring it to a different address or ownership type, or making it [immutable](/guides/developer/objects/object-ownership/immutable.mdx). However, after you create an object and set its ownership, it cannot be [shared](/guides/developer/objects/object-ownership/shared.mdx). + + + +## When to use party objects + +Use party objects when you want an object to be versioned by [consensus](/concepts/sui-architecture/consensus.mdx), such as for operational convenience. If an object is only used with other party or [shared](/guides/developer/objects/object-ownership/shared.mdx) objects, converting it to a party object has no additional performance cost. + +Party objects can be used by multiple inflight [transactions](/concepts/transactions.mdx) at the same time. This contrasts with [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) objects, which only allow a single inflight transaction. Many applications can benefit from the ability to pipeline multiple transactions on the same party object. + +:::info + +`Coin`s can be party objects, including `Coin`. However, you cannot use a party object `Coin` for gas payment. To use a party object `Coin` for gas, you must first transfer it back to address-owned. + +::: + +## Interact with party objects + +You can specify party objects as input to a transaction in the same way as shared objects. Sui validators ensure that the sender of the transaction can access the object. The validator might abort a transaction at execution time if the owner of an input party object has changed because of an earlier, conflicting transaction. + +Party objects whose owning address corresponds to an object ID are not supported for access through the [transfer to object](/guides/developer/objects/transfers/transfer-to-object.mdx) mechanism. To transfer a party object owned by an account address: + + \ No newline at end of file diff --git a/docs/content/concepts/object-ownership/shared.mdx b/docs/content/guides/developer/objects/object-ownership/shared.mdx similarity index 66% rename from docs/content/concepts/object-ownership/shared.mdx rename to docs/content/guides/developer/objects/object-ownership/shared.mdx index 4f127632b0383..173f2b0ab5e21 100644 --- a/docs/content/concepts/object-ownership/shared.mdx +++ b/docs/content/guides/developer/objects/object-ownership/shared.mdx @@ -4,13 +4,36 @@ description: Anyone can access shared objects on the Sui network, so care must b keywords: [ shared objects, sharing objects, objects that are shared, public objects, accessing shared objects, accessing public objects ] --- -Use the `sui::transfer::share_object` function to create a shared object, making it publicly accessible on the network. Extended functionality and accessibility of shared objects requires additional effort by securing access, if needed. +A shared object is a public, mutable object that is accessible to anyone on the network. They are designed such that multiple users or smart contracts can interact with the same object concurrently. + +## Create shared objects + +Use the [`sui::transfer::share_object`](/references/framework/sui_sui/transfer#sui_transfer_share_object) function to create a shared object, making it publicly accessible on the network. Extended functionality and accessibility of shared objects requires additional effort by securing access, if needed. Shared objects require the `key` ability. -## Usage example +```move +public struct Donut has key { id: UID } + +fun init(ctx: &mut TxContext) { + transfer::transfer(ShopOwnerCap { + id: object::new(ctx) + }, ctx.sender()); + + transfer::share_object(DonutShop { + id: object::new(ctx), + price: 1000, + balance: balance::zero() + }) +``` + +## When to use shared objects + +Shared objects are ideal for things like marketplaces, games, or other scenarios where global state needs to be accessed or modified by multiple parties. -The following example creates a shop to sell digital donuts. Everyone needs access to the shop to purchase donuts from it, so the example creates the shop as a shared object using `sui::transfer::share_object`. +## Interact with shared objects + +The following example creates a shop to sell digital donuts. Everyone needs access to the shop to purchase donuts from it, so the example creates the shop as a shared object using [`sui::transfer::share_object`](/references/framework/sui_sui/transfer#sui_transfer_share_object). ```move module examples::donuts; @@ -83,4 +106,4 @@ public fun collect_profits( transfer::public_transfer(profits, ctx.sender()) } -``` +``` \ No newline at end of file diff --git a/docs/content/guides/developer/objects/object-ownership/wrapped.mdx b/docs/content/guides/developer/objects/object-ownership/wrapped.mdx new file mode 100644 index 0000000000000..093d15d7e91a1 --- /dev/null +++ b/docs/content/guides/developer/objects/object-ownership/wrapped.mdx @@ -0,0 +1,244 @@ +--- +title: Wrapped Objects +description: Wrapped objects are object data structures nested inside of another object data structure. Objects can be wrapped directly, through `Option`, or through `vector` fields. +keywords: [ wrapped objects, wrapping objects, unwrapping objects, direct wrapping, wrap directly, wrap object directly, wrap object option, option wrap, vector wrap, wrap object vector ] +--- + +Wrapping refers to nesting structs to organize data structures in Move. When an object is wrapped, the object no longer exists independently on-chain. You can no longer look up the object by its ID, as the object becomes part of the data of the object that wraps it. Most importantly, you can no longer pass the wrapped object as an argument in a Move call. The only access point is through the object that wraps it. + +It is not possible to create circular wrapping behavior, where A wraps B, B wraps C, and C also wraps A. + +This example shows a basic wrapper pattern: + +```move +public struct Foo has key { + id: UID, + bar: Bar, +} + +public struct Bar has store { + value: u64, +} +``` + +To embed a struct type in an object with a `key` ability, the struct type must have the `store` ability. + +In the preceding example, `Bar` is a normal struct, but it is not an object since it doesn't have the `key` ability. The following code turns `Bar` into an object: + +```move +public struct Bar has key, store { + id: UID, + value: u64, +} +``` + +If you put the object of type `Bar` into an object of type `Foo`, the object type `Foo` wraps the object type `Bar`. The object type `Foo` is the wrapper or wrapping object. + +## Create a wrapped object + +This example shows a basic function used to wrap an object: + + + +## Unwrap a wrapped object + +You can take out the wrapped object and transfer it to an address, modify it, delete it, or freeze it. This is called unwrapping. When an object is unwrapped, it becomes an independent object again and can be accessed directly on-chain. The object's ID stays the same across wrapping and unwrapping. + +This example shows a basic function used to unwrap an object: + + + +## Direct wrapping + +Direct wrapping occurs when an object type contains another object type as a direct field. In direct wrapping, the wrapped object cannot be extracted without destroying the wrapper. This provides strong encapsulation guarantees and is ideal for implementing object locking patterns. Direct wrapping requires explicit contract calls to modify access. + +The following example implementation of a trusted swap demonstrates how to use direct wrapping. Assume there is an NFT-style `Object` type that has `scarcity` and `style`. In this example, `scarcity` determines how rare the object is, and `style` determines the object content or how it's rendered. You are only willing to trade this `Object` with one that has identical `scarcity` but with a different `style`. + +First, define an object type: + +```move +public struct Object has key, store { + id: UID, + scarcity: u8, + style: u8, +} +``` + +In a real application, you might make sure that there is a limited supply of the objects and a mechanism to mint them to a list of owners. For demonstration purposes, this example simplifies creation: + +```move +public fun new(scarcity: u8, style: u8, ctx: &mut TxContext): Object { + Object { id: object::new(ctx), scarcity, style } +} +``` + +Only object owners can send a transaction to mutate the object. One person cannot send a transaction that would swap their own object with someone else's object. + +To swap objects, the same address must own both objects. Anyone who wants to swap their object can send their objects to the third party, such as a site that offers swapping services, and the third party helps perform the swap and send the objects to the appropriate owner. + +To ensure that you retain custody of your objects and not give full custody to the third party, use direct wrapping. To define a wrapper object type: + +```move +public struct SwapRequest has key { + id: UID, + owner: address, + object: Object, + fee: Balance, +} +``` + +`SwapRequest` defines a Sui object type, wraps the `object` to swap, and tracks the original `owner` of the object. To define an interface to request a swap by someone who owns an `Object`: + +```move +public fun request_swap( + object: Object, + fee: Coin, + service: address, + ctx: &mut TxContext, +) { + assert!(coin::value(&fee) >= MIN_FEE, EFeeTooLow); + + let request = SwapRequest { + id: object::new(ctx), + owner: ctx.sender(), + object, + fee: coin::into_balance(fee), + }; + + transfer::transfer(request, service) +} + +``` + +In the preceding function, you must pass the object by value so that it is fully consumed and wrapped into `SwapRequest`. The example also provides a fee of type `Coin` and checks that the fee is sufficient. The example turns `Coin` into `Balance` when it's put into the `wrapper` object. This is because `Coin` is only used to pass objects, such as transaction inputs or objects sent to addresses. For coin balances that need to be embedded in other structs, use `Balance` instead to avoid the overhead of carrying around an unnecessary `UID` field. + +The wrapper object is then sent to the service operator with the address specified in the call as `service`. + +The interface for the function that the service operator can call to perform a swap between 2 objects sent from 2 addresses resembles: + +```move +public fun execute_swap(s1: SwapRequest, s2: SwapRequest): Balance; +``` + +`s1` and `s2` are 2 wrapped objects that were sent from different object owners to the service operator. Both wrapped objects are passed by value because they eventually need to be [unpacked](https://move-book.com/move-basics/struct.html#destructing-structures). + +First, unpack the 2 objects to obtain the inner fields: + +```move +let SwapRequest {id: id1, owner: owner1, object: o1, fee: fee1} = s1; +let SwapRequest {id: id2, owner: owner2, object: o2, fee: fee2} = s2; +``` + +Then, check that the swap is legitimate and the 2 objects have identical scarcity but different styles: + +```move +assert!(o1.scarcity == o2.scarcity, EBadSwap); +assert!(o1.style != o2.style, EBadSwap); +``` + +To perform the actual swap: + +```move +transfer::transfer(o1, owner2); +transfer::transfer(o2, owner1); +``` + +Next, send `o1` to the original owner of `o2`, and send `o2` to the original owner of `o1`. The service can then delete the wrapping `SwapRequest` objects: + +```move +id1.delete(); +id2.delete(); +``` + +Finally, the service merges together `fee1` and `fee2` and returns the result. The service provider can turn the result into a coin or merge it into some larger pool where it collects all fees: + +```move +fee1.join(fee2); +``` + +After this call, the 2 objects are swapped and the service provider collects the service fee. + +Because the contract defined only 1 way to deal with `SwapRequest` (`execute_swap`), there is no other way the service operator can interact with `SwapRequest` despite its ownership. + +View the full source code in the [`trusted_swap`](https://github.com/MystenLabs/sui/blob/main/examples/move/trusted_swap) example. + +## Wrapping through `Option` + +When an object `Bar` is directly wrapped into `Foo`, there is not much flexibility, as a `Foo` object must have a `Bar` object in it, and to take out the `Bar` object you must destroy the `Foo` object. + +For more flexibility, the wrapping type might not always have the wrapped object in it and the wrapped object might be replaced with a different object at some point. + +To demonstrate this consider the following example for creating a warrior game character with a sword and shield. The warrior might have a sword and shield or it might not have either. The warrior should be able to add a sword and shield and replace the current ones at any time. To design this, define a `SimpleWarrior` type: + +```move +public struct SimpleWarrior has key { + id: UID, + sword: Option, + shield: Option, +} +``` + +Each `SimpleWarrior` type has an optional `sword` and `shield` wrapped in it, defined as: + +```move +public struct Sword has key, store { + id: UID, + strength: u8, +} + +public struct Shield has key, store { + id: UID, + armor: u8, +} +``` + +When you create a new warrior, set the `sword` and `shield` to `none` to indicate there is no equipment yet: + +```move +public fun create_warrior(ctx: &mut TxContext) { + let warrior = SimpleWarrior { + id: object::new(ctx), + sword: option::none(), + shield: option::none(), + }; + transfer::transfer(warrior, ctx.sender()) +} +``` + +You can then define functions to equip new swords or new shields: + +```move +public fun equip_sword(warrior: &mut SimpleWarrior, sword: Sword, ctx: &mut TxContext) { + if (warrior.sword.is_some()) { + let old_sword = warrior.sword.extract(); + transfer::transfer(old_sword, ctx.sender()); + }; + warrior.sword.fill(sword); +} +``` + +The function passes a `warrior` as a mutable reference of `SimpleWarrior`, and passes a `sword` by value to wrap it into the `warrior`. + +Because `Sword` is a Sui object type without `drop` ability, if the warrior already has a sword equipped, the warrior can't drop that sword. If you call `option::fill` without first calling `equip_sword`, an error occurs. `equip_sword` checks whether there is already a sword equipped and if so, removes it and sends it back to the sender. To the player, this returns an equipped sword to their inventory when they equip the different sword. + +View the source code in the [`simple_warrior`](https://github.com/MystenLabs/sui/tree/main/examples/move/simple_warrior) example or check out the [`hero`](https://github.com/MystenLabs/sui/tree/main/examples/move/hero) example for a more complex implementation. + +## Wrapping through `vector` + +The concept of wrapping objects in a `vector` field of another object is similar to wrapping through `Option`, as an object can contain 0, 1, or many wrapped objects of the same type. + +To wrap an object through `vector`: + +```move +public struct Pet has key, store { + id: UID, + cuteness: u64, +} + +public struct Farm has key { + id: UID, + pets: vector, +} +``` + +This example wraps a `vector` of `Pet` in `Farm` and can be accessed only through the `Farm` object. \ No newline at end of file diff --git a/docs/content/guides/developer/objects/transfers.mdx b/docs/content/guides/developer/objects/transfers.mdx new file mode 100644 index 0000000000000..db4a1b65175b5 --- /dev/null +++ b/docs/content/guides/developer/objects/transfers.mdx @@ -0,0 +1,16 @@ +--- +title: Transfers +description: Everything on Sui is an object. To use objects, they must be transferred between owners, which can be an address or another object. +keywords: [ object transfers, transferring objects, custom transfer rules, transfer to an object, transfer objects ] +--- + +The topics in this section explore the options for transferring objects on the network. + + + + + + + + + \ No newline at end of file diff --git a/docs/content/guides/developer/objects/transfers/custom-rules.mdx b/docs/content/guides/developer/objects/transfers/custom-rules.mdx new file mode 100644 index 0000000000000..9786dbaa351ef --- /dev/null +++ b/docs/content/guides/developer/objects/transfers/custom-rules.mdx @@ -0,0 +1,66 @@ +--- +title: Custom Transfer Rules +description: Custom transfer rules enable you to define a set of rules that must be met before Sui considers a transfer operation valid. +keywords: [ custom transfer rules, transfer rules, store ability, key ability, transfer restrictions, public transfer, custom transfer function, object transfer, transfer conditions, locked objects, unlocked objects, transfer policy, object abilities ] +--- + +Every Sui [object](/guides/developer/objects/object-model.mdx) must have the `key` ability. The `store` ability, on the other hand, is an optional ability you can add to Sui objects. Objects with the `store` ability are transferable by anyone using the [`transfer::public_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_transfer) function and can be [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) in other objects. + +For custom transfer rules, if the Sui object `Object` does not have the `store` ability, you cannot call the [`sui::transfer::public_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_transfer) function to transfer it. The Move module that defines `Object` is the only entity that can transfer objects of that type using the [`sui::transfer::transfer`](/references/framework/sui_sui/transfer#sui_transfer_transfer) function. Consequently, the module that defines the object `Object` can define a custom transfer function for `Object` that can take any number of arguments and enforce any restrictions desired for performing a transfer operation, for example, a fee must be paid to transfer the object. + +## `store` ability + +Custom transfer rules for objects enable you to define the transfer conditions that must be met for a valid transfer operation. You should be intentional about adding the `store` ability to an object because you are providing unrestricted access to that object without having to go through the module that defines it. + +:::caution + +After you enable public transfers on an object, there is no way of re-enabling custom transfer rules or any type of restrictions regarding the transfer of the object. + +::: + +## Example + +This example creates an object type `Object` that is transferrable only if the `unlocked` flag inside of it is set to `true`: + +```move +public struct Object has key { + id: UID, + // An `Object` object can only be transferred if this field is `true` + unlocked: bool, +} +``` + +Within the same module that defines the object `Object`, you can then define a custom transfer rule `transfer_unlocked` for `Object` that takes the object to transfer and the address to transfer it to, then verifies that the object is unlocked before transferring it to the specified address: + +```move +module examples::custom_transfer; + +// Error code for trying to transfer a locked object +const EObjectLocked: u64 = 0; + +public struct Object has key { + id: UID, + // An `Object` object can only be transferred if this field is `true` + unlocked: bool, +} + +// Check that `Object` is unlocked before transferring it +public fun transfer_unlocked(object: Object, to: address) { + assert!(object.unlocked, EObjectLocked); + transfer::transfer(object, to) +} +``` + +You can define multiple different transfer rules for the same object. Each of these rules might have different restrictions that execution of the transaction can dynamically enforce. If you wanted to allow only locked objects to be transferred to a specific address you could add the following function to the previous module: + +```move +const EObjectNotLocked: u64 = 1; +const HOME_ADDRESS = @0xCAFE; + +public fun transfer_locked(object: Object) { + assert!(!object.unlocked, EObjectNotLocked); + transfer::transfer(object, HOME_ADDRESS) +} +``` + +With these rules in place, there are different custom transfer rules for any object `Object`. Either it's unlocked and anyone can transfer it, or it's locked and it can only be transferred to `0xCAFE`. Importantly, these are the only ways of transferring any object of type `Object`. In particular, because `Object` does not have the `store` ability, you cannot transfer it using the [`sui::transfer::public_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_transfer) function. \ No newline at end of file diff --git a/docs/content/concepts/transfers/transfer-policies.mdx b/docs/content/guides/developer/objects/transfers/transfer-policies.mdx similarity index 84% rename from docs/content/concepts/transfers/transfer-policies.mdx rename to docs/content/guides/developer/objects/transfers/transfer-policies.mdx index 6e290ce4c5788..6ec85d2227355 100644 --- a/docs/content/concepts/transfers/transfer-policies.mdx +++ b/docs/content/guides/developer/objects/transfers/transfer-policies.mdx @@ -4,36 +4,33 @@ description: Learn about transfer policies on Sui. keywords: [ transfer, policy, transferpolicy, transfer receipt, transfer request, transferrequest ] --- -A [`TransferPolicy`](/references/framework/sui_sui/transfer_policy) on Sui is a customizable primitive that enables the owner of a type to create custom rules that define how the type can be transferred. You can use a `TransferPolicy` within a Sui Kiosk marketplace or any other system that integrates the `TransferPolicy` primitive. You can set any number of rules, such as paying a royalty or commission, and all must be satisfied in a single transaction for the transfer to succeed. +A [`TransferPolicy`](https://docs.sui.io/references/framework/sui_sui/transfer_policy) on Sui is a customizable primitive that enables the owner of a type to create custom rules that define how the type can be transferred. You can set any number of rules, such as paying a royalty or commission, and all must be satisfied in a single transaction for the transfer to succeed. A `TransferPolicy` can be used within a Sui Kiosk marketplace or any other system that integrates the `TransferPolicy` primitive. In a kiosk, creating and sharing a `TransferPolicy` makes the type `T` tradable in that kiosk. On every kiosk purchase, the `TransferPolicy` must confirm the `TransferRequest`, or the transaction fails. -## Transfer policies for kiosks - -When a kiosk purchase occurs, the system creates a `TransferRequest` _hot potato_, and only the matching `TransferPolicy` can confirm it to unblock the transaction. - -A kiosk can trade an item of type `T` only if the `TransferPolicy` for `T` exists and the buyer can access it. A purchase issues a `TransferRequest` that must be resolved in a matching `TransferPolicy`. If no policy exists or the buyer can't access it, the transaction fails. - -This system gives you maximum freedom and flexibility. By removing transfer policy logic from the trading primitive, only you can set policies, and you control all enforcement as long as the primitive is used. - ## `TransferPolicy` rules -By default, a single `TransferPolicy` enforces nothing and requires no user action. It confirms `TransferRequests` and therefore unblocks a transaction. However, the system allows setting _rules_. A rule is a way to request an additional action from the user before the request can be confirmed. - -The rule logic is straightforward: someone can publish a new rule module, such as a "fixed fee," and add it to the `TransferPolicy`. After the rule is added, `TransferRequest` needs to collect a `TransferReceipt` marking that the requirement specified in the rule was completed. - -### Example rule +By default, a single `TransferPolicy` enforces nothing and requires no user action. It confirms `TransferRequests` and therefore unblocks a transaction. However, the system allows setting rules. A rule is a way to request an additional action from the user before the request can be confirmed. -To implement VAT fees on every merchant transaction, you must introduce a rule. +The rule logic enables someone to publish a new rule module, such as a fixed fee and add it to the `TransferPolicy`. After the rule is added, `TransferRequest` needs to collect a `TransferReceipt` marking that the requirement specified in the rule was completed. A rule needs to provide 4 main components: 1. A `RuleWitness` struct, which uniquely identifies the rule. + 1. A `config` type which is stored in the `TransferPolicy` and is used to configure the rule. + 1. A set function which adds the rule to the `TransferPolicy`. The `TransferPolicyCap` holder must perform this action. + 1. An actionable function which adds the receipt into the `TransferRequest` and potentially adds to the `TransferPolicy` balance if the functionality involves some monetary transaction. -``` +The `TransferPolicy` module allows removing any rule at any time, as guaranteed by the constraints on the rule's configuration. + +### Example + +To demonstrate, consider the following example that implements [value-added tax (VAT) fees](https://en.wikipedia.org/wiki/Value-added_tax) on every merchant transaction by introducing a rule. + +```move module examples::dummy_rule { use sui::coin::Coin; use sui::sui::SUI; @@ -81,19 +78,27 @@ module examples::dummy_rule { } ``` -The `TransferPolicy` module allows removing any rule at any time, as guaranteed by the constraints on the rule's configuration. This example module has no configuration and accepts a `Coin` of any value. +### Use multiple transfer policies -### Royalty rules +You can create multiple policies for different purposes. For example, a default VAT policy requires everyone to use it. However, travelers leaving the country can claim a VAT refund without altering the default policy. + +To achieve this, you can issue a second `TransferPolicy` for the same type and wrap it in a custom object to implement the logic. For instance, a `TaxFreePolicy` object can bypass VAT. This object stores another `TransferPolicy`, accessible only if the buyer presents a valid `Passport` object. The inner policy might contain no rules, so the buyer pays no fees. + +This example module has no configuration and accepts a `Coin` of any value. + +## Royalty rules -To implement a percentage-based fee, such as royalties, a rule module must know the item's purchase price. The `TransferRequest` provides information that supports this and similar scenarios: +To implement a percentage-based fee, such as royalties, a rule module must know the item's purchase price. The `TransferRequest` provides the following information that supports this and similar scenarios: - Item ID -- Amount paid (SUI) -- From ID: The object that was used for selling, such as the kiosk -The `sui::transfer_policy` module provides public functions to access these fields: `paid()`, `item()`, and `from()`. +- Amount paid in SUI -``` +- The object ID that was used for selling, such as a kiosk + +The `sui::transfer_policy` module provides public functions to access the fields `paid()`, `item()`, and `from()`. + +```move module examples::royalty_rule { // skipping dependencies const MAX_BP: u16 = 10_000; @@ -136,11 +141,11 @@ module examples::royalty_rule { } ``` -#### Time-based rules +## Time-based rules Rules apply to more than just payments and fees. Some rules might permit trading only before or after a specific time. Because rules are not standardized, you can encode logic with any object. -``` +```move module examples::time_rule { // skipping some dependencies use sui::clock::{Self, Clock}; @@ -170,13 +175,13 @@ module examples::time_rule { } ``` -### `TransferRequest` receipts +## `TransferRequest` receipts The `TransferRequest` includes a field called `receipts`, which is a `VecSet` of `TypeName`. When the `confirm_request` call runs, the system compares the receipts against `TransferPolicy.rules`. If receipts do not match rules, the system rejects the request and the transaction fails. In the following example, both the rules and receipts are empty, so they match trivially and the request is confirmed: -``` +```move module sui::transfer_policy { // ... skipped ... @@ -206,15 +211,15 @@ module sui::transfer_policy { ## Witness policy -There are two ways to authorize an action: +There are 2 ways to authorize an action: 1. Static, by using a witness pattern. -1. Dynamic, via a capability pattern. +1. Dynamic, through a capability pattern. -Adding type parameters lets you create a generic rule that varies not just by configuration, but also by its type. +Adding type parameters lets you create a generic rule that varies not just by configuration, but also by its type: -``` +```move module examples::witness_rule { // skipping dependencies @@ -248,7 +253,7 @@ module examples::witness_rule { The `witness_rule` is generic and you can use it to require a custom witness depending on the settings. It is a way to link custom marketplace or trading logic to the `TransferPolicy`. With a slight modification, the rule can be turned into a generic capability requirement for any object, even a `TransferPolicy` for a different type or a `TransferRequest`. -``` +```move module examples::capability_rule { // skipping dependencies @@ -273,8 +278,10 @@ module examples::capability_rule { } ``` -## Using multiple transfer policies +## Policies for kiosks -You can create multiple policies for different purposes. For example, a default VAT policy requires everyone to use it. However, travelers leaving the country can claim a VAT refund without altering the default policy. +When a kiosk purchase occurs, the system creates a `TransferRequest` hot potato, and only the matching `TransferPolicy` can confirm it to unblock the transaction. -To achieve this, you can issue a second `TransferPolicy` for the same type and wrap it in a custom object to implement the logic. For instance, a `TaxFreePolicy` object can bypass VAT. This object stores another `TransferPolicy`, accessible only if the buyer presents a valid `Passport` object. The inner policy might contain no rules, so the buyer pays no fees. \ No newline at end of file +A kiosk can trade an item of type `T` only if the `TransferPolicy` for `T` exists and the buyer can access it. A purchase issues a `TransferRequest` that must be resolved in a matching `TransferPolicy`. If no policy exists or the buyer can't access it, the transaction fails. + +This system gives you maximum freedom and flexibility. By removing transfer policy logic from the trading primitive, only you can set policies, and you control all enforcement as long as the primitive is used. diff --git a/docs/content/concepts/transfers/transfer-to-object.mdx b/docs/content/guides/developer/objects/transfers/transfer-to-object.mdx similarity index 55% rename from docs/content/concepts/transfers/transfer-to-object.mdx rename to docs/content/guides/developer/objects/transfers/transfer-to-object.mdx index 7719d1d85d4b0..b3bfa47729766 100644 --- a/docs/content/concepts/transfers/transfer-to-object.mdx +++ b/docs/content/guides/developer/objects/transfers/transfer-to-object.mdx @@ -1,37 +1,40 @@ --- title: Transfer to Object description: On Sui, you can transfer objects to objects in the same way you can transfer objects to addresses. +keywords: [ transfer to object, object transfer, transfer objects, parent-child relationship, object ownership, receiving objects, transfer receive, object id, stable id, custom receiving rules, soul-bound objects, dynamic fields, shared objects, programmable transaction blocks, PTB, receiving argument ] --- -You can transfer objects to an object ID in the same way you transfer objects to an address, using the same functions. This is because Sui does not distinguish between the 32-byte ID of an address and the 32-byte ID of an object (which are guaranteed not to overlap). The transfer to object operation takes advantage of this feature, allowing you to provide an object ID as the address input of a transfer operation. +You can transfer objects to an object ID in the same way you transfer objects to an address. This is because Sui does not distinguish between the 32-byte ID of an address and the 32-byte ID of an object, which are guaranteed not to overlap. The transfer to object operation takes advantage of this feature, allowing you to provide an object ID as the address input of a transfer operation. -:::note +All functionality around [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) objects works the same for objects owned by other objects. -The Transfer to Object mechanism is not supported for [Party Objects](../object-ownership/party.mdx) whose owning address corresponds to an object ID. +When you transfer an object to another object, you're establishing a form of parent-child authentication relationship. Objects that you have transferred to another object can be received by the owner of the parent object. The module that defines the type of the parent object also defines the access control for receiving a child object. -::: +These restrictions for accessing sent child objects are enforced dynamically by providing mutable access to the parent object's `UID` during the execution of the transaction. Because of this, you can transfer objects to and receive them from [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) objects, [dynamic field](/concepts/dynamic-fields.mdx) objects, [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) objects, and [shared](/guides/developer/objects/object-ownership/shared.mdx) objects. -Because of the identical ID structure, you can use an object ID for the address field when transferring an object. In fact, all functionality around address-owned objects works the same for objects owned by other objects, you just replace the address with the object ID. +:::info -When you transfer an object to another object, you're basically establishing a form of parent-child authentication relationship. Objects that you have transferred to another object can be received by the (possibly transitive) owner of the parent object. The module that defines the type of the parent (receiving) object also defines the access control for receiving a child object. +The transfer to object mechanism is not supported for [party objects](/guides/developer/objects/object-ownership/party.mdx) whose owning address corresponds to an object ID. -These restrictions for accessing sent child objects are enforced dynamically by providing mutable access to the parent object's `UID` during the execution of the transaction. Because of this, you can transfer objects to and receive them from owned objects, dynamic field objects, wrapped objects, and shared objects. +::: -One of the benefits of the transfer to object operation is the ability to have a stable ID for an on-chain wallet or account, for example. The transfer of the object doesn't affect its ID, regardless of the state of the object that you send it to. When you transfer an object, all of that object's child objects move with it, and the object's address remains the same whether you transfer it, wrap it, or hold it as a dynamic field. +A benefit of the transfer to object operation is the ability to have a stable ID for an on-chain wallet or account. Transferring an object doesn't affect its ID, regardless of the state of the object that you send it to. When you transfer an object, all of that object's child objects move with it, and the object's address remains the same whether you transfer it, wrap it, or hold it as a dynamic field. -## Transferring to object +## Considerations for transferring to objects -Just like with normal object transfers, you must make sure that the object ID exists that you are transferring the object to. Additionally, make sure that the object that you are transferring to is not immutable. You can't access an object transferred to an immutable object. +Make sure that the object ID exists that you are transferring the object to. Additionally, confirm that the object that you are transferring to is not immutable. You can't access an object transferred to an immutable object. -Be aware of both the type of the object you are transferring to and the object that is being transferred. The object that is transferred to (parent) can _always_: +Be aware of both the type of the object you are transferring to and the object that is being transferred. The object that is transferred to can always: - Define predicates that can be dynamically checked to access the sent object. + - Lack support for accessing objects that have been sent to it. Future versions of that package might support this functionality, but it's up to the package author to include it. If the object being transferred has the `key` ability only, then: -- The module that defines the object that is being transferred must implement a custom receive function for it, similar to custom transfer functions. Just as with custom transfer functions, a custom receivership function might have arbitrary restrictions they can enforce and that you should be aware of, or they may not exist. -- After sending, you can't access or use the object unless the parent object's (object being sent to) module has defined a function to receive objects _and_ the child object's (object you're sending) module has defined a function to receive the object, and the restrictions that _both_ functions define are met. +- The module that defines the object being transferred must implement a custom receive function for it, similar to custom transfer functions. Just as with custom transfer functions, a custom receiver function might have arbitrary restrictions they can enforce and that you should be aware of, or they might not exist. + +- After sending, you can't access or use the object unless the parent object's module has defined a function to receive objects, the child object's module has defined a function to receive the object, and the restrictions defined by both functions are met. ```move // 0xADD is an address @@ -45,7 +48,9 @@ transfer::public_transfer(b, @0xADD); transfer::public_transfer(c, @0x0B); ``` -Transferring an object to an object ID results in the same result as if you transferred the object to an address - the object's owner is the 32-byte address or object ID provided. Additionally, because there is no difference in the result of the object transfer, you can use existing RPC methods such as `getOwnedObjects` on the 32-byte ID. If the ID represents an address, then the method returns the objects owned by that address. If the ID is an object ID, then the method returns the objects the object ID owns (transferred objects). +## RPC methods + +Because there is no difference in the result of the object transfer, you can use existing RPC methods such as `getOwnedObjects` on the 32-byte ID. If the ID represents an address, then the method returns the objects owned by that address. If the ID is an object ID, then the method returns the objects the object ID owns. ```json // Get the objects owned by the address 0xADD. Returns `b`. @@ -69,11 +74,11 @@ Transferring an object to an object ID results in the same result as if you tran } ``` -## Receiving objects +## Receive objects -After an object `c` has been sent to another object `p`, `p` must then receive `c` to do anything with it. To receive the object `c`, a `Receiving(o: ObjectRef)` argument type for programmable transaction blocks (PTBs) is used that takes an object reference containing the to-be-received object's `ObjectID`, `Version`, and `Digest` (just as owned object arguments for PTBs do). However, `Receiving` PTB arguments are not passed as an owned value or mutable reference within the transaction. +An object must receive another object before it can use it. To receive the object, use a `Receiving(o: ObjectRef)` argument type in a [programmable transaction block (PTB)](/concepts/transactions/prog-txn-blocks.mdx). This argument takes an object reference containing the object's `ObjectID`, `Version`, and `Digest`, similar to address-owned object arguments. However, `Receiving` PTB arguments are not passed as an owned value or mutable reference within the transaction. -To explain further, look at the core of the receiving interface in Move, which is defined in the `transfer` module in the Sui framework: +The core receiving interface in Move is defined in the [`transfer`](https://docs.sui.io/references/framework/sui_sui/transferx) module in the Sui framework: ```move module sui::transfer; @@ -104,84 +109,20 @@ public native fun public_receive(parent: &mut UID, object: Recei ... ``` -Each Receiving argument referring to a sent object of type `T` in a PTB results in exactly one argument with a Move type of `sui::transfer::Receiving`. You can then use this argument to receive the sent object of type `T` with the `transfer::receive` function. - -When you call the `transfer::receive` function, you must pass a mutable reference to the parent object's `UID`. You can't get a mutable reference to the `UID` of an object, though, unless the defining module of the object exposes it. Consequently, the module that defines the type of the parent object that is receiving the child object defines access control policies and other restrictions on receiving objects that are sent to it. See the [authorization example](#receive-shared-example) for a demonstration of this pattern. The fact that the passed-in `UID` actually owns the object referenced by the `Receiving` parameter is dynamically checked and enforced. This allows access to objects that have been sent to, for example, dynamic fields where the ownership chain can only be established dynamically. - -Because `sui::transfer::Receiving` has only the `drop` ability, the existence of a `Receiving` argument represents the ability, but not the obligation to receive the object of type `T` specified by the object reference in the PTB `Receiving` argument during that transaction. You can use some, none, or all `Receiving` arguments in a PTB without issue. Any object that corresponds to a `Receiving` argument remains untouched (in particular, its object reference remain the same) unless it is received. - -## Custom receiving rules - -Just like with [custom transfer policies](./custom-rules.mdx), Sui allows for the definition of custom receivership rules for `key`-only objects. In particular, you can use the `transfer::receive` function only on objects defined in the same module as the call to `transfer::receive`--just like you can use the `transfer::transfer` function only on objects defined in the module where it's being used. - -Similarly for objects that also have the `store` ability, anyone can use the `transfer::public_receive` function to receive them--just like `transfer::public_transfer` can transfer any objects that have the `store` ability on them. - -This coupled with the fact that the parent object can always define custom rules around receivership means that you must consider the following matrix of permissions around receiving objects and the abilities of the object being sent based on the child object's abilities: - -| Child abilities | Parent can restrict access | Child can restrict access | -| --------------- | -------------------------- | ------------------------- | -| `key` | Yes | Yes | -| `key` + `store` | Yes | No | - -Just like with custom transfer policies, you can use and couple these restrictions to create powerful expressions. For example, you can implement [soul-bound objects](#soul-bound-example) using both custom transfer and receivership rules. - -## Using SDKs - -When creating transactions, you interact with `Receiving` transaction inputs almost exactly as you would with other object arguments in the Sui TypeScript SDK. For example, if in the [Simple Account](#simple-account) example that follows you want to send a transaction that receives a coin object with ID `0xc0ffee` that was sent to your account at `0xcafe`, you can do the following using either the Sui TypeScript SDK or Sui Rust SDK: - - - - - -```ts -... // Setup TypeScript SDK as normal. -const tx = new Transaction(); -tx.moveCall({ - target: `${examplePackageId}::account::accept_payment`, - arguments: [tx.object("0xcafe"), tx.object("0xc0ffee")] -}); -const result = await client.signAndExecuteTransaction({ - transaction: tx, - }); -... -``` - - - - - -```rust -... // setup Rust SDK client as normal -client - .transaction_builder() - .move_call( - sending_account, - example_package_id, - "account", - "accept_payment", - vec!["0x2::sui::SUI"], - vec![ - SuiJsonValue::from_object_id("0xcafe"), - SuiJsonValue::from_object_id("0xc0ffee") // 0xcoffee is turned into the `Receiving<...>` argument of `accept_payment` by the SDK - ]) - ... -``` - - +Each `Receiving` argument referring to a sent object of type `T` in a PTB results in exactly 1 argument with type `sui::transfer::Receiving`. You can then use this argument to receive the sent object of type `T` with the `transfer::receive` function. - +When you call the `transfer::receive` function, you must pass a mutable reference to the parent object's `UID`. You can't get a mutable reference to the `UID` of an object unless the defining module of the object exposes it. Consequently, the module that defines the type of the parent object receiving the child object defines access control policies and other restrictions on receiving objects that are sent to it. See the [authorization example](#receive-shared-example) for a demonstration of this pattern. -Additionally, just as with object arguments that also have an `ObjectRef` constructor where you can provide an explicit object ID, version, and digest, there is also a `ReceivingRef` constructor that takes the same arguments corresponding to a receiving argument. +The passed-in `UID` owns the object referenced by the `Receiving` parameter and is dynamically checked and enforced. This allows access to objects where the ownership chain can only be established dynamically. -## Examples +Because `sui::transfer::Receiving` has only the `drop` ability, the existence of a `Receiving` argument represents the ability, but not the obligation to receive the object of type `T` specified in the PTB `Receiving` argument during that transaction. You can use some, none, or all `Receiving` arguments in a PTB. Any object that corresponds to a `Receiving` argument remains untouched. In particular, its object reference remains the same unless it is received. -The following examples demonstrate receiving previously sent objects. -### Receiving objects from shared objects {#receive-shared-example} +### Receive objects from shared objects {#receive-shared-example} -Generally, if you want to allow receiving sent objects from shared objects that are defined in the module, add dynamic authorization checks; otherwise, anyone could receive sent objects. In this example, a shared object (`SharedObject`) holds a counter that anyone can increment, but only the address `0xB0B` can receive objects from the shared object. +Generally, if you want to allow receiving objects from [shared](/guides/developer/objects/object-ownership/shared.mdx) objects that are defined in the module, add dynamic authorization checks. Otherwise, anyone could receive sent objects. In this example, a shared object (`SharedObject`) holds a counter that anyone can increment, but only the address `0xB0B` can receive objects from the shared object. -Because the `receive_object` function is generic over the object being received, it can only receive objects that are both `key` and `store`. `receive_object` must also use the `transfer::public_receive` function to receive the object and not `transfer::receive` because you can only use `receive` on objects defined in the current module. +Because the `receive_object` function is generic over the object being received, it can only receive objects that are both `key` and `store`. `receive_object` must also use the [`transfer::public_receive`](/references/framework/sui_sui/transfer#sui_transfer_public_receive) function to receive the object and not [`transfer::receive`](/references/framework/sui_sui/transfer#sui_transfer_receive) because you can only use `receive` on objects defined in the current module. ```move module examples::shared_object_auth; @@ -221,11 +162,11 @@ public fun receive_object( } ``` -### Receiving objects and adding them as dynamic fields {#simple-account} +### Receive objects and add them as dynamic fields {#simple-account} This example defines a basic account-type model where an `Account` object holds its coin balances in different dynamic fields. This `Account` is also transferable to a different address or object. -Importantly, the address that coins are to be sent with this `Account` object remains the same regardless of whether the `Account` object is transferred, wrapped (for example, in an escrow account), or moved into a dynamic field. In particular, there is a stable ID for a given `Account` object across the object's lifecycle, regardless of any ownership changes. +Importantly, the address that coins are to be sent with this `Account` object remains the same regardless of whether the `Account` object is transferred, [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx), or moved into a [dynamic field](/concepts/dynamic-fields.mdx). In particular, there is a stable ID for a given `Account` object across the object's lifecycle, regardless of any ownership changes. ```move module examples::account; @@ -278,7 +219,7 @@ public fun withdraw(account: &mut Account, amount: u64, ctx: &mut TxContext): } /// Can transfer this account to a different address -/// (e.g., to an object or address). +/// (for example, to an object or address). public fun transfer_account(account: Account, to: address, _ctx: &mut TxContext) { // Perform some authorization checks here and if they pass then transfer the account // ... @@ -286,11 +227,74 @@ public fun transfer_account(account: Account, to: address, _ctx: &mut TxContext) } ``` -### Soul-bound objects {#soul-bound-example} +### Custom receiving rules + +Just like with [custom transfer policies](/guides/developer/objects/transfers/custom-rules.mdx), Sui allows for the definition of custom receivership rules for `key`-only objects. In particular, you can use the `transfer::receive` function only on objects defined in the same module as the call to [`transfer::receive`](/references/framework/sui_sui/transfer#sui_transfer_receive), just like you can use the [`transfer::transfer`](/references/framework/sui_sui/transfer#sui_transfer_transfer) function only on objects defined in the module where it's being used. + +Similarly for objects that also have the `store` ability, anyone can use the [`transfer::public_receive`](/references/framework/sui_sui/transfer#sui_transfer_public_receive) function to receive them, just like [`transfer::public_transfer`](/references/framework/sui_sui/transfer#sui_transfer_public_transfer) can transfer any objects that have the `store` ability on them. + +This coupled with the fact that the parent object can always define custom rules around receivership means that you must consider the following matrix of permissions around receiving objects and the abilities of the object being sent based on the child object's abilities: + +| Child abilities | Parent can restrict access | Child can restrict access | +| --------------- | -------------------------- | ------------------------- | +| `key` | Yes | Yes | +| `key` + `store` | Yes | No | + +You can use and couple these restrictions to create powerful expressions. For example, you can implement [soul-bound objects](#soul-bound-example) using both custom transfer and receivership rules. + +## TypeScript and Rust SDKs + +Interact with `Receiving` transaction inputs as you would with other object arguments in the Sui TypeScript SDK. For example, if in the [Simple Account](#simple-account) example you want to send a transaction that receives a coin object with ID `0xc0ffee` that was sent to your account at `0xcafe`, you can do the following using either the [Sui TypeScript SDK](https://sdk.mystenlabs.com/typescript) or [Sui Rust SDK](https://github.com/MystenLabs/sui/tree/main/crates/sui-sdk): + + -The ability to control the rules about how and when an object can be received, and how and when it can be transferred allows us to define a type of _soul-bound_ object that can be used by value in a transaction, but it must always stay in the same place, or be returned to the same object. + -You can implement a simple version of this with the following module where the `get_object` function receives the soul-bound object and creates a receipt that must be destroyed in the transaction in order for it to execute successfully. However, in order to destroy the receipt, the object that was received must be transferred back to the object it was received from in the transaction using the `return_object` function. +```ts +... // Setup TypeScript SDK as normal. +const tx = new Transaction(); +tx.moveCall({ + target: `${examplePackageId}::account::accept_payment`, + arguments: [tx.object("0xcafe"), tx.object("0xc0ffee")] +}); +const result = await client.signAndExecuteTransaction({ + transaction: tx, + }); +... +``` + + + + + +```rust +... // setup Rust SDK client as normal +client + .transaction_builder() + .move_call( + sending_account, + example_package_id, + "account", + "accept_payment", + vec!["0x2::sui::SUI"], + vec![ + SuiJsonValue::from_object_id("0xcafe"), + SuiJsonValue::from_object_id("0xc0ffee") // 0xcoffee is turned into the `Receiving<...>` argument of `accept_payment` by the SDK + ]) + ... +``` + + + + + +Object arguments can also have an `ObjectRef` constructor where you can provide an explicit object ID, version, and digest. There is also a `ReceivingRef` constructor that takes the same arguments corresponding to a receiving argument. + +## Soul-bound objects {#soul-bound-example} + +The ability to control the rules about how and when an object can be received and transferred allows you to define a type of [soul-bound](https://www.coinbase.com/learn/crypto-glossary/what-are-soulbound-tokens-sbt) object. Soul-bound objects can be used by value in a transaction, but it must always stay in the same place or be returned to the same object. + +You can implement a simple version of this with the following module where the `get_object` function receives the soul-bound object and creates a receipt that must be destroyed in the transaction for it to execute successfully. However, to destroy the receipt, the object that was received must be transferred back to the object it was received from in the transaction using the `return_object` function. ```move module examples::soul_bound; @@ -340,4 +344,4 @@ public fun return_object(soul_bound: SoulBound, receipt: ReturnReceipt) { assert!(object::id(&soul_bound) == object_id, EWrongObject); transfer::transfer(soul_bound, return_to); } -``` +``` \ No newline at end of file diff --git a/docs/content/guides/developer/objects/versioning.mdx b/docs/content/guides/developer/objects/versioning.mdx new file mode 100644 index 0000000000000..1e44eae9bfa83 --- /dev/null +++ b/docs/content/guides/developer/objects/versioning.mdx @@ -0,0 +1,184 @@ +--- +title: Object and Package Versioning +description: Versioning provides the ability to upgrade packages and objects on the Sui network. +keywords: [ object versioning, object versions, versioning objects, package versioning, package versions, versioning packages ] +--- + +Every object stored on-chain is referenced by an ID and version. When a transaction modifies an object, it writes the new contents to an on-chain reference with the same ID but a new version. This means that a single object (with ID `I`) might appear in multiple entries in the distributed store: + +``` +(I, v0) => ... +(I, v1) => ... # v0 < v1 +(I, v2) => ... # v1 < v2 +``` + +Despite appearing multiple times in the store, only the latest version of the object is available to transactions. Moreover, only one transaction can modify the object at that version to create a new version, guaranteeing a linear history, such that `v1` was created in a state where `I` was at `v0`, and `v2` was created in a state where `I` was at `v1`. + +Versions are strictly increasing and (`ID`, `version`) pairs are never re-used. This structure allows [node operators](/guides/operator.mdx) to prune their stores of old object versions that are now inaccessible, if they choose. This is not a requirement, though, as node operators might keep prior object versions to serve requests for an object's history, either from other nodes that are catching up or from RPC requests. + +## Move objects + +Sui uses [Lamport timestamps](https://en.wikipedia.org/wiki/Lamport_timestamp) in its versioning algorithm for objects. The use of Lamport timestamps guarantees that versions never get re-used as the new version for objects touched by a transaction is 1 greater than the latest version among all input objects to the transaction. For example, a transaction transferring an object `O` at version 5 using a [gas object](/concepts/tokenomics/gas-in-sui.mdx) `G` at version 3 updates both `O` and `G` versions to `1 + max(5, 3) = 6`, creating version 6. + +### Address-owned objects + +You must reference [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) transaction inputs at a specific ID and version. When a validator signs a transaction with an address-owned object input at a specific version, that version of the object is locked to that transaction. Validators reject requests to sign other transactions that require the same input (same ID and version). + +If two sets of validators each sign different transactions using the same object, that object becomes [equivocated] and cannot be used for any further transactions in that [epoch]. This happens because neither transaction can reach consensus without a validator who has already committed the object to the other transaction. All locks reset at the end of the epoch, freeing the objects again. + +:::info + +Only an object's owner can equivocate it, but this is not a desirable thing to do. You can avoid equivocation by carefully managing the versions of [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) input objects. + +Never attempt to execute 2 different transactions that use the same object. If you don't get a definite success or failure response from the network for a transaction, assume that the transaction might have gone through, and do not re-use any of its objects for different transactions. + +::: + +### Immutable objects + +Like [address-owned](/guides/developer/objects/object-ownership/address-owned.mdx) objects, you reference [immutable](/guides/developer/objects/object-ownership/immutable.mdx) objects at an ID and version, but they do not need to be locked as their contents and versions do not change. Their version is relevant because they could have started as an address-owned object before being frozen. The given version identifies the point at which they became immutable. + +### Shared objects + +Specifying a [shared](/guides/developer/objects/object-ownership/shared.mdx) transaction input is slightly more complex. You reference it by its ID, the version it was shared at, and a flag indicating whether it is accessed mutably. You don't specify the precise version the transaction accesses because consensus decides that during transaction scheduling. When scheduling multiple transactions that touch the same [shared](/guides/developer/objects/object-ownership/shared.mdx) object, validators agree the order of those transactions, and pick each transaction's input versions for the shared object accordingly. 1 transaction's output version becomes the next transaction's input version, and so on. + +Shared transaction inputs that you reference immutably participate in scheduling, but don't modify the object or increment its version. + +### Wrapped objects + +You can't access [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) objects by their ID in the object store. You must access them by the object that wraps them. Consider the following example that creates a `make_wrapped` function with an `Inner` object, wrapped in an `Outer` object, which is returned to the transaction sender. + +```move +module example::wrapped { + use sui::object::{Self, UID}; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + struct Inner has key, store { + id: UID, + x: u64, + } + + struct Outer has key { + id: UID, + inner: Inner, + } + + entry fun make_wrapped(ctx: &mut TxContext) { + let inner = Inner { + id: object::new(ctx), + x: 42, + }; + + let outer = Outer { + id: object::new(ctx), + inner, + }; + + transfer::transfer(outer, tx_context::sender(ctx)); + } +} +``` + +The owner of `Outer` in this example must specify it as the transaction input and then access its `inner` field to read the instance of `Inner`. Validators refuse to sign transactions that directly specify [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) objects, like the `inner` of an `Outer`, as inputs. As a result, you don't need to specify a wrapped object's version in a transaction that reads that object. + +Wrapped objects can eventually become unwrapped, at which they are once again accessible by their ID: + +```move +module example::wrapped { + // ... + + entry fun unwrap(outer: Outer, ctx: &TxContext) { + let Outer { id, inner } = outer; + object::delete(id); + transfer::transfer(inner, tx_context::sender(ctx)); + } +} +``` + +The `unwrap` function takes an instance of `Outer`, destroys it, and sends the `Inner` back to the sender. After calling this function, the previous owner of `Outer` can access `Inner` directly by its ID because it is now unwrapped. Wrapping and unwrapping of an object happens multiple times across its lifespan. The object retains its ID across all events. + +The Lamport timestamp-based versioning scheme ensures that the version an object is unwrapped at is always greater than the version it was wrapped at. + +- After a transaction, `W`, where object `I` is wrapped by object `O`, the `O` version is greater than or equal to the `I` version. This means 1 of the following conditions is true: + + 1. `I` is an input so has a strictly lower version. + + 1. `I` is new and has an equal version. + +- After a later transaction unwrapping `I` out of `O`, the following must be true: + + - The `O` input version is greater than or equal to its version after `W` because it is a later transaction, so the version can only have increased. + + - The `I` version in the output must be strictly greater than the `O` input version. + +This leads to the following chain of inequalities for `I`'s version before wrapping: + +This means that `I`'s version is less than or equal to `O`'s version after wrapping, less than or equal to `O`'s version before unwrapping, and less than `I`'s version after unwrapping + +The `I` version before wrapping is less than the `I` version after unwrapping. + +### Dynamic fields + +From a versioning perspective, values held in [dynamic fields](/concepts/dynamic-fields.mdx) behave like [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) objects. They are only accessible through the field's parent object, not as direct transaction inputs. You do not need to supply their IDs or versions with the transaction inputs. + +Lamport timestamp-based versioning makes sure that when a field contains an object and a transaction removes that field, its value becomes accessible by its ID and the value's version has been incremented to a previously unused version. + +:::info + +A distinction between [dynamic fields](/concepts/dynamic-fields.mdx) and [wrapped](/guides/developer/objects/object-ownership/wrapped.mdx) objects is that if a transaction modifies a dynamic object field, its version is incremented in that transaction, where a wrapped object's version would not be. + +::: + +Adding a new dynamic field to a parent object also creates a `Field` object, responsible for associating the field name and value with that parent. Unlike other newly created objects, the ID for the resulting instance of `Field` is not created using `sui::object::new`. Instead, it is computed as a hash of the parent object ID and the type and value of the field name, so that you can use it to look up the `Field` through its parent and name. + +When you remove a field, Sui deletes its associated `Field`, and if you add a new field with the same name, Sui creates a new instance with the same ID. Versioning using Lamport timestamps coupled with [dynamic fields](/concepts/dynamic-fields.mdx) being only accessible through their parent object ensures that (`ID`, `version`) pairs are not reused in the process. + +The transaction that deletes the original field increments the parent's version to be greater than the deleted field's version. The transaction that creates the new version of the same field creates the field with a version that is greater than the parent's version. + +The version of the new `Field` instance is greater than the version of the deleted `Field`. + +## Packages + +[Move packages](/concepts/sui-move-concepts/packages.mdx) are also versioned and stored on-chain, but follow a different versioning scheme to objects because they are immutable from their inception. This means that you refer to package transaction inputs by just their ID as they are always loaded at their latest version. + +### User packages + +Every time you publish or upgrade a package, Sui generates a new ID. A newly published package has its version set to 1, whereas an upgraded package's version is 1 greater than the package it is upgrading. Unlike objects, older versions of a package remain accessible even after being upgraded. For example, imagine a package `P` that is published and upgraded twice. It might be represented in the store as: + +``` +(0x17fb7f87e48622257725f584949beac81539a3f4ff864317ad90357c37d82605, 1) => P v1 +(0x260f6eeb866c61ab5659f4a89bc0704dd4c51a573c4f4627e40c5bb93d4d500e, 2) => P v2 +(0xd24cc3ec3e2877f085bc756337bf73ae6976c38c3d93a0dbaf8004505de980ef, 3) => P v3 +``` + +In this example, all 3 versions of the same package are at different IDs. The packages have increasing versions but it is possible to call into `v1`, even though `v2` and `v3` exist on chain. + +### Framework packages + +Framework packages, such as the [Move standard library](https://move-book.com/reference/) at `0x1`, the [Sui framework library](https://docs.sui.io/references/framework/sui_sui) at `0x2`, [Sui system library](https://docs.sui.io/references/framework/sui_system) at `0x3` and [DeepBook](/standards/deepbook.mdx) at `0xdee9`, are a special case because their IDs must remain stable across upgrades. The network can upgrade framework packages while preserving their IDs through a system transaction, but can only perform this operation on epoch boundaries because they are considered immutable like other packages. New versions of framework packages retain the same ID as their predecessor, but increment their version by 1: + +``` +(0x1, 1) => MoveStdlib v1 +(0x1, 2) => MoveStdlib v2 +(0x1, 3) => MoveStdlib v3 +``` + +The prior example shows the on-chain representation of the first 3 versions of the Move standard library. + +### Package versions + +Before someone can use an on-chain package, you must publish its first, original version. When you upgrade a package, you create a new version of that package. Each upgrade of a package is based on the immediately preceding version of that package in the versions history. In other words, you can upgrade the `nth` version of a package from only the `nth - 1` version. For example, you can upgrade a package from version 1 to 2, but afterwards you can only upgrade that package from version 2 to 3. + +There is a notion of versioning in package manifest files, existing in both the package section and in the dependencies section. For example, consider the manifest code that follows: + +```toml +[package] +name = "some_pkg" +version = "1.0.0" + +[dependencies] +another_pkg = { git = "https://github.com/another_pkg/another_pkg.git" , version = "2.0.0"} +``` + +The version references in the manifest are used only for user-level documentation as the `publish` and `upgrade` commands do not leverage this information. If you publish a package with a certain package version in the manifest file and then modify and re-publish the same package with a different version, using `publish` command rather than `upgrade` command, they are considered different packages. You cannot use any of these packages as a dependency override to stand in for the other. While you can specify this type of override when building a package, it results in an error when publishing or upgrading on-chain. \ No newline at end of file diff --git a/docs/content/guides/developer/sui-101.mdx b/docs/content/guides/developer/sui-101.mdx index 0632134be1a2c..345177aadab14 100644 --- a/docs/content/guides/developer/sui-101.mdx +++ b/docs/content/guides/developer/sui-101.mdx @@ -8,7 +8,7 @@ pagination_prev: null Follow these guides to learn about essential Sui concepts. - + diff --git a/docs/content/guides/developer/sui-101/object-ownership.mdx b/docs/content/guides/developer/sui-101/object-ownership.mdx deleted file mode 100644 index b0d2f81783209..0000000000000 --- a/docs/content/guides/developer/sui-101/object-ownership.mdx +++ /dev/null @@ -1,198 +0,0 @@ ---- -title: Object Ownership -description: On Sui, object ownership can be represented in different ways. Weigh the benefits of each to decide the best approach for your project. -keywords: [ shared objects, owned objects, object types, locked, key, types of objects, shared, owned, address-owned ] ---- - -Objects on Sui can be versioned either through the fastpath or through consensus. The choice between these 2 paths affects your options for object ownership, as well as the performance and operational complexity of your application. Many applications can be built using a solution that either uses consensus objects or only fastpath objects, with trade-offs for each that need to be weighed. - -Fastpath objects must be owned by a single address (or immutable). This makes it complicated to write some applications that require multiple parties or nodes to access the same object. Access to very hot fastpath objects needs to be coordinated off-chain. However, transactions that use the fastpath benefit from very low latency to finality. - -Consensus objects can be owned by a single address (party objects) or globally accessible for read and writes by any transaction (shared objects). Transactions that access 1 or more consensus objects require consensus to sequence reads and writes to those objects. This makes version management simpler, especially for heavily accessed objects, but results in a slightly higher gas cost and latency. - -Transactions that access multiple consensus objects, or particularly popular objects, might have increases in latency due to contention. However, the advantage of using consensus objects lies in the flexibility of allowing multiple addresses to access the same object in a coordinated manner. - -To summarize, applications that are extremely sensitive to latency or gas costs, that do not need to handle complex multi-party transactions, or that already require an off-chain service could benefit from a design that only uses fastpath objects. Applications that require coordination between multiple parties typically benefit from using shared objects through consensus. - -For more detailed information on the types of objects that Sui supports, see [Object Ownership](/concepts/object-ownership.mdx). - -## Example: Escrow - -The Escrow example demonstrates the trade-offs between consensus objects and fastpath objects by implementing the same application in both styles. Both styles of the example implement a service that enables a trustless swap of objects between 2 addresses (a "trade"), with the service holding those objects in escrow. - -### `Locked` and `Key` - -[Code Sample](https://github.com/MystenLabs/sui/blob/93e6b4845a481300ed4a56ab4ac61c5ccb6aa008/examples/move/escrow/sources/lock.move) - -Both implementations use a primitive for locking values, which offers the following interface: - -```move -module escrow::lock { - public fun lock(obj: T, ctx: &mut TxContext): (Locked, Key); - public fun unlock(locked: Locked, key: Key): T -} -``` - -Any `T: store` can be locked to get a `Locked` and a corresponding `Key`, and conversely, the locked value and its corresponding key can be consumed to get back the wrapped object. - -The important property that this interface provides is that locked values cannot be modified except by unlocking them first (and later relocking them). Because unlocking consumes the key, tampering with a locked value can be detected by remembering the ID of the key that it was locked with. This prevents situations where a party in a swap changes the object they are offering to reduce its value. - -### Address-owned objects (fastpath) - -
- -`owned.move` - - - - -
- -The protocol for swapping through escrow implemented using address-owned objects starts with both parties locking their respective objects. - -```mermaid -flowchart TD - SR[Locked<fa:fa-wrench S>, fa:fa-key key_s] - BR[Locked<fa:fa-coins B>, fa:fa-key key_b] - - subgraph Seller - a2(fa:fa-wrench S)--escrow::lock-->SR - end - - subgraph Buyer - a1(fa:fa-coins B)--escrow::lock-->BR - end -``` - -This is used to prove that the object has not been tampered with after the swap has been agreed to. If either party doesn't want to proceed at this stage, they just unlock their object. - -Assuming both parties are happy to continue, the next step requires both parties to swap the keys. - -```mermaid -flowchart LR - Buyer--fa:fa-key key_b-->Seller - Seller--fa:fa-key key_s-->Buyer -``` - -A third party acts as custodian. The custodian holds objects that are waiting for their counterparts to arrive, and when they arrive, it matches them up to complete the swap. - - - -```mermaid -flowchart TB - S["fa:fa-key key_s, - Locked<fa:fa-wrench S>, - exchange_key: fa:fa-key key_b, - recipient: Buyer - "] - B["fa:fa-key key_b, - Locked<fa:fa-coins B>, - exchange_key: fa:fa-key key_s, - recipient: Seller - "] - - id1(Escrow<fa:fa-coins B>)-->Third_Party - id2(Escrow<fa:fa-wrench S>)-->Third_Party - subgraph Buyer - direction TB - B--create-->id1 - end - - subgraph Seller - direction TB - S--create-->id2 - end -``` - -The `create` function prepares the `Escrow` request and sends it to the `custodian`. The object being offered by this party is passed in, locked, with its key, and the object being requested is identified by the ID of the key it was locked with. While preparing the request, the offered object is unlocked while remembering the ID of its key. - -Although the custodian is trusted to preserve liveness (to complete swaps if it owns both sides of a swap and to return objects if requested), all other correctness properties are maintained in Move: Even though the custodian owns both objects being swapped, the only valid action they are permitted to take is to match them up with their correct counterpart to finish the swap or to return them: - -```mermaid -flowchart TB - - subgraph Third_Party - direction TB - id1(fa:fa-wrench S, fa:fa-coins B) - id2(Escrow<fa:fa-coins B>, Escrow<fa:fa-wrench S>) - id2--swap-->id1 - end - - Third_Party--fa:fa-wrench S-->Buyer - Third_Party--fa:fa-coins B-->Seller -``` - - - -The `swap` function checks that senders and recipients match and that each party wants the object that the other party is offering by comparing their respective key IDs. If the custodian tried to match together 2 unrelated escrow requests to swap, the transaction would not succeed. - -### Shared objects (consensus) - -
- -`shared.move` - - - - -
- -The protocol in the shared object case is less symmetric but still starts with the first party locking the object they want to swap. -```mermaid -flowchart TB - B["Locked<fa:fa-coins B>, fa:fa-key key_b"] - - subgraph Buyer - direction TB - a1(fa:fa-coins B)--escrow::lock-->B - end -``` - -The second party can then view the object that was locked, and if they decide they want to swap with it, they indicate their interest by creating a swap request: - -```mermaid -flowchart TB - S["fa:fa-wrench S, - exchange_key: fa:fa-key key_b, - recipient: Buyer - "] - - id1(Shared Object)-->id2(Escrow<fa:fa-wrench S>) - - subgraph Seller - direction TB - S--create-->id2 - end -``` - - - -This time the `create` request accepts the object being escrowed directly (not locked) and creates a shared `Escrow` object. The request remembers the address that sent it (who is allowed to reclaim the object if the swap hasn't already happened) and the intended recipient, who is then expected to continue the swap by providing the object they initially locked: - -```mermaid -flowchart TB - - subgraph Buyer - direction TB - id1(Escrow<fa:fa-wrench S>,\n fa:fa-key key_b,\n Locked<fa:fa-coins B>) - id2(fa:fa-wrench S) - id1-->swap-->id2 - end - - swap--fa:fa-coins B-->Seller -``` - - - - -Even though the `Escrow` object is a shared object that is accessible by anyone, the Move interface ensures that only the original sender and the intended recipient can successfully interact with it. `swap` checks that the locked object matches the object that was requested when the `Escrow` was created (again, by comparing key IDs) and assumes that the intended recipient wants the escrowed object (if they did not, they would not have called `swap`). - -Assuming all checks pass, the object held in `Escrow` is extracted, its wrapper is deleted, and it is returned to the first party. The locked object offered by the first party is also unlocked and sent to the second party, completing the swap. - -### Comparison - -This topic explores 2 ways to implement a swap between 2 objects. In both cases there is a point at which 1 party has made a request and the other has not responded. At this point, both parties might want to access the `Escrow` object: 1 to cancel the swap and the other to complete it. - -In some cases, the protocol uses only address-owned objects but requires a custodian to act as an intermediary. This has the advantage of avoiding the costs and latencies of consensus altogether but involves more steps and requires trusting a third party for liveness. - -In the other case, the object is in custody on-chain in a shared object. This requires consensus but involves fewer steps and no third party. \ No newline at end of file diff --git a/docs/content/sidebars/concepts.js b/docs/content/sidebars/concepts.js index bdf134f2d2ed0..e00c6e7a41f49 100644 --- a/docs/content/sidebars/concepts.js +++ b/docs/content/sidebars/concepts.js @@ -60,45 +60,6 @@ const concepts = [ 'concepts/tokenomics/gas-in-sui', ], }, - { - type: 'category', - label: 'Object Model', - link: { - type: 'doc', - id: 'concepts/object-model', - }, - items: [ - { - type: 'category', - label: 'Object Ownership', - link: { - type: 'doc', - id: 'concepts/object-ownership', - }, - items: [ - 'concepts/object-ownership/address-owned', - 'concepts/object-ownership/immutable', - 'concepts/object-ownership/party', - 'concepts/object-ownership/shared', - 'concepts/object-ownership/wrapped', - ], - }, - { - type: 'category', - label: 'Transfers', - link: { - type: 'doc', - id: 'concepts/transfers', - }, - items: [ - 'concepts/transfers/custom-rules', - 'concepts/transfers/transfer-policies', - 'concepts/transfers/transfer-to-object', - ], - }, - 'concepts/versioning', - ], - }, { type: 'category', label: 'Move', diff --git a/docs/content/sidebars/guides.js b/docs/content/sidebars/guides.js index 903994f754bb8..3c699cbea8ab4 100644 --- a/docs/content/sidebars/guides.js +++ b/docs/content/sidebars/guides.js @@ -45,7 +45,6 @@ const guides = [ id: 'guides/developer/sui-101', }, items: [ - 'guides/developer/sui-101/object-ownership', 'guides/developer/sui-101/using-events', 'guides/developer/sui-101/local-network', 'guides/developer/sui-101/connect', @@ -69,6 +68,42 @@ const guides = [ }, ], }, + { + type: 'category', + label: 'Objects', + items: [ + 'guides/developer/objects/object-model', + { + type: 'category', + label: 'Object Ownership', + link: { + type: 'doc', + id: 'guides/developer/objects/object-ownership', + }, + items: [ + 'guides/developer/objects/object-ownership/address-owned', + 'guides/developer/objects/object-ownership/immutable', + 'guides/developer/objects/object-ownership/party', + 'guides/developer/objects/object-ownership/shared', + 'guides/developer/objects/object-ownership/wrapped', + ], + }, + { + type: 'category', + label: 'Transfers', + link: { + type: 'doc', + id: 'guides/developer/objects/transfers', + }, + items: [ + 'guides/developer/objects/transfers/custom-rules', + 'guides/developer/objects/transfers/transfer-policies', + 'guides/developer/objects/transfers/transfer-to-object', + ], + }, + 'guides/developer/objects/versioning', + ], + }, { type: 'category', label: 'Currencies and Tokens', diff --git a/docs/content/standards/closed-loop-token.mdx b/docs/content/standards/closed-loop-token.mdx index 8a9b937621657..5afc53c4c21a6 100644 --- a/docs/content/standards/closed-loop-token.mdx +++ b/docs/content/standards/closed-loop-token.mdx @@ -9,7 +9,7 @@ Using the Closed-Loop Token standard, you can limit the applications that can us ## Background and use cases -The [Currency Standard](/standards/currency.mdx) on Sui is an example of an open-loop system. Coins are free-flowing, [wrappable](/concepts/object-ownership/wrapped.mdx), [freely transferable](/concepts/transfers/custom-rules.mdx#the-store-ability-and-transfer-rules) and you can store them in any application. +The [Currency Standard](/standards/currency.mdx) on Sui is an example of an open-loop system. Coins are free-flowing, [wrappable](/guides/developer/objects/object-ownership/wrapped.mdx), [freely transferable](/guides/developer/objects/transfers/custom-rules.mdx#the-store-ability-and-transfer-rules) and you can store them in any application. Some applications, however, require constraining the scope of the token to a specific purpose. For example, some applications might need a token that you can only use for a specific service, or that an authorized account can only use, or a token that you can block certain accounts from using. A real-world analogy would be a bank account that is regulated, bank-controlled, and compliant with certain rules and policies. diff --git a/docs/site/vercel.json b/docs/site/vercel.json index 57d1f7bce4f37..af35bee71570a 100644 --- a/docs/site/vercel.json +++ b/docs/site/vercel.json @@ -3,7 +3,7 @@ { "source": "/testnet/:path*", "destination": "/:path*", "permanent": false }, { "source": "/devnet/:path*", "destination": "/:path*", "permanent": false }, { "source": "/learn/object-package-versions", "destination": "/concepts/dynamic-fields/versioning", "permanent": true }, - { "source": "/learn/objects", "destination": "/concepts/object-model", "permanent": true }, + { "source": "/learn/objects", "destination": "/guides/developer/objects/object-model", "permanent": true }, { "source": "/learn/about-sui", "destination": "/concepts/architecture", "permanent": true }, { "source": "/learn/index", "destination": "/concepts", "permanent": true }, { "source": "/learn", "destination": "/concepts", "permanent": true }, @@ -70,17 +70,17 @@ { "source": "/build/move/time", "destination": "/guides/developer/sui-101/access-time", "permanent": true }, { "source": "/build/move/sui-move-library", "destination": "/concepts/sui-move-concepts", "permanent": true }, { "source": "/build/programming-with-objects/ch6-collections", "destination": "/concepts/dynamic-fields/tables-bags", "permanent": true }, - { "source": "/build/programming-with-objects/ch1-object-basics", "destination": "/concepts/object-model", "permanent": true }, - { "source": "/build/programming-with-objects/ch4-object-wrapping", "destination": "/concepts/object-ownership/wrapped", "permanent": true }, - { "source": "/build/programming-with-objects/ch2-using-objects", "destination": "/concepts/object-ownership", "permanent": true }, - { "source": "/build/programming-with-objects/index", "destination": "/concepts/object-model", "permanent": true }, - { "source": "/build/programming-with-objects/ch3-immutable-objects", "destination": "/concepts/object-ownership/immutable", "permanent": true }, + { "source": "/build/programming-with-objects/ch1-object-basics", "destination": "/guides/developer/objects/object-model", "permanent": true }, + { "source": "/build/programming-with-objects/ch4-object-wrapping", "destination": "/guides/developer/objects/object-ownership/wrapped", "permanent": true }, + { "source": "/build/programming-with-objects/ch2-using-objects", "destination": "/guides/developer/objects/object-ownership", "permanent": true }, + { "source": "/build/programming-with-objects/index", "destination": "/guides/developer/objects/object-model", "permanent": true }, + { "source": "/build/programming-with-objects/ch3-immutable-objects", "destination": "/guides/developer/objects/object-ownership/immutable", "permanent": true }, { "source": "/build/programming-with-objects/ch5-dynamic-fields", "destination": "/concepts/dynamic-fields", "permanent": true }, { "source": "/reference/sui-json", "destination": "/sui-api-ref", "permanent": true }, { "source": "/reference/index", "destination": "/references", "permanent": true }, { "source": "/reference", "destination": "/references", "permanent": true }, { "source": "/concepts/dynamic-fields/events", "destination": "/concepts/events", "permanent": true }, - { "source": "/concepts/dynamic-fields/versioning", "destination": "/concepts/versioning", "permanent": true }, + { "source": "/concepts/dynamic-fields/versioning", "destination": "/guides/developer/objects/versioning", "permanent": true }, { "source": "/learn/exchange-integration-guide", "destination": "/guides/operator/exchange-integration", "permanent": true }, { "source": "/learn/exchange-integration-faq", "destination": "/guides/operator/exchange-integration", "permanent": true }, { "source": "/learn/how-sui-works", "destination": "/concepts/architecture", "permanent": true }, @@ -88,8 +88,8 @@ { "source": "/learn/sui-bridging", "destination": "/concepts/tokenomics/sui-bridging", "permanent": true }, { "source": "/concepts/transactions/transaction-lifecycle", "destination": "/concepts/transactions/transaction-lifecycle", "permanent": true}, { "source": "/standards/closed-loop-token/overview", "destination": "/standards/closed-loop-token", "permanent": true}, - { "source": "/concepts/dynamic-fields/transfers/custom-rules", "destination": "/concepts/transfers/custom-rules", "permanent": true}, - { "source": "/concepts/dynamic-fields/transfers/transfer-to-object", "destination": "/concepts/transfers/transfer-to-object", "permanent": true}, + { "source": "/concepts/dynamic-fields/transfers/custom-rules", "destination": "/guides/developer/objects/transfers/custom-rules", "permanent": true}, + { "source": "/concepts/dynamic-fields/transfers/transfer-to-object", "destination": "/guides/developer/objects/transfers/transfer-to-object", "permanent": true}, { "source": "/references/research-papers", "destination": "/concepts/research-papers", "permanent": true}, { "source": "/guides/developer/app-examples/trading/:path*", "destination": "/guides/developer/app-examples/trustless-swap/:path*", "permanent": true}, { "source": "/guides/developer/app-examples/trustless-token-swap/:path*", "destination": "/guides/developer/app-examples/trustless-swap/:path*", "permanent": true}, @@ -101,7 +101,7 @@ { "source": "/concepts/sui-move-concepts/patterns/id-pointer", "destination": "https://move-book.com/storage/uid-and-id.html", "permanent": true }, { "source": "/concepts/sui-move-concepts/patterns/transferrable-witness", "destination": "https://move-book.com/programmability/witness-pattern.html", "permanent": true }, { "source": "/guides/developer/advanced/asset-tokenization", "destination": "/guides/developer/nft/asset-tokenization", "permanent": true }, - { "source": "/guides/developer/sui-101/shared-owned", "destination": "/guides/developer/sui-101/object-ownership", "permanent": true }, + { "source": "/guides/developer/sui-101/shared-owned", "destination": "/guides/developer/objects/object-ownership", "permanent": true }, { "source": "/references/move/move-toml", "destination": "https://move-book.com/concepts/manifest.html", "permanent": true }, { "source": "/standards/deepbook-pools", "destination": "/standards/deepbook", "permanent": true }, { "source": "/standards/routing-a-swap", "destination": "/standards/deepbook", "permanent": true }, @@ -164,6 +164,14 @@ { "source": "/concepts/transactions/transaction-auth/keys-addresses", "destination": "/concepts/transactions/transaction-auth"}, { "source": "/concepts/cryptography/transaction-auth/keys-addresses", "destination": "/concepts/transactions/transaction-auth"}, { "source": "/concepts/cryptography/transaction-auth/:path*", "destination": "/concepts/transactions/transaction-auth/:path*"}, + { "source": "/concepts/tokenomics/vesting-strategies", "destination": "/guides/developer/coin/vesting-strategies"}, + { "source": "/guides/developer/sui-101/object-ownership", "destination": "/guides/developer/objects/object-ownership"}, + { "source": "/concepts/object-model", "destination": "/guides/developer/objects/object-model"}, + { "source": "/concepts/object-ownership", "destination": "/guides/developer/objects/object-ownership"}, + { "source": "/concepts/object-ownership/:path*", "destination": "/guides/developer/objects/object-ownership/:path*"}, + { "source": "/concepts/transfers", "destination": "/guides/developer/objects/transfers"}, + { "source": "/concepts/transfers/:path*", "destination": "/guides/developer/objects/transfers/:path*"}, + { "source": "/concepts/versioning", "destination": "/guides/developer/objects/versioning"}, { "source": "/concepts/tokenomics/vesting-strategies", "destination": "/guides/developer/coin/vesting-strategies"} ] }