Skip to content

Commit

Permalink
examples compile
Browse files Browse the repository at this point in the history
  • Loading branch information
damirka committed Mar 24, 2024
1 parent a6c6131 commit b3fc81b
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 124 deletions.
2 changes: 1 addition & 1 deletion packages/samples/sources/basic-syntax/constants.move
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module book::shop_price {
}
}
// ANCHOR_END: shop_price

#[allow(unused_const)]
module book::naming {

// ANCHOR: naming
Expand Down
144 changes: 134 additions & 10 deletions packages/samples/sources/programmability/dynamic-fields.move
Original file line number Diff line number Diff line change
@@ -1,20 +1,144 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

#[allow(unused_field)]
// ANCHOR: module
module book::accessories {
// ANCHOR: usage
module book::dynamic_collection {
// a very common alias for `dynamic_field` is `df` since the
// module name is quite long
use sui::dynamic_field as df;
use std::string::String;

/// The object that we will attach dynamic fields to.
public struct Character has key {
id: UID,
name: String
id: UID
}

/// An accessory that can be attached to a character.
public struct Accessory has store {
type_: String,
name: String,
// List of different accessories that can be attached to a character.
// They must have the `store` ability.
public struct Hat has key, store { id: UID, color: u32 }
public struct Mustache has key, store { id: UID }

#[test]
fun test_character_and_accessories() {
let ctx = &mut tx_context::dummy();
let mut character = Character { id: object::new(ctx) };

// Attach a hat to the character's UID
df::add(
&mut character.id,
b"hat_key",
Hat { id: object::new(ctx), color: 0xFF0000 }
);

// Similarly, attach a mustache to the character's UID
df::add(
&mut character.id,
b"mustache_key",
Mustache { id: object::new(ctx) }
);

// Check that the hat and mustache are attached to the character
//
assert!(df::exists_(&character.id, b"hat_key"), 0);
assert!(df::exists_(&character.id, b"mustache_key"), 1);

// Modify the color of the hat
let hat: &mut Hat = df::borrow_mut(&mut character.id, b"hat_key");
hat.color = 0x00FF00;

// Remove the hat and mustache from the character
let hat: Hat = df::remove(&mut character.id, b"hat_key");
let mustache: Mustache = df::remove(&mut character.id, b"mustache_key");

// Check that the hat and mustache are no longer attached to the character
assert!(!df::exists_(&character.id, b"hat_key"), 0);
assert!(!df::exists_(&character.id, b"mustache_key"), 1);

sui::test_utils::destroy(character);
sui::test_utils::destroy(mustache);
sui::test_utils::destroy(hat);
}
// ANCHOR_END: usage


#[test] fun foreign_types() {
let ctx = &mut tx_context::dummy();
// ANCHOR: foreign_types
let mut character = Character { id: object::new(ctx) };

// Attach a `String` via a `vector<u8>` name
df::add(&mut character.id, b"string_key", b"Hello, World!".to_string());

// Attach a `u64` via a `u32` name
df::add(&mut character.id, 1000u32, 1_000_000_000u64);

// Attach a `bool` via a `bool` name
df::add(&mut character.id, true, false);
// ANCHOR_END: foreign_types
sui::test_utils::destroy(character);
}

#[test] fun orphan_fields() {
let ctx = &mut tx_context::dummy();
// ANCHOR: orphan_fields
// ! DO NOT do this in your code:
let hat = Hat { id: object::new(ctx), color: 0xFF0000 };
let mut character = Character { id: object::new(ctx) };

// Attach a `Hat` via a `vector<u8>` name
df::add(&mut character.id, b"hat_key", hat);

// ! Danger - deleting the parent object
let Character { id } = character;
id.delete();

// ...`Hat` is now stuck in a limbo, it will never be accessible again
// ANCHOR_END: orphan_fields
}

// ANCHOR: exposed_uid
/// Exposes the UID of the character, so that other modules can add, remove
/// and modify dynamic fields.
public fun uid_mut(c: &mut Character): &mut UID {
&mut c.id
}

/// Exposes the UID of the character, so that other modules can read
/// dynamic fields.
public fun uid(c: &Character): &UID {
&c.id
}
// ANCHOR_END: exposed_uid

// ANCHOR: custom_type
/// A custom type with fields in it.
public struct AccessoryKey has copy, drop, store { name: String }

/// An empty key, can be attached only once.
public struct MetadataKey has copy, drop, store {}
// ANCHOR_END: custom_type

#[test] fun use_custom_types() {
let ctx = &mut tx_context::dummy();
// ANCHOR: custom_type_usage
let mut character = Character { id: object::new(ctx) };

// Attaching via an `AccessoryKey { name: b"hat" }`
df::add(
&mut character.id,
AccessoryKey { name: b"hat".to_string() },
Hat { id: object::new(ctx), color: 0xFF0000 }
);
// Attaching via an `AccessoryKey { name: b"mustache" }`
df::add(
&mut character.id,
AccessoryKey { name: b"mustache".to_string() },
Mustache { id: object::new(ctx) }
);

// Attaching via a `MetadataKey`
df::add(&mut character.id, MetadataKey {}, 42);
// ANCHOR_END: custom_type_usage
sui::test_utils::destroy(character);
}
}
// ANCHOR_END: module
119 changes: 6 additions & 113 deletions src/programmability/dynamic-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,61 +31,7 @@ As the definition shows, dynamic fields are stored in an internal `Field` object
The methods available for dynamic fields are straightforward: a field can be added with `add`, removed with `remove`, and read with `borrow` and `borrow_mut`. Additionally, the `exists_` method can be used to check if a field exists (for stricter checks with type, there is an `exists_with_type` method).

