Using ExtendRef
Summary
ExtendRef
in Aptos Move allows generating a signer for an object after creation- Key use cases include adding ownership capabilities and enhancing object functionality
- Implementation involves storing, retrieving, and using ExtendRef with
object::generate_signer()
- Important to use
ExtendRef
judiciously and protect it carefully - Common pitfalls include exposing ExtendRef publicly and unnecessary usage
- Proper use of
ExtendRef
enables flexible and safe extension of object functionality
Why ExtendRef?
ExtendRef
allows us to generate a signer for an object after it has been created. This is crucial because:
- A signer can only be created once using ConstructorRef when the object is initialized.
- After object creation, we lose access to ConstructorRef.
Use Cases for ExtendRef
- Adding ownership capabilities to objects
- Enhancing object functionality post-creation
- Facilitating digital asset operations
How to Use ExtendRef
- Store ExtendRef: When creating an object, store ExtendRef in a field of the object.
- Retrieve ExtendRef: Write a function to retrieve ExtendRef from the object.
- Use ExtendRef: Use
object::generate_signer()
to create a signer from ExtendRef.
In this example, we'll create an ExtendRef and store it in a separate resource, which we'll use for future extension purposes.
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct ControllerObject has key {
extend_ref: ExtendRef
}
fun init_module(owner: &signer) {
let state_object_constructor_ref = &object::create_named_object(owner, MOVEMENT_OBJECT_NAME);
let state_object_signer = &object::generate_signer(state_object_constructor_ref);
move_to(state_object_signer, MovementObject {
value: 10
});
let extend_ref = object::generate_extend_ref(state_object_constructor_ref);
move_to(state_object_signer, ControllerObject { extend_ref });
}
In the code above, we initialize two resources: MovementObject and ControllerObject. We create a separate object to store these two resources independently. In the future, if I want to add more resources, I'll use the ExtendRef in ControllerObject to do so.
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct NewObject has key {
new_value: u64
}
I'm creating an additional resource to add to the object address that I created in the init_module
function, and I'm creating a function to add this resource.
// This function adds a new object to an existing object using ExtendRef
// Parameters:
// - owner: The signer who owns the object
// - obj: The object to which we want to add a new object
// The function does the following:
public fun add_new_object(owner: &signer, obj: Object<MovementObject>) acquires ControllerObject {
// 1. Verifies that the owner is indeed the owner of the object
let addr = address_of(owner);
assert!(object::is_owner(obj, addr), 1);
// 2. Retrieves the ExtendRef from the ControllerObject
let object_address = object::object_address(&obj);
let extend_ref = &borrow_global<ControllerObject>(object_address).extend_ref;
// 3. Generates a signer for the object using the ExtendRef
let object_signer = object::generate_signer_for_extending(extend_ref);
// 4. Creates and moves a new NewObject to the object's address
move_to(&object_signer, NewObject { new_value: 50 });
}
The test case for this scenario will be:
#[test(account = @0x1)]
fun test_add_new_object(account: &signer) acquires ControllerObject, NewObject {
test_init_module(account);
let addr = address_of(account);
let obj = get_object(addr);
add_new_object(account, obj);
let movement_object_address = get_object_address(addr);
assert!(exists<NewObject>(movement_object_address), 0);
let new_object_data = borrow_global<NewObject>(movement_object_address);
assert!(new_object_data.new_value == 50, 1);
}
Important Considerations
- Only use ExtendRef when necessary.
- Protect ExtendRef carefully as it allows creating the object's signer.
- Thoroughly check access to functions using ExtendRef.
Common Pitfalls to Avoid
- Don't expose ExtendRef publicly without proper safeguards.
- Avoid unnecessary use of ExtendRef when simpler alternatives exist.
- Don't forget to handle potential errors when using ExtendRef.
By leveraging ExtendRef, you can flexibly and safely extend object functionality in Move.
Full Code
module movement::object_module_entry {
use aptos_framework::object::{Self, Object, ExtendRef, TransferRef, DeleteRef};
use std::signer::address_of;
use std::debug::print;
const MOVEMENT_OBJECT_NAME: vector<u8> = b"MovementObjectName";
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct MovementObject has key {
value: u64
}
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct NewObject has key {
new_value: u64
}
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct ControllerObject has key {
extend_ref: ExtendRef
}
fun init_module(owner: &signer) {
let state_object_constructor_ref = &object::create_named_object(owner, MOVEMENT_OBJECT_NAME);
let state_object_signer = &object::generate_signer(state_object_constructor_ref);
move_to(state_object_signer, MovementObject {
value: 10
});
let extend_ref = object::generate_extend_ref(state_object_constructor_ref);
move_to(state_object_signer, ControllerObject { extend_ref });
}
#[test_only]
fun test_init_module(onwer: &signer) {
init_module(onwer);
}
#[view]
public fun get_object_address(owner: address): address {
object::create_object_address(&owner, MOVEMENT_OBJECT_NAME)
}
#[view]
public fun get_object(owner: address): Object<MovementObject> {
object::address_to_object(get_object_address(owner))
}
public fun get_value(owner: address): u64 acquires MovementObject {
borrow_global<MovementObject>(get_object_address(owner)).value
}
#[view]
public fun get_owner_object(obj: Object<MovementObject>): address {
object::owner(obj)
}
public fun set_value(owner: address, new_value: u64) acquires MovementObject {
let obj = get_object(owner);
assert!(object::is_owner<MovementObject>(obj, owner), 1); // Only the owner can transfer/modify it
let object_data = borrow_global_mut<MovementObject>(get_object_address(owner));
object_data.value = new_value;
}
public fun transfer_obj(owner: &signer, new_owner: address) {
let owner_addr = address_of(owner);
let obj = get_object(owner_addr);
assert!(object::is_owner<MovementObject>(obj, owner_addr), 1); // Only the owner can transfer/modify it
object::transfer(owner, obj, new_owner);
}
public fun add_new_object(owner: &signer, obj: Object<MovementObject>) acquires ControllerObject {
let addr = address_of(owner);
assert!(object::is_owner(obj, addr), 1);
let object_address = object::object_address(&obj);
let extend_ref = &borrow_global<ControllerObject>(object_address).extend_ref;
let object_signer = object::generate_signer_for_extending(extend_ref);
move_to(&object_signer, NewObject { new_value: 50 });
}
#[test(account = @0x1)]
fun test_add_new_object(account: &signer) acquires ControllerObject, NewObject {
test_init_module(account);
let addr = address_of(account);
let obj = get_object(addr);
add_new_object(account, obj);
let movement_object_address = get_object_address(addr);
assert!(exists<NewObject>(movement_object_address), 0);
let new_object_data = borrow_global<NewObject>(movement_object_address);
assert!(new_object_data.new_value == 50, 1);
}
#[test(account = @0x1, new_owner = @0x2)]
fun test_transfer_object(account: &signer, new_owner: address) {
test_init_module(account);
transfer_obj(account, new_owner);
}
#[test(account = @0x1)]
fun test_get_owner_object_address(account: &signer) {
test_init_module(account);
let addr = address_of(account);
let obj = get_object(addr);
let get_owner = get_owner_object(obj);
print(&get_owner);
}
#[test(account = @0x1)]
fun test_get_object_address(account: &signer) {
test_init_module(account);
let addr = address_of(account);
let value = get_object_address(addr);
print(&value);
}
#[test(account = @0x1)]
fun test_get_object(account: &signer) acquires MovementObject {
test_init_module(account);
let addr = address_of(account);
let value = get_value(addr);
assert!(value == 10, 0);
}
#[test(account = @0x1)]
fun test_set_object(account: &signer) acquires MovementObject {
test_init_module(account);
let addr = address_of(account);
set_value(addr, 20);
let value = get_value(addr);
assert!(value == 20, 1);
}
}