diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md index 7ae33a425ee25..e3c6f2f70ece3 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) @@ -133,6 +135,7 @@ make it so that a reference to a global object can be returned from a function. - [Function `is_owner`](#@Specification_1_is_owner) - [Function `owns`](#@Specification_1_owns) - [Function `root_owner`](#@Specification_1_root_owner) + - [Function `grant_permission`](#@Specification_1_grant_permission)
use 0x1::account;
@@ -144,6 +147,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 +500,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
+
+ + + +
+Fields + + +
+
+object: address +
+
+ +
+
+ +
@@ -1999,6 +2031,10 @@ hierarchy. to: address, ) acquires ObjectCore { let owner_address = signer::address_of(owner); + assert!( + permissioned_signer::check_permission_exists(owner, TransferPermission { object }), + error::permission_denied(EOBJECT_NOT_TRANSFERRABLE) + ); verify_ungated_and_descendant(owner_address, object); transfer_raw_inner(object, to); } @@ -2188,6 +2224,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_exists(original_owner, 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)); @@ -2361,6 +2401,38 @@ 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_unlimited(
+        master,
+        permissioned_signer,
+        TransferPermission { object: object.inner }
+    )
+}
+
+ + +
@@ -2437,16 +2509,7 @@ to determine the identity of the starting point of ownership. ### Module-level Specification -
pragma aborts_if_is_strict;
-
- - - - - - - -
fun spec_exists_at<T: key>(object: address): bool;
+
pragma aborts_if_is_partial;
 
@@ -3402,4 +3465,32 @@ 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>)
+
+ + + + +
pragma aborts_if_is_partial;
+aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer);
+aborts_if permissioned_signer::spec_is_permissioned_signer(master);
+aborts_if signer::address_of(master) != signer::address_of(permissioned_signer);
+
+ + + + + + + +
fun spec_exists_at<T: key>(object: address): bool;
+
+ + [move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/sources/object.move b/aptos-move/framework/aptos-framework/sources/object.move index bb6684ff6f430..7f94c73b2ac90 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; @@ -165,6 +166,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, @@ -540,6 +546,10 @@ module aptos_framework::object { to: address, ) acquires ObjectCore { let owner_address = signer::address_of(owner); + assert!( + permissioned_signer::check_permission_exists(owner, TransferPermission { object }), + error::permission_denied(EOBJECT_NOT_TRANSFERRABLE) + ); verify_ungated_and_descendant(owner_address, object); transfer_raw_inner(object, to); } @@ -629,6 +639,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_exists(original_owner, 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)); @@ -699,6 +713,18 @@ module aptos_framework::object { obj_owner } + public fun grant_permission( + master: &signer, + permissioned_signer: &signer, + object: Object, + ) { + permissioned_signer::authorize_unlimited( + master, + permissioned_signer, + TransferPermission { object: object.inner } + ) + } + #[test_only] use std::option::{Self, Option}; @@ -1093,4 +1119,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_handle(&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_handle(&creator_permission_handle); + + transfer_to_object(&creator_permission_signer, weapon, hero); + + permissioned_signer::destroy_permissioned_handle(creator_permission_handle); + } } diff --git a/aptos-move/framework/aptos-framework/sources/object.spec.move b/aptos-move/framework/aptos-framework/sources/object.spec.move index 51ae05b568368..6ddef5f6124bb 100644 --- a/aptos-move/framework/aptos-framework/sources/object.spec.move +++ b/aptos-move/framework/aptos-framework/sources/object.spec.move @@ -46,7 +46,14 @@ spec aptos_framework::object { /// /// spec module { - pragma aborts_if_is_strict; + pragma aborts_if_is_partial; + } + + spec grant_permission { + pragma aborts_if_is_partial; + aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer); + aborts_if permissioned_signer::spec_is_permissioned_signer(master); + aborts_if signer::address_of(master) != signer::address_of(permissioned_signer); } spec fun spec_exists_at(object: address): bool; diff --git a/aptos-move/framework/aptos-token-objects/doc/aptos_token.md b/aptos-move/framework/aptos-token-objects/doc/aptos_token.md index b3f124c43686e..90298df3cab66 100644 --- a/aptos-move/framework/aptos-token-objects/doc/aptos_token.md +++ b/aptos-move/framework/aptos-token-objects/doc/aptos_token.md @@ -864,6 +864,7 @@ With an existing collection, directly mint a soul bound token into the recipient token::creator(*token) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + borrow_global<AptosToken>(token_address) }
@@ -1561,6 +1562,7 @@ With an existing collection, directly mint a soul bound token into the recipient collection::creator(*collection) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + borrow_global<AptosCollection>(collection_address) } diff --git a/aptos-move/framework/aptos-token-objects/sources/aptos_token.move b/aptos-move/framework/aptos-token-objects/sources/aptos_token.move index 5fe04786cd1a9..fc955cae413d8 100644 --- a/aptos-move/framework/aptos-token-objects/sources/aptos_token.move +++ b/aptos-move/framework/aptos-token-objects/sources/aptos_token.move @@ -373,6 +373,7 @@ module aptos_token_objects::aptos_token { token::creator(*token) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + borrow_global(token_address) } @@ -614,6 +615,7 @@ module aptos_token_objects::aptos_token { collection::creator(*collection) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + borrow_global(collection_address) }