diff --git a/aptos-move/framework/aptos-framework/doc/compressed_object.md b/aptos-move/framework/aptos-framework/doc/compressed_object.md new file mode 100644 index 00000000000000..0a9e8ed2fdbf3c --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/compressed_object.md @@ -0,0 +1,259 @@ + + + +# Module `0x1::compressed_object` + + + +- [Struct `CompressedObjectCore`](#0x1_compressed_object_CompressedObjectCore) +- [Struct `CompressedObject`](#0x1_compressed_object_CompressedObject) +- [Struct `DecompressingObject`](#0x1_compressed_object_DecompressingObject) +- [Constants](#@Constants_0) +- [Function `initialize_compressed_object`](#0x1_compressed_object_initialize_compressed_object) +- [Function `compress_existing_object`](#0x1_compressed_object_compress_existing_object) +- [Function `decompress_object`](#0x1_compressed_object_decompress_object) +- [Function `finish_decompressing`](#0x1_compressed_object_finish_decompressing) + + +
use 0x1::compressed_state;
+use 0x1::copyable_any_map;
+use 0x1::object;
+
+ + + + + +## Struct `CompressedObjectCore` + + + +
struct CompressedObjectCore has copy, drop, store
+
+ + + +
+Fields + + +
+
+owner: address +
+
+ +
+
+allow_ungated_transfer: bool +
+
+ +
+
+ + +
+ + + +## Struct `CompressedObject` + + + +
struct CompressedObject has drop, store
+
+ + + +
+Fields + + +
+
+object: object::CreateAtAddressRef +
+
+ Object address used when object is uncompressed +
+
+resources: copyable_any_map::AnyMap +
+
+ +
+
+ + +
+ + + +## Struct `DecompressingObject` + + + +
struct DecompressingObject
+
+ + + +
+Fields + + +
+
+resources: copyable_any_map::AnyMap +
+
+ +
+
+ + +
+ + + +## Constants + + + + + + +
const EDECOMPRESSION_NOT_FINISHED: u64 = 1;
+
+ + + + + +## Function `initialize_compressed_object` + + + +
entry fun initialize_compressed_object(framework_signer: &signer)
+
+ + + +
+Implementation + + +
entry fun initialize_compressed_object(framework_signer: &signer) {
+    compressed_state::enable_compression_for_custom_core<CompressedObjectCore>(framework_signer);
+}
+
+ + + +
+ + + +## Function `compress_existing_object` + + + +
public fun compress_existing_object(ref: object::DeleteAndRecreateRef, resources: copyable_any_map::AnyMap)
+
+ + + +
+Implementation + + +
public fun compress_existing_object(ref: DeleteAndRecreateRef, resources: AnyMap) {
+    let (object, owner, allow_ungated_transfer) = object::delete_and_can_recreate(ref);
+
+    let compressed_core = CompressedObjectCore {
+        owner,
+        allow_ungated_transfer,
+    };
+
+    let compressed_object = CompressedObject {
+        object,
+        resources,
+    };
+
+    compressed_state::compress(compressed_core, compressed_object);
+}
+
+ + + +
+ + + +## Function `decompress_object` + + + +
public fun decompress_object(compressed_id: u64, serialized: vector<u8>): (object::ConstructorRef, compressed_object::DecompressingObject)
+
+ + + +
+Implementation + + +
public fun decompress_object(compressed_id: u64, serialized: vector<u8>): (ConstructorRef, DecompressingObject) {
+    let (
+        CompressedObjectCore {
+            owner,
+            allow_ungated_transfer: _,
+        },
+        CompressedObject {
+            object,
+            resources,
+        }
+    ) = compressed_state::decompress_and_remove<CompressedObjectCore, CompressedObject>(compressed_id, serialized);
+
+    let constructor_ref = object::create_object_at_address_from_ref(owner, object);
+
+    (constructor_ref, DecompressingObject {
+        resources: resources,
+    })
+}
+
+ + + +
+ + + +## Function `finish_decompressing` + + + +
public fun finish_decompressing(object: compressed_object::DecompressingObject)
+
+ + + +
+Implementation + + +
public fun finish_decompressing(object: DecompressingObject) {
+    assert!(copyable_any_map::length(&object.resources) == 0, EDECOMPRESSION_NOT_FINISHED);
+    let DecompressingObject {
+        resources: _
+    } = object;
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/external_object.md b/aptos-move/framework/aptos-framework/doc/external_object.md new file mode 100644 index 00000000000000..c76a25520ac253 --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/external_object.md @@ -0,0 +1,377 @@ + + + +# Module `0x1::external_object` + +Allowing objects (ObjectCore, together with all resources attached to it) to be stored +in external storage, with keeping only the hash onchain. That allows us to retrieve it later._ + +Pair of functions move_existing_object_to_external_storage and move_external_object_to_state +allow any deletable object to be moved to external storage, and back to onchain state. + + +- [Struct `ExternalObjectWitness`](#0x1_external_object_ExternalObjectWitness) +- [Struct `ExternalObject`](#0x1_external_object_ExternalObject) +- [Struct `MovingToStateObject`](#0x1_external_object_MovingToStateObject) +- [Struct `ObjectMovedToExternalStorage`](#0x1_external_object_ObjectMovedToExternalStorage) +- [Constants](#@Constants_0) +- [Function `initialize_external_object`](#0x1_external_object_initialize_external_object) +- [Function `move_existing_object_to_external_storage`](#0x1_external_object_move_existing_object_to_external_storage) +- [Function `move_external_object_to_state`](#0x1_external_object_move_external_object_to_state) +- [Function `get_resources_mut`](#0x1_external_object_get_resources_mut) +- [Function `destroy_empty`](#0x1_external_object_destroy_empty) +- [Function `transfer`](#0x1_external_object_transfer) + + +
use 0x1::any;
+use 0x1::any_map;
+use 0x1::event;
+use 0x1::external_unique_state;
+use 0x1::object;
+
+ + + + + +## Struct `ExternalObjectWitness` + + + +
struct ExternalObjectWitness has drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ + +
+ + + +## Struct `ExternalObject` + + + +
struct ExternalObject has drop, store
+
+ + + +
+Fields + + +
+
+object_ref: object::CreateAtAddressRef +
+
+ Object address used when object is uncompressed +
+
+resources: any_map::AnyMap +
+
+ +
+
+mut_permission: any::Any +
+
+ +
+
+ + +
+ + + +## Struct `MovingToStateObject` + +Undropable value, which makes sure whole object was consumed, +when moving object from external storage to onchain state. + + +
struct MovingToStateObject
+
+ + + +
+Fields + + +
+
+resources: any_map::AnyMap +
+
+ +
+
+ + +
+ + + +## Struct `ObjectMovedToExternalStorage` + + + +
#[event]
+struct ObjectMovedToExternalStorage has drop, store
+
+ + + +
+Fields + + +
+
+object_addr: address +
+
+ +
+
+hash: u256 +
+
+ +
+
+ + +
+ + + +## Constants + + + + + + +
const EMOVING_TO_STATE_NOT_FINISHED: u64 = 1;
+
+ + + + + + + +
const EPERMISSION_DOESNT_MATCH: u64 = 2;
+
+ + + + + +## Function `initialize_external_object` + + + +
entry fun initialize_external_object(framework_signer: &signer)
+
+ + + +
+Implementation + + +
entry fun initialize_external_object(framework_signer: &signer) {
+    external_unique_state::enable_external_storage_for_type<ExternalObject, ExternalObjectWitness>(framework_signer, ExternalObjectWitness {});
+}
+
+ + + +
+ + + +## Function `move_existing_object_to_external_storage` + + + +
public fun move_existing_object_to_external_storage<P: drop, store>(ref: object::DeleteAndRecreateRef, resources: any_map::AnyMap, mut_permission: P)
+
+ + + +
+Implementation + + +
public fun move_existing_object_to_external_storage<P: drop + store>(ref: DeleteAndRecreateRef, resources: AnyMap, mut_permission: P) {
+    let object_addr = ref.address_from_delete_and_recreate_ref();
+    let object_ref = object::delete_and_can_recreate(ref);
+
+    let compressed_object = ExternalObject {
+        object_ref,
+        resources,
+        mut_permission: any::pack(mut_permission),
+    };
+
+    let hash = external_unique_state::move_to_external_storage(compressed_object, &ExternalObjectWitness {});
+
+    event::emit(ObjectMovedToExternalStorage {
+        object_addr,
+        hash,
+    });
+}
+
+ + + +
+ + + +## Function `move_external_object_to_state` + + + +
public fun move_external_object_to_state<P: drop, store>(external_bytes: vector<u8>, mut_permission: P): (object::ConstructorRef, external_object::MovingToStateObject)
+
+ + + +
+Implementation + + +
public fun move_external_object_to_state<P: drop + store>(external_bytes: vector<u8>, mut_permission: P): (ConstructorRef, MovingToStateObject) {
+    let ExternalObject {
+        object_ref,
+        resources,
+        mut_permission: external_mut_perm,
+    } = external_unique_state::move_from_external_storage<ExternalObject, ExternalObjectWitness>(external_bytes, &ExternalObjectWitness {});
+    assert!(mut_permission == external_mut_perm.unpack(), EPERMISSION_DOESNT_MATCH);
+
+    let constructor_ref = object::create_object_at_address_from_ref(object_ref);
+
+    (constructor_ref, MovingToStateObject {
+        resources: resources,
+    })
+}
+
+ + + +
+ + + +## Function `get_resources_mut` + + + +
public fun get_resources_mut(self: &mut external_object::MovingToStateObject): &mut any_map::AnyMap
+
+ + + +
+Implementation + + +
public fun get_resources_mut(self: &mut MovingToStateObject): &mut AnyMap {
+    &mut self.resources
+}
+
+ + + +
+ + + +## Function `destroy_empty` + + + +
public fun destroy_empty(self: external_object::MovingToStateObject)
+
+ + + +
+Implementation + + +
public fun destroy_empty(self: MovingToStateObject) {
+    assert!(any_map::length(&self.resources) == 0, EMOVING_TO_STATE_NOT_FINISHED);
+    let MovingToStateObject {
+        resources: _
+    } = self;
+}
+
+ + + +
+ + + +## Function `transfer` + + + +
public entry fun transfer(owner: &signer, external_bytes: vector<u8>, to: address)
+
+ + + +
+Implementation + + +
public entry fun transfer(owner: &signer, external_bytes: vector<u8>, to: address) {
+    let ExternalObject {
+        object_ref,
+        resources,
+        mut_permission,
+    } = external_unique_state::move_from_external_storage<ExternalObject, ExternalObjectWitness>(external_bytes, &ExternalObjectWitness {});
+
+    let constructor_ref = object::create_object_at_address_from_ref(object_ref);
+
+    object::transfer<ObjectCore>(owner, constructor_ref.object_from_constructor_ref(), to);
+
+    let object_ref = object::delete_and_can_recreate(constructor_ref.generate_delete_and_recreate_ref());
+    let compressed_object = ExternalObject {
+        object_ref,
+        resources,
+        mut_permission,
+    };
+
+    external_unique_state::move_to_external_storage(compressed_object, &ExternalObjectWitness {});
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index 314baa3612ba96..c1339b063773b8 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -32,6 +32,8 @@ This is the reference documentation of the Aptos framework. - [`0x1::dkg`](dkg.md#0x1_dkg) - [`0x1::event`](event.md#0x1_event) - [`0x1::execution_config`](execution_config.md#0x1_execution_config) +- [`0x1::external_object`](external_object.md#0x1_external_object) +- [`0x1::external_unique_state`](external_unique_state.md#0x1_external_unique_state) - [`0x1::function_info`](function_info.md#0x1_function_info) - [`0x1::fungible_asset`](fungible_asset.md#0x1_fungible_asset) - [`0x1::gas_schedule`](gas_schedule.md#0x1_gas_schedule) @@ -68,6 +70,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::transaction_validation`](transaction_validation.md#0x1_transaction_validation) - [`0x1::util`](util.md#0x1_util) - [`0x1::validator_consensus_info`](validator_consensus_info.md#0x1_validator_consensus_info) +- [`0x1::verified_external_value`](verified_external_value.md#0x1_verified_external_value) - [`0x1::version`](version.md#0x1_version) - [`0x1::vesting`](vesting.md#0x1_vesting) - [`0x1::voting`](voting.md#0x1_voting) diff --git a/aptos-move/framework/aptos-framework/sources/external/external_object.move b/aptos-move/framework/aptos-framework/sources/external/external_object.move new file mode 100644 index 00000000000000..1e3d1a79c96fc5 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/external/external_object.move @@ -0,0 +1,109 @@ +/// Allowing objects (ObjectCore, together with all resources attached to it) to be stored +/// in external storage, with keeping only the hash onchain. That allows us to retrieve it later._ +/// +/// Pair of functions `move_existing_object_to_external_storage` and `move_external_object_to_state` +/// allow any deletable object to be moved to external storage, and back to onchain state. +module aptos_framework::external_object { + use aptos_std::any::{Self, Any}; + use aptos_std::any_map::{Self, AnyMap}; + use aptos_framework::external_unique_state; + use aptos_framework::event; + use aptos_framework::object::{Self, ConstructorRef, CreateAtAddressRef, DeleteAndRecreateRef, ObjectCore}; + + const EMOVING_TO_STATE_NOT_FINISHED: u64 = 1; + const EPERMISSION_DOESNT_MATCH: u64 = 2; + + struct ExternalObjectWitness has drop, store { } + + struct ExternalObject has drop, store { + /// Object address used when object is uncompressed + object_ref: CreateAtAddressRef, + + resources: AnyMap, + + mut_permission: Any, + } + + /// Undropable value, which makes sure whole object was consumed, + /// when moving object from external storage to onchain state. + struct MovingToStateObject { + resources: AnyMap, + } + + #[event] + struct ObjectMovedToExternalStorage has drop, store { + object_addr: address, + hash: u256, + } + + entry fun initialize_external_object(framework_signer: &signer) { + external_unique_state::enable_external_storage_for_type(framework_signer, ExternalObjectWitness {}); + } + + public fun move_existing_object_to_external_storage(ref: DeleteAndRecreateRef, resources: AnyMap, mut_permission: P) { + let object_addr = ref.address_from_delete_and_recreate_ref(); + let object_ref = object::delete_and_can_recreate(ref); + + let compressed_object = ExternalObject { + object_ref, + resources, + mut_permission: any::pack(mut_permission), + }; + + let hash = external_unique_state::move_to_external_storage(compressed_object, &ExternalObjectWitness {}); + + event::emit(ObjectMovedToExternalStorage { + object_addr, + hash, + }); + } + + public fun move_external_object_to_state(external_bytes: vector, mut_permission: P): (ConstructorRef, MovingToStateObject) { + let ExternalObject { + object_ref, + resources, + mut_permission: external_mut_perm, + } = external_unique_state::move_from_external_storage(external_bytes, &ExternalObjectWitness {}); + assert!(mut_permission == external_mut_perm.unpack(), EPERMISSION_DOESNT_MATCH); + + let constructor_ref = object::create_object_at_address_from_ref(object_ref); + + (constructor_ref, MovingToStateObject { + resources: resources, + }) + } + + public fun get_resources_mut(self: &mut MovingToStateObject): &mut AnyMap { + &mut self.resources + } + + public fun destroy_empty(self: MovingToStateObject) { + assert!(any_map::length(&self.resources) == 0, EMOVING_TO_STATE_NOT_FINISHED); + let MovingToStateObject { + resources: _ + } = self; + } + + // Allow for object API to be called without any permissions, same as non-external objects. + + public entry fun transfer(owner: &signer, external_bytes: vector, to: address) { + let ExternalObject { + object_ref, + resources, + mut_permission, + } = external_unique_state::move_from_external_storage(external_bytes, &ExternalObjectWitness {}); + + let constructor_ref = object::create_object_at_address_from_ref(object_ref); + + object::transfer(owner, constructor_ref.object_from_constructor_ref(), to); + + let object_ref = object::delete_and_can_recreate(constructor_ref.generate_delete_and_recreate_ref()); + let compressed_object = ExternalObject { + object_ref, + resources, + mut_permission, + }; + + external_unique_state::move_to_external_storage(compressed_object, &ExternalObjectWitness {}); + } +} diff --git a/aptos-move/framework/aptos-framework/sources/external/external_unique_state.move b/aptos-move/framework/aptos-framework/sources/external/external_unique_state.move index ad5314d3a9bbdc..a16ec5924e5086 100644 --- a/aptos-move/framework/aptos-framework/sources/external/external_unique_state.move +++ b/aptos-move/framework/aptos-framework/sources/external/external_unique_state.move @@ -13,6 +13,8 @@ module aptos_framework::external_unique_state { use std::signer; use aptos_framework::verified_external_value::{Self, ExternalValuesSet}; + friend aptos_framework::external_object; + const EHASH_DOESNT_MATCH: u64 = 1; /// compressed_id already exists const ECOMPRESSED_ID_ALREADY_PRESENT: u64 = 2; diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index c7fe5a59010e1c..6704acb00507f8 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -467,6 +467,13 @@ pub enum EntryFunctionCall { amount: u64, }, + ExternalObjectInitializeExternalObject {}, + + ExternalObjectTransfer { + external_bytes: Vec, + to: AccountAddress, + }, + /// This can be called to install or update a set of JWKs for a federated OIDC provider. This function should /// be invoked to intially install a set of JWKs or to update a set of JWKs when a keypair is rotated. /// @@ -1394,6 +1401,12 @@ impl EntryFunctionCall { pool_address, amount, } => delegation_pool_withdraw(pool_address, amount), + ExternalObjectInitializeExternalObject {} => { + external_object_initialize_external_object() + }, + ExternalObjectTransfer { external_bytes, to } => { + external_object_transfer(external_bytes, to) + }, JwksUpdateFederatedJwkSet { iss, kid_vec, @@ -3021,6 +3034,39 @@ pub fn delegation_pool_withdraw(pool_address: AccountAddress, amount: u64) -> Tr )) } +pub fn external_object_initialize_external_object() -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("external_object").to_owned(), + ), + ident_str!("initialize_external_object").to_owned(), + vec![], + vec![], + )) +} + +pub fn external_object_transfer(external_bytes: Vec, to: AccountAddress) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("external_object").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![], + vec![ + bcs::to_bytes(&external_bytes).unwrap(), + bcs::to_bytes(&to).unwrap(), + ], + )) +} + /// This can be called to install or update a set of JWKs for a federated OIDC provider. This function should /// be invoked to intially install a set of JWKs or to update a set of JWKs when a keypair is rotated. /// @@ -5707,6 +5753,27 @@ mod decoder { } } + pub fn external_object_initialize_external_object( + payload: &TransactionPayload, + ) -> Option { + if let TransactionPayload::EntryFunction(_script) = payload { + Some(EntryFunctionCall::ExternalObjectInitializeExternalObject {}) + } else { + None + } + } + + pub fn external_object_transfer(payload: &TransactionPayload) -> Option { + if let TransactionPayload::EntryFunction(script) = payload { + Some(EntryFunctionCall::ExternalObjectTransfer { + external_bytes: bcs::from_bytes(script.args().get(0)?).ok()?, + to: bcs::from_bytes(script.args().get(1)?).ok()?, + }) + } else { + None + } + } + pub fn jwks_update_federated_jwk_set( payload: &TransactionPayload, ) -> Option { @@ -7081,6 +7148,14 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy