`DynamicField` vs `DynamicObjectField`--why do we have both?

This is an answer to a question from Paul in the Suinami Riders group:

Also I still haven’t been able to figure out why DynamicObjectField exists when we can just use DynamicField. As far as I can tell, in Move, in Sui explorer, and for RPC requests, they’re both identical. I can’t think of any compelling reason to keep DynamicObjectField; it seems to just add 3 duplicate modules in sui-core without adding anything. I vote to deprecate it.

The key difference here is that creating a dynamic field allocates a new object for the field value, whereas creating a dynamic field reuses an existing object. This sounds like a minor difference, but this has some important implications:

  • Reusing an existing object means that it keeps the same object ID and can still be read from external APIs via getObjectById. This is highly desirable if (e.g.) you’re building a marketplace that indexes listings by dynamic fields, and you still want to be able to find each NFT by its ID
  • Reusing an existing object has lower storage overhead because there’s no need to allocated new object metadata for the value

In general, we would recommend using dynamic object fields when the type in question has key, and dynamic fields otherwise. The only exception would be if you want to “hide” the ID of a stored object by wrapping it for one reason or another.

For more on dynamic fields, check out:

22 Likes

To my knowledge, there is no sui_getObjectByID; it’s just sui_getObject (and it does in fact use the object-ID as the query param). And sui_getObject works with both Dynamic Fields and Dynamic Object Fields, because every dynamic field is given assigned an object-id.

I see what you mean about object-id durability however. Would it be possible that, when ObjectA is stored inside of ObjectB, that ObjectA retains its original UID? Because right now ObjectA’s UID is wrapped around a newly generated UID.

We could call this ‘stable id for dynamic fields’; we could probably eliminate dynamic_object_fields entirely if this change were made. I think it makes a lot of sense.

8 Likes

Would it be possible that, when ObjectA is stored inside of ObjectB, that ObjectA retains its original UID?

I’m not sure I fully understand this. To make sure we’re on the same page, here’s how things work today, let me know where you think it should be different

struct A has key, store { id: UID }
struct B has key { id: UID, a: A }

fun wrap(a: A, ctx: &mut TxContext): B {
  B { id: ..., a } // a retains its UID, but is not accessible via getObject
}

fun dyn_field(a: A, t: &mut Table<u64, A>) {
  // uses dynamic fields under the hood: create a new object and wrap a in it
  // a retains its UID, but is not accessible via getObject
  table::add(t, 0, a)
}

fun dyn_obj_field(a: A, t: &mut ObjectTable<u64, A>) {
  // uses dynamic object fields under the hood.
  // a retains its UID, and is accessible via getObject
  object_table::insert(t, 0, a)
}

fun dyn_field_prim(x: u64, t: &mut Table<u64, u64>) {
  // uses dynamic fields under the hood: create a new object and wrap x in it
  table::add(t, 0, x)
}

Dynamic object fields are needed because in code like dyn_obj_field, you (1) want a to continue being accessible via getObject and (2) want to save the storage cost of allocating an extra object to wrap a in.

4 Likes