diff --git a/contracts/feature-tests/basic-features/scenarios/echo_varags_vec_with_counted.scen.json b/contracts/feature-tests/basic-features/scenarios/echo_varags_vec_with_counted.scen.json new file mode 100644 index 0000000000..6373e3060b --- /dev/null +++ b/contracts/feature-tests/basic-features/scenarios/echo_varags_vec_with_counted.scen.json @@ -0,0 +1,52 @@ +{ + "gasSchedule": "v3", + "steps": [ + { + "step": "setState", + "accounts": { + "sc:basic-features": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../output/basic-features.mxsc.json" + }, + "address:an_account": { + "nonce": "0", + "balance": "0" + } + } + }, + { + "step": "scCall", + "id": "1", + "tx": { + "from": "address:an_account", + "to": "sc:basic-features", + "function": "echo_varags_vec_with_counted", + "arguments": [ + "str:alice", + "2", + "101", + "102", + "str:bob", + "0" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "str:alice", + "2", + "101", + "102", + "str:bob", + "0" + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + } + ] +} diff --git a/contracts/feature-tests/basic-features/scenarios/echo_varags_vec_with_counted_pairs.scen.json b/contracts/feature-tests/basic-features/scenarios/echo_varags_vec_with_counted_pairs.scen.json new file mode 100644 index 0000000000..d391d0e264 --- /dev/null +++ b/contracts/feature-tests/basic-features/scenarios/echo_varags_vec_with_counted_pairs.scen.json @@ -0,0 +1,68 @@ +{ + "gasSchedule": "v3", + "steps": [ + { + "step": "setState", + "accounts": { + "sc:basic-features": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../output/basic-features.mxsc.json" + }, + "address:an_account": { + "nonce": "0", + "balance": "0" + } + } + }, + { + "step": "scCall", + "id": "1", + "tx": { + "from": "address:an_account", + "to": "sc:basic-features", + "function": "echo_varags_vec_with_counted_pairs", + "arguments": [ + "str:alice", + "1", + "100", + "address:a100", + "str:bob", + "0", + "str:charlie", + "3", + "300", + "address:a300", + "301", + "address:a301", + "302", + "address:a302" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "str:alice", + "1", + "100", + "address:a100", + "str:bob", + "0", + "str:charlie", + "3", + "300", + "address:a300", + "301", + "address:a301", + "302", + "address:a302" + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + } + ] +} diff --git a/contracts/feature-tests/basic-features/src/echo_managed.rs b/contracts/feature-tests/basic-features/src/echo_managed.rs index 17084a26c8..8073cab4f1 100644 --- a/contracts/feature-tests/basic-features/src/echo_managed.rs +++ b/contracts/feature-tests/basic-features/src/echo_managed.rs @@ -100,4 +100,27 @@ pub trait EchoManagedTypes { } result } + + #[endpoint] + fn echo_varags_vec_with_counted( + &self, + m: MultiValueEncoded>>, + ) -> MultiValueEncoded>> { + m + } + + #[endpoint] + fn echo_varags_vec_with_counted_pairs( + &self, + m: MultiValueEncoded< + MultiValue2< + ManagedBuffer, + MultiValueEncodedCounted>, + >, + >, + ) -> MultiValueEncoded< + MultiValue2>>, + > { + m + } } diff --git a/contracts/feature-tests/basic-features/tests/basic_features_scenario_go_test.rs b/contracts/feature-tests/basic-features/tests/basic_features_scenario_go_test.rs index 58bee7c1da..02032a2126 100644 --- a/contracts/feature-tests/basic-features/tests/basic_features_scenario_go_test.rs +++ b/contracts/feature-tests/basic-features/tests/basic_features_scenario_go_test.rs @@ -86,14 +86,14 @@ fn crypto_verify_bls_go() { #[test] #[ignore = "requires EI 1.4 in mx-scenario-go"] -fn crypto_verify_bls_share_go() { - world().run("scenarios/crypto_verify_bls_share.scen.json"); +fn crypto_verify_bls_aggregated_signature_go() { + world().run("scenarios/crypto_verify_bls_aggregated_signature.scen.json"); } #[test] #[ignore = "requires EI 1.4 in mx-scenario-go"] -fn crypto_verify_bls_aggregated_go() { - world().run("scenarios/crypto_verify_bls_aggregated_signature.scen.json"); +fn crypto_verify_bls_share_go() { + world().run("scenarios/crypto_verify_bls_share.scen.json"); } #[test] @@ -192,6 +192,16 @@ fn echo_usize_go() { world().run("scenarios/echo_usize.scen.json"); } +#[test] +fn echo_varags_vec_with_counted_go() { + world().run("scenarios/echo_varags_vec_with_counted.scen.json"); +} + +#[test] +fn echo_varags_vec_with_counted_pairs_go() { + world().run("scenarios/echo_varags_vec_with_counted_pairs.scen.json"); +} + #[test] fn echo_varargs_managed_eager_go() { world().run("scenarios/echo_varargs_managed_eager.scen.json"); diff --git a/contracts/feature-tests/basic-features/tests/basic_features_scenario_rs_test.rs b/contracts/feature-tests/basic-features/tests/basic_features_scenario_rs_test.rs index 79ed434501..a4efa924ab 100644 --- a/contracts/feature-tests/basic-features/tests/basic_features_scenario_rs_test.rs +++ b/contracts/feature-tests/basic-features/tests/basic_features_scenario_rs_test.rs @@ -105,14 +105,14 @@ fn crypto_verify_bls_rs() { #[test] #[ignore] -fn crypto_verify_bls_share_rs() { - world().run("scenarios/crypto_verify_bls_share.scen.json"); +fn crypto_verify_bls_aggregated_signature_rs() { + world().run("scenarios/crypto_verify_bls_aggregated_signature.scen.json"); } #[test] #[ignore] -fn crypto_verify_bls_aggregated_rs() { - world().run("scenarios/crypto_verify_bls_aggregated_signature.scen.json"); +fn crypto_verify_bls_share_rs() { + world().run("scenarios/crypto_verify_bls_share.scen.json"); } #[test] @@ -212,6 +212,16 @@ fn echo_usize_rs() { world().run("scenarios/echo_usize.scen.json"); } +#[test] +fn echo_varags_vec_with_counted_rs() { + world().run("scenarios/echo_varags_vec_with_counted.scen.json"); +} + +#[test] +fn echo_varags_vec_with_counted_pairs_rs() { + world().run("scenarios/echo_varags_vec_with_counted_pairs.scen.json"); +} + #[test] fn echo_varargs_managed_eager_rs() { world().run("scenarios/echo_varargs_managed_eager.scen.json"); diff --git a/framework/base/src/types/managed/multi_value/mod.rs b/framework/base/src/types/managed/multi_value/mod.rs index 2a4af1f18c..bfd2981c16 100644 --- a/framework/base/src/types/managed/multi_value/mod.rs +++ b/framework/base/src/types/managed/multi_value/mod.rs @@ -1,6 +1,7 @@ mod async_call_result_managed; mod esdt_token_payment_multi_value; mod multi_value_encoded; +mod multi_value_encoded_counted; mod multi_value_encoded_iter; mod multi_value_managed_vec; mod multi_value_managed_vec_counted; @@ -8,6 +9,7 @@ mod multi_value_managed_vec_counted; pub use async_call_result_managed::{ManagedAsyncCallError, ManagedAsyncCallResult}; pub use esdt_token_payment_multi_value::{EsdtTokenPaymentMultiArg, EsdtTokenPaymentMultiValue}; pub use multi_value_encoded::{ManagedMultiResultVec, ManagedVarArgs, MultiValueEncoded}; +pub use multi_value_encoded_counted::MultiValueEncodedCounted; pub use multi_value_encoded_iter::MultiValueEncodedIterator; pub use multi_value_managed_vec::{ ManagedMultiResultVecEager, ManagedVarArgsEager, MultiValueManagedVec, diff --git a/framework/base/src/types/managed/multi_value/multi_value_encoded.rs b/framework/base/src/types/managed/multi_value/multi_value_encoded.rs index 04dbde9357..4dd29166e0 100644 --- a/framework/base/src/types/managed/multi_value/multi_value_encoded.rs +++ b/framework/base/src/types/managed/multi_value/multi_value_encoded.rs @@ -15,6 +15,8 @@ use crate::{ }; use core::{iter::FromIterator, marker::PhantomData}; +use super::MultiValueEncodedIterator; + /// A multi-value container, that keeps raw values as ManagedBuffer /// It allows encoding and decoding of multi-values. /// @@ -147,6 +149,18 @@ where } } +impl IntoIterator for MultiValueEncoded +where + M: ManagedTypeApi + ErrorApi, + T: TopDecodeMulti, +{ + type Item = T; + type IntoIter = MultiValueEncodedIterator; + fn into_iter(self) -> Self::IntoIter { + MultiValueEncodedIterator::new(self.raw_buffers) + } +} + impl MultiValueEncoded where M: ManagedTypeApi + ErrorApi, diff --git a/framework/base/src/types/managed/multi_value/multi_value_encoded_counted.rs b/framework/base/src/types/managed/multi_value/multi_value_encoded_counted.rs new file mode 100644 index 0000000000..ce53008377 --- /dev/null +++ b/framework/base/src/types/managed/multi_value/multi_value_encoded_counted.rs @@ -0,0 +1,267 @@ +use unwrap_infallible::UnwrapInfallible; + +use crate::codec::multi_types::MultiValueVec; +use crate::{ + abi::{TypeAbi, TypeAbiFrom, TypeDescriptionContainer, TypeName}, + api::{ErrorApi, ManagedTypeApi}, + codec::{ + DecodeErrorHandler, EncodeErrorHandler, TopDecode, TopDecodeMulti, TopDecodeMultiInput, + TopDecodeMultiLength, TopEncodeMulti, TopEncodeMultiOutput, + }, + contract_base::{ExitCodecErrorHandler, ManagedSerializer}, + err_msg, + types::{ManagedBuffer, ManagedVec, ManagedVecItem}, +}; +use core::{iter::FromIterator, marker::PhantomData}; + +use super::MultiValueEncodedIterator; + +/// A multi-value container, that keeps raw values as ManagedBuffer, and which encodes and decodes its length explicitly. +/// +/// It allows encoding and decoding of multi-values. Its multi-encoding always starts with the number of items. +/// +/// Since items are kept raw, the item type does not need to implement `ManagedVecItem`. +/// +/// Behavior: +/// - It is lazy when decoding, in that it keeps them raw and will not decode the values until they are requested. +/// - It is eager when encoding, items are serialized before being added to this structure. +/// +/// ## Item length +/// +/// Its item type must implement `TopDecodeMultiLength`, which is a length marker for multi-values. +/// +/// Some examples for `TopDecodeMultiLength`: +/// - MultiValue3 has a "multi-length" of 3 +/// - a simple type, like i32, has a "multi-length" of 1 +/// - MultiValueEncoded has no known "multi-length", and therefore cannot be used inside `MultiValueEncodedCounted`. +/// +/// `MultiValueEncodedCounted` requires this "multi-length" to determine the number of buffers needed to store the encoded values. +/// +/// More specifically, the number of buffers (raw length) is equal to the item count multiplied by the item "multi-length". +#[derive(Clone, Default, Debug, PartialEq)] +pub struct MultiValueEncodedCounted +where + M: ManagedTypeApi, + T: TopDecodeMultiLength, +{ + raw_buffers: ManagedVec>, + _phantom: PhantomData, +} + +impl MultiValueEncodedCounted +where + M: ManagedTypeApi, + T: TopDecodeMultiLength, +{ + #[inline] + fn from_raw_vec(raw_buffers: ManagedVec>) -> Self { + MultiValueEncodedCounted { + raw_buffers, + _phantom: PhantomData, + } + } + + #[inline] + pub fn new() -> Self { + MultiValueEncodedCounted::from_raw_vec(ManagedVec::new()) + } +} + +impl MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopEncodeMulti + TopDecodeMultiLength, +{ + pub fn push(&mut self, item: T) { + item.multi_encode_or_handle_err( + &mut self.raw_buffers, + ExitCodecErrorHandler::::from(err_msg::SERIALIZER_ENCODE_ERROR), + ) + .unwrap_infallible() + } +} + +impl IntoIterator for MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopDecodeMulti + TopDecodeMultiLength, +{ + type Item = T; + type IntoIter = MultiValueEncodedIterator; + fn into_iter(self) -> Self::IntoIter { + MultiValueEncodedIterator::new(self.raw_buffers) + } +} + +impl MultiValueEncodedCounted> +where + M: ManagedTypeApi, +{ + pub fn into_vec_of_buffers(self) -> ManagedVec> { + self.raw_buffers + } +} + +impl MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopDecodeMultiLength, +{ + /// Length of the underlying data. + /// + /// Note: + /// In general, it is **not** the number of items that can be decoded. + /// It is the same as `len()` only for single encode items. + #[inline] + pub fn raw_len(&self) -> usize { + self.raw_buffers.len() + } + + pub fn is_empty(&self) -> bool { + self.raw_buffers.is_empty() + } + + /// Number of items. Only available for multi-encode items. + #[inline] + pub fn len(&self) -> usize { + self.raw_len() / T::LEN + } +} + +impl MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: ManagedVecItem + TopDecode + TopDecodeMultiLength, +{ + pub fn to_vec(&self) -> ManagedVec { + let mut result = ManagedVec::new(); + let serializer = ManagedSerializer::::new(); + for item in self.raw_buffers.into_iter() { + result.push(serializer.top_decode_from_managed_buffer(&item)); + } + result + } +} + +impl TopEncodeMulti for &MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopEncodeMulti + TopDecodeMultiLength, +{ + fn multi_encode_or_handle_err(&self, output: &mut O, h: H) -> Result<(), H::HandledErr> + where + O: TopEncodeMultiOutput, + H: EncodeErrorHandler, + { + let raw_count = self.raw_buffers.len(); + let count = raw_count / T::LEN; + count.multi_encode_or_handle_err(output, h)?; + for elem in self.raw_buffers.into_iter() { + elem.multi_encode_or_handle_err(output, h)?; + } + Ok(()) + } +} + +impl TopEncodeMulti for MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopEncodeMulti + TopDecodeMultiLength, +{ + fn multi_encode_or_handle_err(&self, output: &mut O, h: H) -> Result<(), H::HandledErr> + where + O: TopEncodeMultiOutput, + H: EncodeErrorHandler, + { + (&self).multi_encode_or_handle_err(output, h) + } +} + +impl TopDecodeMulti for MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopDecodeMulti + TopDecodeMultiLength, +{ + fn multi_decode_or_handle_err(input: &mut I, h: H) -> Result + where + I: TopDecodeMultiInput, + H: DecodeErrorHandler, + { + let count: usize = input.next_value(h)?; + let raw_count = count * T::LEN; + let mut raw_buffers = ManagedVec::new(); + for _ in 0..raw_count { + raw_buffers.push(input.next_value(h)?); + } + Ok(Self::from_raw_vec(raw_buffers)) + } +} + +impl TypeAbiFrom for MultiValueEncodedCounted +where + M: ManagedTypeApi, + T: TypeAbi + TopDecodeMultiLength, +{ +} + +impl TypeAbiFrom<&Self> for MultiValueEncodedCounted +where + M: ManagedTypeApi, + T: TypeAbi + TopDecodeMultiLength, +{ +} + +impl TypeAbi for MultiValueEncodedCounted +where + M: ManagedTypeApi, + T: TypeAbi + TopDecodeMultiLength, +{ + type Unmanaged = MultiValueVec; + + fn type_name() -> TypeName { + let mut repr = TypeName::from("counted-variadic<"); + repr.push_str(T::type_name().as_str()); + repr.push('>'); + repr + } + + fn type_name_rust() -> TypeName { + crate::abi::type_name_multi_value_encoded::() + } + + fn provide_type_descriptions(accumulator: &mut TDC) { + T::provide_type_descriptions(accumulator); + } + + fn is_variadic() -> bool { + true + } +} + +impl TypeAbiFrom> for MultiValueEncodedCounted +where + M: ManagedTypeApi + ErrorApi, + T: TopEncodeMulti, + U: TypeAbiFrom + TopDecodeMultiLength, +{ +} + +impl TypeAbiFrom> for MultiValueVec +where + M: ManagedTypeApi + ErrorApi, + T: TopEncodeMulti + TopDecodeMultiLength, + U: TypeAbiFrom, +{ +} + +impl FromIterator for MultiValueEncodedCounted +where + M: ManagedTypeApi, + V: TopEncodeMulti + TopDecodeMultiLength, +{ + fn from_iter>(iter: T) -> Self { + let mut result: MultiValueEncodedCounted = MultiValueEncodedCounted::new(); + iter.into_iter().for_each(|f| result.push(f)); + result + } +} diff --git a/framework/base/src/types/managed/multi_value/multi_value_encoded_iter.rs b/framework/base/src/types/managed/multi_value/multi_value_encoded_iter.rs index 8a88ffeef7..4b8db58ffb 100644 --- a/framework/base/src/types/managed/multi_value/multi_value_encoded_iter.rs +++ b/framework/base/src/types/managed/multi_value/multi_value_encoded_iter.rs @@ -4,25 +4,15 @@ use unwrap_infallible::UnwrapInfallible; use crate::codec::{TopDecodeMulti, TopDecodeMultiInput}; +use crate::types::{ManagedBuffer, ManagedVec}; use crate::{ api::{ErrorApi, ManagedTypeApi}, io::{ArgErrorHandler, ArgId, ManagedResultArgLoader}, }; -use super::MultiValueEncoded; - -impl IntoIterator for MultiValueEncoded -where - M: ManagedTypeApi + ErrorApi, - T: TopDecodeMulti, -{ - type Item = T; - type IntoIter = MultiValueEncodedIterator; - fn into_iter(self) -> Self::IntoIter { - MultiValueEncodedIterator::new(self) - } -} - +/// Iterator for `MultiValueEncoded` and `MultiValueEncodedCounted`. +/// +/// Decodes items while it is iterating. pub struct MultiValueEncodedIterator where M: ManagedTypeApi + ErrorApi, @@ -37,9 +27,9 @@ where M: ManagedTypeApi + ErrorApi, T: TopDecodeMulti, { - pub(crate) fn new(obj: MultiValueEncoded) -> Self { + pub(crate) fn new(raw_buffers: ManagedVec>) -> Self { MultiValueEncodedIterator { - data_loader: ManagedResultArgLoader::new(obj.raw_buffers), + data_loader: ManagedResultArgLoader::new(raw_buffers), _phantom: PhantomData, } } diff --git a/framework/derive/src/preprocessing/substitution_list.rs b/framework/derive/src/preprocessing/substitution_list.rs index ec1b330239..a04da2a4c1 100644 --- a/framework/derive/src/preprocessing/substitution_list.rs +++ b/framework/derive/src/preprocessing/substitution_list.rs @@ -61,6 +61,7 @@ fn add_managed_types(substitutions: &mut SubstitutionsMap) { add_managed_type_with_generics(substitutions, "e!(ManagedVarArgs)); add_managed_type_with_generics(substitutions, "e!(ManagedMultiResultVec)); add_managed_type_with_generics(substitutions, "e!(MultiValueManagedVecCounted)); + add_managed_type_with_generics(substitutions, "e!(MultiValueEncodedCounted)); add_managed_type_with_generics(substitutions, "e!(ManagedCountedVarArgs)); add_managed_type_with_generics(substitutions, "e!(ManagedCountedMultiResultVec)); add_managed_type_with_generics(substitutions, "e!(MultiValueManagedVec));