diff --git a/aptos-move/framework/aptos-token-objects/doc/property_map.md b/aptos-move/framework/aptos-token-objects/doc/property_map.md index 048b4de8b9b07..5983af879154c 100644 --- a/aptos-move/framework/aptos-token-objects/doc/property_map.md +++ b/aptos-move/framework/aptos-token-objects/doc/property_map.md @@ -44,6 +44,8 @@ represent types and storing values in bcs format. - [Function `update_typed`](#0x4_property_map_update_typed) - [Function `update_internal`](#0x4_property_map_update_internal) - [Function `remove`](#0x4_property_map_remove) +- [Function `exists_at`](#0x4_property_map_exists_at) +- [Function `delete`](#0x4_property_map_delete) - [Function `assert_end_to_end_input`](#0x4_property_map_assert_end_to_end_input) @@ -1262,6 +1264,54 @@ Removes a property from the map, ensuring that it does in fact exist + + + + +## Function `exists_at` + + + +
public(friend) fun exists_at(addr: address): bool
+
+
+
+
+public(friend) fun exists_at(addr: address): bool {
+ exists<PropertyMap>(addr)
+}
+
+
+
+
+public(friend) fun delete(addr: address): property_map::PropertyMap
+
+
+
+
+public(friend) fun delete(addr: address): PropertyMap acquires PropertyMap {
+ move_from<PropertyMap>(addr)
+}
+
+
+
+
#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
-struct Royalty has copy, drop, key
+struct Royalty has copy, drop, store, key
@@ -277,7 +277,7 @@ Creates a new royalty, verifying that it is a valid percentage
-public(friend) fun delete(addr: address)
+public(friend) fun delete(addr: address): royalty::Royalty
@@ -286,9 +286,9 @@ Creates a new royalty, verifying that it is a valid percentage
Implementation
-public(friend) fun delete(addr: address) acquires Royalty {
+public(friend) fun delete(addr: address): Royalty acquires Royalty {
assert!(exists<Royalty>(addr), error::not_found(EROYALTY_DOES_NOT_EXIST));
- move_from<Royalty>(addr);
+ move_from<Royalty>(addr)
}
diff --git a/aptos-move/framework/aptos-token-objects/doc/token.md b/aptos-move/framework/aptos-token-objects/doc/token.md
index f850767b9a17b..f54896d00b86f 100644
--- a/aptos-move/framework/aptos-token-objects/doc/token.md
+++ b/aptos-move/framework/aptos-token-objects/doc/token.md
@@ -15,8 +15,11 @@ token are:
- [Resource `ConcurrentTokenIdentifiers`](#0x4_token_ConcurrentTokenIdentifiers)
- [Struct `BurnRef`](#0x4_token_BurnRef)
- [Struct `MutatorRef`](#0x4_token_MutatorRef)
+- [Struct `CompressionRef`](#0x4_token_CompressionRef)
- [Struct `MutationEvent`](#0x4_token_MutationEvent)
- [Struct `Mutation`](#0x4_token_Mutation)
+- [Struct `CompressedToken`](#0x4_token_CompressedToken)
+- [Resource `CompressedCollection`](#0x4_token_CompressedCollection)
- [Constants](#@Constants_0)
- [Function `create_common`](#0x4_token_create_common)
- [Function `create_common_with_collection`](#0x4_token_create_common_with_collection)
@@ -41,6 +44,8 @@ token are:
- [Function `generate_mutator_ref`](#0x4_token_generate_mutator_ref)
- [Function `generate_burn_ref`](#0x4_token_generate_burn_ref)
- [Function `address_from_burn_ref`](#0x4_token_address_from_burn_ref)
+- [Function `generate_compression_ref`](#0x4_token_generate_compression_ref)
+- [Function `address_from_compression_ref`](#0x4_token_address_from_compression_ref)
- [Function `borrow`](#0x4_token_borrow)
- [Function `creator`](#0x4_token_creator)
- [Function `collection_name`](#0x4_token_collection_name)
@@ -55,11 +60,16 @@ token are:
- [Function `set_description`](#0x4_token_set_description)
- [Function `set_name`](#0x4_token_set_name)
- [Function `set_uri`](#0x4_token_set_uri)
+- [Function `collection_enable_compressed_tokens`](#0x4_token_collection_enable_compressed_tokens)
+- [Function `compress_token`](#0x4_token_compress_token)
+- [Function `decompress_token`](#0x4_token_decompress_token)
use 0x1::aggregator_v2;
+use 0x1::any_map;
use 0x1::error;
use 0x1::event;
+use 0x1::external_object;
use 0x1::features;
use 0x1::object;
use 0x1::option;
@@ -67,6 +77,7 @@ token are:
use 0x1::string;
use 0x1::vector;
use 0x4::collection;
+use 0x4::property_map;
use 0x4::royalty;
@@ -104,6 +115,7 @@ Represents the common fields to all tokens.
Was populated until concurrent_token_v2_enabled feature flag was enabled.
Unique identifier within the collection, optional, 0 means unassigned
+ DEPRECATED
description: string::String
@@ -120,6 +132,7 @@ Represents the common fields to all tokens.
The name of the token, which should be unique within the collection; the length of name
should be smaller than 128, characters, eg: "Aptos Animal #1234"
+ DEPRECATED
uri: string::String
@@ -273,6 +286,33 @@ This enables mutating description and URI by higher level services.
+
+
+
+
+## Struct `CompressionRef`
+
+
+
+struct CompressionRef has drop, store
+
+
+
+
+
+Fields
+
+
+
+-
+
inner: object::DeleteAndRecreateRef
+
+-
+
+
+
+
+
@@ -360,6 +400,88 @@ directly understand the behavior in a writeset.
+
+
+
+
+## Struct `CompressedToken`
+
+
+
+struct CompressedToken has drop, store
+
+
+
+
+
+Fields
+
+
+
+-
+
collection: object::Object<collection::Collection>
+
+-
+ The collection from which this token resides.
+
+-
+
index: u64
+
+-
+ Unique identifier within the collection, optional, 0 means unassigned
+
+-
+
description: string::String
+
+-
+ A brief description of the token.
+
+-
+
name: string::String
+
+-
+ The name of the token, which should be unique within the collection; the length of name
+ should be smaller than 128, characters, eg: "Aptos Animal #1234"
+
+-
+
uri: string::String
+
+-
+ The Uniform Resource Identifier (uri) pointing to the JSON file stored in off-chain
+ storage; the URL length will likely need a maximum any suggestions?
+
+
+
+
+
+
+
+
+## Resource `CompressedCollection`
+
+Represents the common fields for a collection.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct CompressedCollection has key
+
+
+
+
+
+Fields
+
+
+
+-
+
dummy_field: bool
+
+-
+
+
+
+
+
@@ -425,6 +547,24 @@ The description is over the maximum length
+
+
+
+
+const ECOLLECTION_ALREADY_COMPRESSED: u64 = 30;
+
+
+
+
+
+
+
+
+const ECOLLECTION_NOT_COMPRESSED: u64 = 31;
+
+
+
+
The field being changed is not mutable
@@ -1456,6 +1596,56 @@ Extracts the tokens address from a BurnRef.
+
+
+
+
+## Function `generate_compression_ref`
+
+
+
+public fun generate_compression_ref(ref: &object::ConstructorRef): token::CompressionRef
+
+
+
+
+
+Implementation
+
+
+public fun generate_compression_ref(ref: &ConstructorRef): CompressionRef {
+ CompressionRef {
+ inner: object::generate_delete_and_recreate_ref(ref),
+ }
+}
+
+
+
+
+
+
+
+
+## Function `address_from_compression_ref`
+
+
+
+public fun address_from_compression_ref(ref: &token::CompressionRef): address
+
+
+
+
+
+Implementation
+
+
+public fun address_from_compression_ref(ref: &CompressionRef): address {
+ ref.inner.address_from_delete_and_recreate_ref()
+}
+
+
+
+
@@ -1768,7 +1958,7 @@ as that would prohibit transactions to be executed in parallel.
};
if (royalty::exists_at(addr)) {
- royalty::delete(addr)
+ royalty::delete(addr);
};
let Token {
@@ -1938,6 +2128,181 @@ as that would prohibit transactions to be executed in parallel.
+
+
+
+
+## Function `collection_enable_compressed_tokens`
+
+
+
+public fun collection_enable_compressed_tokens(constructor_ref: object::ConstructorRef)
+
+
+
+
+
+Implementation
+
+
+public fun collection_enable_compressed_tokens(
+ constructor_ref: ConstructorRef, // TODO - should we support with ExtendRef with existing collections?
+) {
+ let object_signer = object::generate_signer(&constructor_ref);
+
+ let compressed_collection = CompressedCollection {
+ };
+ assert!(!exists<CompressedCollection>(signer::address_of(&object_signer)), error::invalid_argument(ECOLLECTION_ALREADY_COMPRESSED));
+ move_to(&object_signer, compressed_collection);
+}
+
+
+
+
+
+
+
+
+## Function `compress_token`
+
+
+
+public fun compress_token<P: drop, store>(compression_ref: token::CompressionRef, resources: any_map::AnyMap, mut_permission: P)
+
+
+
+
+
+Implementation
+
+
+public fun compress_token<P: drop + store>(
+ // TODO: should we gate it to collection creator? do a permission/ref? anyone? owner of the token only?
+ // creator: &signer,
+ compression_ref: CompressionRef,
+ resources: AnyMap,
+ mut_permission: P,
+) acquires Token, TokenIdentifiers {
+ let object_addr = object::address_from_delete_and_recreate_ref(&compression_ref.inner);
+
+ let Token {
+ collection,
+ index: deprecated_index,
+ description,
+ name: deprecated_name,
+ uri,
+ mutation_events,
+ } = move_from<Token>(object_addr);
+
+ // assert!(object::owner(collection) == signer::address_of(creator), error::unauthenticated(ENOT_OWNER));
+ assert!(exists<CompressedCollection>(object::object_address(&collection)), error::invalid_argument(ECOLLECTION_NOT_COMPRESSED));
+
+ event::destroy_handle(mutation_events);
+
+ let (index, name) = if (exists<TokenIdentifiers>(object_addr)) {
+ let TokenIdentifiers {
+ index,
+ name,
+ } = move_from<TokenIdentifiers>(object_addr);
+ (aggregator_v2::read_snapshot(&index), aggregator_v2::read_derived_string(&name))
+ } else {
+ (deprecated_index, deprecated_name)
+ };
+
+ let compressed_token = CompressedToken {
+ collection,
+ index,
+ description,
+ name,
+ uri,
+ };
+
+ resources.add(compressed_token);
+
+ if (royalty::exists_at(object_addr)) {
+ resources.add(royalty::delete(object_addr));
+ };
+
+ if (property_map::exists_at(object_addr)) {
+ resources.add(property_map::delete(object_addr));
+ };
+
+ let CompressionRef { inner } = compression_ref;
+ external_object::move_existing_object_to_external_storage(inner, resources, mut_permission)
+}
+
+
+
+
+
+
+
+
+## Function `decompress_token`
+
+
+
+public fun decompress_token<P: drop, store>(external_bytes: vector<u8>, mut_permission: P): (object::ConstructorRef, external_object::MovingToStateObject)
+
+
+
+
+
+Implementation
+
+
+public fun decompress_token<P: drop + store>(
+ // TODO: should we gate it to collection creator? do a permission/ref? anyone? owner of the token only?
+ // creator: &signer
+ external_bytes: vector<u8>,
+ mut_permission: P,
+): (ConstructorRef, MovingToStateObject) {
+ let (constructor_ref, resources_to_move) = external_object::move_external_object_to_state(external_bytes, mut_permission);
+
+ let object_signer = object::generate_signer(&constructor_ref);
+
+ let CompressedToken {
+ collection,
+ index,
+ description,
+ name,
+ uri,
+ } = resources_to_move.get_resources_mut().remove();
+
+ let deprecated_index = 0;
+ let deprecated_name = string::utf8(b"");
+
+ let token_concurrent = TokenIdentifiers {
+ index: aggregator_v2::create_snapshot(index),
+ name: aggregator_v2::create_derived_string(name),
+ };
+ move_to(&object_signer, token_concurrent);
+
+ let token = Token {
+ collection,
+ index: deprecated_index,
+ description,
+ name: deprecated_name,
+ uri,
+ mutation_events: object::new_event_handle(&object_signer),
+ };
+ move_to(&object_signer, token);
+
+ let royalty = any_map::remove_if_present<Royalty>(resources_to_move.get_resources_mut());
+ if (option::is_some(&royalty)) {
+ royalty::init(&constructor_ref, option::extract(&mut royalty))
+ };
+ let property_map = any_map::remove_if_present<PropertyMap>(resources_to_move.get_resources_mut());
+ if (option::is_some(&property_map)) {
+ property_map::init(&constructor_ref, option::extract(&mut property_map))
+ };
+
+ (constructor_ref, resources_to_move)
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-token-objects/sources/property_map.move b/aptos-move/framework/aptos-token-objects/sources/property_map.move
index e71c120fa2456..2727f1a1f70b6 100644
--- a/aptos-move/framework/aptos-token-objects/sources/property_map.move
+++ b/aptos-move/framework/aptos-token-objects/sources/property_map.move
@@ -11,6 +11,8 @@ module aptos_token_objects::property_map {
use aptos_std::type_info;
use aptos_framework::object::{Self, ConstructorRef, Object, ExtendRef, ObjectCore};
+ friend aptos_token_objects::token;
+
// Errors
/// The property map does not exist
const EPROPERTY_MAP_DOES_NOT_EXIST: u64 = 1;
@@ -347,6 +349,14 @@ module aptos_token_objects::property_map {
simple_map::remove(&mut property_map.inner, key);
}
+ public(friend) fun exists_at(addr: address): bool {
+ exists(addr)
+ }
+
+ public(friend) fun delete(addr: address): PropertyMap acquires PropertyMap {
+ move_from(addr)
+ }
+
// Tests
#[test(creator = @0x123)]
fun test_end_to_end(creator: &signer) acquires PropertyMap {
diff --git a/aptos-move/framework/aptos-token-objects/sources/royalty.move b/aptos-move/framework/aptos-token-objects/sources/royalty.move
index e3068b68ad4ff..6bdcde96d6537 100644
--- a/aptos-move/framework/aptos-token-objects/sources/royalty.move
+++ b/aptos-move/framework/aptos-token-objects/sources/royalty.move
@@ -20,7 +20,7 @@ module aptos_token_objects::royalty {
///
/// Royalties are optional for a collection. Royalty percentage is calculated
/// by (numerator / denominator) * 100%
- struct Royalty has copy, drop, key {
+ struct Royalty has copy, drop, key, store {
numerator: u64,
denominator: u64,
/// The recipient of royalty payments. See the `shared_account` for how to handle multiple
@@ -66,9 +66,9 @@ module aptos_token_objects::royalty {
exists(addr)
}
- public(friend) fun delete(addr: address) acquires Royalty {
+ public(friend) fun delete(addr: address): Royalty acquires Royalty {
assert!(exists(addr), error::not_found(EROYALTY_DOES_NOT_EXIST));
- move_from(addr);
+ move_from(addr)
}
// Accessors
diff --git a/aptos-move/framework/aptos-token-objects/sources/token.move b/aptos-move/framework/aptos-token-objects/sources/token.move
index 1128114036aa5..193ece2d52839 100644
--- a/aptos-move/framework/aptos-token-objects/sources/token.move
+++ b/aptos-move/framework/aptos-token-objects/sources/token.move
@@ -11,11 +11,14 @@ module aptos_token_objects::token {
use std::string::{Self, String};
use std::signer;
use std::vector;
+ use aptos_std::any_map::{Self, AnyMap};
use aptos_framework::aggregator_v2::{Self, AggregatorSnapshot, DerivedStringSnapshot};
+ use aptos_framework::external_object::{Self, MovingToStateObject};
use aptos_framework::event;
use aptos_framework::object::{Self, ConstructorRef, Object};
use aptos_token_objects::collection::{Self, Collection};
use aptos_token_objects::royalty::{Self, Royalty};
+ use aptos_token_objects::property_map::{Self, PropertyMap};
#[test_only]
use aptos_framework::object::ExtendRef;
@@ -53,8 +56,8 @@ module aptos_token_objects::token {
/// Was populated until concurrent_token_v2_enabled feature flag was enabled.
///
/// Unique identifier within the collection, optional, 0 means unassigned
+ /// DEPRECATED
index: u64,
- // DEPRECATED
/// A brief description of the token.
description: String,
/// Deprecated in favor of `name` inside TokenIdentifiers.
@@ -62,8 +65,8 @@ module aptos_token_objects::token {
///
/// The name of the token, which should be unique within the collection; the length of name
/// should be smaller than 128, characters, eg: "Aptos Animal #1234"
+ /// DEPRECATED
name: String,
- // DEPRECATED
/// The Uniform Resource Identifier (uri) pointing to the JSON file stored in off-chain
/// storage; the URL length will likely need a maximum any suggestions?
uri: String,
@@ -103,6 +106,10 @@ module aptos_token_objects::token {
self: address,
}
+ struct CompressionRef has drop, store {
+ inner: object::DeleteAndRecreateRef,
+ }
+
/// Contains the mutated fields name. This makes the life of indexers easier, so that they can
/// directly understand the behavior in a writeset.
struct MutationEvent has drop, store {
@@ -624,6 +631,16 @@ module aptos_token_objects::token {
}
}
+ public fun generate_compression_ref(ref: &ConstructorRef): CompressionRef {
+ CompressionRef {
+ inner: object::generate_delete_and_recreate_ref(ref),
+ }
+ }
+
+ public fun address_from_compression_ref(ref: &CompressionRef): address {
+ ref.inner.address_from_delete_and_recreate_ref()
+ }
+
// Accessors
inline fun borrow(token: &Object): &Token acquires Token {
@@ -749,7 +766,7 @@ module aptos_token_objects::token {
};
if (royalty::exists_at(addr)) {
- royalty::delete(addr)
+ royalty::delete(addr);
};
let Token {
@@ -1434,4 +1451,143 @@ module aptos_token_objects::token {
let collection_address = signer::address_of(&object::generate_signer_for_extending(extend_ref));
object::address_to_object(collection_address)
}
+
+
+ const ECOLLECTION_ALREADY_COMPRESSED: u64 = 30;
+ const ECOLLECTION_NOT_COMPRESSED: u64 = 31;
+
+ struct CompressedToken has drop, store {
+ /// The collection from which this token resides.
+ collection: Object,
+ /// Unique identifier within the collection, optional, 0 means unassigned
+ index: u64, // TODO change to AggregatorSnapshot
+ /// A brief description of the token.
+ description: String,
+ /// The name of the token, which should be unique within the collection; the length of name
+ /// should be smaller than 128, characters, eg: "Aptos Animal #1234"
+ name: String, // TODO change to AggregatorSnapshot
+ /// The Uniform Resource Identifier (uri) pointing to the JSON file stored in off-chain
+ /// storage; the URL length will likely need a maximum any suggestions?
+ uri: String,
+ }
+
+ #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
+ /// Represents the common fields for a collection.
+ struct CompressedCollection has key {
+ }
+
+ public fun collection_enable_compressed_tokens(
+ constructor_ref: ConstructorRef, // TODO - should we support with ExtendRef with existing collections?
+ ) {
+ let object_signer = object::generate_signer(&constructor_ref);
+
+ let compressed_collection = CompressedCollection {
+ };
+ assert!(!exists(signer::address_of(&object_signer)), error::invalid_argument(ECOLLECTION_ALREADY_COMPRESSED));
+ move_to(&object_signer, compressed_collection);
+ }
+
+ public fun compress_token(
+ // TODO: should we gate it to collection creator? do a permission/ref? anyone? owner of the token only?
+ // creator: &signer,
+ compression_ref: CompressionRef,
+ resources: AnyMap,
+ mut_permission: P,
+ ) acquires Token, TokenIdentifiers {
+ let object_addr = object::address_from_delete_and_recreate_ref(&compression_ref.inner);
+
+ let Token {
+ collection,
+ index: deprecated_index,
+ description,
+ name: deprecated_name,
+ uri,
+ mutation_events,
+ } = move_from(object_addr);
+
+ // assert!(object::owner(collection) == signer::address_of(creator), error::unauthenticated(ENOT_OWNER));
+ assert!(exists(object::object_address(&collection)), error::invalid_argument(ECOLLECTION_NOT_COMPRESSED));
+
+ event::destroy_handle(mutation_events);
+
+ let (index, name) = if (exists(object_addr)) {
+ let TokenIdentifiers {
+ index,
+ name,
+ } = move_from(object_addr);
+ (aggregator_v2::read_snapshot(&index), aggregator_v2::read_derived_string(&name))
+ } else {
+ (deprecated_index, deprecated_name)
+ };
+
+ let compressed_token = CompressedToken {
+ collection,
+ index,
+ description,
+ name,
+ uri,
+ };
+
+ resources.add(compressed_token);
+
+ if (royalty::exists_at(object_addr)) {
+ resources.add(royalty::delete(object_addr));
+ };
+
+ if (property_map::exists_at(object_addr)) {
+ resources.add(property_map::delete(object_addr));
+ };
+
+ let CompressionRef { inner } = compression_ref;
+ external_object::move_existing_object_to_external_storage(inner, resources, mut_permission)
+ }
+
+ public fun decompress_token(
+ // TODO: should we gate it to collection creator? do a permission/ref? anyone? owner of the token only?
+ // creator: &signer
+ external_bytes: vector,
+ mut_permission: P,
+ ): (ConstructorRef, MovingToStateObject) {
+ let (constructor_ref, resources_to_move) = external_object::move_external_object_to_state(external_bytes, mut_permission);
+
+ let object_signer = object::generate_signer(&constructor_ref);
+
+ let CompressedToken {
+ collection,
+ index,
+ description,
+ name,
+ uri,
+ } = resources_to_move.get_resources_mut().remove();
+
+ let deprecated_index = 0;
+ let deprecated_name = string::utf8(b"");
+
+ let token_concurrent = TokenIdentifiers {
+ index: aggregator_v2::create_snapshot(index),
+ name: aggregator_v2::create_derived_string(name),
+ };
+ move_to(&object_signer, token_concurrent);
+
+ let token = Token {
+ collection,
+ index: deprecated_index,
+ description,
+ name: deprecated_name,
+ uri,
+ mutation_events: object::new_event_handle(&object_signer),
+ };
+ move_to(&object_signer, token);
+
+ let royalty = any_map::remove_if_present(resources_to_move.get_resources_mut());
+ if (option::is_some(&royalty)) {
+ royalty::init(&constructor_ref, option::extract(&mut royalty))
+ };
+ let property_map = any_map::remove_if_present(resources_to_move.get_resources_mut());
+ if (option::is_some(&property_map)) {
+ property_map::init(&constructor_ref, option::extract(&mut property_map))
+ };
+
+ (constructor_ref, resources_to_move)
+ }
}