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-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/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-clis/src/replay/cmd_sync.rs b/radix-clis/src/replay/cmd_sync.rs index 89d5213f699..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 - .into_latest() + .fully_update_and_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..5dabe36c443 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_and_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_and_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_and_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_and_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_and_into_latest_version()) }) .collect() } diff --git a/radix-clis/src/resim/mod.rs b/radix-clis/src/resim/mod.rs index 55e7f39b7f9..c89f4e04c5d 100644 --- a/radix-clis/src/resim/mod.rs +++ b/radix-clis/src/resim/mod.rs @@ -409,9 +409,9 @@ pub fn get_event_schema( ) .unwrap()?; - let bp_interface = match bp_definition { - VersionedPackageBlueprintVersionDefinition::V1(blueprint) => blueprint.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 { @@ -489,7 +489,7 @@ pub fn db_upsert_epoch(epoch: Epoch) -> Result<(), Error> { started: true, }) }) - .into_latest(); + .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 a552386eb19..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_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_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_ref() + .as_latest_version() .ok_or(schema::SchemaError::FailedToGetSchemaFromSchemaHash)? .resolve_type_validation(type_identifier.1) .ok_or(schema::SchemaError::NonExistentLocalTypeIndex( 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/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..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 VersionedSchema::V1(s) = schema; + 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-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/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(), 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, diff --git a/radix-engine-tests/tests/kernel/test_environment.rs b/radix-engine-tests/tests/kernel/test_environment.rs index f53f231422c..071fe14bad1 100644 --- a/radix-engine-tests/tests/kernel/test_environment.rs +++ b/radix-engine-tests/tests/kernel/test_environment.rs @@ -232,12 +232,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.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..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() - .into_latest(); + .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.into_latest()); + .map(|v| v.fully_update_and_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_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() - .into_latest(); + .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.into_latest()) + .map(|versioned| versioned.fully_update_and_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..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.into_latest(); + 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/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/radix-engine/src/blueprints/access_controller/blueprint.rs b/radix-engine/src/blueprints/access_controller/blueprint.rs index d856d01bf3c..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.into_latest() + 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.into_latest() + 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.into_latest() + 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 311222d39e7..47076aaaf9d 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_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.into_latest()); + .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.into_latest()); + .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 d55dc1c224a..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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .fully_update_and_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_and_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_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)? - .into_latest(); + .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, )? - .into_latest(); + .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, )? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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.into_latest(); + 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 - .into_latest() + .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.into_latest(); + 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( @@ -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_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; @@ -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_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; @@ -1187,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.into_latest(); + 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 @@ -1198,7 +1200,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_and_into_latest_version(); let previous_statistics = statistic_substate.validator_statistics; // Read & write validator rewards @@ -1209,7 +1211,7 @@ impl ConsensusManagerBlueprint { )?; let mut rewards_substate = api .field_read_typed::(rewards_handle)? - .into_latest(); + .fully_update_and_into_latest_version(); // Apply emissions Self::apply_validator_emissions_and_rewards( @@ -1247,19 +1249,21 @@ 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.as_unique_version(); + let validator2 = validator_2.as_unique_version(); + validator1.stake.cmp(&validator2.stake).reverse() }); let next_active_validator_set = ActiveValidatorSet { 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_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 3a09097da73..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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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().into_latest(); + .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().into_latest(); + .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 282a4906077..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.into_latest()); + .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, @@ -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_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/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 65c6692c851..027dc9eb70a 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 { ( @@ -579,7 +582,86 @@ 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, + payload_trait: $payload_trait, + ---- + $(#[$attributes])* + pub struct $payload_type_name([]); + ); + + 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_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`]."] + pub fn from_latest_version(latest_version: $ident_core) -> Self { + []::from_latest_version(latest_version).into() + } + + #[doc = "Delegates to [`"[]"::into_unique_version`]."] + pub fn into_unique_version(self) -> $ident_core { + self.content.into_unique_version() + } + + #[doc = "Delegates to [`"[]"::from_unique_version`]."] + pub fn from_unique_version(unique_version: $ident_core) -> Self { + []::from_unique_version(unique_version).into() + } + } + + // 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, @@ -589,20 +671,43 @@ mod helper_macros { pub struct $payload_type_name([]); ); - impl HasLatestVersion for $payload_type_name + // NOTE: If updating here, also update StaticSingleVersioned + impl $payload_type_name { - type Latest = <[] as HasLatestVersion>::Latest; - fn into_latest(self) -> Self::Latest { - self.into_content().into_latest() + #[doc = "Delegates to [`"[]"::fully_update`]."] + pub fn fully_update(self) -> Self { + Self::of(self.content.fully_update()) + } + + #[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() } - 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() } } - // 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>] + // > 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() @@ -660,7 +765,6 @@ mod helper_macros { impl [<$ident_core ContentMarker>] for RawScryptoValue<'_> {} } }; - // TODO - Add support for some kind of StaticMultiVersioned type here } #[allow(unused)] @@ -817,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, @@ -842,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)] @@ -954,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)] @@ -985,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, @@ -1057,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(); @@ -1099,45 +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 = VersionedTestBlueprintRoyalty::V1(TestBlueprintRoyaltyV1); - let mut payload = TestBlueprintRoyaltyFieldPayload { - content: VersionedTestBlueprintRoyalty::V1(TestBlueprintRoyaltyV1), - }; + 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: VersionedTestBlueprintMyCoolKeyValueStore::V1( - TestBlueprintMyCoolKeyValueStoreV1, - ), - } + TestBlueprintMyCoolKeyValueStoreEntryPayload::of( + TestBlueprintMyCoolKeyValueStoreV1.into(), + ) } assert_eq!( @@ -1164,40 +1279,41 @@ mod tests { .lock_status() ); - assert!(create_payload().as_latest_ref().is_some()); + assert!(create_payload().as_latest_version().is_some()); } #[test] fn validate_index_entry_payload() { - let payload = TestBlueprintMyCoolIndexEntryPayload { - content: VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1), - }; + let payload = TestBlueprintMyCoolIndexEntryPayload::of(TestBlueprintMyCoolIndexV1.into()); assert_eq!( - payload.into_substate().value().content, - VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1) + payload.into_substate().into_value().into_content(), + VersionedTestBlueprintMyCoolIndex::from(TestBlueprintMyCoolIndexV1) ); - let content = VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1); + let content = + TestBlueprintMyCoolIndexVersions::V1(TestBlueprintMyCoolIndexV1).into_versioned(); assert_eq!( - VersionedTestBlueprintMyCoolIndex::V1(TestBlueprintMyCoolIndexV1), - 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: VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1), - }; + let payload = + TestBlueprintMyCoolSortedIndexEntryPayload::of(TestBlueprintMyCoolSortedIndexV1.into()); assert_eq!( - payload.into_substate().value().content, - VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1) + payload.into_substate().into_value().into_content(), + TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned() ); - let content = VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1); + let content = TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned(); assert_eq!( - VersionedTestBlueprintMyCoolSortedIndex::V1(TestBlueprintMyCoolSortedIndexV1), - content.into_substate().value().content + TestBlueprintMyCoolSortedIndexVersions::V1(TestBlueprintMyCoolSortedIndexV1) + .into_versioned(), + content.into_substate().into_value().into_content() ); } @@ -1236,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 97d500e6524..49e007afc3d 100644 --- a/radix-engine/src/blueprints/models/payloads.rs +++ b/radix-engine/src/blueprints/models/payloads.rs @@ -11,14 +11,29 @@ 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. + /// + /// 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> @@ -40,6 +55,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 +75,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..30ddee935d7 100644 --- a/radix-engine/src/blueprints/package/package.rs +++ b/radix-engine/src/blueprints/package/package.rs @@ -1483,11 +1483,13 @@ 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() + .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), } - PackageRoyaltyConfig::Disabled => Some(RoyaltyAmount::Free), }) .unwrap_or(RoyaltyAmount::Free); @@ -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_and_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_and_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_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 2ffb95b93f3..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 @@ -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,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.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 a7e0565ab61..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 @@ -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)? + .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 073ce9702d6..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 @@ -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)? + .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 b9b5dede139..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 @@ -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_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 7627c1daf86..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)? - .into_latest(); + .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 7a15fc70555..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)? - .into_latest(); + .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 ecb5c5935aa..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, )? - .into_latest(); + .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 f70763ada26..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.into_latest() + divisibility.fully_update_and_into_latest_version() }; // check amount @@ -589,7 +589,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( total_supply_handle, )? - .into_latest(); + .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, )? - .into_latest(); + .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, )? - .into_latest(); + .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, )? - .into_latest(); + .fully_update_and_into_latest_version(); Ok(Some(total_supply)) } else { Ok(None) @@ -807,7 +807,7 @@ impl FungibleResourceManagerBlueprint { .field_read_typed::( divisibility_handle, )? - .into_latest(); + .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 5a3685daccb..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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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 1a6ba5f8d8b..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, )? - .into_latest(); + .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)? - .into_latest(); + .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, )? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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, )? - .into_latest(); + .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 4e0a2c52d4f..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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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)? - .into_latest(); + .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 a676ef21d97..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| v.into_latest())) + 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 fd5f6a3235c..c088a6f3bf2 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_and_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_and_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_and_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_and_into_latest_version())) } } @@ -662,7 +664,7 @@ impl RoleAssignmentBottlenoseExtension { )?; let owner_role_entry = api .field_read_typed::(handle)? - .into_latest() + .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 451704ef091..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)? - .into_latest(); + .fully_update_and_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_and_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_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 05c9edff09f..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.into_latest(); + 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 9ed38e6323d..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.into_latest(); + 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 9661aae6df0..ae8ac0e5a81 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,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.into_latest()); + tracker.expected = + Some(total_supply.fully_update_and_into_latest_version()); } _ => {} } @@ -81,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.into_latest().amount(); + let amount = vault_balance + .fully_update_and_into_latest_version() + .amount(); if amount.is_negative() { panic!("Found Fungible Vault negative balance"); @@ -104,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.into_latest()); + tracker.expected = + Some(total_supply.fully_update_and_into_latest_version()); } _ => {} } @@ -119,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.into_latest(); + let vault_balance = vault_balance.fully_update_and_into_latest_version(); tracker.tracking_supply = tracker .tracking_supply .checked_add(vault_balance.amount) @@ -148,7 +151,7 @@ impl ApplicationChecker for ResourceDatabaseChecker { let mut prev = Decimal::MAX; for validator in validator_set - .into_latest() + .fully_update_and_into_latest_version() .validator_set .validators_by_stake_desc { @@ -162,7 +165,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_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 6dd15d97e34..3814f4baaa8 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_and_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.content.into_latest(); + 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 57f5321c2d3..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.into_latest(), + 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_callback.rs b/radix-engine/src/system/system_callback.rs index 9e19b567171..9b75b9347df 100644 --- a/radix-engine/src/system/system_callback.rs +++ b/radix-engine/src/system/system_callback.rs @@ -189,7 +189,7 @@ impl System { Some(x) => { let substate: FieldSubstate = x.as_typed().unwrap(); - Some(substate.into_payload().into_latest().epoch) + Some(substate.into_payload().into_unique_version().epoch) } None => None, } @@ -330,7 +330,7 @@ impl System { .as_typed::() .unwrap() .into_payload() - .into_latest(); + .into_unique_version(); vault_balance.put(LiquidFungibleResource::new(amount)); let updated_substate_content = FungibleVaultBalanceFieldPayload::from_content_source(vault_balance) @@ -389,7 +389,7 @@ impl System { .as_typed::() .unwrap() .into_payload() - .into_latest(); + .into_unique_version(); vault_balance.put(locked); let updated_substate_content = FungibleVaultBalanceFieldPayload::from_content_source(vault_balance) @@ -463,7 +463,7 @@ impl System { .unwrap() .as_typed() .unwrap(); - let current_leader = substate.into_payload().into_latest().current_leader; + let current_leader = substate.into_payload().into_unique_version().current_leader; // Update validator rewards let substate: FieldSubstate = track @@ -476,7 +476,7 @@ impl System { .as_typed() .unwrap(); - let mut rewards = substate.into_payload().into_latest(); + let mut rewards = substate.into_payload().into_unique_version(); if let Some(current_leader) = current_leader { let entry = rewards.proposer_rewards.entry(current_leader).or_default(); @@ -510,7 +510,7 @@ impl System { .as_typed::() .unwrap() .into_payload() - .into_latest(); + .into_unique_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/system/system_db_reader.rs b/radix-engine/src/system/system_db_reader.rs index a099d38fa1f..8e125b2e4bc 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, @@ -16,9 +17,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}; @@ -160,7 +159,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_and_into_latest_version(), ); } @@ -537,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().into_latest()); + ).ok_or_else(|| SystemReaderError::BlueprintDoesNotExist)?.into_value().unwrap().fully_update_and_into_latest_version()); self.blueprint_cache .borrow_mut() @@ -865,7 +867,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_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 9d1ef068d18..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.content.into_latest(), + 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() - .into_latest() + .fully_update_and_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 059ffc76458..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().into_latest().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().into_latest().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/transaction/transaction_receipt.rs b/radix-engine/src/transaction/transaction_receipt.rs index 96fdbc04018..31047ac632c 100644 --- a/radix-engine/src/transaction/transaction_receipt.rs +++ b/radix-engine/src/transaction/transaction_receipt.rs @@ -23,7 +23,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 68afce3ea76..1da52778f4a 100644 --- a/radix-engine/src/updates/state_updates.rs +++ b/radix-engine/src/updates/state_updates.rs @@ -25,7 +25,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. @@ -77,11 +76,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, } @@ -131,7 +130,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_and_into_latest_version(); let export = definition .function_exports @@ -164,7 +163,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() }; @@ -258,15 +259,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(); @@ -294,7 +296,8 @@ 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_and_into_latest_version(); for (_, export) in blueprint_definition.function_exports.iter_mut() { export.code_hash = new_code_hash @@ -302,7 +305,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(), ) @@ -382,7 +386,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_and_into_latest_version(); config.config.validator_creation_usd_cost = Decimal::from(100); let updated_substate = config.into_locked_substate(); @@ -419,9 +423,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, @@ -449,7 +454,7 @@ pub fn generate_owner_role_getter_state_updates(db: &S) -> ) .unwrap() .unwrap() - .into_latest(); + .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 { @@ -600,9 +605,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, @@ -626,7 +631,7 @@ pub fn generate_account_bottlenose_extension_state_updates( ) .unwrap() .unwrap() - .into_latest(); + .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 b9acfcaa659..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.into_latest().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( @@ -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_and_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_and_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/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( diff --git a/radix-sbor-derive/src/scrypto_encode.rs b/radix-sbor-derive/src/scrypto_encode.rs index 7f04fe44bad..eeec7226c5b 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 6a1a0228236..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 @@ -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_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_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.into_latest()) + .map(|versioned| versioned.fully_update_and_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..9ca9fdab71a 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. @@ -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 { diff --git a/radix-substate-store-queries/src/query/traverse.rs b/radix-substate-store-queries/src/query/traverse.rs index b0130163c4d..b946f0d9e6b 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.fully_update_and_into_latest_version(); 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.fully_update_and_into_latest_version(); visitor.visit_non_fungible_vault( node_id, @@ -196,7 +192,7 @@ impl<'s, 'v, S: SubstateDatabase, V: StateTreeVisitor + 'v> StateTreeTraverser<' 0u8, ) .expect("Broken database") - .into_latest(); + .fully_update_and_into_latest_version(); Self::traverse_recursive( system_db_reader, visitor, diff --git a/radix-transaction-scenarios/assets/blueprints/radiswap/src/lib.rs b/radix-transaction-scenarios/assets/blueprints/radiswap/src/lib.rs index ef9cce2f203..f948a5468aa 100644 --- a/radix-transaction-scenarios/assets/blueprints/radiswap/src/lib.rs +++ b/radix-transaction-scenarios/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/radix-transaction-scenarios/src/executor.rs b/radix-transaction-scenarios/src/executor.rs index 2a3f489b7c1..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() - .into_latest() + .fully_update_and_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 5f33be82e9b..399b4ff1b53 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 { @@ -70,25 +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_for_encode(&v.fields)?; - Ok(( - quote! { Self::#v_id #empty_fields_unpacking => #discriminator, }, - quote! { Self::#v_id #empty_fields_unpacking => #unskipped_field_count, }, - )) + .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, }, + ) + }, + 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(); @@ -143,98 +161,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)] @@ -282,7 +337,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() @@ -293,7 +349,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) } } @@ -301,11 +357,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) } } }, @@ -344,7 +400,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() @@ -355,7 +413,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) } } @@ -363,11 +421,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 9358de32cf2..50f8a683427 100644 --- a/sbor-derive-common/src/decode.rs +++ b/sbor-derive-common/src/decode.rs @@ -18,12 +18,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")] @@ -37,17 +42,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, @@ -55,7 +52,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.")); } @@ -65,52 +62,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) + } } }; @@ -147,21 +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(quote! { - #discriminator => { - #decode_fields_content - } - }) - }) + .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 + } + }) + }, + ) .collect::>>()?; // Note: We use #[deny(unreachable_patterns)] to protect against users @@ -201,7 +225,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(_) => { @@ -217,7 +241,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>()?}) @@ -285,9 +309,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 766eb0e8971..904427808e5 100644 --- a/sbor-derive-common/src/describe.rs +++ b/sbor-derive-common/src/describe.rs @@ -20,12 +20,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")] @@ -40,22 +45,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.")); @@ -63,50 +59,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) + } } }; @@ -124,12 +159,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( @@ -156,7 +190,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 { @@ -181,7 +215,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! { @@ -216,54 +250,55 @@ 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 EnumVariantsData { sbor_variants, .. } = process_enum_variants(&attrs, &variants)?; + 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 = sbor_variants + .iter() + .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, + .. + } = fields_data; + + all_field_types.extend_from_slice(&unskipped_field_types); + + let variant_type_data = match &source_variant.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, + })) + }) + .collect::>>()?; let unique_field_types = get_unique_types(&all_field_types); @@ -276,7 +311,7 @@ fn handle_normal_describe( sbor::TypeData::enum_variants( #type_name, sbor::rust::prelude::indexmap![ - #(#variant_discriminators => #variant_type_data,)* + #(#match_arms)* ], ) } @@ -295,6 +330,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 a083a6bde08..1e8bf679693 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")] @@ -37,41 +40,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 +70,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 +129,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] @@ -123,25 +148,49 @@ 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_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)?;)* + .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)?; + encoder.write_size(#unskipped_field_count)?; + #(encoder.encode(#unskipped_unpacked_field_names)?;)* + } + } + } + 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), + } } }) }) @@ -298,9 +347,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 ab8c2df78b1..b44515afdc3 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); }; @@ -195,15 +195,34 @@ pub fn extract_typed_attributes( Ok(fields) } -enum VariantValue { - Byte(LitByte), - Path(Path), // EG a constant +pub(crate) enum SourceVariantData { + Reachable(VariantData), + Unreachable(UnreachableVariantData), } -pub fn get_variant_discriminator_mapping( +#[derive(Clone)] +pub(crate) struct UnreachableVariantData { + pub source_variant: Variant, + pub fields_data: FieldsData, +} + +#[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(), @@ -212,90 +231,191 @@ 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"), - ) - })?, + get_sbor_attribute_bool_value(enum_attributes, "use_repr_discriminators")?.value(); + + 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(SourceVariantData::Unreachable(UnreachableVariantData { + fields_data, + source_variant, + })); + } + 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::>()?; - variant_ids.insert(i, id); - continue; + 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 sbor_variants = source_variants + .iter() + .filter_map(|source_variant| match source_variant { + SourceVariantData::Reachable(variant_data) => Some(variant_data.clone()), + SourceVariantData::Unreachable(_) => None, + }) + .collect(); + + 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, } - 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())) + } + + fn explicit_path(value: &Path) -> Self { + Self { + expression: parse_quote!(#value), + pattern: parse_quote!(#value), + implicit: false, + } + } + + 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)); } - _ => 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."), - )); - }; - - variant_ids.insert(i, id); - continue; + } + 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 variant_ids.len() > 0 { - if variant_ids.len() < variants.len() { - 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()), - )); + 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, + }; + + 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); } - return Ok(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) - }) - .collect()); } - // If no explicit indices, use default indices - Ok(variants - .iter() - .enumerate() - .map(|(i, _)| { - let i_as_u8 = u8::try_from(i).unwrap(); - (i, parse_quote!(#i_as_u8)) - }) - .collect()) + + 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_u8_from_literal(literal: &Lit) -> Option { +fn parse_expr_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, + } +} + +fn parse_pattern_from_literal(literal: &Lit) -> Option { + match literal { + Lit::Str(str_literal) => str_literal.parse().ok(), _ => None, } } @@ -303,43 +423,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 { @@ -352,15 +533,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 { @@ -376,23 +572,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 { @@ -410,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, @@ -422,27 +677,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(); @@ -570,7 +805,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 { @@ -582,8 +817,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 { @@ -636,7 +870,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 { @@ -648,8 +882,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 { @@ -703,7 +936,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 { @@ -744,11 +977,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(); @@ -758,7 +990,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 { @@ -766,23 +998,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 @@ -842,16 +1057,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/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 new file mode 100644 index 00000000000..2422d8b451e --- /dev/null +++ b/sbor-derive/src/eager.rs @@ -0,0 +1,345 @@ +use core::iter; +use proc_macro2::*; +use std::{collections::HashMap, str::FromStr}; +use syn::*; + +type TokenIter = ::IntoIter; +type PeekableTokenIter = iter::Peekable; + +pub(crate) fn replace(token_stream: TokenStream) -> Result { + let mut state = EagerState::new(); + replace_recursive(&mut state, token_stream.into_iter()) +} + +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(state: &mut EagerState, token_iter: TokenIter) -> Result { + let mut tokens = token_iter.peekable(); + let mut expanded = TokenStream::new(); + loop { + match consume_next_meaningful_token_batch(&mut tokens)? { + MeaningfulTokenBatch::EagerCallStart(call_kind_group, eager_call_intent) => { + let call_output = + execute_eager_call(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 [!raw! {marker}{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(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, +) -> Result { + Ok(match tokens.next() { + None => MeaningfulTokenBatch::EndOfStream, + Some(TokenTree::Group(group)) => { + if let Some(eager_call_intent) = denotes_eager_call_intent(&group)? { + MeaningfulTokenBatch::EagerCallStart(group, eager_call_intent) + } else { + 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 { + MeaningfulTokenBatch::Leaf(TokenTree::Punct(punct)) + } + } else { + MeaningfulTokenBatch::Leaf(TokenTree::Punct(punct)) + } + } + Some(leaf) => MeaningfulTokenBatch::Leaf(leaf), + }) +} + +enum EagerIntentKind { + Output(EagerFunctionKind), + Set(EagerFunctionKind), +} + +enum EagerFunctionKind { + Stringify, + Concat, + Ident, + Literal, + ProcessedTokens, + RawTokens, +} + +struct EagerCallIntent { + intent_kind: EagerIntentKind, + args: TokenIter, +} + +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); + } + let Some(TokenTree::Ident(call_ident)) = tokens.next() else { + return Ok(None); + }; + + // 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())); + }; + 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())); + } + intent_kind + } + _ => return Err(eager_call_intent_error(group.span())), + } + } + _ => { + 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 { + intent_kind, + args: tokens, + })) +} + +fn eager_call_intent_error(span: Span) -> Error { + Error::new( + span, + "Expected `[!! ..]`, `[!SET! #var = ..]` or `[!SET:! #var = ..]` for one of: stringify, concat, ident, literal or raw.", + ) +} + +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, + "raw" => EagerFunctionKind::RawTokens, + func => { + return Err(Error::new( + ident.span(), + format!("Unknown function: {func}"), + )) + } + }) +} + +fn consume_expected_punct(tokens: &mut TokenIter, char: char) -> Option { + let Some(TokenTree::Punct(punct)) = tokens.next() else { + return None; + }; + if punct.as_char() != char { + return None; + } + Some(punct) +} + +fn execute_eager_call( + state: &mut EagerState, + call_intent: EagerCallIntent, + span: Span, +) -> Result { + match call_intent.intent_kind { + EagerIntentKind::Output(func) => { + execute_eager_function(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(state, func, span, tokens)?; + state.set_variable(ident.to_string(), result_tokens); + + return Ok(TokenStream::new()); + } + } +} + +fn execute_eager_function( + state: &mut EagerState, + function_kind: EagerFunctionKind, + span: Span, + token_iter: TokenIter, +) -> Result { + Ok(match function_kind { + 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(), + }) +} + +fn stringify(span: Span, arguments: TokenStream) -> Result { + let output = arguments.to_string(); + Ok(str_literal_token_stream(span, &output)) +} + +fn concat(span: Span, arguments: TokenStream) -> Result { + 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); + TokenTree::Literal(literal).into() +} + +fn concat_ident(span: Span, arguments: TokenStream) -> Result { + let mut output = String::new(); + concat_recursive_internal(&mut output, arguments); + // As per paste + 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", output), + )); + } + }; + Ok(TokenTree::Ident(ident).into()) +} + +fn concat_literal(span: Span, arguments: TokenStream) -> Result { + 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_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.push_str(&lit_str.value()), + Lit::Char(lit_char) => output.push(lit_char.value()), + _ => { + output.push_str(&literal.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()), + } + } +} diff --git a/sbor-derive/src/lib.rs b/sbor-derive/src/lib.rs index 3f07edd5f38..8f4b8ad9d2e 100644 --- a/sbor-derive/src/lib.rs +++ b/sbor-derive/src/lib.rs @@ -1,4 +1,6 @@ use proc_macro::TokenStream; +use std::str::FromStr; +mod eager; /// Derive code that returns the value kind. #[proc_macro_derive(Categorize, attributes(sbor))] @@ -41,6 +43,116 @@ 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() +} + +/// 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 which create +/// new types. +/// +/// ## 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: +/// 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. +/// +/// An example of case 1: +/// ```rust +/// // Inside a macro_rules! expression: +/// eager_replace!{ +/// #[sbor(as_type = [!stringify! $my_inner_type])] +/// $vis struct $my_type($my_inner_type) +/// } +/// ``` +/// +/// ## Specific functions +/// +/// * `[!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. +/// ```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 +/// +/// * `[!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 +/// ```rust +/// macro_rules! impl_marker_traits { +/// { +/// $(#[$attributes:meta])* +/// $vis:vis $type_name_suffix:ident +/// // Arbitrary generics +/// $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)? +/// [ +/// $($trait:ident),* +/// $(,) // Optional trailing comma +/// ] +/// } => {eager_replace!{ +/// [!SET! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?] +/// [!SET! #TypeGenerics = $(< $( $lt ),+ >)?] +/// [!SET:ident! #MyType = Type $type_name_suffix #TypeGenerics] +/// +/// // Output for each marker trait +/// $( +/// impl #ImplGenerics $trait for #MyType +/// { +/// // Empty trait body +/// } +/// )* +/// }} +/// } +/// ``` +/// +/// ## 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(proc_macro2::TokenStream::from(token_stream)) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind"; const BASIC_CUSTOM_TYPE_KIND: &str = "sbor::NoCustomTypeKind"; 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-tests/tests/eager.rs b/sbor-tests/tests/eager.rs new file mode 100644 index 00000000000..c80da5a4f19 --- /dev/null +++ b/sbor-tests/tests/eager.rs @@ -0,0 +1,30 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use sbor::*; + +eager_replace! { + [!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_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); + 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); +} diff --git a/sbor-tests/tests/enum.rs b/sbor-tests/tests/enum.rs index ddef74e24eb..4c414612dc0 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(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(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-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 = 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 6c537dfd4a9..060f6f49d80 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::{ - BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, Categorize, Decode, - Describe, Encode, Sbor, + eager_replace, BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, Categorize, + Decode, Describe, Encode, PermitSborAttributes, Sbor, }; // extern crate self as X; in lib.rs allows ::X and X to resolve to this crate inside this crate. @@ -98,6 +98,7 @@ 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::path::{SborPath, SborPathBuf}; @@ -105,11 +106,16 @@ pub mod prelude { pub use crate::schema::prelude::*; pub use crate::value::{CustomValue as SborCustomValue, Value as SborValue}; pub use crate::value_kind::*; - pub use crate::versioned::{CloneIntoLatest, HasLatestVersion, UpdateResult}; + pub use crate::versioned::*; pub use crate::{ basic_decode, basic_encode, BasicCategorize, BasicDecode, BasicDescribe, BasicEncode, BasicSbor, }; + pub use crate::{define_single_versioned, define_versioned}; pub use crate::{Categorize, Decode, Encode, Sbor, SborEnum, SborTuple}; pub use crate::{DecodeError, EncodeError}; } + +pub(crate) mod internal_prelude { + pub use crate::prelude::*; +} diff --git a/sbor/src/schema/schema.rs b/sbor/src/schema/schema.rs index 7445a6b83c0..0ac5291a725 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 VersionedSchema(SchemaVersions) => Schema = SchemaV1:: ); impl VersionedSchema { pub fn v1(&self) -> &SchemaV1 { - match self { - VersionedSchema::V1(schema) => schema, - } + self.as_unique_version() } pub fn v1_mut(&mut self) -> &mut SchemaV1 { - match self { - VersionedSchema::V1(schema) => schema, - } + 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 caf7c825a2e..ac02489ca6b 100644 --- a/sbor/src/versioned.rs +++ b/sbor/src/versioned.rs @@ -1,47 +1,152 @@ -pub enum UpdateResult { - Updated(T), - AtLatest(T), -} - -/// 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 { - type Latest; - fn into_latest(self) -> Self::Latest; - fn as_latest_ref(&self) -> Option<&Self::Latest>; -} +use crate::internal_prelude::*; -pub trait CloneIntoLatest { - type Latest; - fn clone_into_latest(&self) -> Self::Latest; -} +/// 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; + + /// The type for the latest content. + 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 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() + } -impl + Clone, Latest> CloneIntoLatest for T { - type Latest = Latest; + /// Updates to the latest version in place. + fn in_place_fully_update(&mut self) -> &mut Self; - fn clone_into_latest(&self) -> Self::Latest { - self.clone().into_latest() + /// Consumes self, updates to the latest version and returns itself. + fn fully_update(mut self) -> Self { + self.in_place_fully_update(); + self } + + /// Updates itself to the latest version, then returns the latest content + 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; + + /// 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 + /// [`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 + /// [`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(&self) -> &Self::Versions; + + /// 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; +} + +/// 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(&self) -> &Self::LatestVersion; + + /// Returns a mutable reference to (currently) the only possible version of the inner content. + /// + /// 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_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; + + /// 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. /// 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. /// -/// This macro is just a simpler wrapper around the [`crate::define_versioned`] macro, +/// 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 [`define_versioned`] macro, /// for use when there's just a single version. +/// +/// Example usage: +/// ```rust +/// use sbor::prelude::*; +/// +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] +/// pub struct FooV1 { +/// bar: u8, +/// } +/// +/// define_single_versioned! { +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, Sbor)] +/// pub VersionedFoo(FooVersions) => Foo = FooV1 +/// } +/// +/// // `Foo` is created as an alias for `FooV1` +/// 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!(a, a2); +/// assert_eq!(a2, a3); +/// assert_eq!(42, a.as_unique_version().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: [], @@ -50,6 +155,36 @@ macro_rules! define_single_versioned { }, } ); + + $crate::paste::paste! { + #[allow(dead_code)] + impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? + UniqueVersioned + for $versioned_name $(< $( $lt ),+ >)? + { + fn as_unique_version(&self) -> &Self::LatestVersion { + match self.as_ref() { + $versions_name $(::< $( $lt ),+ >)? ::V1(content) => content, + } + } + + 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() + } + } + } }; } @@ -61,11 +196,62 @@ 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::*; +/// +/// #[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)] +/// VersionedFoo(FooVersions) { +/// previous_versions: [ +/// 1 => FooV1: { updates_to: 2 }, +/// ], +/// latest_version: { +/// 2 => Foo = FooV2, +/// }, +/// } +/// ); +/// +/// 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 b = VersionedFoo::from(FooVersions::V2(Foo { bar: 42, baz: None })); +/// +/// assert_ne!(a, b); +/// assert_eq!(&*a.in_place_fully_update_and_as_latest_version_mut(), b.as_latest_version().unwrap()); +/// +/// // 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] 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)? ),+ >)? @@ -83,137 +269,273 @@ macro_rules! define_versioned { $(,)? // Optional trailing comma } ) => { - $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>] { - ( - $trait:ty, - $impl_block:tt - ) => { - #[allow(dead_code)] - impl - $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $trait - for $name $(< $( $lt ),+ >)? - $impl_block - }; - } + $crate::eager_replace! { + [!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; + use $crate::PermitSborAttributes as #PermitSborAttributesAlias; + + #[derive(#PermitSborAttributesAlias)] $(#[$attributes])* - // 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)? ),+ >)? + // Needs to go below $attributes so that a #[derive(Sbor)] in the attributes can see it. + #[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 + { + inner: Option<#VersionsType>, + } + + impl #ImplGenerics #VersionedType + { + pub fn new(inner: #VersionsType) -> Self { + Self { + inner: Some(inner), + } + } + } + + impl #ImplGenerics AsRef<#VersionsType> for #VersionedType + { + fn as_ref(&self) -> &#VersionsType { + self.inner.as_ref().unwrap() + } + } + + impl #ImplGenerics AsMut<#VersionsType> for #VersionedType + { + fn as_mut(&mut self) -> &mut #VersionsType { + self.inner.as_mut().unwrap() + } + } + + impl #ImplGenerics From<#VersionsType> for #VersionedType + { + fn from(value: #VersionsType) -> Self { + Self::new(value) + } + } + + impl #ImplGenerics From<#VersionedType> for #VersionsType { + fn from(value: #VersionedType) -> Self { + value.inner.unwrap() + } + } + + impl #ImplGenerics Versioned for #VersionedType + { + type Versions = #VersionsType; + type LatestVersion = $latest_version_type; + + fn is_fully_updated(&self) -> bool { + self.as_ref().is_fully_updated() + } + + 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_and_into_latest_version(self) -> Self::LatestVersion { + self.inner.unwrap().fully_update_and_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(&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(&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) + } + } + + [!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. $($( - []($version_type) = $version_num, + pub const [!ident! VERSION_ $version_num]: u8 = $version_num - 1; )*)? - []($latest_version_type) = $latest_version, + pub const LATEST_VERSION: u8 = $latest_version - 1; } - #[allow(dead_code)] - impl - $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? - $name - $(< $( $lt ),+ >)? + #[derive(#PermitSborAttributesAlias)] + $(#[$attributes])* + $vis enum $versions_name #FullGenerics { - pub fn new_latest(value: $latest_version_type) -> Self { - Self::[](value) - } + $($( + #[sbor(discriminator(#discriminators::[!ident! VERSION_ $version_num]))] + [!ident! V $version_num]($version_type), + )*)? + #[sbor(discriminator(#discriminators::LATEST_VERSION))] + [!ident! V $latest_version]($latest_version_type), + } - pub fn update_once(self) -> $crate::UpdateResult { + #[allow(dead_code)] + impl #ImplGenerics #VersionsType + { + /// 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::[!ident! V $version_num](value) => (true, Self::[!ident! V $update_to_version_num](value.into())), )*)? - Self::[](value) => $crate::UpdateResult::AtLatest(Self::[](value)), + this @ Self::[!ident! V $latest_version](_) => (false, this), } } - pub 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(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(unreachable_patterns)] + pub fn is_fully_updated(&self) -> bool { + match self { + Self::[!ident! V $latest_version](_) => true, + _ => false, } + } - #[allow(unreachable_patterns)] - fn as_latest_ref(&self) -> Option<&Self::Latest> { - match self { - Self::[](latest) => Some(latest), - _ => None, - } + #[allow(irrefutable_let_patterns)] + 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::[!ident! V $latest_version](latest) + } + + #[allow(unreachable_patterns)] + fn as_latest_version(&self) -> Option<&$latest_version_type> { + match self { + Self::[!ident! V $latest_version](latest) => Some(latest), + _ => None, } } - ); - $($([<$name _trait_impl>]!( - From<$version_type>, - { + #[allow(unreachable_patterns)] + fn as_latest_version_mut(&mut self) -> Option<&mut $latest_version_type> { + match self { + Self::[!ident! V $latest_version](latest) => Some(latest), + _ => None, + } + } + } + + $($( + #[allow(dead_code)] + impl #ImplGenerics From<$version_type> for #VersionsType { fn from(value: $version_type) -> Self { - Self::[](value) + Self::[!ident! V $version_num](value) } } - );)*)? - [<$name _trait_impl>]!( - From<$latest_version_type>, - { - fn from(value: $latest_version_type) -> Self { - Self::[](value) + #[allow(dead_code)] + impl #ImplGenerics From<$version_type> for #VersionedType { + fn from(value: $version_type) -> Self { + Self::new(#VersionsTypePath::[!ident! V $version_num](value)) } } - ); + )*)? + + #[allow(dead_code)] + impl #ImplGenerics From<$latest_version_type> for #VersionsType { + fn from(value: $latest_version_type) -> Self { + 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::[!ident! V $latest_version](value)) + } + } + [!SET:ident! #VersionTrait = $versioned_name Version] #[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 #VersionTrait { + // 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>] { - ($inner_type:ty) => { - impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? [<$name Version>] for $inner_type - { - type Versioned = $name $(< $( $lt ),+ >)?; + impl #ImplGenerics #VersionTrait for #VersionsType + { + type Versioned = #VersionedType; - fn into_versioned(self) -> Self::Versioned { - self.into() - } - } - }; + fn into_versioned(self) -> Self::Versioned { + #VersionedTypePath::new(self) + } } - $($([<$name _versionable_impl>]!($version_type);)*)? - [<$name _versionable_impl>]!($latest_version_type); + $($( + impl #ImplGenerics #VersionTrait for $version_type + { + type Versioned = #VersionedType; + + fn into_versioned(self) -> Self::Versioned { + #VersionedTypePath::new(self.into()) + } + } + )*)? + + impl #ImplGenerics #VersionTrait for $latest_version_type + { + type Versioned = $versioned_name #TypeGenerics; + + fn into_versioned(self) -> Self::Versioned { + #VersionedTypePath::new(self.into()) + } + } } }; } @@ -225,7 +547,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 }, @@ -284,26 +606,26 @@ mod tests { fn validate_latest( actual: impl Into, - expected: ::Latest, + expected: ::LatestVersion, ) { - let versioned_actual = actual.into(); + let versioned_actual = actual.into().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_and_into_latest_version (which returns an ExampleV4) assert_eq!( - versioned_actual.clone().update_to_latest(), - versioned_expected, + versioned_actual.fully_update_and_into_latest_version(), + expected, ); - // Check into_latest (which returns an ExampleV4) - assert_eq!(versioned_actual.into_latest(), expected,); } - #[derive(Debug, Clone, PartialEq, Eq)] + #[derive(Debug, Clone, PartialEq, Eq, Sbor)] struct GenericModelV1(T); define_single_versioned!( /// This is some rust doc as an example annotation - #[derive(Debug, Clone, PartialEq, Eq)] - enum VersionedGenericModel => GenericModel = GenericModelV1 + #[derive(Debug, Clone, PartialEq, Eq, Sbor)] + VersionedGenericModel(GenericModelVersions) => GenericModel = GenericModelV1 ); #[test] @@ -311,7 +633,67 @@ 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_and_into_latest_version(), + v1_model + ); assert_eq!(versioned, versioned_2); } + + #[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: 0, // V1 maps to 0 for legacy compatibility + fields: vec![ + // GenericModelV1 + Value::Tuple { + fields: vec![Value::U64 { value: 51 }], + }, + ], + }; + 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) + ); + } } diff --git a/scrypto-test/src/environment/env.rs b/scrypto-test/src/environment/env.rs index c03e6609f37..99f4472938a 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.as_unique_version_mut().epoch = epoch; + env.field_write_typed(manager_handle, &manager_substate)?; env.field_close(manager_handle)?; Ok(()) @@ -749,11 +746,9 @@ 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 + .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 000679730a1..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.into_latest()) + .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() - .into_latest(); + .fully_update_and_into_latest_version(); let balance = reader .read_typed_object_field::( @@ -567,7 +567,7 @@ impl LedgerSimulator { FungibleVaultField::Balance.field_index(), ) .unwrap() - .into_latest(); + .fully_update_and_into_latest_version(); balance.amount() } @@ -581,7 +581,7 @@ impl LedgerSimulator { PackageField::RoyaltyAccumulator.field_index(), ) .ok()? - .into_latest(); + .fully_update_and_into_latest_version(); let balance = reader .read_typed_object_field::( @@ -590,7 +590,7 @@ impl LedgerSimulator { FungibleVaultField::Balance.field_index(), ) .unwrap() - .into_latest(); + .fully_update_and_into_latest_version(); Some(balance.amount()) } @@ -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() } @@ -672,7 +672,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_and_into_latest_version()) }) .collect() } @@ -731,7 +731,7 @@ impl LedgerSimulator { ) .ok(); - vault.map(|v| v.into_latest().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.into_latest().amount; + let amount = vault_balance.fully_update_and_into_latest_version().amount; // TODO: Remove .collect() by using SystemDatabaseReader in ledger let iter: Vec = reader @@ -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( @@ -828,7 +828,7 @@ impl LedgerSimulator { ) .unwrap() .into_payload() - .into_latest(); + .fully_update_and_into_latest_version(); total_supply } @@ -900,7 +900,7 @@ impl LedgerSimulator { ValidatorField::State.field_index(), ) .unwrap() - .into_latest(); + .fully_update_and_into_latest_version(); substate } @@ -914,7 +914,7 @@ impl LedgerSimulator { ConsensusManagerField::CurrentValidatorSet.field_index(), ) .unwrap() - .into_latest(); + .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() - .into_latest(); + .fully_update_and_into_latest_version(); substate.epoch = epoch; @@ -2145,7 +2145,7 @@ impl LedgerSimulator { ConsensusManagerField::ProposerMilliTimestamp.field_index(), ) .unwrap() - .into_latest() + .fully_update_and_into_latest_version() .epoch_milli } @@ -2158,7 +2158,7 @@ impl LedgerSimulator { ConsensusManagerField::State.field_index(), ) .unwrap() - .into_latest() + .fully_update_and_into_latest_version() } pub fn get_current_time(&mut self, precision: TimePrecision) -> Instant {