diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md index bba128592ff31d..bee9b2123fe4a1 100644 --- a/aptos-move/framework/aptos-framework/doc/object.md +++ b/aptos-move/framework/aptos-framework/doc/object.md @@ -32,6 +32,7 @@ make it so that a reference to a global object can be returned from a function. - [Struct `TransferRef`](#0x1_object_TransferRef) - [Struct `LinearTransferRef`](#0x1_object_LinearTransferRef) - [Struct `DeriveRef`](#0x1_object_DeriveRef) +- [Struct `TransferPermission`](#0x1_object_TransferPermission) - [Struct `TransferEvent`](#0x1_object_TransferEvent) - [Struct `Transfer`](#0x1_object_Transfer) - [Constants](#@Constants_0) @@ -89,6 +90,7 @@ make it so that a reference to a global object can be returned from a function. - [Function `is_owner`](#0x1_object_is_owner) - [Function `owns`](#0x1_object_owns) - [Function `root_owner`](#0x1_object_root_owner) +- [Function `grant_permission`](#0x1_object_grant_permission) - [Specification](#@Specification_1) - [High-level Requirements](#high-level-req) - [Module-level Specification](#module-level-spec) @@ -144,6 +146,7 @@ make it so that a reference to a global object can be returned from a function. use 0x1::from_bcs; use 0x1::guid; use 0x1::hash; +use 0x1::permissioned_signer; use 0x1::signer; use 0x1::transaction_context; use 0x1::vector; @@ -496,6 +499,34 @@ Used to create derived objects from a given objects. + + + + +## Struct `TransferPermission` + +Permission to transfer object with permissioned signer. + + +
struct TransferPermission has copy, drop, store
+
+
+
+
+object: address
+public entry fun burn<T: key>(owner: &signer, object: Object<T>) acquires ObjectCore {
let original_owner = signer::address_of(owner);
+ assert!(
+ permissioned_signer::check_permission(owner, 0, TransferPermission { object: object.inner }),
+ error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+ );
assert!(is_owner(object, original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
let object_addr = object.inner;
move_to(&create_signer(object_addr), TombStone { original_owner });
@@ -2179,6 +2218,10 @@ Allow origin owners to reclaim any objects they previous burnt.
) acquires TombStone, ObjectCore {
let object_addr = object.inner;
assert!(exists<TombStone>(object_addr), error::invalid_argument(EOBJECT_NOT_BURNT));
+ assert!(
+ permissioned_signer::check_permission(original_owner, 0, TransferPermission { object: object_addr }),
+ error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+ );
let TombStone { original_owner: original_owner_addr } = move_from<TombStone>(object_addr);
assert!(original_owner_addr == signer::address_of(original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
@@ -2351,6 +2394,39 @@ to determine the identity of the starting point of ownership.
+
+
+
+
+## Function `grant_permission`
+
+
+
+public fun grant_permission<T>(master: &signer, permissioned_signer: &signer, object: object::Object<T>)
+
+
+
+
+
+Implementation
+
+
+public fun grant_permission<T>(
+ master: &signer,
+ permissioned_signer: &signer,
+ object: Object<T>,
+) {
+ permissioned_signer::authorize(
+ master,
+ permissioned_signer,
+ 1,
+ TransferPermission { object: object.inner }
+ )
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-framework/sources/object.move b/aptos-move/framework/aptos-framework/sources/object.move
index 6e809e87e87365..51666cb59e5ce4 100644
--- a/aptos-move/framework/aptos-framework/sources/object.move
+++ b/aptos-move/framework/aptos-framework/sources/object.move
@@ -28,6 +28,7 @@ module aptos_framework::object {
use aptos_framework::create_signer::create_signer;
use aptos_framework::event;
use aptos_framework::guid;
+ use aptos_framework::permissioned_signer;
friend aptos_framework::coin;
friend aptos_framework::primary_fungible_store;
@@ -163,6 +164,11 @@ module aptos_framework::object {
self: address,
}
+ /// Permission to transfer object with permissioned signer.
+ struct TransferPermission has copy, drop, store {
+ object: address,
+ }
+
/// Emitted whenever the object's owner field is changed.
struct TransferEvent has drop, store {
object: address,
@@ -537,6 +543,10 @@ module aptos_framework::object {
to: address,
) acquires ObjectCore {
let owner_address = signer::address_of(owner);
+ assert!(
+ permissioned_signer::check_permission(owner, 0, TransferPermission { object }),
+ error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+ );
verify_ungated_and_descendant(owner_address, object);
transfer_raw_inner(object, to);
}
@@ -615,6 +625,10 @@ module aptos_framework::object {
/// Original owners can reclaim burnt objects any time in the future by calling unburn.
public entry fun burn(owner: &signer, object: Object) acquires ObjectCore {
let original_owner = signer::address_of(owner);
+ assert!(
+ permissioned_signer::check_permission(owner, 0, TransferPermission { object: object.inner }),
+ error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+ );
assert!(is_owner(object, original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
let object_addr = object.inner;
move_to(&create_signer(object_addr), TombStone { original_owner });
@@ -628,6 +642,10 @@ module aptos_framework::object {
) acquires TombStone, ObjectCore {
let object_addr = object.inner;
assert!(exists(object_addr), error::invalid_argument(EOBJECT_NOT_BURNT));
+ assert!(
+ permissioned_signer::check_permission(original_owner, 0, TransferPermission { object: object_addr }),
+ error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+ );
let TombStone { original_owner: original_owner_addr } = move_from(object_addr);
assert!(original_owner_addr == signer::address_of(original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
@@ -697,6 +715,19 @@ module aptos_framework::object {
obj_owner
}
+ public fun grant_permission(
+ master: &signer,
+ permissioned_signer: &signer,
+ object: Object,
+ ) {
+ permissioned_signer::authorize(
+ master,
+ permissioned_signer,
+ 1,
+ TransferPermission { object: object.inner }
+ )
+ }
+
#[test_only]
use std::option::{Self, Option};
@@ -1070,4 +1101,48 @@ module aptos_framework::object {
set_untransferable(&weapon_constructor_ref);
transfer_with_ref(linear_transfer_ref, @0x456);
}
+
+ #[test_only]
+ use aptos_framework::timestamp;
+
+ #[test(creator = @0x123)]
+ fun test_transfer_permission_e2e(
+ creator: &signer,
+ ) acquires ObjectCore {
+ let aptos_framework = account::create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let (_, hero) = create_hero(creator);
+ let (_, weapon) = create_weapon(creator);
+
+ // Create a permissioned signer
+ let creator_permission_handle = permissioned_signer::create_permissioned_handle(creator);
+ let creator_permission_signer = permissioned_signer::signer_from_permissioned(&creator_permission_handle);
+
+ // Grant aaron_permission_signer permission to transfer weapon object
+ grant_permission(creator, &creator_permission_signer, weapon);
+ transfer_to_object(&creator_permission_signer, weapon, hero);
+
+ permissioned_signer::destroy_permissioned_handle(creator_permission_handle);
+ }
+
+ #[test(creator = @0x123)]
+ #[expected_failure(abort_code = 327689, location = Self)]
+ fun test_transfer_no_permission(
+ creator: &signer,
+ ) acquires ObjectCore {
+ let aptos_framework = account::create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let (_, hero) = create_hero(creator);
+ let (_, weapon) = create_weapon(creator);
+
+ // Create a permissioned signer
+ let creator_permission_handle = permissioned_signer::create_permissioned_handle(creator);
+ let creator_permission_signer = permissioned_signer::signer_from_permissioned(&creator_permission_handle);
+
+ transfer_to_object(&creator_permission_signer, weapon, hero);
+
+ permissioned_signer::destroy_permissioned_handle(creator_permission_handle);
+ }
}