From a420b4cb06ace053d5c7b2dae50636513cda2d7b Mon Sep 17 00:00:00 2001 From: Igor Date: Wed, 16 Oct 2024 14:05:21 -0700 Subject: [PATCH] [move-stdlib] Implement bcs::constant_serialized_size(): Option --- .../sources/transaction_context_test.move | 9 +++- aptos-move/framework/move-stdlib/doc/bcs.md | 29 +++++++++++- .../framework/move-stdlib/sources/bcs.move | 12 +++++ .../framework/move-stdlib/src/natives/bcs.rs | 41 ++++++++++++++-- .../move-stdlib/tests/bcs_tests.move | 40 ++++++++++++++++ .../move/move-vm/types/src/value_serde.rs | 47 ++++++++++++++++++- 6 files changed, 171 insertions(+), 7 deletions(-) diff --git a/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move b/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move index 0116d1035c9b0..de1ef952205a5 100644 --- a/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move +++ b/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move @@ -110,6 +110,10 @@ module admin::transaction_context_test { ), type_info::type_name()], 13 ); + + assert!(option::some(option::destroy_some(payload_opt)) == transaction_context::entry_function_payload(), 13); + } else { + assert!(option::none() == payload_opt, 14); } } @@ -128,7 +132,10 @@ module admin::transaction_context_test { store.function_name = transaction_context::function_name(entry); store.type_arg_names = transaction_context::type_arg_names(entry); store.args = transaction_context::args(entry); - } + }; + assert!(option::some(option::destroy_some(multisig_opt)) == transaction_context::multisig_payload(), 1); + } else { + assert!(option::none() == multisig_opt, 2); } } diff --git a/aptos-move/framework/move-stdlib/doc/bcs.md b/aptos-move/framework/move-stdlib/doc/bcs.md index 9bdcf7f45f7d3..ac0c96272144c 100644 --- a/aptos-move/framework/move-stdlib/doc/bcs.md +++ b/aptos-move/framework/move-stdlib/doc/bcs.md @@ -11,11 +11,13 @@ details on BCS. - [Function `to_bytes`](#0x1_bcs_to_bytes) - [Function `serialized_size`](#0x1_bcs_serialized_size) +- [Function `constant_serialized_size`](#0x1_bcs_constant_serialized_size) - [Specification](#@Specification_0) - [Function `serialized_size`](#@Specification_0_serialized_size) -
+
use 0x1::option;
+
@@ -65,6 +67,31 @@ Aborts with 0x1c5 error code if there is a failure when calculating + + + + +## Function `constant_serialized_size` + +If the type has known constant (always the same, independent of instance) serialized size +in BCS (Binary Canonical Serialization) format, returns it, otherwise returns None. +Aborts with 0x1c5 error code if there is a failure when calculating serialized size. + + +
public fun constant_serialized_size<MoveValue>(): option::Option<u64>
+
+ + + +
+Implementation + + +
native public fun constant_serialized_size<MoveValue>(): std::option::Option<u64>;
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/bcs.move b/aptos-move/framework/move-stdlib/sources/bcs.move index 478cbed627f24..6ca0ae74fe7dc 100644 --- a/aptos-move/framework/move-stdlib/sources/bcs.move +++ b/aptos-move/framework/move-stdlib/sources/bcs.move @@ -11,6 +11,18 @@ module std::bcs { /// Aborts with `0x1c5` error code if there is a failure when calculating serialized size. native public fun serialized_size(v: &MoveValue): u64; + /// If the type has known constant (always the same, independent of instance) serialized size + /// in BCS (Binary Canonical Serialization) format, returns it, otherwise returns None. + /// Aborts with `0x1c5` error code if there is a failure when calculating serialized size. + /// + /// Note: + /// For some types it might not be known they have constant size, and function might return None. + /// For example, signer appears to have constant size, but it's size might change. + /// If this function returned Some() for some type before - it is guaranteed to continue returning Some() + /// On the other hand, if function has returned None for some type, + /// it might change in the future to return Some() instead, if size becomes "known". + native public fun constant_serialized_size(): std::option::Option; + // ============================== // Module Specification spec module {} // switch to module documentation context diff --git a/aptos-move/framework/move-stdlib/src/natives/bcs.rs b/aptos-move/framework/move-stdlib/src/natives/bcs.rs index 283e890c1cdf7..57df2e33608c1 100644 --- a/aptos-move/framework/move-stdlib/src/natives/bcs.rs +++ b/aptos-move/framework/move-stdlib/src/natives/bcs.rs @@ -11,18 +11,22 @@ use aptos_native_interface::{ SafeNativeResult, }; use move_core_types::{ - gas_algebra::NumBytes, vm_status::sub_status::NFE_BCS_SERIALIZATION_FAILURE, + gas_algebra::NumBytes, vm_status::sub_status::NFE_BCS_SERIALIZATION_FAILURE }; use move_vm_runtime::native_functions::NativeFunction; use move_vm_types::{ loaded_data::runtime_types::Type, natives::function::PartialVMResult, - value_serde::serialized_size_allowing_delayed_values, - values::{values_impl::Reference, Value}, + value_serde::{constant_serialized_size, serialized_size_allowing_delayed_values}, + values::{values_impl::Reference, Struct, Value}, }; use smallvec::{smallvec, SmallVec}; use std::collections::VecDeque; +pub fn create_option_u64(value: Option) -> Value { + Value::struct_(Struct::pack(vec![Value::vector_u64(value)])) +} + /*************************************************************************************************** * native fun to_bytes * @@ -126,6 +130,36 @@ fn serialized_size_impl( serialized_size_allowing_delayed_values(&value, &ty_layout) } +fn native_constant_serialized_size( + context: &mut SafeNativeContext, + mut ty_args: Vec, + _args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(ty_args.len() == 1); + + context.charge(BCS_SERIALIZED_SIZE_BASE)?; + + let ty = ty_args.pop().unwrap(); + let ty_layout = context.type_to_type_layout(&ty)?; + + let result = match constant_serialized_size(&ty_layout) { + Ok(value) => create_option_u64(value.map(|v| v as u64)), + Err(_) => { + context.charge(BCS_SERIALIZED_SIZE_FAILURE)?; + + // Re-use the same abort code as bcs::to_bytes. + return Err(SafeNativeError::Abort { + abort_code: NFE_BCS_SERIALIZATION_FAILURE, + }); + }, + }; + + // Charge based on type layout size? + // context.charge(BCS_SERIALIZED_SIZE_PER_BYTE_SERIALIZED * NumBytes::new(serialized_size))?; + + Ok(smallvec![result]) +} + /*************************************************************************************************** * module **************************************************************************************************/ @@ -135,6 +169,7 @@ pub fn make_all( let funcs = [ ("to_bytes", native_to_bytes as RawSafeNative), ("serialized_size", native_serialized_size), + ("constant_serialized_size", native_constant_serialized_size), ]; builder.make_named_natives(funcs) diff --git a/aptos-move/framework/move-stdlib/tests/bcs_tests.move b/aptos-move/framework/move-stdlib/tests/bcs_tests.move index 367f504044694..bdcd72535ee8b 100644 --- a/aptos-move/framework/move-stdlib/tests/bcs_tests.move +++ b/aptos-move/framework/move-stdlib/tests/bcs_tests.move @@ -2,6 +2,8 @@ module std::bcs_tests { use std::bcs; use std::vector; + use std::option; + use std::signer; struct Box has copy, drop, store { x: T } struct Box3 has copy, drop, store { x: Box> } @@ -20,6 +22,8 @@ module std::bcs_tests { let expected_size = vector::length(&actual_bytes); let actual_size = bcs::serialized_size(&true); assert!(actual_size == expected_size, 1); + + assert!(option::some(actual_size) == bcs::constant_serialized_size()); } #[test] @@ -31,6 +35,8 @@ module std::bcs_tests { let expected_size = vector::length(&actual_bytes); let actual_size = bcs::serialized_size(&1u8); assert!(actual_size == expected_size, 1); + + assert!(option::some(actual_size) == bcs::constant_serialized_size()); } #[test] @@ -42,6 +48,8 @@ module std::bcs_tests { let expected_size = vector::length(&actual_bytes); let actual_size = bcs::serialized_size(&1); assert!(actual_size == expected_size, 1); + + assert!(option::some(actual_size) == bcs::constant_serialized_size()); } #[test] @@ -53,6 +61,8 @@ module std::bcs_tests { let expected_size = vector::length(&actual_bytes); let actual_size = bcs::serialized_size(&1u128); assert!(actual_size == expected_size, 1); + + assert!(option::some(actual_size) == bcs::constant_serialized_size()); } #[test] @@ -66,6 +76,23 @@ module std::bcs_tests { let expected_size = vector::length(&actual_bytes); let actual_size = bcs::serialized_size(&v); assert!(actual_size == expected_size, 1); + + assert!(option::none() == bcs::constant_serialized_size>()); + } + + #[test(creator = @0xcafe)] + fun bcs_address(creator: &signer) { + let v = signer::address_of(creator); + + let expected_bytes = x"000000000000000000000000000000000000000000000000000000000000CAFE"; + let actual_bytes = bcs::to_bytes(&v); + assert!(actual_bytes == expected_bytes, 0); + + let expected_size = vector::length(&actual_bytes); + let actual_size = bcs::serialized_size(&v); + assert!(actual_size == expected_size, 1); + + assert!(option::some(actual_size) == bcs::constant_serialized_size
()); } fun box3(x: T): Box3 { @@ -101,5 +128,18 @@ module std::bcs_tests { let actual_size = bcs::serialized_size(&box); assert!(actual_size == expected_size, 0); + + assert!(option::some(actual_size) == bcs::constant_serialized_size>()); + assert!(option::none() == bcs::constant_serialized_size>>()); + assert!(option::none() == bcs::constant_serialized_size>>()); + } + + enum Singleton { + V1(u64), + } + + fun encode_enum() { + assert!(option::none() == bcs::constant_serialized_size()); + assert!(option::none() == bcs::constant_serialized_size>()); } } diff --git a/third_party/move/move-vm/types/src/value_serde.rs b/third_party/move/move-vm/types/src/value_serde.rs index 5f17bfe9b3848..992da06e79270 100644 --- a/third_party/move/move-vm/types/src/value_serde.rs +++ b/third_party/move/move-vm/types/src/value_serde.rs @@ -9,8 +9,7 @@ use crate::{ }; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ - value::{IdentifierMappingKind, MoveTypeLayout}, - vm_status::StatusCode, + account_address::AccountAddress, u256, value::{IdentifierMappingKind, MoveStructLayout, MoveTypeLayout}, vm_status::StatusCode }; use serde::{ de::{DeserializeSeed, Error as DeError}, @@ -172,6 +171,50 @@ pub fn serialized_size_allowing_delayed_values( }) } +/// If given type has a constant serialized size (irrespective of the instance), it returns the serialized +/// size in bytes any value would have. +/// Otherwise it returns None. +pub fn constant_serialized_size(ty_layout: &MoveTypeLayout) -> PartialVMResult> { + let bcs_size_result = match ty_layout { + MoveTypeLayout::Bool => bcs::serialized_size(&false).map(|size| Some(size)), + MoveTypeLayout::U8 => bcs::serialized_size(&0u8).map(|size| Some(size)), + MoveTypeLayout::U64 => bcs::serialized_size(&0u64).map(|size| Some(size)), + MoveTypeLayout::U128 => bcs::serialized_size(&0u128).map(|size| Some(size)), + MoveTypeLayout::Address => bcs::serialized_size(&AccountAddress::ZERO).map(|size| Some(size)), + // vectors have no constant size + MoveTypeLayout::Vector(_) => Ok(None), + // enums have no constant size + MoveTypeLayout::Struct(MoveStructLayout::RuntimeVariants(_) | MoveStructLayout::WithVariants(_)) => Ok(None), + MoveTypeLayout::Struct(MoveStructLayout::Runtime(fields)) => { + Ok(fields + .iter() + .map(|field| constant_serialized_size(field)) + .collect::>, _>>() + .map(|o| o.map(|v| v.iter().sum()))?) + }, + MoveTypeLayout::Struct(MoveStructLayout::WithFields(fields)) + | MoveTypeLayout::Struct(MoveStructLayout::WithTypes{fields, ..}) => { + Ok(fields + .iter() + .map(|field| constant_serialized_size(&field.layout)) + .collect::>, _>>() + .map(|o| o.map(|v| v.iter().sum()))?) + }, + // signer's size is VM implementation detail, and can change at will. + MoveTypeLayout::Signer => Ok(None), + MoveTypeLayout::U16 => bcs::serialized_size(&0u16).map(|size| Some(size)), + MoveTypeLayout::U32 => bcs::serialized_size(&0u32).map(|size| Some(size)), + MoveTypeLayout::U256 => bcs::serialized_size(&u256::U256::zero()).map(|size| Some(size)), + MoveTypeLayout::Native(_, inner) => Ok(constant_serialized_size(inner)?), + }; + bcs_size_result.map_err(|e| { + PartialVMError::new(StatusCode::VALUE_SERIALIZATION_ERROR).with_message(format!( + "failed to compute serialized size of a value: {:?}", + e + )) + }) +} + /// Allow conversion between values and identifiers (delayed values). For example, /// this trait can be implemented to fetch a concrete Move value from the global /// state based on the identifier stored inside a delayed value.