```move
module book::dynamic_collection {
// a very common alias for `dynamic_field` is `df` since the
// module name is quite long
use sui::dynamic_field as df;
/// The object that we will attach dynamic fields to.
public struct Character has key {
id: UID
}
// List of different accessories that can be attached to a character.
// They must have the `store` ability.
public struct Hat has key, store { id: UID, color: u32 }
public struct Mustache has key, store { id: UID }
#[test]
fun test_character_and_accessories() {
let ctx = &mut tx_context::dummy();
let character = Character { id: object::new(ctx) };
// Attach a hat to the character's UID
df::add(
&mut character.id,
b"hat_key",
Hat { id: object::new(ctx), color: 0xFF0000 }
);
// Similarly, attach a mustache to the character's UID
df::add(
&mut character.id,
b"mustache_key",
Mustache { id: object::new(ctx) }
);
// Check that the hat and mustache are attached to the character
//
assert!(df::exists_(&character.id, b"hat_key"), 0);
assert!(df::exists_(&character.id, b"mustache_key"), 1);
// Modify the color of the hat
let hat: &mut Hat = df::borrow_mut(&mut character.id, b"hat_key");
hat.color = 0x00FF00;
// Remove the hat and mustache from the character
let hat = df::remove(&mut character.id, b"hat_key");
let mustache = df::remove(&mut character.id, b"mustache_key");
// Check that the hat and mustache are no longer attached to the character
assert!(!df::exists_(&character.id, b"hat_key"), 0);
assert!(!df::exists_(&character.id, b"mustache_key"), 1);
sui::test_utils::destroy(character);
sui::test_utils::destroy(mustache);
sui::test_utils::destroy(hat);
}
{{#include ../../packages/samples/sources/programmability/dynamic-fields.move:usage}}
}
```

Expand All @@ -100,17 +46,7 @@ And the last important property of dynamic fields we should highlight is that th
Dynamic fields allow objects to carry data of any type, including those defined in other modules. This is possible due to their generic nature and relatively weak constraints on the type parameters. Let's illustrate this by attaching a few different values to a `Character` object.

```move
let ctx = tx_context::dummy();
let character = Character { id: object::new(ctx) };
// Attach a `String` via a `vector<u8>` name
df::add(&mut character.id, b"string_key", b"Hello, World!".to_string());
// Attach a `u64` via a `u32` name
df::add(&mut character.id, 1000u32, 1_000_000_000u64);
// Attach a `bool` via a `bool` name
df::add(&mut character.id, true, false);
{{#include ../../packages/samples/sources/programmability/dynamic-fields.move:foreign_types}}
```

