Object
Last updated
Last updated
The allows Move to represent a complex type as a set of resources stored within a single address and offers a rich capability model that allows for fine-grained resource control and ownership management.
In the object model, an NFT or token can place common token data within a Token resource, object data within an ObjectCore resource, and then specialize into additional resources as necessary. For example, a Player object could define a player within a game and be an NFT at the same time. The ObjectCore itself stores both the address of the current owner and the appropriate data for creating event streams.
For more usage based details checkout Building With Objects.
The existing Endless data model emphasizes the use of the store ability within Move. Store allows for a struct to exist within any struct that is stored on-chain. As a result, data can live anywhere within any struct and at any address. While this provides great flexibility it has many limitations:
Data is not be guaranteed to be accessible, for example, it can be placed within a user-defined resource that may violate expectations for that data, e.g., a creator attempting to burn an NFT put into a user-defined store. This can be confusing to both the users and creators of this data.
Data of differing types can be stored to a single data structure (e.g., map, vector) via any
, but for complex data types any
incurs additional costs within Move as each access requires deserialization. It also can lead to confusion if API developers expect that a specific any field changes the type it represents.
While resource accounts allow for greater autonomy of data, they do so inefficiently for objects and do not take advantage of resource groups.
Data cannot be recursively composable, because Move currently prohibits recursive data structures. Furthermore, experience suggests that true recursive data structures can lead to security vulnerabilities.
Existing data cannot be easily referenced from entry functions, for example, supporting string validation requires many lines of code. Attempting to make tables directly becomes impractical as keys can be composed of many types, thus specializing to support within entry functions becomes complex.
Events cannot be emitted from data but from an account that may not be associated with the data.
Transferring logic is limited to the APIs provided in the respective modules and generally requires loading resources on both the sender and receiver adding unnecessary cost overheads.
:::tip Object is a core primitive in Endless Move and created via the object module at 0x1::object :::
An object is stored in the ObjectGroup resource group, which enables other resources within the object to be co-located for data locality and data cost savings. It's important to note that not all resources within an object need to be co-located within the ObjectGroup, and it's up to the developer of an object to determine their data layout.
Object is a container for resources that are stored within a single address. These resources usually represent related data often accessed together and should be stored within a single address for data locality and cost savings. When created, an object has a resource group, ObjectGroup, by default:
Each object also has the core ObjectCore resource with fundamental properties:
After creating an object, creators can extend with additional resources. For example, an exchange can create an object for each of its liquidity pools and add a resource to track the pool's liquidity.
In the above code, token_a
and token_b
are references to other objects. Specifically, Object<T>
is a reference to an object stored at a given address that contains T
resource. In this example, they're fungible assets (similar to coins). This is covered in more detail in the Endless Fungible Asset Standard. LiquidityPool resource is part of the ObjectGroup resource group. This means that the LiquidityPool resource is stored in the same storage slot as the ObjectCore resource. This is more storage and gas efficient for reading and writing data.
LiquidityPool resource can be added during construction of the object:
More resources can also be added post-creation if the exchange module stores the ExtendRef. This is covered in more detail in the Capabilities section.
Objects can be created via several different functions provided in the object module:
These functions generate object addresses in different schemas:
create_named_object
generates an address from the caller-provided seed and creator address. This is a deterministic address that can be queried globally. The formula used is sha3(creator address + seed + 0xFD).
create_object
generates an address from the caller's address and a auid generated by hashing the transaction hash of this transaction and a sequence number specific to this transaction. The formula used is sha3(creator address + auid counter + 0xFB).
create_sticky_object
generates an address from the caller's address and a auid generated by hashing the transaction hash of this transaction and a sequence number specific to this transaction. The object will be undeletable The formula used is sha3(creator address + auid counter + 0xFB).
Note that since named objects have deterministic addresses, they cannot be deleted. This is to prevent a malicious user from creating an object with the same seed as a named object and deleting it.
The object creation functions all return a transient ConstructorRef that cannot be stored. ConstructorRef allows adding resources to an object (see example from the previous section). ConstructorRef can also be used to generate the other capabilities (or "refs") that are used to manage the object:
These refs can be stored and used to manage the object.
DeleteRef can be used to delete the object:
ExtendRef can be used to add resources to the object like the LiquidityPool resource in the previous section: TransferRef can be used to disable owner-transfer when ungated_transfer_allowed = true
or to forcefully transfer the object without the owner being involved:
Once the resources have been created on an object, they can be modified by the creator modules without the refs/ Example:
A reference to the object can be generated any time and stored in a resource as part of an object or account:
Object<T>
is a reference around the object address with the guarantee that T
exists when the reference is created. For example, we can create an Object<LiquidityPool>
for a liquidity pool object. Creating an object reference with a non-existent T
will fail at runtime. Note that after references are created and stored, they do not guarantee that the resource T
or the entire object itself has not been deleted.
Objects come with transfer_events by default, which are emitted when the object is transferred. Transfer events are stored in the ObjectCore resource.
Additionally, similar to account resources, events can be added in an object's resources. The object module offers the following functions to create event handles for objects:
These event handles can be stored in the custom resources added to the object. Example: