Dynamic Fields Migration Guide
Dynamic Fields is a new concept for Sui Move, replacing Child Objects and creating greater programming flexibility.
With the release of Sui 0.13.0, we will be replacing the concept of Child Objects with Dynamic Fields. This is a breaking change, with the Move APIs for interacting with Child Objects in sui::transfer
(transfer_to_object
and transfer_to_object_id
) being removed and replaced with sui::dynamic_field
and sui::dynamic_object_field
for working with Dynamic Fields.
To avoid errors in applications that use child objects, please follow the migration guide below and be ready to land changes when Devnet updates to 0.13.0 (watch #devnet-updates on Discord for the announcement) . To minimize downtime for your application, please wait to land changes until after the Devnet update as the new APIs for interacting with Dynamic Fields will not be available until then.
What are Dynamic Fields?
Dynamic Fields are a generalization of Child Objects. They allow Move developers on Sui to extend their structs with new data, on-the-fly.
They address the main frustration with Child Objects which require passing their ancestor ownership chain (its parent, its parent's parent, and so on up to the root object owned by the sender) via the entry
function. This restriction made working with deeply nested child objects painful, and didn't support uses where the ancestor chain couldn't be known at the start of the transaction (i.e. loading child objects dynamically). With Dynamic Fields, only the root object is passed into the entry
function, and its dynamic fields can be accessed on-the-fly during transaction execution. (In fact, passing a dynamic field value as an input to an entry function will now fail).
They also come with a few other improvements:
- Holding any
store
value, not just objects. - Labeling and looking up fields by name (where a name can be any
copy
,drop
,store
value). - Powering new on-chain key-value stores like
Table
(homogeneous keys and values) andBag
(heterogeneous keys and values).
Go crazy with them! We have heard a lot of frustration around missing data structures and then being unable to implement them in Move. You should be able to efficiently implement all sorts of data structures in Move now! Whether it be a linked hash-map or a prefix trie or just a simple linked list. Dynamic fields do require reading/writing objects to storage. We still expect simple vectors to be more efficient than complex data structures for very small numbers of items.
Current Limitations
We are still actively working on some parts of Dynamic Fields, but we couldn’t wait to share this feature with you to see what you could build with it, and to get your feedback. While you are using this feature, watch out for the following known issues, which will be addressed in a future release:
remove
for dynamic fields is currently unoptimized and will not give a full storage refund. Under the hood, thedynamic_field
(anddynamic_object_field
) createsField
objects to store its key-value pairs.- There is currently no
exists_
function fordynamic_field
s to check whether a field with a given name is already defined on the object. - There are some potential durability/consistency issues with dynamic field objects: If a validator goes down and comes back up while processing a transaction with dynamic fields, it might be unable to further process transactions with those objects. We are actively working on a solution for this.
Migration Guide
The suggestions below will work to convert any existing use of Child Objects to using Dynamic Fields, but note that in many cases, you may want to rethink your usage of the APIs. In many cases, the code can be made simpler or more end-user friendly. The examples below are designed to help with drop-in replacements to help get things running.
Replace calls to transfer_to_object
with add
// Old, Child Object API
use sui::transfer;
transfer::transfer_to_object(&mut parent, child)
// New, Dynamic Field API
use sui::dynamic_object_field as ofield;
let id = object::id(&child);
ofield::add(&mut parent.id, id, child);
The new API adds a field to parent
with child
's ID as the name, and child
as the value.
Replace ownership ancestor chains in entry
funs with calls borrow
, borrow_mut
, or remove
(Note: this suggestion assumes that you have followed the earlier suggestion to migrate from transfer_to_object
to ofield::add
using the child
's ID as the field name).
// Child, Parent, GrandParent are all Objects
struct Child has key, store { id: UID, /* ... */ }
struct Parent has key, store { id: UID, /* ... */ }
struct GrandParent has key, store { id: UID, /* ... */ }
// Old, Child Object API
entry fun read_child(gp: &GrandParent, p: &Parent, c: &Child) {
/* ... */
}
entry fun write_child(gp: &mut GrandParent, p: &mut Parent, c: &mut Child) {
/* ... */
}
entry fun take_child(gp: &mut GrandParent, p: &mut Parent, c: Child) {
/* ... */
}
// New, Dynamic Field API
use sui::dynamic_object_field as ofield;
entry fun read_child(gp: &GrandParent, pid: ID, cid: ID) {
let p = ofield::borrow<ID, Parent>(&gp.id, pid);
let c = ofield::borrow<ID, Child>(&p.id, cid);
/* ... */
}
entry fun write_child(gp: &mut GrandParent, pid: ID, cid: ID) {
let p = ofield::borrow_mut<ID, Parent>(&mut gp.id, pid);
let c = ofield::borrow_mut<ID, Child>(&mut p.id, cid);
/* ... */
}
entry fun take_child(gp: &mut GrandParent, pid: ID, cid: ID) {
let p = ofield::borrow_mut<ID, Parent>(&mut gp.id, pid);
let c = ofield::remove<ID, Child>(&mut p.id, cid);
/* ... */
}