diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md index cb633a173bf6ce..7c67f27f976886 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)); @@ -2360,6 +2400,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 }
+    )
+}
+
+ + +
@@ -2441,15 +2514,6 @@ to determine the identity of the starting point of ownership. - - - - -
fun spec_exists_at<T: key>(object: address): bool;
-
- - - ### Function `address_to_object` @@ -3401,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 abca67bb2451a4..86e61f6ce4f4ed 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)); @@ -698,6 +712,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}; @@ -1092,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(&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-framework/sources/object.spec.move b/aptos-move/framework/aptos-framework/sources/object.spec.move index 51ae05b568368d..86f8eed4fcf46a 100644 --- a/aptos-move/framework/aptos-framework/sources/object.spec.move +++ b/aptos-move/framework/aptos-framework/sources/object.spec.move @@ -49,6 +49,13 @@ spec aptos_framework::object { pragma aborts_if_is_strict; } + 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; 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 b3f124c43686e7..2faa7be311d1dd 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_capacity_above(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_capacity_above(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..0569c32c9b3914 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_capacity_above(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_capacity_above(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]