From 059d0f83c7c09df6ec9d50498e7d112470828cb8 Mon Sep 17 00:00:00 2001 From: JohnChangUK Date: Sat, 17 Aug 2024 15:27:45 -0400 Subject: [PATCH 1/2] Separate out collection creator and owner permissions --- .../src/components/feature_flags.rs | 3 + .../framework/aptos-framework/doc/jwks.md | 113 +------ .../aptos-token-objects/doc/collection.md | 129 ++++++++ .../aptos-token-objects/doc/token.md | 284 ++++++++++++++++- .../sources/collection.move | 88 ++++- .../aptos-token-objects/sources/token.move | 300 ++++++++++++++++-- .../framework/move-stdlib/doc/features.md | 57 ++++ .../move-stdlib/sources/configs/features.move | 8 + types/src/on_chain_config/aptos_features.rs | 1 + 9 files changed, 848 insertions(+), 135 deletions(-) diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index 2c921f8a16dae..755f964346a3b 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -129,6 +129,7 @@ pub enum FeatureFlag { RejectUnstableBytecodeForScript, FederatedKeyless, TransactionSimulationEnhancement, + CollectionOwner, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -342,6 +343,7 @@ impl From for AptosFeatureFlag { FeatureFlag::TransactionSimulationEnhancement => { AptosFeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT }, + FeatureFlag::CollectionOwner => AptosFeatureFlag::COLLECTION_OWNER, } } } @@ -484,6 +486,7 @@ impl From for FeatureFlag { AptosFeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT => { FeatureFlag::TransactionSimulationEnhancement }, + AptosFeatureFlag::COLLECTION_OWNER => FeatureFlag::CollectionOwner, } } } diff --git a/aptos-move/framework/aptos-framework/doc/jwks.md b/aptos-move/framework/aptos-framework/doc/jwks.md index 2f3e0f34e307b..e0c09f7fa7214 100644 --- a/aptos-move/framework/aptos-framework/doc/jwks.md +++ b/aptos-move/framework/aptos-framework/doc/jwks.md @@ -27,9 +27,7 @@ have a simple layout which is easily accessible in Rust. - [Struct `PatchUpsertJWK`](#0x1_jwks_PatchUpsertJWK) - [Resource `Patches`](#0x1_jwks_Patches) - [Resource `PatchedJWKs`](#0x1_jwks_PatchedJWKs) -- [Resource `FederatedJWKs`](#0x1_jwks_FederatedJWKs) - [Constants](#@Constants_0) -- [Function `patch_federated_jwks`](#0x1_jwks_patch_federated_jwks) - [Function `get_patched_jwk`](#0x1_jwks_get_patched_jwk) - [Function `try_get_patched_jwk`](#0x1_jwks_try_get_patched_jwk) - [Function `upsert_oidc_provider`](#0x1_jwks_upsert_oidc_provider) @@ -61,8 +59,7 @@ have a simple layout which is easily accessible in Rust. - [Function `on_new_epoch`](#@Specification_1_on_new_epoch) -
use 0x1::bcs;
-use 0x1::chain_status;
+
use 0x1::chain_status;
 use 0x1::comparator;
 use 0x1::config_buffer;
 use 0x1::copyable_any;
@@ -70,7 +67,6 @@ have a simple layout which is easily accessible in Rust.
 use 0x1::event;
 use 0x1::option;
 use 0x1::reconfiguration;
-use 0x1::signer;
 use 0x1::string;
 use 0x1::system_addresses;
 use 0x1::vector;
@@ -593,34 +589,6 @@ This is what applications should consume.
 
 
 
-
-Fields - - -
-
-jwks: jwks::AllProvidersJWKs -
-
- -
-
- - -
- - - -## Resource `FederatedJWKs` - -JWKs for federated keyless accounts are stored in this resource. - - -
struct FederatedJWKs has drop, key
-
- - -
Fields @@ -642,24 +610,6 @@ JWKs for federated keyless accounts are stored in this resource. ## Constants - - - - -
const EFEDERATED_JWKS_TOO_LARGE: u64 = 8;
-
- - - - - - - -
const EINSTALL_FEDERATED_JWKS_AT_APTOS_FRAMEWORK: u64 = 7;
-
- - - @@ -759,65 +709,6 @@ JWKs for federated keyless accounts are stored in this resource. - - -We limit the size of a PatchedJWKs resource installed by a dapp owner for federated keyless accounts. -Note: If too large, validators waste work reading it for invalid TXN signatures. - - -
const MAX_FEDERATED_JWKS_SIZE_BYTES: u64 = 2048;
-
- - - - - -## Function `patch_federated_jwks` - -Called by a federated keyless dapp owner to install the JWKs for the federated OIDC provider (e.g., Auth0, AWS -Cognito, etc). - -For type-safety, we explicitly use a struct FederatedJWKs { jwks: AllProviderJWKs } instead of -reusing PatchedJWKs { jwks: AllProviderJWKs }, which is a JWK-consensus-specific struct. We'd -need to be careful how we read it in Rust (but BCS serialization should be the same). - - -
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<jwks::Patch>)
-
- - - -
-Implementation - - -
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<Patch>) acquires FederatedJWKs {
-    // Prevents accidental calls in 0x1::jwks that install federated JWKs at the Aptos framework address.
-    assert!(!system_addresses::is_aptos_framework_address(signer::address_of(jwk_owner)),
-        error::invalid_argument(EINSTALL_FEDERATED_JWKS_AT_APTOS_FRAMEWORK)
-    );
-
-    let jwk_addr = signer::address_of(jwk_owner);
-    if (!exists<FederatedJWKs>(jwk_addr)) {
-        move_to(jwk_owner, FederatedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
-    };
-
-    let fed_jwks = borrow_global_mut<FederatedJWKs>(jwk_addr);
-    vector::for_each_ref(&patches, |obj|{
-        let patch: &Patch = obj;
-        apply_patch(&mut fed_jwks.jwks, *patch);
-    });
-
-    // TODO: Can we check the size more efficiently instead of serializing it via BCS?
-    let num_bytes = vector::length(&bcs::to_bytes(fed_jwks));
-    assert!(num_bytes < MAX_FEDERATED_JWKS_SIZE_BYTES, error::invalid_argument(EFEDERATED_JWKS_TOO_LARGE));
-}
-
- - - -
- ## Function `get_patched_jwk` @@ -1424,7 +1315,7 @@ Regenerate PatchedJWKs f ## Function `try_get_jwk_by_issuer` -Get a JWK by issuer and key ID from an AllProvidersJWKs, if it exists. +Get a JWK by issuer and key ID from a AllProvidersJWKs, if it exists.
fun try_get_jwk_by_issuer(jwks: &jwks::AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
diff --git a/aptos-move/framework/aptos-token-objects/doc/collection.md b/aptos-move/framework/aptos-token-objects/doc/collection.md
index 59b704ea47bc0..3193210b619f8 100644
--- a/aptos-move/framework/aptos-token-objects/doc/collection.md
+++ b/aptos-move/framework/aptos-token-objects/doc/collection.md
@@ -39,9 +39,12 @@ require adding the field original_name.
 -  [Struct `SetMaxSupply`](#0x4_collection_SetMaxSupply)
 -  [Constants](#@Constants_0)
 -  [Function `create_fixed_collection`](#0x4_collection_create_fixed_collection)
+-  [Function `create_fixed_collection_as_owner`](#0x4_collection_create_fixed_collection_as_owner)
 -  [Function `create_unlimited_collection`](#0x4_collection_create_unlimited_collection)
+-  [Function `create_unlimited_collection_as_owner`](#0x4_collection_create_unlimited_collection_as_owner)
 -  [Function `create_untracked_collection`](#0x4_collection_create_untracked_collection)
 -  [Function `create_collection_internal`](#0x4_collection_create_collection_internal)
+-  [Function `enable_ungated_transfer`](#0x4_collection_enable_ungated_transfer)
 -  [Function `create_collection_address`](#0x4_collection_create_collection_address)
 -  [Function `create_collection_seed`](#0x4_collection_create_collection_seed)
 -  [Function `increment_supply`](#0x4_collection_increment_supply)
@@ -708,6 +711,16 @@ The collection name is over the maximum length
 
 
 
+
+
+The collection owner feature is not supported
+
+
+
const ECOLLECTION_OWNER_NOT_SUPPORTED: u64 = 11;
+
+ + + The collection has reached its supply and no more tokens can be minted, unless some are burned @@ -837,6 +850,51 @@ Beyond that, it adds supply tracking with events. +
+ + + +## Function `create_fixed_collection_as_owner` + +Same functionality as create_fixed_collection, but the caller is the owner of the collection. +This means that the caller can transfer the collection to another address. +This transfers ownership and minting permissions to the new address. + + +
public fun create_fixed_collection_as_owner(creator: &signer, description: string::String, max_supply: u64, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+ + + +
+Implementation + + +
public fun create_fixed_collection_as_owner(
+    creator: &signer,
+    description: String,
+    max_supply: u64,
+    name: String,
+    royalty: Option<Royalty>,
+    uri: String,
+): ConstructorRef {
+    assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED));
+
+    let constructor_ref = create_fixed_collection(
+        creator,
+        description,
+        max_supply,
+        name,
+        royalty,
+        uri,
+    );
+    enable_ungated_transfer(&constructor_ref);
+    constructor_ref
+}
+
+ + +
@@ -885,6 +943,49 @@ the supply of tokens. + + + + +## Function `create_unlimited_collection_as_owner` + +Same functionality as create_unlimited_collection, but the caller is the owner of the collection. +This means that the caller can transfer the collection to another address. +This transfers ownership and minting permissions to the new address. + + +
public fun create_unlimited_collection_as_owner(creator: &signer, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+ + + +
+Implementation + + +
public fun create_unlimited_collection_as_owner(
+    creator: &signer,
+    description: String,
+    name: String,
+    royalty: Option<Royalty>,
+    uri: String,
+): ConstructorRef {
+    assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED));
+
+    let constructor_ref = create_unlimited_collection(
+        creator,
+        description,
+        name,
+        royalty,
+        uri,
+    );
+    enable_ungated_transfer(&constructor_ref);
+    constructor_ref
+}
+
+ + +
@@ -980,12 +1081,40 @@ TODO: Hide this until we bring back meaningful way to enforce burns royalty::init(&constructor_ref, option::extract(&mut royalty)) }; + let transfer_ref = object::generate_transfer_ref(&constructor_ref); + object::disable_ungated_transfer(&transfer_ref); + constructor_ref }
+ + + + +## Function `enable_ungated_transfer` + + + +
fun enable_ungated_transfer(constructor_ref: &object::ConstructorRef)
+
+ + + +
+Implementation + + +
inline fun enable_ungated_transfer(constructor_ref: &ConstructorRef) {
+    let transfer_ref = object::generate_transfer_ref(constructor_ref);
+    object::enable_ungated_transfer(&transfer_ref);
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-token-objects/doc/token.md b/aptos-move/framework/aptos-token-objects/doc/token.md index 0068cdeaf002b..11d56968b9188 100644 --- a/aptos-move/framework/aptos-token-objects/doc/token.md +++ b/aptos-move/framework/aptos-token-objects/doc/token.md @@ -20,13 +20,19 @@ token are: - [Constants](#@Constants_0) - [Function `create_common`](#0x4_token_create_common) - [Function `create_common_with_collection`](#0x4_token_create_common_with_collection) +- [Function `create_common_with_collection_as_owner`](#0x4_token_create_common_with_collection_as_owner) +- [Function `create_common_with_collection_internal`](#0x4_token_create_common_with_collection_internal) - [Function `create_token`](#0x4_token_create_token) - [Function `create`](#0x4_token_create) +- [Function `create_token_as_collection_owner`](#0x4_token_create_token_as_collection_owner) - [Function `create_numbered_token_object`](#0x4_token_create_numbered_token_object) - [Function `create_numbered_token`](#0x4_token_create_numbered_token) +- [Function `create_numbered_token_as_collection_owner`](#0x4_token_create_numbered_token_as_collection_owner) - [Function `create_named_token_object`](#0x4_token_create_named_token_object) - [Function `create_named_token`](#0x4_token_create_named_token) +- [Function `create_named_token_as_collection_owner`](#0x4_token_create_named_token_as_collection_owner) - [Function `create_named_token_from_seed`](#0x4_token_create_named_token_from_seed) +- [Function `create_named_token_from_seed_as_collection_owner`](#0x4_token_create_named_token_from_seed_as_collection_owner) - [Function `create_from_account`](#0x4_token_create_from_account) - [Function `create_token_address`](#0x4_token_create_token_address) - [Function `create_token_address_with_seed`](#0x4_token_create_token_address_with_seed) @@ -390,6 +396,16 @@ The calling signer is not the owner + + +The collection owner feature is not supported + + +
const ECOLLECTION_OWNER_NOT_SUPPORTED: u64 = 9;
+
+ + + The description is over the maximum length @@ -552,8 +568,96 @@ The token name is over the maximum length royalty: Option<Royalty>, uri: String, ) { - assert!(object::owner(collection) == signer::address_of(creator), error::unauthenticated(ENOT_OWNER)); + assert!(collection::creator(collection) == signer::address_of(creator), error::unauthenticated(ENOT_CREATOR)); + + create_common_with_collection_internal( + constructor_ref, + collection, + description, + name_prefix, + name_with_index_suffix, + royalty, + uri + ); +} +
+ + + + + + + +## Function `create_common_with_collection_as_owner` + + + +
fun create_common_with_collection_as_owner(owner: &signer, constructor_ref: &object::ConstructorRef, collection: object::Object<collection::Collection>, description: string::String, name_prefix: string::String, name_with_index_suffix: option::Option<string::String>, royalty: option::Option<royalty::Royalty>, uri: string::String)
+
+ + + +
+Implementation + + +
inline fun create_common_with_collection_as_owner(
+    owner: &signer,
+    constructor_ref: &ConstructorRef,
+    collection: Object<Collection>,
+    description: String,
+    name_prefix: String,
+    // If option::some, numbered token is created - i.e. index is appended to the name.
+    // If option::none, name_prefix is the full name of the token.
+    name_with_index_suffix: Option<String>,
+    royalty: Option<Royalty>,
+    uri: String,
+) {
+    assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED));
+    assert!(object::owner(collection) == signer::address_of(owner), error::unauthenticated(ENOT_OWNER));
+
+    create_common_with_collection_internal(
+        constructor_ref,
+        collection,
+        description,
+        name_prefix,
+        name_with_index_suffix,
+        royalty,
+        uri
+    );
+}
+
+ + + +
+ + + +## Function `create_common_with_collection_internal` + + + +
fun create_common_with_collection_internal(constructor_ref: &object::ConstructorRef, collection: object::Object<collection::Collection>, description: string::String, name_prefix: string::String, name_with_index_suffix: option::Option<string::String>, royalty: option::Option<royalty::Royalty>, uri: string::String)
+
+ + +
+Implementation + + +
inline fun create_common_with_collection_internal(
+    constructor_ref: &ConstructorRef,
+    collection: Object<Collection>,
+    description: String,
+    name_prefix: String,
+    // If option::some, numbered token is created - i.e. index is appended to the name.
+    // If option::none, name_prefix is the full name of the token.
+    name_with_index_suffix: Option<String>,
+    royalty: Option<Royalty>,
+    uri: String,
+) {
     if (option::is_some(&name_with_index_suffix)) {
         // Be conservative, as we don't know what length the index will be, and assume worst case (20 chars in MAX_U64)
         assert!(
@@ -701,6 +805,50 @@ for additional specialization.
 
 
 
+
+ + + +## Function `create_token_as_collection_owner` + +Same functionality as create_token, but the token can only be created by the collection owner. + + +
public fun create_token_as_collection_owner(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+ + + +
+Implementation + + +
public fun create_token_as_collection_owner(
+    creator: &signer,
+    collection: Object<Collection>,
+    description: String,
+    name: String,
+    royalty: Option<Royalty>,
+    uri: String,
+): ConstructorRef {
+    let creator_address = signer::address_of(creator);
+    let constructor_ref = object::create_object(creator_address);
+    create_common_with_collection_as_owner(
+        creator,
+        &constructor_ref,
+        collection,
+        description,
+        name,
+        option::none(),
+        royalty,
+        uri
+    );
+    constructor_ref
+}
+
+ + +
@@ -802,6 +950,51 @@ while providing sequential names. + + + + +## Function `create_numbered_token_as_collection_owner` + +Same functionality as create_numbered_token_object, but the token can only be created by the collection owner. + + +
public fun create_numbered_token_as_collection_owner(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name_with_index_prefix: string::String, name_with_index_suffix: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+ + + +
+Implementation + + +
public fun create_numbered_token_as_collection_owner(
+    creator: &signer,
+    collection: Object<Collection>,
+    description: String,
+    name_with_index_prefix: String,
+    name_with_index_suffix: String,
+    royalty: Option<Royalty>,
+    uri: String,
+): ConstructorRef {
+    let creator_address = signer::address_of(creator);
+    let constructor_ref = object::create_object(creator_address);
+    create_common_with_collection_as_owner(
+        creator,
+        &constructor_ref,
+        collection,
+        description,
+        name_with_index_prefix,
+        option::some(name_with_index_suffix),
+        royalty,
+        uri
+    );
+    constructor_ref
+}
+
+ + +
@@ -894,6 +1087,50 @@ additional specialization. + + + + +## Function `create_named_token_as_collection_owner` + +Same functionality as create_named_token_object, but the token can only be created by the collection owner. + + +
public fun create_named_token_as_collection_owner(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+ + + +
+Implementation + + +
public fun create_named_token_as_collection_owner(
+    creator: &signer,
+    collection: Object<Collection>,
+    description: String,
+    name: String,
+    royalty: Option<Royalty>,
+    uri: String,
+): ConstructorRef {
+    let seed = create_token_seed(&collection::name(collection), &name);
+    let constructor_ref = object::create_named_object(creator, seed);
+    create_common_with_collection_as_owner(
+        creator,
+        &constructor_ref,
+        collection,
+        description,
+        name,
+        option::none(),
+        royalty,
+        uri
+    );
+    constructor_ref
+}
+
+ + +
@@ -932,6 +1169,51 @@ This function must be called if the collection name has been previously changed. + + + + +## Function `create_named_token_from_seed_as_collection_owner` + +Same functionality as create_named_token_from_seed, but the token can only be created by the collection owner. + + +
public fun create_named_token_from_seed_as_collection_owner(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name: string::String, seed: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+ + + +
+Implementation + + +
public fun create_named_token_from_seed_as_collection_owner(
+    creator: &signer,
+    collection: Object<Collection>,
+    description: String,
+    name: String,
+    seed: String,
+    royalty: Option<Royalty>,
+    uri: String,
+): ConstructorRef {
+    let seed = create_token_name_with_seed(&collection::name(collection), &name, &seed);
+    let constructor_ref = object::create_named_object(creator, seed);
+    create_common_with_collection_as_owner(
+        creator,
+        &constructor_ref,
+        collection,
+        description,
+        name,
+        option::none(),
+        royalty,
+        uri
+    );
+    constructor_ref
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-token-objects/sources/collection.move b/aptos-move/framework/aptos-token-objects/sources/collection.move index af88007f84dfa..ae545b241d606 100644 --- a/aptos-move/framework/aptos-token-objects/sources/collection.move +++ b/aptos-move/framework/aptos-token-objects/sources/collection.move @@ -18,6 +18,7 @@ /// * Add aggregator support when added to framework module aptos_token_objects::collection { use std::error; + use std::features; use std::option::{Self, Option}; use std::signer; use std::string::{Self, String}; @@ -49,6 +50,8 @@ module aptos_token_objects::collection { const EINVALID_MAX_SUPPLY: u64 = 9; /// The collection does not have a max supply const ENO_MAX_SUPPLY_IN_COLLECTION: u64 = 10; + /// The collection owner feature is not supported + const ECOLLECTION_OWNER_NOT_SUPPORTED: u64 = 11; const MAX_COLLECTION_NAME_LENGTH: u64 = 128; const MAX_URI_LENGTH: u64 = 512; @@ -210,6 +213,31 @@ module aptos_token_objects::collection { ) } + /// Same functionality as `create_fixed_collection`, but the caller is the owner of the collection. + /// This means that the caller can transfer the collection to another address. + /// This transfers ownership and minting permissions to the new address. + public fun create_fixed_collection_as_owner( + creator: &signer, + description: String, + max_supply: u64, + name: String, + royalty: Option, + uri: String, + ): ConstructorRef { + assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED)); + + let constructor_ref = create_fixed_collection( + creator, + description, + max_supply, + name, + royalty, + uri, + ); + enable_ungated_transfer(&constructor_ref); + constructor_ref + } + /// Creates an unlimited collection. This has support for supply tracking but does not limit /// the supply of tokens. public fun create_unlimited_collection( @@ -238,6 +266,29 @@ module aptos_token_objects::collection { ) } + /// Same functionality as `create_unlimited_collection`, but the caller is the owner of the collection. + /// This means that the caller can transfer the collection to another address. + /// This transfers ownership and minting permissions to the new address. + public fun create_unlimited_collection_as_owner( + creator: &signer, + description: String, + name: String, + royalty: Option, + uri: String, + ): ConstructorRef { + assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED)); + + let constructor_ref = create_unlimited_collection( + creator, + description, + name, + royalty, + uri, + ); + enable_ungated_transfer(&constructor_ref); + constructor_ref + } + /// Creates an untracked collection, or a collection that supports an arbitrary amount of /// tokens. This is useful for mass airdrops that fully leverage Aptos parallelization. /// TODO: Hide this until we bring back meaningful way to enforce burns @@ -296,9 +347,17 @@ module aptos_token_objects::collection { royalty::init(&constructor_ref, option::extract(&mut royalty)) }; + let transfer_ref = object::generate_transfer_ref(&constructor_ref); + object::disable_ungated_transfer(&transfer_ref); + constructor_ref } + inline fun enable_ungated_transfer(constructor_ref: &ConstructorRef) { + let transfer_ref = object::generate_transfer_ref(constructor_ref); + object::enable_ungated_transfer(&transfer_ref); + } + /// Generates the collections address based upon the creators address and the collection's name public fun create_collection_address(creator: &address, name: &String): address { object::create_object_address(creator, create_collection_seed(name)) @@ -747,6 +806,7 @@ module aptos_token_objects::collection { } #[test(creator = @0x123, trader = @0x456)] + #[expected_failure(abort_code = 0x50003, location = aptos_framework::object)] entry fun test_create_and_transfer(creator: &signer, trader: &signer) { let creator_address = signer::address_of(creator); let trader_address = signer::address_of(trader); @@ -757,7 +817,22 @@ module aptos_token_objects::collection { create_collection_address(&creator_address, &collection_name), ); assert!(object::owner(collection) == creator_address, 1); - // Transferring collections is allowed + object::transfer(creator, collection, trader_address); + } + + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] + entry fun test_create_and_transfer_as_owner(creator: &signer, trader: &signer, aptos_framework: &signer) { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let creator_address = signer::address_of(creator); + let trader_address = signer::address_of(trader); + let collection_name = string::utf8(b"collection name"); + create_unlimited_collection_as_owner_helper(creator, collection_name); + + let collection = object::address_to_object( + create_collection_address(&creator_address, &collection_name), + ); + assert!(object::owner(collection) == creator_address, 1); + // Transferring owned collections are allowed object::transfer(creator, collection, trader_address); assert!(object::owner(collection) == trader_address, 1); } @@ -944,6 +1019,17 @@ module aptos_token_objects::collection { ) } + #[test_only] + fun create_unlimited_collection_as_owner_helper(creator: &signer, name: String): ConstructorRef { + create_unlimited_collection_as_owner( + creator, + string::utf8(b"description"), + name, + option::none(), + string::utf8(b"uri"), + ) + } + #[test_only] /// Create a token as we cannot create a dependency cycle between collection and token modules. fun create_token(creator: &signer): signer { diff --git a/aptos-move/framework/aptos-token-objects/sources/token.move b/aptos-move/framework/aptos-token-objects/sources/token.move index 4187f5306393e..bee0a4a2e15b7 100644 --- a/aptos-move/framework/aptos-token-objects/sources/token.move +++ b/aptos-move/framework/aptos-token-objects/sources/token.move @@ -6,6 +6,7 @@ /// module aptos_token_objects::token { use std::error; + use std::features; use std::option::{Self, Option}; use std::string::{Self, String}; use std::signer; @@ -35,6 +36,8 @@ module aptos_token_objects::token { const ESEED_TOO_LONG: u64 = 7; /// The calling signer is not the owner const ENOT_OWNER: u64 = 8; + /// The collection owner feature is not supported + const ECOLLECTION_OWNER_NOT_SUPPORTED: u64 = 9; const MAX_TOKEN_NAME_LENGTH: u64 = 128; const MAX_TOKEN_SEED_LENGTH: u64 = 128; @@ -156,8 +159,56 @@ module aptos_token_objects::token { royalty: Option, uri: String, ) { - assert!(object::owner(collection) == signer::address_of(creator), error::unauthenticated(ENOT_OWNER)); + assert!(collection::creator(collection) == signer::address_of(creator), error::unauthenticated(ENOT_CREATOR)); + create_common_with_collection_internal( + constructor_ref, + collection, + description, + name_prefix, + name_with_index_suffix, + royalty, + uri + ); + } + + inline fun create_common_with_collection_as_owner( + owner: &signer, + constructor_ref: &ConstructorRef, + collection: Object, + description: String, + name_prefix: String, + // If option::some, numbered token is created - i.e. index is appended to the name. + // If option::none, name_prefix is the full name of the token. + name_with_index_suffix: Option, + royalty: Option, + uri: String, + ) { + assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED)); + assert!(object::owner(collection) == signer::address_of(owner), error::unauthenticated(ENOT_OWNER)); + + create_common_with_collection_internal( + constructor_ref, + collection, + description, + name_prefix, + name_with_index_suffix, + royalty, + uri + ); + } + + inline fun create_common_with_collection_internal( + constructor_ref: &ConstructorRef, + collection: Object, + description: String, + name_prefix: String, + // If option::some, numbered token is created - i.e. index is appended to the name. + // If option::none, name_prefix is the full name of the token. + name_with_index_suffix: Option, + royalty: Option, + uri: String, + ) { if (option::is_some(&name_with_index_suffix)) { // Be conservative, as we don't know what length the index will be, and assume worst case (20 chars in MAX_U64) assert!( @@ -262,6 +313,30 @@ module aptos_token_objects::token { constructor_ref } + /// Same functionality as `create_token`, but the token can only be created by the collection owner. + public fun create_token_as_collection_owner( + creator: &signer, + collection: Object, + description: String, + name: String, + royalty: Option, + uri: String, + ): ConstructorRef { + let creator_address = signer::address_of(creator); + let constructor_ref = object::create_object(creator_address); + create_common_with_collection_as_owner( + creator, + &constructor_ref, + collection, + description, + name, + option::none(), + royalty, + uri + ); + constructor_ref + } + /// Creates a new token object with a unique address and returns the ConstructorRef /// for additional specialization. /// The name is created by concatenating the (name_prefix, index, name_suffix). @@ -323,6 +398,31 @@ module aptos_token_objects::token { constructor_ref } + /// Same functionality as `create_numbered_token_object`, but the token can only be created by the collection owner. + public fun create_numbered_token_as_collection_owner( + creator: &signer, + collection: Object, + description: String, + name_with_index_prefix: String, + name_with_index_suffix: String, + royalty: Option, + uri: String, + ): ConstructorRef { + let creator_address = signer::address_of(creator); + let constructor_ref = object::create_object(creator_address); + create_common_with_collection_as_owner( + creator, + &constructor_ref, + collection, + description, + name_with_index_prefix, + option::some(name_with_index_suffix), + royalty, + uri + ); + constructor_ref + } + /// Creates a new token object from a token name and returns the ConstructorRef for /// additional specialization. /// This function must be called if the collection name has been previously changed. @@ -375,6 +475,30 @@ module aptos_token_objects::token { constructor_ref } + /// Same functionality as `create_named_token_object`, but the token can only be created by the collection owner. + public fun create_named_token_as_collection_owner( + creator: &signer, + collection: Object, + description: String, + name: String, + royalty: Option, + uri: String, + ): ConstructorRef { + let seed = create_token_seed(&collection::name(collection), &name); + let constructor_ref = object::create_named_object(creator, seed); + create_common_with_collection_as_owner( + creator, + &constructor_ref, + collection, + description, + name, + option::none(), + royalty, + uri + ); + constructor_ref + } + /// Creates a new token object from a token name and seed. /// Returns the ConstructorRef for additional specialization. /// This function must be called if the collection name has been previously changed. @@ -393,6 +517,31 @@ module aptos_token_objects::token { constructor_ref } + /// Same functionality as `create_named_token_from_seed`, but the token can only be created by the collection owner. + public fun create_named_token_from_seed_as_collection_owner( + creator: &signer, + collection: Object, + description: String, + name: String, + seed: String, + royalty: Option, + uri: String, + ): ConstructorRef { + let seed = create_token_name_with_seed(&collection::name(collection), &name, &seed); + let constructor_ref = object::create_named_object(creator, seed); + create_common_with_collection_as_owner( + creator, + &constructor_ref, + collection, + description, + name, + option::none(), + royalty, + uri + ); + constructor_ref + } + #[deprecated] /// DEPRECATED: Use `create` instead for identical behavior. /// @@ -723,9 +872,30 @@ module aptos_token_objects::token { assert!(option::some(expected_royalty) == royalty(token), 2); } + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] + fun test_create_and_transfer_token_as_collection_owner(creator: &signer, trader: &signer, aptos_framework: &signer) acquires Token { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let collection_name = string::utf8(b"collection name"); + let token_name = string::utf8(b"token name"); + + let extend_ref = create_collection_as_collection_owner_helper(creator, collection_name, 1); + let collection = get_collection_from_ref(&extend_ref); + create_named_token_as_collection_owner_helper(creator, collection, token_name); + + let creator_address = signer::address_of(creator); + let token_addr = create_token_address(&creator_address, &collection_name, &token_name); + let token = object::address_to_object(token_addr); + assert!(object::owner(token) == creator_address, 1); + object::transfer(creator, token, signer::address_of(trader)); + assert!(object::owner(token) == signer::address_of(trader), 1); + + let expected_royalty = royalty::create(25, 10000, creator_address); + assert!(option::some(expected_royalty) == royalty(token), 2); + } + #[test(creator = @0x123, trader = @0x456)] - #[expected_failure(abort_code = 0x40008, location = aptos_token_objects::token)] - fun test_create_token_non_collection_owner(creator: &signer, trader: &signer) { + #[expected_failure(abort_code = 0x40002, location = aptos_token_objects::token)] + fun test_create_token_non_creator(creator: &signer, trader: &signer) { let constructor_ref = &create_fixed_collection(creator, string::utf8(b"collection name"), 5); let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); create_token( @@ -734,17 +904,38 @@ module aptos_token_objects::token { ); } - #[test(creator = @0x123, trader = @0x456)] + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] #[expected_failure(abort_code = 0x40008, location = aptos_token_objects::token)] - fun test_create_named_token_non_collection_owner(creator: &signer, trader: &signer) { + fun test_create_token_non_collection_owner(creator: &signer, trader: &signer, aptos_framework: &signer) { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let constructor_ref = &create_fixed_collection_as_collection_owner(creator, string::utf8(b"collection name"), 5); + let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); + create_token_as_collection_owner( + trader, collection, string::utf8(b"token description"), string::utf8(b"token name"), + option::some(royalty::create(25, 10000, signer::address_of(creator))), string::utf8(b"uri"), + ); + } + + #[test(creator = @0x123, trader = @0x456)] + #[expected_failure(abort_code = 0x40002, location = aptos_token_objects::token)] + fun test_create_named_token_non_creator(creator: &signer, trader: &signer) { let constructor_ref = &create_fixed_collection(creator, string::utf8(b"collection name"), 5); let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); create_token_with_collection_helper(trader, collection, string::utf8(b"token name")); } - #[test(creator = @0x123, trader = @0x456)] + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] #[expected_failure(abort_code = 0x40008, location = aptos_token_objects::token)] - fun test_create_named_token_object_non_collection_owner(creator: &signer, trader: &signer) { + fun test_create_named_token_non_collection_owner(creator: &signer, trader: &signer, aptos_framework: &signer) { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let constructor_ref = &create_fixed_collection_as_collection_owner(creator, string::utf8(b"collection name"), 5); + let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); + create_named_token_as_collection_owner_helper(trader, collection, string::utf8(b"token name")); + } + + #[test(creator = @0x123, trader = @0x456)] + #[expected_failure(abort_code = 0x40002, location = aptos_token_objects::token)] + fun test_create_named_token_object_non_creator(creator: &signer, trader: &signer) { let constructor_ref = &create_fixed_collection(creator, string::utf8(b"collection name"), 5); let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); create_named_token_object( @@ -754,8 +945,8 @@ module aptos_token_objects::token { } #[test(creator = @0x123, trader = @0x456)] - #[expected_failure(abort_code = 0x40008, location = aptos_token_objects::token)] - fun test_create_named_token_from_seed_non_collection_owner(creator: &signer, trader: &signer) { + #[expected_failure(abort_code = 0x40002, location = aptos_token_objects::token)] + fun test_create_named_token_from_seed_non_creator(creator: &signer, trader: &signer) { let constructor_ref = &create_fixed_collection(creator, string::utf8(b"collection name"), 5); let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); create_named_token_object( @@ -764,6 +955,18 @@ module aptos_token_objects::token { ); } + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] + #[expected_failure(abort_code = 0x40008, location = aptos_token_objects::token)] + fun test_create_named_token_from_seed_non_collection_owner(creator: &signer, trader: &signer, aptos_framework: &signer) { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let constructor_ref = &create_fixed_collection_as_collection_owner(creator, string::utf8(b"collection name"), 5); + let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); + create_named_token_as_collection_owner( + trader, collection, string::utf8(b"token description"), string::utf8(b"token name"), + option::some(royalty::create(25, 10000, signer::address_of(creator))), string::utf8(b"uri"), + ); + } + #[test(creator = @0x123, trader = @0x456)] fun test_create_and_transfer_token_with_seed(creator: &signer, trader: &signer) acquires Token { let collection_name = string::utf8(b"collection name"); @@ -786,12 +989,13 @@ module aptos_token_objects::token { assert!(option::some(expected_royalty) == royalty(token), 2); } - #[test(creator = @0x123, trader = @0x456)] + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] #[expected_failure(abort_code = 0x40008, location = aptos_token_objects::token)] - fun test_create_token_after_transferring_collection(creator: &signer, trader: &signer) { - let constructor_ref = &create_fixed_collection(creator, string::utf8(b"collection name"), 5); + fun test_create_token_after_transferring_collection(creator: &signer, trader: &signer, aptos_framework: &signer) { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let constructor_ref = &create_fixed_collection_as_collection_owner(creator, string::utf8(b"collection name"), 5); let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); - create_token( + create_token_as_collection_owner( creator, collection, string::utf8(b"token description"), string::utf8(b"token name"), option::some(royalty::create(25, 10000, signer::address_of(creator))), string::utf8(b"uri"), ); @@ -799,17 +1003,18 @@ module aptos_token_objects::token { object::transfer(creator, collection, signer::address_of(trader)); // This should fail as the collection is no longer owned by the creator. - create_token( + create_token_as_collection_owner( creator, collection, string::utf8(b"token description"), string::utf8(b"token name"), option::some(royalty::create(25, 10000, signer::address_of(creator))), string::utf8(b"uri"), ); } - #[test(creator = @0x123, trader = @0x456)] - fun create_token_works_with_new_collection_owner(creator: &signer, trader: &signer) { - let constructor_ref = &create_fixed_collection(creator, string::utf8(b"collection name"), 5); + #[test(creator = @0x123, trader = @0x456, aptos_framework = @aptos_framework)] + fun create_token_works_with_new_collection_owner(creator: &signer, trader: &signer, aptos_framework: &signer) { + features::change_feature_flags_for_testing(aptos_framework, vector[features::get_collection_owner_feature()], vector[]); + let constructor_ref = &create_fixed_collection_as_collection_owner(creator, string::utf8(b"collection name"), 5); let collection = get_collection_from_ref(&object::generate_extend_ref(constructor_ref)); - create_token( + create_token_as_collection_owner( creator, collection, string::utf8(b"token description"), string::utf8(b"token name"), option::some(royalty::create(25, 10000, signer::address_of(creator))), string::utf8(b"uri"), ); @@ -817,7 +1022,7 @@ module aptos_token_objects::token { object::transfer(creator, collection, signer::address_of(trader)); // This should pass as `trader` is the new collection owner - create_token( + create_token_as_collection_owner( trader, collection, string::utf8(b"token description"), string::utf8(b"token name"), option::some(royalty::create(25, 10000, signer::address_of(creator))), string::utf8(b"uri"), ); @@ -1098,6 +1303,12 @@ module aptos_token_objects::token { object::generate_extend_ref(&constructor_ref) } + #[test_only] + fun create_collection_as_collection_owner_helper(creator: &signer, collection_name: String, max_supply: u64): ExtendRef { + let constructor_ref = create_fixed_collection_as_collection_owner(creator, collection_name, max_supply); + object::generate_extend_ref(&constructor_ref) + } + #[test_only] fun create_fixed_collection(creator: &signer, collection_name: String, max_supply: u64): ConstructorRef { collection::create_fixed_collection( @@ -1110,6 +1321,22 @@ module aptos_token_objects::token { ) } + #[test_only] + fun create_fixed_collection_as_collection_owner( + creator: &signer, + collection_name: String, + max_supply: u64, + ): ConstructorRef { + collection::create_fixed_collection_as_owner( + creator, + string::utf8(b"collection description as owner"), + max_supply, + collection_name, + option::none(), + string::utf8(b"collection uri as owner"), + ) + } + #[test_only] fun create_token_helper(creator: &signer, collection_name: String, token_name: String): ConstructorRef { create_named_token( @@ -1123,7 +1350,11 @@ module aptos_token_objects::token { } #[test_only] - fun create_token_with_collection_helper(creator: &signer, collection: Object, token_name: String): ConstructorRef { + fun create_token_with_collection_helper( + creator: &signer, + collection: Object, + token_name: String + ): ConstructorRef { create_named_token_object( creator, collection, @@ -1135,7 +1366,28 @@ module aptos_token_objects::token { } #[test_only] - fun create_token_object_with_seed_helper(creator: &signer, collection: Object, token_name: String, seed: String): ConstructorRef { + fun create_named_token_as_collection_owner_helper( + creator: &signer, + collection: Object, + token_name: String + ): ConstructorRef { + create_named_token_as_collection_owner( + creator, + collection, + string::utf8(b"token description"), + token_name, + option::some(royalty::create(25, 10000, signer::address_of(creator))), + string::utf8(b"uri"), + ) + } + + #[test_only] + fun create_token_object_with_seed_helper( + creator: &signer, + collection: Object, + token_name: String, + seed: String + ): ConstructorRef { create_named_token_from_seed( creator, collection, @@ -1148,7 +1400,11 @@ module aptos_token_objects::token { } #[test_only] - fun create_numbered_token_helper(creator: &signer, collection: Object, token_prefix: String): ConstructorRef { + fun create_numbered_token_helper( + creator: &signer, + collection: Object, + token_prefix: String + ): ConstructorRef { create_numbered_token_object( creator, collection, diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index be36f1c502b96..c1513bbd1560f 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -131,6 +131,8 @@ return true. - [Function `abort_if_multisig_payload_mismatch_enabled`](#0x1_features_abort_if_multisig_payload_mismatch_enabled) - [Function `get_transaction_simulation_enhancement_feature`](#0x1_features_get_transaction_simulation_enhancement_feature) - [Function `transaction_simulation_enhancement_enabled`](#0x1_features_transaction_simulation_enhancement_enabled) +- [Function `get_collection_owner_feature`](#0x1_features_get_collection_owner_feature) +- [Function `is_collection_owner_enabled`](#0x1_features_is_collection_owner_enabled) - [Function `change_feature_flags`](#0x1_features_change_feature_flags) - [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal) - [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch) @@ -354,6 +356,15 @@ Lifetime: transient + + + + +
const COLLECTION_OWNER: u64 = 77;
+
+ + + Whether gas fees are collected and distributed to the block proposers. @@ -3168,6 +3179,52 @@ Lifetime: transient + + + + +## Function `get_collection_owner_feature` + + + +
public fun get_collection_owner_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_collection_owner_feature(): u64 { COLLECTION_OWNER }
+
+ + + +
+ + + +## Function `is_collection_owner_enabled` + + + +
public fun is_collection_owner_enabled(): bool
+
+ + + +
+Implementation + + +
public fun is_collection_owner_enabled(): bool acquires Features {
+    is_enabled(COLLECTION_OWNER)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 44c21927c8aef..69e34db01c19b 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -595,6 +595,14 @@ module std::features { is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT) } + const COLLECTION_OWNER: u64 = 79; + + public fun get_collection_owner_feature(): u64 { COLLECTION_OWNER } + + public fun is_collection_owner_enabled(): bool acquires Features { + is_enabled(COLLECTION_OWNER) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index 5522a395335b3..7c5f24edd4c36 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -94,6 +94,7 @@ pub enum FeatureFlag { REJECT_UNSTABLE_BYTECODE_FOR_SCRIPT = 76, FEDERATED_KEYLESS = 77, TRANSACTION_SIMULATION_ENHANCEMENT = 78, + COLLECTION_OWNER = 79, } impl FeatureFlag { From f68531a44ef61e65cbca0c7c9fec8ef13bb74afe Mon Sep 17 00:00:00 2001 From: JohnChangUK Date: Wed, 21 Aug 2024 14:35:08 -0400 Subject: [PATCH 2/2] Separate out collection creator and owner permissions --- .../framework/aptos-framework/doc/jwks.md | 113 +++++++++++++++++- .../framework/move-stdlib/doc/features.md | 38 +++--- 2 files changed, 130 insertions(+), 21 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/jwks.md b/aptos-move/framework/aptos-framework/doc/jwks.md index e0c09f7fa7214..2f3e0f34e307b 100644 --- a/aptos-move/framework/aptos-framework/doc/jwks.md +++ b/aptos-move/framework/aptos-framework/doc/jwks.md @@ -27,7 +27,9 @@ have a simple layout which is easily accessible in Rust. - [Struct `PatchUpsertJWK`](#0x1_jwks_PatchUpsertJWK) - [Resource `Patches`](#0x1_jwks_Patches) - [Resource `PatchedJWKs`](#0x1_jwks_PatchedJWKs) +- [Resource `FederatedJWKs`](#0x1_jwks_FederatedJWKs) - [Constants](#@Constants_0) +- [Function `patch_federated_jwks`](#0x1_jwks_patch_federated_jwks) - [Function `get_patched_jwk`](#0x1_jwks_get_patched_jwk) - [Function `try_get_patched_jwk`](#0x1_jwks_try_get_patched_jwk) - [Function `upsert_oidc_provider`](#0x1_jwks_upsert_oidc_provider) @@ -59,7 +61,8 @@ have a simple layout which is easily accessible in Rust. - [Function `on_new_epoch`](#@Specification_1_on_new_epoch) -
use 0x1::chain_status;
+
use 0x1::bcs;
+use 0x1::chain_status;
 use 0x1::comparator;
 use 0x1::config_buffer;
 use 0x1::copyable_any;
@@ -67,6 +70,7 @@ have a simple layout which is easily accessible in Rust.
 use 0x1::event;
 use 0x1::option;
 use 0x1::reconfiguration;
+use 0x1::signer;
 use 0x1::string;
 use 0x1::system_addresses;
 use 0x1::vector;
@@ -589,6 +593,34 @@ This is what applications should consume.
 
 
 
+
+Fields + + +
+
+jwks: jwks::AllProvidersJWKs +
+
+ +
+
+ + +
+ + + +## Resource `FederatedJWKs` + +JWKs for federated keyless accounts are stored in this resource. + + +
struct FederatedJWKs has drop, key
+
+ + +
Fields @@ -610,6 +642,24 @@ This is what applications should consume. ## Constants + + + + +
const EFEDERATED_JWKS_TOO_LARGE: u64 = 8;
+
+ + + + + + + +
const EINSTALL_FEDERATED_JWKS_AT_APTOS_FRAMEWORK: u64 = 7;
+
+ + + @@ -709,6 +759,65 @@ This is what applications should consume. + + +We limit the size of a PatchedJWKs resource installed by a dapp owner for federated keyless accounts. +Note: If too large, validators waste work reading it for invalid TXN signatures. + + +
const MAX_FEDERATED_JWKS_SIZE_BYTES: u64 = 2048;
+
+ + + + + +## Function `patch_federated_jwks` + +Called by a federated keyless dapp owner to install the JWKs for the federated OIDC provider (e.g., Auth0, AWS +Cognito, etc). + +For type-safety, we explicitly use a struct FederatedJWKs { jwks: AllProviderJWKs } instead of +reusing PatchedJWKs { jwks: AllProviderJWKs }, which is a JWK-consensus-specific struct. We'd +need to be careful how we read it in Rust (but BCS serialization should be the same). + + +
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<jwks::Patch>)
+
+ + + +
+Implementation + + +
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<Patch>) acquires FederatedJWKs {
+    // Prevents accidental calls in 0x1::jwks that install federated JWKs at the Aptos framework address.
+    assert!(!system_addresses::is_aptos_framework_address(signer::address_of(jwk_owner)),
+        error::invalid_argument(EINSTALL_FEDERATED_JWKS_AT_APTOS_FRAMEWORK)
+    );
+
+    let jwk_addr = signer::address_of(jwk_owner);
+    if (!exists<FederatedJWKs>(jwk_addr)) {
+        move_to(jwk_owner, FederatedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
+    };
+
+    let fed_jwks = borrow_global_mut<FederatedJWKs>(jwk_addr);
+    vector::for_each_ref(&patches, |obj|{
+        let patch: &Patch = obj;
+        apply_patch(&mut fed_jwks.jwks, *patch);
+    });
+
+    // TODO: Can we check the size more efficiently instead of serializing it via BCS?
+    let num_bytes = vector::length(&bcs::to_bytes(fed_jwks));
+    assert!(num_bytes < MAX_FEDERATED_JWKS_SIZE_BYTES, error::invalid_argument(EFEDERATED_JWKS_TOO_LARGE));
+}
+
+ + + +
+ ## Function `get_patched_jwk` @@ -1315,7 +1424,7 @@ Regenerate PatchedJWKs f ## Function `try_get_jwk_by_issuer` -Get a JWK by issuer and key ID from a AllProvidersJWKs, if it exists. +Get a JWK by issuer and key ID from an AllProvidersJWKs, if it exists.
fun try_get_jwk_by_issuer(jwks: &jwks::AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md
index c1513bbd1560f..ae1cb772989c2 100644
--- a/aptos-move/framework/move-stdlib/doc/features.md
+++ b/aptos-move/framework/move-stdlib/doc/features.md
@@ -360,7 +360,7 @@ Lifetime: transient
 
 
 
-
const COLLECTION_OWNER: u64 = 77;
+
const COLLECTION_OWNER: u64 = 79;
 
@@ -3181,13 +3181,13 @@ Lifetime: transient
- + -## Function `get_collection_owner_feature` +## Function `get_transaction_simulation_enhancement_feature` -
public fun get_collection_owner_feature(): u64
+
public fun get_transaction_simulation_enhancement_feature(): u64
 
@@ -3196,20 +3196,20 @@ Lifetime: transient Implementation -
public fun get_collection_owner_feature(): u64 { COLLECTION_OWNER }
+
public fun get_transaction_simulation_enhancement_feature(): u64 { TRANSACTION_SIMULATION_ENHANCEMENT }
 
- + -## Function `is_collection_owner_enabled` +## Function `transaction_simulation_enhancement_enabled` -
public fun is_collection_owner_enabled(): bool
+
public fun transaction_simulation_enhancement_enabled(): bool
 
@@ -3218,8 +3218,8 @@ Lifetime: transient Implementation -
public fun is_collection_owner_enabled(): bool acquires Features {
-    is_enabled(COLLECTION_OWNER)
+
public fun transaction_simulation_enhancement_enabled(): bool acquires Features {
+    is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT)
 }
 
@@ -3227,13 +3227,13 @@ Lifetime: transient - + -## Function `get_transaction_simulation_enhancement_feature` +## Function `get_collection_owner_feature` -
public fun get_transaction_simulation_enhancement_feature(): u64
+
public fun get_collection_owner_feature(): u64
 
@@ -3242,20 +3242,20 @@ Lifetime: transient Implementation -
public fun get_transaction_simulation_enhancement_feature(): u64 { TRANSACTION_SIMULATION_ENHANCEMENT }
+
public fun get_collection_owner_feature(): u64 { COLLECTION_OWNER }
 
- + -## Function `transaction_simulation_enhancement_enabled` +## Function `is_collection_owner_enabled` -
public fun transaction_simulation_enhancement_enabled(): bool
+
public fun is_collection_owner_enabled(): bool
 
@@ -3264,8 +3264,8 @@ Lifetime: transient Implementation -
public fun transaction_simulation_enhancement_enabled(): bool acquires Features {
-    is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT)
+
public fun is_collection_owner_enabled(): bool acquires Features {
+    is_enabled(COLLECTION_OWNER)
 }