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
+
+ + + +
+Fields + + +
+
+object: address +
+
+ +
+
+ +
@@ -1988,6 +2019,10 @@ hierarchy. 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); } @@ -2146,6 +2181,10 @@ Original owners can reclaim burnt objects any time in the future by calling unbu
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); + } } 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 b3f124c43686e7..147e9d6800e000 100644 --- a/aptos-move/framework/aptos-token-objects/doc/aptos_token.md +++ b/aptos-move/framework/aptos-token-objects/doc/aptos_token.md @@ -15,6 +15,8 @@ The key features are: - [Resource `AptosCollection`](#0x4_aptos_token_AptosCollection) - [Resource `AptosToken`](#0x4_aptos_token_AptosToken) +- [Struct `TokenUpdatePermission`](#0x4_aptos_token_TokenUpdatePermission) +- [Struct `CollectionUpdatePermission`](#0x4_aptos_token_CollectionUpdatePermission) - [Constants](#@Constants_0) - [Function `create_collection`](#0x4_aptos_token_create_collection) - [Function `create_collection_object`](#0x4_aptos_token_create_collection_object) @@ -58,11 +60,16 @@ The key features are: - [Function `set_collection_royalties`](#0x4_aptos_token_set_collection_royalties) - [Function `set_collection_royalties_call`](#0x4_aptos_token_set_collection_royalties_call) - [Function `set_collection_uri`](#0x4_aptos_token_set_collection_uri) +- [Function `authorize_token_mutation`](#0x4_aptos_token_authorize_token_mutation) +- [Function `revoke_token_mutation`](#0x4_aptos_token_revoke_token_mutation) +- [Function `authorize_collection_mutation`](#0x4_aptos_token_authorize_collection_mutation) +- [Function `revoke_collection_mutation`](#0x4_aptos_token_revoke_collection_mutation)
use 0x1::error;
 use 0x1::object;
 use 0x1::option;
+use 0x1::permissioned_signer;
 use 0x1::signer;
 use 0x1::string;
 use 0x4::collection;
@@ -201,6 +208,60 @@ Storage state for managing the no-code Token.
 
 
 
+
+
+
+
+## Struct `TokenUpdatePermission`
+
+
+
+
struct TokenUpdatePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+token_address: address +
+
+ +
+
+ + +
+ + + +## Struct `CollectionUpdatePermission` + + + +
struct CollectionUpdatePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+collection_address: address +
+
+ +
+
+ +
@@ -864,6 +925,11 @@ 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), ); + + assert!( + permissioned_signer::check_permission(creator, 0, TokenUpdatePermission { token_address }), + error::permission_denied(ENOT_CREATOR), + ); borrow_global<AptosToken>(token_address) }
@@ -1561,6 +1627,11 @@ 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), ); + + assert!( + permissioned_signer::check_permission(creator, 0, CollectionUpdatePermission { collection_address }), + error::permission_denied(ENOT_CREATOR), + ); borrow_global<AptosCollection>(collection_address) }
@@ -1697,6 +1768,142 @@ With an existing collection, directly mint a soul bound token into the recipient + + + + +## Function `authorize_token_mutation` + + + +
public fun authorize_token_mutation<T: key>(creator: &signer, permissioned_creator: &signer, token: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun authorize_token_mutation<T: key>(
+    creator: &signer,
+    permissioned_creator: &signer,
+    token: Object<T>,
+) {
+    let token_address = object::object_address(&token);
+    assert!(
+        exists<AptosToken>(token_address),
+        error::not_found(ETOKEN_DOES_NOT_EXIST),
+    );
+    permissioned_signer::authorize(
+        creator,
+        permissioned_creator,
+        0,
+        TokenUpdatePermission { token_address },
+    )
+}
+
+ + + +
+ + + +## Function `revoke_token_mutation` + + + +
public fun revoke_token_mutation<T: key>(permissioned_signer: &signer, token: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun revoke_token_mutation<T: key>(
+    permissioned_signer: &signer,
+    token: Object<T>,
+) {
+    permissioned_signer::revoke_permission(
+        permissioned_signer,
+        TokenUpdatePermission { token_address: object::object_address(&token) },
+    )
+}
+
+ + + +
+ + + +## Function `authorize_collection_mutation` + + + +
public fun authorize_collection_mutation<T: key>(creator: &signer, permissioned_signer: &signer, collection: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun authorize_collection_mutation<T: key>(
+    creator: &signer,
+    permissioned_signer: &signer,
+    collection: Object<T>,
+) {
+    let collection_address = object::object_address(&collection);
+    assert!(
+        exists<AptosCollection>(collection_address),
+        error::not_found(ETOKEN_DOES_NOT_EXIST),
+    );
+    permissioned_signer::authorize(
+        creator,
+        permissioned_signer,
+        0,
+        CollectionUpdatePermission { collection_address },
+    )
+}
+
+ + + +
+ + + +## Function `revoke_collection_mutation` + + + +
public fun revoke_collection_mutation<T: key>(permissioned_signer: &signer, collection: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun revoke_collection_mutation<T: key>(
+    permissioned_signer: &signer,
+    collection: Object<T>,
+) {
+    permissioned_signer::revoke_permission(
+        permissioned_signer,
+        CollectionUpdatePermission { collection_address: object::object_address(&collection) },
+    )
+}
+
+ + +
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 5fe04786cd1a93..bb620b4c06165b 100644 --- a/aptos-move/framework/aptos-token-objects/sources/aptos_token.move +++ b/aptos-move/framework/aptos-token-objects/sources/aptos_token.move @@ -12,6 +12,7 @@ module aptos_token_objects::aptos_token { use std::string::String; use std::signer; use aptos_framework::object::{Self, ConstructorRef, Object}; + use aptos_framework::permissioned_signer; use aptos_token_objects::collection; use aptos_token_objects::property_map; use aptos_token_objects::royalty; @@ -68,6 +69,14 @@ module aptos_token_objects::aptos_token { property_mutator_ref: property_map::MutatorRef, } + struct TokenUpdatePermission has copy, drop, store { + token_address: address, + } + + struct CollectionUpdatePermission has copy, drop, store { + collection_address: address, + } + /// Create a new collection public entry fun create_collection( creator: &signer, @@ -373,6 +382,11 @@ module aptos_token_objects::aptos_token { token::creator(*token) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + + assert!( + permissioned_signer::check_permission(creator, 0, TokenUpdatePermission { token_address }), + error::permission_denied(ENOT_CREATOR), + ); borrow_global(token_address) } @@ -614,6 +628,11 @@ module aptos_token_objects::aptos_token { collection::creator(*collection) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + + assert!( + permissioned_signer::check_permission(creator, 0, CollectionUpdatePermission { collection_address }), + error::permission_denied(ENOT_CREATOR), + ); borrow_global(collection_address) } @@ -667,6 +686,63 @@ module aptos_token_objects::aptos_token { collection::set_uri(option::borrow(&aptos_collection.mutator_ref), uri); } + // Permissions + public fun authorize_token_mutation( + creator: &signer, + permissioned_creator: &signer, + token: Object, + ) { + let token_address = object::object_address(&token); + assert!( + exists(token_address), + error::not_found(ETOKEN_DOES_NOT_EXIST), + ); + permissioned_signer::authorize( + creator, + permissioned_creator, + 0, + TokenUpdatePermission { token_address }, + ) + } + + public fun revoke_token_mutation( + permissioned_signer: &signer, + token: Object, + ) { + permissioned_signer::revoke_permission( + permissioned_signer, + TokenUpdatePermission { token_address: object::object_address(&token) }, + ) + } + + public fun authorize_collection_mutation( + creator: &signer, + permissioned_signer: &signer, + collection: Object, + ) { + let collection_address = object::object_address(&collection); + assert!( + exists(collection_address), + error::not_found(ETOKEN_DOES_NOT_EXIST), + ); + permissioned_signer::authorize( + creator, + permissioned_signer, + 0, + CollectionUpdatePermission { collection_address }, + ) + } + + public fun revoke_collection_mutation( + permissioned_signer: &signer, + collection: Object, + ) { + permissioned_signer::revoke_permission( + permissioned_signer, + CollectionUpdatePermission { collection_address: object::object_address(&collection) }, + ) + } + // Tests #[test_only]