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