In this example we showed how different types can be used for both *name* and the *value* of a dynamic field. The `String` is attached via a `vector<u8>` name, the `u64` is attached via a `u32` name, and the `bool` is attached via a `bool` name. Anything is possible with dynamic fields!
Expand All @@ -122,19 +58,7 @@ In this example we showed how different types can be used for both *name* and th
The `object::delete()` function, which is used to delete a UID, does not track the dynamic fields, and cannot prevent dynamic fields from becoming orphaned. Once the parent UID is deleted, the dynamic fields are not automatically deleted, and they become orphaned. This means that the dynamic fields are still stored in the blockchain, but they will never become accessible again.

```move
// ! DO NOT do this in your code:
let ctx = tx_context::dummy();
let hat = Hat { id: object::new(ctx), color: 0xFF0000 };
let character = Character { id: object::new(ctx) };
// Attach a `Hat` via a `vector<u8>` name
df::add(&mut character.id, b"hat_key", hat);
// ! Danger - deleting the parent object
let Character { id } = character;
id.delete();
// ...`Hat` is now stuck in a limbo, it will never be accessible again
{{#include ../../packages/samples/sources/programmability/dynamic-fields.move:orphan_fields}}
```

Orphaned objects are not a subject to storage rebate, and the storage fees will remain unclaimed.
Expand All @@ -146,17 +70,7 @@ Because dynamic fields are attached to `UID`s, their usage in other modules depe
> Please, remember, that `&mut UID` access affects not only dynamic fields, but also [Transfer to Object](./object/transfer-to-object.md) and [Dynamic Object Fields](#dynamic-object-fields). Should you decide to expose the `UID` as a mutable reference, make sure to understand the implications.
```move
/// Exposes the UID of the character, so that other modules can add, remove
/// and modify dynamic fields.
public fun uid_mut(c: &mut Character): &mut UID {
&mut c.id
}
/// Exposes the UID of the character, so that other modules can read
/// dynamic fields.
public fun uid(c: &Character): &UID {
&c.id
}
{{#include ../../packages/samples/sources/programmability/dynamic-fields.move:exposed_uid}}
```

In the example above, we show how to expose the `UID` of a `Character` object. This solution may work for some applications, however, it is imporant to remember that the exposed `UID` can be a security risk. Especially, if the object's dynamic fields are not supposed to be modified by other modules.

Check warning on line 76 in src/programmability/dynamic-fields.md

View workflow job for this annotation

GitHub Actions / Lint documentation

"imporant" should be "important".
Expand All @@ -168,34 +82,13 @@ If you need to expose the `UID` within the package, consider using a more restri
In the examples above, we used primitive types as field names since they have the required set of abilities. But dynamic fields get even more interesting when we use custom types as field names. This allows for a more structured way of storing data, and also allows for protecting the field names from being accessed by other modules.

```move
/// A custom type with fields in it.
public struct AccessoryKey has copy, drop, store { name: String }
/// An empty key, can be attached only once.
public struct MetadataKey has copy, drop, store {}
{{#include ../../packages/samples/sources/programmability/dynamic-fields.move:custom_type}}
```

Two field names that we defined above are `AccessoryKey` and `MetadataKey`. The `AccessoryKey` has a `String` field in it, hence it can be used multiple times with different `name` values. The `MetadataKey` is an empty key, and can be attached only once.

```move
let ctx = tx_context::dummy();
let character = Character { id: object::new(ctx) };
// Attaching via an `AccessoryKey { name: "hat" }`
df::add(
&mut character.id,
AccessoryKey { name: "hat".to_string() },
Hat { id: object::new(ctx), color: 0xFF0000 }
);
// Attaching via an `AccessoryKey { name: "mustache" }`
df::add(
&mut character.id,
AccessoryKey { name: "mustache".to_string() },
Mustache { id: object::new(ctx) }
);
// Attaching via a `MetadataKey`
df::add(&mut character.id, MetadataKey {}, 42);
{{#include ../../packages/samples/sources/programmability/dynamic-fields.move:custom_type_usage}}
```

As you can see, custom types do work as field names but as long as they can be *constructed* by the module, in other words - if they are *internal* to the module and defined in it. This limitation on struct packing can open up new ways in the design of the application.
Expand Down

0 comments on commit b3fc81b

Please sign in to comment.