diff --git a/crates/revm-context-interface/src/tron_ext.rs b/crates/revm-context-interface/src/tron_ext.rs index d965c6f..08d6512 100644 --- a/crates/revm-context-interface/src/tron_ext.rs +++ b/crates/revm-context-interface/src/tron_ext.rs @@ -74,6 +74,8 @@ pub struct TronStorageLoadReport { /// java returned without caching (`oldKeyTaken`); the journal must not /// keep the value either. pub uncached: bool, + /// Proposal 102 detected a claimed legacy key with an existing row. + pub out_of_time: bool, } /// TRON's address prefix byte (java `Constant.ADD_PRE_FIX_BYTE_MAINNET`). diff --git a/crates/revm-context/src/context.rs b/crates/revm-context/src/context.rs index 344bde0..c4a9aca 100644 --- a/crates/revm-context/src/context.rs +++ b/crates/revm-context/src/context.rs @@ -1082,6 +1082,10 @@ impl< }; if let Some(report) = self.journaled_state.db_mut().tron_storage_take_load_report() { if report.address == address && report.key == key { + if report.out_of_time { + self.journaled_state.tron_set_pending_out_of_time(); + return; + } if let Some(kind) = report.kind { self.journaled_state .tron_set_storage_read_kind(address, key, kind); diff --git a/crates/tron-actuator/src/proposal.rs b/crates/tron-actuator/src/proposal.rs index 70ab34e..38bb514 100644 --- a/crates/tron-actuator/src/proposal.rs +++ b/crates/tron-actuator/src/proposal.rs @@ -565,7 +565,19 @@ fn validate_proposal_value( return Err(bad()); } } - // java 4.8.2.3: proposable only once `forkController.pass(VERSION_4_8_2_3)`. + // java 4.8.2.4: storage-key protection is independently proposable. + AllowOptimizeTvmStorageKey => { + if !dyn_props.fork_passed(tron_chainbase::fork::VERSION_4_8_2_4) { + return Err(bad()); + } + if flag(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY") == 1 { + return Err(bad()); + } + if value != 1 { + return Err(only_one("ALLOW_OPTIMIZE_TVM_STORAGE_KEY")); + } + } + // java 4.8.2.3: storage optimization without legacy-read protection. AllowOptimizeTvmStorage => { if !dyn_props.fork_passed(tron_chainbase::fork::VERSION_4_8_2_3) { return Err(bad()); @@ -666,6 +678,7 @@ enum ProposalType { AllowHardenResourceCalculation, AllowHardenExchangeCalculation, AllowOptimizeTvmStorage, + AllowOptimizeTvmStorageKey, } impl ProposalType { @@ -755,6 +768,7 @@ impl ProposalType { 97 => AllowHardenResourceCalculation, 98 => AllowHardenExchangeCalculation, 99 => AllowOptimizeTvmStorage, + 102 => AllowOptimizeTvmStorageKey, _ => return None, }) } diff --git a/crates/tron-actuator/tests/proposal.rs b/crates/tron-actuator/tests/proposal.rs index 1c01268..0df400d 100644 --- a/crates/tron-actuator/tests/proposal.rs +++ b/crates/tron-actuator/tests/proposal.rs @@ -709,3 +709,54 @@ fn create_rejects_optimize_tvm_storage_when_already_active() { .unwrap_err(); assert!(matches!(err, ActuatorError::ProposalParameterOutOfRange), "got: {err:?}"); } + +// Parameter 102 must be understood at proposal creation, before activation. +fn storage_key_param(value: i64) -> BTreeMap { + BTreeMap::from([(102, value)]) +} + +fn pass_storage_key_fork(ctx: &Ctx) { + ctx.dp.save_latest_block_header_timestamp(1_790_000_000_000); + ctx.dp.save_fork_stats(tron_chainbase::fork::VERSION_4_8_2_4, &[1u8; 27]); +} + +#[test] +fn storage_key_proposal_requires_4_8_2_4_not_only_4_8_2_3() { + let ctx = ctx(); + put_witness(&ctx, ALICE); + pass_version_4_8_2_3(&ctx); + let c = ProposalCreateContract { + owner_address: ALICE.to_vec(), + parameters: storage_key_param(1), + }; + assert!(proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).is_err()); + pass_storage_key_fork(&ctx); + proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).unwrap(); +} + +#[test] +fn storage_key_proposal_only_accepts_one_and_cannot_be_reenabled() { + let ctx = ctx(); + put_witness(&ctx, ALICE); + pass_storage_key_fork(&ctx); + for value in [-1, 0, 2] { + let c = ProposalCreateContract { + owner_address: ALICE.to_vec(), + parameters: storage_key_param(value), + }; + assert!(proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).is_err()); + } + // Proposal 99 being active does not prevent proposing 102. + ctx.dp.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE", 1); + let c = ProposalCreateContract { + owner_address: ALICE.to_vec(), + parameters: storage_key_param(1), + }; + proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).unwrap(); + proposal::execute_proposal_create(&ctx.proposals, &ctx.dp, &c).unwrap(); + assert!(!ctx.dp.allow_optimize_tvm_storage_key()); + let created = ctx.proposals.get(1).unwrap().unwrap(); + assert_eq!(created.parameters.get(&102), Some(&1)); + ctx.dp.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 1); + assert!(proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).is_err()); +} diff --git a/crates/tron-chainbase/src/fork.rs b/crates/tron-chainbase/src/fork.rs index 4ef986b..7a7afa8 100644 --- a/crates/tron-chainbase/src/fork.rs +++ b/crates/tron-chainbase/src/fork.rs @@ -62,13 +62,14 @@ pub const VERSION_4_8_1_1: i32 = 35; pub const VERSION_4_8_2: i32 = 36; pub const VERSION_4_8_2_2: i32 = 37; pub const VERSION_4_8_2_3: i32 = 38; +pub const VERSION_4_8_2_4: i32 = 39; /// `Parameter.ChainConstant.BLOCK_VERSION` — the version a producing node /// stamps on its blocks. -pub const BLOCK_VERSION: i32 = 38; +pub const BLOCK_VERSION: i32 = 39; /// `ForkBlockVersionEnum.values()`, in declaration order. -pub const FORK_VERSIONS: [ForkVersion; 28] = [ +pub const FORK_VERSIONS: [ForkVersion; 29] = [ ForkVersion { version: ENERGY_LIMIT, hard_fork_time_ms: 0, rate: 0 }, ForkVersion { version: VERSION_3_2_2, hard_fork_time_ms: 0, rate: 0 }, ForkVersion { version: VERSION_3_5, hard_fork_time_ms: 0, rate: 0 }, @@ -97,6 +98,7 @@ pub const FORK_VERSIONS: [ForkVersion; 28] = [ ForkVersion { version: VERSION_4_8_2, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 80 }, ForkVersion { version: VERSION_4_8_2_2, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 70 }, ForkVersion { version: VERSION_4_8_2_3, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 70 }, + ForkVersion { version: VERSION_4_8_2_4, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 70 }, ]; const STATS_KEY_PREFIX: &[u8] = b"FORK_VERSION_"; diff --git a/crates/tron-chainbase/src/stores/dynamic_properties_store.rs b/crates/tron-chainbase/src/stores/dynamic_properties_store.rs index 27d88e9..bf7dd3c 100644 --- a/crates/tron-chainbase/src/stores/dynamic_properties_store.rs +++ b/crates/tron-chainbase/src/stores/dynamic_properties_store.rs @@ -96,6 +96,8 @@ pub mod keys { pub const ALLOW_TVM_OSAKA: &[u8] = b"ALLOW_TVM_OSAKA"; /// Proposal 99: the 48-byte storage-row key scheme (`Storage.getNewRowKey`). pub const ALLOW_OPTIMIZE_TVM_STORAGE: &[u8] = b"ALLOW_OPTIMIZE_TVM_STORAGE"; + /// Proposal 102: optimized keys with protected legacy reads. + pub const ALLOW_OPTIMIZE_TVM_STORAGE_KEY: &[u8] = b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY"; /// TIP-2935 install marker, set once `BlockHashHistory` is deployed. pub const BLOCK_HASH_HISTORY_INSTALLED: &[u8] = b"BLOCK_HASH_HISTORY_INSTALLED"; pub const ALLOW_TVM_FREEZE: &[u8] = b"ALLOW_TVM_FREEZE"; @@ -771,6 +773,15 @@ impl DynamicPropertiesStore { self.get_long(keys::ALLOW_OPTIMIZE_TVM_STORAGE).unwrap_or(0) == 1 } + pub fn allow_optimize_tvm_storage_key(&self) -> bool { + self.get_long(keys::ALLOW_OPTIMIZE_TVM_STORAGE_KEY).unwrap_or(0) == 1 + } + + /// java DynamicPropertiesStore.allowTvmStorageOptimization(). + pub fn allow_tvm_storage_optimization(&self) -> bool { + self.allow_optimize_tvm_storage() || self.allow_optimize_tvm_storage_key() + } + pub fn is_block_hash_history_installed(&self) -> bool { self.get_long(keys::BLOCK_HASH_HISTORY_INSTALLED).unwrap_or(0) == 1 } diff --git a/crates/tron-consensus/src/proposals.rs b/crates/tron-consensus/src/proposals.rs index 9e14a76..9a25a9f 100644 --- a/crates/tron-consensus/src/proposals.rs +++ b/crates/tron-consensus/src/proposals.rs @@ -450,6 +450,7 @@ pub fn parameter_id_to_key(id: i64) -> Option<&'static [u8]> { 97 => b"ALLOW_HARDEN_RESOURCE_CALCULATION", 98 => b"ALLOW_HARDEN_EXCHANGE_CALCULATION", 99 => b"ALLOW_OPTIMIZE_TVM_STORAGE", + 102 => b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", _ => return None, }) } @@ -525,3 +526,38 @@ mod bitmap_evolution_tests { assert!(bit_set(&act, 48) && bit_set(&act, 49)); } } + +#[cfg(test)] +mod storage_key_activation_tests { + use super::*; + use std::{collections::BTreeMap, sync::Arc}; + use tron_chainbase::MemBackend; + + #[test] + fn parameter_102_activates_only_when_an_approved_proposal_expires() { + let dp = DynamicPropertiesStore::new(Arc::new(MemBackend::new())); + let proposals = ProposalStore::new(Arc::new(MemBackend::new())); + let active: Vec
= (0..3).map(|n| { + let mut raw = [0u8; 21]; + raw[0] = 0x41; + raw[20] = n; + Address::from_raw(raw) + }).collect(); + proposals.put(108, &tron_proto::Proposal { + proposal_id: 108, + expiration_time: 20_000, + parameters: BTreeMap::from([(102, 1)]), + approvals: active[..2].iter().map(|a| a.as_bytes().to_vec()).collect(), + ..Default::default() + }).unwrap(); + activate_expired_proposals(&proposals, &dp, 19_999, &active).unwrap(); + assert!(!dp.allow_tvm_storage_optimization()); + let report = activate_expired_proposals(&proposals, &dp, 20_001, &active).unwrap(); + assert_eq!(report.approved, vec![108]); + assert!(dp.allow_optimize_tvm_storage_key()); + assert!(dp.allow_tvm_storage_optimization()); + assert!(!dp.allow_optimize_tvm_storage()); + assert_eq!(parameter_id_to_key(102), Some(&b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY"[..])); + assert_eq!(proposals.get(108).unwrap().unwrap().state, ProposalState::Approved as i32); + } +} diff --git a/crates/tron-grpc/src/service.rs b/crates/tron-grpc/src/service.rs index abe715c..90984ec 100644 --- a/crates/tron-grpc/src/service.rs +++ b/crates/tron-grpc/src/service.rs @@ -1120,6 +1120,7 @@ impl Wallet for WalletService { "ALLOW_HARDEN_RESOURCE_CALCULATION", "ALLOW_HARDEN_EXCHANGE_CALCULATION", "ALLOW_OPTIMIZE_TVM_STORAGE", + "ALLOW_OPTIMIZE_TVM_STORAGE_KEY", ]; let mut chain_parameter = Vec::with_capacity(KEYS.len()); for k in KEYS { diff --git a/crates/tron-rpc/src/methods.rs b/crates/tron-rpc/src/methods.rs index 7bd4c3d..632572c 100644 --- a/crates/tron-rpc/src/methods.rs +++ b/crates/tron-rpc/src/methods.rs @@ -441,7 +441,7 @@ pub fn eth_get_storage_at(p: &Value, s: &RpcState) -> Result { // a read-only RPC the v2 default matches what new contracts emit. let addr_hash = tron_chainbase::StorageRowStore::addr_hash(&addr, &[]); let value = storage - .read_slot(&addr_hash, &slot, false, s.dyn_props.allow_optimize_tvm_storage())? + .read_slot(&addr_hash, &slot, false, s.dyn_props.allow_tvm_storage_optimization())? .unwrap_or_else(|| vec![0u8; 32]); let mut padded = [0u8; 32]; let n = value.len().min(32); @@ -1290,6 +1290,7 @@ pub fn get_chain_parameters(_p: &Value, s: &RpcState) -> Result ("getAllowHardenResourceCalculation", b"ALLOW_HARDEN_RESOURCE_CALCULATION", 0), ("getAllowHardenExchangeCalculation", b"ALLOW_HARDEN_EXCHANGE_CALCULATION", 0), ("getAllowOptimizeTvmStorage", b"ALLOW_OPTIMIZE_TVM_STORAGE", 0), + ("getAllowOptimizeTvmStorageKey", b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 0), ]; let entries: Vec = PARAMS .iter() diff --git a/crates/tron-rpc/tests/rpc.rs b/crates/tron-rpc/tests/rpc.rs index 95bfe30..2dae7d6 100644 --- a/crates/tron-rpc/tests/rpc.rs +++ b/crates/tron-rpc/tests/rpc.rs @@ -459,6 +459,7 @@ async fn get_chain_parameters_returns_seeded_entries() { let (addr, _accts, _blocks, _idx, _txs, dp) = spawn_server().await; dp.put_long(b"TRANSACTION_FEE", 1234); dp.put_long(b"ENERGY_FEE", 5678); + dp.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 1); let resp = call( addr, json!({"jsonrpc":"2.0","method":"getChainParameters","id":1}), @@ -477,10 +478,11 @@ async fn get_chain_parameters_returns_seeded_entries() { assert_eq!(by_key.get("getTransactionFee"), Some(&1234)); assert_eq!(by_key.get("getEnergyFee"), Some(&5678)); // Every java entry is present even when unset (value omitted → 0). - assert_eq!(params.len(), 80); + assert_eq!(params.len(), 81); + assert_eq!(by_key.get("getAllowOptimizeTvmStorageKey"), Some(&1)); assert_eq!(by_key.get("getAllowNewResourceModel"), Some(&0)); assert_eq!(by_key.get("getAllowOptimizeTvmStorage"), Some(&0)); - // java 4.8.2.3 order: the five newest keys follow getProposalExpireTime. + // java 4.8.2.4 order: the six newest keys follow getProposalExpireTime. let tail: Vec<&str> = params[75..] .iter() .map(|p| p["key"].as_str().unwrap()) @@ -493,6 +495,7 @@ async fn get_chain_parameters_returns_seeded_entries() { "getAllowHardenResourceCalculation", "getAllowHardenExchangeCalculation", "getAllowOptimizeTvmStorage", + "getAllowOptimizeTvmStorageKey", ] ); } diff --git a/crates/tron-sim/src/override_set.rs b/crates/tron-sim/src/override_set.rs index 177d597..553767e 100644 --- a/crates/tron-sim/src/override_set.rs +++ b/crates/tron-sim/src/override_set.rs @@ -268,7 +268,7 @@ fn write_slot( is_v1: bool, ) -> Result<(), tron_chainbase::StoreError> { let legacy = StorageRowStore::compose_key_with_addr_hash(addr_hash, slot, is_v1); - if vm.dynamic_properties.allow_optimize_tvm_storage() { + if vm.dynamic_properties.allow_tvm_storage_optimization() { vm.storage .put_raw(&StorageRowStore::new_row_key(addr_hash, slot), value)?; vm.storage.delete(&legacy) diff --git a/crates/tron-tvm/src/database.rs b/crates/tron-tvm/src/database.rs index 37ba929..cb4ad06 100644 --- a/crates/tron-tvm/src/database.rs +++ b/crates/tron-tvm/src/database.rs @@ -170,6 +170,8 @@ pub struct TronDatabase { /// `ALLOW_OPTIMIZE_TVM_STORAGE`: storage rows live under 48-byte keys, /// loads consult the new key first, commits migrate legacy rows. pub(crate) optimize_tvm_storage: bool, + /// Proposal 102: reject aliased legacy reads and leave read-only rows unmigrated. + pub(crate) legacy_read_protection: bool, /// `(address, slot, old_key_taken)` the journal set before a cache-miss /// load under the optimized scheme (java `Storage.oldKeyTaken`). pub(crate) storage_load_hint: RefCell>, @@ -212,6 +214,7 @@ impl TronDatabase { top_level_deploy_version: None, fork_4_8_2_2: false, optimize_tvm_storage: false, + legacy_read_protection: false, storage_load_hint: RefCell::new(None), storage_load_report: RefCell::new(None), commit_read_kinds: HashMap::new(), @@ -227,6 +230,12 @@ impl TronDatabase { self } + /// java Storage.legacyReadProtection (proposal 102), separate from proposal 99. + pub fn with_legacy_read_protection(mut self, enabled: bool) -> Self { + self.legacy_read_protection = enabled; + self + } + /// Mark the top-level `CreateSmartContract` deploy address so its init /// frame executes as `version` for the EIP-150 1/64 / GASPRICE gates /// (java forces a new CREATE to version 1 under `ALLOW_TVM_COMPATIBLE_EVM`). @@ -409,6 +418,7 @@ impl TronDatabase { kind: None, own_old_key: false, uncached: false, + out_of_time: false, }; let new_key = StorageRowStore::new_row_key(&ah, &slot); let value = if let Some(raw) = self.storage.get_raw(&new_key)? { @@ -420,6 +430,10 @@ impl TronDatabase { storage_word(&raw) } } else if old_key_taken { + if self.legacy_read_protection { + let old = StorageRowStore::compose_key_with_addr_hash(&ah, &slot, is_v1); + report.out_of_time = self.storage.get(&old)?.is_some(); + } report.uncached = true; StorageValue::ZERO } else if let Some(raw) = self @@ -940,7 +954,9 @@ impl TronDatabase { let dirty = slot.present_value != slot.original_value || self.commit_written.contains(&(*address, *slot_key)); let kind = self.commit_read_kinds.get(&(*address, *slot_key)).copied(); - let migrate = !dirty && kind == Some(TronStorageReadKind::Old); + let migrate = !self.legacy_read_protection + && !dirty + && kind == Some(TronStorageReadKind::Old); if !dirty && !migrate { continue; } diff --git a/crates/tron-tvm/src/execute.rs b/crates/tron-tvm/src/execute.rs index f3ebcfa..37a1daa 100644 --- a/crates/tron-tvm/src/execute.rs +++ b/crates/tron-tvm/src/execute.rs @@ -596,8 +596,9 @@ fn execute_trigger_inner( ) .with_fork_flags( stores.dynamic_properties.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2), - stores.dynamic_properties.allow_optimize_tvm_storage(), - ); + stores.dynamic_properties.allow_tvm_storage_optimization(), + ) + .with_legacy_read_protection(stores.dynamic_properties.allow_optimize_tvm_storage_key()); if let Some(tx_id) = root_tx_id { tron_db = tron_db.with_root_tx_id(tx_id); } @@ -999,8 +1000,9 @@ fn execute_trigger_inner_with_tracer( ) .with_fork_flags( stores.dynamic_properties.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2), - stores.dynamic_properties.allow_optimize_tvm_storage(), - ); + stores.dynamic_properties.allow_tvm_storage_optimization(), + ) + .with_legacy_read_protection(stores.dynamic_properties.allow_optimize_tvm_storage_key()); if let Some(tx_id) = root_tx_id { tron_db = tron_db.with_root_tx_id(tx_id); } @@ -1615,8 +1617,9 @@ fn execute_create_inner( ) .with_fork_flags( stores.dynamic_properties.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2), - stores.dynamic_properties.allow_optimize_tvm_storage(), + stores.dynamic_properties.allow_tvm_storage_optimization(), ) + .with_legacy_read_protection(stores.dynamic_properties.allow_optimize_tvm_storage_key()) .with_root_tx_id(*tx_id); if let Some(idx) = &stores.block_index { tron_db = tron_db.with_block_index(Arc::clone(idx)); diff --git a/crates/tron-tvm/src/proposals.rs b/crates/tron-tvm/src/proposals.rs index 8c0af65..8cec7eb 100644 --- a/crates/tron-tvm/src/proposals.rs +++ b/crates/tron-tvm/src/proposals.rs @@ -118,7 +118,7 @@ pub struct ProposalSet { /// `ForkController.pass(VERSION_4_8_2_3)` — block version 38 on 70% of /// SRs: java `Storage.checkAlias`. pub fork_4_8_2_3: bool, - /// `ALLOW_OPTIMIZE_TVM_STORAGE` (proposal 99): 48-byte storage-row keys. + /// Effective storage optimization (proposal 99 or 102): 48-byte row keys. pub allow_optimize_tvm_storage: bool, } @@ -161,7 +161,7 @@ impl ProposalSet { allow_energy_adjustment: flag(b"ALLOW_ENERGY_ADJUSTMENT"), fork_4_8_2_2: dps.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2), fork_4_8_2_3: dps.fork_passed(tron_chainbase::fork::VERSION_4_8_2_3), - allow_optimize_tvm_storage: flag(b"ALLOW_OPTIMIZE_TVM_STORAGE"), + allow_optimize_tvm_storage: dps.allow_tvm_storage_optimization(), } } diff --git a/crates/tron-tvm/tests/optimized_tvm_storage.rs b/crates/tron-tvm/tests/optimized_tvm_storage.rs index 4abf790..8ba3f48 100644 --- a/crates/tron-tvm/tests/optimized_tvm_storage.rs +++ b/crates/tron-tvm/tests/optimized_tvm_storage.rs @@ -360,3 +360,93 @@ fn legacy_scheme_without_the_fork_lets_aliasing_slots_share_a_row() { assert_eq!(legacy_row(&stores, &slot(6)), Some(word(7).to_vec())); assert_eq!(legacy_row(&stores, &slot(7)), Some(word(7).to_vec())); } + +fn protected_stores() -> VmStores { + let stores = stores(); + stores.dynamic_properties.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 1); + stores +} + +#[test] +fn parameter_102_does_not_migrate_read_only_legacy_rows_even_if_99_is_set() { + for old_flag in [0, 1] { + let stores = protected_stores(); + stores.dynamic_properties.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE", old_flag); + stores.storage.put(&legacy_key(&slot(5)), &word(7)).unwrap(); + let mut code = read_slot(slot(5)); + code.push(0x00); + install(&stores, code); + run_ok(&stores); + assert_eq!(legacy_row(&stores, &slot(5)), Some(word(7).to_vec())); + assert_eq!(new_row(&stores, &slot(5)), None); + } +} + +#[test] +fn parameter_102_migrates_an_unchanged_write_and_stores_zero_explicitly() { + for value in [0, 7] { + let stores = protected_stores(); + stores.storage.put(&legacy_key(&slot(5)), &word(7)).unwrap(); + let mut code = write_slot(slot(5), word(value)); + code.push(0x00); + install(&stores, code); + run_ok(&stores); + assert_eq!(legacy_row(&stores, &slot(5)), None); + assert_eq!(new_row(&stores, &slot(5)), Some(word(value).to_vec())); + } +} + +#[test] +fn parameter_102_legacy_read_conflict_halts_and_reverts_prior_writes() { + let stores = protected_stores(); + let a = slot(5); + let b = aliasing_slot(5); + stores.storage.put(&legacy_key(&a), &word(7)).unwrap(); + let mut code = copy_slot(a, slot(6)); + code.extend(read_slot(b)); + code.push(0x00); + install(&stores, code); + match run(&stores) { + VmOutcome::Halt { result, energy_used, .. } => { + assert_eq!(result, ContractResult::OutOfTime); + assert_eq!(energy_used, LIMIT); + } + other => panic!("expected OUT_OF_TIME, got {other:?}"), + } + assert_eq!(new_row(&stores, &slot(6)), None); + assert_eq!(legacy_row(&stores, &a), Some(word(7).to_vec())); +} + +#[test] +fn parameter_102_claimed_legacy_key_without_a_row_remains_an_empty_read() { + let stores = protected_stores(); + let a = slot(5); + let b = aliasing_slot(5); + stores.storage.put_raw(&new_key(&a), &word(9)).unwrap(); + let mut code = copy_slot(a, slot(6)); + code.extend(copy_slot(b, slot(7))); + code.push(0x00); + install(&stores, code); + run_ok(&stores); + assert_eq!(new_row(&stores, &slot(6)), Some(word(9).to_vec())); + assert_eq!(new_row(&stores, &slot(7)), Some(word(0).to_vec())); + assert_eq!(new_row(&stores, &b), None); +} + +#[test] +fn parameter_102_new_rows_win_before_legacy_alias_protection() { + let stores = protected_stores(); + let a = slot(5); + let b = aliasing_slot(5); + stores.storage.put(&legacy_key(&a), &word(7)).unwrap(); + stores.storage.put_raw(&new_key(&a), &word(9)).unwrap(); + stores.storage.put_raw(&new_key(&b), &word(11)).unwrap(); + let mut code = copy_slot(a, slot(6)); + code.extend(copy_slot(b, slot(7))); + code.push(0x00); + install(&stores, code); + run_ok(&stores); + assert_eq!(new_row(&stores, &slot(6)), Some(word(9).to_vec())); + assert_eq!(new_row(&stores, &slot(7)), Some(word(11).to_vec())); + assert_eq!(legacy_row(&stores, &a), Some(word(7).to_vec())); +}