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);
+ }
}
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>)
+
+
+
+
+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 },
+ )
+}
+
+
+
+
+public fun revoke_token_mutation<T: key>(permissioned_signer: &signer, token: object::Object<T>)
+
+
+
+
+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) },
+ )
+}
+
+
+
+
+public fun authorize_collection_mutation<T: key>(creator: &signer, permissioned_signer: &signer, collection: object::Object<T>)
+
+
+
+
+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 },
+ )
+}
+
+
+
+
+public fun revoke_collection_mutation<T: key>(permissioned_signer: &signer, collection: object::Object<T>)
+
+
+
+
+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) },
+ )
+}
+
+
+
+