From 70b429878b7a467a2724f3a5f9e998ede62c5f89 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 30 Mar 2024 02:47:50 +0000 Subject: [PATCH 01/18] feat: Add in place update support to versioned macro --- radix-clis/src/resim/mod.rs | 4 +- .../non_fungible_resource_manager.rs | 2 +- .../tests/kernel/test_environment.rs | 7 +- .../consensus_manager/consensus_manager.rs | 9 +- .../models/native_blueprint_state_macro.rs | 19 ++ .../v1/v1_0/multi_resource_pool_blueprint.rs | 4 +- .../v1/v1_0/one_resource_pool_blueprint.rs | 7 +- .../v1/v1_0/two_resource_pool_blueprint.rs | 10 +- radix-engine/src/vm/wasm/wasmi.rs | 1 + .../src/rocks_db_with_merkle_tree/mod.rs | 2 +- .../src/query/traverse.rs | 8 +- sbor-derive-common/src/categorize.rs | 20 ++- sbor-derive-common/src/decode.rs | 15 +- sbor-derive-common/src/describe.rs | 95 +++++----- sbor-derive-common/src/encode.rs | 22 ++- sbor-derive-common/src/utils.rs | 151 +++++++++------- sbor-derive/src/lib.rs | 23 +++ sbor-tests/tests/enum.rs | 43 ++++- sbor/src/lib.rs | 2 +- sbor/src/schema/schema.rs | 16 +- sbor/src/versioned.rs | 169 +++++++++++++++++- scrypto-test/src/environment/env.rs | 16 +- 22 files changed, 458 insertions(+), 187 deletions(-) diff --git a/radix-clis/src/resim/mod.rs b/radix-clis/src/resim/mod.rs index 6390769d78e..327a69ce9a1 100644 --- a/radix-clis/src/resim/mod.rs +++ b/radix-clis/src/resim/mod.rs @@ -419,9 +419,7 @@ pub fn get_event_schema( ) .unwrap()?; - let bp_interface = match bp_definition { - VersionedPackageBlueprintVersionDefinition::V1(blueprint) => blueprint.interface, - }; + let bp_interface = bp_definition.into_latest().interface; let event_def = bp_interface.events.get(event_name)?; match event_def { diff --git a/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs b/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs index c282a2cf618..ccc4a289710 100644 --- a/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs +++ b/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs @@ -432,7 +432,7 @@ mod test { mutable_fields, } = ds { - let VersionedSchema::V1(s) = schema; + let s = schema.into_latest(); assert_eq!(s.type_kinds.len(), 1); assert_eq!(s.type_metadata.len(), 1); assert_eq!(s.type_validations.len(), 1); diff --git a/radix-engine-tests/tests/kernel/test_environment.rs b/radix-engine-tests/tests/kernel/test_environment.rs index 1da6993acec..28a4edc6f54 100644 --- a/radix-engine-tests/tests/kernel/test_environment.rs +++ b/radix-engine-tests/tests/kernel/test_environment.rs @@ -224,12 +224,7 @@ fn references_read_from_state_are_visible_in_tests1() { .with_component_state::( radiswap_pool_component, |state, env| { - let VersionedTwoResourcePoolState::V1( - radix_engine::blueprints::pool::v1::substates::two_resource_pool::Substate { - vaults: [(_, vault1), (_, _)], - .. - }, - ) = state; + let [(_, vault1), (_, _)] = &mut state.to_latest_mut().vaults; vault1.amount(env) }, ) diff --git a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs index d55dc1c224a..a8f031dcc26 100644 --- a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs +++ b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs @@ -1247,12 +1247,9 @@ impl ConsensusManagerBlueprint { // then let's be even more accurate here. This sort is stable, so if two validators tie, then the resultant order will be // decided on sort key DESC. top_registered_validators.sort_by(|(_, validator_1), (_, validator_2)| { - match (&validator_1.content, &validator_2.content) { - ( - VersionedConsensusManagerRegisteredValidatorByStake::V1(validator1), - VersionedConsensusManagerRegisteredValidatorByStake::V1(validator2), - ) => validator1.stake.cmp(&validator2.stake).reverse(), - } + let validator1 = validator_1.content.as_unique_latest_ref(); + let validator2 = validator_2.content.as_unique_latest_ref(); + validator1.stake.cmp(&validator2.stake).reverse() }); let next_active_validator_set = ActiveValidatorSet { diff --git a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs index 65c6692c851..e772eff5b89 100644 --- a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs +++ b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs @@ -592,13 +592,32 @@ mod helper_macros { impl HasLatestVersion for $payload_type_name { type Latest = <[] as HasLatestVersion>::Latest; + + fn is_latest(&self) -> bool { + self.as_ref().is_latest() + } + fn into_latest(self) -> Self::Latest { self.into_content().into_latest() } + fn to_latest_mut(&mut self) -> &mut Self::Latest { + self.as_mut().to_latest_mut() + } + + fn from_latest(latest: Self::Latest) -> Self { + Self { + content: <[] as HasLatestVersion>::from_latest(latest), + } + } + fn as_latest_ref(&self) -> Option<&Self::Latest> { self.as_ref().as_latest_ref() } + + fn as_latest_mut(&mut self) -> Option<&mut Self::Latest> { + self.as_mut().as_latest_mut() + } } // Now implement other relevant content traits, for: diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs index 2ffb95b93f3..593c2a30e94 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs @@ -628,9 +628,7 @@ impl MultiResourcePoolBlueprint { let substate_key = MultiResourcePoolField::State.into(); let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let multi_resource_pool: VersionedMultiResourcePoolState = api.field_read_typed(handle)?; - let multi_resource_pool = match multi_resource_pool { - VersionedMultiResourcePoolState::V1(pool) => pool, - }; + let multi_resource_pool = multi_resource_pool.into_latest(); Ok((multi_resource_pool, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs index a7e0565ab61..0152cb5d299 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs @@ -412,10 +412,9 @@ impl OneResourcePoolBlueprint { { let substate_key = OneResourcePoolField::State.into(); let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; - let substate = api.field_read_typed::(handle)?; - let substate = match substate { - VersionedOneResourcePoolState::V1(state) => state, - }; + let substate = api + .field_read_typed::(handle)? + .into_latest(); Ok((substate, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs index 073ce9702d6..9d181d2eea9 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs @@ -595,13 +595,9 @@ impl TwoResourcePoolBlueprint { { let substate_key = TwoResourcePoolField::State.into(); let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; - let two_resource_pool_substate = - api.field_read_typed::(handle)?; - let two_resource_pool_substate = match two_resource_pool_substate { - VersionedTwoResourcePoolState::V1(two_resource_pool_substate) => { - two_resource_pool_substate - } - }; + let two_resource_pool_substate = api + .field_read_typed::(handle)? + .into_latest(); Ok((two_resource_pool_substate, handle)) } diff --git a/radix-engine/src/vm/wasm/wasmi.rs b/radix-engine/src/vm/wasm/wasmi.rs index c206505eebd..c8057dd3963 100644 --- a/radix-engine/src/vm/wasm/wasmi.rs +++ b/radix-engine/src/vm/wasm/wasmi.rs @@ -1902,6 +1902,7 @@ impl WasmEngine for WasmiEngine { mod tests { use super::*; use wabt::{wat2wasm, wat2wasm_with_features, ErrorKind, Features}; + use wasmi::Global; static MODULE_MUTABLE_GLOBALS: &str = r#" (module diff --git a/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs b/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs index 6a1a0228236..d40a24734eb 100644 --- a/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs +++ b/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs @@ -207,7 +207,7 @@ impl CommittableSubstateDatabase for RocksDBWithMerkleTreeSubstateStore { batch.put_cf( self.cf(MERKLE_NODES_CF), encode_key(&key), - scrypto_encode(&VersionedTreeNode::new_latest(node)).unwrap(), + scrypto_encode(&VersionedTreeNode::from_latest(node)).unwrap(), ); } if !self.pruning_enabled { diff --git a/radix-substate-store-queries/src/query/traverse.rs b/radix-substate-store-queries/src/query/traverse.rs index b0130163c4d..8d8c8ca0ac8 100644 --- a/radix-substate-store-queries/src/query/traverse.rs +++ b/radix-substate-store-queries/src/query/traverse.rs @@ -135,9 +135,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' ) .expect("Broken database"); - let liquid = match liquid { - VersionedFungibleVaultBalance::V1(liquid) => liquid, - }; + let liquid = liquid.into_latest(); visitor.visit_fungible_vault( node_id, @@ -156,9 +154,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' ) .expect("Broken database"); - let liquid = match liquid { - VersionedNonFungibleVaultBalance::V1(liquid) => liquid, - }; + let liquid = liquid.into_latest(); visitor.visit_non_fungible_vault( node_id, diff --git a/sbor-derive-common/src/categorize.rs b/sbor-derive-common/src/categorize.rs index b61e2e98d3f..5526cda4ed9 100644 --- a/sbor-derive-common/src/categorize.rs +++ b/sbor-derive-common/src/categorize.rs @@ -83,10 +83,22 @@ fn handle_normal_categorize( empty_fields_unpacking, .. } = process_fields_for_encode(&v.fields)?; - Ok(( - quote! { Self::#v_id #empty_fields_unpacking => #discriminator, }, - quote! { Self::#v_id #empty_fields_unpacking => #unskipped_field_count, }, - )) + + Ok(match discriminator { + VariantDiscriminator::Expr(discriminator) => { + ( + quote! { Self::#v_id #empty_fields_unpacking => #discriminator, }, + quote! { Self::#v_id #empty_fields_unpacking => #unskipped_field_count, }, + ) + }, + VariantDiscriminator::IgnoreAsUnreachable => { + let panic_message = format!("Variant with index {i} ignored as unreachable"); + ( + quote! { Self::#v_id #empty_fields_unpacking => panic!(#panic_message), }, + quote! { Self::#v_id #empty_fields_unpacking => panic!(#panic_message), }, + ) + }, + }) }) .collect::>>()? .into_iter() diff --git a/sbor-derive-common/src/decode.rs b/sbor-derive-common/src/decode.rs index 3784274101e..360439dda2e 100644 --- a/sbor-derive-common/src/decode.rs +++ b/sbor-derive-common/src/decode.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; @@ -156,12 +157,20 @@ pub fn handle_normal_decode( let discriminator = &discriminator_mapping[&i]; let decode_fields_content = decode_fields_content(quote! { Self::#v_id }, &v.fields)?; - Ok(quote! { - #discriminator => { - #decode_fields_content + + Ok(match discriminator { + VariantDiscriminator::Expr(discriminator) => Some(quote! { + #discriminator => { + #decode_fields_content + } + }), + VariantDiscriminator::IgnoreAsUnreachable => { + // Don't output any decoder + None } }) }) + .filter_map_ok(|x| x) .collect::>>()?; // Note: We use #[deny(unreachable_patterns)] to protect against users diff --git a/sbor-derive-common/src/describe.rs b/sbor-derive-common/src/describe.rs index ad663bc3f0d..622bf647ac6 100644 --- a/sbor-derive-common/src/describe.rs +++ b/sbor-derive-common/src/describe.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; @@ -217,53 +218,61 @@ fn handle_normal_describe( }, Data::Enum(DataEnum { variants, .. }) => { let discriminator_mapping = get_variant_discriminator_mapping(&attrs, &variants)?; - let variant_discriminators = (0..variants.len()) - .into_iter() - .map(|i| &discriminator_mapping[&i]) - .collect::>(); + let mut all_field_types = Vec::new(); - let variant_type_data: Vec<_> = { - variants - .iter() - .map(|v| { - let variant_name = v.ident.to_string(); - let FieldsData { - unskipped_field_types, - unskipped_field_name_strings, - .. - } = process_fields_for_describe(&v.fields)?; - all_field_types.extend_from_slice(&unskipped_field_types); - Ok(match &v.fields { - Fields::Named(FieldsNamed { .. }) => { - quote! { - ::sbor::TypeData::struct_with_named_fields( - #variant_name, - ::sbor::rust::vec![ - #((#unskipped_field_name_strings, <#unskipped_field_types as ::sbor::Describe<#custom_type_kind_generic>>::TYPE_ID),)* - ], - ) - } + let match_arms = variants + .iter() + .enumerate() + .map(|(i, v)| { + let variant_name_str = v.ident.to_string(); + + let discriminator = match &discriminator_mapping[&i] { + VariantDiscriminator::Expr(d) => d, + VariantDiscriminator::IgnoreAsUnreachable => return Ok(None), + }; + + let FieldsData { + unskipped_field_types, + unskipped_field_name_strings, + .. + } = process_fields_for_describe(&v.fields)?; + + all_field_types.extend_from_slice(&unskipped_field_types); + + let variant_type_data = match &v.fields { + Fields::Named(FieldsNamed { .. }) => { + quote! { + ::sbor::TypeData::struct_with_named_fields( + #variant_name_str, + ::sbor::rust::vec![ + #((#unskipped_field_name_strings, <#unskipped_field_types as ::sbor::Describe<#custom_type_kind_generic>>::TYPE_ID),)* + ], + ) } - Fields::Unnamed(FieldsUnnamed { .. }) => { - quote! { - ::sbor::TypeData::struct_with_unnamed_fields( - #variant_name, - ::sbor::rust::vec![ - #(<#unskipped_field_types as ::sbor::Describe<#custom_type_kind_generic>>::TYPE_ID,)* - ], - ) - } + } + Fields::Unnamed(FieldsUnnamed { .. }) => { + quote! { + ::sbor::TypeData::struct_with_unnamed_fields( + #variant_name_str, + ::sbor::rust::vec![ + #(<#unskipped_field_types as ::sbor::Describe<#custom_type_kind_generic>>::TYPE_ID,)* + ], + ) } - Fields::Unit => { - quote! { - ::sbor::TypeData::struct_with_unit_fields(#variant_name) - } + } + Fields::Unit => { + quote! { + ::sbor::TypeData::struct_with_unit_fields(#variant_name_str) } - }) - }) - .collect::>()? - }; + } + }; + Ok(Some(quote! { + #discriminator => #variant_type_data, + })) + }) + .filter_map_ok(|x| x) + .collect::>>()?; let unique_field_types = get_unique_types(&all_field_types); @@ -276,7 +285,7 @@ fn handle_normal_describe( ::sbor::TypeData::enum_variants( #type_name, ::sbor::rust::prelude::indexmap![ - #(#variant_discriminators => #variant_type_data,)* + #(#match_arms)* ], ) } diff --git a/sbor-derive-common/src/encode.rs b/sbor-derive-common/src/encode.rs index dc7883bbc39..6c1929569a7 100644 --- a/sbor-derive-common/src/encode.rs +++ b/sbor-derive-common/src/encode.rs @@ -137,11 +137,23 @@ pub fn handle_normal_encode( unskipped_unpacked_field_names, .. } = process_fields_for_encode(&v.fields)?; - Ok(quote! { - Self::#v_id #fields_unpacking => { - encoder.write_discriminator(#discriminator)?; - encoder.write_size(#unskipped_field_count)?; - #(encoder.encode(#unskipped_unpacked_field_names)?;)* + + Ok(match discriminator { + VariantDiscriminator::Expr(discriminator) => { + quote! { + Self::#v_id #fields_unpacking => { + encoder.write_discriminator(#discriminator)?; + encoder.write_size(#unskipped_field_count)?; + #(encoder.encode(#unskipped_unpacked_field_names)?;)* + } + } + } + VariantDiscriminator::IgnoreAsUnreachable => { + let panic_message = + format!("Variant with index {i} ignored as unreachable"); + quote! { + Self::#v_id #fields_unpacking => panic!(#panic_message), + } } }) }) diff --git a/sbor-derive-common/src/utils.rs b/sbor-derive-common/src/utils.rs index 4247d888e59..037f4182f41 100644 --- a/sbor-derive-common/src/utils.rs +++ b/sbor-derive-common/src/utils.rs @@ -198,12 +198,19 @@ pub fn extract_typed_attributes( enum VariantValue { Byte(LitByte), Path(Path), // EG a constant + UseDefaultIndex, + IgnoreAsUnreachable, +} + +pub enum VariantDiscriminator { + Expr(Expr), + IgnoreAsUnreachable, } pub fn get_variant_discriminator_mapping( enum_attributes: &[Attribute], variants: &Punctuated, -) -> Result> { +) -> Result> { if variants.len() > 255 { return Err(Error::new( Span::call_site(), @@ -213,82 +220,102 @@ pub fn get_variant_discriminator_mapping( let use_repr_discriminators = get_sbor_attribute_bool_value(enum_attributes, "use_repr_discriminators")?; - let mut variant_ids: BTreeMap = BTreeMap::new(); - - for (i, variant) in variants.iter().enumerate() { - let mut variant_attributes = extract_typed_attributes(&variant.attrs, "sbor")?; - if let Some(attribute) = variant_attributes.remove("discriminator") { - let id = match attribute { - AttributeValue::None(span) => { - return Err(Error::new(span, format!("No discriminator was provided"))); - } - AttributeValue::Path(path) => VariantValue::Path(path), - AttributeValue::Lit(literal) => parse_u8_from_literal(&literal) - .map(|b| VariantValue::Byte(LitByte::new(b, literal.span()))) - .ok_or_else(|| { - Error::new( - literal.span(), - format!("This discriminator is not a u8-convertible value"), - ) - })?, - }; - - variant_ids.insert(i, id); - continue; - } - if use_repr_discriminators { - if let Some(discriminant) = &variant.discriminant { - let expression = &discriminant.1; - - let id = match expression { - Expr::Lit(literal_expression) => parse_u8_from_literal(&literal_expression.lit) - .map(|b| VariantValue::Byte(LitByte::new(b, literal_expression.span()))), - Expr::Path(path_expression) => { - Some(VariantValue::Path(path_expression.path.clone())) + let variant_ids: Vec = variants.iter() + .map(|variant| -> Result { + let mut variant_attributes = extract_typed_attributes(&variant.attrs, "sbor")?; + if let Some(_) = variant_attributes.remove("ignore_as_unreachable") { + return Ok(VariantValue::IgnoreAsUnreachable); + } + if let Some(attribute) = variant_attributes.remove("discriminator") { + return Ok(match attribute { + AttributeValue::None(span) => { + return Err(Error::new(span, format!("No discriminator was provided"))); } - _ => None, - }; + AttributeValue::Path(path) => VariantValue::Path(path), + AttributeValue::Lit(literal) => parse_u8_from_literal(&literal) + .map(|b| VariantValue::Byte(LitByte::new(b, literal.span()))) + .ok_or_else(|| { + Error::new( + literal.span(), + format!("This discriminator is not a u8-convertible value"), + ) + })?, + }); + } + if use_repr_discriminators { + if let Some(discriminant) = &variant.discriminant { + let expression = &discriminant.1; + + let id = match expression { + Expr::Lit(literal_expression) => parse_u8_from_literal(&literal_expression.lit) + .map(|b| VariantValue::Byte(LitByte::new(b, literal_expression.span()))), + Expr::Path(path_expression) => { + Some(VariantValue::Path(path_expression.path.clone())) + } + _ => None, + }; + + let Some(id) = id else { + return Err(Error::new( + expression.span(), + format!("This discriminator is not a u8-convertible value or a path. Add an #[sbor(discriminator(X))] annotation with a u8-compatible literal or path to const/static variable to fix."), + )); + }; + return Ok(id); + } + } + Ok(VariantValue::UseDefaultIndex) + }) + .collect::>()?; - let Some(id) = id else { - return Err(Error::new( - expression.span(), - format!("This discriminator is not a u8-convertible value or a path. Add an #[sbor(discriminator(X))] annotation with a u8-compatible literal or path to const/static variable to fix."), - )); - }; + let explicit_indices_count = variant_ids + .iter() + .filter(|v| matches!(v, VariantValue::Byte(_) | VariantValue::Path(_))) + .count(); - variant_ids.insert(i, id); - continue; - } - } - } + let default_indices_count = variant_ids + .iter() + .filter(|v| matches!(v, VariantValue::UseDefaultIndex)) + .count(); - if variant_ids.len() > 0 { - if variant_ids.len() < variants.len() { + if explicit_indices_count > 0 { + if default_indices_count > 0 { return Err(Error::new( Span::call_site(), - format!("Either all or no variants must be assigned an id. Currently {} of {} variants have one.", variant_ids.len(), variants.len()), + format!("Either all or no variants must be assigned an id. Currently {} of {} variants have one.", explicit_indices_count, explicit_indices_count + default_indices_count), )); } - return Ok(variant_ids + let output = variant_ids .into_iter() - .map(|(i, id)| { - let expression = match id { - VariantValue::Byte(id) => parse_quote!(#id), - VariantValue::Path(id) => parse_quote!(#id), - }; - (i, expression) + .map(|id| match id { + VariantValue::Byte(id) => VariantDiscriminator::Expr(parse_quote!(#id)), + VariantValue::Path(id) => VariantDiscriminator::Expr(parse_quote!(#id)), + VariantValue::IgnoreAsUnreachable => VariantDiscriminator::IgnoreAsUnreachable, + VariantValue::UseDefaultIndex => unreachable!("default_indices_count was 0"), }) - .collect()); + .enumerate() + .collect(); + + return Ok(output); } // If no explicit indices, use default indices - Ok(variants + let output = variant_ids .iter() .enumerate() - .map(|(i, _)| { - let i_as_u8 = u8::try_from(i).unwrap(); - (i, parse_quote!(#i_as_u8)) + .map(|(i, id)| { + let discriminator = match id { + VariantValue::Byte(_) => unreachable!("explicit_indices_count was 0"), + VariantValue::Path(_) => unreachable!("explicit_indices_count was 0"), + VariantValue::IgnoreAsUnreachable => VariantDiscriminator::IgnoreAsUnreachable, + VariantValue::UseDefaultIndex => { + let i_as_u8 = u8::try_from(i).unwrap(); + VariantDiscriminator::Expr(parse_quote!(#i_as_u8)) + } + }; + (i, discriminator) }) - .collect()) + .collect(); + Ok(output) } fn parse_u8_from_literal(literal: &Lit) -> Option { diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 3f07edd5f38..530f10e0d24 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use proc_macro::TokenStream; /// Derive code that returns the value kind. @@ -41,6 +43,27 @@ pub fn sbor(input: TokenStream) -> TokenStream { .into() } +/// An empty derive which exists solely to allow the helper "sbor" attribute +/// to be used without generating a compile error. +/// +/// The intended use-case is as a utility for building other macros, +/// which wish to add sbor attribute annotations to types for when they do +/// use an Sbor derive - but wish to avoid the following error when they don't: +/// ```text +/// error: cannot find attribute `sbor` in this scope +/// ``` +/// +/// Ideally this would output an empty token stream, but instead we +/// return a simply comment, to avoid the proc macro system thinking +/// the macro build has broken and returning this error: +/// ```text +/// proc macro `PermitSborAttributes` not expanded: internal error +/// ``` +#[proc_macro_derive(PermitSborAttributes, attributes(sbor))] +pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { + TokenStream::from_str(&"// Empty PermitSborAttributes expansion").unwrap() +} + const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind"; const BASIC_CUSTOM_TYPE_KIND: &str = "sbor::NoCustomTypeKind"; diff --git a/sbor-tests/tests/enum.rs b/sbor-tests/tests/enum.rs index ddef74e24eb..87c2fd8a581 100644 --- a/sbor-tests/tests/enum.rs +++ b/sbor-tests/tests/enum.rs @@ -14,6 +14,28 @@ pub enum Abc { Variant2, } +#[derive(Sbor, PartialEq, Eq, Debug)] +#[sbor(type_name = "Abc")] +pub enum AbcV2 { + #[sbor(discriminator(VARIANT_1))] + Variant1, + #[sbor(ignore_as_unreachable)] + UnreachableVariant, + #[sbor(discriminator(VARIANT_2))] + Variant2, +} + +#[derive(PermitSborAttributes, PartialEq, Eq, Debug)] +#[sbor(type_name = "Abc")] +pub enum TestThatPermitSborAttributesCanCompile { + #[sbor(discriminator(VARIANT_1))] + Variant1, + #[sbor(ignore_as_unreachable)] + UnreachableVariant, + #[sbor(discriminator(VARIANT_2))] + Variant2, +} + const CONST_55_U8: u8 = 55; const CONST_32_U32: u32 = 32; const CONST_32_U8: u8 = 32; @@ -57,6 +79,8 @@ pub enum Mixed { fn can_encode_and_decode() { check_encode_decode_schema(&Abc::Variant1); check_encode_decode_schema(&Abc::Variant2); + check_encode_decode_schema(&AbcV2::Variant1); + check_encode_decode_schema(&AbcV2::Variant2); check_encode_decode_schema(&Mixed::A); check_encode_decode_schema(&Mixed::B); check_encode_decode_schema(&Mixed::C { @@ -69,6 +93,8 @@ fn can_encode_and_decode() { check_encode_decode_schema(&Mixed::H); check_encode_decode_schema(&Mixed::I); + check_schema_equality::(); + check_encode_identically( &Mixed::C { test: "hello".to_string(), @@ -86,7 +112,15 @@ fn can_encode_and_decode() { discriminator: 14, fields: vec![], }, - ) + ); + check_encode_identically(&Abc::Variant1, &AbcV2::Variant1); + check_encode_identically(&Abc::Variant2, &AbcV2::Variant2); +} + +#[test] +#[should_panic] +fn test_encoding_unreachable_variant_panics() { + let _ignored = basic_encode(&AbcV2::UnreachableVariant); } fn check_encode_decode_schema( @@ -111,3 +145,10 @@ fn check_encode_decode_schema(value1: &T1, value2: &T2) { assert_eq!(basic_encode(value1).unwrap(), basic_encode(value2).unwrap()); } + +fn check_schema_equality() { + assert_eq!( + generate_full_schema_from_single_type::(), + generate_full_schema_from_single_type::(), + ); +} diff --git a/sbor/src/lib.rs b/sbor/src/lib.rs index 791e8d60133..b79a319b794 100644 --- a/sbor/src/lib.rs +++ b/sbor/src/lib.rs @@ -67,7 +67,7 @@ pub use versioned::*; extern crate sbor_derive; pub use sbor_derive::{ BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, Categorize, Decode, - Describe, Encode, Sbor, + Describe, Encode, PermitSborAttributes, Sbor, }; // This is to make derives work within this crate. diff --git a/sbor/src/schema/schema.rs b/sbor/src/schema/schema.rs index 7445a6b83c0..c3e4f9ce3f1 100644 --- a/sbor/src/schema/schema.rs +++ b/sbor/src/schema/schema.rs @@ -1,27 +1,19 @@ use crate::rust::prelude::*; use crate::*; -define_versioned!( +define_single_versioned!( #[derive(Debug, Clone, PartialEq, Eq, Sbor)] #[sbor(child_types = "S::CustomTypeKind, S::CustomTypeValidation")] - pub enum VersionedSchema { - latest_version: { - 1 => Schema = SchemaV1::, - } - } + pub enum VersionedSchema => Schema = SchemaV1:: ); impl VersionedSchema { pub fn v1(&self) -> &SchemaV1 { - match self { - VersionedSchema::V1(schema) => schema, - } + self.as_unique_latest_ref() } pub fn v1_mut(&mut self) -> &mut SchemaV1 { - match self { - VersionedSchema::V1(schema) => schema, - } + self.as_unique_latest_mut() } } diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index caf7c825a2e..9cfaa91baad 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -6,9 +6,33 @@ pub enum UpdateResult { /// A marker trait to indicate that the type is versioned. /// This can be used for type bounds for requiring that types are versioned. pub trait HasLatestVersion { + /// The type for the latest content. type Latest; + + /// Returns true if the versioned enum is at the latest version. + fn is_latest(&self) -> bool; + /// Consumes the versioned enum to update it to the latest version, + /// then returns the latest content fn into_latest(self) -> Self::Latest; + /// Updates the versioned enum to the latest version in place, + /// and then returns a mutable reference to the latest content + fn to_latest_mut(&mut self) -> &mut Self::Latest; + /// Constructs the versioned enum from the latest content + fn from_latest(latest: Self::Latest) -> Self; + /// If the versioned enum is at the latest version, it returns + /// an immutable reference to the latest content, otherwise it returns `None`. + /// + /// If you require the latest version unconditionally, consider using + /// [`to_latest_mut`] to update to the latest version first - or, if + /// there is only a single version, use [`as_unique_latest_ref`]. fn as_latest_ref(&self) -> Option<&Self::Latest>; + /// If the versioned enum is at the latest version, it returns + /// a mutable reference to the latest content, otherwise it returns `None`. + /// + /// If you require the latest version unconditionally, consider using + /// [`to_latest_mut`] to update to the latest version first - or, if + /// there is only a single version, use [`as_unique_latest_mut`]. + fn as_latest_mut(&mut self) -> Option<&mut Self::Latest>; } pub trait CloneIntoLatest { @@ -26,10 +50,33 @@ impl + Clone, Latest> CloneIntoLatest for T /// This macro is intended for creating a data model which supports versioning. /// This is useful for creating an SBOR data model which can be updated in future. -/// In future, enum variants can be added, and automatically mapped to. +/// +/// In future, the type can be converted to `define_versioned`, enum variants can +/// be added, and automatically mapped to the latest version. /// /// This macro is just a simpler wrapper around the [`crate::define_versioned`] macro, /// for use when there's just a single version. +/// +/// Example usage: +/// ```rust +/// # use ::sbor::prelude::*; +/// # use ::sbor::define_single_versioned; +/// +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] +/// pub struct FooV1 { +/// bar: u8, +/// } +/// +/// define_single_versioned! { +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] +/// pub enum VersionedFoo => Foo = FooV1 +/// } +/// +/// // `Foo` is created as an alias for `FooV1` +/// let versioned = VersionedFoo::V1(Foo { bar: 42 }); +/// +/// assert_eq!(42, versioned.as_unique_latest_ref().bar); +/// ``` #[macro_export] macro_rules! define_single_versioned { ( @@ -50,6 +97,23 @@ macro_rules! define_single_versioned { }, } ); + + $crate::paste::paste! { + #[allow(dead_code)] + impl + $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + $name + $(< $( $lt ),+ >)? + { + pub fn as_unique_latest_ref(&self) -> &$latest_version_type { + self.as_latest_ref().unwrap() + } + + pub fn as_unique_latest_mut(&mut self) -> &mut $latest_version_type { + self.as_latest_mut().unwrap() + } + } + } }; } @@ -61,6 +125,55 @@ macro_rules! define_single_versioned { /// /// In the future, this may become a programmatic macro to support better error handling / /// edge case detection, and opting into more explicit SBOR handling. +/// +/// Example usage: +/// ```rust +/// use sbor::prelude::*; +/// use sbor::define_versioned; +/// +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] +/// pub struct FooV1 { +/// bar: u8, +/// } +/// +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] +/// pub struct FooV2 { +/// bar: u8, +/// baz: Option, +/// } +/// +/// impl From for FooV2 { +/// fn from(value: FooV1) -> FooV2 { +/// FooV2 { +/// bar: value.bar, +/// // Could also use `value.bar` as sensible default during inline update +/// baz: None, +/// } +/// } +/// } +/// +/// define_versioned!( +/// #[derive(Debug, Clone, PartialEq, Eq, Sbor)] +/// enum VersionedFoo { +/// previous_versions: [ +/// 1 => FooV1: { updates_to: 2 }, +/// ], +/// latest_version: { +/// 2 => Foo = FooV2, +/// }, +/// } +/// ); +/// +/// let mut versioned_old = VersionedFoo::V1(FooV1 { bar: 42 }); +/// // `Foo` is created as an alias for the latest content, `FooV2` +/// let versioned_new = VersionedFoo::V2(Foo { bar: 42, baz: None }); +/// +/// assert_ne!(&versioned_new, &versioned_old); +/// assert_eq!(versioned_new.as_latest_ref().unwrap(), &*versioned_old.to_latest_mut()); +/// +/// // After a call to `to_latest_mut`, `versioned_old` has been updated: +/// assert_eq!(versioned_new, versioned_old); +/// ``` #[macro_export] macro_rules! define_versioned { ( @@ -104,7 +217,10 @@ macro_rules! define_versioned { #[allow(dead_code)] $vis type $latest_version_alias = $latest_version_type; + use $crate::PermitSborAttributes as [<$name _PermitSborAttributes>]; + $(#[$attributes])* + #[derive([<$name _PermitSborAttributes>])] // We include the repr(u8) so that the SBOR discriminants are assigned // to match the version numbers if SBOR is used on the versioned enum #[repr(u8)] @@ -114,6 +230,18 @@ macro_rules! define_versioned { []($version_type) = $version_num, )*)? []($latest_version_type) = $latest_version, + #[sbor(ignore_as_unreachable)] + /// This variant is not intended to be used, except inside private + /// versioning code as a marker for transient internal state during + /// inline updates. + /// + /// If matching on this enum, consider instead using `.to_latest_mut()` + /// or `.into_latest()`, which updates the versioned enum to the latest + /// version, and exposes the latest content. + /// + /// If matching on the enum is required, the match arm for this variant + /// should panic!(). + __InternalUpdateInProgress = 255, } #[allow(dead_code)] @@ -122,20 +250,17 @@ macro_rules! define_versioned { $name $(< $( $lt ),+ >)? { - pub fn new_latest(value: $latest_version_type) -> Self { - Self::[](value) - } - - pub fn update_once(self) -> $crate::UpdateResult { + fn update_once(self) -> $crate::UpdateResult { match self { $($( Self::[](value) => $crate::UpdateResult::Updated(Self::[](value.into())), )*)? Self::[](value) => $crate::UpdateResult::AtLatest(Self::[](value)), + Self::__InternalUpdateInProgress => panic!("Update is already in progress"), } } - pub fn update_to_latest(mut self) -> Self { + fn update_to_latest(mut self) -> Self { loop { match self.update_once() { $crate::UpdateResult::Updated(new) => { @@ -154,6 +279,14 @@ macro_rules! define_versioned { { type Latest = $latest_version_type; + #[allow(unreachable_patterns)] + fn is_latest(&self) -> bool { + match self { + Self::[](_) => true, + _ => false, + } + } + #[allow(irrefutable_let_patterns)] fn into_latest(self) -> Self::Latest { let Self::[](latest) = self.update_to_latest() else { @@ -162,6 +295,18 @@ macro_rules! define_versioned { return latest; } + fn to_latest_mut(&mut self) -> &mut $latest_version_type { + if !self.is_latest() { + let current = radix_rust::prelude::mem::replace(self, Self::__InternalUpdateInProgress); + *self = current.update_to_latest(); + } + self.as_latest_mut().unwrap() + } + + fn from_latest(latest: Self::Latest) -> Self { + Self::[](latest) + } + #[allow(unreachable_patterns)] fn as_latest_ref(&self) -> Option<&Self::Latest> { match self { @@ -169,6 +314,14 @@ macro_rules! define_versioned { _ => None, } } + + #[allow(unreachable_patterns)] + fn as_latest_mut(&mut self) -> Option<&mut Self::Latest> { + match self { + Self::[](latest) => Some(latest), + _ => None, + } + } } ); @@ -297,7 +450,7 @@ mod tests { assert_eq!(versioned_actual.into_latest(), expected,); } - #[derive(Debug, Clone, PartialEq, Eq)] + #[derive(Debug, Clone, PartialEq, Eq, Sbor)] struct GenericModelV1(T); define_single_versioned!( diff --git a/scrypto-test/src/environment/env.rs b/scrypto-test/src/environment/env.rs index c03e6609f37..eff0351c303 100644 --- a/scrypto-test/src/environment/env.rs +++ b/scrypto-test/src/environment/env.rs @@ -714,12 +714,9 @@ where .unwrap(); let mut manager_substate = env.field_read_typed::(manager_handle)?; - match &mut manager_substate { - VersionedConsensusManagerState::V1(ConsensusManagerSubstate { - epoch: state_epoch, - .. - }) => *state_epoch = epoch, - } + + manager_substate.to_latest_mut().epoch = epoch; + env.field_write_typed(manager_handle, &manager_substate)?; env.field_close(manager_handle)?; Ok(()) @@ -749,11 +746,8 @@ where env.field_read_typed::( handle, )?; - match &mut proposer_minute_timestamp { - ConsensusManagerProposerMinuteTimestampFieldPayload { - content: VersionedConsensusManagerProposerMinuteTimestamp::V1(timestamp), - } => timestamp.epoch_minute = (instant.seconds_since_unix_epoch / 60) as i32, - }; + proposer_minute_timestamp.to_latest_mut().epoch_minute = + (instant.seconds_since_unix_epoch / 60) as i32; env.field_write_typed(handle, &proposer_minute_timestamp)?; env.field_close(handle)?; Ok(()) From 62fc8aebda6d659c6b491e57a15c74e01e55855d Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 30 Mar 2024 09:54:16 +0000 Subject: [PATCH 02/18] fix: Build error and warning fixes --- assets/blueprints/radiswap/src/lib.rs | 2 +- assets/blueprints/radiswap/tests/lib.rs | 9 ++++----- sbor/src/versioned.rs | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/assets/blueprints/radiswap/src/lib.rs b/assets/blueprints/radiswap/src/lib.rs index ef9cce2f203..f948a5468aa 100644 --- a/assets/blueprints/radiswap/src/lib.rs +++ b/assets/blueprints/radiswap/src/lib.rs @@ -85,7 +85,7 @@ mod radiswap { let input_amount = input_bucket.amount(); let input_reserves = reserves - .remove(&input_bucket.resource_address()) + .swap_remove(&input_bucket.resource_address()) .expect("Resource does not belong to the pool"); let (output_resource_address, output_reserves) = reserves.into_iter().next().unwrap(); diff --git a/assets/blueprints/radiswap/tests/lib.rs b/assets/blueprints/radiswap/tests/lib.rs index 17e5795c0ff..c2d740355c3 100644 --- a/assets/blueprints/radiswap/tests/lib.rs +++ b/assets/blueprints/radiswap/tests/lib.rs @@ -68,11 +68,10 @@ fn reading_and_asserting_against_radiswap_pool_state() -> Result<(), RuntimeErro })?; let (amount1, amount2) = env.with_component_state::( pool_component, - |VersionedTwoResourcePoolState::V1(TwoResourcePoolStateV1 { - vaults: [(_, vault1), (_, vault2)], - .. - }), - env| { (vault1.amount(env).unwrap(), vault2.amount(env).unwrap()) }, + |state, env| { + let [(_, vault1), (_, vault2)] = &mut state.to_latest_mut().vaults; + (vault1.amount(env).unwrap(), vault2.amount(env).unwrap()) + }, )?; assert_eq!(amount1, dec!("100")); assert_eq!(amount2, dec!("100")); diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index 9cfaa91baad..861594a1444 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -56,7 +56,7 @@ impl + Clone, Latest> CloneIntoLatest for T /// /// This macro is just a simpler wrapper around the [`crate::define_versioned`] macro, /// for use when there's just a single version. -/// +/// /// Example usage: /// ```rust /// # use ::sbor::prelude::*; @@ -71,7 +71,7 @@ impl + Clone, Latest> CloneIntoLatest for T /// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] /// pub enum VersionedFoo => Foo = FooV1 /// } -/// +/// /// // `Foo` is created as an alias for `FooV1` /// let versioned = VersionedFoo::V1(Foo { bar: 42 }); /// @@ -135,7 +135,7 @@ macro_rules! define_single_versioned { /// pub struct FooV1 { /// bar: u8, /// } -/// +/// /// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] /// pub struct FooV2 { /// bar: u8, @@ -163,7 +163,7 @@ macro_rules! define_single_versioned { /// }, /// } /// ); -/// +/// /// let mut versioned_old = VersionedFoo::V1(FooV1 { bar: 42 }); /// // `Foo` is created as an alias for the latest content, `FooV2` /// let versioned_new = VersionedFoo::V2(Foo { bar: 42, baz: None }); From bcfc9dcb9c1f8f429f515efbfb5ef5d0b9139570 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sun, 31 Mar 2024 10:25:35 +0100 Subject: [PATCH 03/18] tweak: Payload types implement HasUniqueLatestVersion --- .../consensus_manager/consensus_manager.rs | 4 +-- .../models/native_blueprint_state_macro.rs | 10 +++++++ .../checkers/role_assignment_db_checker.rs | 2 +- .../system_modules/auth/authorization.rs | 2 +- sbor/src/versioned.rs | 28 +++++++++++++------ 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs index a8f031dcc26..18b23d57865 100644 --- a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs +++ b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs @@ -1247,8 +1247,8 @@ impl ConsensusManagerBlueprint { // then let's be even more accurate here. This sort is stable, so if two validators tie, then the resultant order will be // decided on sort key DESC. top_registered_validators.sort_by(|(_, validator_1), (_, validator_2)| { - let validator1 = validator_1.content.as_unique_latest_ref(); - let validator2 = validator_2.content.as_unique_latest_ref(); + let validator1 = validator_1.as_unique_latest_ref(); + let validator2 = validator_2.as_unique_latest_ref(); validator1.stake.cmp(&validator2.stake).reverse() }); diff --git a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs index e772eff5b89..bbc423b604b 100644 --- a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs +++ b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs @@ -620,6 +620,16 @@ mod helper_macros { } } + impl HasUniqueLatestVersion for $payload_type_name { + fn as_unique_latest_ref(&self) -> &Self::Latest { + self.as_ref().as_unique_latest_ref() + } + + fn as_unique_latest_mut(&mut self) -> &mut Self::Latest { + self.as_mut().as_unique_latest_mut() + } + } + // Now implement other relevant content traits, for: // > The "latest" type: $ident_core impl $content_trait<$payload_type_name> for $ident_core { diff --git a/radix-engine/src/system/checkers/role_assignment_db_checker.rs b/radix-engine/src/system/checkers/role_assignment_db_checker.rs index 6dd15d97e34..bb84a609dab 100644 --- a/radix-engine/src/system/checkers/role_assignment_db_checker.rs +++ b/radix-engine/src/system/checkers/role_assignment_db_checker.rs @@ -154,7 +154,7 @@ impl RoleAssignmentDatabaseChecker { F: FnMut(RoleAssignmentDatabaseCheckerError), { let key = key.content; - let value = value.content.into_latest(); + let value = value.into_latest(); Self::check_access_rule_limits(value, add_error); Self::check_is_role_key_reserved(&key, add_error); diff --git a/radix-engine/src/system/system_modules/auth/authorization.rs b/radix-engine/src/system/system_modules/auth/authorization.rs index 9d1ef068d18..6d6022fc72f 100644 --- a/radix-engine/src/system/system_modules/auth/authorization.rs +++ b/radix-engine/src/system/system_modules/auth/authorization.rs @@ -353,7 +353,7 @@ impl Authorization { api.kernel_close_substate(handle)?; match substate.into_value() { - Some(access_rule) => access_rule.content.into_latest(), + Some(access_rule) => access_rule.into_latest(), None => { let handle = api.kernel_open_substate( role_assignment_of.as_node_id(), diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index 861594a1444..fc84675f216 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -35,19 +35,22 @@ pub trait HasLatestVersion { fn as_latest_mut(&mut self) -> Option<&mut Self::Latest>; } -pub trait CloneIntoLatest { - type Latest; +pub trait CloneIntoLatest: HasLatestVersion { fn clone_into_latest(&self) -> Self::Latest; } -impl + Clone, Latest> CloneIntoLatest for T { - type Latest = Latest; - +impl CloneIntoLatest for T { fn clone_into_latest(&self) -> Self::Latest { self.clone().into_latest() } } +pub trait HasUniqueLatestVersion: HasLatestVersion { + fn as_unique_latest_ref(&self) -> &Self::Latest; + + fn as_unique_latest_mut(&mut self) -> &mut Self::Latest; +} + /// This macro is intended for creating a data model which supports versioning. /// This is useful for creating an SBOR data model which can be updated in future. /// @@ -102,15 +105,22 @@ macro_rules! define_single_versioned { #[allow(dead_code)] impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + HasUniqueLatestVersion for $name $(< $( $lt ),+ >)? { - pub fn as_unique_latest_ref(&self) -> &$latest_version_type { - self.as_latest_ref().unwrap() + fn as_unique_latest_ref(&self) -> &Self::Latest { + match self { + Self::V1(content) => content, + Self::__InternalUpdateInProgress => panic!("Unexpected variant __InternalUpdateInProgress"), + } } - pub fn as_unique_latest_mut(&mut self) -> &mut $latest_version_type { - self.as_latest_mut().unwrap() + fn as_unique_latest_mut(&mut self) -> &mut Self::Latest { + match self { + Self::V1(content) => content, + Self::__InternalUpdateInProgress => panic!("Unexpected variant __InternalUpdateInProgress"), + } } } } From c2720e57299992011694a29287808fe083faea59 Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 20 Apr 2024 05:02:59 +0100 Subject: [PATCH 04/18] feat: Add #[sbor(as_type)] and update versioned --- radix-blueprint-schema-init/src/lib.rs | 5 +- radix-clis/src/replay/cmd_sync.rs | 4 +- radix-clis/src/resim/dumper.rs | 15 +- radix-clis/src/resim/mod.rs | 4 +- radix-clis/src/scrypto_bindgen/mod.rs | 6 +- .../non_fungible_resource_manager.rs | 2 +- .../src/object_modules/metadata/models/mod.rs | 1 + .../src/types/kv_store_init.rs | 2 +- .../tests/kernel/test_environment.rs | 2 +- radix-engine-tests/tests/system/bootstrap.rs | 16 +- radix-engine-tests/tests/system/package.rs | 2 +- .../tests/system/schema_sanity_check.rs | 2 +- .../blueprints/access_controller/blueprint.rs | 6 +- .../src/blueprints/account/blueprint.rs | 8 +- .../consensus_manager/consensus_manager.rs | 50 +- .../blueprints/consensus_manager/validator.rs | 56 +- .../src/blueprints/locker/blueprint.rs | 6 +- radix-engine/src/blueprints/models/keys.rs | 3 +- .../models/native_blueprint_state_macro.rs | 85 ++- .../src/blueprints/models/payloads.rs | 22 +- .../src/blueprints/package/package.rs | 27 +- .../v1/v1_0/multi_resource_pool_blueprint.rs | 4 +- .../v1/v1_0/one_resource_pool_blueprint.rs | 2 +- .../v1/v1_0/two_resource_pool_blueprint.rs | 2 +- .../v1/v1_1/multi_resource_pool_blueprint.rs | 4 +- .../v1/v1_1/one_resource_pool_blueprint.rs | 2 +- .../v1/v1_1/two_resource_pool_blueprint.rs | 2 +- .../resource/fungible/fungible_bucket.rs | 2 +- .../fungible/fungible_resource_manager.rs | 12 +- .../resource/fungible/fungible_vault.rs | 22 +- .../non_fungible_resource_manager.rs | 12 +- .../non_fungible/non_fungible_vault.rs | 22 +- .../src/object_modules/metadata/package.rs | 2 +- .../object_modules/role_assignment/package.rs | 12 +- .../src/object_modules/royalty/package.rs | 8 +- .../checkers/component_royalty_db_checker.rs | 2 +- .../checkers/package_royalty_db_checker.rs | 2 +- .../system/checkers/resource_db_checker.rs | 16 +- .../checkers/role_assignment_db_checker.rs | 7 +- radix-engine/src/system/system.rs | 2 +- radix-engine/src/system/system_db_reader.rs | 17 +- .../system_modules/auth/authorization.rs | 4 +- radix-engine/src/system/system_substates.rs | 14 +- .../src/transaction/state_update_summary.rs | 4 +- .../src/transaction/transaction_executor.rs | 20 +- .../src/transaction/transaction_receipt.rs | 2 +- radix-engine/src/updates/state_updates.rs | 48 +- radix-engine/src/vm/vm.rs | 13 +- radix-engine/src/vm/wasm/prepare.rs | 4 +- radix-sbor-derive/src/scrypto_encode.rs | 3 +- .../src/rocks_db_with_merkle_tree/mod.rs | 6 +- .../src/state_tree/tree_store.rs | 2 +- .../src/query/traverse.rs | 6 +- radix-transaction-scenarios/src/executor.rs | 2 +- sbor-derive-common/src/categorize.rs | 215 ++++--- sbor-derive-common/src/decode.rs | 111 ++-- sbor-derive-common/src/describe.rs | 199 ++++--- sbor-derive-common/src/encode.rs | 86 +-- sbor-derive-common/src/utils.rs | 296 ++++++---- sbor-derive/src/eager_stringify.rs | 63 +++ sbor-derive/src/lib.rs | 10 +- sbor-tests/tests/enum.rs | 4 +- sbor-tests/tests/transparent.rs | 26 +- sbor/src/lib.rs | 12 +- sbor/src/schema/schema.rs | 6 +- .../type_metadata_validation.rs | 3 + sbor/src/schema/type_aggregator.rs | 5 +- sbor/src/schema/type_data/type_kind.rs | 2 +- sbor/src/versioned.rs | 523 ++++++++++++------ scrypto-test/src/environment/env.rs | 7 +- .../src/ledger_simulator/ledger_simulator.rs | 28 +- 71 files changed, 1393 insertions(+), 809 deletions(-) create mode 100644 sbor-derive/src/eager_stringify.rs diff --git a/radix-blueprint-schema-init/src/lib.rs b/radix-blueprint-schema-init/src/lib.rs index db749081436..6f9920acb2e 100644 --- a/radix-blueprint-schema-init/src/lib.rs +++ b/radix-blueprint-schema-init/src/lib.rs @@ -43,11 +43,12 @@ impl Default for BlueprintSchemaInit { fn default() -> Self { Self { generics: Vec::new(), - schema: VersionedScryptoSchema::V1(SchemaV1 { + schema: Schema { type_kinds: Vec::new(), type_metadata: Vec::new(), type_validations: Vec::new(), - }), + } + .into_versioned(), state: BlueprintStateSchemaInit::default(), events: BlueprintEventSchemaInit::default(), types: BlueprintTypeSchemaInit::default(), diff --git a/radix-clis/src/replay/cmd_sync.rs b/radix-clis/src/replay/cmd_sync.rs index 89d5213f699..dfe256626d2 100644 --- a/radix-clis/src/replay/cmd_sync.rs +++ b/radix-clis/src/replay/cmd_sync.rs @@ -185,7 +185,7 @@ impl CommittedTxnReader { let next_identifiers: VersionedCommittedTransactionIdentifiers = scrypto_decode(next_identifiers_bytes.1.as_ref()).unwrap(); let expected_state_root_hash = next_identifiers - .into_latest() + .fully_update_into_latest_version() .resultant_ledger_hashes .state_root .0; @@ -208,7 +208,7 @@ impl CommittedTxnReader { define_single_versioned! { #[derive(Debug, Clone, Sbor)] - pub enum VersionedCommittedTransactionIdentifiers => CommittedTransactionIdentifiers = CommittedTransactionIdentifiersV1 + pub VersionedCommittedTransactionIdentifiers(CommittedTransactionIdentifiersVersions) => CommittedTransactionIdentifiers = CommittedTransactionIdentifiersV1 } #[derive(Debug, Clone, Sbor)] diff --git a/radix-clis/src/resim/dumper.rs b/radix-clis/src/resim/dumper.rs index b0f48e7142e..143508811a8 100644 --- a/radix-clis/src/resim/dumper.rs +++ b/radix-clis/src/resim/dumper.rs @@ -55,7 +55,12 @@ pub fn dump_package( output, "{}: {} bytes", "Code size".green().bold(), - substate.into_value().unwrap().into_latest().code.len() + substate + .into_value() + .unwrap() + .fully_update_into_latest_version() + .code + .len() ); let metadata = get_entity_metadata(package_address.as_node_id(), substate_db); @@ -232,7 +237,7 @@ pub fn dump_resource_manager( NonFungibleResourceManagerField::TotalSupply.into(), ) .map_err(|_| EntityDumpError::InvalidStore("Missing Total Supply".to_string()))? - .into_latest(); + .fully_update_into_latest_version(); writeln!( output, @@ -249,7 +254,7 @@ pub fn dump_resource_manager( FungibleResourceManagerField::Divisibility.into(), ) .map_err(|_| EntityDumpError::InvalidStore("Missing Divisibility".to_string()))? - .into_latest(); + .fully_update_into_latest_version(); writeln!(output, "{}: {}", "Resource Type".green().bold(), "Fungible"); writeln!( @@ -270,7 +275,7 @@ pub fn dump_resource_manager( FungibleResourceManagerField::TotalSupply.into(), ) .map_err(|_| EntityDumpError::InvalidStore("Missing Total Supply".to_string()))? - .into_latest(); + .fully_update_into_latest_version(); writeln!( output, @@ -306,7 +311,7 @@ fn get_entity_metadata( let map_key = key.into_map(); let key = scrypto_decode::(&map_key).unwrap(); let value = scrypto_decode::(&value).unwrap(); - (key, value.into_latest()) + (key, value.fully_update_into_latest_version()) }) .collect() } diff --git a/radix-clis/src/resim/mod.rs b/radix-clis/src/resim/mod.rs index 1e3a8061a83..570a6b651b0 100644 --- a/radix-clis/src/resim/mod.rs +++ b/radix-clis/src/resim/mod.rs @@ -409,7 +409,7 @@ pub fn get_event_schema( ) .unwrap()?; - let bp_interface = bp_definition.into_latest().interface; + let bp_interface = bp_definition.fully_update_into_latest_version().interface; let event_def = bp_interface.events.get(event_name)?; match event_def { @@ -487,7 +487,7 @@ pub fn db_upsert_epoch(epoch: Epoch) -> Result<(), Error> { started: true, }) }) - .into_latest(); + .fully_update_into_latest_version(); consensus_mgr_state.epoch = epoch; diff --git a/radix-clis/src/scrypto_bindgen/mod.rs b/radix-clis/src/scrypto_bindgen/mod.rs index a552386eb19..fb65033268f 100644 --- a/radix-clis/src/scrypto_bindgen/mod.rs +++ b/radix-clis/src/scrypto_bindgen/mod.rs @@ -110,7 +110,7 @@ where ) -> Result, schema::SchemaError> { self.lookup_schema(&type_identifier.0) .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? - .as_latest_ref() + .as_latest_version_ref() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_kind(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( @@ -125,7 +125,7 @@ where ) -> Result { self.lookup_schema(&type_identifier.0) .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? - .as_latest_ref() + .as_latest_version_ref() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_metadata(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( @@ -140,7 +140,7 @@ where ) -> Result, schema::SchemaError> { self.lookup_schema(&type_identifier.0) .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? - .as_latest_ref() + .as_latest_version_ref() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_validation(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( diff --git a/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs b/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs index ccc4a289710..1a11ce0f48f 100644 --- a/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs +++ b/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs @@ -432,7 +432,7 @@ mod test { mutable_fields, } = ds { - let s = schema.into_latest(); + let s = schema.fully_update_into_latest_version(); assert_eq!(s.type_kinds.len(), 1); assert_eq!(s.type_metadata.len(), 1); assert_eq!(s.type_validations.len(), 1); diff --git a/radix-engine-interface/src/object_modules/metadata/models/mod.rs b/radix-engine-interface/src/object_modules/metadata/models/mod.rs index 3b2a1776046..7ff54b8bb7d 100644 --- a/radix-engine-interface/src/object_modules/metadata/models/mod.rs +++ b/radix-engine-interface/src/object_modules/metadata/models/mod.rs @@ -22,6 +22,7 @@ use sbor::SborEnum; #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] #[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor, ManifestSbor)] +#[sbor(categorize_types = "U, O")] pub enum GenericMetadataValue { #[sbor(discriminator(METADATA_VALUE_STRING_DISCRIMINATOR))] String(String), diff --git a/radix-engine-interface/src/types/kv_store_init.rs b/radix-engine-interface/src/types/kv_store_init.rs index 1b5d848e256..34602233dca 100644 --- a/radix-engine-interface/src/types/kv_store_init.rs +++ b/radix-engine-interface/src/types/kv_store_init.rs @@ -6,7 +6,7 @@ use sbor::rust::prelude::*; #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] #[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor, ManifestSbor)] -#[sbor(transparent)] +#[sbor(transparent, categorize_types = "K")] pub struct KeyValueStoreInit { pub data: IndexMap>, } diff --git a/radix-engine-tests/tests/kernel/test_environment.rs b/radix-engine-tests/tests/kernel/test_environment.rs index 9fa4a2dc4be..071fe14bad1 100644 --- a/radix-engine-tests/tests/kernel/test_environment.rs +++ b/radix-engine-tests/tests/kernel/test_environment.rs @@ -232,7 +232,7 @@ fn references_read_from_state_are_visible_in_tests1() { .with_component_state::( radiswap_pool_component, |state, env| { - let [(_, vault1), (_, _)] = &mut state.to_latest_mut().vaults; + let [(_, vault1), (_, _)] = &mut state.as_unique_version_mut().vaults; vault1.amount(env) }, ) diff --git a/radix-engine-tests/tests/system/bootstrap.rs b/radix-engine-tests/tests/system/bootstrap.rs index 8c1002afe83..faa742ad5bb 100644 --- a/radix-engine-tests/tests/system/bootstrap.rs +++ b/radix-engine-tests/tests/system/bootstrap.rs @@ -274,7 +274,7 @@ fn test_genesis_resource_with_initial_allocation(owned_resource: bool) { ) .unwrap() .into_payload() - .into_latest(); + .fully_update_into_latest_version(); assert_eq!(total_supply, allocation_amount); let reader = SystemDatabaseReader::new(&substate_db); @@ -288,7 +288,7 @@ fn test_genesis_resource_with_initial_allocation(owned_resource: bool) { ), ) .unwrap() - .map(|v| v.into_latest()); + .map(|v| v.fully_update_into_latest_version()); if let Some(MetadataValue::String(symbol)) = entry { assert_eq!(symbol, "TST"); @@ -317,10 +317,10 @@ fn test_genesis_resource_with_initial_allocation(owned_resource: bool) { .unwrap(); assert!(substate.is_locked()); assert_eq!( - substate.into_value(), - Some(VersionedMetadataEntry::V1(MetadataValue::StringArray( + substate.into_value().map(|v| v.into_unique_version()), + Some(MetadataValue::StringArray( vec!["badge".to_owned()] - ))) + )) ); assert_eq!( @@ -491,7 +491,7 @@ fn test_genesis_stake_allocation() { ), ) .unwrap() - .map(|v| v.into_latest()); + .map(|v| v.fully_update_into_latest_version()); if let Some(MetadataValue::Url(url)) = validator_url_entry { assert_eq!( url, @@ -532,7 +532,7 @@ fn test_genesis_time() { ConsensusManagerField::ProposerMinuteTimestamp.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); assert_eq!(timestamp.epoch_minute, 123); } @@ -758,7 +758,7 @@ fn test_bootstrap_should_create_consensus_manager_with_sorted_validator_index() ObjectCollectionKey::SortedIndex(0, u16::from_be_bytes(sort_prefix), &address), ) .expect("validator cannot be read") - .map(|versioned| versioned.into_latest()) + .map(|versioned| versioned.fully_update_into_latest_version()) .expect("validator not found"); assert_eq!( diff --git a/radix-engine-tests/tests/system/package.rs b/radix-engine-tests/tests/system/package.rs index ab3aac8584b..45869e4ce89 100644 --- a/radix-engine-tests/tests/system/package.rs +++ b/radix-engine-tests/tests/system/package.rs @@ -153,7 +153,7 @@ fn test_basic_package_missing_export() { schema: BlueprintSchemaInit { generics: vec![], - schema: VersionedScryptoSchema::V1(SchemaV1 { + schema: VersionedScryptoSchema::from_latest_version(SchemaV1 { type_kinds: vec![], type_metadata: vec![], type_validations: vec![], diff --git a/radix-engine-tests/tests/system/schema_sanity_check.rs b/radix-engine-tests/tests/system/schema_sanity_check.rs index a8fd41b5550..fa71161b0e0 100644 --- a/radix-engine-tests/tests/system/schema_sanity_check.rs +++ b/radix-engine-tests/tests/system/schema_sanity_check.rs @@ -459,7 +459,7 @@ fn native_blueprints_with_typed_addresses_have_expected_schema() { panic!("Generic output!") }; - let schema = blueprint_definition.schema.schema.into_latest(); + let schema = blueprint_definition.schema.schema.fully_update_into_latest_version(); let type_kind = schema.resolve_type_kind(local_type_index).unwrap(); let type_validation = schema.resolve_type_validation(local_type_index).unwrap(); diff --git a/radix-engine/src/blueprints/access_controller/blueprint.rs b/radix-engine/src/blueprints/access_controller/blueprint.rs index d856d01bf3c..e12084c6d0a 100644 --- a/radix-engine/src/blueprints/access_controller/blueprint.rs +++ b/radix-engine/src/blueprints/access_controller/blueprint.rs @@ -1111,7 +1111,7 @@ impl AccessControllerBlueprint { let access_controller = { let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?; - access_controller.into_latest() + access_controller.fully_update_into_latest_version() }; access_controller.recovery_badge }; @@ -1175,7 +1175,7 @@ where let access_controller = { let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?; - access_controller.into_latest() + access_controller.fully_update_into_latest_version() }; let rtn = access_controller.transition(api, input)?; @@ -1201,7 +1201,7 @@ where let mut access_controller = { let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?; - access_controller.into_latest() + access_controller.fully_update_into_latest_version() }; let rtn = access_controller.transition_mut(api, input)?; diff --git a/radix-engine/src/blueprints/account/blueprint.rs b/radix-engine/src/blueprints/account/blueprint.rs index 311222d39e7..4b08a3e1f9d 100644 --- a/radix-engine/src/blueprints/account/blueprint.rs +++ b/radix-engine/src/blueprints/account/blueprint.rs @@ -1180,7 +1180,7 @@ impl AccountBlueprint { )?; api.key_value_entry_set_typed( kv_store_entry_lock_handle, - &VersionedAccountResourcePreference::V1(resource_preference), + &AccountResourcePreferenceVersions::V1(resource_preference).into_versioned(), )?; api.key_value_entry_close(kv_store_entry_lock_handle)?; @@ -1281,7 +1281,7 @@ impl AccountBlueprint { )?; let deposit_rule = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let default = deposit_rule.default_deposit_rule; api.field_close(handle)?; @@ -1314,7 +1314,7 @@ impl AccountBlueprint { .key_value_entry_get_typed::( kv_store_entry_lock_handle, )? - .map(|v| v.into_latest()); + .map(|v| v.fully_update_into_latest_version()); match entry { Some(vault) => Ok(vault), @@ -1427,7 +1427,7 @@ impl AccountBlueprint { .key_value_entry_get_typed::( kv_store_entry_lock_handle, )? - .map(|v| v.into_latest()); + .map(|v| v.fully_update_into_latest_version()); api.key_value_entry_close(kv_store_entry_lock_handle)?; Ok(entry) } diff --git a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs index 18b23d57865..84ab0ffc96e 100644 --- a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs +++ b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs @@ -644,7 +644,7 @@ impl ConsensusManagerBlueprint { let consensus_manager = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); Ok(consensus_manager.epoch) } @@ -661,7 +661,7 @@ impl ConsensusManagerBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(config_handle)?; config_substate }; @@ -673,7 +673,7 @@ impl ConsensusManagerBlueprint { )?; let mut manager_substate = api .field_read_typed::(manager_handle)? - .into_latest(); + .fully_update_into_latest_version(); if manager_substate.started { return Err(RuntimeError::ApplicationError( @@ -722,7 +722,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; Ok(Self::epoch_minute_to_instant( @@ -750,7 +750,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; Ok(Self::epoch_minute_to_instant( @@ -765,7 +765,7 @@ impl ConsensusManagerBlueprint { )?; let proposer_milli_timestamp = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; Ok(Self::epoch_milli_to_instant( @@ -811,7 +811,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; // convert back to Instant only for comparison operation @@ -860,7 +860,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; // convert back to Instant only for comparison operation @@ -881,7 +881,7 @@ impl ConsensusManagerBlueprint { )?; let proposer_milli_timestamp = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; // convert back to Instant only for comparison operation @@ -924,7 +924,7 @@ impl ConsensusManagerBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(config_handle)?; let manager_handle = api.actor_open_field( @@ -934,7 +934,7 @@ impl ConsensusManagerBlueprint { )?; let mut manager_substate = api .field_read_typed::(manager_handle)? - .into_latest(); + .fully_update_into_latest_version(); let progressed_rounds = Round::calculate_progress(manager_substate.round, round) .ok_or_else(|| { @@ -1001,7 +1001,7 @@ impl ConsensusManagerBlueprint { )?; let manager_substate = api.field_read_typed::(manager_handle)?; - let manager_substate = manager_substate.into_latest(); + let manager_substate = manager_substate.fully_update_into_latest_version(); let validator_creation_xrd_cost = if manager_substate.started { let config_handle = api.actor_open_field( @@ -1014,7 +1014,7 @@ impl ConsensusManagerBlueprint { api.field_close(config_handle)?; let validator_creation_xrd_cost = manager_config - .into_latest() + .fully_update_into_latest_version() .config .validator_creation_usd_cost .checked_mul(api.usd_price()?) @@ -1074,7 +1074,7 @@ impl ConsensusManagerBlueprint { )?; let exact_time_substate: ConsensusManagerProposerMilliTimestampFieldPayload = api.field_read_typed(handle)?; - let mut exact_time_substate = exact_time_substate.into_latest(); + let mut exact_time_substate = exact_time_substate.fully_update_into_latest_version(); let previous_timestamp = exact_time_substate.epoch_milli; if current_time_ms < previous_timestamp { return Err(RuntimeError::ApplicationError( @@ -1108,7 +1108,8 @@ impl ConsensusManagerBlueprint { )?; let rounded_timestamp_substate: ConsensusManagerProposerMinuteTimestampFieldPayload = api.field_read_typed(handle)?; - let mut rounded_timestamp_substate = rounded_timestamp_substate.into_latest(); + let mut rounded_timestamp_substate = + rounded_timestamp_substate.fully_update_into_latest_version(); let previous_rounded_value = rounded_timestamp_substate.epoch_minute; if new_rounded_value > previous_rounded_value { rounded_timestamp_substate.epoch_minute = new_rounded_value; @@ -1150,7 +1151,7 @@ impl ConsensusManagerBlueprint { )?; let statistic: ConsensusManagerCurrentProposalStatisticFieldPayload = api.field_read_typed(statistic_handle)?; - let mut statistic = statistic.into_latest(); + let mut statistic = statistic.fully_update_into_latest_version(); for gap_round_leader in proposal_history.gap_round_leaders { let gap_round_statistic = statistic.get_mut_proposal_statistic(gap_round_leader)?; gap_round_statistic.missed += 1; @@ -1187,7 +1188,7 @@ impl ConsensusManagerBlueprint { )?; let validator_set_substate: ConsensusManagerCurrentValidatorSetFieldPayload = api.field_read_typed(validator_set_handle)?; - let mut validator_set_substate = validator_set_substate.into_latest(); + let mut validator_set_substate = validator_set_substate.fully_update_into_latest_version(); let previous_validator_set = validator_set_substate.validator_set; // Read previous validator statistics @@ -1198,7 +1199,7 @@ impl ConsensusManagerBlueprint { )?; let statistic_substate: ConsensusManagerCurrentProposalStatisticFieldPayload = api.field_read_typed(statistic_handle)?; - let mut statistic_substate = statistic_substate.into_latest(); + let mut statistic_substate = statistic_substate.fully_update_into_latest_version(); let previous_statistics = statistic_substate.validator_statistics; // Read & write validator rewards @@ -1209,7 +1210,7 @@ impl ConsensusManagerBlueprint { )?; let mut rewards_substate = api .field_read_typed::(rewards_handle)? - .into_latest(); + .fully_update_into_latest_version(); // Apply emissions Self::apply_validator_emissions_and_rewards( @@ -1247,8 +1248,8 @@ impl ConsensusManagerBlueprint { // then let's be even more accurate here. This sort is stable, so if two validators tie, then the resultant order will be // decided on sort key DESC. top_registered_validators.sort_by(|(_, validator_1), (_, validator_2)| { - let validator1 = validator_1.as_unique_latest_ref(); - let validator2 = validator_2.as_unique_latest_ref(); + let validator1 = validator_1.as_unique_version_ref(); + let validator2 = validator_2.as_unique_version_ref(); validator1.stake.cmp(&validator2.stake).reverse() }); @@ -1256,7 +1257,12 @@ impl ConsensusManagerBlueprint { validators_by_stake_desc: top_registered_validators .into_iter() .take(config.max_validators as usize) - .map(|(component_address, validator)| (component_address, validator.into_latest())) + .map(|(component_address, validator)| { + ( + component_address, + validator.fully_update_into_latest_version(), + ) + }) .collect(), }; diff --git a/radix-engine/src/blueprints/consensus_manager/validator.rs b/radix-engine/src/blueprints/consensus_manager/validator.rs index 3a09097da73..8735c244a6a 100644 --- a/radix-engine/src/blueprints/consensus_manager/validator.rs +++ b/radix-engine/src/blueprints/consensus_manager/validator.rs @@ -590,7 +590,7 @@ impl ValidatorBlueprint { let mut validator = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); if !is_owner { if !validator.accepts_delegated_stake { @@ -656,7 +656,7 @@ impl ValidatorBlueprint { )?; let mut validator_substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Unstake let (unstake_bucket, new_stake_amount) = { @@ -680,7 +680,7 @@ impl ValidatorBlueprint { )?; let manager_substate = api .field_read_typed::(manager_handle)? - .into_latest(); + .fully_update_into_latest_version(); let current_epoch = manager_substate.epoch; api.field_close(manager_handle)?; @@ -691,7 +691,7 @@ impl ValidatorBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(config_handle)?; let claim_epoch = current_epoch @@ -763,7 +763,7 @@ impl ValidatorBlueprint { )?; let mut signal = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); signal.protocol_version_name = Some(protocol_version_name.clone()); api.field_write_typed( handle, @@ -792,7 +792,7 @@ impl ValidatorBlueprint { )?; let signal = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; Ok(signal.protocol_version_name) @@ -810,7 +810,7 @@ impl ValidatorBlueprint { let mut validator = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // No update if validator.is_registered == new_registered { return Ok(()); @@ -895,7 +895,7 @@ impl ValidatorBlueprint { )?; let validator = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let mut nft_resman = ResourceManager(validator.claim_nft); let resource_address = validator.claim_nft; let mut unstake_vault = Vault(validator.pending_xrd_withdraw_vault_id); @@ -914,7 +914,7 @@ impl ValidatorBlueprint { )?; let mgr_substate = api .field_read_typed::(mgr_handle)? - .into_latest(); + .fully_update_into_latest_version(); let epoch = mgr_substate.epoch; api.field_close(mgr_handle)?; epoch @@ -961,7 +961,7 @@ impl ValidatorBlueprint { )?; let mut validator = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Update Consensus Manager { @@ -999,7 +999,7 @@ impl ValidatorBlueprint { )?; let consensus_manager = api .field_read_typed::(consensus_manager_handle)? - .into_latest(); + .fully_update_into_latest_version(); let current_epoch = consensus_manager.epoch; api.field_close(consensus_manager_handle)?; @@ -1011,7 +1011,7 @@ impl ValidatorBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(config_handle)?; // begin the read+modify+write of the validator substate... @@ -1022,7 +1022,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // - promote any currently pending change if it became effective already if let Some(previous_request) = substate.validator_fee_change_request { @@ -1067,7 +1067,7 @@ impl ValidatorBlueprint { let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; Ok(substate.accepts_delegated_stake) @@ -1085,7 +1085,7 @@ impl ValidatorBlueprint { let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let stake_vault = Vault(substate.stake_xrd_vault_id); let stake_amount = stake_vault.amount(api)?; api.field_close(handle)?; @@ -1105,7 +1105,7 @@ impl ValidatorBlueprint { let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let stake_resource = ResourceManager(substate.stake_unit_resource); let total_stake_unit_supply = stake_resource.total_supply(api)?.unwrap(); api.field_close(handle)?; @@ -1133,7 +1133,7 @@ impl ValidatorBlueprint { )?; let validator = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); { let stake_unit_resman = ResourceManager(validator.stake_unit_resource); @@ -1166,7 +1166,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); substate.accepts_delegated_stake = accept_delegated_stake; api.field_write_typed( handle, @@ -1201,7 +1201,7 @@ impl ValidatorBlueprint { )?; let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); Vault(substate.locked_owner_stake_unit_vault_id).put(stake_unit_bucket, api)?; @@ -1228,7 +1228,7 @@ impl ValidatorBlueprint { )?; let consensus_manager = api .field_read_typed::(consensus_manager_handle)? - .into_latest(); + .fully_update_into_latest_version(); let current_epoch = consensus_manager.epoch; api.field_close(consensus_manager_handle)?; @@ -1240,7 +1240,7 @@ impl ValidatorBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(config_handle)?; // begin the read+modify+write of the validator substate... @@ -1251,7 +1251,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // - move the already-available withdrawals to a dedicated field Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?; @@ -1306,7 +1306,7 @@ impl ValidatorBlueprint { )?; let consensus_manager = api .field_read_typed::(consensus_manager_handle)? - .into_latest(); + .fully_update_into_latest_version(); let current_epoch = consensus_manager.epoch; api.field_close(consensus_manager_handle)?; @@ -1318,7 +1318,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?; let total_already_available_amount = mem::replace( @@ -1395,7 +1395,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // - resolve the effective validator fee factor let effective_validator_fee_factor = match &substate.validator_fee_change_request { @@ -1490,7 +1490,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Get the total reward amount let total_reward_xrd = xrd_bucket.amount(api)?; @@ -1581,7 +1581,7 @@ impl ValidatorBlueprint { ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(), &index_key, )? - .unwrap().into_latest(); + .unwrap().fully_update_into_latest_version(); validator.key = key; api.actor_sorted_index_insert_typed( ACTOR_STATE_OUTER_OBJECT, @@ -1604,7 +1604,7 @@ impl ValidatorBlueprint { ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(), &index_key, )? - .unwrap().into_latest(); + .unwrap().fully_update_into_latest_version(); validator.stake = new_stake_amount; api.actor_sorted_index_insert_typed( ACTOR_STATE_OUTER_OBJECT, diff --git a/radix-engine/src/blueprints/locker/blueprint.rs b/radix-engine/src/blueprints/locker/blueprint.rs index 282a4906077..84fb4521b75 100644 --- a/radix-engine/src/blueprints/locker/blueprint.rs +++ b/radix-engine/src/blueprints/locker/blueprint.rs @@ -578,7 +578,7 @@ impl AccountLockerBlueprint { .key_value_entry_get_typed::( account_claims_handle, )? - .map(|entry| entry.into_latest()); + .map(|entry| entry.fully_update_into_latest_version()); let account_claims_kv_store = match account_claims { Some(account_claims_kv_store) => account_claims_kv_store, @@ -595,7 +595,7 @@ impl AccountLockerBlueprint { // Write the kv-store's node id to the collection entry. api.key_value_entry_set_typed( account_claims_handle, - VersionedAccountLockerAccountClaims::V1(key_value_store), + AccountLockerAccountClaimsVersions::V1(key_value_store).into_versioned(), )?; // Return the NodeId of the kv-store. key_value_store @@ -655,7 +655,7 @@ impl AccountLockerBlueprint { .key_value_entry_get_typed::( account_claims_handle, )? - .map(|entry| entry.into_latest()); + .map(|entry| entry.fully_update_into_latest_version()); let account_claims_kv_store = match account_claims { Some(account_claims_kv_store) => account_claims_kv_store, diff --git a/radix-engine/src/blueprints/models/keys.rs b/radix-engine/src/blueprints/models/keys.rs index b696c6d0932..75da4f188dc 100644 --- a/radix-engine/src/blueprints/models/keys.rs +++ b/radix-engine/src/blueprints/models/keys.rs @@ -17,7 +17,6 @@ macro_rules! declare_key_new_type { ($content_type:ty)$(;)? ) => { $(#[$attributes])* - #[sbor(categorize_types = "")] /// This type represents the payload of the key of a particular sorted index collection. $vis struct $payload_type_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? @@ -136,7 +135,7 @@ macro_rules! declare_key_new_type { ($content_type:ty)$(;)? ) => { $(#[$attributes])* - #[sbor(transparent, categorize_types = "", transparent_name)] + #[sbor(transparent, transparent_name)] /// This new type represents the payload of the key of a particular collection. $vis struct $payload_type_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? diff --git a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs index bbc423b604b..6c545819103 100644 --- a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs +++ b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs @@ -579,7 +579,7 @@ mod helper_macros { paste::paste! { sbor::define_single_versioned!( $(#[$attributes])* - pub enum [] => $ident_core = [<$ident_core V1>] + pub []([<$ident_core Versions>]) => $ident_core = [<$ident_core V1>] ); declare_payload_new_type!( content_trait: $content_trait, @@ -589,44 +589,26 @@ mod helper_macros { pub struct $payload_type_name([]); ); - impl HasLatestVersion for $payload_type_name + impl $payload_type_name { - type Latest = <[] as HasLatestVersion>::Latest; - - fn is_latest(&self) -> bool { - self.as_ref().is_latest() - } - - fn into_latest(self) -> Self::Latest { - self.into_content().into_latest() - } - - fn to_latest_mut(&mut self) -> &mut Self::Latest { - self.as_mut().to_latest_mut() - } - - fn from_latest(latest: Self::Latest) -> Self { - Self { - content: <[] as HasLatestVersion>::from_latest(latest), - } + #[doc = "Delegates to [`"[]"::fully_update_into_latest_version`]."] + pub fn fully_update_into_latest_version(self) -> $ident_core { + self.content.fully_update_into_latest_version() } - fn as_latest_ref(&self) -> Option<&Self::Latest> { - self.as_ref().as_latest_ref() + #[doc = "Delegates to [`"[]"::from_latest_version`]."] + pub fn from_latest_version(latest_version: $ident_core) -> Self { + []::from_latest_version(latest_version).into() } - fn as_latest_mut(&mut self) -> Option<&mut Self::Latest> { - self.as_mut().as_latest_mut() - } - } - - impl HasUniqueLatestVersion for $payload_type_name { - fn as_unique_latest_ref(&self) -> &Self::Latest { - self.as_ref().as_unique_latest_ref() + #[doc = "Delegates to [`"[]"::into_unique_version`]."] + pub fn into_unique_version(self) -> $ident_core { + self.content.into_unique_version() } - fn as_unique_latest_mut(&mut self) -> &mut Self::Latest { - self.as_mut().as_unique_latest_mut() + #[doc = "Delegates to [`"[]"::from_unique_version`]."] + pub fn from_unique_version(unique_version: $ident_core) -> Self { + []::from_unique_version(unique_version).into() } } @@ -637,6 +619,12 @@ mod helper_macros { self.into() } } + + impl $content_trait<$payload_type_name> for [<$ident_core Versions>] { + fn into_content(self) -> [] { + []::from(self).into() + } + } } }; ( @@ -1136,9 +1124,9 @@ mod tests { #[test] fn validate_royalty_field_payload_mutability() { - let mut content = VersionedTestBlueprintRoyalty::V1(TestBlueprintRoyaltyV1); + let mut content = TestBlueprintRoyaltyVersions::V1(TestBlueprintRoyaltyV1).into_versioned(); let mut payload = TestBlueprintRoyaltyFieldPayload { - content: VersionedTestBlueprintRoyalty::V1(TestBlueprintRoyaltyV1), + content: TestBlueprintRoyaltyVersions::V1(TestBlueprintRoyaltyV1).into_versioned(), }; assert_eq!(&content, payload.as_ref()); assert_eq!(&mut content, payload.as_mut()); @@ -1163,9 +1151,10 @@ mod tests { fn validate_key_value_store_entry_payload_mutability() { fn create_payload() -> TestBlueprintMyCoolKeyValueStoreEntryPayload { TestBlueprintMyCoolKeyValueStoreEntryPayload { - content: VersionedTestBlueprintMyCoolKeyValueStore::V1( + content: TestBlueprintMyCoolKeyValueStoreVersions::V1( TestBlueprintMyCoolKeyValueStoreV1, - ), + ) + .into_versioned(), } } @@ -1193,22 +1182,24 @@ mod tests { .lock_status() ); - assert!(create_payload().as_latest_ref().is_some()); + assert!(create_payload().as_latest_version_ref().is_some()); } #[test] fn validate_index_entry_payload() { let payload = TestBlueprintMyCoolIndexEntryPayload { - content: VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1), + content: TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1) + .into_versioned(), }; assert_eq!( payload.into_substate().value().content, - VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1) + TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned() ); - let content = VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1); + let content = + TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned(); assert_eq!( - VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1), + TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned(), content.into_substate().value().content ); } @@ -1216,16 +1207,20 @@ mod tests { #[test] fn validate_sorted_index_entry_payload() { let payload = TestBlueprintMyCoolSortedIndexEntryPayload { - content: VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1), + content: TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned(), }; assert_eq!( payload.into_substate().value().content, - VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1) + TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned() ); - let content = VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1); + let content = TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned(); assert_eq!( - VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1), + TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned(), content.into_substate().value().content ); } diff --git a/radix-engine/src/blueprints/models/payloads.rs b/radix-engine/src/blueprints/models/payloads.rs index 97d500e6524..5b107efa8ac 100644 --- a/radix-engine/src/blueprints/models/payloads.rs +++ b/radix-engine/src/blueprints/models/payloads.rs @@ -11,7 +11,7 @@ macro_rules! declare_payload_new_type { ($content_type:ty)$(;)? ) => { $(#[$attributes])* - #[sbor(transparent, categorize_types = "")] + #[sbor(transparent)] /// This new type represents the payload of a particular field or collection. /// It is unique to this particular field/collection. $vis struct $payload_type_name @@ -40,6 +40,17 @@ macro_rules! declare_payload_new_type { } } + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + core::ops::Deref + for $payload_type_name $(< $( $lt ),+ >)? + { + type Target = $content_type; + + fn deref(&self) -> &Self::Target { + &self.content + } + } + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? core::convert::AsMut<$content_type> for $payload_type_name $(< $( $lt ),+ >)? @@ -49,6 +60,15 @@ macro_rules! declare_payload_new_type { } } + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + core::ops::DerefMut + for $payload_type_name $(< $( $lt ),+ >)? + { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.content + } + } + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $payload_trait for $payload_type_name $(< $( $lt ),+ >)? diff --git a/radix-engine/src/blueprints/package/package.rs b/radix-engine/src/blueprints/package/package.rs index 69bee49a9c7..bae8f2deb31 100644 --- a/radix-engine/src/blueprints/package/package.rs +++ b/radix-engine/src/blueprints/package/package.rs @@ -1483,12 +1483,14 @@ impl PackageRoyaltyNativeBlueprint { let royalty_charge = substate .into_value() - .and_then(|royalty_config| match royalty_config.into_latest() { - PackageRoyaltyConfig::Enabled(royalty_amounts) => { - royalty_amounts.get(ident).cloned() - } - PackageRoyaltyConfig::Disabled => Some(RoyaltyAmount::Free), - }) + .and_then( + |royalty_config| match royalty_config.fully_update_into_latest_version() { + PackageRoyaltyConfig::Enabled(royalty_amounts) => { + royalty_amounts.get(ident).cloned() + } + PackageRoyaltyConfig::Disabled => Some(RoyaltyAmount::Free), + }, + ) .unwrap_or(RoyaltyAmount::Free); // we check for negative royalties at the instantiation time of the royalty module. @@ -1506,7 +1508,11 @@ impl PackageRoyaltyNativeBlueprint { let substate: PackageRoyaltyAccumulatorFieldSubstate = api.kernel_read_substate(handle)?.as_typed().unwrap(); - let vault_id = substate.into_payload().into_latest().royalty_vault.0; + let vault_id = substate + .into_payload() + .fully_update_into_latest_version() + .royalty_vault + .0; let package_address = PackageAddress::new_or_panic(receiver.0); apply_royalty_cost( api, @@ -1540,7 +1546,10 @@ impl PackageRoyaltyNativeBlueprint { )?; let substate: PackageRoyaltyAccumulatorFieldPayload = api.field_read_typed(handle)?; - let bucket = substate.into_latest().royalty_vault.take_all(api)?; + let bucket = substate + .fully_update_into_latest_version() + .royalty_vault + .take_all(api)?; Ok(bucket) } @@ -1631,7 +1640,7 @@ impl PackageAuthNativeBlueprint { api.kernel_close_substate(handle)?; let template = match auth_template.into_value() { - Some(template) => template.into_latest(), + Some(template) => template.fully_update_into_latest_version(), None => { return Err(RuntimeError::SystemError( SystemError::AuthTemplateDoesNotExist(package_bp_version_id), diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs index 593c2a30e94..6a5c729f934 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs @@ -118,7 +118,7 @@ impl MultiResourcePoolBlueprint { api.new_simple_object( MULTI_RESOURCE_POOL_BLUEPRINT_IDENT, indexmap! { - MultiResourcePoolField::State.field_index() => FieldValue::new(&VersionedMultiResourcePoolState::V1(substate)), + MultiResourcePoolField::State.field_index() => FieldValue::new(MultiResourcePoolStateFieldPayload::from_content_source(substate)), }, )? }; @@ -628,7 +628,7 @@ impl MultiResourcePoolBlueprint { let substate_key = MultiResourcePoolField::State.into(); let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let multi_resource_pool: VersionedMultiResourcePoolState = api.field_read_typed(handle)?; - let multi_resource_pool = multi_resource_pool.into_latest(); + let multi_resource_pool = multi_resource_pool.fully_update_into_latest_version(); Ok((multi_resource_pool, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs index 0152cb5d299..e32490fe5a3 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs @@ -414,7 +414,7 @@ impl OneResourcePoolBlueprint { let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); Ok((substate, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs index 9d181d2eea9..f7cab9f7ba4 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs @@ -597,7 +597,7 @@ impl TwoResourcePoolBlueprint { let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let two_resource_pool_substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); Ok((two_resource_pool_substate, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs index b9b5dede139..2d445fd5999 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs @@ -118,7 +118,7 @@ impl MultiResourcePoolBlueprint { api.new_simple_object( MULTI_RESOURCE_POOL_BLUEPRINT_IDENT, indexmap! { - MultiResourcePoolField::State.field_index() => FieldValue::new(&VersionedMultiResourcePoolState::V1(substate)), + MultiResourcePoolField::State.field_index() => FieldValue::new(&MultiResourcePoolStateFieldPayload::from_content_source(substate)), }, )? }; @@ -591,7 +591,7 @@ impl MultiResourcePoolBlueprint { api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?; let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Op let rtn = callback(substate, api); diff --git a/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs index 7627c1daf86..61fadeb8e48 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs @@ -421,7 +421,7 @@ impl OneResourcePoolBlueprint { api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?; let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Op let rtn = callback(substate, api); diff --git a/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs index 7a15fc70555..a97514bdd9b 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs @@ -656,7 +656,7 @@ impl TwoResourcePoolBlueprint { api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?; let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Op let rtn = callback(substate, api); diff --git a/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs b/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs index ecb5c5935aa..f24209c3643 100644 --- a/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs +++ b/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs @@ -26,7 +26,7 @@ impl FungibleBucketBlueprint { .field_read_typed::( divisibility_handle, )? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(divisibility_handle)?; Ok(divisibility) } diff --git a/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs b/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs index f0bf049fc67..29305cb6ba3 100644 --- a/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs +++ b/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs @@ -566,7 +566,7 @@ impl FungibleResourceManagerBlueprint { )?; let divisibility: FungibleResourceManagerDivisibilityFieldPayload = api.field_read_typed(divisibility_handle)?; - divisibility.into_latest() + divisibility.fully_update_into_latest_version() }; // check amount @@ -590,7 +590,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .into_latest(); + .fully_update_into_latest_version(); // This should never overflow due to the 2^152 limit we place on mints. // Since Decimal have 2^192 max we would need to mint 2^40 times before // an overflow occurs. @@ -659,7 +659,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .into_latest(); + .fully_update_into_latest_version(); total_supply = total_supply .checked_sub(other_bucket.liquid.amount()) .ok_or(RuntimeError::ApplicationError( @@ -760,7 +760,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( divisibility_handle, )? - .into_latest(); + .fully_update_into_latest_version(); let resource_type = ResourceType::Fungible { divisibility }; Ok(resource_type) @@ -783,7 +783,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .into_latest(); + .fully_update_into_latest_version(); Ok(Some(total_supply)) } else { Ok(None) @@ -808,7 +808,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( divisibility_handle, )? - .into_latest(); + .fully_update_into_latest_version(); Ok(amount .for_withdrawal(divisibility, withdraw_strategy) diff --git a/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs b/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs index 5a3685daccb..1468273ff04 100644 --- a/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs +++ b/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs @@ -290,7 +290,7 @@ impl FungibleVaultBlueprint { )?; let divisibility = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; Ok(divisibility) } @@ -409,7 +409,7 @@ impl FungibleVaultBlueprint { let mut vault = api .field_read_typed::(vault_handle)? - .into_latest(); + .fully_update_into_latest_version(); let fee = vault.take_by_amount(amount).map_err(|e| { let vault_error = match e { ResourceError::InsufficientBalance { requested, actual } => { @@ -475,7 +475,7 @@ impl FungibleVaultBlueprint { let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .into_latest(); + .fully_update_into_latest_version(); frozen.frozen.insert(to_freeze); api.field_write_typed( frozen_flag_handle, @@ -498,7 +498,7 @@ impl FungibleVaultBlueprint { )?; let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .into_latest(); + .fully_update_into_latest_version(); frozen.frozen.remove(to_unfreeze); api.field_write_typed( frozen_flag_handle, @@ -570,7 +570,7 @@ impl FungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let max_locked = locked.amount(); // Take from liquid if needed @@ -605,7 +605,7 @@ impl FungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let max_locked = locked.amount(); let cnt = locked @@ -652,7 +652,7 @@ impl FungibleVaultBlueprint { )?; let frozen = api .field_read_typed::(frozen_flag_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(frozen_flag_handle)?; if frozen.frozen.intersects(flags) { @@ -715,7 +715,7 @@ impl FungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let amount = substate_ref.amount(); api.field_close(handle)?; Ok(amount) @@ -732,7 +732,7 @@ impl FungibleVaultBlueprint { )?; let substate_ref: LockedFungibleResource = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let amount = substate_ref.amount(); api.field_close(handle)?; Ok(amount) @@ -752,7 +752,7 @@ impl FungibleVaultBlueprint { )?; let mut substate_ref = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let taken = substate_ref.take_by_amount(amount).map_err(|e| { RuntimeError::ApplicationError(ApplicationError::VaultError(VaultError::ResourceError( e, @@ -782,7 +782,7 @@ impl FungibleVaultBlueprint { )?; let mut vault_balance = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); vault_balance.put(resource); api.field_write_typed( handle, diff --git a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs index 4a855a1d777..e4362733a84 100644 --- a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs +++ b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs @@ -919,7 +919,7 @@ impl NonFungibleResourceManagerBlueprint { .field_read_typed::( data_schema_handle, )? - .into_latest(); + .fully_update_into_latest_version(); mutable_fields .mutable_field_index .get(&field_name) @@ -1168,7 +1168,7 @@ impl NonFungibleResourceManagerBlueprint { let id_type = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let resource_type = ResourceType::NonFungible { id_type }; Ok(resource_type) @@ -1191,7 +1191,7 @@ impl NonFungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .into_latest(); + .fully_update_into_latest_version(); Ok(Some(total_supply)) } else { Ok(None) @@ -1292,7 +1292,7 @@ impl NonFungibleResourceManagerBlueprint { )?; let id_type = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; if id_type == NonFungibleIdType::RUID { return Err(RuntimeError::ApplicationError( @@ -1316,7 +1316,7 @@ impl NonFungibleResourceManagerBlueprint { )?; let id_type = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(handle)?; if id_type != NonFungibleIdType::RUID { @@ -1391,7 +1391,7 @@ impl NonFungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .into_latest(); + .fully_update_into_latest_version(); total_supply = total_supply .checked_add(amount) diff --git a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs index 4e0a2c52d4f..2afda3cd373 100644 --- a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs +++ b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs @@ -540,7 +540,7 @@ impl NonFungibleVaultBlueprint { let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .into_latest(); + .fully_update_into_latest_version(); frozen.frozen.insert(to_freeze); api.field_write_typed( frozen_flag_handle, @@ -563,7 +563,7 @@ impl NonFungibleVaultBlueprint { )?; let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .into_latest(); + .fully_update_into_latest_version(); frozen.frozen.remove(to_unfreeze); api.field_write_typed( frozen_flag_handle, @@ -663,7 +663,7 @@ impl NonFungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); // Take from liquid if needed let delta: IndexSet = ids @@ -701,7 +701,7 @@ impl NonFungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let mut liquid_non_fungibles: IndexSet = index_set_new(); for id in ids { @@ -746,7 +746,7 @@ impl NonFungibleVaultBlueprint { )?; let frozen = api .field_read_typed::(frozen_flag_handle)? - .into_latest(); + .fully_update_into_latest_version(); api.field_close(frozen_flag_handle)?; if frozen.frozen.intersects(flags) { @@ -809,7 +809,7 @@ impl NonFungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let amount = substate_ref.amount; api.field_close(handle)?; Ok(amount) @@ -826,7 +826,7 @@ impl NonFungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let amount = substate_ref.amount(); api.field_close(handle)?; Ok(amount) @@ -862,7 +862,7 @@ impl NonFungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let limit: usize = limit.try_into().unwrap(); let ids = substate_ref.ids().into_iter().take(limit).collect(); api.field_close(handle)?; @@ -884,7 +884,7 @@ impl NonFungibleVaultBlueprint { )?; let mut balance = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); if balance.amount < Decimal::from(n) { return Err(RuntimeError::ApplicationError( @@ -933,7 +933,7 @@ impl NonFungibleVaultBlueprint { )?; let mut substate_ref = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); substate_ref.amount = substate_ref @@ -987,7 +987,7 @@ impl NonFungibleVaultBlueprint { )?; let mut vault = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); vault.amount = vault diff --git a/radix-engine/src/object_modules/metadata/package.rs b/radix-engine/src/object_modules/metadata/package.rs index a676ef21d97..5e0b6727f16 100644 --- a/radix-engine/src/object_modules/metadata/package.rs +++ b/radix-engine/src/object_modules/metadata/package.rs @@ -426,7 +426,7 @@ impl MetadataNativePackage { let data = api.key_value_entry_get(handle)?; let substate: Option = scrypto_decode(&data).unwrap(); - Ok(substate.map(|v| v.into_latest())) + Ok(substate.map(|v: MetadataEntryEntryPayload| v.fully_update_into_latest_version())) } pub(crate) fn remove(key: String, api: &mut Y) -> Result diff --git a/radix-engine/src/object_modules/role_assignment/package.rs b/radix-engine/src/object_modules/role_assignment/package.rs index fd5f6a3235c..4c684f669be 100644 --- a/radix-engine/src/object_modules/role_assignment/package.rs +++ b/radix-engine/src/object_modules/role_assignment/package.rs @@ -306,7 +306,9 @@ impl RoleAssignmentNativePackage { api.kernel_read_substate(handle)?.as_typed().unwrap(); api.kernel_close_substate(handle)?; - let owner_role = owner_role_substate.into_payload().into_latest(); + let owner_role = owner_role_substate + .into_payload() + .fully_update_into_latest_version(); let rule = match owner_role.owner_role_entry.updater { OwnerRoleUpdater::None => AccessRule::DenyAll, @@ -466,7 +468,7 @@ impl RoleAssignmentNativePackage { let mut owner_role = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); owner_role.owner_role_entry.rule = rule.clone(); api.field_write_typed( handle, @@ -486,7 +488,7 @@ impl RoleAssignmentNativePackage { let handle = api.actor_open_field(ACTOR_STATE_SELF, 0u8, LockFlags::MUTABLE)?; let mut owner_role = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); owner_role.owner_role_entry.updater = OwnerRoleUpdater::None; api.field_write_typed( handle, @@ -596,7 +598,7 @@ impl RoleAssignmentNativePackage { api.key_value_entry_close(handle)?; - Ok(rule.map(|v| v.into_latest())) + Ok(rule.map(|v| v.fully_update_into_latest_version())) } } @@ -662,7 +664,7 @@ impl RoleAssignmentBottlenoseExtension { )?; let owner_role_entry = api .field_read_typed::(handle)? - .into_latest() + .fully_update_into_latest_version() .owner_role_entry; api.field_close(handle)?; diff --git a/radix-engine/src/object_modules/royalty/package.rs b/radix-engine/src/object_modules/royalty/package.rs index 451704ef091..54a3f8f6099 100644 --- a/radix-engine/src/object_modules/royalty/package.rs +++ b/radix-engine/src/object_modules/royalty/package.rs @@ -426,7 +426,7 @@ impl ComponentRoyaltyBlueprint { let substate = api .field_read_typed::(handle)? - .into_latest(); + .fully_update_into_latest_version(); let mut royalty_vault = substate.royalty_vault; let bucket = royalty_vault.take_all(api)?; api.field_close(handle)?; @@ -457,7 +457,9 @@ impl ComponentRoyaltyBlueprint { .as_typed() .unwrap(); - let component_royalty = component_royalty.into_payload().into_latest(); + let component_royalty = component_royalty + .into_payload() + .fully_update_into_latest_version(); let royalty_charge = { let handle = api.kernel_open_substate_with_default( @@ -481,7 +483,7 @@ impl ComponentRoyaltyBlueprint { api.kernel_close_substate(handle)?; substate .into_value() - .map(|v| v.into_latest()) + .map(|v| v.fully_update_into_latest_version()) .unwrap_or(RoyaltyAmount::Free) }; diff --git a/radix-engine/src/system/checkers/component_royalty_db_checker.rs b/radix-engine/src/system/checkers/component_royalty_db_checker.rs index 05c9edff09f..803ff9d8fc2 100644 --- a/radix-engine/src/system/checkers/component_royalty_db_checker.rs +++ b/radix-engine/src/system/checkers/component_royalty_db_checker.rs @@ -60,7 +60,7 @@ impl ComponentRoyaltyDatabaseChecker { royalty_amount: ComponentRoyaltyMethodAmountEntryPayload, location: ErrorLocation, ) { - let royalty_amount = royalty_amount.into_latest(); + let royalty_amount = royalty_amount.fully_update_into_latest_version(); let max_royalty_in_xrd = Decimal::from_str(MAX_PER_FUNCTION_ROYALTY_IN_XRD).unwrap(); let max_royalty_in_usd = max_royalty_in_xrd / Decimal::from_str(USD_PRICE_IN_XRD).unwrap(); diff --git a/radix-engine/src/system/checkers/package_royalty_db_checker.rs b/radix-engine/src/system/checkers/package_royalty_db_checker.rs index 9ed38e6323d..0a74b752c2b 100644 --- a/radix-engine/src/system/checkers/package_royalty_db_checker.rs +++ b/radix-engine/src/system/checkers/package_royalty_db_checker.rs @@ -95,7 +95,7 @@ where config: PackageBlueprintVersionRoyaltyConfigEntryPayload, location: ErrorLocation, ) { - let royalty_config = config.into_latest(); + let royalty_config = config.fully_update_into_latest_version(); match royalty_config { PackageRoyaltyConfig::Disabled => {} diff --git a/radix-engine/src/system/checkers/resource_db_checker.rs b/radix-engine/src/system/checkers/resource_db_checker.rs index 9661aae6df0..d26f772aee8 100644 --- a/radix-engine/src/system/checkers/resource_db_checker.rs +++ b/radix-engine/src/system/checkers/resource_db_checker.rs @@ -21,7 +21,6 @@ use radix_engine_interface::blueprints::resource::{ }; use sbor::rust::collections::BTreeMap; use sbor::rust::vec::Vec; -use sbor::HasLatestVersion; #[derive(Debug, Default)] pub struct ResourceCounter { @@ -67,7 +66,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { scrypto_decode(value).unwrap(); let address = ResourceAddress::new_or_panic(node_id.0); let tracker = self.resources.entry(address).or_default(); - tracker.expected = Some(total_supply.into_latest()); + tracker.expected = Some(total_supply.fully_update_into_latest_version()); } _ => {} } @@ -81,7 +80,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { let address = ResourceAddress::new_or_panic( info.outer_obj_info.expect().into_node_id().0, ); - let amount = vault_balance.into_latest().amount(); + let amount = vault_balance.fully_update_into_latest_version().amount(); if amount.is_negative() { panic!("Found Fungible Vault negative balance"); @@ -104,7 +103,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { scrypto_decode(value).unwrap(); let address = ResourceAddress::new_or_panic(node_id.0); let tracker = self.resources.entry(address).or_default(); - tracker.expected = Some(total_supply.into_latest()); + tracker.expected = Some(total_supply.fully_update_into_latest_version()); } _ => {} } @@ -119,7 +118,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { info.outer_obj_info.expect().into_node_id().0, ); let tracker = self.resources.entry(address).or_default(); - let vault_balance = vault_balance.into_latest(); + let vault_balance = vault_balance.fully_update_into_latest_version(); tracker.tracking_supply = tracker .tracking_supply .checked_add(vault_balance.amount) @@ -148,7 +147,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { let mut prev = Decimal::MAX; for validator in validator_set - .into_latest() + .fully_update_into_latest_version() .validator_set .validators_by_stake_desc { @@ -162,7 +161,10 @@ impl ApplicationChecker for ResourceDatabaseChecker { ConsensusManagerField::CurrentProposalStatistic => { let stats: ConsensusManagerCurrentProposalStatisticFieldPayload = scrypto_decode(value).unwrap(); - stats_count = stats.into_latest().validator_statistics.len(); + stats_count = stats + .fully_update_into_latest_version() + .validator_statistics + .len(); } _ => {} } diff --git a/radix-engine/src/system/checkers/role_assignment_db_checker.rs b/radix-engine/src/system/checkers/role_assignment_db_checker.rs index bb84a609dab..01ff0b278d6 100644 --- a/radix-engine/src/system/checkers/role_assignment_db_checker.rs +++ b/radix-engine/src/system/checkers/role_assignment_db_checker.rs @@ -141,7 +141,10 @@ impl RoleAssignmentDatabaseChecker { ) where F: FnMut(RoleAssignmentDatabaseCheckerError), { - let owner_rule = owner_role_entry.into_latest().owner_role_entry.rule; + let owner_rule = owner_role_entry + .fully_update_into_latest_version() + .owner_role_entry + .rule; Self::check_access_rule_limits(owner_rule, add_error) } @@ -154,7 +157,7 @@ impl RoleAssignmentDatabaseChecker { F: FnMut(RoleAssignmentDatabaseCheckerError), { let key = key.content; - let value = value.into_latest(); + let value = value.fully_update_into_latest_version(); Self::check_access_rule_limits(value, add_error); Self::check_is_role_key_reserved(&key, add_error); diff --git a/radix-engine/src/system/system.rs b/radix-engine/src/system/system.rs index 57f5321c2d3..d5531ac1c1b 100644 --- a/radix-engine/src/system/system.rs +++ b/radix-engine/src/system/system.rs @@ -357,7 +357,7 @@ where self.api.kernel_close_substate(handle)?; let definition = Rc::new(match substate.into_value() { - Some(definition) => definition.into_latest(), + Some(definition) => definition.fully_update_into_latest_version(), None => { return Err(RuntimeError::SystemError( SystemError::BlueprintDoesNotExist(canonical_bp_id), diff --git a/radix-engine/src/system/system_db_reader.rs b/radix-engine/src/system/system_db_reader.rs index d8d156c38cc..d21ae306d22 100644 --- a/radix-engine/src/system/system_db_reader.rs +++ b/radix-engine/src/system/system_db_reader.rs @@ -1,3 +1,4 @@ +use crate::internal_prelude::*; use radix_common::data::scrypto::ScryptoDecode; use radix_common::prelude::{ scrypto_decode, scrypto_encode, ScryptoCustomExtension, ScryptoEncode, ScryptoValue, @@ -17,9 +18,7 @@ use radix_substate_store_interface::{ db_key_mapper::{DatabaseKeyMapper, MappedSubstateDatabase, SpreadPrefixKeyMapper}, interface::SubstateDatabase, }; -use sbor::rust::prelude::*; -use sbor::LocalTypeId; -use sbor::{validate_payload_against_schema, HasLatestVersion, LocatedValidationError}; +use sbor::{validate_payload_against_schema, LocalTypeId, LocatedValidationError}; use crate::blueprints::package::PackageBlueprintVersionDefinitionEntrySubstate; use crate::internal_prelude::{IndexEntrySubstate, SortedIndexEntrySubstate}; @@ -161,7 +160,10 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { blueprints.insert( bp_version_key, - blueprint_definition.into_value().unwrap().into_latest(), + blueprint_definition + .into_value() + .unwrap() + .fully_update_into_latest_version(), ); } @@ -538,7 +540,7 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { .at_offset(PACKAGE_BLUEPRINTS_PARTITION_OFFSET) .unwrap(), &SubstateKey::Map(scrypto_encode(&bp_version_key).unwrap()), - ).ok_or_else(|| SystemReaderError::BlueprintDoesNotExist)?.into_value().unwrap().into_latest()); + ).ok_or_else(|| SystemReaderError::BlueprintDoesNotExist)?.into_value().unwrap().fully_update_into_latest_version()); self.blueprint_cache .borrow_mut() @@ -866,7 +868,10 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { ) .ok_or_else(|| SystemReaderError::BlueprintDoesNotExist)?; - Ok(definition.into_value().unwrap().into_latest()) + Ok(definition + .into_value() + .unwrap() + .fully_update_into_latest_version()) } pub fn validate_payload<'b>( diff --git a/radix-engine/src/system/system_modules/auth/authorization.rs b/radix-engine/src/system/system_modules/auth/authorization.rs index 6d6022fc72f..64882992106 100644 --- a/radix-engine/src/system/system_modules/auth/authorization.rs +++ b/radix-engine/src/system/system_modules/auth/authorization.rs @@ -353,7 +353,7 @@ impl Authorization { api.kernel_close_substate(handle)?; match substate.into_value() { - Some(access_rule) => access_rule.into_latest(), + Some(access_rule) => access_rule.fully_update_into_latest_version(), None => { let handle = api.kernel_open_substate( role_assignment_of.as_node_id(), @@ -370,7 +370,7 @@ impl Authorization { api.kernel_close_substate(handle)?; owner_role_substate .into_payload() - .into_latest() + .fully_update_into_latest_version() .owner_role_entry .rule } diff --git a/radix-engine/src/system/system_substates.rs b/radix-engine/src/system/system_substates.rs index 8c9f43cb454..9273b51a82e 100644 --- a/radix-engine/src/system/system_substates.rs +++ b/radix-engine/src/system/system_substates.rs @@ -1,17 +1,15 @@ use crate::internal_prelude::*; #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -#[sbor(categorize_types = "")] pub struct FieldSubstateV1 { pub payload: V, pub lock_status: LockStatus, } // Note - we manually version these instead of using the defined_versioned! macro, -// to avoid FieldSubstate implementing HasLatestVersion and inheriting +// to avoid FieldSubstate implementing UpgradableVersioned and inheriting // potentially confusing methods #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -#[sbor(categorize_types = "")] pub enum FieldSubstate { V1(FieldSubstateV1), } @@ -71,17 +69,15 @@ pub enum LockStatus { } #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -#[sbor(categorize_types = "")] pub struct KeyValueEntrySubstateV1 { pub value: Option, pub lock_status: LockStatus, } // Note - we manually version these instead of using the defined_versioned! macro, -// to avoid KeyValueEntrySubstate implementing HasLatestVersion and inheriting +// to avoid KeyValueEntrySubstate implementing UpgradableVersioned and inheriting // potentially confusing methods #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -#[sbor(categorize_types = "")] pub enum KeyValueEntrySubstate { V1(KeyValueEntrySubstateV1), } @@ -155,10 +151,9 @@ impl Default for KeyValueEntrySubstate { pub type IndexEntrySubstateV1 = V; // Note - we manually version these instead of using the defined_versioned! macro, -// to avoid IndexEntrySubstate implementing HasLatestVersion and inheriting +// to avoid IndexEntrySubstate implementing UpgradableVersioned and inheriting // potentially confusing methods #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -#[sbor(categorize_types = "")] pub enum IndexEntrySubstate { V1(IndexEntrySubstateV1), } @@ -184,10 +179,9 @@ impl IndexEntrySubstate { pub type SortedIndexEntrySubstateV1 = V; // Note - we manually version these instead of using the defined_versioned! macro, -// to avoid SortedIndexEntrySubstate implementing HasLatestVersion and inheriting +// to avoid SortedIndexEntrySubstate implementing UpgradableVersioned and inheriting // potentially confusing methods #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -#[sbor(categorize_types = "")] pub enum SortedIndexEntrySubstate { V1(SortedIndexEntrySubstateV1), } diff --git a/radix-engine/src/transaction/state_update_summary.rs b/radix-engine/src/transaction/state_update_summary.rs index 9cc44af0726..071ba2c67bf 100644 --- a/radix-engine/src/transaction/state_update_summary.rs +++ b/radix-engine/src/transaction/state_update_summary.rs @@ -194,7 +194,7 @@ impl<'a, S: SubstateDatabase> BalanceAccounter<'a, S> { MAIN_BASE_PARTITION, &FungibleVaultField::Balance.into(), ) - .map(|new_substate| new_substate.into_payload().into_latest().amount()) + .map(|new_substate| new_substate.into_payload().fully_update_into_latest_version().amount()) .map(|new_balance| { let old_balance = self .system_reader @@ -203,7 +203,7 @@ impl<'a, S: SubstateDatabase> BalanceAccounter<'a, S> { MAIN_BASE_PARTITION, &FungibleVaultField::Balance.into(), ) - .map(|old_balance| old_balance.into_payload().into_latest().amount()) + .map(|old_balance| old_balance.into_payload().fully_update_into_latest_version().amount()) .unwrap_or(Decimal::ZERO); // TODO: Handle potential Decimal arithmetic operation (safe_sub) errors instead of panicking. diff --git a/radix-engine/src/transaction/transaction_executor.rs b/radix-engine/src/transaction/transaction_executor.rs index 2fd73410942..ea7862e9895 100644 --- a/radix-engine/src/transaction/transaction_executor.rs +++ b/radix-engine/src/transaction/transaction_executor.rs @@ -580,7 +580,12 @@ where Some(x) => { let substate: FieldSubstate = x.as_typed().unwrap(); - Some(substate.into_payload().into_latest().epoch) + Some( + substate + .into_payload() + .fully_update_into_latest_version() + .epoch, + ) } None => None, } @@ -851,7 +856,7 @@ where .as_typed::() .unwrap() .into_payload() - .into_latest(); + .fully_update_into_latest_version(); vault_balance.put(LiquidFungibleResource::new(amount)); let updated_substate_content = FungibleVaultBalanceFieldPayload::from_content_source(vault_balance) @@ -910,7 +915,7 @@ where .as_typed::() .unwrap() .into_payload() - .into_latest(); + .fully_update_into_latest_version(); vault_balance.put(locked); let updated_substate_content = FungibleVaultBalanceFieldPayload::from_content_source(vault_balance) @@ -984,7 +989,10 @@ where .unwrap() .as_typed() .unwrap(); - let current_leader = substate.into_payload().into_latest().current_leader; + let current_leader = substate + .into_payload() + .fully_update_into_latest_version() + .current_leader; // Update validator rewards let substate: FieldSubstate = track @@ -997,7 +1005,7 @@ where .as_typed() .unwrap(); - let mut rewards = substate.into_payload().into_latest(); + let mut rewards = substate.into_payload().fully_update_into_latest_version(); if let Some(current_leader) = current_leader { let entry = rewards.proposer_rewards.entry(current_leader).or_default(); @@ -1031,7 +1039,7 @@ where .as_typed::() .unwrap() .into_payload() - .into_latest(); + .fully_update_into_latest_version(); vault_balance.put(collected_fees.take_by_amount(total_amount).unwrap()); let updated_substate_content = FungibleVaultBalanceFieldPayload::from_content_source(vault_balance) diff --git a/radix-engine/src/transaction/transaction_receipt.rs b/radix-engine/src/transaction/transaction_receipt.rs index 2c27af1baad..86662a5edc1 100644 --- a/radix-engine/src/transaction/transaction_receipt.rs +++ b/radix-engine/src/transaction/transaction_receipt.rs @@ -21,7 +21,7 @@ define_single_versioned! { /// receipt versions, allowing us to release a wallet ahead-of-time which is forward /// compatible with a new version of the engine (and so a new transaction receipt). #[derive(Clone, ScryptoSbor)] - pub enum VersionedTransactionReceipt => TransactionReceipt = TransactionReceiptV1 + pub VersionedTransactionReceipt(TransactionReceiptVersions) => TransactionReceipt = TransactionReceiptV1 } #[derive(Clone, ScryptoSbor, PartialEq, Eq)] diff --git a/radix-engine/src/updates/state_updates.rs b/radix-engine/src/updates/state_updates.rs index 19ec07030fa..f3be2a0a3d5 100644 --- a/radix-engine/src/updates/state_updates.rs +++ b/radix-engine/src/updates/state_updates.rs @@ -23,7 +23,6 @@ use radix_engine_interface::prelude::*; use radix_engine_interface::types::CollectionDescriptor; use radix_rust::indexmap; use radix_substate_store_interface::interface::*; -use sbor::HasLatestVersion; use sbor::{generate_full_schema, TypeAggregator}; /// A quick macro for encoding and unwrapping. @@ -75,11 +74,11 @@ pub fn generate_seconds_precision_timestamp_state_updates( .to_vec(); let code_hash = CodeHash::from_hash(hash(&original_code)); - let versioned_code = VersionedPackageCodeOriginalCode::V1(PackageCodeOriginalCodeV1 { + let code_substate = PackageCodeOriginalCodeV1 { code: original_code, - }); - let code_payload = versioned_code.into_payload(); - let code_substate = code_payload.into_locked_substate(); + } + .into_versioned() + .into_locked_substate(); let vm_type_substate = PackageCodeVmTypeV1 { vm_type: VmType::Native, } @@ -129,7 +128,7 @@ pub fn generate_seconds_precision_timestamp_state_updates( .unwrap() .unwrap(); - let mut definition = versioned_definition.into_latest(); + let mut definition = versioned_definition.fully_update_into_latest_version(); let export = definition .function_exports @@ -162,7 +161,9 @@ pub fn generate_seconds_precision_timestamp_state_updates( )); scrypto_encode( - &VersionedPackageBlueprintVersionDefinition::V1(definition).into_locked_substate(), + &PackageBlueprintVersionDefinitionVersions::V1(definition) + .into_versioned() + .into_locked_substate(), ) .unwrap() }; @@ -256,15 +257,16 @@ pub fn generate_pool_math_precision_fix_state_updates(db: & let new_code_hash = CodeHash::from_hash(hash(&new_code)); // New code substate created from the new code - let new_code_substate = - VersionedPackageCodeOriginalCode::V1(PackageCodeOriginalCodeV1 { code: new_code }) - .into_payload() - .into_locked_substate(); + let new_code_substate = PackageCodeOriginalCodeV1 { code: new_code } + .into_versioned() + .into_payload() + .into_locked_substate(); // New VM substate, which we will map the new code hash to. - let new_vm_type_substate = VersionedPackageCodeVmType::V1(PackageCodeVmTypeV1 { + let new_vm_type_substate = PackageCodeVmTypeV1 { vm_type: VmType::Native, - }) + } + .into_versioned() .into_payload() .into_locked_substate(); @@ -292,7 +294,7 @@ pub fn generate_pool_math_precision_fix_state_updates(db: & ) .unwrap() .unwrap(); - let mut blueprint_definition = versioned_definition.into_latest(); + let mut blueprint_definition = versioned_definition.fully_update_into_latest_version(); for (_, export) in blueprint_definition.function_exports.iter_mut() { export.code_hash = new_code_hash @@ -300,7 +302,8 @@ pub fn generate_pool_math_precision_fix_state_updates(db: & ( blueprint_version_key, - VersionedPackageBlueprintVersionDefinition::V1(blueprint_definition) + PackageBlueprintVersionDefinitionVersions::V1(blueprint_definition) + .into_versioned() .into_payload() .into_locked_substate(), ) @@ -380,7 +383,7 @@ pub fn generate_validator_creation_fee_fix_state_updates( ) .unwrap(); - let mut config = versioned_config.into_latest(); + let mut config = versioned_config.fully_update_into_latest_version(); config.config.validator_creation_usd_cost = Decimal::from(100); let updated_substate = config.into_locked_substate(); @@ -417,9 +420,10 @@ pub fn generate_owner_role_getter_state_updates(db: &S) -> .to_vec(); let code_hash = CodeHash::from_hash(hash(&original_code)); - let code_substate = VersionedPackageCodeOriginalCode::V1(PackageCodeOriginalCodeV1 { + let code_substate = PackageCodeOriginalCodeV1 { code: original_code, - }) + } + .into_versioned() .into_locked_substate(); let vm_type_substate = PackageCodeVmTypeV1 { vm_type: VmType::Native, @@ -447,7 +451,7 @@ pub fn generate_owner_role_getter_state_updates(db: &S) -> ) .unwrap() .unwrap() - .into_latest(); + .fully_update_into_latest_version(); for (function_name, added_function) in added_functions.into_iter() { let TypeRef::Static(input_local_id) = added_function.input else { @@ -598,9 +602,9 @@ pub fn generate_account_bottlenose_extension_state_updates( let original_code = ACCOUNT_BOTTLENOSE_EXTENSION_CODE_ID.to_be_bytes().to_vec(); let code_hash = CodeHash::from_hash(hash(&original_code)); - let code_substate = VersionedPackageCodeOriginalCode::V1(PackageCodeOriginalCodeV1 { + let code_substate = PackageCodeOriginalCodeV1 { code: original_code, - }) + } .into_locked_substate(); let vm_type_substate = PackageCodeVmTypeV1 { vm_type: VmType::Native, @@ -624,7 +628,7 @@ pub fn generate_account_bottlenose_extension_state_updates( ) .unwrap() .unwrap() - .into_latest(); + .fully_update_into_latest_version(); for function_name in [ ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT, diff --git a/radix-engine/src/vm/vm.rs b/radix-engine/src/vm/vm.rs index 72abede7a9d..adb1bd2bb40 100644 --- a/radix-engine/src/vm/vm.rs +++ b/radix-engine/src/vm/vm.rs @@ -146,7 +146,7 @@ impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<' .vm_version .clone(); - let output = match vm_type.into_latest().vm_type { + let output = match vm_type.fully_update_into_latest_version().vm_type { VmType::Native => { let original_code = { let handle = api.kernel_open_substate_with_default( @@ -171,11 +171,10 @@ impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<' .unwrap_or_else(|| panic!("Original code not found: {:?}", export)) }; - let mut vm_instance = api - .kernel_get_system() - .callback - .native_vm - .create_instance(address, &original_code.into_latest().code)?; + let mut vm_instance = api.kernel_get_system().callback.native_vm.create_instance( + address, + &original_code.fully_update_into_latest_version().code, + )?; let output = { vm_instance.invoke(export.export_name.as_str(), input, api, &vm_api)? }; @@ -203,7 +202,7 @@ impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<' instrumented_code .into_value() .unwrap_or_else(|| panic!("Instrumented code not found: {:?}", export)) - .into_latest() + .fully_update_into_latest_version() }; let mut scrypto_vm_instance = { diff --git a/radix-engine/src/vm/wasm/prepare.rs b/radix-engine/src/vm/wasm/prepare.rs index c5e5f81871d..5ffbe2a2932 100644 --- a/radix-engine/src/vm/wasm/prepare.rs +++ b/radix-engine/src/vm/wasm/prepare.rs @@ -1605,11 +1605,11 @@ mod tests { schema: BlueprintSchemaInit { generics: vec![], - schema: VersionedScryptoSchema::V1(SchemaV1 { + schema: SchemaV1 { type_kinds: vec![], type_metadata: vec![], type_validations: vec![], - }), + }.into_versioned(), state: BlueprintStateSchemaInit { fields: vec![FieldSchema::static_field(LocalTypeId::WellKnown(UNIT_TYPE))], collections: vec![], diff --git a/radix-sbor-derive/src/scrypto_encode.rs b/radix-sbor-derive/src/scrypto_encode.rs index dcdcebf1f55..a1f1f0699ed 100644 --- a/radix-sbor-derive/src/scrypto_encode.rs +++ b/radix-sbor-derive/src/scrypto_encode.rs @@ -59,8 +59,7 @@ mod tests { E: ::sbor::Encoder > ::sbor::Encode for MyEnum where - T: ::sbor::Encode, - T: ::sbor::Categorize + T: ::sbor::Encode { #[inline] fn encode_value_kind(&self, encoder: &mut E) -> Result<(), ::sbor::EncodeError> { diff --git a/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs b/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs index d40a24734eb..da58cc5e300 100644 --- a/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs +++ b/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs @@ -207,7 +207,7 @@ impl CommittableSubstateDatabase for RocksDBWithMerkleTreeSubstateStore { batch.put_cf( self.cf(MERKLE_NODES_CF), encode_key(&key), - scrypto_encode(&VersionedTreeNode::from_latest(node)).unwrap(), + scrypto_encode(&VersionedTreeNode::from_latest_version(node)).unwrap(), ); } if !self.pruning_enabled { @@ -255,7 +255,7 @@ impl CommittableSubstateDatabase for RocksDBWithMerkleTreeSubstateStore { .delete_cf(self.cf(MERKLE_NODES_CF), encode_key(&node_key)) .unwrap(); let value: VersionedTreeNode = scrypto_decode(&bytes).unwrap(); - match value.into_latest() { + match value.fully_update_into_latest_version() { TreeNodeV1::Internal(x) => { for child in x.children { queue.push_back( @@ -301,7 +301,7 @@ impl ReadableTreeStore for RocksDBWithMerkleTreeSubstateStore { .get_cf(self.cf(MERKLE_NODES_CF), &encode_key(key)) .unwrap() .map(|bytes| scrypto_decode::(&bytes).unwrap()) - .map(|versioned| versioned.into_latest()) + .map(|versioned| versioned.fully_update_into_latest_version()) } } diff --git a/radix-substate-store-impls/src/state_tree/tree_store.rs b/radix-substate-store-impls/src/state_tree/tree_store.rs index bfb32b14b7d..72360b95a8a 100644 --- a/radix-substate-store-impls/src/state_tree/tree_store.rs +++ b/radix-substate-store-impls/src/state_tree/tree_store.rs @@ -17,7 +17,7 @@ use sbor::*; define_single_versioned! { #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] - pub enum VersionedTreeNode => TreeNode = TreeNodeV1 + pub VersionedTreeNode(TreeNodeVersions) => TreeNode = TreeNodeV1 } /// A physical tree node, to be used in the storage. diff --git a/radix-substate-store-queries/src/query/traverse.rs b/radix-substate-store-queries/src/query/traverse.rs index 8d8c8ca0ac8..22abe46cb8f 100644 --- a/radix-substate-store-queries/src/query/traverse.rs +++ b/radix-substate-store-queries/src/query/traverse.rs @@ -135,7 +135,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' ) .expect("Broken database"); - let liquid = liquid.into_latest(); + let liquid = liquid.fully_update_into_latest_version(); visitor.visit_fungible_vault( node_id, @@ -154,7 +154,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' ) .expect("Broken database"); - let liquid = liquid.into_latest(); + let liquid = liquid.fully_update_into_latest_version(); visitor.visit_non_fungible_vault( node_id, @@ -192,7 +192,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' 0u8, ) .expect("Broken database") - .into_latest(); + .fully_update_into_latest_version(); Self::traverse_recursive( system_db_reader, visitor, diff --git a/radix-transaction-scenarios/src/executor.rs b/radix-transaction-scenarios/src/executor.rs index 152b21f9749..c71b5d1dcb5 100644 --- a/radix-transaction-scenarios/src/executor.rs +++ b/radix-transaction-scenarios/src/executor.rs @@ -308,7 +308,7 @@ where .map_err(|_| ScenarioExecutorError::FailedToGetEpoch)? .as_typed::() .unwrap() - .into_latest() + .fully_update_into_latest_version() .epoch; let mut scenario = scenario_builder(ScenarioCore::new( self.network_definition.clone(), diff --git a/sbor-derive-common/src/categorize.rs b/sbor-derive-common/src/categorize.rs index 5526cda4ed9..5a97ef29a61 100644 --- a/sbor-derive-common/src/categorize.rs +++ b/sbor-derive-common/src/categorize.rs @@ -18,12 +18,15 @@ pub fn handle_categorize( trace!("handle_categorize() starts"); let parsed: DeriveInput = parse2(input)?; - let is_transparent = is_transparent(&parsed.attrs)?; - let output = if is_transparent { - handle_transparent_categorize(parsed, context_custom_value_kind)? - } else { - handle_normal_categorize(parsed, context_custom_value_kind)? + let output = match get_derive_strategy(&parsed.attrs)? { + DeriveStrategy::Normal => handle_normal_categorize(parsed, context_custom_value_kind)?, + DeriveStrategy::Transparent => { + handle_transparent_categorize(parsed, context_custom_value_kind)? + } + DeriveStrategy::DeriveAs { + as_type, as_ref, .. + } => handle_categorize_as(parsed, context_custom_value_kind, &as_type, &as_ref)?, }; #[cfg(feature = "trace")] @@ -45,14 +48,14 @@ fn handle_normal_categorize( .. } = parsed; let (impl_generics, ty_generics, where_clause, sbor_cvk) = - build_custom_categorize_generic(&generics, &attrs, context_custom_value_kind, false)?; + build_categorize_generics(&generics, &attrs, context_custom_value_kind)?; let output = match data { Data::Struct(s) => { let FieldsData { unskipped_field_names, .. - } = process_fields_for_categorize(&s.fields)?; + } = process_fields(&s.fields)?; let field_count = unskipped_field_names.len(); quote! { impl #impl_generics ::sbor::Categorize <#sbor_cvk> for #ident #ty_generics #where_clause { @@ -82,7 +85,7 @@ fn handle_normal_categorize( unskipped_field_count, empty_fields_unpacking, .. - } = process_fields_for_encode(&v.fields)?; + } = process_fields(&v.fields)?; Ok(match discriminator { VariantDiscriminator::Expr(discriminator) => { @@ -155,98 +158,135 @@ fn handle_transparent_categorize( parsed: DeriveInput, context_custom_value_kind: Option<&'static str>, ) -> Result { - let DeriveInput { - attrs, - ident, - data, - generics, - .. - } = parsed; - let (impl_generics, ty_generics, where_clause, sbor_cvk) = - build_custom_categorize_generic(&generics, &attrs, context_custom_value_kind, true)?; - let output = match data { + let DeriveInput { data, .. } = &parsed; + match data { Data::Struct(s) => { let FieldsData { unskipped_field_names, unskipped_field_types, .. - } = process_fields_for_categorize(&s.fields)?; + } = process_fields(&s.fields)?; if unskipped_field_types.len() != 1 { return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); } let field_type = &unskipped_field_types[0]; let field_name = &unskipped_field_names[0]; - let categorize_impl = quote! { - impl #impl_generics ::sbor::Categorize <#sbor_cvk> for #ident #ty_generics #where_clause { - #[inline] - fn value_kind() -> ::sbor::ValueKind <#sbor_cvk> { - <#field_type as ::sbor::Categorize::<#sbor_cvk>>::value_kind() - } - } - }; - - // Dependent SborTuple impl: - // We'd like to just say "where #field_type: ::sbor::SborTuple" - but this doesn't work because of - // https://github.com/rust-lang/rust/issues/48214#issuecomment-1374378038 - // Instead we can use that T: SborTuple => &T: SborTuple to apply a constraint on &T: SborTuple instead - - // Rebuild the generic parameters without requiring categorize on generic parameters - let (impl_generics, ty_generics, where_clause, sbor_cvk) = - build_custom_categorize_generic( - &generics, - &attrs, - context_custom_value_kind, - false, - )?; - - let tuple_where_clause = add_where_predicate( - where_clause, - parse_quote!(for<'b_> &'b_ #field_type: ::sbor::SborTuple <#sbor_cvk>), - ); - - let dependent_sbor_tuple_impl = quote! { - impl #impl_generics ::sbor::SborTuple <#sbor_cvk> for #ident #ty_generics #tuple_where_clause { - fn get_length(&self) -> usize { - <&#field_type as ::sbor::SborTuple <#sbor_cvk>>::get_length(&&self.#field_name) - } - } - }; - - let enum_where_clause = add_where_predicate( - where_clause, - parse_quote!(for<'b_> &'b_ #field_type: ::sbor::SborEnum <#sbor_cvk>), - ); + handle_categorize_as( + parsed, + context_custom_value_kind, + field_type, + "e! { &self.#field_name } + ) + } + Data::Enum(_) => { + Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")) + } + Data::Union(_) => { + Err(Error::new(Span::call_site(), "Union is not supported!")) + } + } +} - let dependent_sbor_enum_impl = quote! { - impl #impl_generics ::sbor::SborEnum <#sbor_cvk> for #ident #ty_generics #enum_where_clause { - fn get_discriminator(&self) -> u8 { - <&#field_type as ::sbor::SborEnum <#sbor_cvk>>::get_discriminator(&&self.#field_name) - } +/// This requires that Categorize is implemented for the "as" type. +/// This ensure that e.g. attempting to derive Categorize on `TransparentStruct(sbor::Value)` fails. +/// +/// If we have ` TransparentStruct(T)` then the user can use `#[sbor(categorize_as = "T")]` +/// to make the `Categorize` implementation on `TransparentStruct` conditional on `T: Categorize`. +/// +/// It also implements SborTuple / SborEnum, but only conditionally - i.e. only if +/// they're implemented for the "as" type. +fn handle_categorize_as( + parsed: DeriveInput, + context_custom_value_kind: Option<&'static str>, + as_type: &Type, + as_ref_code: &TokenStream, +) -> Result { + let DeriveInput { + attrs, + ident, + generics, + .. + } = parsed; + let (impl_generics, ty_generics, where_clause, sbor_cvk) = + build_categorize_generics(&generics, &attrs, context_custom_value_kind)?; - fn get_length(&self) -> usize { - <&#field_type as ::sbor::SborEnum <#sbor_cvk>>::get_length(&&self.#field_name) - } - } - }; + // First - Explict impl of Categorize - quote! { - #categorize_impl + let categorize_bound_type = + get_type_requiring_categorize_bound_for_categorize_as(as_type, &attrs, &generics)?; - #dependent_sbor_tuple_impl + let categorize_where_clause = if let Some(categorize_bound_type) = categorize_bound_type { + Some(add_where_predicate( + where_clause, + parse_quote!(#categorize_bound_type: ::sbor::Categorize <#sbor_cvk>), + )) + } else { + where_clause.cloned() + }; - #dependent_sbor_enum_impl + let categorize_impl = quote! { + impl #impl_generics ::sbor::Categorize <#sbor_cvk> for #ident #ty_generics #categorize_where_clause { + #[inline] + fn value_kind() -> ::sbor::ValueKind <#sbor_cvk> { + <#as_type as ::sbor::Categorize::<#sbor_cvk>>::value_kind() } } - Data::Enum(_) => { - return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); + }; + + // Dependent implementations of X = SborTuple / SborEnum. + // + // We'd like to implement X for the type if and only if it is implemented for `as_type`. + // + // We'd like to just say "where #as_type: X" - but this doesn't work because of + // https://github.com/rust-lang/rust/issues/48214#issuecomment-1374378038 + // + // Basically - these bounds are either trivially true or false, so the compiler "helpfully" reports a + // compile error to the user, because such a bound is clearly a mistake (or maybe because the compiler + // needs some work to actually support them!) + // + // Instead we can use that for each of X, if T: X then we have implemented X for &T... + // And it turns out that the constrant &T: X is not trivial enough to cause the compiler to complain. + // + // So we can just cheat and use the implementation from `&T` instead! + + let tuple_where_clause = add_where_predicate( + where_clause, + parse_quote!(for<'b_> &'b_ #as_type: ::sbor::SborTuple <#sbor_cvk>), + ); + + let dependent_sbor_tuple_impl = quote! { + impl #impl_generics ::sbor::SborTuple <#sbor_cvk> for #ident #ty_generics #tuple_where_clause { + fn get_length(&self) -> usize { + <&#as_type as ::sbor::SborTuple <#sbor_cvk>>::get_length(&#as_ref_code) + } } - Data::Union(_) => { - return Err(Error::new(Span::call_site(), "Union is not supported!")); + }; + + let enum_where_clause = add_where_predicate( + where_clause, + parse_quote!(for<'b_> &'b_ #as_type: ::sbor::SborEnum <#sbor_cvk>), + ); + + let dependent_sbor_enum_impl = quote! { + impl #impl_generics ::sbor::SborEnum <#sbor_cvk> for #ident #ty_generics #enum_where_clause { + fn get_discriminator(&self) -> u8 { + <&#as_type as ::sbor::SborEnum <#sbor_cvk>>::get_discriminator(&#as_ref_code) + } + + fn get_length(&self) -> usize { + <&#as_type as ::sbor::SborEnum <#sbor_cvk>>::get_length(&#as_ref_code) + } } }; - Ok(output) + Ok(quote! { + #categorize_impl + + #dependent_sbor_tuple_impl + + #dependent_sbor_enum_impl + }) } #[cfg(test)] @@ -294,7 +334,8 @@ mod tests { assert_code_eq( output, quote! { - impl ::sbor::Categorize for Test { + impl ::sbor::Categorize for Test + { #[inline] fn value_kind() -> ::sbor::ValueKind { >::value_kind() @@ -305,7 +346,7 @@ mod tests { where for <'b_> &'b_ u32: ::sbor::SborTuple { fn get_length(&self) -> usize { - <&u32 as ::sbor::SborTuple>::get_length(&&self.a) + <&u32 as ::sbor::SborTuple>::get_length(& &self.a) } } @@ -313,11 +354,11 @@ mod tests { where for <'b_> &'b_ u32: ::sbor::SborEnum { fn get_discriminator(&self) -> u8 { - <&u32 as ::sbor::SborEnum>::get_discriminator(&&self.a) + <&u32 as ::sbor::SborEnum>::get_discriminator(& &self.a) } fn get_length(&self) -> usize { - <&u32 as ::sbor::SborEnum>::get_length(&&self.a) + <&u32 as ::sbor::SborEnum>::get_length(& &self.a) } } }, @@ -356,7 +397,9 @@ mod tests { assert_code_eq( output, quote! { - impl , X: ::sbor::CustomValueKind> ::sbor::Categorize for Test { + impl ::sbor::Categorize for Test + where A: ::sbor::Categorize + { #[inline] fn value_kind() -> ::sbor::ValueKind { >::value_kind() @@ -367,7 +410,7 @@ mod tests { where for <'b_> &'b_ A: ::sbor::SborTuple { fn get_length(&self) -> usize { - <&A as ::sbor::SborTuple>::get_length(&&self.a) + <&A as ::sbor::SborTuple>::get_length(& &self.a) } } @@ -375,11 +418,11 @@ mod tests { where for <'b_> &'b_ A: ::sbor::SborEnum { fn get_discriminator(&self) -> u8 { - <&A as ::sbor::SborEnum>::get_discriminator(&&self.a) + <&A as ::sbor::SborEnum>::get_discriminator(& &self.a) } fn get_length(&self) -> usize { - <&A as ::sbor::SborEnum>::get_length(&&self.a) + <&A as ::sbor::SborEnum>::get_length(& &self.a) } } }, diff --git a/sbor-derive-common/src/decode.rs b/sbor-derive-common/src/decode.rs index 360439dda2e..b32da394c08 100644 --- a/sbor-derive-common/src/decode.rs +++ b/sbor-derive-common/src/decode.rs @@ -19,12 +19,17 @@ pub fn handle_decode( trace!("handle_decode() starts"); let parsed: DeriveInput = parse2(input)?; - let is_transparent = is_transparent(&parsed.attrs)?; - let output = if is_transparent { - handle_transparent_decode(parsed, context_custom_value_kind)? - } else { - handle_normal_decode(parsed, context_custom_value_kind)? + let output = match get_derive_strategy(&parsed.attrs)? { + DeriveStrategy::Normal => handle_normal_decode(parsed, context_custom_value_kind)?, + DeriveStrategy::Transparent => { + handle_transparent_decode(parsed, context_custom_value_kind)? + } + DeriveStrategy::DeriveAs { + as_type, + from_value, + .. + } => handle_decode_as(parsed, context_custom_value_kind, &as_type, &from_value)?, }; #[cfg(feature = "trace")] @@ -38,17 +43,9 @@ pub fn handle_transparent_decode( parsed: DeriveInput, context_custom_value_kind: Option<&'static str>, ) -> Result { - let DeriveInput { - attrs, - ident, - data, - generics, - .. - } = parsed; - let (impl_generics, ty_generics, where_clause, custom_value_kind_generic, decoder_generic) = - build_decode_generics(&generics, &attrs, context_custom_value_kind)?; + let DeriveInput { data, .. } = &parsed; - let output = match data { + match data { Data::Struct(s) => { let FieldsData { unskipped_field_names, @@ -56,7 +53,7 @@ pub fn handle_transparent_decode( skipped_field_names, skipped_field_types, .. - } = process_fields_for_decode(&s.fields)?; + } = process_fields(&s.fields)?; if unskipped_field_names.len() != 1 { return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); } @@ -66,52 +63,74 @@ pub fn handle_transparent_decode( let decode_content = match &s.fields { syn::Fields::Named(_) => { quote! { - Ok(Self { - #field_name: inner, + Self { + #field_name: value, #(#skipped_field_names: <#skipped_field_types>::default(),)* - }) + } } } syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { - let mut fields = Vec::::new(); - for f in unnamed { - let ty = &f.ty; - if is_decoding_skipped(f)? { - fields.push(parse_quote! {<#ty>::default()}) + let mut field_values = Vec::::new(); + for field in unnamed { + if is_skipped(field)? { + let field_type = &field.ty; + field_values.push(parse_quote! {<#field_type>::default()}) } else { - fields.push(parse_quote! {inner}) + field_values.push(parse_quote! {value}) } } quote! { - Ok(Self - ( - #(#fields,)* - )) + Self( + #(#field_values,)* + ) } } syn::Fields::Unit => { quote! { - Ok(Self {}) + Self {} } } }; - quote! { - impl #impl_generics ::sbor::Decode <#custom_value_kind_generic, #decoder_generic> for #ident #ty_generics #where_clause { - #[inline] - fn decode_body_with_value_kind(decoder: &mut #decoder_generic, value_kind: ::sbor::ValueKind<#custom_value_kind_generic>) -> Result { - use ::sbor::{self, Decode}; - let inner = <#field_type as ::sbor::Decode<#custom_value_kind_generic, #decoder_generic>>::decode_body_with_value_kind(decoder, value_kind)?; - #decode_content - } - } - } + handle_decode_as( + parsed, + context_custom_value_kind, + field_type, + &decode_content, + ) } Data::Enum(_) => { - return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); + Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")) } Data::Union(_) => { - return Err(Error::new(Span::call_site(), "Union is not supported!")); + Err(Error::new(Span::call_site(), "Union is not supported!")) + } + } +} + +fn handle_decode_as( + parsed: DeriveInput, + context_custom_value_kind: Option<&'static str>, + as_type: &Type, + from_value: &TokenStream, +) -> Result { + let DeriveInput { + attrs, + ident, + generics, + .. + } = parsed; + let (impl_generics, ty_generics, where_clause, custom_value_kind_generic, decoder_generic) = + build_decode_generics(&generics, &attrs, context_custom_value_kind)?; + + let output = quote! { + impl #impl_generics ::sbor::Decode <#custom_value_kind_generic, #decoder_generic> for #ident #ty_generics #where_clause { + #[inline] + fn decode_body_with_value_kind(decoder: &mut #decoder_generic, value_kind: ::sbor::ValueKind<#custom_value_kind_generic>) -> Result { + use ::sbor::{self, Decode}; + let value = <#as_type as ::sbor::Decode<#custom_value_kind_generic, #decoder_generic>>::decode_body_with_value_kind(decoder, value_kind)?; + Ok(#from_value) + } } }; @@ -210,7 +229,7 @@ pub fn decode_fields_content( skipped_field_types, unskipped_field_count, .. - } = process_fields_for_decode(fields)?; + } = process_fields(fields)?; Ok(match fields { syn::Fields::Named(_) => { @@ -226,7 +245,7 @@ pub fn decode_fields_content( let mut fields = Vec::::new(); for f in unnamed { let ty = &f.ty; - if is_decoding_skipped(f)? { + if is_skipped(f)? { fields.push(parse_quote! {<#ty>::default()}) } else { fields.push(parse_quote! {decoder.decode::<#ty>()?}) @@ -294,9 +313,7 @@ mod tests { impl , X: ::sbor::CustomValueKind> ::sbor::Decode for Test where T : ::sbor::Decode, - D : ::sbor::Decode, - T : ::sbor::Categorize, - D : ::sbor::Categorize + D : ::sbor::Decode { #[inline] fn decode_body_with_value_kind(decoder: &mut D0, value_kind: ::sbor::ValueKind) -> Result { diff --git a/sbor-derive-common/src/describe.rs b/sbor-derive-common/src/describe.rs index 622bf647ac6..0ed3bcef488 100644 --- a/sbor-derive-common/src/describe.rs +++ b/sbor-derive-common/src/describe.rs @@ -21,12 +21,17 @@ pub fn handle_describe( let code_hash = get_code_hash_const_array_token_stream(&input); let parsed: DeriveInput = parse2(input)?; - let is_transparent = is_transparent(&parsed.attrs)?; - let output = if is_transparent { - handle_transparent_describe(parsed, code_hash, context_custom_type_kind)? - } else { - handle_normal_describe(parsed, code_hash, context_custom_type_kind)? + let output = match get_derive_strategy(&parsed.attrs)? { + DeriveStrategy::Normal => { + handle_normal_describe(parsed, code_hash, context_custom_type_kind)? + } + DeriveStrategy::Transparent => { + handle_transparent_describe(parsed, code_hash, context_custom_type_kind)? + } + DeriveStrategy::DeriveAs { as_type, .. } => { + handle_describe_as(parsed, context_custom_type_kind, &as_type, code_hash)? + } }; #[cfg(feature = "trace")] @@ -41,22 +46,13 @@ fn handle_transparent_describe( code_hash: TokenStream, context_custom_type_kind: Option<&'static str>, ) -> Result { - let DeriveInput { - attrs, - ident, - data, - generics, - .. - } = parsed; - let (impl_generics, ty_generics, where_clause, _, custom_type_kind_generic) = - build_describe_generics(&generics, &attrs, context_custom_type_kind)?; - - let output = match data { + let DeriveInput { data, .. } = &parsed; + match &data { Data::Struct(s) => { let FieldsData { unskipped_field_types, .. - } = process_fields_for_describe(&s.fields)?; + } = process_fields(&s.fields)?; if unskipped_field_types.len() != 1 { return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); @@ -64,50 +60,89 @@ fn handle_transparent_describe( let field_type = &unskipped_field_types[0]; - let mut type_data_content = quote! { - <#field_type as ::sbor::Describe <#custom_type_kind_generic>>::type_data() - }; - let mut type_id = quote! { - <#field_type as ::sbor::Describe <#custom_type_kind_generic>>::TYPE_ID - }; - - // Replace the type name, unless opted out using the "transparent_name" tag - if !get_sbor_attribute_bool_value(&attrs, "transparent_name")? { - let type_name = get_sbor_attribute_string_value(&attrs, "type_name")? - .unwrap_or(ident.to_string()); - type_data_content = quote! { - use ::sbor::rust::prelude::*; - #type_data_content - .with_name(Some(Cow::Borrowed(#type_name))) - }; - type_id = quote! { - ::sbor::RustTypeId::novel_with_code( - #type_name, - &[#type_id], - &#code_hash - ) - }; - }; + handle_describe_as( + parsed, + context_custom_type_kind, + field_type, + code_hash, + ) + } + Data::Enum(_) => { + Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")) + } + Data::Union(_) => { + Err(Error::new(Span::call_site(), "Union is not supported!")) + } + } +} - quote! { - impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { - const TYPE_ID: ::sbor::RustTypeId = #type_id; +fn handle_describe_as( + parsed: DeriveInput, + context_custom_type_kind: Option<&'static str>, + as_type: &Type, + code_hash: TokenStream, +) -> Result { + let DeriveInput { + attrs, + ident, + generics, + .. + } = &parsed; + let (impl_generics, ty_generics, where_clause, _, custom_type_kind_generic) = + build_describe_generics(&generics, &attrs, context_custom_type_kind)?; - fn type_data() -> ::sbor::TypeData<#custom_type_kind_generic, ::sbor::RustTypeId> { - #type_data_content - } + // Prepare base conditions before any overrides + let mut is_fully_transparent = true; + let mut type_data_content = quote! { + <#as_type as ::sbor::Describe <#custom_type_kind_generic>>::type_data() + }; - fn add_all_dependencies(aggregator: &mut ::sbor::TypeAggregator<#custom_type_kind_generic>) { - <#field_type as ::sbor::Describe <#custom_type_kind_generic>>::add_all_dependencies(aggregator) - } - } - } + // Perform each override (currently there's just one, this could be expanded in future) + let override_type_name = if get_sbor_attribute_bool_value(attrs, "transparent_name")?.value() { + None + } else { + Some(resolve_type_name(ident, attrs)?) + }; + if let Some(new_type_name) = &override_type_name { + validate_type_name(new_type_name)?; + is_fully_transparent = false; + type_data_content = quote! { + #type_data_content + .with_name(Some(Cow::Borrowed(#new_type_name))) } - Data::Enum(_) => { - return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); + } + + // Calculate the type id to use + let transparent_type_id = quote! { + <#as_type as ::sbor::Describe <#custom_type_kind_generic>>::TYPE_ID + }; + + let type_id = if is_fully_transparent { + transparent_type_id + } else { + let novel_type_name = + override_type_name.unwrap_or(LitStr::new(&ident.to_string(), ident.span())); + quote! { + ::sbor::RustTypeId::novel_with_code( + #novel_type_name, + &[#transparent_type_id], + &#code_hash + ) } - Data::Union(_) => { - return Err(Error::new(Span::call_site(), "Union is not supported!")); + }; + + let output = quote! { + impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { + const TYPE_ID: ::sbor::RustTypeId = #type_id; + + fn type_data() -> ::sbor::TypeData<#custom_type_kind_generic, ::sbor::RustTypeId> { + use ::sbor::rust::prelude::*; + #type_data_content + } + + fn add_all_dependencies(aggregator: &mut ::sbor::TypeAggregator<#custom_type_kind_generic>) { + <#as_type as ::sbor::Describe <#custom_type_kind_generic>>::add_all_dependencies(aggregator) + } } }; @@ -125,12 +160,11 @@ fn handle_normal_describe( data, generics, .. - } = parsed; + } = &parsed; let (impl_generics, ty_generics, where_clause, child_types, custom_type_kind_generic) = - build_describe_generics(&generics, &attrs, context_custom_type_kind)?; + build_describe_generics(generics, attrs, context_custom_type_kind)?; - let type_name = - get_sbor_attribute_string_value(&attrs, "type_name")?.unwrap_or(ident.to_string()); + let type_name = resolve_type_name(ident, attrs)?; let type_id = quote! { ::sbor::RustTypeId::novel_with_code( @@ -157,7 +191,7 @@ fn handle_normal_describe( unskipped_field_types, unskipped_field_name_strings, .. - } = process_fields_for_describe(&s.fields)?; + } = process_fields(&s.fields)?; let unique_field_types: Vec<_> = get_unique_types(&unskipped_field_types); quote! { impl #impl_generics ::sbor::Describe <#custom_type_kind_generic> for #ident #ty_generics #where_clause { @@ -182,7 +216,7 @@ fn handle_normal_describe( let FieldsData { unskipped_field_types, .. - } = process_fields_for_describe(&s.fields)?; + } = process_fields(&s.fields)?; let unique_field_types: Vec<_> = get_unique_types(&unskipped_field_types); quote! { @@ -236,7 +270,7 @@ fn handle_normal_describe( unskipped_field_types, unskipped_field_name_strings, .. - } = process_fields_for_describe(&v.fields)?; + } = process_fields(&v.fields)?; all_field_types.extend_from_slice(&unskipped_field_types); @@ -304,6 +338,45 @@ fn handle_normal_describe( Ok(output) } +pub fn validate_type_name(type_name: &LitStr) -> Result<()> { + validate_schema_ident("Sbor type names", &type_name.value()) + .map_err(|error_message| Error::new(type_name.span(), error_message)) +} + +// IMPORTANT: +// For crate dependency regions, this is duplicated from `sbor` +// If you change it here, please change it there as well +fn validate_schema_ident( + ident_category_name: &str, + name: &str, +) -> core::result::Result<(), String> { + if name.len() == 0 { + return Err(format!("{ident_category_name} cannot be empty")); + } + + if name.len() > 100 { + return Err(format!( + "{ident_category_name} cannot be more than 100 characters" + )); + } + + let first_char = name.chars().next().unwrap(); + if !matches!(first_char, 'A'..='Z' | 'a'..='z') { + return Err(format!( + "{ident_category_name} must match [A-Za-z][0-9A-Za-z_]{{0,99}}" + )); + } + + for char in name.chars() { + if !matches!(char, '0'..='9' | 'A'..='Z' | 'a'..='z' | '_') { + return Err(format!( + "{ident_category_name} must match [A-Za-z][0-9A-Za-z_]{{0,99}}" + )); + } + } + Ok(()) +} + #[cfg(test)] mod tests { use proc_macro2::TokenStream; diff --git a/sbor-derive-common/src/encode.rs b/sbor-derive-common/src/encode.rs index 6c1929569a7..1c60ac366fa 100644 --- a/sbor-derive-common/src/encode.rs +++ b/sbor-derive-common/src/encode.rs @@ -37,41 +37,24 @@ pub fn handle_transparent_encode( parsed: DeriveInput, context_custom_value_kind: Option<&'static str>, ) -> Result { - let DeriveInput { - attrs, - ident, - data, - generics, - .. - } = parsed; - let (impl_generics, ty_generics, where_clause, custom_value_kind_generic, encoder_generic) = - build_encode_generics(&generics, &attrs, context_custom_value_kind)?; - - let output = match data { + let output = match &parsed.data { Data::Struct(s) => { let FieldsData { + unskipped_field_types, unskipped_field_names, .. - } = process_fields_for_encode(&s.fields)?; - if unskipped_field_names.len() != 1 { + } = process_fields(&s.fields)?; + if unskipped_field_types.len() != 1 { return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); } + let field_type = &unskipped_field_types[0]; let field_name = &unskipped_field_names[0]; - quote! { - impl #impl_generics ::sbor::Encode <#custom_value_kind_generic, #encoder_generic> for #ident #ty_generics #where_clause { - #[inline] - fn encode_value_kind(&self, encoder: &mut #encoder_generic) -> Result<(), ::sbor::EncodeError> { - use ::sbor::{self, Encode}; - self.#field_name.encode_value_kind(encoder) - } - - #[inline] - fn encode_body(&self, encoder: &mut #encoder_generic) -> Result<(), ::sbor::EncodeError> { - use ::sbor::{self, Encode}; - self.#field_name.encode_body(encoder) - } - } - } + handle_encode_as( + parsed, + context_custom_value_kind, + &field_type, + "e! { &self.#field_name }, + )? } Data::Enum(_) => { return Err(Error::new(Span::call_site(), "The transparent attribute is only supported for structs with a single unskipped field.")); @@ -84,6 +67,45 @@ pub fn handle_transparent_encode( Ok(output) } +pub fn handle_encode_as( + parsed: DeriveInput, + context_custom_value_kind: Option<&'static str>, + as_type: &Type, + as_ref_code: &TokenStream, +) -> Result { + let DeriveInput { + attrs, + ident, + generics, + .. + } = parsed; + let (impl_generics, ty_generics, where_clause, custom_value_kind_generic, encoder_generic) = + build_encode_generics(&generics, &attrs, context_custom_value_kind)?; + + // NOTE: The `: &#as_type` is not strictly needed for the code to compile, + // but it is useful to sanity check that the user has provided the correct implementation. + // If they have not, they should get a nice and clear error message. + let output = quote! { + impl #impl_generics ::sbor::Encode <#custom_value_kind_generic, #encoder_generic> for #ident #ty_generics #where_clause { + #[inline] + fn encode_value_kind(&self, encoder: &mut #encoder_generic) -> Result<(), ::sbor::EncodeError> { + use ::sbor::{self, Encode}; + let as_ref: &#as_type = #as_ref_code; + as_ref.encode_value_kind(encoder) + } + + #[inline] + fn encode_body(&self, encoder: &mut #encoder_generic) -> Result<(), ::sbor::EncodeError> { + use ::sbor::{self, Encode}; + let as_ref: &#as_type = #as_ref_code; + as_ref.encode_body(encoder) + } + } + }; + + Ok(output) +} + pub fn handle_normal_encode( parsed: DeriveInput, context_custom_value_kind: Option<&'static str>, @@ -104,7 +126,7 @@ pub fn handle_normal_encode( unskipped_field_names, unskipped_field_count, .. - } = process_fields_for_encode(&s.fields)?; + } = process_fields(&s.fields)?; quote! { impl #impl_generics ::sbor::Encode <#custom_value_kind_generic, #encoder_generic> for #ident #ty_generics #where_clause { #[inline] @@ -136,7 +158,7 @@ pub fn handle_normal_encode( fields_unpacking, unskipped_unpacked_field_names, .. - } = process_fields_for_encode(&v.fields)?; + } = process_fields(&v.fields)?; Ok(match discriminator { VariantDiscriminator::Expr(discriminator) => { @@ -310,9 +332,7 @@ mod tests { impl , X: ::sbor::CustomValueKind > ::sbor::Encode for Test where T: ::sbor::Encode, - E: ::sbor::Encode, - T: ::sbor::Categorize, - E: ::sbor::Categorize + E: ::sbor::Encode { #[inline] fn encode_value_kind(&self, encoder: &mut E0) -> Result<(), ::sbor::EncodeError> { diff --git a/sbor-derive-common/src/utils.rs b/sbor-derive-common/src/utils.rs index 037f4182f41..1e79c1d7547 100644 --- a/sbor-derive-common/src/utils.rs +++ b/sbor-derive-common/src/utils.rs @@ -48,22 +48,22 @@ pub enum AttributeValue { } impl AttributeValue { - fn as_string(&self) -> Option { + fn as_string(&self) -> Option { match self { - AttributeValue::Lit(Lit::Str(str)) => Some(str.value()), + AttributeValue::Lit(Lit::Str(str)) => Some(str.clone()), _ => None, } } - fn as_bool(&self) -> Option { + fn as_bool(&self) -> Option { match self { - AttributeValue::None(_) => Some(true), + AttributeValue::None(_) => Some(LitBool::new(true, self.span())), AttributeValue::Lit(Lit::Str(str)) => match str.value().as_str() { - "true" => Some(true), - "false" => Some(false), + "true" => Some(LitBool::new(true, self.span())), + "false" => Some(LitBool::new(false, self.span())), _ => None, }, - AttributeValue::Lit(Lit::Bool(bool)) => Some(bool.value()), + AttributeValue::Lit(Lit::Bool(bool)) => Some(bool.clone()), _ => None, } } @@ -78,21 +78,21 @@ impl AttributeValue { } trait AttributeMap { - fn get_bool_value(&self, name: &str) -> Result; - fn get_string_value(&self, name: &str) -> Result>; + fn get_bool_value(&self, name: &str) -> Result; + fn get_string_value(&self, name: &str) -> Result>; } impl AttributeMap for BTreeMap { - fn get_bool_value(&self, name: &str) -> Result { + fn get_bool_value(&self, name: &str) -> Result { let Some(value) = self.get(name) else { - return Ok(false); + return Ok(LitBool::new(false, Span::call_site())); }; value .as_bool() .ok_or_else(|| Error::new(value.span(), format!("Expected bool attribute"))) } - fn get_string_value(&self, name: &str) -> Result> { + fn get_string_value(&self, name: &str) -> Result> { let Some(value) = self.get(name) else { return Ok(None); }; @@ -219,11 +219,11 @@ pub fn get_variant_discriminator_mapping( } let use_repr_discriminators = - get_sbor_attribute_bool_value(enum_attributes, "use_repr_discriminators")?; + get_sbor_attribute_bool_value(enum_attributes, "use_repr_discriminators")?.value(); let variant_ids: Vec = variants.iter() .map(|variant| -> Result { let mut variant_attributes = extract_typed_attributes(&variant.attrs, "sbor")?; - if let Some(_) = variant_attributes.remove("ignore_as_unreachable") { + if let Some(_) = variant_attributes.remove("unreachable") { return Ok(VariantValue::IgnoreAsUnreachable); } if let Some(attribute) = variant_attributes.remove("discriminator") { @@ -330,43 +330,104 @@ fn parse_u8_from_literal(literal: &Lit) -> Option { pub fn get_sbor_attribute_string_value( attributes: &[Attribute], attribute_name: &str, -) -> Result> { +) -> Result> { extract_sbor_typed_attributes(attributes)?.get_string_value(attribute_name) } pub fn get_sbor_attribute_bool_value( attributes: &[Attribute], attribute_name: &str, -) -> Result { +) -> Result { extract_sbor_typed_attributes(attributes)?.get_bool_value(attribute_name) } -pub fn is_categorize_skipped(f: &Field) -> Result { +pub fn is_skipped(f: &Field) -> Result { let attributes = extract_sbor_typed_attributes(&f.attrs)?; - Ok(attributes.get_bool_value("skip")? || attributes.get_bool_value("skip_categorize")?) + Ok(attributes.get_bool_value("skip")?.value()) } -pub fn is_decoding_skipped(f: &Field) -> Result { - let attributes = extract_sbor_typed_attributes(&f.attrs)?; - Ok(attributes.get_bool_value("skip")? || attributes.get_bool_value("skip_decode")?) +pub enum DeriveStrategy { + Normal, + Transparent, + DeriveAs { + as_type: Type, + as_ref: TokenStream, + from_value: TokenStream, + }, } -pub fn is_encoding_skipped(f: &Field) -> Result { - let attributes = extract_sbor_typed_attributes(&f.attrs)?; - Ok(attributes.get_bool_value("skip")? || attributes.get_bool_value("skip_encode")?) +pub fn get_derive_strategy(attributes: &[Attribute]) -> Result { + let attributes = extract_sbor_typed_attributes(attributes)?; + let transparent_flag = attributes.get_bool_value("transparent")?; + let as_type = attributes.get_string_value("as_type")?; + let as_ref = attributes.get_string_value("as_ref")?; + let from_value = attributes.get_string_value("from_value")?; + match (transparent_flag.value(), as_type, as_ref, from_value) { + (true, None, None, None) => Ok(DeriveStrategy::Transparent), + (true, _, _, _) => Err(Error::new( + transparent_flag.span, + "The `transparent` option cannot be used with `as_type` / `as_ref` / `from_value`", + )), + (false, Some(as_type), as_ref, from_value) => { + let as_type_str = as_type.value(); + let as_type: Type = as_type.parse()?; + Ok(DeriveStrategy::DeriveAs { + as_ref: match as_ref { + Some(v) => { + if v.value().contains("self") { + v.parse()? + } else { + return Err(Error::new(v.span(), format!("The `as_ref` value should be code mapping `self` into a &{as_type_str}"))); + } + } + None => quote! { >::as_ref(self) }, + }, + from_value: match from_value { + Some(v) => { + if v.value().contains("value") { + v.parse()? + } else { + return Err(Error::new(v.span(), format!("The `from_value` value should be code mapping `value` (of type {as_type_str}) into `Self`"))); + } + } + None => quote! { >::from(value) }, + }, + as_type, + }) + } + (false, None, Some(_), Some(_)) => Err(Error::new( + transparent_flag.span, + "The `as_ref` or `from_value` options cannot be used without `as_type`", + )), + (false, None, Some(_), None) => Err(Error::new( + transparent_flag.span, + "The `as_ref` option cannot be used without `as_type`", + )), + (false, None, None, Some(_)) => Err(Error::new( + transparent_flag.span, + "The `from_value` option cannot be used without `as_type`", + )), + (false, None, None, None) => Ok(DeriveStrategy::Normal), + } } pub fn is_transparent(attributes: &[Attribute]) -> Result { let attributes = extract_sbor_typed_attributes(attributes)?; - Ok(attributes.get_bool_value("transparent")?) + Ok(attributes.get_bool_value("transparent")?.value()) +} + +pub fn get_custom_value_kind(attributes: &[Attribute]) -> Result> { + get_sbor_attribute_string_value(attributes, "custom_value_kind") } -pub fn get_custom_value_kind(attributes: &[Attribute]) -> Result> { - extract_sbor_typed_attributes(attributes)?.get_string_value("custom_value_kind") +pub fn get_custom_type_kind(attributes: &[Attribute]) -> Result> { + get_sbor_attribute_string_value(attributes, "custom_type_kind") } -pub fn get_custom_type_kind(attributes: &[Attribute]) -> Result> { - extract_sbor_typed_attributes(attributes)?.get_string_value("custom_type_kind") +pub fn resolve_type_name(ident: &Ident, attributes: &[Attribute]) -> Result { + let type_name = get_sbor_attribute_string_value(attributes, "type_name")? + .unwrap_or(LitStr::new(&ident.to_string(), ident.span())); + Ok(type_name) } pub fn get_generic_types(generics: &Generics) -> Vec { @@ -379,15 +440,30 @@ pub fn get_generic_types(generics: &Generics) -> Vec { .collect() } -pub fn parse_comma_separated_types(source_string: &str) -> syn::Result> { +pub fn parse_single_type(source_string: &LitStr) -> syn::Result { + source_string.parse() +} + +pub fn parse_comma_separated_types(source_string: &LitStr) -> syn::Result> { + let span = source_string.span(); source_string + .value() .split(',') .map(|s| s.trim().to_owned()) .filter(|f| f.len() > 0) - .map(|s| parse_str(&s)) + .map(|s| LitStr::new(&s, span).parse()) .collect() } +/// Child types are intended to capture what non-concrete types are embedded in the +/// given type as descendents in the SBOR value model - these types will require explicit bounds for +/// `Encode` / `Decode` / `Describe`. +/// +/// By default, like e.g. the default `Clone` impl, we assume that all generic types are +/// child types. But a user can override this with the `#[sbor(child_types = "A,B")]` attribute. +/// +/// One of the prime use cases for this is where associated types are used, for example +/// a type ` MyStruct(T::MyAssociatedType)` should use `#[sbor(child_types = "T::MyAssociatedType")]`. fn get_child_types(attributes: &[Attribute], existing_generics: &Generics) -> Result> { let Some(comma_separated_types) = get_sbor_attribute_string_value(attributes, "child_types")? else { @@ -403,23 +479,81 @@ fn get_child_types(attributes: &[Attribute], existing_generics: &Generics) -> Re fn get_types_requiring_categorize_bound_for_encode_and_decode( attributes: &[Attribute], - child_types: &[Type], ) -> Result> { - let Some(comma_separated_types) = - get_sbor_attribute_string_value(attributes, "categorize_types")? - else { - // A categorize bound is only needed for child types when you have a collection, eg Vec - // But if no explicit "categorize_types" is set, we assume all are needed. - // > Note as of Aug 2023: - // This is perhaps the wrong call. - // In future, I'd suggest: - // - Change this to assume none, and add categorize_child_types_for_encode if needed. - // - Add separate categorize_child_types_for_categorize - and also default to not needed. - // These can be removed / overridden with the "categorize_types" field - return Ok(child_types.to_owned()); - }; + let comma_separated_types = get_sbor_attribute_string_value(attributes, "categorize_types")?; + // We need to work out what the default behaviour is if no `categorize_types` are provided. + // + // Now, for a given generic parameter T, we have a few cases how it appears in the type: + // 1. It's embedded as a T into the type, e.g. MyNewType(T) + // 2. It's wrapped in a collection like a Vec or Map<..> + // + // Note that only in case 2 (the rarer case) do we require that T implements Categorize. + // + // We can do one of the following options: + // (A) Assume none + // (B) Use child_types if they exist + // (C) Use child_types if they exist, else use the existing generic parameters + // (D) Use the existing generic parameters + // + // - The issue with (C/D) is that generic types such as MyStruct(T) require you to know to include + // #[sbor(categorize_types = "")] otherwise they cryptically don't get an Encode/Decode implementation + // for something like T = sbor::Value, which is subtle, easy to miss, and perplexing when it happens. + // - The issue with (B) is the same as (C) - the error is too cryptic. Let's say we define + // #[sbor(child_types = "T::MyAssociatedValue")] and the type is MyStruct(T::MyAssociatedValue), + // then we lose an encode implementation if T::MyAssociatedValue = sbor::Value. + // - The issue with (A) is that generic types such as MyStruct(Vec) require you to know to include + // #[sbor(categorize_types = "T")] else the implementation doesn't compile. This I think is a slightly + // clearer error than (C). + // + // We used to use (C), but we have now switched to (A) because we believe it to be clearer, on balance. + // Also, providing #[sbor(categorize_types = "T")] feels more explicit sometimes than confusingly having + // to add #[sbor(categorize_types = "")] sometimes. + if let Some(comma_separated_types) = comma_separated_types { + parse_comma_separated_types(&comma_separated_types) + } else { + Ok(vec![]) + } +} - parse_comma_separated_types(&comma_separated_types) +/// Note - this is only needed for implementing an inherited categorize. +pub fn get_type_requiring_categorize_bound_for_categorize_as( + as_type: &Type, + attributes: &[Attribute], + existing_generics: &Generics, +) -> Result> { + let explicit_type = get_sbor_attribute_string_value(attributes, "categorize_as")?; + + if let Some(explicit_type) = explicit_type { + Ok(Some(parse_single_type(&explicit_type)?)) + } else { + // We need to work out what the default behaviour is if no `categorize_as` are provided. + // + // Thankfully we have a 99% strategy: + // - If `as_type` _IS_ a child type, then it must be generic, not concrete. + // - If `as_type` _IS NOT_ a child type, then it's probably a concrete type... + // + // Hypothetically we could have OuterWrapper(InnerWrapper(T)) where `as_type = InnerWrapper(T)` but `child_types = T` + // And we should have a constraint on InnerWrapper(T): Categorize. But that's what the `categorize_as` fallback is for. + // + // Or the more likely case might be that the two types are the same type, but syn doesn't match them because of formatting + // issues of different paths or something, there's not much we can do there. + // + // If we miss the constraint then the user will get a compiler error and go digging and find `categorize_as`. + + // IMPORTANT: + // We convert to string here because type equality is too strict and relies on span equality which won't exist + // in many cases. But by converting to string we have a looser/better equality check. + let child_type_strs = get_child_types(attributes, existing_generics)? + .iter() + .map(|t| quote!(#t).to_string()) + .collect_vec(); + let as_type_str = quote!(#as_type).to_string(); + if child_type_strs.contains(&as_type_str) { + Ok(Some(as_type.clone())) + } else { + Ok(None) + } + } } pub fn get_code_hash_const_array_token_stream(input: &TokenStream) -> TokenStream { @@ -449,27 +583,7 @@ pub(crate) struct FieldsData { pub unskipped_field_count: Index, } -pub(crate) fn process_fields_for_categorize(fields: &syn::Fields) -> Result { - process_fields(fields, is_categorize_skipped) -} - -pub(crate) fn process_fields_for_encode(fields: &syn::Fields) -> Result { - process_fields(fields, is_encoding_skipped) -} - -pub(crate) fn process_fields_for_decode(fields: &syn::Fields) -> Result { - process_fields(fields, is_decoding_skipped) -} - -pub(crate) fn process_fields_for_describe(fields: &syn::Fields) -> Result { - // Note - describe has to agree with decoding / encoding - process_fields(fields, is_decoding_skipped) -} - -fn process_fields( - fields: &syn::Fields, - is_skipped: impl Fn(&Field) -> Result, -) -> Result { +pub(crate) fn process_fields(fields: &syn::Fields) -> Result { Ok(match fields { Fields::Named(fields) => { let mut unskipped_field_names = Vec::new(); @@ -597,7 +711,7 @@ pub fn build_decode_generics<'a>( let (custom_value_kind_generic, need_to_add_cvk_generic): (Path, bool) = if let Some(path) = custom_value_kind { - (parse_str(path.as_str())?, false) + (path.parse()?, false) } else if let Some(path) = context_custom_value_kind { (parse_str(path)?, false) } else { @@ -609,8 +723,7 @@ pub fn build_decode_generics<'a>( let decoder_generic: Path = parse_str(&decoder_label)?; let child_types = get_child_types(&attributes, &impl_generics)?; - let categorize_types = - get_types_requiring_categorize_bound_for_encode_and_decode(&attributes, &child_types)?; + let categorize_types = get_types_requiring_categorize_bound_for_encode_and_decode(&attributes)?; let mut where_clause = where_clause.cloned(); if child_types.len() > 0 || categorize_types.len() > 0 { @@ -663,7 +776,7 @@ pub fn build_encode_generics<'a>( let (custom_value_kind_generic, need_to_add_cvk_generic): (Path, bool) = if let Some(path) = custom_value_kind { - (parse_str(path.as_str())?, false) + (path.parse()?, false) } else if let Some(path) = context_custom_value_kind { (parse_str(path)?, false) } else { @@ -675,8 +788,7 @@ pub fn build_encode_generics<'a>( let encoder_generic: Path = parse_str(&encoder_label)?; let child_types = get_child_types(&attributes, &impl_generics)?; - let categorize_types = - get_types_requiring_categorize_bound_for_encode_and_decode(&attributes, &child_types)?; + let categorize_types = get_types_requiring_categorize_bound_for_encode_and_decode(&attributes)?; let mut where_clause = where_clause.cloned(); if child_types.len() > 0 || categorize_types.len() > 0 { @@ -730,7 +842,7 @@ pub fn build_describe_generics<'a>( let (custom_type_kind_generic, need_to_add_ctk_generic): (Path, bool) = if let Some(path) = custom_type_kind { - (parse_str(path.as_str())?, false) + (path.parse()?, false) } else if let Some(path) = context_custom_type_kind { (parse_str(&path)?, false) } else { @@ -771,11 +883,10 @@ pub fn build_describe_generics<'a>( )) } -pub fn build_custom_categorize_generic<'a>( +pub fn build_categorize_generics<'a>( original_generics: &'a Generics, attributes: &'a [Attribute], context_custom_value_kind: Option<&'static str>, - require_categorize_on_generic_params: bool, ) -> syn::Result<(Generics, TypeGenerics<'a>, Option<&'a WhereClause>, Path)> { let custom_value_kind = get_custom_value_kind(&attributes)?; let (impl_generics, ty_generics, where_clause) = original_generics.split_for_impl(); @@ -785,7 +896,7 @@ pub fn build_custom_categorize_generic<'a>( let (custom_value_kind_generic, need_to_add_cvk_generic): (Path, bool) = if let Some(path) = custom_value_kind { - (parse_str(path.as_str())?, false) + (path.parse()?, false) } else if let Some(path) = context_custom_value_kind { (parse_str(path)?, false) } else { @@ -793,23 +904,6 @@ pub fn build_custom_categorize_generic<'a>( (parse_str(&custom_type_label)?, true) }; - if require_categorize_on_generic_params { - // In order to implement transparent Categorize, we need to pass through Categorize to the child field. - // To do this, we need to ensure that type is Categorize. - // So we add a bound that all pre-existing type parameters have to implement Categorize - // This is essentially what derived traits such as Clone do: https://github.com/rust-lang/rust/issues/26925 - // It's not perfect - but it's typically good enough! - - for param in impl_generics.params.iter_mut() { - let GenericParam::Type(type_param) = param else { - continue; - }; - type_param - .bounds - .push(parse_quote!(::sbor::Categorize<#custom_value_kind_generic>)); - } - } - if need_to_add_cvk_generic { impl_generics .params @@ -869,16 +963,20 @@ mod tests { #[sbor(skip3)] }; let extracted = extract_typed_attributes(&[attr, attr2], "sbor").unwrap(); - assert_eq!(extracted.get_bool_value("skip").unwrap(), true); - assert_eq!(extracted.get_bool_value("skip2").unwrap(), false); - assert_eq!(extracted.get_bool_value("skip3").unwrap(), true); + assert_eq!(extracted.get_bool_value("skip").unwrap().value(), true); + assert_eq!(extracted.get_bool_value("skip2").unwrap().value(), false); + assert_eq!(extracted.get_bool_value("skip3").unwrap().value(), true); assert!(matches!( extracted.get_bool_value("custom_value_kind"), Err(_) )); assert_eq!( - extracted.get_string_value("custom_value_kind").unwrap(), - Some("NoCustomValueKind".to_string()) + extracted + .get_string_value("custom_value_kind") + .unwrap() + .unwrap() + .value(), + "NoCustomValueKind".to_string() ); assert_eq!( extracted.get_string_value("custom_value_kind_2").unwrap(), diff --git a/sbor-derive/src/eager_stringify.rs b/sbor-derive/src/eager_stringify.rs new file mode 100644 index 00000000000..4ceab2173f4 --- /dev/null +++ b/sbor-derive/src/eager_stringify.rs @@ -0,0 +1,63 @@ +use proc_macro::*; + +pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { + let mut tokens = token_stream.into_iter().peekable(); + let mut expanded = TokenStream::new(); + loop { + let Some(token_tree) = tokens.next() else { + break; + }; + + if let Some(eager_stringify_ident_span) = + is_eager_stringify_followed_by_exclamation_mark(&token_tree, &mut tokens) + { + let exclamation_mark = tokens.next().unwrap(); + let Some(next_token_tree) = tokens.next() else { + break; + }; + if let proc_macro::TokenTree::Group(group) = next_token_tree { + expanded.extend(stringify_tokens(eager_stringify_ident_span, group.stream())); + } else { + // If we get eager_stringify! but then it doesn't get followed by a group, then add the token back which we've just consumed + expanded.extend(core::iter::once(token_tree)); + expanded.extend(core::iter::once(exclamation_mark)); + expanded.extend(core::iter::once(next_token_tree)); + } + } else { + if let proc_macro::TokenTree::Group(group) = token_tree { + expanded.extend(core::iter::once(TokenTree::Group(proc_macro::Group::new( + group.delimiter(), + replace_recursive(group.stream()), + )))) + } else { + expanded.extend(core::iter::once(token_tree)); + } + } + } + return expanded; +} + +fn is_eager_stringify_followed_by_exclamation_mark( + current: &TokenTree, + tokens: &mut core::iter::Peekable<::IntoIter>, +) -> Option { + let TokenTree::Ident(ident) = ¤t else { + return None; + }; + if ident.to_string() != "eager_stringify" { + return None; + } + let Some(TokenTree::Punct(punct)) = tokens.peek() else { + return None; + }; + if punct.as_char() != '!' { + return None; + } + Some(ident.span()) +} + +fn stringify_tokens(span: Span, token_stream: TokenStream) -> TokenStream { + let mut literal = Literal::string(&token_stream.to_string()); + literal.set_span(span); + TokenTree::Literal(literal).into() +} diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 530f10e0d24..5da94c0eaed 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; - use proc_macro::TokenStream; +use std::str::FromStr; +mod eager_stringify; /// Derive code that returns the value kind. #[proc_macro_derive(Categorize, attributes(sbor))] @@ -64,6 +64,12 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { TokenStream::from_str(&"// Empty PermitSborAttributes expansion").unwrap() } +/// Allows the `eager_stringify!` macro to stringify its contents immediately. +#[proc_macro] +pub fn enable_eager_stringify(token_stream: TokenStream) -> TokenStream { + eager_stringify::replace_recursive(token_stream) +} + const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind"; const BASIC_CUSTOM_TYPE_KIND: &str = "sbor::NoCustomTypeKind"; diff --git a/sbor-tests/tests/enum.rs b/sbor-tests/tests/enum.rs index 87c2fd8a581..4c414612dc0 100644 --- a/sbor-tests/tests/enum.rs +++ b/sbor-tests/tests/enum.rs @@ -19,7 +19,7 @@ pub enum Abc { pub enum AbcV2 { #[sbor(discriminator(VARIANT_1))] Variant1, - #[sbor(ignore_as_unreachable)] + #[sbor(unreachable)] UnreachableVariant, #[sbor(discriminator(VARIANT_2))] Variant2, @@ -30,7 +30,7 @@ pub enum AbcV2 { pub enum TestThatPermitSborAttributesCanCompile { #[sbor(discriminator(VARIANT_1))] Variant1, - #[sbor(ignore_as_unreachable)] + #[sbor(unreachable)] UnreachableVariant, #[sbor(discriminator(VARIANT_2))] Variant2, diff --git a/sbor-tests/tests/transparent.rs b/sbor-tests/tests/transparent.rs index d1544161532..33537b9bbe6 100644 --- a/sbor-tests/tests/transparent.rs +++ b/sbor-tests/tests/transparent.rs @@ -27,6 +27,12 @@ pub struct TestStruct { pub state: T, } +#[derive(Sbor, PartialEq, Eq, Debug)] +#[sbor(transparent, transparent_name)] +pub struct TestStructTransparentNamed { + pub state: T, +} + #[test] fn categorize_is_correct() { // With inner u32 @@ -139,20 +145,24 @@ fn decode_is_correct() { #[test] fn describe_is_correct() { // With inner u32 - check_identical_types::("TestStructNamed"); - check_identical_types::("TestStructRenamed2"); - check_identical_types::("TestStructUnnamed"); - check_identical_types::, u32>("TestStruct"); + check_identical_types::(Some("TestStructNamed")); + check_identical_types::(Some("TestStructRenamed2")); + check_identical_types::(Some("TestStructUnnamed")); + check_identical_types::, u32>(Some("TestStruct")); + check_identical_types::, u32>(None); // With inner tuple - check_identical_types::, ()>("TestStruct"); + check_identical_types::, ()>(Some("TestStruct")); // With multiple layers of transparent - check_identical_types::, u32>("TestStruct"); + check_identical_types::, u32>(Some("TestStruct")); + check_identical_types::, u32>(Some( + "TestStructRenamed2", + )); } fn check_identical_types, T2: Describe>( - rename: &'static str, + name: Option<&'static str>, ) { let (type_id1, schema1) = generate_full_schema_from_single_type::(); let (type_id2, schema2) = generate_full_schema_from_single_type::(); @@ -172,7 +182,7 @@ fn check_identical_types, T2: Describe => Schema = SchemaV1:: + pub VersionedSchema(SchemaVersions) => Schema = SchemaV1:: ); impl VersionedSchema { pub fn v1(&self) -> &SchemaV1 { - self.as_unique_latest_ref() + self.as_unique_version_ref() } pub fn v1_mut(&mut self) -> &mut SchemaV1 { - self.as_unique_latest_mut() + self.as_unique_version_mut() } } diff --git a/sbor/src/schema/schema_validation/type_metadata_validation.rs b/sbor/src/schema/schema_validation/type_metadata_validation.rs index 6f36e9d3f78..8cd8266e7fc 100644 --- a/sbor/src/schema/schema_validation/type_metadata_validation.rs +++ b/sbor/src/schema/schema_validation/type_metadata_validation.rs @@ -154,6 +154,9 @@ pub fn validate_schema_field_name(name: &str) -> Result<(), SchemaValidationErro validate_schema_ident("field name", name) } +// IMPORTANT: +// For crate dependency regions, this is duplicated in `sbor-derive-common` +// If you change it here, please change it there as well fn validate_schema_ident(ident_name: &str, name: &str) -> Result<(), SchemaValidationError> { if name.len() == 0 { return Err(SchemaValidationError::InvalidIdentName { diff --git a/sbor/src/schema/type_aggregator.rs b/sbor/src/schema/type_aggregator.rs index 03bf2cea48c..fae7db35503 100644 --- a/sbor/src/schema/type_aggregator.rs +++ b/sbor/src/schema/type_aggregator.rs @@ -25,11 +25,12 @@ pub fn generate_full_schema( type_validations.push(type_data.validation); } - VersionedSchema::V1(Schema { + Schema { type_kinds, type_metadata, type_validations, - }) + } + .into_versioned() } pub fn localize_well_known_type_data( diff --git a/sbor/src/schema/type_data/type_kind.rs b/sbor/src/schema/type_data/type_kind.rs index a69f5bd58cc..5f10dcc5b51 100644 --- a/sbor/src/schema/type_data/type_kind.rs +++ b/sbor/src/schema/type_data/type_kind.rs @@ -4,7 +4,7 @@ use crate::rust::vec::Vec; /// A schema for the values that a codec can decode / views as valid #[derive(Debug, Clone, PartialEq, Eq, Sbor)] -#[sbor(child_types = "C,L")] +#[sbor(child_types = "C,L", categorize_types = "L")] pub enum TypeKind, L: SchemaTypeLink> { Any, diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index fc84675f216..943eb852d0a 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -1,54 +1,104 @@ -pub enum UpdateResult { - Updated(T), - AtLatest(T), -} +use crate::internal_prelude::*; + +// !!!!!!! TODO !!!!!!!!! +// - Add #[sbor(as = '')] in order to encode Versioned as Versions +// - Add sbor tests to equate the schema + +/// A trait implemented by versioned types created via [`define_versioned`] and [`define_single_versioned`]. +/// +/// A versioned type is a type wrapping an enum, this enum is the associated type [`Versioned::Versions`], +/// and contains a variant for each supported version. +/// +/// This [`Versioned`] type itself is a struct wrapper around this enum, which allows for fully updating +/// the contained version to [`Versioned::LatestVersion`]. This wrapper is required so that the wrapper +/// can take ownership of old versions as part of the upgrade process, in order to incrementally update +/// them using the [`From`] trait. +pub trait Versioned: AsRef + AsMut + From { + /// The type for the enum of versions. + type Versions: From; -/// A marker trait to indicate that the type is versioned. -/// This can be used for type bounds for requiring that types are versioned. -pub trait HasLatestVersion { /// The type for the latest content. - type Latest; - - /// Returns true if the versioned enum is at the latest version. - fn is_latest(&self) -> bool; - /// Consumes the versioned enum to update it to the latest version, - /// then returns the latest content - fn into_latest(self) -> Self::Latest; - /// Updates the versioned enum to the latest version in place, - /// and then returns a mutable reference to the latest content - fn to_latest_mut(&mut self) -> &mut Self::Latest; - /// Constructs the versioned enum from the latest content - fn from_latest(latest: Self::Latest) -> Self; - /// If the versioned enum is at the latest version, it returns + type LatestVersion; + + /// Returns true if at the latest version. + fn is_fully_updated(&self) -> bool; + + /// Updates the latest version in place, and returns a `&mut` to the latest content + fn fully_update_to_latest_version_mut(&mut self) -> &mut Self::LatestVersion { + self.fully_update(); + self.as_latest_version_mut().unwrap() + } + + /// Updates to the latest version in place. + fn fully_update(&mut self); + + /// Updates itself to the latest version, then returns the latest content + fn fully_update_into_latest_version(self) -> Self::LatestVersion; + + /// Constructs a versioned wrapper around the latest content + fn from_latest_version(latest: Self::LatestVersion) -> Self; + + /// If the versioned wrapper is at the latest version, it returns /// an immutable reference to the latest content, otherwise it returns `None`. /// /// If you require the latest version unconditionally, consider using - /// [`to_latest_mut`] to update to the latest version first - or, if - /// there is only a single version, use [`as_unique_latest_ref`]. - fn as_latest_ref(&self) -> Option<&Self::Latest>; - /// If the versioned enum is at the latest version, it returns + /// [`fully_update_to_latest_version_mut`] to update to the latest version first - or, if + /// there is only a single version, use [`as_unique_version_ref`]. + fn as_latest_version_ref(&self) -> Option<&Self::LatestVersion>; + + /// If the versioned wrapper is at the latest version, it returns /// a mutable reference to the latest content, otherwise it returns `None`. /// /// If you require the latest version unconditionally, consider using - /// [`to_latest_mut`] to update to the latest version first - or, if - /// there is only a single version, use [`as_unique_latest_mut`]. - fn as_latest_mut(&mut self) -> Option<&mut Self::Latest>; -} + /// [`fully_update_to_latest_version_mut`] to update to the latest version first - or, if + /// there is only a single version, use [`as_unique_version_mut`]. + fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion>; -pub trait CloneIntoLatest: HasLatestVersion { - fn clone_into_latest(&self) -> Self::Latest; -} + /// Gets a reference the inner versions enum, for e.g. matching on the enum. + /// + /// This is essentially a clearer alias for `as_ref`. + fn as_versions_ref(&self) -> &Self::Versions; -impl CloneIntoLatest for T { - fn clone_into_latest(&self) -> Self::Latest { - self.clone().into_latest() - } + /// Gets a mutable reference the inner versions enum, for e.g. matching on the enum. + /// + /// This is essentially a clearer alias for `as_mut`. + fn as_versions_mut(&mut self) -> &mut Self::Versions; + + /// Removes the upgradable wrapper to get at the inner versions enum, for e.g. matching on the enum. + fn into_versions(self) -> Self::Versions; + + /// Creates a new Versioned wrapper from a given specific version. + fn from_versions(version: Self::Versions) -> Self; } -pub trait HasUniqueLatestVersion: HasLatestVersion { - fn as_unique_latest_ref(&self) -> &Self::Latest; +/// A trait for Versioned types which only have a single version. +/// +/// This enables a number of special-cased methods to be implemented which are only possible when there +/// is only one version. +pub trait UniqueVersioned: Versioned { + /// Returns an immutable reference to (currently) the only possible version of the inner content. + fn as_unique_version_ref(&self) -> &Self::LatestVersion; + + /// Returns a mutable reference to (currently) the only possible version of the inner content. + /// + /// This is somewhat equivalent to `fully_update_to_latest_version_mut`, but doesn't need to do + /// any updating, so can be used where logical correctness requires there to be a unique version, + /// requires no updating, or simply for slightly better performance. + fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion; - fn as_unique_latest_mut(&mut self) -> &mut Self::Latest; + /// Returns the (currently) only possible version of the inner content. + /// + /// This is somewhat equivalent to `fully_update_into_latest_version`, but doesn't need to do + /// any updating, so can be used where logical correctness requires there to be a unique version, + /// requires no updating, or simply for slightly better performance. + fn into_unique_version(self) -> Self::LatestVersion; + + /// Creates the versioned wrapper from the (currently) only possible version. + /// + /// This is equivalent to `from_latest_version`, but useful to use instead if your logic's correctness + /// is dependent on there only being a single version. If another version gets added, this + /// method will give a compile error. + fn from_unique_version(unique_version: Self::LatestVersion) -> Self; } /// This macro is intended for creating a data model which supports versioning. @@ -57,13 +107,12 @@ pub trait HasUniqueLatestVersion: HasLatestVersion { /// In future, the type can be converted to `define_versioned`, enum variants can /// be added, and automatically mapped to the latest version. /// -/// This macro is just a simpler wrapper around the [`crate::define_versioned`] macro, +/// This macro is just a simpler wrapper around the [`define_versioned`] macro, /// for use when there's just a single version. /// /// Example usage: /// ```rust -/// # use ::sbor::prelude::*; -/// # use ::sbor::define_single_versioned; +/// use ::sbor::prelude::*; /// /// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] /// pub struct FooV1 { @@ -72,26 +121,30 @@ pub trait HasUniqueLatestVersion: HasLatestVersion { /// /// define_single_versioned! { /// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] -/// pub enum VersionedFoo => Foo = FooV1 +/// pub VersionedFoo(FooVersions) => Foo = FooV1 /// } /// /// // `Foo` is created as an alias for `FooV1` -/// let versioned = VersionedFoo::V1(Foo { bar: 42 }); +/// let a = Foo { bar: 42 }.into_versioned(); +/// let a3 = VersionedFoo::from(FooVersions::V1(FooV1 { bar: 42 })); +/// let a2 = VersionedFoo::from_unique_version(Foo { bar: 42 }); /// -/// assert_eq!(42, versioned.as_unique_latest_ref().bar); +/// assert_eq!(a, a2); +/// assert_eq!(a2, a3); +/// assert_eq!(42, a.as_unique_version_ref().bar); /// ``` #[macro_export] macro_rules! define_single_versioned { ( $(#[$attributes:meta])* - $vis:vis enum $name:ident + $vis:vis $versioned_name:ident($versions_name:ident) $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)? => $latest_version_alias:ty = $latest_version_type:ty ) => { $crate::define_versioned!( $(#[$attributes])* - $vis enum $name + $vis $versioned_name($versions_name) $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? { previous_versions: [], @@ -103,25 +156,31 @@ macro_rules! define_single_versioned { $crate::paste::paste! { #[allow(dead_code)] - impl - $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - HasUniqueLatestVersion for - $name - $(< $( $lt ),+ >)? + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + UniqueVersioned + for $versioned_name $(< $( $lt ),+ >)? { - fn as_unique_latest_ref(&self) -> &Self::Latest { - match self { - Self::V1(content) => content, - Self::__InternalUpdateInProgress => panic!("Unexpected variant __InternalUpdateInProgress"), + fn as_unique_version_ref(&self) -> &Self::LatestVersion { + match self.as_ref() { + $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content, } } - fn as_unique_latest_mut(&mut self) -> &mut Self::Latest { - match self { - Self::V1(content) => content, - Self::__InternalUpdateInProgress => panic!("Unexpected variant __InternalUpdateInProgress"), + fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion { + match self.as_mut() { + $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content, } } + + fn into_unique_version(self) -> Self::LatestVersion { + match $versions_name $(::< $( $lt ),+ >)? ::from(self) { + $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content, + } + } + + fn from_unique_version(content: Self::LatestVersion) -> Self { + $versions_name $(::< $( $lt ),+ >)? ::V1(content).into() + } } } }; @@ -139,7 +198,6 @@ macro_rules! define_single_versioned { /// Example usage: /// ```rust /// use sbor::prelude::*; -/// use sbor::define_versioned; /// /// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] /// pub struct FooV1 { @@ -164,7 +222,7 @@ macro_rules! define_single_versioned { /// /// define_versioned!( /// #[derive(Debug, Clone, PartialEq, Eq, Sbor)] -/// enum VersionedFoo { +/// VersionedFoo(FooVersions) { /// previous_versions: [ /// 1 => FooV1: { updates_to: 2 }, /// ], @@ -174,21 +232,24 @@ macro_rules! define_single_versioned { /// } /// ); /// -/// let mut versioned_old = VersionedFoo::V1(FooV1 { bar: 42 }); +/// let mut a = FooV1 { bar: 42 }.into_versioned(); +/// let equivalent_a = VersionedFoo::from(FooVersions::V1(FooV1 { bar: 42 })); +/// assert_eq!(a, equivalent_a); +/// /// // `Foo` is created as an alias for the latest content, `FooV2` -/// let versioned_new = VersionedFoo::V2(Foo { bar: 42, baz: None }); +/// let b = VersionedFoo::from(FooVersions::V2(Foo { bar: 42, baz: None })); /// -/// assert_ne!(&versioned_new, &versioned_old); -/// assert_eq!(versioned_new.as_latest_ref().unwrap(), &*versioned_old.to_latest_mut()); +/// assert_ne!(a, b); +/// assert_eq!(&*a.fully_update_to_latest_version_mut(), b.as_latest_version_ref().unwrap()); /// -/// // After a call to `to_latest_mut`, `versioned_old` has been updated: -/// assert_eq!(versioned_new, versioned_old); +/// // After a call to `a.fully_update_to_latest_version_mut()`, `a` has now been updated: +/// assert_eq!(a, b); /// ``` #[macro_export] macro_rules! define_versioned { ( $(#[$attributes:meta])* - $vis:vis enum $name:ident + $vis:vis $versioned_name:ident($versions_name:ident) // Now match the optional type parameters // See https://stackoverflow.com/questions/41603424/rust-macro-accepting-type-with-generic-parameters $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)? @@ -206,11 +267,12 @@ macro_rules! define_versioned { $(,)? // Optional trailing comma } ) => { + $crate::enable_eager_stringify! { $crate::paste::paste! { // Create inline sub-macros to handle the type generics nested inside // iteration over previous_versions // See eg https://stackoverflow.com/a/73543948 - macro_rules! [<$name _trait_impl>] { + macro_rules! [<$versioned_name _versions_trait_impl>] { ( $trait:ty, $impl_block:tt @@ -219,7 +281,21 @@ macro_rules! define_versioned { impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $trait - for $name $(< $( $lt ),+ >)? + for $versions_name $(< $( $lt ),+ >)? + $impl_block + }; + } + + macro_rules! [<$versioned_name _versioned_trait_impl>] { + ( + $trait:ty, + $impl_block:tt + ) => { + #[allow(dead_code)] + impl + $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + $trait + for $versioned_name $(< $( $lt ),+ >)? $impl_block }; } @@ -227,124 +303,213 @@ macro_rules! define_versioned { #[allow(dead_code)] $vis type $latest_version_alias = $latest_version_type; - use $crate::PermitSborAttributes as [<$name _PermitSborAttributes>]; + use $crate::PermitSborAttributes as [<$versioned_name _PermitSborAttributes>]; + + #[derive([<$versioned_name _PermitSborAttributes>])] + #[sbor(as_type = eager_stringify!($versions_name $(< $( $lt ),+ >)?))] + $(#[$attributes])* + /// If you wish to get access to match on the versions, use `.as_ref()` or `.as_mut()`. + $vis struct $versioned_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? + { + inner: Option<$versions_name $(< $( $lt ),+ >)?>, + } + + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + $versioned_name $(< $( $lt ),+ >)? + { + pub fn new(inner: $versions_name $(< $( $lt ),+ >)?) -> Self { + Self { + inner: Some(inner), + } + } + } + + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + AsRef<$versions_name $(< $( $lt ),+ >)?> + for $versioned_name $(< $( $lt ),+ >)? + { + fn as_ref(&self) -> &$versions_name $(< $( $lt ),+ >)? { + self.inner.as_ref().unwrap() + } + } + + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + AsMut<$versions_name $(< $( $lt ),+ >)?> + for $versioned_name $(< $( $lt ),+ >)? + { + fn as_mut(&mut self) -> &mut $versions_name $(< $( $lt ),+ >)? { + self.inner.as_mut().unwrap() + } + } + + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + From<$versions_name $(< $( $lt ),+ >)?> + for $versioned_name $(< $( $lt ),+ >)? + { + fn from(value: $versions_name $(< $( $lt ),+ >)?) -> Self { + Self::new(value) + } + } + + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + From<$versioned_name $(< $( $lt ),+ >)?> + for $versions_name $(< $( $lt ),+ >)? + { + fn from(value: $versioned_name $(< $( $lt ),+ >)?) -> Self { + value.inner.unwrap() + } + } + + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + Versioned + for $versioned_name $(< $( $lt ),+ >)? + { + type Versions = $versions_name $(< $( $lt ),+ >)?; + type LatestVersion = $latest_version_type; + + fn is_fully_updated(&self) -> bool { + self.as_ref().is_fully_updated() + } + + fn fully_update(&mut self) { + if !self.is_fully_updated() { + let current = self.inner.take().unwrap(); + self.inner = Some(current.fully_update()); + } + } + + fn fully_update_into_latest_version(self) -> Self::LatestVersion { + self.inner.unwrap().fully_update_into_latest_version() + } + + /// Constructs the versioned enum from the latest content + fn from_latest_version(latest: Self::LatestVersion) -> Self { + Self::new(latest.into()) + } + + fn as_latest_version_ref(&self) -> Option<&Self::LatestVersion> { + self.as_ref().as_latest_version_ref() + } + + fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion> { + self.as_mut().as_latest_version_mut() + } + + fn as_versions_ref(&self) -> &Self::Versions { + self.as_ref() + } + + fn as_versions_mut(&mut self) -> &mut Self::Versions { + self.as_mut() + } + + fn into_versions(self) -> Self::Versions { + self.inner.unwrap() + } + + fn from_versions(version: Self::Versions) -> Self { + Self::new(version) + } + } $(#[$attributes])* - #[derive([<$name _PermitSborAttributes>])] - // We include the repr(u8) so that the SBOR discriminants are assigned - // to match the version numbers if SBOR is used on the versioned enum - #[repr(u8)] - $vis enum $name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? + #[derive([<$versioned_name _PermitSborAttributes>])] + #[sbor(type_name = eager_stringify!($versioned_name))] + $vis enum $versions_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? { $($( - []($version_type) = $version_num, + #[sbor(discriminator($version_num))] + []($version_type), )*)? - []($latest_version_type) = $latest_version, - #[sbor(ignore_as_unreachable)] - /// This variant is not intended to be used, except inside private - /// versioning code as a marker for transient internal state during - /// inline updates. - /// - /// If matching on this enum, consider instead using `.to_latest_mut()` - /// or `.into_latest()`, which updates the versioned enum to the latest - /// version, and exposes the latest content. - /// - /// If matching on the enum is required, the match arm for this variant - /// should panic!(). - __InternalUpdateInProgress = 255, + #[sbor(discriminator($latest_version))] + []($latest_version_type), } #[allow(dead_code)] impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $name + $versions_name $(< $( $lt ),+ >)? { - fn update_once(self) -> $crate::UpdateResult { + /// Returns if update happened, and the updated versioned enum. + fn attempt_single_update(self) -> (bool, Self) { match self { $($( - Self::[](value) => $crate::UpdateResult::Updated(Self::[](value.into())), + Self::[](value) => (true, Self::[](value.into())), )*)? - Self::[](value) => $crate::UpdateResult::AtLatest(Self::[](value)), - Self::__InternalUpdateInProgress => panic!("Update is already in progress"), + this @ Self::[](_) => (false, this), } } - fn update_to_latest(mut self) -> Self { + fn fully_update(mut self) -> Self { loop { - match self.update_once() { - $crate::UpdateResult::Updated(new) => { - self = new; - } - $crate::UpdateResult::AtLatest(latest) => { - return latest; - } + let (did_update, updated) = self.attempt_single_update(); + if did_update { + // We should try updating + self = updated; + } else { + // We're at latest - return + return updated; } } } - } - - [<$name _trait_impl>]!( - $crate::HasLatestVersion, - { - type Latest = $latest_version_type; - #[allow(unreachable_patterns)] - fn is_latest(&self) -> bool { - match self { - Self::[](_) => true, - _ => false, - } + #[allow(unreachable_patterns)] + pub fn is_fully_updated(&self) -> bool { + match self { + Self::[](_) => true, + _ => false, } + } - #[allow(irrefutable_let_patterns)] - fn into_latest(self) -> Self::Latest { - let Self::[](latest) = self.update_to_latest() else { - panic!("Invalid resolved latest version not equal to latest type") - }; - return latest; - } + #[allow(irrefutable_let_patterns)] + fn fully_update_into_latest_version(self) -> $latest_version_type { + let Self::[](latest) = self.fully_update() else { + panic!("Invalid resolved latest version not equal to latest type") + }; + return latest; + } - fn to_latest_mut(&mut self) -> &mut $latest_version_type { - if !self.is_latest() { - let current = radix_rust::prelude::mem::replace(self, Self::__InternalUpdateInProgress); - *self = current.update_to_latest(); - } - self.as_latest_mut().unwrap() - } + fn from_latest_version(latest: $latest_version_type) -> Self { + Self::[](latest) + } - fn from_latest(latest: Self::Latest) -> Self { - Self::[](latest) + #[allow(unreachable_patterns)] + fn as_latest_version_ref(&self) -> Option<&$latest_version_type> { + match self { + Self::[](latest) => Some(latest), + _ => None, } + } - #[allow(unreachable_patterns)] - fn as_latest_ref(&self) -> Option<&Self::Latest> { - match self { - Self::[](latest) => Some(latest), - _ => None, - } + #[allow(unreachable_patterns)] + fn as_latest_version_mut(&mut self) -> Option<&mut $latest_version_type> { + match self { + Self::[](latest) => Some(latest), + _ => None, } + } + } - #[allow(unreachable_patterns)] - fn as_latest_mut(&mut self) -> Option<&mut Self::Latest> { - match self { - Self::[](latest) => Some(latest), - _ => None, - } + $($([<$versioned_name _versions_trait_impl>]!( + From<$version_type>, + { + fn from(value: $version_type) -> Self { + Self::[](value) } } - ); + );)*)? - $($([<$name _trait_impl>]!( + $($([<$versioned_name _versioned_trait_impl>]!( From<$version_type>, { fn from(value: $version_type) -> Self { - Self::[](value) + Self::new($versions_name::[](value)) } } );)*)? - [<$name _trait_impl>]!( + [<$versioned_name _versions_trait_impl>]!( From<$latest_version_type>, { fn from(value: $latest_version_type) -> Self { @@ -353,30 +518,50 @@ macro_rules! define_versioned { } ); + [<$versioned_name _versioned_trait_impl>]!( + From<$latest_version_type>, + { + fn from(value: $latest_version_type) -> Self { + Self::from($versions_name::[](value)) + } + } + ); + #[allow(dead_code)] - $vis trait [<$name Version>] { - // Note - we have an explicit Versioned associated type so that - // different generic parameters can each create their own specific concrete type - type Versioned; + $vis trait [<$versioned_name Version>] { + // Note: We need to use an explicit associated type to capture the generics. + type Versioned: $crate::Versioned; fn into_versioned(self) -> Self::Versioned; } - macro_rules! [<$name _versionable_impl>] { + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + [<$versioned_name Version>] + for $versions_name $(< $( $lt ),+ >)? + { + type Versioned = $versioned_name $(< $( $lt ),+ >)?; + + fn into_versioned(self) -> Self::Versioned { + $versioned_name $(::< $( $lt ),+ >)?::new(self) + } + } + + macro_rules! [<$versioned_name _versionable_impl>] { ($inner_type:ty) => { - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? [<$name Version>] for $inner_type + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? [<$versioned_name Version>] for $inner_type { - type Versioned = $name $(< $( $lt ),+ >)?; + type Versioned = $versioned_name $(< $( $lt ),+ >)?; fn into_versioned(self) -> Self::Versioned { - self.into() + $versioned_name $(::< $( $lt ),+ >)?::new(self.into()) } } }; } - $($([<$name _versionable_impl>]!($version_type);)*)? - [<$name _versionable_impl>]!($latest_version_type); + $($([<$versioned_name _versionable_impl>]!($version_type);)*)? + [<$versioned_name _versionable_impl>]!($latest_version_type); + } } }; } @@ -388,7 +573,7 @@ mod tests { crate::define_versioned!( #[derive(Debug, Clone, PartialEq, Eq, Sbor)] - enum VersionedExample { + VersionedExample(ExampleVersions) { previous_versions: [ 1 => ExampleV1: { updates_to: 2 }, 2 => ExampleV2: { updates_to: 4 }, @@ -447,17 +632,18 @@ mod tests { fn validate_latest( actual: impl Into, - expected: ::Latest, + expected: ::LatestVersion, ) { - let versioned_actual = actual.into(); + let mut versioned_actual = actual.into(); + versioned_actual.fully_update(); let versioned_expected = VersionedExample::from(expected.clone()); - // Check update_to_latest (which returns a VersionedExample) + // Check fully_update (which returns a VersionedExample) + assert_eq!(versioned_actual, versioned_expected,); + // Check fully_update_into_latest_version (which returns an ExampleV4) assert_eq!( - versioned_actual.clone().update_to_latest(), - versioned_expected, + versioned_actual.fully_update_into_latest_version(), + expected, ); - // Check into_latest (which returns an ExampleV4) - assert_eq!(versioned_actual.into_latest(), expected,); } #[derive(Debug, Clone, PartialEq, Eq, Sbor)] @@ -466,7 +652,7 @@ mod tests { define_single_versioned!( /// This is some rust doc as an example annotation #[derive(Debug, Clone, PartialEq, Eq)] - enum VersionedGenericModel => GenericModel = GenericModelV1 + VersionedGenericModel(GenericModelVersions) => GenericModel = GenericModelV1 ); #[test] @@ -474,7 +660,10 @@ mod tests { let v1_model: GenericModel<_> = GenericModelV1(51u64); let versioned = VersionedGenericModel::from(v1_model.clone()); let versioned_2 = v1_model.clone().into_versioned(); - assert_eq!(versioned.clone().into_latest(), v1_model); + assert_eq!( + versioned.clone().fully_update_into_latest_version(), + v1_model + ); assert_eq!(versioned, versioned_2); } } diff --git a/scrypto-test/src/environment/env.rs b/scrypto-test/src/environment/env.rs index eff0351c303..99f4472938a 100644 --- a/scrypto-test/src/environment/env.rs +++ b/scrypto-test/src/environment/env.rs @@ -715,7 +715,7 @@ where let mut manager_substate = env.field_read_typed::(manager_handle)?; - manager_substate.to_latest_mut().epoch = epoch; + manager_substate.as_unique_version_mut().epoch = epoch; env.field_write_typed(manager_handle, &manager_substate)?; env.field_close(manager_handle)?; @@ -746,8 +746,9 @@ where env.field_read_typed::( handle, )?; - proposer_minute_timestamp.to_latest_mut().epoch_minute = - (instant.seconds_since_unix_epoch / 60) as i32; + proposer_minute_timestamp + .as_unique_version_mut() + .epoch_minute = (instant.seconds_since_unix_epoch / 60) as i32; env.field_write_typed(handle, &proposer_minute_timestamp)?; env.field_close(handle)?; Ok(()) diff --git a/scrypto-test/src/ledger_simulator/ledger_simulator.rs b/scrypto-test/src/ledger_simulator/ledger_simulator.rs index df0d374128b..90c426388ed 100644 --- a/scrypto-test/src/ledger_simulator/ledger_simulator.rs +++ b/scrypto-test/src/ledger_simulator/ledger_simulator.rs @@ -548,7 +548,7 @@ impl LedgerSimulator { ), ) .unwrap() - .map(|v| v.into_latest()) + .map(|v| v.fully_update_into_latest_version()) } pub fn inspect_component_royalty(&mut self, component_address: ComponentAddress) -> Decimal { @@ -560,7 +560,7 @@ impl LedgerSimulator { ComponentRoyaltyField::Accumulator.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); let balance = reader .read_typed_object_field::( @@ -569,7 +569,7 @@ impl LedgerSimulator { FungibleVaultField::Balance.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); balance.amount() } @@ -583,7 +583,7 @@ impl LedgerSimulator { PackageField::RoyaltyAccumulator.field_index(), ) .ok()? - .into_latest(); + .fully_update_into_latest_version(); let balance = reader .read_typed_object_field::( @@ -592,7 +592,7 @@ impl LedgerSimulator { FungibleVaultField::Balance.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); Some(balance.amount()) } @@ -674,7 +674,7 @@ impl LedgerSimulator { let key: BlueprintVersionKey = scrypto_decode(&map_key).unwrap(); let definition: PackageBlueprintVersionDefinitionEntryPayload = scrypto_decode(&value).unwrap(); - (key, definition.into_latest()) + (key, definition.fully_update_into_latest_version()) }) .collect() } @@ -733,7 +733,7 @@ impl LedgerSimulator { ) .ok(); - vault.map(|v| v.into_latest().amount()) + vault.map(|v| v.fully_update_into_latest_version().amount()) } pub fn inspect_non_fungible_vault( @@ -748,7 +748,7 @@ impl LedgerSimulator { NonFungibleVaultField::Balance.into(), ) .ok()?; - let amount = vault_balance.into_latest().amount; + let amount = vault_balance.fully_update_into_latest_version().amount; // TODO: Remove .collect() by using SystemDatabaseReader in ledger let iter: Vec = reader @@ -830,7 +830,7 @@ impl LedgerSimulator { ) .unwrap() .into_payload() - .into_latest(); + .fully_update_into_latest_version(); total_supply } @@ -902,7 +902,7 @@ impl LedgerSimulator { ValidatorField::State.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); substate } @@ -916,7 +916,7 @@ impl LedgerSimulator { ConsensusManagerField::CurrentValidatorSet.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); substate .validator_set .get_by_public_key(key) @@ -2068,7 +2068,7 @@ impl LedgerSimulator { ConsensusManagerField::State.field_index(), ) .unwrap() - .into_latest(); + .fully_update_into_latest_version(); substate.epoch = epoch; @@ -2143,7 +2143,7 @@ impl LedgerSimulator { ConsensusManagerField::ProposerMilliTimestamp.field_index(), ) .unwrap() - .into_latest() + .fully_update_into_latest_version() .epoch_milli } @@ -2156,7 +2156,7 @@ impl LedgerSimulator { ConsensusManagerField::State.field_index(), ) .unwrap() - .into_latest() + .fully_update_into_latest_version() } pub fn get_current_time(&mut self, precision: TimePrecision) -> Instant { From b2b3af1b4dcdd33d081b7dc668d7a239d2c940dc Mon Sep 17 00:00:00 2001 From: David Edey Date: Sat, 20 Apr 2024 16:40:21 +0100 Subject: [PATCH 05/18] tweak: Small tweaks to `evaluate_eager_macros` --- .../src/{eager_stringify.rs => eager.rs} | 15 ++++--- sbor-derive/src/lib.rs | 43 +++++++++++++++++-- sbor/src/lib.rs | 4 +- sbor/src/versioned.rs | 5 +-- 4 files changed, 53 insertions(+), 14 deletions(-) rename sbor-derive/src/{eager_stringify.rs => eager.rs} (79%) diff --git a/sbor-derive/src/eager_stringify.rs b/sbor-derive/src/eager.rs similarity index 79% rename from sbor-derive/src/eager_stringify.rs rename to sbor-derive/src/eager.rs index 4ceab2173f4..4991c80d8f0 100644 --- a/sbor-derive/src/eager_stringify.rs +++ b/sbor-derive/src/eager.rs @@ -12,16 +12,18 @@ pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { is_eager_stringify_followed_by_exclamation_mark(&token_tree, &mut tokens) { let exclamation_mark = tokens.next().unwrap(); - let Some(next_token_tree) = tokens.next() else { - break; - }; - if let proc_macro::TokenTree::Group(group) = next_token_tree { + let next_token_tree = tokens.next(); + if let Some(proc_macro::TokenTree::Group(group)) = &next_token_tree { expanded.extend(stringify_tokens(eager_stringify_ident_span, group.stream())); } else { // If we get eager_stringify! but then it doesn't get followed by a group, then add the token back which we've just consumed expanded.extend(core::iter::once(token_tree)); expanded.extend(core::iter::once(exclamation_mark)); - expanded.extend(core::iter::once(next_token_tree)); + if let Some(next_token_tree) = next_token_tree { + expanded.extend(core::iter::once(next_token_tree)); + } else { + break; + } } } else { if let proc_macro::TokenTree::Group(group) = token_tree { @@ -37,6 +39,9 @@ pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { return expanded; } +// TODO: Add eager_concat so that this works: +// `#[doc = eager_concat!("Hello world ", eager_stringify!(Bob), " XYZ")]` +// NB: Could also support eager_ident and eager_literal fn is_eager_stringify_followed_by_exclamation_mark( current: &TokenTree, tokens: &mut core::iter::Peekable<::IntoIter>, diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 5da94c0eaed..c365580b1ce 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use std::str::FromStr; -mod eager_stringify; +mod eager; /// Derive code that returns the value kind. #[proc_macro_derive(Categorize, attributes(sbor))] @@ -64,10 +64,45 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { TokenStream::from_str(&"// Empty PermitSborAttributes expansion").unwrap() } -/// Allows the `eager_stringify!` macro to stringify its contents immediately. +/// This macro causes the `eager_stringify!` pseudo-macro to stringify its contents immediately. +/// Similar to the `paste!` macro, this is intended for use in declarative macros. +/// +/// It is particularly useful in scenarios where `paste` doesn't work - in particular, to +/// create non-idents, or to create non-doc attribute string content, which paste cannot do, e.g.: +/// ```rust +/// // Inside a macro_rules! expression: +/// evaluate_eager_macros!{ +/// #[sbor(as_type = eager_stringify!($my_inner_type))] +/// $vis struct $my_type($my_inner_type) +/// } +/// ``` +/// +/// ## Use with docs +/// +/// You can combine `eager_stringify!` with the `paste` macro's ability to concat doc string literals together, +/// as follows. In some cases, `paste` can be used without `eager_stringify!` for the same effect. +/// ```rust +/// // Inside a macro_rules! expression: +/// evaluate_eager_macros!{paste!{ +/// #[doc = "This is the [`" eager_stringify!($my_type $(< $( $generic_type ),+ >)?) "`] type."] +/// $vis struct $my_type $(< $( $generic_type ),+ >)?( +/// $my_inner_type $(< $( $generic_type ),+ >)? +/// ) +/// }} +/// ``` +/// +/// ## Future extensions +/// In future, we could add further eager utilities: +/// * Enable recursion, e.g. `eager_stringify!(Hello eager_stringify!($my_world))` gives `"Hello \"World\""` +/// * An `eager_concat!` which converts its immediate children to strings and concats them without spaces, e.g. `eager_concat!("Hello " eager_stringify!($my_world) ". My " $world " is cool!")` gives `"Hello World. My World is cool!"` +/// * An `eager_format!` where `eager_format!(lowercase, X Y Z)` gives `"x y z"`. +/// * An `eager_ident!` which is like `eager_concat!` but converts to an ident. +/// * An `eager_literal!` which is like `eager_concat!` but converts to a literal. +/// * An `eager_tokens!` which is like `eager_concat!` but converts to a token stream. This would be the most general/powerful. +/// ``` #[proc_macro] -pub fn enable_eager_stringify(token_stream: TokenStream) -> TokenStream { - eager_stringify::replace_recursive(token_stream) +pub fn evaluate_eager_macros(token_stream: TokenStream) -> TokenStream { + eager::replace_recursive(token_stream) } const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind"; diff --git a/sbor/src/lib.rs b/sbor/src/lib.rs index 91a429b4c29..efacb47b4d6 100644 --- a/sbor/src/lib.rs +++ b/sbor/src/lib.rs @@ -66,7 +66,7 @@ pub use versioned::*; // Re-export derives extern crate sbor_derive; pub use sbor_derive::{ - enable_eager_stringify, BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, + evaluate_eager_macros, BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, Categorize, Decode, Describe, Encode, PermitSborAttributes, Sbor, }; @@ -90,9 +90,9 @@ pub mod prelude { pub use radix_rust::prelude::*; // Exports from current crate - pub use crate::enable_eager_stringify; pub use crate::encoded_wrappers::{RawPayload as SborRawPayload, RawValue as SborRawValue}; pub use crate::enum_variant::FixedEnumVariant as SborFixedEnumVariant; + pub use crate::evaluate_eager_macros; pub use crate::path::{SborPath, SborPathBuf}; pub use crate::representations; pub use crate::schema::prelude::*; diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index 943eb852d0a..8348bec6a83 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -267,7 +267,7 @@ macro_rules! define_versioned { $(,)? // Optional trailing comma } ) => { - $crate::enable_eager_stringify! { + $crate::evaluate_eager_macros! { $crate::paste::paste! { // Create inline sub-macros to handle the type generics nested inside // iteration over previous_versions @@ -412,9 +412,8 @@ macro_rules! define_versioned { } } - $(#[$attributes])* #[derive([<$versioned_name _PermitSborAttributes>])] - #[sbor(type_name = eager_stringify!($versioned_name))] + $(#[$attributes])* $vis enum $versions_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? { $($( From 4e8d53bb9633d250ceed3728a67d5ca6d3bc4ee6 Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 00:10:32 +0100 Subject: [PATCH 06/18] fix: Fix sbor as_type encode which wasn't saved --- sbor-derive-common/src/encode.rs | 13 +-- sbor-derive/src/eager.rs | 22 ++--- sbor-derive/src/lib.rs | 94 +++++++++++++++++--- sbor-tests/tests/as_type.rs | 148 +++++++++++++++++++++++++++++++ sbor/src/basic.rs | 1 + sbor/src/lib.rs | 6 +- sbor/src/versioned.rs | 20 ++++- 7 files changed, 269 insertions(+), 35 deletions(-) create mode 100644 sbor-tests/tests/as_type.rs diff --git a/sbor-derive-common/src/encode.rs b/sbor-derive-common/src/encode.rs index 1c60ac366fa..c7a938c4857 100644 --- a/sbor-derive-common/src/encode.rs +++ b/sbor-derive-common/src/encode.rs @@ -18,12 +18,15 @@ pub fn handle_encode( trace!("handle_encode() starts"); let parsed: DeriveInput = parse2(input)?; - let is_transparent = is_transparent(&parsed.attrs)?; - let output = if is_transparent { - handle_transparent_encode(parsed, context_custom_value_kind)? - } else { - handle_normal_encode(parsed, context_custom_value_kind)? + let output = match get_derive_strategy(&parsed.attrs)? { + DeriveStrategy::Normal => handle_normal_encode(parsed, context_custom_value_kind)?, + DeriveStrategy::Transparent => { + handle_transparent_encode(parsed, context_custom_value_kind)? + } + DeriveStrategy::DeriveAs { + as_type, as_ref, .. + } => handle_encode_as(parsed, context_custom_value_kind, &as_type, &as_ref)?, }; #[cfg(feature = "trace")] diff --git a/sbor-derive/src/eager.rs b/sbor-derive/src/eager.rs index 4991c80d8f0..22d5df42cb8 100644 --- a/sbor-derive/src/eager.rs +++ b/sbor-derive/src/eager.rs @@ -8,13 +8,11 @@ pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { break; }; - if let Some(eager_stringify_ident_span) = - is_eager_stringify_followed_by_exclamation_mark(&token_tree, &mut tokens) - { + if is_eager_stringify_followed_by_exclamation_mark(&token_tree, &mut tokens) { let exclamation_mark = tokens.next().unwrap(); let next_token_tree = tokens.next(); if let Some(proc_macro::TokenTree::Group(group)) = &next_token_tree { - expanded.extend(stringify_tokens(eager_stringify_ident_span, group.stream())); + expanded.extend(stringify_tokens(group.span(), group.stream())); } else { // If we get eager_stringify! but then it doesn't get followed by a group, then add the token back which we've just consumed expanded.extend(core::iter::once(token_tree)); @@ -26,6 +24,7 @@ pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { } } } else { + // If it's a group, run replace on its contents recursively. if let proc_macro::TokenTree::Group(group) = token_tree { expanded.extend(core::iter::once(TokenTree::Group(proc_macro::Group::new( group.delimiter(), @@ -39,26 +38,23 @@ pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { return expanded; } -// TODO: Add eager_concat so that this works: -// `#[doc = eager_concat!("Hello world ", eager_stringify!(Bob), " XYZ")]` -// NB: Could also support eager_ident and eager_literal fn is_eager_stringify_followed_by_exclamation_mark( current: &TokenTree, tokens: &mut core::iter::Peekable<::IntoIter>, -) -> Option { +) -> bool { let TokenTree::Ident(ident) = ¤t else { - return None; + return false; }; if ident.to_string() != "eager_stringify" { - return None; + return false; } let Some(TokenTree::Punct(punct)) = tokens.peek() else { - return None; + return false; }; if punct.as_char() != '!' { - return None; + return false; } - Some(ident.span()) + true } fn stringify_tokens(span: Span, token_stream: TokenStream) -> TokenStream { diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index c365580b1ce..69cb7ce3ad2 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -64,6 +64,8 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { TokenStream::from_str(&"// Empty PermitSborAttributes expansion").unwrap() } +/// NOTE: This should probably be moved out of sbor to its own crate. +/// /// This macro causes the `eager_stringify!` pseudo-macro to stringify its contents immediately. /// Similar to the `paste!` macro, this is intended for use in declarative macros. /// @@ -71,7 +73,7 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// create non-idents, or to create non-doc attribute string content, which paste cannot do, e.g.: /// ```rust /// // Inside a macro_rules! expression: -/// evaluate_eager_macros!{ +/// eager_replace!{ /// #[sbor(as_type = eager_stringify!($my_inner_type))] /// $vis struct $my_type($my_inner_type) /// } @@ -83,7 +85,7 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// as follows. In some cases, `paste` can be used without `eager_stringify!` for the same effect. /// ```rust /// // Inside a macro_rules! expression: -/// evaluate_eager_macros!{paste!{ +/// eager_replace!{paste!{ /// #[doc = "This is the [`" eager_stringify!($my_type $(< $( $generic_type ),+ >)?) "`] type."] /// $vis struct $my_type $(< $( $generic_type ),+ >)?( /// $my_inner_type $(< $( $generic_type ),+ >)? @@ -91,17 +93,87 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// }} /// ``` /// -/// ## Future extensions -/// In future, we could add further eager utilities: -/// * Enable recursion, e.g. `eager_stringify!(Hello eager_stringify!($my_world))` gives `"Hello \"World\""` -/// * An `eager_concat!` which converts its immediate children to strings and concats them without spaces, e.g. `eager_concat!("Hello " eager_stringify!($my_world) ". My " $world " is cool!")` gives `"Hello World. My World is cool!"` -/// * An `eager_format!` where `eager_format!(lowercase, X Y Z)` gives `"x y z"`. -/// * An `eager_ident!` which is like `eager_concat!` but converts to an ident. -/// * An `eager_literal!` which is like `eager_concat!` but converts to a literal. -/// * An `eager_tokens!` which is like `eager_concat!` but converts to a token stream. This would be the most general/powerful. +/// ## Future vision +/// +/// The below describes a future vision which would expand this macro into a powerful +/// but simple general-purpose tool to ease building declarative macros. +/// +/// Effectively it would be a more powerful version of [paste](https://github.com/dtolnay/paste) +/// whilst bringing the power of [quote](https://docs.rs/quote/latest/quote/)'s variable +/// substitution to declarative macros. +/// +/// This approach neatly solves the following cases: +/// * [Pasting into non-doc attributes](https://github.com/dtolnay/paste/issues/40#issuecomment-2062953012) +/// * Simplify handling sub-repetition which currently needs an internal `macro_rules!` definition [per this stack overflow post](https://stackoverflow.com/a/73543948) +/// * Improved readability of long procedural macros through substitution of repeated segments +/// +/// ### More types +/// +/// Output `string`, `ident`, `literal` or just a token stream: +/// * `[!EAGER!string](X Y " " Z)` gives "XY Z" concats each argument stringified without spaces +/// (except removing the quotes around string literals). Spaces can be added with " ". +/// * `[!EAGER!ident]` does the same for idents +/// * `[!EAGER!literal]` does the same for literals +/// * `[!EAGER!]` parses and outputs a token stream. +/// This would be a no-op, except it's not when combined with other features below: +/// variables, nested calls, etc. +/// +/// ### Variables + Cleaner Coding +/// +/// You can define variables starting with `#` which can be used inside other eager evaluations. +/// +/// The command `[!EAGER!define:#MyZ:ident](ZZZ)` doesn't output anything, but sets `#MyZ` +/// to be the given `Ident`. Then, insides any other eager macro, `#MyZ` outputs the given ident. +/// +/// This would also work for literals, strings and token streams. +/// +/// ### Nesting +/// +/// Would add support for nesting [!EAGER!] calls inside eachother - although +/// using variables might well be cleaner code. +/// +/// ### String case conversion +/// +/// Could in future support case conversion like [paste](https://docs.rs/paste/latest/paste/#case-conversion). +/// +/// ### Alternative EAGER tag +/// +/// Would allow a user to specify their own tag instead of `EAGER`. This could: +/// * Facilitate nesting `eager_replace` calls: `eager_replace!{ [!tag = INNER_EAGER] code...}` +/// * Allow using shorthand e.g. `E` instead +/// +/// ### Example of future vision +/// ```rust +/// macro_rules! impl_marker_traits { +/// { +/// $(#[$attributes:meta])* +/// $vis:vis $type_name:ident +/// // Arbitrary generics +/// $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)? +/// [ +/// $($trait:ident),* +/// $(,) // Optional trailing comma +/// ] +/// } => {eager_replace!{ +/// [!EAGER!define:#ImplGenerics]($(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?) +/// [!EAGER!define:#TypeGenerics]($(< $( $lt ),+ >)?) +/// +/// // Output for each marker trait +/// $( +/// // NOTE: [!EAGER] outputs a token stream, not just a token tree +/// // so it can be used for outputting things like +/// // enum variants and attributes where a declarative macro +/// // couldn't be used +/// [!EAGER!]{ impl #ImplGenerics $trait for Type #TypeGenerics } +/// { +/// // Empty trait body +/// } +/// )* +/// }} +/// } /// ``` #[proc_macro] -pub fn evaluate_eager_macros(token_stream: TokenStream) -> TokenStream { +pub fn eager_replace(token_stream: TokenStream) -> TokenStream { eager::replace_recursive(token_stream) } diff --git a/sbor-tests/tests/as_type.rs b/sbor-tests/tests/as_type.rs new file mode 100644 index 00000000000..12e031b038e --- /dev/null +++ b/sbor-tests/tests/as_type.rs @@ -0,0 +1,148 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use sbor::rust::prelude::*; +use sbor::*; + +#[derive(Sbor, PartialEq, Eq, Debug)] +#[sbor( + as_type = "u32", + as_ref = "&self.state", + from_value = "Self { state: value }" +)] +pub struct TestStructNamed { + pub state: u32, +} + +#[derive(Sbor, PartialEq, Eq, Debug)] +#[sbor( + as_type = "u32", + as_ref = "&self.state", + from_value = "Self { state: value }" +)] +#[sbor(transparent_name)] +pub struct TestStructTransparentNamed { + pub state: u32, +} + +#[derive(Sbor, PartialEq, Eq, Debug)] +#[sbor( + as_type = "u32", + as_ref = "&self.state", + from_value = "Self { state: value }" +)] +#[sbor(type_name = "HEY")] +pub struct TestStructRenamed { + pub state: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Sbor)] +#[sbor(as_type = "GenericModelVersions < T >")] +struct VersionedGenericModel { + inner: Option>, +} + +impl VersionedGenericModel { + pub fn new(inner: GenericModelVersions) -> Self { + Self { inner: Some(inner) } + } +} + +impl AsRef> for VersionedGenericModel { + fn as_ref(&self) -> &GenericModelVersions { + self.inner.as_ref().unwrap() + } +} +impl From> for VersionedGenericModel { + fn from(value: GenericModelVersions) -> Self { + Self::new(value) + } +} +#[derive(Debug, Clone, PartialEq, Eq, Sbor)] +enum GenericModelVersions { + V1(T), +} + +#[test] +fn categorize_is_correct() { + // With inner u32 + assert_eq!(get_value_kind::(), ValueKind::U32); + assert_eq!( + get_value_kind::>(), + ValueKind::Enum + ); +} + +fn get_value_kind>() -> ValueKind { + T::value_kind() +} + +#[test] +fn encode_is_correct() { + // With inner u32 + let inner_value = 45u32; + assert_eq!( + basic_encode(&TestStructNamed { state: inner_value }).unwrap(), + basic_encode(&inner_value).unwrap() + ); + // With generic enum using AsRef + assert_eq!( + basic_encode(&VersionedGenericModel::new(GenericModelVersions::V1(45u32))).unwrap(), + basic_encode(&GenericModelVersions::V1(45u32)).unwrap() + ); +} + +#[test] +fn decode_is_correct() { + // With inner u32 + let inner_value = 45u32; + let payload = basic_encode(&inner_value).unwrap(); + assert_eq!( + basic_decode::(&payload).unwrap(), + TestStructNamed { state: inner_value } + ); + // With generic enum using AsRef + let payload = basic_encode(&GenericModelVersions::V1(45u32)).unwrap(); + assert_eq!( + basic_decode::>(&payload).unwrap(), + VersionedGenericModel::new(GenericModelVersions::V1(45u32)) + ); +} + +#[test] +fn describe_is_correct() { + check_identical_types::(Some("TestStructNamed")); + check_identical_types::(None); + check_identical_types::(Some("HEY")); + check_identical_types::, GenericModelVersions>(Some( + "VersionedGenericModel", + )); +} + +fn check_identical_types, T2: Describe>( + name: Option<&'static str>, +) { + let (type_id1, schema1) = generate_full_schema_from_single_type::(); + let (type_id2, schema2) = generate_full_schema_from_single_type::(); + + assert_eq!( + schema1.v1().resolve_type_kind(type_id1), + schema2.v1().resolve_type_kind(type_id2) + ); + assert_eq!( + schema1 + .v1() + .resolve_type_metadata(type_id1) + .unwrap() + .clone(), + schema2 + .v1() + .resolve_type_metadata(type_id2) + .unwrap() + .clone() + .with_name(name.map(|name| Cow::Borrowed(name))) + ); + assert_eq!( + schema1.v1().resolve_type_validation(type_id1), + schema2.v1().resolve_type_validation(type_id2) + ); +} diff --git a/sbor/src/basic.rs b/sbor/src/basic.rs index c90e7367c14..486836e1b38 100644 --- a/sbor/src/basic.rs +++ b/sbor/src/basic.rs @@ -30,6 +30,7 @@ pub type BasicDecoder<'a> = VecDecoder<'a, NoCustomValueKind>; pub type BasicTraverser<'a> = VecTraverser<'a, NoCustomTraversal>; pub type BasicValue = Value; pub type BasicValueKind = ValueKind; +pub type BasicEnumVariantValue = EnumVariantValue; // 5b for (basic) [5b]or - (90 in decimal) pub const BASIC_SBOR_V1_PAYLOAD_PREFIX: u8 = 0x5b; diff --git a/sbor/src/lib.rs b/sbor/src/lib.rs index efacb47b4d6..4898b171e33 100644 --- a/sbor/src/lib.rs +++ b/sbor/src/lib.rs @@ -66,8 +66,8 @@ pub use versioned::*; // Re-export derives extern crate sbor_derive; pub use sbor_derive::{ - evaluate_eager_macros, BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, - Categorize, Decode, Describe, Encode, PermitSborAttributes, Sbor, + eager_replace, BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, Categorize, + Decode, Describe, Encode, PermitSborAttributes, Sbor, }; // This is to make derives work within this crate. @@ -90,9 +90,9 @@ pub mod prelude { pub use radix_rust::prelude::*; // Exports from current crate + pub use crate::eager_replace; pub use crate::encoded_wrappers::{RawPayload as SborRawPayload, RawValue as SborRawValue}; pub use crate::enum_variant::FixedEnumVariant as SborFixedEnumVariant; - pub use crate::evaluate_eager_macros; pub use crate::path::{SborPath, SborPathBuf}; pub use crate::representations; pub use crate::schema::prelude::*; diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index 8348bec6a83..df39e76e854 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -267,7 +267,7 @@ macro_rules! define_versioned { $(,)? // Optional trailing comma } ) => { - $crate::evaluate_eager_macros! { + $crate::eager_replace! { $crate::paste::paste! { // Create inline sub-macros to handle the type generics nested inside // iteration over previous_versions @@ -306,8 +306,9 @@ macro_rules! define_versioned { use $crate::PermitSborAttributes as [<$versioned_name _PermitSborAttributes>]; #[derive([<$versioned_name _PermitSborAttributes>])] - #[sbor(as_type = eager_stringify!($versions_name $(< $( $lt ),+ >)?))] $(#[$attributes])* + // Needs to go below $attributes so that a #[derive(Sbor)] in the attributes can see it. + #[sbor(as_type = eager_stringify!($versions_name $(< $( $lt ),+ >)?))] /// If you wish to get access to match on the versions, use `.as_ref()` or `.as_mut()`. $vis struct $versioned_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? { @@ -650,7 +651,7 @@ mod tests { define_single_versioned!( /// This is some rust doc as an example annotation - #[derive(Debug, Clone, PartialEq, Eq)] + #[derive(Debug, Clone, PartialEq, Eq, Sbor)] VersionedGenericModel(GenericModelVersions) => GenericModel = GenericModelV1 ); @@ -664,5 +665,18 @@ mod tests { v1_model ); assert_eq!(versioned, versioned_2); + let encoded = basic_encode(&versioned).unwrap(); + let expected = basic_encode(&BasicEnumVariantValue { + // GenericModelVersions + discriminator: 1, + fields: vec![ + // GenericModelV1 + Value::Tuple { + fields: vec![Value::U64 { value: 51 }], + }, + ], + }) + .unwrap(); + assert_eq!(encoded, expected); } } From e8e1b7f44ed0a707f3b1c97f527c464b71905c97 Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 00:28:14 +0100 Subject: [PATCH 07/18] tweak: Implement SBOR for Nibble using transparent --- .../src/state_tree/tree_store.rs | 38 ------------------- .../src/state_tree/types.rs | 3 +- 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/radix-substate-store-impls/src/state_tree/tree_store.rs b/radix-substate-store-impls/src/state_tree/tree_store.rs index 72360b95a8a..9ca9fdab71a 100644 --- a/radix-substate-store-impls/src/state_tree/tree_store.rs +++ b/radix-substate-store-impls/src/state_tree/tree_store.rs @@ -367,44 +367,6 @@ pub fn encode_key(key: &StoredTreeNodeKey) -> Vec { // structures can simply use SBOR, with only the most efficiency-sensitive parts having custom // codecs, implemented below: -impl Categorize for Nibble { - #[inline] - fn value_kind() -> ValueKind { - ValueKind::U8 - } -} - -impl> Encode for Nibble { - #[inline] - fn encode_value_kind(&self, encoder: &mut E) -> Result<(), EncodeError> { - encoder.write_value_kind(Self::value_kind()) - } - - #[inline] - fn encode_body(&self, encoder: &mut E) -> Result<(), EncodeError> { - u8::from(*self).encode_body(encoder) - } -} - -impl> Decode for Nibble { - fn decode_body_with_value_kind( - decoder: &mut D, - value_kind: ValueKind, - ) -> Result { - Ok(Nibble::from(u8::decode_body_with_value_kind( - decoder, value_kind, - )?)) - } -} - -impl> Describe for Nibble { - const TYPE_ID: RustTypeId = RustTypeId::WellKnown(basic_well_known_types::U8_TYPE); - - fn type_data() -> TypeData { - basic_well_known_types::u8_type_data() - } -} - impl Categorize for NibblePath { #[inline] fn value_kind() -> ValueKind { diff --git a/radix-substate-store-impls/src/state_tree/types.rs b/radix-substate-store-impls/src/state_tree/types.rs index 9446ca873e3..1e4218a3828 100644 --- a/radix-substate-store-impls/src/state_tree/types.rs +++ b/radix-substate-store-impls/src/state_tree/types.rs @@ -356,7 +356,8 @@ impl IteratedLeafKey for LeafKey { pub type Version = u64; // SOURCE: https://github.com/aptos-labs/aptos-core/blob/1.0.4/types/src/nibble/mod.rs#L20 -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Sbor)] +#[sbor(transparent, transparent_name)] pub struct Nibble(u8); impl From for Nibble { From d83f58146a55fe679358c33387d3ea5c79fca8fc Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 13:05:10 +0100 Subject: [PATCH 08/18] fix: Fix comments --- sbor/src/versioned.rs | 58 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index df39e76e854..d8f94771ffc 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -1,9 +1,5 @@ use crate::internal_prelude::*; -// !!!!!!! TODO !!!!!!!!! -// - Add #[sbor(as = '')] in order to encode Versioned as Versions -// - Add sbor tests to equate the schema - /// A trait implemented by versioned types created via [`define_versioned`] and [`define_single_versioned`]. /// /// A versioned type is a type wrapping an enum, this enum is the associated type [`Versioned::Versions`], @@ -665,8 +661,15 @@ mod tests { v1_model ); assert_eq!(versioned, versioned_2); - let encoded = basic_encode(&versioned).unwrap(); - let expected = basic_encode(&BasicEnumVariantValue { + } + + #[test] + pub fn verify_sbor_equivalence() { + // Value model + let v1_model: GenericModel<_> = GenericModelV1(51u64); + let versions = GenericModelVersions::V1(v1_model.clone()); + let versioned = VersionedGenericModel::from(v1_model.clone()); + let expected_sbor_value = BasicEnumVariantValue { // GenericModelVersions discriminator: 1, fields: vec![ @@ -675,8 +678,45 @@ mod tests { fields: vec![Value::U64 { value: 51 }], }, ], - }) - .unwrap(); - assert_eq!(encoded, expected); + }; + let encoded_versioned = basic_encode(&versioned).unwrap(); + let encoded_versions = basic_encode(&versions).unwrap(); + let expected = basic_encode(&expected_sbor_value).unwrap(); + assert_eq!(encoded_versioned, expected); + assert_eq!(encoded_versions, expected); + + // Type model + check_identical_types::, GenericModelVersions>(Some( + "VersionedGenericModel", + )); + } + + fn check_identical_types, T2: Describe>( + name: Option<&'static str>, + ) { + let (type_id1, schema1) = generate_full_schema_from_single_type::(); + let (type_id2, schema2) = generate_full_schema_from_single_type::(); + + assert_eq!( + schema1.v1().resolve_type_kind(type_id1), + schema2.v1().resolve_type_kind(type_id2) + ); + assert_eq!( + schema1 + .v1() + .resolve_type_metadata(type_id1) + .unwrap() + .clone(), + schema2 + .v1() + .resolve_type_metadata(type_id2) + .unwrap() + .clone() + .with_name(name.map(|name| Cow::Borrowed(name))) + ); + assert_eq!( + schema1.v1().resolve_type_validation(type_id1), + schema2.v1().resolve_type_validation(type_id2) + ); } } From cf0339246c131de50d9ad74b4ae6069c24f96782 Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 18:58:39 +0100 Subject: [PATCH 09/18] fix: Fix versioned discriminators to be 0-indexed --- .../src/data/scrypto/custom_schema.rs | 1 + sbor-derive-common/src/categorize.rs | 41 +-- sbor-derive-common/src/decode.rs | 38 ++- sbor-derive-common/src/describe.rs | 20 +- sbor-derive-common/src/encode.rs | 50 +-- sbor-derive-common/src/utils.rs | 286 ++++++++++++------ sbor/src/versioned.rs | 10 +- 7 files changed, 274 insertions(+), 172 deletions(-) diff --git a/radix-common/src/data/scrypto/custom_schema.rs b/radix-common/src/data/scrypto/custom_schema.rs index 159b3a18d4c..d0533bb84b7 100644 --- a/radix-common/src/data/scrypto/custom_schema.rs +++ b/radix-common/src/data/scrypto/custom_schema.rs @@ -2,6 +2,7 @@ use crate::internal_prelude::*; pub type ScryptoTypeKind = TypeKind; pub type VersionedScryptoSchema = VersionedSchema; +pub type ScryptoSchema = Schema; pub type ScryptoTypeData = TypeData; /// A schema for the values that a codec can decode / views as valid diff --git a/sbor-derive-common/src/categorize.rs b/sbor-derive-common/src/categorize.rs index 5a97ef29a61..3be5869b0b0 100644 --- a/sbor-derive-common/src/categorize.rs +++ b/sbor-derive-common/src/categorize.rs @@ -73,37 +73,40 @@ fn handle_normal_categorize( } } Data::Enum(DataEnum { variants, .. }) => { - let discriminator_mapping = get_variant_discriminator_mapping(&attrs, &variants)?; - let (discriminator_match_arms, field_count_match_arms): (Vec<_>, Vec<_>) = variants + let EnumVariantsData { + source_variants, .. + } = process_enum_variants(&attrs, &variants)?; + let (discriminator_match_arms, field_count_match_arms): (Vec<_>, Vec<_>) = source_variants .iter() - .enumerate() - .map(|(i, v)| { - let v_id = &v.ident; - let discriminator = &discriminator_mapping[&i]; - - let FieldsData { - unskipped_field_count, - empty_fields_unpacking, - .. - } = process_fields(&v.fields)?; - - Ok(match discriminator { - VariantDiscriminator::Expr(discriminator) => { + .map(|source_variant| { + match source_variant { + SourceVariantData::Reachable(VariantData { source_variant, discriminator, fields_data, .. }) => { + let v_id = &source_variant.ident; + let FieldsData { + unskipped_field_count, + empty_fields_unpacking, + .. + } = &fields_data; ( quote! { Self::#v_id #empty_fields_unpacking => #discriminator, }, quote! { Self::#v_id #empty_fields_unpacking => #unskipped_field_count, }, ) }, - VariantDiscriminator::IgnoreAsUnreachable => { - let panic_message = format!("Variant with index {i} ignored as unreachable"); + SourceVariantData::Unreachable(UnreachableVariantData { source_variant, fields_data, ..}) => { + let v_id = &source_variant.ident; + let FieldsData { + empty_fields_unpacking, + .. + } = &fields_data; + let panic_message = format!("Variant {} ignored as unreachable", v_id.to_string()); ( quote! { Self::#v_id #empty_fields_unpacking => panic!(#panic_message), }, quote! { Self::#v_id #empty_fields_unpacking => panic!(#panic_message), }, ) }, - }) + } }) - .collect::>>()? + .collect::>() .into_iter() .unzip(); diff --git a/sbor-derive-common/src/decode.rs b/sbor-derive-common/src/decode.rs index b32da394c08..c0b80dde0fe 100644 --- a/sbor-derive-common/src/decode.rs +++ b/sbor-derive-common/src/decode.rs @@ -1,4 +1,3 @@ -use itertools::Itertools; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; @@ -167,29 +166,26 @@ pub fn handle_normal_decode( } } Data::Enum(DataEnum { variants, .. }) => { - let discriminator_mapping = get_variant_discriminator_mapping(&attrs, &variants)?; - let match_arms = variants + let EnumVariantsData { sbor_variants, .. } = process_enum_variants(&attrs, &variants)?; + let match_arms = sbor_variants .iter() - .enumerate() - .map(|(i, v)| { - let v_id = &v.ident; - let discriminator = &discriminator_mapping[&i]; - let decode_fields_content = - decode_fields_content(quote! { Self::#v_id }, &v.fields)?; - - Ok(match discriminator { - VariantDiscriminator::Expr(discriminator) => Some(quote! { - #discriminator => { + .map( + |VariantData { + source_variant, + discriminator_pattern, + .. + }| + -> Result<_> { + let v_id = &source_variant.ident; + let decode_fields_content = + decode_fields_content(quote! { Self::#v_id }, &source_variant.fields)?; + Ok(quote! { + #discriminator_pattern => { #decode_fields_content } - }), - VariantDiscriminator::IgnoreAsUnreachable => { - // Don't output any decoder - None - } - }) - }) - .filter_map_ok(|x| x) + }) + }, + ) .collect::>>()?; // Note: We use #[deny(unreachable_patterns)] to protect against users diff --git a/sbor-derive-common/src/describe.rs b/sbor-derive-common/src/describe.rs index 0ed3bcef488..24ff0a03744 100644 --- a/sbor-derive-common/src/describe.rs +++ b/sbor-derive-common/src/describe.rs @@ -1,4 +1,3 @@ -use itertools::Itertools; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::*; @@ -251,30 +250,24 @@ fn handle_normal_describe( } }, Data::Enum(DataEnum { variants, .. }) => { - let discriminator_mapping = get_variant_discriminator_mapping(&attrs, &variants)?; + let EnumVariantsData { sbor_variants, .. } = process_enum_variants(&attrs, &variants)?; let mut all_field_types = Vec::new(); - let match_arms = variants + let match_arms = sbor_variants .iter() - .enumerate() - .map(|(i, v)| { - let variant_name_str = v.ident.to_string(); - - let discriminator = match &discriminator_mapping[&i] { - VariantDiscriminator::Expr(d) => d, - VariantDiscriminator::IgnoreAsUnreachable => return Ok(None), - }; + .map(|VariantData { discriminator, source_variant, fields_data, .. }| { + let variant_name_str = source_variant.ident.to_string(); let FieldsData { unskipped_field_types, unskipped_field_name_strings, .. - } = process_fields(&v.fields)?; + } = fields_data; all_field_types.extend_from_slice(&unskipped_field_types); - let variant_type_data = match &v.fields { + let variant_type_data = match &source_variant.fields { Fields::Named(FieldsNamed { .. }) => { quote! { ::sbor::TypeData::struct_with_named_fields( @@ -305,7 +298,6 @@ fn handle_normal_describe( #discriminator => #variant_type_data, })) }) - .filter_map_ok(|x| x) .collect::>>()?; let unique_field_types = get_unique_types(&all_field_types); diff --git a/sbor-derive-common/src/encode.rs b/sbor-derive-common/src/encode.rs index c7a938c4857..54bc3972656 100644 --- a/sbor-derive-common/src/encode.rs +++ b/sbor-derive-common/src/encode.rs @@ -148,23 +148,26 @@ pub fn handle_normal_encode( } } Data::Enum(DataEnum { variants, .. }) => { - let discriminator_mapping = get_variant_discriminator_mapping(&attrs, &variants)?; - let match_arms = variants + let EnumVariantsData { + source_variants, .. + } = process_enum_variants(&attrs, &variants)?; + let match_arms = source_variants .iter() - .enumerate() - .map(|(i, v)| { - let v_id = &v.ident; - let discriminator = &discriminator_mapping[&i]; - - let FieldsData { - unskipped_field_count, - fields_unpacking, - unskipped_unpacked_field_names, - .. - } = process_fields(&v.fields)?; - - Ok(match discriminator { - VariantDiscriminator::Expr(discriminator) => { + .map(|source_variant| { + Ok(match source_variant { + SourceVariantData::Reachable(VariantData { + source_variant, + discriminator, + fields_data, + .. + }) => { + let v_id = &source_variant.ident; + let FieldsData { + unskipped_field_count, + fields_unpacking, + unskipped_unpacked_field_names, + .. + } = fields_data; quote! { Self::#v_id #fields_unpacking => { encoder.write_discriminator(#discriminator)?; @@ -173,11 +176,20 @@ pub fn handle_normal_encode( } } } - VariantDiscriminator::IgnoreAsUnreachable => { + SourceVariantData::Unreachable(UnreachableVariantData { + source_variant, + fields_data, + .. + }) => { + let v_id = &source_variant.ident; + let FieldsData { + empty_fields_unpacking, + .. + } = &fields_data; let panic_message = - format!("Variant with index {i} ignored as unreachable"); + format!("Variant {} ignored as unreachable", v_id.to_string()); quote! { - Self::#v_id #fields_unpacking => panic!(#panic_message), + Self::#v_id #empty_fields_unpacking => panic!(#panic_message), } } }) diff --git a/sbor-derive-common/src/utils.rs b/sbor-derive-common/src/utils.rs index 1e79c1d7547..d09c75faa3a 100644 --- a/sbor-derive-common/src/utils.rs +++ b/sbor-derive-common/src/utils.rs @@ -195,22 +195,34 @@ pub fn extract_typed_attributes( Ok(fields) } -enum VariantValue { - Byte(LitByte), - Path(Path), // EG a constant - UseDefaultIndex, - IgnoreAsUnreachable, +pub(crate) enum SourceVariantData { + Reachable(VariantData), + Unreachable(UnreachableVariantData), } -pub enum VariantDiscriminator { - Expr(Expr), - IgnoreAsUnreachable, +#[derive(Clone)] +pub(crate) struct UnreachableVariantData { + pub source_variant: Variant, + pub fields_data: FieldsData, } -pub fn get_variant_discriminator_mapping( +#[derive(Clone)] +pub(crate) struct VariantData { + pub source_variant: Variant, + pub discriminator: Expr, + pub discriminator_pattern: Pat, + pub fields_data: FieldsData, +} + +pub(crate) struct EnumVariantsData { + pub source_variants: Vec, + pub sbor_variants: Vec, +} + +pub(crate) fn process_enum_variants( enum_attributes: &[Attribute], variants: &Punctuated, -) -> Result> { +) -> Result { if variants.len() > 255 { return Err(Error::new( Span::call_site(), @@ -220,109 +232,190 @@ pub fn get_variant_discriminator_mapping( let use_repr_discriminators = get_sbor_attribute_bool_value(enum_attributes, "use_repr_discriminators")?.value(); - let variant_ids: Vec = variants.iter() - .map(|variant| -> Result { + + let mut explicit_discriminators_count = 0usize; + let mut implicit_discriminators_count = 0usize; + let mut reachable_variants_count = 0usize; + + let source_variants: Vec = variants + .iter() + .enumerate() + .map(|(i, variant)| -> Result<_> { let mut variant_attributes = extract_typed_attributes(&variant.attrs, "sbor")?; + let fields_data = process_fields(&variant.fields)?; + let source_variant = variant.clone(); if let Some(_) = variant_attributes.remove("unreachable") { - return Ok(VariantValue::IgnoreAsUnreachable); - } - if let Some(attribute) = variant_attributes.remove("discriminator") { - return Ok(match attribute { - AttributeValue::None(span) => { - return Err(Error::new(span, format!("No discriminator was provided"))); - } - AttributeValue::Path(path) => VariantValue::Path(path), - AttributeValue::Lit(literal) => parse_u8_from_literal(&literal) - .map(|b| VariantValue::Byte(LitByte::new(b, literal.span()))) - .ok_or_else(|| { - Error::new( - literal.span(), - format!("This discriminator is not a u8-convertible value"), - ) - })?, - }); + return Ok(SourceVariantData::Unreachable(UnreachableVariantData { + fields_data, + source_variant, + })); } - if use_repr_discriminators { - if let Some(discriminant) = &variant.discriminant { - let expression = &discriminant.1; - - let id = match expression { - Expr::Lit(literal_expression) => parse_u8_from_literal(&literal_expression.lit) - .map(|b| VariantValue::Byte(LitByte::new(b, literal_expression.span()))), - Expr::Path(path_expression) => { - Some(VariantValue::Path(path_expression.path.clone())) - } - _ => None, - }; - - let Some(id) = id else { - return Err(Error::new( - expression.span(), - format!("This discriminator is not a u8-convertible value or a path. Add an #[sbor(discriminator(X))] annotation with a u8-compatible literal or path to const/static variable to fix."), - )); - }; - return Ok(id); - } - } - Ok(VariantValue::UseDefaultIndex) + reachable_variants_count += 1; + let discriminator = + resolve_discriminator(use_repr_discriminators, i, variant, variant_attributes)?; + if discriminator.implicit { + implicit_discriminators_count += 1; + } else { + explicit_discriminators_count += 1; + }; + Ok(SourceVariantData::Reachable(VariantData { + discriminator: discriminator.expression, + discriminator_pattern: discriminator.pattern, + source_variant, + fields_data, + })) }) .collect::>()?; - let explicit_indices_count = variant_ids - .iter() - .filter(|v| matches!(v, VariantValue::Byte(_) | VariantValue::Path(_))) - .count(); + if explicit_discriminators_count > 0 && implicit_discriminators_count > 0 { + return Err(Error::new( + Span::call_site(), + format!("Either all or no variants must be assigned an explicit discriminator. Currently {} of {} variants have one.", explicit_discriminators_count, reachable_variants_count), + )); + } - let default_indices_count = variant_ids + let sbor_variants = source_variants .iter() - .filter(|v| matches!(v, VariantValue::UseDefaultIndex)) - .count(); + .filter_map(|source_variant| match source_variant { + SourceVariantData::Reachable(variant_data) => Some(variant_data.clone()), + SourceVariantData::Unreachable(_) => None, + }) + .collect(); - if explicit_indices_count > 0 { - if default_indices_count > 0 { - return Err(Error::new( - Span::call_site(), - format!("Either all or no variants must be assigned an id. Currently {} of {} variants have one.", explicit_indices_count, explicit_indices_count + default_indices_count), - )); + Ok(EnumVariantsData { + source_variants, + sbor_variants, + }) +} + +struct Discriminator { + expression: Expr, + pattern: Pat, + implicit: bool, +} + +impl Discriminator { + fn explicit_u8(value: &LitByte) -> Self { + Self { + expression: parse_quote!(#value), + pattern: parse_quote!(#value), + implicit: false, } - let output = variant_ids - .into_iter() - .map(|id| match id { - VariantValue::Byte(id) => VariantDiscriminator::Expr(parse_quote!(#id)), - VariantValue::Path(id) => VariantDiscriminator::Expr(parse_quote!(#id)), - VariantValue::IgnoreAsUnreachable => VariantDiscriminator::IgnoreAsUnreachable, - VariantValue::UseDefaultIndex => unreachable!("default_indices_count was 0"), - }) - .enumerate() - .collect(); + } - return Ok(output); + fn explicit_path(value: &Path) -> Self { + Self { + expression: parse_quote!(#value), + pattern: parse_quote!(#value), + implicit: false, + } } - // If no explicit indices, use default indices - let output = variant_ids - .iter() - .enumerate() - .map(|(i, id)| { - let discriminator = match id { - VariantValue::Byte(_) => unreachable!("explicit_indices_count was 0"), - VariantValue::Path(_) => unreachable!("explicit_indices_count was 0"), - VariantValue::IgnoreAsUnreachable => VariantDiscriminator::IgnoreAsUnreachable, - VariantValue::UseDefaultIndex => { - let i_as_u8 = u8::try_from(i).unwrap(); - VariantDiscriminator::Expr(parse_quote!(#i_as_u8)) + + fn explicit(expression: Expr, pattern: Pat) -> Self { + Self { + expression, + pattern, + implicit: false, + } + } + + fn implicit_u8(span: Span, index: usize) -> Option { + let value = LitByte::new(u8::try_from(index).ok()?, span); + Some(Self { + expression: parse_quote!(#value), + pattern: parse_quote!(#value), + implicit: true, + }) + } +} + +fn resolve_discriminator( + use_repr_discriminators: bool, + index: usize, + variant: &Variant, + mut variant_attributes: BTreeMap, +) -> Result { + if let Some(attribute) = variant_attributes.remove("discriminator") { + match attribute { + AttributeValue::None(span) => { + return Err(Error::new(span, format!("No discriminator was provided"))); + } + AttributeValue::Path(path) => { + return Ok(Discriminator::explicit_path(&path)); + } + AttributeValue::Lit(literal) => { + if let Some(b) = parse_u8_from_literal(&literal) { + return Ok(Discriminator::explicit_u8(&b)); + } + let expr = parse_expr_from_literal(&literal); + let pattern = parse_pattern_from_literal(&literal); + if let Some(expr) = expr { + if let Some(pattern) = pattern { + return Ok(Discriminator::explicit(expr, pattern)); + } } + return Err(Error::new( + literal.span(), + format!("This discriminator is not convertible into a u8; or convertible into both an expression and a pattern. You may want to try using a path to a constant instead for more power."), + )); + } + } + } + + if use_repr_discriminators { + if let Some(discriminant) = &variant.discriminant { + let expression = &discriminant.1; + + let parsed = match expression { + Expr::Lit(literal_expression) => parse_u8_from_literal(&literal_expression.lit) + .map(|b| Discriminator::explicit_u8(&b)), + Expr::Path(path_expression) => { + Some(Discriminator::explicit_path(&path_expression.path)) + } + _ => None, }; - (i, discriminator) - }) - .collect(); - Ok(output) + + let Some(disc) = parsed else { + return Err(Error::new( + expression.span(), + format!("This discriminator is not a u8-convertible value or a path. Add an #[sbor(discriminator(X))] annotation with a u8-compatible literal or path to const/static variable to fix."), + )); + }; + return Ok(disc); + } + } + + let implicit = Discriminator::implicit_u8(variant.span(), index) + .ok_or_else(|| Error::new(variant.span(), format!("Too many variants")))?; + + Ok(implicit) +} + +fn parse_u8_from_literal(literal: &Lit) -> Option { + match literal { + Lit::Byte(byte_literal) => Some(byte_literal.clone()), + Lit::Int(int_literal) => Some(LitByte::new( + int_literal.base10_parse::().ok()?, + literal.span(), + )), + Lit::Str(str_literal) => Some(LitByte::new( + str_literal.value().parse::().ok()?, + literal.span(), + )), + _ => None, + } +} + +fn parse_expr_from_literal(literal: &Lit) -> Option { + match literal { + Lit::Str(str_literal) => str_literal.parse().ok(), + _ => None, + } } -fn parse_u8_from_literal(literal: &Lit) -> Option { +fn parse_pattern_from_literal(literal: &Lit) -> Option { match literal { - Lit::Byte(byte_literal) => Some(byte_literal.value()), - Lit::Int(int_literal) => int_literal.base10_parse::().ok(), - Lit::Str(str_literal) => str_literal.value().parse::().ok(), + Lit::Str(str_literal) => str_literal.parse().ok(), _ => None, } } @@ -571,6 +664,7 @@ pub fn get_unique_types<'a>(types: &[syn::Type]) -> Vec { types.iter().unique().cloned().collect() } +#[derive(Clone)] pub(crate) struct FieldsData { pub unskipped_field_names: Vec, pub unskipped_field_name_strings: Vec, diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index d8f94771ffc..f87d1d57a4a 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -409,15 +409,19 @@ macro_rules! define_versioned { } } + // TODO: + // - Sadly the first version of versioned used 0-indexed discriminators (by mistake, sadly) + // - To enforce this, and prevent the API being dependent on version, we should add back + // in explicit discriminators as $version_num - 1 (by using consts) + // - This requires extensions to eager_stringify + #[derive([<$versioned_name _PermitSborAttributes>])] $(#[$attributes])* $vis enum $versions_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? { $($( - #[sbor(discriminator($version_num))] []($version_type), )*)? - #[sbor(discriminator($latest_version))] []($latest_version_type), } @@ -671,7 +675,7 @@ mod tests { let versioned = VersionedGenericModel::from(v1_model.clone()); let expected_sbor_value = BasicEnumVariantValue { // GenericModelVersions - discriminator: 1, + discriminator: 0, // V1 maps to 0 for legacy compatibility fields: vec![ // GenericModelV1 Value::Tuple { From c351677dd4ba9d4131da489c99c6420821545fa6 Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 20:24:38 +0100 Subject: [PATCH 10/18] feature: Add support for StaticMultiVersioned --- .../models/native_blueprint_state_macro.rs | 422 +++++++++++++++--- .../src/blueprints/models/payloads.rs | 21 +- sbor/src/versioned.rs | 15 +- 3 files changed, 376 insertions(+), 82 deletions(-) diff --git a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs index 6c545819103..05bacebba66 100644 --- a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs +++ b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs @@ -31,25 +31,28 @@ use crate::internal_prelude::*; /// kind: Generic, /// ident: BlueprintGenericParameterIdent, /// }, -/// // In future /// { /// kind: StaticMultiVersioned, -/// previous_versions: [V1, V2], -/// latest: V3, +/// previous_versions: { +/// 1 => { updates_to: 2 }, +/// 2 => { updates_to: 3 }, +/// }, +/// latest_version: 3, /// } /// ``` /// -/// Choosing `StaticSingleVersioned`, which will create a -/// forward-compatible enum wrapper with a single version for the content. +/// Choosing `StaticSingleVersioned`, which will create a forward-compatible enum wrapper with a single version for the content. +/// /// For Fields, it will assume the existence of a type called /// `V1` and will generate the following types: /// * `` - a type alias for the latest version (V1). /// * `Versioned` - the enum wrapper with a single version. This will be the content of `FieldPayload`. /// -/// For collection values, it will assume the existence of `V1` -/// and generate the following types: +/// For collection values, it will assume the existence of `V1` and generate the following types: /// * `` - a type alias for the latest version (V1). /// * `Versioned` - the enum wrapper with a single version. This will be the content of `EntryPayload`. +/// +/// Choosing `StaticMultiVersioned` will create the same types, but the type alias will be for the latest version. #[allow(unused)] macro_rules! declare_native_blueprint_state { ( @@ -591,6 +594,11 @@ mod helper_macros { impl $payload_type_name { + #[doc = "Delegates to [`"[]"::fully_update`]."] + pub fn fully_update(self) -> Self { + Self::of(self.content.fully_update()) + } + #[doc = "Delegates to [`"[]"::fully_update_into_latest_version`]."] pub fn fully_update_into_latest_version(self) -> $ident_core { self.content.fully_update_into_latest_version() @@ -612,19 +620,99 @@ mod helper_macros { } } - // Now implement other relevant content traits, for: - // > The "latest" type: $ident_core + // Now implement the Content trait for other relevant content types that don't already have it: + // > The internal enum: [<$ident_core Versions>] + // > The latest (and unique) version: $ident_core = [<$ident_core V $latest_version_num>] + impl $content_trait<$payload_type_name> for [<$ident_core Versions>] { + fn into_content(self) -> [] { + []::from(self).into() + } + } + impl $content_trait<$payload_type_name> for $ident_core { fn into_content(self) -> [] { self.into() } } + } + }; + ( + content_trait: $content_trait:ident, + payload_trait: $payload_trait:ident, + ident_core: $ident_core:ident, + $(#[$attributes:meta])* + struct $payload_type_name:ident = { + kind: StaticMultiVersioned, + previous_versions: [ + $($version_num:expr => { updates_to: $update_to_version_num:expr }),* + $(,)? // Optional trailing comma + ], + latest_version: $latest_version_num:expr + $(,)? + }$(,)? + ) => { + paste::paste! { + sbor::define_versioned!( + $(#[$attributes])* + pub []([<$ident_core Versions>]) { + previous_versions: [ + $($version_num => [<$ident_core V $version_num>]: { updates_to: $update_to_version_num }),* + ], + latest_version: { + $latest_version_num => $ident_core = [<$ident_core V $latest_version_num>] + } + } + ); + declare_payload_new_type!( + content_trait: $content_trait, + payload_trait: $payload_trait, + ---- + $(#[$attributes])* + pub struct $payload_type_name([]); + ); + + // NOTE: If updating here, also update StaticSingleVersioned + impl $payload_type_name + { + #[doc = "Delegates to [`"[]"::fully_update`]."] + pub fn fully_update(self) -> Self { + Self::of(self.content.fully_update()) + } + + #[doc = "Delegates to [`"[]"::fully_update_into_latest_version`]."] + pub fn fully_update_into_latest_version(self) -> $ident_core { + self.content.fully_update_into_latest_version() + } + + #[doc = "Delegates to [`"[]"::from_latest_version`]."] + pub fn from_latest_version(latest_version: $ident_core) -> Self { + []::from_latest_version(latest_version).into() + } + } + // Now implement the Content trait for other relevant content types that don't already have it: + // > The internal enum: [<$ident_core Versions>] + // > Each previous version: [<$ident_core V $version_num>] + // > The latest version: $ident_core = [<$ident_core V $latest_version_num>] impl $content_trait<$payload_type_name> for [<$ident_core Versions>] { fn into_content(self) -> [] { []::from(self).into() } } + + $( + impl $content_trait<$payload_type_name> for [<$ident_core V $version_num>] { + fn into_content(self) -> [] { + self.into() + } + } + )* + + impl $content_trait<$payload_type_name> for $ident_core { + fn into_content(self) -> [] { + self.into() + } + } } }; ( @@ -677,7 +765,6 @@ mod helper_macros { impl [<$ident_core ContentMarker>] for RawScryptoValue<'_> {} } }; - // TODO - Add support for some kind of StaticMultiVersioned type here } #[allow(unused)] @@ -834,6 +921,22 @@ mod helper_macros { ) => { TypeRef::Static($aggregator.add_child_type_and_descendents::<$payload_alias>()) }; + ( + $blueprint_ident:ident, + $aggregator:ident, + { + kind: StaticMultiVersioned, + previous_versions: [ + $($version_num:expr => { updates_to: $update_to_version_num:expr }),* + $(,)? // Optional trailing comma + ], + latest_version: $latest_version_num:expr + $(,)? + }, + $payload_alias:ident$(,)? + ) => { + TypeRef::Static($aggregator.add_child_type_and_descendents::<$payload_alias>()) + }; ( $blueprint_ident:ident, $aggregator:ident, @@ -859,7 +962,7 @@ mod helper_macros { paste::paste! { TypeRef::Generic([<$blueprint_ident Generic>]::$generic_ident.generic_index()) } - }; // TODO - Add support for some kind of StaticMultiVersioned type here + }; } #[allow(unused)] @@ -971,8 +1074,8 @@ mod helper_macros { mod tests { use super::*; - // Check that the below compiles - #[derive(Debug, PartialEq, Eq, Sbor)] + // Types and Impls required by the macro: + #[derive(Debug, PartialEq, Eq, Sbor, Copy, Clone)] pub struct TestBlueprintRoyaltyV1; #[derive(Debug, PartialEq, Eq, Sbor)] @@ -1002,9 +1105,34 @@ mod tests { } } + pub struct ExampleSortedIndexKey(u16, BlueprintVersion); + + impl SortedIndexKeyFullContent for ExampleSortedIndexKey { + fn from_sort_key_and_content(sort_key: u16, content: BlueprintVersion) -> Self { + ExampleSortedIndexKey(sort_key, content) + } + + fn as_content(&self) -> &BlueprintVersion { + &self.1 + } + } + + impl SortedIndexKeyContentSource + for ExampleSortedIndexKey + { + fn sort_key(&self) -> u16 { + self.0 + } + + fn into_content(self) -> BlueprintVersion { + self.1 + } + } + + // Macro itself declare_native_blueprint_state! { blueprint_ident: TestBlueprint, - blueprint_snake_case: package, + blueprint_snake_case: test_blueprint_v1, generics: { abc: { ident: Abc, @@ -1074,30 +1202,6 @@ mod tests { } } - pub struct ExampleSortedIndexKey(u16, BlueprintVersion); - - impl SortedIndexKeyFullContent for ExampleSortedIndexKey { - fn from_sort_key_and_content(sort_key: u16, content: BlueprintVersion) -> Self { - ExampleSortedIndexKey(sort_key, content) - } - - fn as_content(&self) -> &BlueprintVersion { - &self.1 - } - } - - impl SortedIndexKeyContentSource - for ExampleSortedIndexKey - { - fn sort_key(&self) -> u16 { - self.0 - } - - fn into_content(self) -> BlueprintVersion { - self.1 - } - } - #[test] fn validate_declare_sorted_index_key_new_type_macro() { let mut bv = BlueprintVersion::default(); @@ -1116,46 +1220,39 @@ mod tests { assert_eq!(&bv, payload.as_ref()); assert_eq!(&mut bv, payload.as_mut()); - assert_eq!( - bv, - IndexKeyContentSource::into_content(payload.into_content()) - ); + assert_eq!(bv, payload.into_content()); } #[test] fn validate_royalty_field_payload_mutability() { - let mut content = TestBlueprintRoyaltyVersions::V1(TestBlueprintRoyaltyV1).into_versioned(); - let mut payload = TestBlueprintRoyaltyFieldPayload { - content: TestBlueprintRoyaltyVersions::V1(TestBlueprintRoyaltyV1).into_versioned(), - }; + let mut content = VersionedTestBlueprintRoyalty::from(TestBlueprintRoyaltyV1); + let mut payload = TestBlueprintRoyaltyFieldPayload::from( + VersionedTestBlueprintRoyalty::from(TestBlueprintRoyaltyV1), + ); assert_eq!(&content, payload.as_ref()); assert_eq!(&mut content, payload.as_mut()); + let payload = TestBlueprintRoyaltyFieldPayload::from(VersionedTestBlueprintRoyalty::from( + TestBlueprintRoyaltyV1, + )); assert_eq!( &LockStatus::Locked, payload.into_locked_substate().lock_status() ); - - assert_eq!( - &LockStatus::Locked, - TestBlueprintRoyaltyV1.into_locked_substate().lock_status() - ); + let payload = TestBlueprintRoyaltyFieldPayload::from(VersionedTestBlueprintRoyalty::from( + TestBlueprintRoyaltyV1, + )); assert_eq!( &LockStatus::Unlocked, - TestBlueprintRoyaltyV1 - .into_unlocked_substate() - .lock_status() + payload.into_unlocked_substate().lock_status() ); } #[test] fn validate_key_value_store_entry_payload_mutability() { fn create_payload() -> TestBlueprintMyCoolKeyValueStoreEntryPayload { - TestBlueprintMyCoolKeyValueStoreEntryPayload { - content: TestBlueprintMyCoolKeyValueStoreVersions::V1( - TestBlueprintMyCoolKeyValueStoreV1, - ) - .into_versioned(), - } + TestBlueprintMyCoolKeyValueStoreEntryPayload::of( + TestBlueprintMyCoolKeyValueStoreV1.into(), + ) } assert_eq!( @@ -1187,31 +1284,26 @@ mod tests { #[test] fn validate_index_entry_payload() { - let payload = TestBlueprintMyCoolIndexEntryPayload { - content: TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1) - .into_versioned(), - }; + let payload = TestBlueprintMyCoolIndexEntryPayload::of(TestBlueprintMyCoolIndexV1.into()); assert_eq!( - payload.into_substate().value().content, - TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned() + payload.into_substate().into_value().into_content(), + VersionedTestBlueprintMyCoolIndex::from(TestBlueprintMyCoolIndexV1) ); let content = TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned(); assert_eq!( - TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned(), - content.into_substate().value().content + VersionedTestBlueprintMyCoolIndex::from(TestBlueprintMyCoolIndexV1), + content.into_substate().into_value().into_content() ); } #[test] fn validate_sorted_index_entry_payload() { - let payload = TestBlueprintMyCoolSortedIndexEntryPayload { - content: TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) - .into_versioned(), - }; + let payload = + TestBlueprintMyCoolSortedIndexEntryPayload::of(TestBlueprintMyCoolSortedIndexV1.into()); assert_eq!( - payload.into_substate().value().content, + payload.into_substate().into_value().into_content(), TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) .into_versioned() ); @@ -1221,7 +1313,7 @@ mod tests { assert_eq!( TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) .into_versioned(), - content.into_substate().value().content + content.into_substate().into_value().into_content() ); } @@ -1260,4 +1352,186 @@ mod tests { ) .is_err()); } + + // And now imagine we have a V2 blueprint needing types: + + pub type TestBlueprintV2RoyaltyV1 = TestBlueprintRoyaltyV1; + pub type TestBlueprintV2MyCoolKeyValueStoreV1 = TestBlueprintMyCoolKeyValueStoreV1; + pub type TestBlueprintV2MyCoolIndexV1 = TestBlueprintMyCoolIndexV1; + pub type TestBlueprintV2MyCoolSortedIndexV1 = TestBlueprintMyCoolSortedIndexV1; + + pub type TestBlueprintV2PartitionOffset = TestBlueprintPartitionOffset; + + #[derive(Debug, PartialEq, Eq, Sbor, Clone)] + pub struct TestBlueprintV2RoyaltyV2 { + my_new_value: String, + } + + impl From for TestBlueprintV2RoyaltyV2 { + fn from(_value: TestBlueprintV2RoyaltyV1) -> Self { + Self { + my_new_value: "created during upgrade".to_string(), + } + } + } + + impl SortedIndexKeyFullContent + for ExampleSortedIndexKey + { + fn from_sort_key_and_content(sort_key: u16, content: BlueprintVersion) -> Self { + ExampleSortedIndexKey(sort_key, content) + } + + fn as_content(&self) -> &BlueprintVersion { + &self.1 + } + } + + impl SortedIndexKeyContentSource + for ExampleSortedIndexKey + { + fn sort_key(&self) -> u16 { + self.0 + } + + fn into_content(self) -> BlueprintVersion { + self.1 + } + } + + declare_native_blueprint_state! { + blueprint_ident: TestBlueprintV2, + blueprint_snake_case: test_blueprint_v2, + generics: { + abc: { + ident: Abc, + description: "Some generic parameter called Abc", + } + }, + features: { + some_feature: { + ident: Feature, + description: "Some feature", + } + }, + fields: { + royalty: { + ident: Royalty, + field_type: { + kind: StaticMultiVersioned, + previous_versions: [ + 1 => { updates_to: 2 } + ], + latest_version: 2, + }, + condition: Condition::Always, + }, + some_generic_field: { + ident: GenericField, + field_type: { + kind: Generic, + ident: Abc, + }, + } + }, + collections: { + some_key_value_store: KeyValue { + entry_ident: MyCoolKeyValueStore, + key_type: { + kind: Static, + content_type: BlueprintVersion, + }, + value_type: { + kind: StaticSingleVersioned, + }, + allow_ownership: true, + }, + abc: Index { + entry_ident: MyCoolIndex, + key_type: { + kind: Static, + content_type: BlueprintVersion, + }, + value_type: { + kind: StaticSingleVersioned, + }, + allow_ownership: true, + }, + def: SortedIndex { + entry_ident: MyCoolSortedIndex, + key_type: { + kind: Static, + content_type: BlueprintVersion, + }, + full_key_content: { + full_content_type: ExampleSortedIndexKey, + sort_prefix_property_name: sort_prefix, + }, + value_type: { + kind: StaticSingleVersioned, + }, + allow_ownership: true, + }, + } + } + + #[test] + fn validate_v1_data_can_be_loaded_as_v1_or_v2() { + // ---------- + // V1 Content + // ---------- + // Content v1 is used by both TestBlueprint and TestBlueprintV2 - it's actually the same type + let content_v1 = TestBlueprintRoyaltyV1; + let v1_versioned_content_v1 = VersionedTestBlueprintRoyalty::from(content_v1.clone()); + let v1_payload_v1 = TestBlueprintRoyaltyFieldPayload::of(content_v1.clone().into()); + let v2_versioned_content_v1 = VersionedTestBlueprintV2Royalty::from(content_v1.clone()); + let v2_payload_v1 = TestBlueprintV2RoyaltyFieldPayload::of(content_v1.clone().into()); + + // These should all be the same: + let encoded_v1_versioned_content_v1 = scrypto_encode(&v1_versioned_content_v1).unwrap(); + let encoded_v1_payload_v1 = scrypto_encode(&v1_payload_v1).unwrap(); + let encoded_v2_versioned_content_v1 = scrypto_encode(&v2_versioned_content_v1).unwrap(); + let encoded_v2_payload_v1 = scrypto_encode(&v2_payload_v1).unwrap(); + + assert_eq!(encoded_v1_versioned_content_v1, encoded_v1_payload_v1); + assert_eq!( + encoded_v1_versioned_content_v1, + encoded_v2_versioned_content_v1 + ); + assert_eq!(encoded_v1_versioned_content_v1, encoded_v2_payload_v1); + + let v1_payload_v1_decoded_as_v2 = + scrypto_decode::(&encoded_v1_payload_v1).unwrap(); + assert_eq!(&v1_payload_v1_decoded_as_v2, &v2_payload_v1); + + // ---------- + // V2 Content + // ---------- + // Content v2 is used by only TestBlueprintV2: + let content_v2 = TestBlueprintV2RoyaltyV2 { + my_new_value: "created during upgrade".to_string(), + }; + let v2_versioned_content_v2 = VersionedTestBlueprintV2Royalty::from(content_v2.clone()); + let v2_payload_v2 = TestBlueprintV2RoyaltyFieldPayload::of(content_v2.clone().into()); + + // These should both be the same: + let encoded_v2_versioned_content_v2 = scrypto_encode(&v2_versioned_content_v2).unwrap(); + let encoded_v2_payload_v2 = scrypto_encode(&v2_payload_v2).unwrap(); + assert_eq!(encoded_v2_versioned_content_v2, encoded_v2_payload_v2); + + // And we can check that upgrading it works: + let v2_payload_v1_updated = + TestBlueprintV2RoyaltyFieldPayload::of(content_v1.clone().into()).fully_update(); + assert_eq!(&v2_payload_v1_updated, &v2_payload_v2); + + // And deserializing into a v2_payload works: + let decoded_v2_payload_v2 = + scrypto_decode::(&encoded_v2_payload_v2).unwrap(); + assert_eq!(&decoded_v2_payload_v2, &v2_payload_v2); + + // But deserializing as a v1_payload (which doesn't understand v2) breaks: + let decode_result_v1_payload_v2 = + scrypto_decode::(&encoded_v2_payload_v2); + assert!(decode_result_v1_payload_v2.is_err()); + } } diff --git a/radix-engine/src/blueprints/models/payloads.rs b/radix-engine/src/blueprints/models/payloads.rs index 5b107efa8ac..49e007afc3d 100644 --- a/radix-engine/src/blueprints/models/payloads.rs +++ b/radix-engine/src/blueprints/models/payloads.rs @@ -14,11 +14,26 @@ macro_rules! declare_payload_new_type { #[sbor(transparent)] /// This new type represents the payload of a particular field or collection. /// It is unique to this particular field/collection. + /// + /// Therefore, it can be used to disambiguate if the same content type is used + /// by different blueprints (e.g. two different versions of the same blueprint) $vis struct $payload_type_name - $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? - { - pub content: $content_type + $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? + { + content: $content_type + } + + impl + $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + $payload_type_name $(< $( $lt ),+ >)? + { + pub fn of(content: $content_type) -> Self { + Self { + content, + } } + } + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? core::convert::From<$content_type> diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index f87d1d57a4a..4fdafde5420 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -21,12 +21,18 @@ pub trait Versioned: AsRef + AsMut + From &mut Self::LatestVersion { - self.fully_update(); + self.fully_update_mut(); self.as_latest_version_mut().unwrap() } /// Updates to the latest version in place. - fn fully_update(&mut self); + fn fully_update_mut(&mut self); + + /// Consumes self, updates to the latest version and returns itself. + fn fully_update(mut self) -> Self { + self.fully_update_mut(); + self + } /// Updates itself to the latest version, then returns the latest content fn fully_update_into_latest_version(self) -> Self::LatestVersion; @@ -368,7 +374,7 @@ macro_rules! define_versioned { self.as_ref().is_fully_updated() } - fn fully_update(&mut self) { + fn fully_update_mut(&mut self) { if !self.is_fully_updated() { let current = self.inner.take().unwrap(); self.inner = Some(current.fully_update()); @@ -634,8 +640,7 @@ mod tests { actual: impl Into, expected: ::LatestVersion, ) { - let mut versioned_actual = actual.into(); - versioned_actual.fully_update(); + let versioned_actual = actual.into().fully_update(); let versioned_expected = VersionedExample::from(expected.clone()); // Check fully_update (which returns a VersionedExample) assert_eq!(versioned_actual, versioned_expected,); From f364d7c74299c3385ac912caf14c14bcf9163b01 Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 20:37:43 +0100 Subject: [PATCH 11/18] fix: Fix scrypto-test compilation --- scrypto-test/src/ledger_simulator/ledger_simulator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scrypto-test/src/ledger_simulator/ledger_simulator.rs b/scrypto-test/src/ledger_simulator/ledger_simulator.rs index 8d65afd3f4a..646922937df 100644 --- a/scrypto-test/src/ledger_simulator/ledger_simulator.rs +++ b/scrypto-test/src/ledger_simulator/ledger_simulator.rs @@ -650,7 +650,7 @@ impl LedgerSimulator { let key = key.into_map(); let hash: SchemaHash = scrypto_decode(&key).unwrap(); let schema: PackageSchemaEntryPayload = scrypto_decode(&value).unwrap(); - (hash, schema.content) + (hash, schema.into_content()) }) .collect() } @@ -806,7 +806,7 @@ impl LedgerSimulator { .unwrap() .unwrap(); - scrypto_decode(&scrypto_encode(&payload.content).unwrap()).unwrap() + scrypto_decode(&scrypto_encode(&payload).unwrap()).unwrap() } pub fn get_kv_store_entry( From de5468130c4a15fe51f7a868db1abe13819960c5 Mon Sep 17 00:00:00 2001 From: David Edey Date: Mon, 22 Apr 2024 22:09:13 +0100 Subject: [PATCH 12/18] fix: Fixed package_invalid tests --- .../assets/blueprints/package_invalid/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/radix-engine-tests/assets/blueprints/package_invalid/src/lib.rs b/radix-engine-tests/assets/blueprints/package_invalid/src/lib.rs index 04767c0c147..e311f371a71 100644 --- a/radix-engine-tests/assets/blueprints/package_invalid/src/lib.rs +++ b/radix-engine-tests/assets/blueprints/package_invalid/src/lib.rs @@ -1,4 +1,3 @@ -use sbor::*; use scrypto::prelude::*; use scrypto::radix_blueprint_schema_init::*; @@ -21,11 +20,7 @@ pub extern "C" fn BadFunctionSchema_schema() -> Slice { ); // Empty Schema - let empty_schema = VersionedScryptoSchema::V1(SchemaV1 { - type_kinds: Vec::new(), - type_metadata: Vec::new(), - type_validations: Vec::new(), - }); + let empty_schema = Schema::empty().into_versioned(); let return_data = scrypto::blueprints::package::BlueprintDefinitionInit { blueprint_type: scrypto::blueprints::package::BlueprintType::default(), From ce662a25146e96fb2c6596c71b79f4957ec7fca9 Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 23 Apr 2024 05:34:03 +0100 Subject: [PATCH 13/18] feat: Fixed eager macro, used it for define_versioned! --- examples/everything/Cargo.lock | 1 + examples/hello-world/Cargo.lock | 1 + examples/no-std/Cargo.lock | 1 + radix-clis/Cargo.lock | 1 + radix-engine/assets/blueprints/Cargo.lock | 1 + sbor-derive/Cargo.toml | 1 + sbor-derive/src/eager.rs | 379 +++++++++++++++++++--- sbor-derive/src/lib.rs | 107 ++---- sbor-tests/tests/eager.rs | 14 + sbor/src/versioned.rs | 211 +++++------- 10 files changed, 480 insertions(+), 237 deletions(-) create mode 100644 sbor-tests/tests/eager.rs diff --git a/examples/everything/Cargo.lock b/examples/everything/Cargo.lock index 1defe138999..56305e7488d 100644 --- a/examples/everything/Cargo.lock +++ b/examples/everything/Cargo.lock @@ -1147,6 +1147,7 @@ version = "1.2.0-dev" dependencies = [ "proc-macro2", "sbor-derive-common", + "syn 1.0.109", ] [[package]] diff --git a/examples/hello-world/Cargo.lock b/examples/hello-world/Cargo.lock index 0974a9f7759..2c4ff043a22 100644 --- a/examples/hello-world/Cargo.lock +++ b/examples/hello-world/Cargo.lock @@ -1147,6 +1147,7 @@ version = "1.2.0-dev" dependencies = [ "proc-macro2", "sbor-derive-common", + "syn 1.0.109", ] [[package]] diff --git a/examples/no-std/Cargo.lock b/examples/no-std/Cargo.lock index 33e409c6013..0e89eb859fb 100644 --- a/examples/no-std/Cargo.lock +++ b/examples/no-std/Cargo.lock @@ -572,6 +572,7 @@ version = "1.2.0-dev" dependencies = [ "proc-macro2", "sbor-derive-common", + "syn 1.0.109", ] [[package]] diff --git a/radix-clis/Cargo.lock b/radix-clis/Cargo.lock index 571046f1672..c9ac8483c58 100644 --- a/radix-clis/Cargo.lock +++ b/radix-clis/Cargo.lock @@ -1537,6 +1537,7 @@ version = "1.2.0-dev" dependencies = [ "proc-macro2", "sbor-derive-common", + "syn 1.0.109", ] [[package]] diff --git a/radix-engine/assets/blueprints/Cargo.lock b/radix-engine/assets/blueprints/Cargo.lock index 26a44c06009..47882ff92b9 100644 --- a/radix-engine/assets/blueprints/Cargo.lock +++ b/radix-engine/assets/blueprints/Cargo.lock @@ -552,6 +552,7 @@ version = "1.2.0-dev" dependencies = [ "proc-macro2", "sbor-derive-common", + "syn 1.0.109", ] [[package]] diff --git a/sbor-derive/Cargo.toml b/sbor-derive/Cargo.toml index 4872a9d8bb6..2cd1a761a0d 100644 --- a/sbor-derive/Cargo.toml +++ b/sbor-derive/Cargo.toml @@ -14,6 +14,7 @@ bench = false [dependencies] proc-macro2 = { workspace = true } +syn = { workspace = true, features = ["full", "extra-traits"] } sbor-derive-common = { workspace = true } [features] diff --git a/sbor-derive/src/eager.rs b/sbor-derive/src/eager.rs index 22d5df42cb8..e4e749f9f77 100644 --- a/sbor-derive/src/eager.rs +++ b/sbor-derive/src/eager.rs @@ -1,64 +1,355 @@ -use proc_macro::*; +use core::iter; +use proc_macro2::*; +use std::{collections::HashMap, str::FromStr}; +use syn::*; -pub(crate) fn replace_recursive(token_stream: TokenStream) -> TokenStream { - let mut tokens = token_stream.into_iter().peekable(); +type TokenIter = ::IntoIter; +type PeekableTokenIter = iter::Peekable; + +pub(crate) fn replace(token_stream: TokenStream) -> Result { + let settings = Settings { + tag: "EAGER".to_string(), + }; + let mut state = EagerState::new(); + replace_recursive(&settings, &mut state, token_stream.into_iter()) +} + +struct Settings { + tag: String, +} + +struct EagerState { + variables: HashMap, +} + +impl EagerState { + fn new() -> Self { + Self { + variables: Default::default(), + } + } + + fn set_variable(&mut self, name: String, tokens: TokenStream) { + self.variables.insert(name, tokens); + } + + fn get_variable(&self, name: &str) -> Option<&TokenStream> { + self.variables.get(name) + } +} + +fn replace_recursive( + settings: &Settings, + state: &mut EagerState, + token_iter: TokenIter, +) -> Result { + let mut tokens = token_iter.peekable(); let mut expanded = TokenStream::new(); + let tag = &settings.tag; loop { - let Some(token_tree) = tokens.next() else { - break; - }; - - if is_eager_stringify_followed_by_exclamation_mark(&token_tree, &mut tokens) { - let exclamation_mark = tokens.next().unwrap(); - let next_token_tree = tokens.next(); - if let Some(proc_macro::TokenTree::Group(group)) = &next_token_tree { - expanded.extend(stringify_tokens(group.span(), group.stream())); + match consume_next_meaningful_token_batch(&mut tokens, tag)? { + MeaningfulTokenBatch::EagerCallStart(call_kind_group, eager_call_intent) => { + let call_output = + execute_eager_call(settings, state, eager_call_intent, call_kind_group.span())?; + expanded.extend(call_output); + } + MeaningfulTokenBatch::EagerVariable { marker, name } => { + let Some(substituted) = state.get_variable(&name.to_string()) else { + let marker = marker.as_char(); + let name_str = name.to_string(); + let name_str = &name_str; + return Err(Error::new( + name.span(), + format!("The variable {marker}{name_str} wasn't set. If this wasn't intended to be a variable, work around this with {marker}[!{tag}!]({name_str})"), + )); + }; + expanded.extend(substituted.clone()); + } + MeaningfulTokenBatch::Group(group) => { + // If it's a group, run replace on its contents recursively. + expanded.extend(iter::once(TokenTree::Group(Group::new( + group.delimiter(), + replace_recursive(settings, state, group.stream().into_iter())?, + )))); + } + MeaningfulTokenBatch::Leaf(token_tree) => { + expanded.extend(iter::once(token_tree)); + } + MeaningfulTokenBatch::EndOfStream => break, + } + } + return Ok(expanded); +} + +enum MeaningfulTokenBatch { + EagerCallStart(Group, EagerCallIntent), + EagerVariable { marker: Punct, name: Ident }, + Group(Group), + Leaf(TokenTree), + EndOfStream, +} + +fn consume_next_meaningful_token_batch( + tokens: &mut PeekableTokenIter, + tag: &str, +) -> Result { + Ok(match tokens.next() { + None => MeaningfulTokenBatch::EndOfStream, + Some(TokenTree::Group(group)) => { + if let Some(eager_call_intent) = denotes_eager_call_intent(tag, &group)? { + MeaningfulTokenBatch::EagerCallStart(group, eager_call_intent) } else { - // If we get eager_stringify! but then it doesn't get followed by a group, then add the token back which we've just consumed - expanded.extend(core::iter::once(token_tree)); - expanded.extend(core::iter::once(exclamation_mark)); - if let Some(next_token_tree) = next_token_tree { - expanded.extend(core::iter::once(next_token_tree)); + MeaningfulTokenBatch::Group(group) + } + } + Some(TokenTree::Punct(punct)) => { + if punct.as_char() == '#' { + if let Some(TokenTree::Ident(_)) = tokens.peek() { + let Some(TokenTree::Ident(name)) = tokens.next() else { + unreachable!(); + }; + MeaningfulTokenBatch::EagerVariable { + marker: punct, + name, + } } else { - break; + MeaningfulTokenBatch::Leaf(TokenTree::Punct(punct)) } - } - } else { - // If it's a group, run replace on its contents recursively. - if let proc_macro::TokenTree::Group(group) = token_tree { - expanded.extend(core::iter::once(TokenTree::Group(proc_macro::Group::new( - group.delimiter(), - replace_recursive(group.stream()), - )))) } else { - expanded.extend(core::iter::once(token_tree)); + MeaningfulTokenBatch::Leaf(TokenTree::Punct(punct)) } } + Some(leaf) => MeaningfulTokenBatch::Leaf(leaf), + }) +} + +enum EagerIntentKind { + Output(EagerFunctionKind), + Set(EagerFunctionKind), +} +enum EagerFunctionKind { + Stringify, + Concat, + Ident, + Literal, + Tokens, +} + +struct EagerCallIntent { + intent_kind: EagerIntentKind, + args: TokenIter, +} + +fn denotes_eager_call_intent<'g>(tag: &str, group: &'g Group) -> Result> { + // Until we see [!EAGER...] we will assume we're not matching an eager call. + if group.delimiter() != Delimiter::Bracket { + return Ok(None); + } + let mut tokens = group.stream().into_iter(); + if consume_expected_punct(&mut tokens, '!').is_none() { + return Ok(None); + } + if consume_expected_ident(&mut tokens, tag).is_none() { + return Ok(None); } - return expanded; + // We have now established we're in an eager call intent. + // Anything wrong after this point is a compile error. + + let Some(TokenTree::Punct(punct)) = tokens.next() else { + return Err(eager_intent_error_for_tag(group.span(), tag)); + }; + + let intent_kind = match punct.as_char() { + // [!EAGER! ..] is interpreted as a pass-through of tokens, but doing replacements + '!' => EagerIntentKind::Output(EagerFunctionKind::Tokens), + ':' => { + let Some(TokenTree::Ident(call_type)) = tokens.next() else { + return Err(eager_intent_error_for_tag(group.span(), tag)); + }; + if &call_type.to_string() == "set" { + let Some(TokenTree::Punct(punct)) = tokens.next() else { + return Err(eager_intent_error_for_tag(group.span(), tag)); + }; + match punct.as_char() { + '!' => EagerIntentKind::Set(EagerFunctionKind::Tokens), + ':' => { + let Some(TokenTree::Ident(func_name)) = tokens.next() else { + return Err(eager_intent_error_for_tag(group.span(), tag)); + }; + let intent_kind = + EagerIntentKind::Set(parse_supported_func_name(&func_name)?); + if consume_expected_punct(&mut tokens, '!').is_none() { + return Err(eager_intent_error_for_tag(group.span(), tag)); + } + intent_kind + } + _ => return Err(eager_intent_error_for_tag(group.span(), tag)), + } + } else { + let intent_kind = EagerIntentKind::Output(parse_supported_func_name(&call_type)?); + if consume_expected_punct(&mut tokens, '!').is_none() { + return Err(eager_intent_error_for_tag(group.span(), tag)); + } + intent_kind + } + } + _ => return Err(eager_intent_error_for_tag(group.span(), tag)), + }; + + Ok(Some(EagerCallIntent { + intent_kind, + args: tokens, + })) } -fn is_eager_stringify_followed_by_exclamation_mark( - current: &TokenTree, - tokens: &mut core::iter::Peekable<::IntoIter>, -) -> bool { - let TokenTree::Ident(ident) = ¤t else { - return false; +fn eager_intent_error_for_tag(span: Span, tag: &str) -> Error { + Error::new( + span, + format!("Expected `[!{tag}! ..]`, `[!{tag}:! ..]`, `[!{tag}:set! ..]`, `[!{tag}:set:! #var = ..]` for one of: stringify, concat, ident or literal.") + ) +} + +fn parse_supported_func_name(ident: &Ident) -> Result { + Ok(match ident.to_string().as_ref() { + "stringify" => EagerFunctionKind::Stringify, + "concat" => EagerFunctionKind::Concat, + "ident" => EagerFunctionKind::Ident, + "literal" => EagerFunctionKind::Literal, + "tokens" => EagerFunctionKind::Tokens, + func => { + return Err(Error::new( + ident.span(), + format!("Unknown EAGER function: {func}"), + )) + } + }) +} + +fn consume_expected_ident(tokens: &mut TokenIter, ident_str: &str) -> Option { + let Some(TokenTree::Ident(ident)) = tokens.next() else { + return None; }; - if ident.to_string() != "eager_stringify" { - return false; + if &ident.to_string() != ident_str { + return None; } - let Some(TokenTree::Punct(punct)) = tokens.peek() else { - return false; + Some(ident) +} + +fn consume_expected_punct(tokens: &mut TokenIter, char: char) -> Option { + let Some(TokenTree::Punct(punct)) = tokens.next() else { + return None; }; - if punct.as_char() != '!' { - return false; + if punct.as_char() != char { + return None; + } + Some(punct) +} + +fn execute_eager_call( + settings: &Settings, + state: &mut EagerState, + call_intent: EagerCallIntent, + span: Span, +) -> Result { + match call_intent.intent_kind { + EagerIntentKind::Output(func) => { + execute_eager_function(settings, state, func, span, call_intent.args) + } + EagerIntentKind::Set(func) => { + let mut tokens = call_intent.args; + const SET_ERROR_MESSAGE: &'static str = + "A set call is expected to start with `#VariableName = ..`."; + match consume_expected_punct(&mut tokens, '#') { + Some(_) => {} + _ => return Err(Error::new(span, SET_ERROR_MESSAGE)), + } + let Some(TokenTree::Ident(ident)) = tokens.next() else { + return Err(Error::new(span, SET_ERROR_MESSAGE)); + }; + match consume_expected_punct(&mut tokens, '=') { + Some(_) => {} + _ => return Err(Error::new(span, SET_ERROR_MESSAGE)), + } + + let result_tokens = execute_eager_function(settings, state, func, span, tokens)?; + state.set_variable(ident.to_string(), result_tokens); + + return Ok(TokenStream::new()); + } } - true } -fn stringify_tokens(span: Span, token_stream: TokenStream) -> TokenStream { - let mut literal = Literal::string(&token_stream.to_string()); +fn execute_eager_function( + settings: &Settings, + state: &mut EagerState, + function_kind: EagerFunctionKind, + span: Span, + token_iter: TokenIter, +) -> Result { + let replaced_arguments = replace_recursive(settings, state, token_iter)?; + Ok(match function_kind { + EagerFunctionKind::Stringify => stringify(span, replaced_arguments)?, + EagerFunctionKind::Concat => concat(span, replaced_arguments)?, + EagerFunctionKind::Ident => concat_ident(span, replaced_arguments)?, + EagerFunctionKind::Literal => concat_literal(span, replaced_arguments)?, + EagerFunctionKind::Tokens => replaced_arguments, + }) +} + +fn stringify(span: Span, arguments: TokenStream) -> Result { + let stringify_str = arguments.to_string(); + let mut literal = Literal::string(&stringify_str); literal.set_span(span); - TokenTree::Literal(literal).into() + Ok(TokenTree::Literal(literal).into()) +} + +fn concat(span: Span, arguments: TokenStream) -> Result { + let concat_str = concat_internal(arguments); + let mut literal = Literal::string(&concat_str); + literal.set_span(span); + Ok(TokenTree::Literal(literal).into()) +} + +fn concat_ident(span: Span, arguments: TokenStream) -> Result { + let concat_str = concat_internal(arguments); + // As per paste + let ident = match std::panic::catch_unwind(|| Ident::new(&concat_str, span)) { + Ok(literal) => literal, + Err(_) => { + return Err(Error::new( + span, + &format!("`{:?}` is not a valid ident", concat_str), + )); + } + }; + Ok(TokenTree::Ident(ident).into()) +} + +fn concat_literal(span: Span, arguments: TokenStream) -> Result { + let concat_str = concat_internal(arguments); + // Similar to paste + let mut literal = Literal::from_str(&concat_str) + .map_err(|_| Error::new(span, &format!("`{:?}` is not a valid literal", concat_str)))?; + literal.set_span(span); + Ok(TokenTree::Literal(literal).into()) +} + +fn concat_internal(arguments: TokenStream) -> String { + let mut output_str = String::new(); + for token_tree in arguments { + match token_tree { + TokenTree::Literal(literal) => { + let lit: Lit = parse_quote!(#literal); + match lit { + Lit::Str(lit_str) => output_str.push_str(&lit_str.value()), + Lit::Char(lit_char) => output_str.push(lit_char.value()), + _ => { + output_str.push_str(&literal.to_string()); + } + } + } + _ => output_str.push_str(&token_tree.to_string()), + } + } + output_str } diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 69cb7ce3ad2..5f48906b8a0 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -66,39 +66,9 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// NOTE: This should probably be moved out of sbor to its own crate. /// -/// This macro causes the `eager_stringify!` pseudo-macro to stringify its contents immediately. -/// Similar to the `paste!` macro, this is intended for use in declarative macros. +/// This macro is a powerful but simple general-purpose tool to ease building declarative macros. /// -/// It is particularly useful in scenarios where `paste` doesn't work - in particular, to -/// create non-idents, or to create non-doc attribute string content, which paste cannot do, e.g.: -/// ```rust -/// // Inside a macro_rules! expression: -/// eager_replace!{ -/// #[sbor(as_type = eager_stringify!($my_inner_type))] -/// $vis struct $my_type($my_inner_type) -/// } -/// ``` -/// -/// ## Use with docs -/// -/// You can combine `eager_stringify!` with the `paste` macro's ability to concat doc string literals together, -/// as follows. In some cases, `paste` can be used without `eager_stringify!` for the same effect. -/// ```rust -/// // Inside a macro_rules! expression: -/// eager_replace!{paste!{ -/// #[doc = "This is the [`" eager_stringify!($my_type $(< $( $generic_type ),+ >)?) "`] type."] -/// $vis struct $my_type $(< $( $generic_type ),+ >)?( -/// $my_inner_type $(< $( $generic_type ),+ >)? -/// ) -/// }} -/// ``` -/// -/// ## Future vision -/// -/// The below describes a future vision which would expand this macro into a powerful -/// but simple general-purpose tool to ease building declarative macros. -/// -/// Effectively it would be a more powerful version of [paste](https://github.com/dtolnay/paste) +/// Effectively it functions as a more powerful version of [paste](https://github.com/dtolnay/paste), /// whilst bringing the power of [quote](https://docs.rs/quote/latest/quote/)'s variable /// substitution to declarative macros. /// @@ -107,47 +77,38 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// * Simplify handling sub-repetition which currently needs an internal `macro_rules!` definition [per this stack overflow post](https://stackoverflow.com/a/73543948) /// * Improved readability of long procedural macros through substitution of repeated segments /// -/// ### More types -/// -/// Output `string`, `ident`, `literal` or just a token stream: -/// * `[!EAGER!string](X Y " " Z)` gives "XY Z" concats each argument stringified without spaces -/// (except removing the quotes around string literals). Spaces can be added with " ". -/// * `[!EAGER!ident]` does the same for idents -/// * `[!EAGER!literal]` does the same for literals -/// * `[!EAGER!]` parses and outputs a token stream. -/// This would be a no-op, except it's not when combined with other features below: -/// variables, nested calls, etc. -/// -/// ### Variables + Cleaner Coding -/// -/// You can define variables starting with `#` which can be used inside other eager evaluations. /// -/// The command `[!EAGER!define:#MyZ:ident](ZZZ)` doesn't output anything, but sets `#MyZ` -/// to be the given `Ident`. Then, insides any other eager macro, `#MyZ` outputs the given ident. -/// -/// This would also work for literals, strings and token streams. -/// -/// ### Nesting -/// -/// Would add support for nesting [!EAGER!] calls inside eachother - although -/// using variables might well be cleaner code. +/// It is particularly useful in scenarios where `paste` doesn't work - in particular, to +/// create non-idents, or to create non-doc attribute string content, which paste cannot do, e.g.: +/// ```rust +/// // Inside a macro_rules! expression: +/// eager_replace!{ +/// #[sbor(as_type = [!EAGER:stringify! $my_inner_type])] +/// $vis struct $my_type($my_inner_type) +/// } +/// ``` /// -/// ### String case conversion +/// ## Specific functions /// -/// Could in future support case conversion like [paste](https://docs.rs/paste/latest/paste/#case-conversion). +/// * `[!EAGER:stringify! X Y " " Z]` gives `"XY \" \" Z"` +/// * `[!EAGER:concat! X Y " " Z]` gives `"XY Z"` by concatenating each argument stringified without spaces. String and Char literals are first unquoted. Spaces can be added with " ". +/// * `[!EAGER:ident! X Y "Z"]` gives an ident `XYZ`. +/// * `[!EAGER:literal! 31 u 32]` gives `31u32`. +/// * `[!EAGER! ...]` outputs the `...` token stream, can be used for outputting `#[!EAGER! ident]` so that `#ident` isn't detected as a variable. /// -/// ### Alternative EAGER tag +/// ## Variables for cleaner coding /// -/// Would allow a user to specify their own tag instead of `EAGER`. This could: -/// * Facilitate nesting `eager_replace` calls: `eager_replace!{ [!tag = INNER_EAGER] code...}` -/// * Allow using shorthand e.g. `E` instead +/// You can define variables starting with `#` which can be used outside the set call. +/// +/// * The command `[!EAGER:set! #MyZ = 1 + 2]` doesn't output anything, but sets `#MyZ` to the given token stream. +/// * Similarly `[!EAGER:set:ident! #MyZ = ZZZ]` sets `#MyZ` as an ident. This also works with `stringify`, `concat` and `literal`. /// -/// ### Example of future vision +/// ### Demonstration /// ```rust /// macro_rules! impl_marker_traits { /// { /// $(#[$attributes:meta])* -/// $vis:vis $type_name:ident +/// $vis:vis $type_name_suffix:ident /// // Arbitrary generics /// $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)? /// [ @@ -155,16 +116,13 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// $(,) // Optional trailing comma /// ] /// } => {eager_replace!{ -/// [!EAGER!define:#ImplGenerics]($(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?) -/// [!EAGER!define:#TypeGenerics]($(< $( $lt ),+ >)?) +/// [!EAGER:set! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] +/// [!EAGER:set! #TypeGenerics = $(< $( $lt ),+ >)?] +/// [!EAGER:set:ident! #MyType = Type $type_name_suffix #TypeGenerics] /// /// // Output for each marker trait /// $( -/// // NOTE: [!EAGER] outputs a token stream, not just a token tree -/// // so it can be used for outputting things like -/// // enum variants and attributes where a declarative macro -/// // couldn't be used -/// [!EAGER!]{ impl #ImplGenerics $trait for Type #TypeGenerics } +/// impl #ImplGenerics $trait for #MyType /// { /// // Empty trait body /// } @@ -172,9 +130,16 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// }} /// } /// ``` +/// +/// ## Future extensions +/// ### String case conversion +/// +/// Could in future support case conversion like [paste](https://docs.rs/paste/latest/paste/#case-conversion). #[proc_macro] pub fn eager_replace(token_stream: TokenStream) -> TokenStream { - eager::replace_recursive(token_stream) + eager::replace(proc_macro2::TokenStream::from(token_stream)) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind"; diff --git a/sbor-tests/tests/eager.rs b/sbor-tests/tests/eager.rs new file mode 100644 index 00000000000..36193ff209e --- /dev/null +++ b/sbor-tests/tests/eager.rs @@ -0,0 +1,14 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use sbor::*; + +eager_replace! { + [!EAGER:set:ident! #boo = Hello World 2] + struct HelloWorld2 {} + type [!EAGER:ident! X "Boo" [!EAGER:concat! Hello 1] #boo] = HelloWorld2; +} + +#[test] +fn can_create() { + let _x: XBooHello1HelloWorld2 = HelloWorld2 {}; +} diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index 520b7f8efc2..093981c5fb0 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -270,104 +270,70 @@ macro_rules! define_versioned { } ) => { $crate::eager_replace! { - $crate::paste::paste! { - // Create inline sub-macros to handle the type generics nested inside - // iteration over previous_versions - // See eg https://stackoverflow.com/a/73543948 - macro_rules! [<$versioned_name _versions_trait_impl>] { - ( - $trait:ty, - $impl_block:tt - ) => { - #[allow(dead_code)] - impl - $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $trait - for $versions_name $(< $( $lt ),+ >)? - $impl_block - }; - } - - macro_rules! [<$versioned_name _versioned_trait_impl>] { - ( - $trait:ty, - $impl_block:tt - ) => { - #[allow(dead_code)] - impl - $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $trait - for $versioned_name $(< $( $lt ),+ >)? - $impl_block - }; - } + [!EAGER:set! #FullGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?] + [!EAGER:set! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] + [!EAGER:set! #TypeGenerics = $(< $( $lt ),+ >)?] + [!EAGER:set! #VersionedType = $versioned_name $(< $( $lt ),+ >)?] + [!EAGER:set! #VersionedTypePath = $versioned_name $(::< $( $lt ),+ >)?] + [!EAGER:set! #VersionsType = $versions_name $(< $( $lt ),+ >)?] + [!EAGER:set! #VersionsTypePath = $versions_name $(::< $( $lt ),+ >)?] + [!EAGER:set:ident! #PermitSborAttributesAlias = $versioned_name _PermitSborAttributes] #[allow(dead_code)] $vis type $latest_version_alias = $latest_version_type; - use $crate::PermitSborAttributes as [<$versioned_name _PermitSborAttributes>]; + use $crate::PermitSborAttributes as #PermitSborAttributesAlias; - #[derive([<$versioned_name _PermitSborAttributes>])] + #[derive(#PermitSborAttributesAlias)] $(#[$attributes])* // Needs to go below $attributes so that a #[derive(Sbor)] in the attributes can see it. - #[sbor(as_type = eager_stringify!($versions_name $(< $( $lt ),+ >)?))] + #[sbor(as_type = [!EAGER:stringify! #VersionsType])] /// If you wish to get access to match on the versions, use `.as_ref()` or `.as_mut()`. - $vis struct $versioned_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? + $vis struct $versioned_name #FullGenerics { - inner: Option<$versions_name $(< $( $lt ),+ >)?>, + inner: Option<#VersionsType>, } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $versioned_name $(< $( $lt ),+ >)? + impl #ImplGenerics #VersionedType { - pub fn new(inner: $versions_name $(< $( $lt ),+ >)?) -> Self { + pub fn new(inner: #VersionsType) -> Self { Self { inner: Some(inner), } } } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - AsRef<$versions_name $(< $( $lt ),+ >)?> - for $versioned_name $(< $( $lt ),+ >)? + impl #ImplGenerics AsRef<#VersionsType> for #VersionedType { - fn as_ref(&self) -> &$versions_name $(< $( $lt ),+ >)? { + fn as_ref(&self) -> &#VersionsType { self.inner.as_ref().unwrap() } } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - AsMut<$versions_name $(< $( $lt ),+ >)?> - for $versioned_name $(< $( $lt ),+ >)? + impl #ImplGenerics AsMut<#VersionsType> for #VersionedType { - fn as_mut(&mut self) -> &mut $versions_name $(< $( $lt ),+ >)? { + fn as_mut(&mut self) -> &mut #VersionsType { self.inner.as_mut().unwrap() } } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - From<$versions_name $(< $( $lt ),+ >)?> - for $versioned_name $(< $( $lt ),+ >)? + impl #ImplGenerics From<#VersionsType> for #VersionedType { - fn from(value: $versions_name $(< $( $lt ),+ >)?) -> Self { + fn from(value: #VersionsType) -> Self { Self::new(value) } } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - From<$versioned_name $(< $( $lt ),+ >)?> - for $versions_name $(< $( $lt ),+ >)? + impl #ImplGenerics From<#VersionedType> for #VersionsType { - fn from(value: $versioned_name $(< $( $lt ),+ >)?) -> Self { + fn from(value: #VersionedType) -> Self { value.inner.unwrap() } } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - Versioned - for $versioned_name $(< $( $lt ),+ >)? + impl #ImplGenerics Versioned for #VersionedType { - type Versions = $versions_name $(< $( $lt ),+ >)?; + type Versions = #VersionsType; type LatestVersion = $latest_version_type; fn is_fully_updated(&self) -> bool { @@ -415,35 +381,40 @@ macro_rules! define_versioned { } } - // TODO: - // - Sadly the first version of versioned used 0-indexed discriminators (by mistake, sadly) - // - To enforce this, and prevent the API being dependent on version, we should add back - // in explicit discriminators as $version_num - 1 (by using consts) - // - This requires extensions to eager_stringify + [!EAGER:set:ident! #discriminators = $versioned_name _discriminators] + #[allow(non_snake_case)] + mod #discriminators { + // The initial version of this tool used 0-indexed/off-by-one discriminators accidentally. + // We're stuck with these now unfortunately... + // But we make them explicit in case versions are skipped. + $($( + pub const [!EAGER:ident! VERSION_ $version_num]: u8 = $version_num - 1; + )*)? + pub const LATEST_VERSION: u8 = $latest_version - 1; + } - #[derive([<$versioned_name _PermitSborAttributes>])] + #[derive(#PermitSborAttributesAlias)] $(#[$attributes])* - $vis enum $versions_name $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)? + $vis enum $versions_name #FullGenerics { $($( - []($version_type), + #[sbor(discriminator(#discriminators::[!EAGER:ident! VERSION_ $version_num]))] + [!EAGER:ident! V $version_num]($version_type), )*)? - []($latest_version_type), + #[sbor(discriminator(#discriminators::LATEST_VERSION))] + [!EAGER:ident! V $latest_version]($latest_version_type), } #[allow(dead_code)] - impl - $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $versions_name - $(< $( $lt ),+ >)? + impl #ImplGenerics #VersionsType { /// Returns if update happened, and the updated versioned enum. fn attempt_single_update(self) -> (bool, Self) { match self { $($( - Self::[](value) => (true, Self::[](value.into())), + Self::[!EAGER:ident! V $version_num](value) => (true, Self::[!EAGER:ident! V $update_to_version_num](value.into())), )*)? - this @ Self::[](_) => (false, this), + this @ Self::[!EAGER:ident! V $latest_version](_) => (false, this), } } @@ -463,27 +434,27 @@ macro_rules! define_versioned { #[allow(unreachable_patterns)] pub fn is_fully_updated(&self) -> bool { match self { - Self::[](_) => true, + Self::[!EAGER:ident! V $latest_version](_) => true, _ => false, } } #[allow(irrefutable_let_patterns)] fn fully_update_into_latest_version(self) -> $latest_version_type { - let Self::[](latest) = self.fully_update() else { + let Self::[!EAGER:ident! V $latest_version](latest) = self.fully_update() else { panic!("Invalid resolved latest version not equal to latest type") }; return latest; } fn from_latest_version(latest: $latest_version_type) -> Self { - Self::[](latest) + Self::[!EAGER:ident! V $latest_version](latest) } #[allow(unreachable_patterns)] fn as_latest_version_ref(&self) -> Option<&$latest_version_type> { match self { - Self::[](latest) => Some(latest), + Self::[!EAGER:ident! V $latest_version](latest) => Some(latest), _ => None, } } @@ -491,83 +462,79 @@ macro_rules! define_versioned { #[allow(unreachable_patterns)] fn as_latest_version_mut(&mut self) -> Option<&mut $latest_version_type> { match self { - Self::[](latest) => Some(latest), + Self::[!EAGER:ident! V $latest_version](latest) => Some(latest), _ => None, } } } - $($([<$versioned_name _versions_trait_impl>]!( - From<$version_type>, - { + $($( + #[allow(dead_code)] + impl #ImplGenerics From<$version_type> for #VersionsType { fn from(value: $version_type) -> Self { - Self::[](value) + Self::[!EAGER:ident! V $version_num](value) } } - );)*)? - $($([<$versioned_name _versioned_trait_impl>]!( - From<$version_type>, - { + #[allow(dead_code)] + impl #ImplGenerics From<$version_type> for #VersionedType { fn from(value: $version_type) -> Self { - Self::new($versions_name::[](value)) + Self::new(#VersionsTypePath::[!EAGER:ident! V $version_num](value)) } } - );)*)? + )*)? - [<$versioned_name _versions_trait_impl>]!( - From<$latest_version_type>, - { - fn from(value: $latest_version_type) -> Self { - Self::[](value) - } + #[allow(dead_code)] + impl #ImplGenerics From<$latest_version_type> for #VersionsType { + fn from(value: $latest_version_type) -> Self { + Self::[!EAGER:ident! V $latest_version](value) } - ); + } - [<$versioned_name _versioned_trait_impl>]!( - From<$latest_version_type>, - { - fn from(value: $latest_version_type) -> Self { - Self::from($versions_name::[](value)) - } + #[allow(dead_code)] + impl #ImplGenerics From<$latest_version_type> for #VersionedType { + fn from(value: $latest_version_type) -> Self { + Self::new($versions_name::[!EAGER:ident! V $latest_version](value)) } - ); + } + [!EAGER:set:ident! #VersionTrait = $versioned_name Version] #[allow(dead_code)] - $vis trait [<$versioned_name Version>] { + $vis trait #VersionTrait { // Note: We need to use an explicit associated type to capture the generics. type Versioned: $crate::Versioned; fn into_versioned(self) -> Self::Versioned; } - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - [<$versioned_name Version>] - for $versions_name $(< $( $lt ),+ >)? + impl #ImplGenerics #VersionTrait for #VersionsType { - type Versioned = $versioned_name $(< $( $lt ),+ >)?; + type Versioned = #VersionedType; fn into_versioned(self) -> Self::Versioned { - $versioned_name $(::< $( $lt ),+ >)?::new(self) + #VersionedTypePath::new(self) } } - macro_rules! [<$versioned_name _versionable_impl>] { - ($inner_type:ty) => { - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? [<$versioned_name Version>] for $inner_type - { - type Versioned = $versioned_name $(< $( $lt ),+ >)?; + $($( + impl #ImplGenerics #VersionTrait for $version_type + { + type Versioned = #VersionedType; - fn into_versioned(self) -> Self::Versioned { - $versioned_name $(::< $( $lt ),+ >)?::new(self.into()) - } + fn into_versioned(self) -> Self::Versioned { + #VersionedTypePath::new(self.into()) } - }; - } + } + )*)? - $($([<$versioned_name _versionable_impl>]!($version_type);)*)? - [<$versioned_name _versionable_impl>]!($latest_version_type); - } + impl #ImplGenerics #VersionTrait for $latest_version_type + { + type Versioned = $versioned_name #TypeGenerics; + + fn into_versioned(self) -> Self::Versioned { + #VersionedTypePath::new(self.into()) + } + } } }; } From 9e2077270d80441e94951883c0195e4499e197d3 Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 23 Apr 2024 05:39:20 +0100 Subject: [PATCH 14/18] fix: Fix scrypto_env tests --- .../assets/blueprints/scrypto_env/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/radix-engine-tests/assets/blueprints/scrypto_env/src/lib.rs b/radix-engine-tests/assets/blueprints/scrypto_env/src/lib.rs index 0859fde9385..61b6edc22fc 100644 --- a/radix-engine-tests/assets/blueprints/scrypto_env/src/lib.rs +++ b/radix-engine-tests/assets/blueprints/scrypto_env/src/lib.rs @@ -62,13 +62,8 @@ mod max_sbor_depth { impl MaxSborDepthTest { pub fn write_kv_store_entry_with_depth(buffer: Vec) { // Create KeyValueStore - let schema = VersionedScryptoSchema::V1(SchemaV1 { - type_kinds: vec![], - type_metadata: vec![], - type_validations: vec![], - }); let kv_store = ScryptoVmV1Api::kv_store_new(KeyValueStoreDataSchema::Local { - additional_schema: schema, + additional_schema: Schema::empty().into(), key_type: LocalTypeId::from(ANY_TYPE), value_type: LocalTypeId::from(ANY_TYPE), allow_ownership: false, From 36053bafd4a0b0e17f0911116c9d098d6f171ccd Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 23 Apr 2024 05:55:57 +0100 Subject: [PATCH 15/18] fix: Fix formatting --- sbor-derive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 5f48906b8a0..033b1ba226b 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -99,7 +99,7 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// ## Variables for cleaner coding /// /// You can define variables starting with `#` which can be used outside the set call. -/// +/// /// * The command `[!EAGER:set! #MyZ = 1 + 2]` doesn't output anything, but sets `#MyZ` to the given token stream. /// * Similarly `[!EAGER:set:ident! #MyZ = ZZZ]` sets `#MyZ` as an ident. This also works with `stringify`, `concat` and `literal`. /// @@ -130,7 +130,7 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// }} /// } /// ``` -/// +/// /// ## Future extensions /// ### String case conversion /// From de487ac774511a885dddf47d83655f6d8dc1e9a1 Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 23 Apr 2024 11:27:13 +0100 Subject: [PATCH 16/18] fix: Fix radix-sbor-derive tests --- radix-sbor-derive/src/manifest_decode.rs | 3 +-- radix-sbor-derive/src/manifest_encode.rs | 3 +-- radix-sbor-derive/src/scrypto_decode.rs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/radix-sbor-derive/src/manifest_decode.rs b/radix-sbor-derive/src/manifest_decode.rs index 122769e8f53..6adbd694983 100644 --- a/radix-sbor-derive/src/manifest_decode.rs +++ b/radix-sbor-derive/src/manifest_decode.rs @@ -59,8 +59,7 @@ mod tests { D: sbor::Decoder > sbor::Decode for MyEnum where - T: sbor::Decode, - T: sbor::Categorize + T: sbor::Decode { #[inline] fn decode_body_with_value_kind( diff --git a/radix-sbor-derive/src/manifest_encode.rs b/radix-sbor-derive/src/manifest_encode.rs index e73b091f3f3..52967329e65 100644 --- a/radix-sbor-derive/src/manifest_encode.rs +++ b/radix-sbor-derive/src/manifest_encode.rs @@ -59,8 +59,7 @@ mod tests { E: sbor::Encoder > sbor::Encode for MyEnum where - T: sbor::Encode, - T: sbor::Categorize + T: sbor::Encode { #[inline] fn encode_value_kind(&self, encoder: &mut E) -> Result<(), sbor::EncodeError> { diff --git a/radix-sbor-derive/src/scrypto_decode.rs b/radix-sbor-derive/src/scrypto_decode.rs index 4bf2e5244b0..b463564caa4 100644 --- a/radix-sbor-derive/src/scrypto_decode.rs +++ b/radix-sbor-derive/src/scrypto_decode.rs @@ -59,8 +59,7 @@ mod tests { D: sbor::Decoder > sbor::Decode for MyEnum where - T: sbor::Decode, - T: sbor::Categorize + T: sbor::Decode { #[inline] fn decode_body_with_value_kind( From 75bcfa6558d33facea447767c64497e721567f0e Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 23 Apr 2024 15:24:21 +0100 Subject: [PATCH 17/18] markups: Tweaks to versioned method naming and eager_replace calls --- radix-clis/src/replay/cmd_sync.rs | 2 +- radix-clis/src/resim/dumper.rs | 10 +- radix-clis/src/resim/mod.rs | 6 +- radix-clis/src/scrypto_bindgen/mod.rs | 6 +- .../non_fungible_resource_manager.rs | 2 +- radix-engine-tests/tests/system/bootstrap.rs | 10 +- .../tests/system/schema_sanity_check.rs | 2 +- .../blueprints/access_controller/blueprint.rs | 6 +- .../src/blueprints/account/blueprint.rs | 6 +- .../consensus_manager/consensus_manager.rs | 45 +++--- .../blueprints/consensus_manager/validator.rs | 56 ++++---- .../src/blueprints/locker/blueprint.rs | 4 +- .../models/native_blueprint_state_macro.rs | 14 +- .../src/blueprints/package/package.rs | 14 +- .../v1/v1_0/multi_resource_pool_blueprint.rs | 2 +- .../v1/v1_0/one_resource_pool_blueprint.rs | 2 +- .../v1/v1_0/two_resource_pool_blueprint.rs | 2 +- .../v1/v1_1/multi_resource_pool_blueprint.rs | 2 +- .../v1/v1_1/one_resource_pool_blueprint.rs | 2 +- .../v1/v1_1/two_resource_pool_blueprint.rs | 2 +- .../resource/fungible/fungible_bucket.rs | 2 +- .../fungible/fungible_resource_manager.rs | 12 +- .../resource/fungible/fungible_vault.rs | 22 +-- .../non_fungible_resource_manager.rs | 12 +- .../non_fungible/non_fungible_vault.rs | 22 +-- .../src/object_modules/metadata/package.rs | 2 +- .../object_modules/role_assignment/package.rs | 10 +- .../src/object_modules/royalty/package.rs | 6 +- .../checkers/component_royalty_db_checker.rs | 2 +- .../checkers/package_royalty_db_checker.rs | 2 +- .../system/checkers/resource_db_checker.rs | 16 ++- .../checkers/role_assignment_db_checker.rs | 4 +- radix-engine/src/system/system.rs | 2 +- radix-engine/src/system/system_db_reader.rs | 6 +- .../system_modules/auth/authorization.rs | 4 +- .../src/transaction/state_update_summary.rs | 4 +- radix-engine/src/updates/state_updates.rs | 11 +- radix-engine/src/vm/vm.rs | 6 +- .../src/rocks_db_with_merkle_tree/mod.rs | 4 +- .../src/query/traverse.rs | 6 +- radix-transaction-scenarios/src/executor.rs | 2 +- sbor-derive/src/eager.rs | 133 +++++++----------- sbor-derive/src/lib.rs | 53 ++++--- sbor-tests/tests/eager.rs | 18 ++- sbor/src/schema/schema.rs | 2 +- sbor/src/versioned.rs | 109 +++++++------- .../src/ledger_simulator/ledger_simulator.rs | 28 ++-- 47 files changed, 344 insertions(+), 351 deletions(-) diff --git a/radix-clis/src/replay/cmd_sync.rs b/radix-clis/src/replay/cmd_sync.rs index dfe256626d2..b1c966a26dc 100644 --- a/radix-clis/src/replay/cmd_sync.rs +++ b/radix-clis/src/replay/cmd_sync.rs @@ -185,7 +185,7 @@ impl CommittedTxnReader { let next_identifiers: VersionedCommittedTransactionIdentifiers = scrypto_decode(next_identifiers_bytes.1.as_ref()).unwrap(); let expected_state_root_hash = next_identifiers - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .resultant_ledger_hashes .state_root .0; diff --git a/radix-clis/src/resim/dumper.rs b/radix-clis/src/resim/dumper.rs index 143508811a8..5dabe36c443 100644 --- a/radix-clis/src/resim/dumper.rs +++ b/radix-clis/src/resim/dumper.rs @@ -58,7 +58,7 @@ pub fn dump_package( substate .into_value() .unwrap() - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .code .len() ); @@ -237,7 +237,7 @@ pub fn dump_resource_manager( NonFungibleResourceManagerField::TotalSupply.into(), ) .map_err(|_| EntityDumpError::InvalidStore("Missing Total Supply".to_string()))? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); writeln!( output, @@ -254,7 +254,7 @@ pub fn dump_resource_manager( FungibleResourceManagerField::Divisibility.into(), ) .map_err(|_| EntityDumpError::InvalidStore("Missing Divisibility".to_string()))? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); writeln!(output, "{}: {}", "Resource Type".green().bold(), "Fungible"); writeln!( @@ -275,7 +275,7 @@ pub fn dump_resource_manager( FungibleResourceManagerField::TotalSupply.into(), ) .map_err(|_| EntityDumpError::InvalidStore("Missing Total Supply".to_string()))? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); writeln!( output, @@ -311,7 +311,7 @@ fn get_entity_metadata( let map_key = key.into_map(); let key = scrypto_decode::(&map_key).unwrap(); let value = scrypto_decode::(&value).unwrap(); - (key, value.fully_update_into_latest_version()) + (key, value.fully_update_and_into_latest_version()) }) .collect() } diff --git a/radix-clis/src/resim/mod.rs b/radix-clis/src/resim/mod.rs index 570a6b651b0..c89f4e04c5d 100644 --- a/radix-clis/src/resim/mod.rs +++ b/radix-clis/src/resim/mod.rs @@ -409,7 +409,9 @@ pub fn get_event_schema( ) .unwrap()?; - let bp_interface = bp_definition.fully_update_into_latest_version().interface; + let bp_interface = bp_definition + .fully_update_and_into_latest_version() + .interface; let event_def = bp_interface.events.get(event_name)?; match event_def { @@ -487,7 +489,7 @@ pub fn db_upsert_epoch(epoch: Epoch) -> Result<(), Error> { started: true, }) }) - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); consensus_mgr_state.epoch = epoch; diff --git a/radix-clis/src/scrypto_bindgen/mod.rs b/radix-clis/src/scrypto_bindgen/mod.rs index fb65033268f..5d797de4ded 100644 --- a/radix-clis/src/scrypto_bindgen/mod.rs +++ b/radix-clis/src/scrypto_bindgen/mod.rs @@ -110,7 +110,7 @@ where ) -> Result, schema::SchemaError> { self.lookup_schema(&type_identifier.0) .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? - .as_latest_version_ref() + .as_latest_version() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_kind(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( @@ -125,7 +125,7 @@ where ) -> Result { self.lookup_schema(&type_identifier.0) .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? - .as_latest_version_ref() + .as_latest_version() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_metadata(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( @@ -140,7 +140,7 @@ where ) -> Result, schema::SchemaError> { self.lookup_schema(&type_identifier.0) .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? - .as_latest_version_ref() + .as_latest_version() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_validation(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( diff --git a/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs b/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs index 1a11ce0f48f..3cb9775bd7f 100644 --- a/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs +++ b/radix-engine-interface/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs @@ -432,7 +432,7 @@ mod test { mutable_fields, } = ds { - let s = schema.fully_update_into_latest_version(); + let s = schema.fully_update_and_into_latest_version(); assert_eq!(s.type_kinds.len(), 1); assert_eq!(s.type_metadata.len(), 1); assert_eq!(s.type_validations.len(), 1); diff --git a/radix-engine-tests/tests/system/bootstrap.rs b/radix-engine-tests/tests/system/bootstrap.rs index faa742ad5bb..9287ad58da9 100644 --- a/radix-engine-tests/tests/system/bootstrap.rs +++ b/radix-engine-tests/tests/system/bootstrap.rs @@ -274,7 +274,7 @@ fn test_genesis_resource_with_initial_allocation(owned_resource: bool) { ) .unwrap() .into_payload() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); assert_eq!(total_supply, allocation_amount); let reader = SystemDatabaseReader::new(&substate_db); @@ -288,7 +288,7 @@ fn test_genesis_resource_with_initial_allocation(owned_resource: bool) { ), ) .unwrap() - .map(|v| v.fully_update_into_latest_version()); + .map(|v| v.fully_update_and_into_latest_version()); if let Some(MetadataValue::String(symbol)) = entry { assert_eq!(symbol, "TST"); @@ -491,7 +491,7 @@ fn test_genesis_stake_allocation() { ), ) .unwrap() - .map(|v| v.fully_update_into_latest_version()); + .map(|v| v.fully_update_and_into_latest_version()); if let Some(MetadataValue::Url(url)) = validator_url_entry { assert_eq!( url, @@ -532,7 +532,7 @@ fn test_genesis_time() { ConsensusManagerField::ProposerMinuteTimestamp.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); assert_eq!(timestamp.epoch_minute, 123); } @@ -758,7 +758,7 @@ fn test_bootstrap_should_create_consensus_manager_with_sorted_validator_index() ObjectCollectionKey::SortedIndex(0, u16::from_be_bytes(sort_prefix), &address), ) .expect("validator cannot be read") - .map(|versioned| versioned.fully_update_into_latest_version()) + .map(|versioned| versioned.fully_update_and_into_latest_version()) .expect("validator not found"); assert_eq!( diff --git a/radix-engine-tests/tests/system/schema_sanity_check.rs b/radix-engine-tests/tests/system/schema_sanity_check.rs index fa71161b0e0..d5b2d74a118 100644 --- a/radix-engine-tests/tests/system/schema_sanity_check.rs +++ b/radix-engine-tests/tests/system/schema_sanity_check.rs @@ -459,7 +459,7 @@ fn native_blueprints_with_typed_addresses_have_expected_schema() { panic!("Generic output!") }; - let schema = blueprint_definition.schema.schema.fully_update_into_latest_version(); + let schema = blueprint_definition.schema.schema.fully_update_and_into_latest_version(); let type_kind = schema.resolve_type_kind(local_type_index).unwrap(); let type_validation = schema.resolve_type_validation(local_type_index).unwrap(); diff --git a/radix-engine/src/blueprints/access_controller/blueprint.rs b/radix-engine/src/blueprints/access_controller/blueprint.rs index e12084c6d0a..3610ee428e8 100644 --- a/radix-engine/src/blueprints/access_controller/blueprint.rs +++ b/radix-engine/src/blueprints/access_controller/blueprint.rs @@ -1111,7 +1111,7 @@ impl AccessControllerBlueprint { let access_controller = { let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?; - access_controller.fully_update_into_latest_version() + access_controller.fully_update_and_into_latest_version() }; access_controller.recovery_badge }; @@ -1175,7 +1175,7 @@ where let access_controller = { let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?; - access_controller.fully_update_into_latest_version() + access_controller.fully_update_and_into_latest_version() }; let rtn = access_controller.transition(api, input)?; @@ -1201,7 +1201,7 @@ where let mut access_controller = { let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?; - access_controller.fully_update_into_latest_version() + access_controller.fully_update_and_into_latest_version() }; let rtn = access_controller.transition_mut(api, input)?; diff --git a/radix-engine/src/blueprints/account/blueprint.rs b/radix-engine/src/blueprints/account/blueprint.rs index 4b08a3e1f9d..47076aaaf9d 100644 --- a/radix-engine/src/blueprints/account/blueprint.rs +++ b/radix-engine/src/blueprints/account/blueprint.rs @@ -1281,7 +1281,7 @@ impl AccountBlueprint { )?; let deposit_rule = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let default = deposit_rule.default_deposit_rule; api.field_close(handle)?; @@ -1314,7 +1314,7 @@ impl AccountBlueprint { .key_value_entry_get_typed::( kv_store_entry_lock_handle, )? - .map(|v| v.fully_update_into_latest_version()); + .map(|v| v.fully_update_and_into_latest_version()); match entry { Some(vault) => Ok(vault), @@ -1427,7 +1427,7 @@ impl AccountBlueprint { .key_value_entry_get_typed::( kv_store_entry_lock_handle, )? - .map(|v| v.fully_update_into_latest_version()); + .map(|v| v.fully_update_and_into_latest_version()); api.key_value_entry_close(kv_store_entry_lock_handle)?; Ok(entry) } diff --git a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs index 84ab0ffc96e..e0ffa52f48b 100644 --- a/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs +++ b/radix-engine/src/blueprints/consensus_manager/consensus_manager.rs @@ -644,7 +644,7 @@ impl ConsensusManagerBlueprint { let consensus_manager = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Ok(consensus_manager.epoch) } @@ -661,7 +661,7 @@ impl ConsensusManagerBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(config_handle)?; config_substate }; @@ -673,7 +673,7 @@ impl ConsensusManagerBlueprint { )?; let mut manager_substate = api .field_read_typed::(manager_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); if manager_substate.started { return Err(RuntimeError::ApplicationError( @@ -722,7 +722,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; Ok(Self::epoch_minute_to_instant( @@ -750,7 +750,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; Ok(Self::epoch_minute_to_instant( @@ -765,7 +765,7 @@ impl ConsensusManagerBlueprint { )?; let proposer_milli_timestamp = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; Ok(Self::epoch_milli_to_instant( @@ -811,7 +811,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; // convert back to Instant only for comparison operation @@ -860,7 +860,7 @@ impl ConsensusManagerBlueprint { .field_read_typed::( handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; // convert back to Instant only for comparison operation @@ -881,7 +881,7 @@ impl ConsensusManagerBlueprint { )?; let proposer_milli_timestamp = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; // convert back to Instant only for comparison operation @@ -924,7 +924,7 @@ impl ConsensusManagerBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(config_handle)?; let manager_handle = api.actor_open_field( @@ -934,7 +934,7 @@ impl ConsensusManagerBlueprint { )?; let mut manager_substate = api .field_read_typed::(manager_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let progressed_rounds = Round::calculate_progress(manager_substate.round, round) .ok_or_else(|| { @@ -1001,7 +1001,7 @@ impl ConsensusManagerBlueprint { )?; let manager_substate = api.field_read_typed::(manager_handle)?; - let manager_substate = manager_substate.fully_update_into_latest_version(); + let manager_substate = manager_substate.fully_update_and_into_latest_version(); let validator_creation_xrd_cost = if manager_substate.started { let config_handle = api.actor_open_field( @@ -1014,7 +1014,7 @@ impl ConsensusManagerBlueprint { api.field_close(config_handle)?; let validator_creation_xrd_cost = manager_config - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .config .validator_creation_usd_cost .checked_mul(api.usd_price()?) @@ -1074,7 +1074,7 @@ impl ConsensusManagerBlueprint { )?; let exact_time_substate: ConsensusManagerProposerMilliTimestampFieldPayload = api.field_read_typed(handle)?; - let mut exact_time_substate = exact_time_substate.fully_update_into_latest_version(); + let mut exact_time_substate = exact_time_substate.fully_update_and_into_latest_version(); let previous_timestamp = exact_time_substate.epoch_milli; if current_time_ms < previous_timestamp { return Err(RuntimeError::ApplicationError( @@ -1109,7 +1109,7 @@ impl ConsensusManagerBlueprint { let rounded_timestamp_substate: ConsensusManagerProposerMinuteTimestampFieldPayload = api.field_read_typed(handle)?; let mut rounded_timestamp_substate = - rounded_timestamp_substate.fully_update_into_latest_version(); + rounded_timestamp_substate.fully_update_and_into_latest_version(); let previous_rounded_value = rounded_timestamp_substate.epoch_minute; if new_rounded_value > previous_rounded_value { rounded_timestamp_substate.epoch_minute = new_rounded_value; @@ -1151,7 +1151,7 @@ impl ConsensusManagerBlueprint { )?; let statistic: ConsensusManagerCurrentProposalStatisticFieldPayload = api.field_read_typed(statistic_handle)?; - let mut statistic = statistic.fully_update_into_latest_version(); + let mut statistic = statistic.fully_update_and_into_latest_version(); for gap_round_leader in proposal_history.gap_round_leaders { let gap_round_statistic = statistic.get_mut_proposal_statistic(gap_round_leader)?; gap_round_statistic.missed += 1; @@ -1188,7 +1188,8 @@ impl ConsensusManagerBlueprint { )?; let validator_set_substate: ConsensusManagerCurrentValidatorSetFieldPayload = api.field_read_typed(validator_set_handle)?; - let mut validator_set_substate = validator_set_substate.fully_update_into_latest_version(); + let mut validator_set_substate = + validator_set_substate.fully_update_and_into_latest_version(); let previous_validator_set = validator_set_substate.validator_set; // Read previous validator statistics @@ -1199,7 +1200,7 @@ impl ConsensusManagerBlueprint { )?; let statistic_substate: ConsensusManagerCurrentProposalStatisticFieldPayload = api.field_read_typed(statistic_handle)?; - let mut statistic_substate = statistic_substate.fully_update_into_latest_version(); + let mut statistic_substate = statistic_substate.fully_update_and_into_latest_version(); let previous_statistics = statistic_substate.validator_statistics; // Read & write validator rewards @@ -1210,7 +1211,7 @@ impl ConsensusManagerBlueprint { )?; let mut rewards_substate = api .field_read_typed::(rewards_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Apply emissions Self::apply_validator_emissions_and_rewards( @@ -1248,8 +1249,8 @@ impl ConsensusManagerBlueprint { // then let's be even more accurate here. This sort is stable, so if two validators tie, then the resultant order will be // decided on sort key DESC. top_registered_validators.sort_by(|(_, validator_1), (_, validator_2)| { - let validator1 = validator_1.as_unique_version_ref(); - let validator2 = validator_2.as_unique_version_ref(); + let validator1 = validator_1.as_unique_version(); + let validator2 = validator_2.as_unique_version(); validator1.stake.cmp(&validator2.stake).reverse() }); @@ -1260,7 +1261,7 @@ impl ConsensusManagerBlueprint { .map(|(component_address, validator)| { ( component_address, - validator.fully_update_into_latest_version(), + validator.fully_update_and_into_latest_version(), ) }) .collect(), diff --git a/radix-engine/src/blueprints/consensus_manager/validator.rs b/radix-engine/src/blueprints/consensus_manager/validator.rs index 8735c244a6a..a0bee77dce1 100644 --- a/radix-engine/src/blueprints/consensus_manager/validator.rs +++ b/radix-engine/src/blueprints/consensus_manager/validator.rs @@ -590,7 +590,7 @@ impl ValidatorBlueprint { let mut validator = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); if !is_owner { if !validator.accepts_delegated_stake { @@ -656,7 +656,7 @@ impl ValidatorBlueprint { )?; let mut validator_substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Unstake let (unstake_bucket, new_stake_amount) = { @@ -680,7 +680,7 @@ impl ValidatorBlueprint { )?; let manager_substate = api .field_read_typed::(manager_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let current_epoch = manager_substate.epoch; api.field_close(manager_handle)?; @@ -691,7 +691,7 @@ impl ValidatorBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(config_handle)?; let claim_epoch = current_epoch @@ -763,7 +763,7 @@ impl ValidatorBlueprint { )?; let mut signal = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); signal.protocol_version_name = Some(protocol_version_name.clone()); api.field_write_typed( handle, @@ -792,7 +792,7 @@ impl ValidatorBlueprint { )?; let signal = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; Ok(signal.protocol_version_name) @@ -810,7 +810,7 @@ impl ValidatorBlueprint { let mut validator = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // No update if validator.is_registered == new_registered { return Ok(()); @@ -895,7 +895,7 @@ impl ValidatorBlueprint { )?; let validator = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let mut nft_resman = ResourceManager(validator.claim_nft); let resource_address = validator.claim_nft; let mut unstake_vault = Vault(validator.pending_xrd_withdraw_vault_id); @@ -914,7 +914,7 @@ impl ValidatorBlueprint { )?; let mgr_substate = api .field_read_typed::(mgr_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let epoch = mgr_substate.epoch; api.field_close(mgr_handle)?; epoch @@ -961,7 +961,7 @@ impl ValidatorBlueprint { )?; let mut validator = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Update Consensus Manager { @@ -999,7 +999,7 @@ impl ValidatorBlueprint { )?; let consensus_manager = api .field_read_typed::(consensus_manager_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let current_epoch = consensus_manager.epoch; api.field_close(consensus_manager_handle)?; @@ -1011,7 +1011,7 @@ impl ValidatorBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(config_handle)?; // begin the read+modify+write of the validator substate... @@ -1022,7 +1022,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // - promote any currently pending change if it became effective already if let Some(previous_request) = substate.validator_fee_change_request { @@ -1067,7 +1067,7 @@ impl ValidatorBlueprint { let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; Ok(substate.accepts_delegated_stake) @@ -1085,7 +1085,7 @@ impl ValidatorBlueprint { let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let stake_vault = Vault(substate.stake_xrd_vault_id); let stake_amount = stake_vault.amount(api)?; api.field_close(handle)?; @@ -1105,7 +1105,7 @@ impl ValidatorBlueprint { let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let stake_resource = ResourceManager(substate.stake_unit_resource); let total_stake_unit_supply = stake_resource.total_supply(api)?.unwrap(); api.field_close(handle)?; @@ -1133,7 +1133,7 @@ impl ValidatorBlueprint { )?; let validator = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); { let stake_unit_resman = ResourceManager(validator.stake_unit_resource); @@ -1166,7 +1166,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); substate.accepts_delegated_stake = accept_delegated_stake; api.field_write_typed( handle, @@ -1201,7 +1201,7 @@ impl ValidatorBlueprint { )?; let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Vault(substate.locked_owner_stake_unit_vault_id).put(stake_unit_bucket, api)?; @@ -1228,7 +1228,7 @@ impl ValidatorBlueprint { )?; let consensus_manager = api .field_read_typed::(consensus_manager_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let current_epoch = consensus_manager.epoch; api.field_close(consensus_manager_handle)?; @@ -1240,7 +1240,7 @@ impl ValidatorBlueprint { )?; let config_substate = api .field_read_typed::(config_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(config_handle)?; // begin the read+modify+write of the validator substate... @@ -1251,7 +1251,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // - move the already-available withdrawals to a dedicated field Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?; @@ -1306,7 +1306,7 @@ impl ValidatorBlueprint { )?; let consensus_manager = api .field_read_typed::(consensus_manager_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let current_epoch = consensus_manager.epoch; api.field_close(consensus_manager_handle)?; @@ -1318,7 +1318,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Self::normalize_available_owner_stake_unit_withdrawals(&mut substate, current_epoch)?; let total_already_available_amount = mem::replace( @@ -1395,7 +1395,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // - resolve the effective validator fee factor let effective_validator_fee_factor = match &substate.validator_fee_change_request { @@ -1490,7 +1490,7 @@ impl ValidatorBlueprint { )?; let mut substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Get the total reward amount let total_reward_xrd = xrd_bucket.amount(api)?; @@ -1581,7 +1581,7 @@ impl ValidatorBlueprint { ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(), &index_key, )? - .unwrap().fully_update_into_latest_version(); + .unwrap().fully_update_and_into_latest_version(); validator.key = key; api.actor_sorted_index_insert_typed( ACTOR_STATE_OUTER_OBJECT, @@ -1604,7 +1604,7 @@ impl ValidatorBlueprint { ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(), &index_key, )? - .unwrap().fully_update_into_latest_version(); + .unwrap().fully_update_and_into_latest_version(); validator.stake = new_stake_amount; api.actor_sorted_index_insert_typed( ACTOR_STATE_OUTER_OBJECT, diff --git a/radix-engine/src/blueprints/locker/blueprint.rs b/radix-engine/src/blueprints/locker/blueprint.rs index 84fb4521b75..a243dea861a 100644 --- a/radix-engine/src/blueprints/locker/blueprint.rs +++ b/radix-engine/src/blueprints/locker/blueprint.rs @@ -578,7 +578,7 @@ impl AccountLockerBlueprint { .key_value_entry_get_typed::( account_claims_handle, )? - .map(|entry| entry.fully_update_into_latest_version()); + .map(|entry| entry.fully_update_and_into_latest_version()); let account_claims_kv_store = match account_claims { Some(account_claims_kv_store) => account_claims_kv_store, @@ -655,7 +655,7 @@ impl AccountLockerBlueprint { .key_value_entry_get_typed::( account_claims_handle, )? - .map(|entry| entry.fully_update_into_latest_version()); + .map(|entry| entry.fully_update_and_into_latest_version()); let account_claims_kv_store = match account_claims { Some(account_claims_kv_store) => account_claims_kv_store, diff --git a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs index 05bacebba66..027dc9eb70a 100644 --- a/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs +++ b/radix-engine/src/blueprints/models/native_blueprint_state_macro.rs @@ -599,9 +599,9 @@ mod helper_macros { Self::of(self.content.fully_update()) } - #[doc = "Delegates to [`"[]"::fully_update_into_latest_version`]."] - pub fn fully_update_into_latest_version(self) -> $ident_core { - self.content.fully_update_into_latest_version() + #[doc = "Delegates to [`"[]"::fully_update_and_into_latest_version`]."] + pub fn fully_update_and_into_latest_version(self) -> $ident_core { + self.content.fully_update_and_into_latest_version() } #[doc = "Delegates to [`"[]"::from_latest_version`]."] @@ -679,9 +679,9 @@ mod helper_macros { Self::of(self.content.fully_update()) } - #[doc = "Delegates to [`"[]"::fully_update_into_latest_version`]."] - pub fn fully_update_into_latest_version(self) -> $ident_core { - self.content.fully_update_into_latest_version() + #[doc = "Delegates to [`"[]"::fully_update_and_into_latest_version`]."] + pub fn fully_update_and_into_latest_version(self) -> $ident_core { + self.content.fully_update_and_into_latest_version() } #[doc = "Delegates to [`"[]"::from_latest_version`]."] @@ -1279,7 +1279,7 @@ mod tests { .lock_status() ); - assert!(create_payload().as_latest_version_ref().is_some()); + assert!(create_payload().as_latest_version().is_some()); } #[test] diff --git a/radix-engine/src/blueprints/package/package.rs b/radix-engine/src/blueprints/package/package.rs index bae8f2deb31..30ddee935d7 100644 --- a/radix-engine/src/blueprints/package/package.rs +++ b/radix-engine/src/blueprints/package/package.rs @@ -1483,14 +1483,14 @@ impl PackageRoyaltyNativeBlueprint { let royalty_charge = substate .into_value() - .and_then( - |royalty_config| match royalty_config.fully_update_into_latest_version() { + .and_then(|royalty_config| { + match royalty_config.fully_update_and_into_latest_version() { PackageRoyaltyConfig::Enabled(royalty_amounts) => { royalty_amounts.get(ident).cloned() } PackageRoyaltyConfig::Disabled => Some(RoyaltyAmount::Free), - }, - ) + } + }) .unwrap_or(RoyaltyAmount::Free); // we check for negative royalties at the instantiation time of the royalty module. @@ -1510,7 +1510,7 @@ impl PackageRoyaltyNativeBlueprint { let vault_id = substate .into_payload() - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .royalty_vault .0; let package_address = PackageAddress::new_or_panic(receiver.0); @@ -1547,7 +1547,7 @@ impl PackageRoyaltyNativeBlueprint { let substate: PackageRoyaltyAccumulatorFieldPayload = api.field_read_typed(handle)?; let bucket = substate - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .royalty_vault .take_all(api)?; @@ -1640,7 +1640,7 @@ impl PackageAuthNativeBlueprint { api.kernel_close_substate(handle)?; let template = match auth_template.into_value() { - Some(template) => template.fully_update_into_latest_version(), + Some(template) => template.fully_update_and_into_latest_version(), None => { return Err(RuntimeError::SystemError( SystemError::AuthTemplateDoesNotExist(package_bp_version_id), diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs index 6a5c729f934..65cc89db70e 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/multi_resource_pool_blueprint.rs @@ -628,7 +628,7 @@ impl MultiResourcePoolBlueprint { let substate_key = MultiResourcePoolField::State.into(); let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let multi_resource_pool: VersionedMultiResourcePoolState = api.field_read_typed(handle)?; - let multi_resource_pool = multi_resource_pool.fully_update_into_latest_version(); + let multi_resource_pool = multi_resource_pool.fully_update_and_into_latest_version(); Ok((multi_resource_pool, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs index e32490fe5a3..6a56d95fe3a 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/one_resource_pool_blueprint.rs @@ -414,7 +414,7 @@ impl OneResourcePoolBlueprint { let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Ok((substate, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs index f7cab9f7ba4..03b70ea2440 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_0/two_resource_pool_blueprint.rs @@ -597,7 +597,7 @@ impl TwoResourcePoolBlueprint { let handle = api.actor_open_field(ACTOR_STATE_SELF, substate_key, lock_flags)?; let two_resource_pool_substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Ok((two_resource_pool_substate, handle)) } diff --git a/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs index 2d445fd5999..9d75e36b575 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_1/multi_resource_pool_blueprint.rs @@ -591,7 +591,7 @@ impl MultiResourcePoolBlueprint { api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?; let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Op let rtn = callback(substate, api); diff --git a/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs index 61fadeb8e48..0fe501d07c0 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_1/one_resource_pool_blueprint.rs @@ -421,7 +421,7 @@ impl OneResourcePoolBlueprint { api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?; let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Op let rtn = callback(substate, api); diff --git a/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs b/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs index a97514bdd9b..8216a946d70 100644 --- a/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs +++ b/radix-engine/src/blueprints/pool/v1/v1_1/two_resource_pool_blueprint.rs @@ -656,7 +656,7 @@ impl TwoResourcePoolBlueprint { api.actor_open_field(ACTOR_STATE_SELF, substate_key, LockFlags::read_only())?; let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Op let rtn = callback(substate, api); diff --git a/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs b/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs index f24209c3643..c29aaab7657 100644 --- a/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs +++ b/radix-engine/src/blueprints/resource/fungible/fungible_bucket.rs @@ -26,7 +26,7 @@ impl FungibleBucketBlueprint { .field_read_typed::( divisibility_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(divisibility_handle)?; Ok(divisibility) } diff --git a/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs b/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs index cda43e02fcf..901a78a664f 100644 --- a/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs +++ b/radix-engine/src/blueprints/resource/fungible/fungible_resource_manager.rs @@ -565,7 +565,7 @@ impl FungibleResourceManagerBlueprint { )?; let divisibility: FungibleResourceManagerDivisibilityFieldPayload = api.field_read_typed(divisibility_handle)?; - divisibility.fully_update_into_latest_version() + divisibility.fully_update_and_into_latest_version() }; // check amount @@ -589,7 +589,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // This should never overflow due to the 2^152 limit we place on mints. // Since Decimal have 2^192 max we would need to mint 2^40 times before // an overflow occurs. @@ -658,7 +658,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); total_supply = total_supply .checked_sub(other_bucket.liquid.amount()) .ok_or(RuntimeError::ApplicationError( @@ -759,7 +759,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( divisibility_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let resource_type = ResourceType::Fungible { divisibility }; Ok(resource_type) @@ -782,7 +782,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Ok(Some(total_supply)) } else { Ok(None) @@ -807,7 +807,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( divisibility_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Ok(amount .for_withdrawal(divisibility, withdraw_strategy) diff --git a/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs b/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs index 1468273ff04..14456f4dadb 100644 --- a/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs +++ b/radix-engine/src/blueprints/resource/fungible/fungible_vault.rs @@ -290,7 +290,7 @@ impl FungibleVaultBlueprint { )?; let divisibility = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; Ok(divisibility) } @@ -409,7 +409,7 @@ impl FungibleVaultBlueprint { let mut vault = api .field_read_typed::(vault_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let fee = vault.take_by_amount(amount).map_err(|e| { let vault_error = match e { ResourceError::InsufficientBalance { requested, actual } => { @@ -475,7 +475,7 @@ impl FungibleVaultBlueprint { let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); frozen.frozen.insert(to_freeze); api.field_write_typed( frozen_flag_handle, @@ -498,7 +498,7 @@ impl FungibleVaultBlueprint { )?; let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); frozen.frozen.remove(to_unfreeze); api.field_write_typed( frozen_flag_handle, @@ -570,7 +570,7 @@ impl FungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let max_locked = locked.amount(); // Take from liquid if needed @@ -605,7 +605,7 @@ impl FungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let max_locked = locked.amount(); let cnt = locked @@ -652,7 +652,7 @@ impl FungibleVaultBlueprint { )?; let frozen = api .field_read_typed::(frozen_flag_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(frozen_flag_handle)?; if frozen.frozen.intersects(flags) { @@ -715,7 +715,7 @@ impl FungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let amount = substate_ref.amount(); api.field_close(handle)?; Ok(amount) @@ -732,7 +732,7 @@ impl FungibleVaultBlueprint { )?; let substate_ref: LockedFungibleResource = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let amount = substate_ref.amount(); api.field_close(handle)?; Ok(amount) @@ -752,7 +752,7 @@ impl FungibleVaultBlueprint { )?; let mut substate_ref = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let taken = substate_ref.take_by_amount(amount).map_err(|e| { RuntimeError::ApplicationError(ApplicationError::VaultError(VaultError::ResourceError( e, @@ -782,7 +782,7 @@ impl FungibleVaultBlueprint { )?; let mut vault_balance = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); vault_balance.put(resource); api.field_write_typed( handle, diff --git a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs index 6cbb8fb9e88..c8435fb0d60 100644 --- a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs +++ b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_resource_manager.rs @@ -918,7 +918,7 @@ impl NonFungibleResourceManagerBlueprint { .field_read_typed::( data_schema_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); mutable_fields .mutable_field_index .get(&field_name) @@ -1167,7 +1167,7 @@ impl NonFungibleResourceManagerBlueprint { let id_type = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let resource_type = ResourceType::NonFungible { id_type }; Ok(resource_type) @@ -1190,7 +1190,7 @@ impl NonFungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Ok(Some(total_supply)) } else { Ok(None) @@ -1291,7 +1291,7 @@ impl NonFungibleResourceManagerBlueprint { )?; let id_type = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; if id_type == NonFungibleIdType::RUID { return Err(RuntimeError::ApplicationError( @@ -1315,7 +1315,7 @@ impl NonFungibleResourceManagerBlueprint { )?; let id_type = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(handle)?; if id_type != NonFungibleIdType::RUID { @@ -1390,7 +1390,7 @@ impl NonFungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); total_supply = total_supply .checked_add(amount) diff --git a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs index 2afda3cd373..4cd4ff4d643 100644 --- a/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs +++ b/radix-engine/src/blueprints/resource/non_fungible/non_fungible_vault.rs @@ -540,7 +540,7 @@ impl NonFungibleVaultBlueprint { let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); frozen.frozen.insert(to_freeze); api.field_write_typed( frozen_flag_handle, @@ -563,7 +563,7 @@ impl NonFungibleVaultBlueprint { )?; let mut frozen = api .field_read_typed::(frozen_flag_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); frozen.frozen.remove(to_unfreeze); api.field_write_typed( frozen_flag_handle, @@ -663,7 +663,7 @@ impl NonFungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); // Take from liquid if needed let delta: IndexSet = ids @@ -701,7 +701,7 @@ impl NonFungibleVaultBlueprint { )?; let mut locked = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let mut liquid_non_fungibles: IndexSet = index_set_new(); for id in ids { @@ -746,7 +746,7 @@ impl NonFungibleVaultBlueprint { )?; let frozen = api .field_read_typed::(frozen_flag_handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); api.field_close(frozen_flag_handle)?; if frozen.frozen.intersects(flags) { @@ -809,7 +809,7 @@ impl NonFungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let amount = substate_ref.amount; api.field_close(handle)?; Ok(amount) @@ -826,7 +826,7 @@ impl NonFungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let amount = substate_ref.amount(); api.field_close(handle)?; Ok(amount) @@ -862,7 +862,7 @@ impl NonFungibleVaultBlueprint { )?; let substate_ref = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let limit: usize = limit.try_into().unwrap(); let ids = substate_ref.ids().into_iter().take(limit).collect(); api.field_close(handle)?; @@ -884,7 +884,7 @@ impl NonFungibleVaultBlueprint { )?; let mut balance = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); if balance.amount < Decimal::from(n) { return Err(RuntimeError::ApplicationError( @@ -933,7 +933,7 @@ impl NonFungibleVaultBlueprint { )?; let mut substate_ref = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); substate_ref.amount = substate_ref @@ -987,7 +987,7 @@ impl NonFungibleVaultBlueprint { )?; let mut vault = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); vault.amount = vault diff --git a/radix-engine/src/object_modules/metadata/package.rs b/radix-engine/src/object_modules/metadata/package.rs index 5e0b6727f16..9838c7483f1 100644 --- a/radix-engine/src/object_modules/metadata/package.rs +++ b/radix-engine/src/object_modules/metadata/package.rs @@ -426,7 +426,7 @@ impl MetadataNativePackage { let data = api.key_value_entry_get(handle)?; let substate: Option = scrypto_decode(&data).unwrap(); - Ok(substate.map(|v: MetadataEntryEntryPayload| v.fully_update_into_latest_version())) + Ok(substate.map(|v: MetadataEntryEntryPayload| v.fully_update_and_into_latest_version())) } pub(crate) fn remove(key: String, api: &mut Y) -> Result diff --git a/radix-engine/src/object_modules/role_assignment/package.rs b/radix-engine/src/object_modules/role_assignment/package.rs index 4c684f669be..c088a6f3bf2 100644 --- a/radix-engine/src/object_modules/role_assignment/package.rs +++ b/radix-engine/src/object_modules/role_assignment/package.rs @@ -308,7 +308,7 @@ impl RoleAssignmentNativePackage { let owner_role = owner_role_substate .into_payload() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let rule = match owner_role.owner_role_entry.updater { OwnerRoleUpdater::None => AccessRule::DenyAll, @@ -468,7 +468,7 @@ impl RoleAssignmentNativePackage { let mut owner_role = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); owner_role.owner_role_entry.rule = rule.clone(); api.field_write_typed( handle, @@ -488,7 +488,7 @@ impl RoleAssignmentNativePackage { let handle = api.actor_open_field(ACTOR_STATE_SELF, 0u8, LockFlags::MUTABLE)?; let mut owner_role = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); owner_role.owner_role_entry.updater = OwnerRoleUpdater::None; api.field_write_typed( handle, @@ -598,7 +598,7 @@ impl RoleAssignmentNativePackage { api.key_value_entry_close(handle)?; - Ok(rule.map(|v| v.fully_update_into_latest_version())) + Ok(rule.map(|v| v.fully_update_and_into_latest_version())) } } @@ -664,7 +664,7 @@ impl RoleAssignmentBottlenoseExtension { )?; let owner_role_entry = api .field_read_typed::(handle)? - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .owner_role_entry; api.field_close(handle)?; diff --git a/radix-engine/src/object_modules/royalty/package.rs b/radix-engine/src/object_modules/royalty/package.rs index 54a3f8f6099..05f7719f7b9 100644 --- a/radix-engine/src/object_modules/royalty/package.rs +++ b/radix-engine/src/object_modules/royalty/package.rs @@ -426,7 +426,7 @@ impl ComponentRoyaltyBlueprint { let substate = api .field_read_typed::(handle)? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let mut royalty_vault = substate.royalty_vault; let bucket = royalty_vault.take_all(api)?; api.field_close(handle)?; @@ -459,7 +459,7 @@ impl ComponentRoyaltyBlueprint { let component_royalty = component_royalty .into_payload() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let royalty_charge = { let handle = api.kernel_open_substate_with_default( @@ -483,7 +483,7 @@ impl ComponentRoyaltyBlueprint { api.kernel_close_substate(handle)?; substate .into_value() - .map(|v| v.fully_update_into_latest_version()) + .map(|v| v.fully_update_and_into_latest_version()) .unwrap_or(RoyaltyAmount::Free) }; diff --git a/radix-engine/src/system/checkers/component_royalty_db_checker.rs b/radix-engine/src/system/checkers/component_royalty_db_checker.rs index 803ff9d8fc2..63f8a1ffc72 100644 --- a/radix-engine/src/system/checkers/component_royalty_db_checker.rs +++ b/radix-engine/src/system/checkers/component_royalty_db_checker.rs @@ -60,7 +60,7 @@ impl ComponentRoyaltyDatabaseChecker { royalty_amount: ComponentRoyaltyMethodAmountEntryPayload, location: ErrorLocation, ) { - let royalty_amount = royalty_amount.fully_update_into_latest_version(); + let royalty_amount = royalty_amount.fully_update_and_into_latest_version(); let max_royalty_in_xrd = Decimal::from_str(MAX_PER_FUNCTION_ROYALTY_IN_XRD).unwrap(); let max_royalty_in_usd = max_royalty_in_xrd / Decimal::from_str(USD_PRICE_IN_XRD).unwrap(); diff --git a/radix-engine/src/system/checkers/package_royalty_db_checker.rs b/radix-engine/src/system/checkers/package_royalty_db_checker.rs index 0a74b752c2b..46245ebc8b6 100644 --- a/radix-engine/src/system/checkers/package_royalty_db_checker.rs +++ b/radix-engine/src/system/checkers/package_royalty_db_checker.rs @@ -95,7 +95,7 @@ where config: PackageBlueprintVersionRoyaltyConfigEntryPayload, location: ErrorLocation, ) { - let royalty_config = config.fully_update_into_latest_version(); + let royalty_config = config.fully_update_and_into_latest_version(); match royalty_config { PackageRoyaltyConfig::Disabled => {} diff --git a/radix-engine/src/system/checkers/resource_db_checker.rs b/radix-engine/src/system/checkers/resource_db_checker.rs index d26f772aee8..ae8ac0e5a81 100644 --- a/radix-engine/src/system/checkers/resource_db_checker.rs +++ b/radix-engine/src/system/checkers/resource_db_checker.rs @@ -66,7 +66,8 @@ impl ApplicationChecker for ResourceDatabaseChecker { scrypto_decode(value).unwrap(); let address = ResourceAddress::new_or_panic(node_id.0); let tracker = self.resources.entry(address).or_default(); - tracker.expected = Some(total_supply.fully_update_into_latest_version()); + tracker.expected = + Some(total_supply.fully_update_and_into_latest_version()); } _ => {} } @@ -80,7 +81,9 @@ impl ApplicationChecker for ResourceDatabaseChecker { let address = ResourceAddress::new_or_panic( info.outer_obj_info.expect().into_node_id().0, ); - let amount = vault_balance.fully_update_into_latest_version().amount(); + let amount = vault_balance + .fully_update_and_into_latest_version() + .amount(); if amount.is_negative() { panic!("Found Fungible Vault negative balance"); @@ -103,7 +106,8 @@ impl ApplicationChecker for ResourceDatabaseChecker { scrypto_decode(value).unwrap(); let address = ResourceAddress::new_or_panic(node_id.0); let tracker = self.resources.entry(address).or_default(); - tracker.expected = Some(total_supply.fully_update_into_latest_version()); + tracker.expected = + Some(total_supply.fully_update_and_into_latest_version()); } _ => {} } @@ -118,7 +122,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { info.outer_obj_info.expect().into_node_id().0, ); let tracker = self.resources.entry(address).or_default(); - let vault_balance = vault_balance.fully_update_into_latest_version(); + let vault_balance = vault_balance.fully_update_and_into_latest_version(); tracker.tracking_supply = tracker .tracking_supply .checked_add(vault_balance.amount) @@ -147,7 +151,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { let mut prev = Decimal::MAX; for validator in validator_set - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .validator_set .validators_by_stake_desc { @@ -162,7 +166,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { let stats: ConsensusManagerCurrentProposalStatisticFieldPayload = scrypto_decode(value).unwrap(); stats_count = stats - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .validator_statistics .len(); } diff --git a/radix-engine/src/system/checkers/role_assignment_db_checker.rs b/radix-engine/src/system/checkers/role_assignment_db_checker.rs index 01ff0b278d6..3814f4baaa8 100644 --- a/radix-engine/src/system/checkers/role_assignment_db_checker.rs +++ b/radix-engine/src/system/checkers/role_assignment_db_checker.rs @@ -142,7 +142,7 @@ impl RoleAssignmentDatabaseChecker { F: FnMut(RoleAssignmentDatabaseCheckerError), { let owner_rule = owner_role_entry - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .owner_role_entry .rule; Self::check_access_rule_limits(owner_rule, add_error) @@ -157,7 +157,7 @@ impl RoleAssignmentDatabaseChecker { F: FnMut(RoleAssignmentDatabaseCheckerError), { let key = key.content; - let value = value.fully_update_into_latest_version(); + let value = value.fully_update_and_into_latest_version(); Self::check_access_rule_limits(value, add_error); Self::check_is_role_key_reserved(&key, add_error); diff --git a/radix-engine/src/system/system.rs b/radix-engine/src/system/system.rs index d5531ac1c1b..9fb65ac1362 100644 --- a/radix-engine/src/system/system.rs +++ b/radix-engine/src/system/system.rs @@ -357,7 +357,7 @@ where self.api.kernel_close_substate(handle)?; let definition = Rc::new(match substate.into_value() { - Some(definition) => definition.fully_update_into_latest_version(), + Some(definition) => definition.fully_update_and_into_latest_version(), None => { return Err(RuntimeError::SystemError( SystemError::BlueprintDoesNotExist(canonical_bp_id), diff --git a/radix-engine/src/system/system_db_reader.rs b/radix-engine/src/system/system_db_reader.rs index 08758627085..8e125b2e4bc 100644 --- a/radix-engine/src/system/system_db_reader.rs +++ b/radix-engine/src/system/system_db_reader.rs @@ -162,7 +162,7 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { blueprint_definition .into_value() .unwrap() - .fully_update_into_latest_version(), + .fully_update_and_into_latest_version(), ); } @@ -539,7 +539,7 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { .at_offset(PACKAGE_BLUEPRINTS_PARTITION_OFFSET) .unwrap(), &SubstateKey::Map(scrypto_encode(&bp_version_key).unwrap()), - ).ok_or_else(|| SystemReaderError::BlueprintDoesNotExist)?.into_value().unwrap().fully_update_into_latest_version()); + ).ok_or_else(|| SystemReaderError::BlueprintDoesNotExist)?.into_value().unwrap().fully_update_and_into_latest_version()); self.blueprint_cache .borrow_mut() @@ -870,7 +870,7 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { Ok(definition .into_value() .unwrap() - .fully_update_into_latest_version()) + .fully_update_and_into_latest_version()) } pub fn validate_payload<'b>( diff --git a/radix-engine/src/system/system_modules/auth/authorization.rs b/radix-engine/src/system/system_modules/auth/authorization.rs index 64882992106..f2686752da2 100644 --- a/radix-engine/src/system/system_modules/auth/authorization.rs +++ b/radix-engine/src/system/system_modules/auth/authorization.rs @@ -353,7 +353,7 @@ impl Authorization { api.kernel_close_substate(handle)?; match substate.into_value() { - Some(access_rule) => access_rule.fully_update_into_latest_version(), + Some(access_rule) => access_rule.fully_update_and_into_latest_version(), None => { let handle = api.kernel_open_substate( role_assignment_of.as_node_id(), @@ -370,7 +370,7 @@ impl Authorization { api.kernel_close_substate(handle)?; owner_role_substate .into_payload() - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .owner_role_entry .rule } diff --git a/radix-engine/src/transaction/state_update_summary.rs b/radix-engine/src/transaction/state_update_summary.rs index a340ced88c3..c036f477898 100644 --- a/radix-engine/src/transaction/state_update_summary.rs +++ b/radix-engine/src/transaction/state_update_summary.rs @@ -193,7 +193,7 @@ impl<'a, S: SubstateDatabase> BalanceAccounter<'a, S> { MAIN_BASE_PARTITION, &FungibleVaultField::Balance.into(), ) - .map(|new_substate| new_substate.into_payload().fully_update_into_latest_version().amount()) + .map(|new_substate| new_substate.into_payload().fully_update_and_into_latest_version().amount()) .map(|new_balance| { let old_balance = self .system_reader @@ -202,7 +202,7 @@ impl<'a, S: SubstateDatabase> BalanceAccounter<'a, S> { MAIN_BASE_PARTITION, &FungibleVaultField::Balance.into(), ) - .map(|old_balance| old_balance.into_payload().fully_update_into_latest_version().amount()) + .map(|old_balance| old_balance.into_payload().fully_update_and_into_latest_version().amount()) .unwrap_or(Decimal::ZERO); // TODO: Handle potential Decimal arithmetic operation (safe_sub) errors instead of panicking. diff --git a/radix-engine/src/updates/state_updates.rs b/radix-engine/src/updates/state_updates.rs index 50eb601f477..1da52778f4a 100644 --- a/radix-engine/src/updates/state_updates.rs +++ b/radix-engine/src/updates/state_updates.rs @@ -130,7 +130,7 @@ pub fn generate_seconds_precision_timestamp_state_updates( .unwrap() .unwrap(); - let mut definition = versioned_definition.fully_update_into_latest_version(); + let mut definition = versioned_definition.fully_update_and_into_latest_version(); let export = definition .function_exports @@ -296,7 +296,8 @@ pub fn generate_pool_math_precision_fix_state_updates(db: & ) .unwrap() .unwrap(); - let mut blueprint_definition = versioned_definition.fully_update_into_latest_version(); + let mut blueprint_definition = + versioned_definition.fully_update_and_into_latest_version(); for (_, export) in blueprint_definition.function_exports.iter_mut() { export.code_hash = new_code_hash @@ -385,7 +386,7 @@ pub fn generate_validator_creation_fee_fix_state_updates( ) .unwrap(); - let mut config = versioned_config.fully_update_into_latest_version(); + let mut config = versioned_config.fully_update_and_into_latest_version(); config.config.validator_creation_usd_cost = Decimal::from(100); let updated_substate = config.into_locked_substate(); @@ -453,7 +454,7 @@ pub fn generate_owner_role_getter_state_updates(db: &S) -> ) .unwrap() .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); for (function_name, added_function) in added_functions.into_iter() { let TypeRef::Static(input_local_id) = added_function.input else { @@ -630,7 +631,7 @@ pub fn generate_account_bottlenose_extension_state_updates( ) .unwrap() .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); for function_name in [ ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT, diff --git a/radix-engine/src/vm/vm.rs b/radix-engine/src/vm/vm.rs index b6723c7e446..bea3078e856 100644 --- a/radix-engine/src/vm/vm.rs +++ b/radix-engine/src/vm/vm.rs @@ -146,7 +146,7 @@ impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<' .vm_version .clone(); - let output = match vm_type.fully_update_into_latest_version().vm_type { + let output = match vm_type.fully_update_and_into_latest_version().vm_type { VmType::Native => { let original_code = { let handle = api.kernel_open_substate_with_default( @@ -173,7 +173,7 @@ impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<' let mut vm_instance = api.kernel_get_system().callback.native_vm.create_instance( address, - &original_code.fully_update_into_latest_version().code, + &original_code.fully_update_and_into_latest_version().code, )?; let output = { vm_instance.invoke(export.export_name.as_str(), input, api, &vm_api)? }; @@ -202,7 +202,7 @@ impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<' instrumented_code .into_value() .unwrap_or_else(|| panic!("Instrumented code not found: {:?}", export)) - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() }; let mut scrypto_vm_instance = { diff --git a/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs b/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs index da58cc5e300..7f96c085f0f 100644 --- a/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs +++ b/radix-substate-store-impls/src/rocks_db_with_merkle_tree/mod.rs @@ -255,7 +255,7 @@ impl CommittableSubstateDatabase for RocksDBWithMerkleTreeSubstateStore { .delete_cf(self.cf(MERKLE_NODES_CF), encode_key(&node_key)) .unwrap(); let value: VersionedTreeNode = scrypto_decode(&bytes).unwrap(); - match value.fully_update_into_latest_version() { + match value.fully_update_and_into_latest_version() { TreeNodeV1::Internal(x) => { for child in x.children { queue.push_back( @@ -301,7 +301,7 @@ impl ReadableTreeStore for RocksDBWithMerkleTreeSubstateStore { .get_cf(self.cf(MERKLE_NODES_CF), &encode_key(key)) .unwrap() .map(|bytes| scrypto_decode::(&bytes).unwrap()) - .map(|versioned| versioned.fully_update_into_latest_version()) + .map(|versioned| versioned.fully_update_and_into_latest_version()) } } diff --git a/radix-substate-store-queries/src/query/traverse.rs b/radix-substate-store-queries/src/query/traverse.rs index 22abe46cb8f..b946f0d9e6b 100644 --- a/radix-substate-store-queries/src/query/traverse.rs +++ b/radix-substate-store-queries/src/query/traverse.rs @@ -135,7 +135,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' ) .expect("Broken database"); - let liquid = liquid.fully_update_into_latest_version(); + let liquid = liquid.fully_update_and_into_latest_version(); visitor.visit_fungible_vault( node_id, @@ -154,7 +154,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' ) .expect("Broken database"); - let liquid = liquid.fully_update_into_latest_version(); + let liquid = liquid.fully_update_and_into_latest_version(); visitor.visit_non_fungible_vault( node_id, @@ -192,7 +192,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' 0u8, ) .expect("Broken database") - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Self::traverse_recursive( system_db_reader, visitor, diff --git a/radix-transaction-scenarios/src/executor.rs b/radix-transaction-scenarios/src/executor.rs index e89252b4a09..29be7e7118f 100644 --- a/radix-transaction-scenarios/src/executor.rs +++ b/radix-transaction-scenarios/src/executor.rs @@ -310,7 +310,7 @@ where .map_err(|_| ScenarioExecutorError::FailedToGetEpoch)? .as_typed::() .unwrap() - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .epoch; let mut scenario = scenario_builder(ScenarioCore::new( self.network_definition.clone(), diff --git a/sbor-derive/src/eager.rs b/sbor-derive/src/eager.rs index e4e749f9f77..7b8e259eb53 100644 --- a/sbor-derive/src/eager.rs +++ b/sbor-derive/src/eager.rs @@ -7,15 +7,8 @@ type TokenIter = ::IntoIter; type PeekableTokenIter = iter::Peekable; pub(crate) fn replace(token_stream: TokenStream) -> Result { - let settings = Settings { - tag: "EAGER".to_string(), - }; let mut state = EagerState::new(); - replace_recursive(&settings, &mut state, token_stream.into_iter()) -} - -struct Settings { - tag: String, + replace_recursive(&mut state, token_stream.into_iter()) } struct EagerState { @@ -38,19 +31,14 @@ impl EagerState { } } -fn replace_recursive( - settings: &Settings, - state: &mut EagerState, - token_iter: TokenIter, -) -> Result { +fn replace_recursive(state: &mut EagerState, token_iter: TokenIter) -> Result { let mut tokens = token_iter.peekable(); let mut expanded = TokenStream::new(); - let tag = &settings.tag; loop { - match consume_next_meaningful_token_batch(&mut tokens, tag)? { + match consume_next_meaningful_token_batch(&mut tokens)? { MeaningfulTokenBatch::EagerCallStart(call_kind_group, eager_call_intent) => { let call_output = - execute_eager_call(settings, state, eager_call_intent, call_kind_group.span())?; + execute_eager_call(state, eager_call_intent, call_kind_group.span())?; expanded.extend(call_output); } MeaningfulTokenBatch::EagerVariable { marker, name } => { @@ -60,7 +48,7 @@ fn replace_recursive( let name_str = &name_str; return Err(Error::new( name.span(), - format!("The variable {marker}{name_str} wasn't set. If this wasn't intended to be a variable, work around this with {marker}[!{tag}!]({name_str})"), + format!("The variable {marker}{name_str} wasn't set. If this wasn't intended to be a variable, work around this with [!raw! {marker}{name_str}]"), )); }; expanded.extend(substituted.clone()); @@ -69,7 +57,7 @@ fn replace_recursive( // If it's a group, run replace on its contents recursively. expanded.extend(iter::once(TokenTree::Group(Group::new( group.delimiter(), - replace_recursive(settings, state, group.stream().into_iter())?, + replace_recursive(state, group.stream().into_iter())?, )))); } MeaningfulTokenBatch::Leaf(token_tree) => { @@ -91,12 +79,11 @@ enum MeaningfulTokenBatch { fn consume_next_meaningful_token_batch( tokens: &mut PeekableTokenIter, - tag: &str, ) -> Result { Ok(match tokens.next() { None => MeaningfulTokenBatch::EndOfStream, Some(TokenTree::Group(group)) => { - if let Some(eager_call_intent) = denotes_eager_call_intent(tag, &group)? { + if let Some(eager_call_intent) = denotes_eager_call_intent(&group)? { MeaningfulTokenBatch::EagerCallStart(group, eager_call_intent) } else { MeaningfulTokenBatch::Group(group) @@ -127,12 +114,14 @@ enum EagerIntentKind { Output(EagerFunctionKind), Set(EagerFunctionKind), } + enum EagerFunctionKind { Stringify, Concat, Ident, Literal, - Tokens, + ProcessedTokens, + RawTokens, } struct EagerCallIntent { @@ -140,60 +129,48 @@ struct EagerCallIntent { args: TokenIter, } -fn denotes_eager_call_intent<'g>(tag: &str, group: &'g Group) -> Result> { - // Until we see [!EAGER...] we will assume we're not matching an eager call. +fn denotes_eager_call_intent<'g>(group: &'g Group) -> Result> { if group.delimiter() != Delimiter::Bracket { return Ok(None); } + let mut tokens = group.stream().into_iter(); if consume_expected_punct(&mut tokens, '!').is_none() { return Ok(None); } - if consume_expected_ident(&mut tokens, tag).is_none() { + let Some(TokenTree::Ident(call_ident)) = tokens.next() else { return Ok(None); - } - // We have now established we're in an eager call intent. - // Anything wrong after this point is a compile error. - - let Some(TokenTree::Punct(punct)) = tokens.next() else { - return Err(eager_intent_error_for_tag(group.span(), tag)); }; - let intent_kind = match punct.as_char() { - // [!EAGER! ..] is interpreted as a pass-through of tokens, but doing replacements - '!' => EagerIntentKind::Output(EagerFunctionKind::Tokens), - ':' => { - let Some(TokenTree::Ident(call_type)) = tokens.next() else { - return Err(eager_intent_error_for_tag(group.span(), tag)); + // We have now checked enough that we're confident the user is pretty intentionally using + // the call convention. Any issues we hit from this point will be a helpful compiler error. + let intent_kind = match call_ident.to_string().as_ref() { + "SET" => { + let Some(TokenTree::Punct(punct)) = tokens.next() else { + return Err(eager_call_intent_error(group.span())); }; - if &call_type.to_string() == "set" { - let Some(TokenTree::Punct(punct)) = tokens.next() else { - return Err(eager_intent_error_for_tag(group.span(), tag)); - }; - match punct.as_char() { - '!' => EagerIntentKind::Set(EagerFunctionKind::Tokens), - ':' => { - let Some(TokenTree::Ident(func_name)) = tokens.next() else { - return Err(eager_intent_error_for_tag(group.span(), tag)); - }; - let intent_kind = - EagerIntentKind::Set(parse_supported_func_name(&func_name)?); - if consume_expected_punct(&mut tokens, '!').is_none() { - return Err(eager_intent_error_for_tag(group.span(), tag)); - } - intent_kind + match punct.as_char() { + '!' => EagerIntentKind::Set(EagerFunctionKind::ProcessedTokens), + ':' => { + let Some(TokenTree::Ident(func_name)) = tokens.next() else { + return Err(eager_call_intent_error(group.span())); + }; + let intent_kind = EagerIntentKind::Set(parse_supported_func_name(&func_name)?); + if consume_expected_punct(&mut tokens, '!').is_none() { + return Err(eager_call_intent_error(group.span())); } - _ => return Err(eager_intent_error_for_tag(group.span(), tag)), - } - } else { - let intent_kind = EagerIntentKind::Output(parse_supported_func_name(&call_type)?); - if consume_expected_punct(&mut tokens, '!').is_none() { - return Err(eager_intent_error_for_tag(group.span(), tag)); + intent_kind } - intent_kind + _ => return Err(eager_call_intent_error(group.span())), } } - _ => return Err(eager_intent_error_for_tag(group.span(), tag)), + _ => { + let intent_kind = EagerIntentKind::Output(parse_supported_func_name(&call_ident)?); + if consume_expected_punct(&mut tokens, '!').is_none() { + return Err(eager_call_intent_error(group.span())); + } + intent_kind + } }; Ok(Some(EagerCallIntent { @@ -202,10 +179,10 @@ fn denotes_eager_call_intent<'g>(tag: &str, group: &'g Group) -> Result Error { +fn eager_call_intent_error(span: Span) -> Error { Error::new( span, - format!("Expected `[!{tag}! ..]`, `[!{tag}:! ..]`, `[!{tag}:set! ..]`, `[!{tag}:set:! #var = ..]` for one of: stringify, concat, ident or literal.") + "Expected `[!! ..]`, `[!SET! #var = ..]` or `[!SET:! #var = ..]` for one of: stringify, concat, ident, literal or raw.", ) } @@ -215,26 +192,16 @@ fn parse_supported_func_name(ident: &Ident) -> Result { "concat" => EagerFunctionKind::Concat, "ident" => EagerFunctionKind::Ident, "literal" => EagerFunctionKind::Literal, - "tokens" => EagerFunctionKind::Tokens, + "raw" => EagerFunctionKind::RawTokens, func => { return Err(Error::new( ident.span(), - format!("Unknown EAGER function: {func}"), + format!("Unknown function: {func}"), )) } }) } -fn consume_expected_ident(tokens: &mut TokenIter, ident_str: &str) -> Option { - let Some(TokenTree::Ident(ident)) = tokens.next() else { - return None; - }; - if &ident.to_string() != ident_str { - return None; - } - Some(ident) -} - fn consume_expected_punct(tokens: &mut TokenIter, char: char) -> Option { let Some(TokenTree::Punct(punct)) = tokens.next() else { return None; @@ -246,14 +213,13 @@ fn consume_expected_punct(tokens: &mut TokenIter, char: char) -> Option { } fn execute_eager_call( - settings: &Settings, state: &mut EagerState, call_intent: EagerCallIntent, span: Span, ) -> Result { match call_intent.intent_kind { EagerIntentKind::Output(func) => { - execute_eager_function(settings, state, func, span, call_intent.args) + execute_eager_function(state, func, span, call_intent.args) } EagerIntentKind::Set(func) => { let mut tokens = call_intent.args; @@ -271,7 +237,7 @@ fn execute_eager_call( _ => return Err(Error::new(span, SET_ERROR_MESSAGE)), } - let result_tokens = execute_eager_function(settings, state, func, span, tokens)?; + let result_tokens = execute_eager_function(state, func, span, tokens)?; state.set_variable(ident.to_string(), result_tokens); return Ok(TokenStream::new()); @@ -280,19 +246,18 @@ fn execute_eager_call( } fn execute_eager_function( - settings: &Settings, state: &mut EagerState, function_kind: EagerFunctionKind, span: Span, token_iter: TokenIter, ) -> Result { - let replaced_arguments = replace_recursive(settings, state, token_iter)?; Ok(match function_kind { - EagerFunctionKind::Stringify => stringify(span, replaced_arguments)?, - EagerFunctionKind::Concat => concat(span, replaced_arguments)?, - EagerFunctionKind::Ident => concat_ident(span, replaced_arguments)?, - EagerFunctionKind::Literal => concat_literal(span, replaced_arguments)?, - EagerFunctionKind::Tokens => replaced_arguments, + EagerFunctionKind::Stringify => stringify(span, replace_recursive(state, token_iter)?)?, + EagerFunctionKind::Concat => concat(span, replace_recursive(state, token_iter)?)?, + EagerFunctionKind::Ident => concat_ident(span, replace_recursive(state, token_iter)?)?, + EagerFunctionKind::Literal => concat_literal(span, replace_recursive(state, token_iter)?)?, + EagerFunctionKind::ProcessedTokens => replace_recursive(state, token_iter)?, + EagerFunctionKind::RawTokens => token_iter.collect(), }) } diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 033b1ba226b..c8975d82cb9 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -66,44 +66,55 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// NOTE: This should probably be moved out of sbor to its own crate. /// -/// This macro is a powerful but simple general-purpose tool to ease building declarative macros. +/// This macro is a powerful but simple general-purpose tool to ease building declarative macros which create +/// new types. /// -/// Effectively it functions as a more powerful version of [paste](https://github.com/dtolnay/paste), -/// whilst bringing the power of [quote](https://docs.rs/quote/latest/quote/)'s variable +/// ## Motivation +/// +/// Effectively it functions as a more powerful version of [paste!](https://github.com/dtolnay/paste), +/// whilst bringing the power of [quote!](https://docs.rs/quote/latest/quote/)'s variable /// substitution to declarative macros. /// /// This approach neatly solves the following cases: -/// * [Pasting into non-doc attributes](https://github.com/dtolnay/paste/issues/40#issuecomment-2062953012) -/// * Simplify handling sub-repetition which currently needs an internal `macro_rules!` definition [per this stack overflow post](https://stackoverflow.com/a/73543948) -/// * Improved readability of long procedural macros through substitution of repeated segments -/// +/// 1. Wanting `paste!` to output strings or work with [attributes other than doc](https://github.com/dtolnay/paste/issues/40#issuecomment-2062953012). +/// 2. Avoiding defining internal `macro_rules!` to handle instances where you need to do a procedural macro repeat across two conflicting expansions [per this stack overflow post](https://stackoverflow.com/a/73543948). +/// 3. Improves readability of long procedural macros through substitution of repeated segments. /// -/// It is particularly useful in scenarios where `paste` doesn't work - in particular, to -/// create non-idents, or to create non-doc attribute string content, which paste cannot do, e.g.: +/// An example of case 1: /// ```rust /// // Inside a macro_rules! expression: /// eager_replace!{ -/// #[sbor(as_type = [!EAGER:stringify! $my_inner_type])] +/// #[sbor(as_type = [!stringify! $my_inner_type])] /// $vis struct $my_type($my_inner_type) /// } /// ``` /// /// ## Specific functions /// -/// * `[!EAGER:stringify! X Y " " Z]` gives `"XY \" \" Z"` -/// * `[!EAGER:concat! X Y " " Z]` gives `"XY Z"` by concatenating each argument stringified without spaces. String and Char literals are first unquoted. Spaces can be added with " ". -/// * `[!EAGER:ident! X Y "Z"]` gives an ident `XYZ`. -/// * `[!EAGER:literal! 31 u 32]` gives `31u32`. -/// * `[!EAGER! ...]` outputs the `...` token stream, can be used for outputting `#[!EAGER! ident]` so that `#ident` isn't detected as a variable. +/// * `[!stringify! X Y " " Z]` gives `"X Y \" \" Z"` +/// * `[!concat! X Y " " Z]` gives `"XY Z"` by concatenating each argument stringified without spaces. String and char literals are first unquoted. Spaces can be added with " ". +/// * `[!ident! X Y "Z"]` gives an ident `XYZ`. +/// * `[!literal! 31 u 32]` gives `31u32`. +/// * `[!raw! abc #abc [!ident! test]]` outputs its contents without any nested expansion, giving `abc #abc [!ident! test]`. +/// +/// Note that all functions except `raw` resolve in a nested manner as you would expected, e.g. +/// ```rust +/// [!concat! X Y [!ident! Hello World] Z] // "XYHelloWorldZ" +/// ``` /// /// ## Variables for cleaner coding /// /// You can define variables starting with `#` which can be used outside the set call. +/// All of the following calls don't return anything, but create a variable, which can be embedded later in the macro. +/// See the `Demonstration` section for details /// -/// * The command `[!EAGER:set! #MyZ = 1 + 2]` doesn't output anything, but sets `#MyZ` to the given token stream. -/// * Similarly `[!EAGER:set:ident! #MyZ = ZZZ]` sets `#MyZ` as an ident. This also works with `stringify`, `concat` and `literal`. +/// * `[!SET! #MyVar = ..]` sets `#MyVar` to the given token stream. +/// * `[!SET:stringify! #MyVar = ..]` sets `#MyVar` to the result of applying the `stringify` function to the token stream. +/// * `[!SET:concat! #MyVar = ..]` sets `#MyVar` to the result of applying the `concat` function to the token stream. +/// * `[!SET:ident! #MyVar = ..]` sets `#MyVar` to the result of applying the `ident` function to the token stream. +/// * `[!SET:literal! #MyVar = ..]` sets `#MyVar` to the result of applying the `literal` function to the token stream. /// -/// ### Demonstration +/// ## Demonstration /// ```rust /// macro_rules! impl_marker_traits { /// { @@ -116,9 +127,9 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// $(,) // Optional trailing comma /// ] /// } => {eager_replace!{ -/// [!EAGER:set! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] -/// [!EAGER:set! #TypeGenerics = $(< $( $lt ),+ >)?] -/// [!EAGER:set:ident! #MyType = Type $type_name_suffix #TypeGenerics] +/// [!SET! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] +/// [!SET! #TypeGenerics = $(< $( $lt ),+ >)?] +/// [!SET:ident! #MyType = Type $type_name_suffix #TypeGenerics] /// /// // Output for each marker trait /// $( diff --git a/sbor-tests/tests/eager.rs b/sbor-tests/tests/eager.rs index 36193ff209e..1ef0a81bc53 100644 --- a/sbor-tests/tests/eager.rs +++ b/sbor-tests/tests/eager.rs @@ -3,12 +3,20 @@ use sbor::*; eager_replace! { - [!EAGER:set:ident! #boo = Hello World 2] - struct HelloWorld2 {} - type [!EAGER:ident! X "Boo" [!EAGER:concat! Hello 1] #boo] = HelloWorld2; + [!SET! #bytes = 32] + [!SET! #postfix = Hello World #bytes] + [!SET:raw! #MyRawVar = Test no #str [!ident! replacement]] + struct MyStruct; + type [!ident! X "Boo" [!concat! Hello 1] #postfix] = MyStruct; + const MY_NUM: u32 = [!literal! 1337u #bytes]; + const MY_STR: &'static str = [!stringify! #MyRawVar]; } #[test] -fn can_create() { - let _x: XBooHello1HelloWorld2 = HelloWorld2 {}; +fn complex_example_evaluates_correctly() { + let _x: XBooHello1HelloWorld32 = MyStruct; + assert_eq!(MY_NUM, 1337u32); + // Note: TokenStream loses information about whether idents are attached to the proceeding punctuation. + // So this is an equivalent raw stream to #MyRawVar. + assert_eq!(MY_STR, "Test no # str [! ident! replacement]"); } diff --git a/sbor/src/schema/schema.rs b/sbor/src/schema/schema.rs index cc6f6530d8e..0ac5291a725 100644 --- a/sbor/src/schema/schema.rs +++ b/sbor/src/schema/schema.rs @@ -9,7 +9,7 @@ define_single_versioned!( impl VersionedSchema { pub fn v1(&self) -> &SchemaV1 { - self.as_unique_version_ref() + self.as_unique_version() } pub fn v1_mut(&mut self) -> &mut SchemaV1 { diff --git a/sbor/src/versioned.rs b/sbor/src/versioned.rs index 093981c5fb0..ac02489ca6b 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -20,22 +20,22 @@ pub trait Versioned: AsRef + AsMut + From bool; /// Updates the latest version in place, and returns a `&mut` to the latest content - fn fully_update_to_latest_version_mut(&mut self) -> &mut Self::LatestVersion { - self.fully_update_mut(); + fn in_place_fully_update_and_as_latest_version_mut(&mut self) -> &mut Self::LatestVersion { + self.in_place_fully_update(); self.as_latest_version_mut().unwrap() } /// Updates to the latest version in place. - fn fully_update_mut(&mut self); + fn in_place_fully_update(&mut self) -> &mut Self; /// Consumes self, updates to the latest version and returns itself. fn fully_update(mut self) -> Self { - self.fully_update_mut(); + self.in_place_fully_update(); self } /// Updates itself to the latest version, then returns the latest content - fn fully_update_into_latest_version(self) -> Self::LatestVersion; + fn fully_update_and_into_latest_version(self) -> Self::LatestVersion; /// Constructs a versioned wrapper around the latest content fn from_latest_version(latest: Self::LatestVersion) -> Self; @@ -44,22 +44,22 @@ pub trait Versioned: AsRef + AsMut + From Option<&Self::LatestVersion>; + /// [`in_place_fully_update_and_as_latest_version_mut`] to update to the latest version first - or, if + /// there is only a single version, use [`as_unique_version`]. + fn as_latest_version(&self) -> Option<&Self::LatestVersion>; /// If the versioned wrapper is at the latest version, it returns /// a mutable reference to the latest content, otherwise it returns `None`. /// /// If you require the latest version unconditionally, consider using - /// [`fully_update_to_latest_version_mut`] to update to the latest version first - or, if + /// [`in_place_fully_update_and_as_latest_version_mut`] to update to the latest version first - or, if /// there is only a single version, use [`as_unique_version_mut`]. fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion>; /// Gets a reference the inner versions enum, for e.g. matching on the enum. /// /// This is essentially a clearer alias for `as_ref`. - fn as_versions_ref(&self) -> &Self::Versions; + fn as_versions(&self) -> &Self::Versions; /// Gets a mutable reference the inner versions enum, for e.g. matching on the enum. /// @@ -79,18 +79,18 @@ pub trait Versioned: AsRef + AsMut + From &Self::LatestVersion; + fn as_unique_version(&self) -> &Self::LatestVersion; /// Returns a mutable reference to (currently) the only possible version of the inner content. /// - /// This is somewhat equivalent to `fully_update_to_latest_version_mut`, but doesn't need to do + /// This is somewhat equivalent to `in_place_fully_update_and_as_latest_version_mut`, but doesn't need to do /// any updating, so can be used where logical correctness requires there to be a unique version, /// requires no updating, or simply for slightly better performance. fn as_unique_version_mut(&mut self) -> &mut Self::LatestVersion; /// Returns the (currently) only possible version of the inner content. /// - /// This is somewhat equivalent to `fully_update_into_latest_version`, but doesn't need to do + /// This is somewhat equivalent to `fully_update_and_into_latest_version`, but doesn't need to do /// any updating, so can be used where logical correctness requires there to be a unique version, /// requires no updating, or simply for slightly better performance. fn into_unique_version(self) -> Self::LatestVersion; @@ -133,7 +133,7 @@ pub trait UniqueVersioned: Versioned { /// /// assert_eq!(a, a2); /// assert_eq!(a2, a3); -/// assert_eq!(42, a.as_unique_version_ref().bar); +/// assert_eq!(42, a.as_unique_version().bar); /// ``` #[macro_export] macro_rules! define_single_versioned { @@ -162,7 +162,7 @@ macro_rules! define_single_versioned { UniqueVersioned for $versioned_name $(< $( $lt ),+ >)? { - fn as_unique_version_ref(&self) -> &Self::LatestVersion { + fn as_unique_version(&self) -> &Self::LatestVersion { match self.as_ref() { $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content, } @@ -242,9 +242,9 @@ macro_rules! define_single_versioned { /// let b = VersionedFoo::from(FooVersions::V2(Foo { bar: 42, baz: None })); /// /// assert_ne!(a, b); -/// assert_eq!(&*a.fully_update_to_latest_version_mut(), b.as_latest_version_ref().unwrap()); +/// assert_eq!(&*a.in_place_fully_update_and_as_latest_version_mut(), b.as_latest_version().unwrap()); /// -/// // After a call to `a.fully_update_to_latest_version_mut()`, `a` has now been updated: +/// // After a call to `a.in_place_fully_update_and_as_latest_version_mut()`, `a` has now been updated: /// assert_eq!(a, b); /// ``` #[macro_export] @@ -270,14 +270,14 @@ macro_rules! define_versioned { } ) => { $crate::eager_replace! { - [!EAGER:set! #FullGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?] - [!EAGER:set! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] - [!EAGER:set! #TypeGenerics = $(< $( $lt ),+ >)?] - [!EAGER:set! #VersionedType = $versioned_name $(< $( $lt ),+ >)?] - [!EAGER:set! #VersionedTypePath = $versioned_name $(::< $( $lt ),+ >)?] - [!EAGER:set! #VersionsType = $versions_name $(< $( $lt ),+ >)?] - [!EAGER:set! #VersionsTypePath = $versions_name $(::< $( $lt ),+ >)?] - [!EAGER:set:ident! #PermitSborAttributesAlias = $versioned_name _PermitSborAttributes] + [!SET! #FullGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? $( = $deflt)? ),+ >)?] + [!SET! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] + [!SET! #TypeGenerics = $(< $( $lt ),+ >)?] + [!SET! #VersionedType = $versioned_name $(< $( $lt ),+ >)?] + [!SET! #VersionedTypePath = $versioned_name $(::< $( $lt ),+ >)?] + [!SET! #VersionsType = $versions_name $(< $( $lt ),+ >)?] + [!SET! #VersionsTypePath = $versions_name $(::< $( $lt ),+ >)?] + [!SET:ident! #PermitSborAttributesAlias = $versioned_name _PermitSborAttributes] #[allow(dead_code)] $vis type $latest_version_alias = $latest_version_type; @@ -287,7 +287,7 @@ macro_rules! define_versioned { #[derive(#PermitSborAttributesAlias)] $(#[$attributes])* // Needs to go below $attributes so that a #[derive(Sbor)] in the attributes can see it. - #[sbor(as_type = [!EAGER:stringify! #VersionsType])] + #[sbor(as_type = [!stringify! #VersionsType])] /// If you wish to get access to match on the versions, use `.as_ref()` or `.as_mut()`. $vis struct $versioned_name #FullGenerics { @@ -340,15 +340,16 @@ macro_rules! define_versioned { self.as_ref().is_fully_updated() } - fn fully_update_mut(&mut self) { + fn in_place_fully_update(&mut self) -> &mut Self { if !self.is_fully_updated() { let current = self.inner.take().unwrap(); self.inner = Some(current.fully_update()); } + self } - fn fully_update_into_latest_version(self) -> Self::LatestVersion { - self.inner.unwrap().fully_update_into_latest_version() + fn fully_update_and_into_latest_version(self) -> Self::LatestVersion { + self.inner.unwrap().fully_update_and_into_latest_version() } /// Constructs the versioned enum from the latest content @@ -356,15 +357,15 @@ macro_rules! define_versioned { Self::new(latest.into()) } - fn as_latest_version_ref(&self) -> Option<&Self::LatestVersion> { - self.as_ref().as_latest_version_ref() + fn as_latest_version(&self) -> Option<&Self::LatestVersion> { + self.as_ref().as_latest_version() } fn as_latest_version_mut(&mut self) -> Option<&mut Self::LatestVersion> { self.as_mut().as_latest_version_mut() } - fn as_versions_ref(&self) -> &Self::Versions { + fn as_versions(&self) -> &Self::Versions { self.as_ref() } @@ -381,14 +382,14 @@ macro_rules! define_versioned { } } - [!EAGER:set:ident! #discriminators = $versioned_name _discriminators] + [!SET:ident! #discriminators = $versioned_name _discriminators] #[allow(non_snake_case)] mod #discriminators { // The initial version of this tool used 0-indexed/off-by-one discriminators accidentally. // We're stuck with these now unfortunately... // But we make them explicit in case versions are skipped. $($( - pub const [!EAGER:ident! VERSION_ $version_num]: u8 = $version_num - 1; + pub const [!ident! VERSION_ $version_num]: u8 = $version_num - 1; )*)? pub const LATEST_VERSION: u8 = $latest_version - 1; } @@ -398,11 +399,11 @@ macro_rules! define_versioned { $vis enum $versions_name #FullGenerics { $($( - #[sbor(discriminator(#discriminators::[!EAGER:ident! VERSION_ $version_num]))] - [!EAGER:ident! V $version_num]($version_type), + #[sbor(discriminator(#discriminators::[!ident! VERSION_ $version_num]))] + [!ident! V $version_num]($version_type), )*)? #[sbor(discriminator(#discriminators::LATEST_VERSION))] - [!EAGER:ident! V $latest_version]($latest_version_type), + [!ident! V $latest_version]($latest_version_type), } #[allow(dead_code)] @@ -412,9 +413,9 @@ macro_rules! define_versioned { fn attempt_single_update(self) -> (bool, Self) { match self { $($( - Self::[!EAGER:ident! V $version_num](value) => (true, Self::[!EAGER:ident! V $update_to_version_num](value.into())), + Self::[!ident! V $version_num](value) => (true, Self::[!ident! V $update_to_version_num](value.into())), )*)? - this @ Self::[!EAGER:ident! V $latest_version](_) => (false, this), + this @ Self::[!ident! V $latest_version](_) => (false, this), } } @@ -434,27 +435,27 @@ macro_rules! define_versioned { #[allow(unreachable_patterns)] pub fn is_fully_updated(&self) -> bool { match self { - Self::[!EAGER:ident! V $latest_version](_) => true, + Self::[!ident! V $latest_version](_) => true, _ => false, } } #[allow(irrefutable_let_patterns)] - fn fully_update_into_latest_version(self) -> $latest_version_type { - let Self::[!EAGER:ident! V $latest_version](latest) = self.fully_update() else { + fn fully_update_and_into_latest_version(self) -> $latest_version_type { + let Self::[!ident! V $latest_version](latest) = self.fully_update() else { panic!("Invalid resolved latest version not equal to latest type") }; return latest; } fn from_latest_version(latest: $latest_version_type) -> Self { - Self::[!EAGER:ident! V $latest_version](latest) + Self::[!ident! V $latest_version](latest) } #[allow(unreachable_patterns)] - fn as_latest_version_ref(&self) -> Option<&$latest_version_type> { + fn as_latest_version(&self) -> Option<&$latest_version_type> { match self { - Self::[!EAGER:ident! V $latest_version](latest) => Some(latest), + Self::[!ident! V $latest_version](latest) => Some(latest), _ => None, } } @@ -462,7 +463,7 @@ macro_rules! define_versioned { #[allow(unreachable_patterns)] fn as_latest_version_mut(&mut self) -> Option<&mut $latest_version_type> { match self { - Self::[!EAGER:ident! V $latest_version](latest) => Some(latest), + Self::[!ident! V $latest_version](latest) => Some(latest), _ => None, } } @@ -472,14 +473,14 @@ macro_rules! define_versioned { #[allow(dead_code)] impl #ImplGenerics From<$version_type> for #VersionsType { fn from(value: $version_type) -> Self { - Self::[!EAGER:ident! V $version_num](value) + Self::[!ident! V $version_num](value) } } #[allow(dead_code)] impl #ImplGenerics From<$version_type> for #VersionedType { fn from(value: $version_type) -> Self { - Self::new(#VersionsTypePath::[!EAGER:ident! V $version_num](value)) + Self::new(#VersionsTypePath::[!ident! V $version_num](value)) } } )*)? @@ -487,18 +488,18 @@ macro_rules! define_versioned { #[allow(dead_code)] impl #ImplGenerics From<$latest_version_type> for #VersionsType { fn from(value: $latest_version_type) -> Self { - Self::[!EAGER:ident! V $latest_version](value) + Self::[!ident! V $latest_version](value) } } #[allow(dead_code)] impl #ImplGenerics From<$latest_version_type> for #VersionedType { fn from(value: $latest_version_type) -> Self { - Self::new($versions_name::[!EAGER:ident! V $latest_version](value)) + Self::new($versions_name::[!ident! V $latest_version](value)) } } - [!EAGER:set:ident! #VersionTrait = $versioned_name Version] + [!SET:ident! #VersionTrait = $versioned_name Version] #[allow(dead_code)] $vis trait #VersionTrait { // Note: We need to use an explicit associated type to capture the generics. @@ -611,9 +612,9 @@ mod tests { let versioned_expected = VersionedExample::from(expected.clone()); // Check fully_update (which returns a VersionedExample) assert_eq!(versioned_actual, versioned_expected,); - // Check fully_update_into_latest_version (which returns an ExampleV4) + // Check fully_update_and_into_latest_version (which returns an ExampleV4) assert_eq!( - versioned_actual.fully_update_into_latest_version(), + versioned_actual.fully_update_and_into_latest_version(), expected, ); } @@ -633,7 +634,7 @@ mod tests { let versioned = VersionedGenericModel::from(v1_model.clone()); let versioned_2 = v1_model.clone().into_versioned(); assert_eq!( - versioned.clone().fully_update_into_latest_version(), + versioned.clone().fully_update_and_into_latest_version(), v1_model ); assert_eq!(versioned, versioned_2); diff --git a/scrypto-test/src/ledger_simulator/ledger_simulator.rs b/scrypto-test/src/ledger_simulator/ledger_simulator.rs index 646922937df..5913d48f8ca 100644 --- a/scrypto-test/src/ledger_simulator/ledger_simulator.rs +++ b/scrypto-test/src/ledger_simulator/ledger_simulator.rs @@ -546,7 +546,7 @@ impl LedgerSimulator { ), ) .unwrap() - .map(|v| v.fully_update_into_latest_version()) + .map(|v| v.fully_update_and_into_latest_version()) } pub fn inspect_component_royalty(&mut self, component_address: ComponentAddress) -> Decimal { @@ -558,7 +558,7 @@ impl LedgerSimulator { ComponentRoyaltyField::Accumulator.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let balance = reader .read_typed_object_field::( @@ -567,7 +567,7 @@ impl LedgerSimulator { FungibleVaultField::Balance.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); balance.amount() } @@ -581,7 +581,7 @@ impl LedgerSimulator { PackageField::RoyaltyAccumulator.field_index(), ) .ok()? - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); let balance = reader .read_typed_object_field::( @@ -590,7 +590,7 @@ impl LedgerSimulator { FungibleVaultField::Balance.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); Some(balance.amount()) } @@ -672,7 +672,7 @@ impl LedgerSimulator { let key: BlueprintVersionKey = scrypto_decode(&map_key).unwrap(); let definition: PackageBlueprintVersionDefinitionEntryPayload = scrypto_decode(&value).unwrap(); - (key, definition.fully_update_into_latest_version()) + (key, definition.fully_update_and_into_latest_version()) }) .collect() } @@ -731,7 +731,7 @@ impl LedgerSimulator { ) .ok(); - vault.map(|v| v.fully_update_into_latest_version().amount()) + vault.map(|v| v.fully_update_and_into_latest_version().amount()) } pub fn inspect_non_fungible_vault( @@ -746,7 +746,7 @@ impl LedgerSimulator { NonFungibleVaultField::Balance.into(), ) .ok()?; - let amount = vault_balance.fully_update_into_latest_version().amount; + let amount = vault_balance.fully_update_and_into_latest_version().amount; // TODO: Remove .collect() by using SystemDatabaseReader in ledger let iter: Vec = reader @@ -828,7 +828,7 @@ impl LedgerSimulator { ) .unwrap() .into_payload() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); total_supply } @@ -900,7 +900,7 @@ impl LedgerSimulator { ValidatorField::State.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); substate } @@ -914,7 +914,7 @@ impl LedgerSimulator { ConsensusManagerField::CurrentValidatorSet.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); substate .validator_set .get_by_public_key(key) @@ -2070,7 +2070,7 @@ impl LedgerSimulator { ConsensusManagerField::State.field_index(), ) .unwrap() - .fully_update_into_latest_version(); + .fully_update_and_into_latest_version(); substate.epoch = epoch; @@ -2145,7 +2145,7 @@ impl LedgerSimulator { ConsensusManagerField::ProposerMilliTimestamp.field_index(), ) .unwrap() - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() .epoch_milli } @@ -2158,7 +2158,7 @@ impl LedgerSimulator { ConsensusManagerField::State.field_index(), ) .unwrap() - .fully_update_into_latest_version() + .fully_update_and_into_latest_version() } pub fn get_current_time(&mut self, precision: TimePrecision) -> Instant { From a675f6dc4974634af4fdda6d8ee226bcce5a7d09 Mon Sep 17 00:00:00 2001 From: David Edey Date: Tue, 23 Apr 2024 16:34:11 +0100 Subject: [PATCH 18/18] fix: Fixes eager_replace test which was compiler-version dependent --- sbor-derive/src/eager.rs | 67 +++++++++++++++++++++++++++------------ sbor-derive/src/lib.rs | 8 ++--- sbor-tests/tests/eager.rs | 16 +++++++--- 3 files changed, 62 insertions(+), 29 deletions(-) diff --git a/sbor-derive/src/eager.rs b/sbor-derive/src/eager.rs index 7b8e259eb53..2422d8b451e 100644 --- a/sbor-derive/src/eager.rs +++ b/sbor-derive/src/eager.rs @@ -262,28 +262,32 @@ fn execute_eager_function( } fn stringify(span: Span, arguments: TokenStream) -> Result { - let stringify_str = arguments.to_string(); - let mut literal = Literal::string(&stringify_str); - literal.set_span(span); - Ok(TokenTree::Literal(literal).into()) + let output = arguments.to_string(); + Ok(str_literal_token_stream(span, &output)) } fn concat(span: Span, arguments: TokenStream) -> Result { - let concat_str = concat_internal(arguments); - let mut literal = Literal::string(&concat_str); + let mut output = String::new(); + concat_recursive_internal(&mut output, arguments); + Ok(str_literal_token_stream(span, &output)) +} + +fn str_literal_token_stream(span: Span, content: &str) -> TokenStream { + let mut literal = Literal::string(content); literal.set_span(span); - Ok(TokenTree::Literal(literal).into()) + TokenTree::Literal(literal).into() } fn concat_ident(span: Span, arguments: TokenStream) -> Result { - let concat_str = concat_internal(arguments); + let mut output = String::new(); + concat_recursive_internal(&mut output, arguments); // As per paste - let ident = match std::panic::catch_unwind(|| Ident::new(&concat_str, span)) { + let ident = match std::panic::catch_unwind(|| Ident::new(&output, span)) { Ok(literal) => literal, Err(_) => { return Err(Error::new( span, - &format!("`{:?}` is not a valid ident", concat_str), + &format!("`{:?}` is not a valid ident", output), )); } }; @@ -291,30 +295,51 @@ fn concat_ident(span: Span, arguments: TokenStream) -> Result { } fn concat_literal(span: Span, arguments: TokenStream) -> Result { - let concat_str = concat_internal(arguments); - // Similar to paste - let mut literal = Literal::from_str(&concat_str) - .map_err(|_| Error::new(span, &format!("`{:?}` is not a valid literal", concat_str)))?; + let mut output = String::new(); + concat_recursive_internal(&mut output, arguments); + let mut literal = Literal::from_str(&output) + .map_err(|_| Error::new(span, &format!("`{:?}` is not a valid literal", output)))?; literal.set_span(span); Ok(TokenTree::Literal(literal).into()) } -fn concat_internal(arguments: TokenStream) -> String { - let mut output_str = String::new(); +fn concat_recursive_internal(output: &mut String, arguments: TokenStream) { for token_tree in arguments { match token_tree { TokenTree::Literal(literal) => { let lit: Lit = parse_quote!(#literal); match lit { - Lit::Str(lit_str) => output_str.push_str(&lit_str.value()), - Lit::Char(lit_char) => output_str.push(lit_char.value()), + Lit::Str(lit_str) => output.push_str(&lit_str.value()), + Lit::Char(lit_char) => output.push(lit_char.value()), _ => { - output_str.push_str(&literal.to_string()); + output.push_str(&literal.to_string()); } } } - _ => output_str.push_str(&token_tree.to_string()), + TokenTree::Group(group) => match group.delimiter() { + Delimiter::Parenthesis => { + output.push('('); + concat_recursive_internal(output, group.stream()); + output.push(')'); + } + Delimiter::Brace => { + output.push('{'); + concat_recursive_internal(output, group.stream()); + output.push('}'); + } + Delimiter::Bracket => { + output.push('['); + concat_recursive_internal(output, group.stream()); + output.push(']'); + } + Delimiter::None => { + concat_recursive_internal(output, group.stream()); + } + }, + TokenTree::Punct(punct) => { + output.push(punct.as_char()); + } + TokenTree::Ident(ident) => output.push_str(&ident.to_string()), } } - output_str } diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index c8975d82cb9..8f4b8ad9d2e 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -91,10 +91,10 @@ pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream { /// /// ## Specific functions /// -/// * `[!stringify! X Y " " Z]` gives `"X Y \" \" Z"` -/// * `[!concat! X Y " " Z]` gives `"XY Z"` by concatenating each argument stringified without spaces. String and char literals are first unquoted. Spaces can be added with " ". -/// * `[!ident! X Y "Z"]` gives an ident `XYZ`. -/// * `[!literal! 31 u 32]` gives `31u32`. +/// * `[!stringify! X Y " " Z]` gives `"X Y \" \" Z"` - IMPORTANT: This uses `token_stream.into_string()` which is compiler-version dependent. Do not use if that is important. Instead, the output from `concat` should be independent of compiler version. +/// * `[!concat! X Y " " Z (Hello World)]` gives `"XY Z(HelloWorld)"` by concatenating each argument without spaces, and recursing inside groups. String and char literals are first unquoted. Spaces can be added with " ". +/// * `[!ident! X Y "Z"]` gives an ident `XYZ`, using the same algorithm as `concat`. +/// * `[!literal! 31 u 32]` gives `31u32`, using the same algorithm as `concat`. /// * `[!raw! abc #abc [!ident! test]]` outputs its contents without any nested expansion, giving `abc #abc [!ident! test]`. /// /// Note that all functions except `raw` resolve in a nested manner as you would expected, e.g. diff --git a/sbor-tests/tests/eager.rs b/sbor-tests/tests/eager.rs index 1ef0a81bc53..c80da5a4f19 100644 --- a/sbor-tests/tests/eager.rs +++ b/sbor-tests/tests/eager.rs @@ -9,14 +9,22 @@ eager_replace! { struct MyStruct; type [!ident! X "Boo" [!concat! Hello 1] #postfix] = MyStruct; const MY_NUM: u32 = [!literal! 1337u #bytes]; - const MY_STR: &'static str = [!stringify! #MyRawVar]; + const MY_CONCAT: &'static str = [!concat! #MyRawVar]; + const MY_STRINGIFY: &'static str = [!stringify! #MyRawVar]; } #[test] fn complex_example_evaluates_correctly() { let _x: XBooHello1HelloWorld32 = MyStruct; assert_eq!(MY_NUM, 1337u32); - // Note: TokenStream loses information about whether idents are attached to the proceeding punctuation. - // So this is an equivalent raw stream to #MyRawVar. - assert_eq!(MY_STR, "Test no # str [! ident! replacement]"); + assert_eq!(MY_CONCAT, "Testno#str[!ident!replacement]"); + // Note: TokenStream loses information about whether idents are attached to the proceeding punctuation... + // And actually the exact string is compiler-version dependent! + // + // Both of the following have been seen on different test runs on develop/CI: + // * `"Test no # str [! ident! replacement]"` + // * `"Test no # str [!ident! replacement]"` + // + // So instead, let's remove spaces and check it's equivalent to flatten_concat: + assert_eq!(MY_STRINGIFY.replace(" ", ""), MY_CONCAT); }