Dynamic Fields Migration Guide

Dynamic Fields is a new concept for Sui Move, replacing Child Objects and creating greater programming flexibility.

Dynamic Fields Migration Guide

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) and Bag (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, the dynamic_field (and dynamic_object_field) creates Field objects to store its key-value pairs.
  • There is currently no exists_ function for dynamic_fields 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);
    /* ... */
}

Learn More about Dynamic Fields