From 4b786a105e3cc59896c258a98ca2399c2e11b29f Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 22 May 2025 15:58:45 +0800 Subject: [PATCH 01/21] tests: add basic test helper functions --- src/vm/mod.rs | 1 - src/vm/{op_contract.rs => op_contract/mod.rs} | 3 + src/vm/op_contract/tests.rs | 221 ++++++++++++++++++ 3 files changed, 224 insertions(+), 1 deletion(-) rename src/vm/{op_contract.rs => op_contract/mod.rs} (99%) create mode 100644 src/vm/op_contract/tests.rs diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 5a3b4bed..1d1357b1 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -30,7 +30,6 @@ mod op_contract; #[macro_use] mod macroasm; mod contract; - pub use aluvm::aluasm_isa; pub use contract::{ ContractStateAccess, ContractStateEvolve, GlobalContractState, GlobalOrd, GlobalStateIter, diff --git a/src/vm/op_contract.rs b/src/vm/op_contract/mod.rs similarity index 99% rename from src/vm/op_contract.rs rename to src/vm/op_contract/mod.rs index a1c58b0d..8409af21 100644 --- a/src/vm/op_contract.rs +++ b/src/vm/op_contract/mod.rs @@ -672,3 +672,6 @@ impl Bytecode for ContractOp { }) } } + +#[cfg(test)] +mod tests; diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs new file mode 100644 index 00000000..5b1f030e --- /dev/null +++ b/src/vm/op_contract/tests.rs @@ -0,0 +1,221 @@ +#[cfg(test)] +mod tests { + use std::borrow::Borrow; + use std::cell::RefCell; + use std::collections::{BTreeMap, BTreeSet}; + use std::iter; + use std::num::NonZeroU32; + use std::rc::Rc; + + use aluvm::data::Number; + use aluvm::isa::{ExecStep, InstructionSet}; + use aluvm::library::LibSite; + use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegS}; + use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob}; + use amplify::num::u24; + use bp::{Outpoint, Txid}; + use commit_verify::StrictHash; + use strict_encoding::StrictDumb; + + use super::*; + use crate::operation::assignments::AssignVec; + use crate::operation::Operation; + use crate::vm::{ + ContractOp, ContractStateAccess, GlobalContractState, GlobalOrd, GlobalStateIter, OpInfo, + OrdOpRef, UnknownGlobalStateType, VmContext, WitnessOrd, WitnessPos, + }; + use crate::{ + schema, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, + FungibleState, Genesis, GenesisSeal, GlobalState, GlobalStateType, GraphSeal, Identity, + Inputs, MetaType, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, + SealClosingStrategy, Signature, Transition, TypedAssigns, + }; + + const DUMMY_ASSIGN_TYPE_FUNGIBLE: AssignmentType = AssignmentType::with(1000); + const DUMMY_ASSIGN_TYPE_DATA: AssignmentType = AssignmentType::with(1001); + const DUMMY_ASSIGN_TYPE_RIGHTS: AssignmentType = AssignmentType::with(1002); + + const DUMMY_GLOBAL_TYPE_A: GlobalStateType = GlobalStateType::with(2000); + const DUMMY_GLOBAL_TYPE_B: GlobalStateType = GlobalStateType::with(2001); + + const DUMMY_META_TYPE_A: MetaType = MetaType::with(3000); + + #[derive(Debug, Default, Clone)] + struct MockContractState { + global_data: BTreeMap>, + fungible_data: BTreeMap<(Outpoint, AssignmentType), Vec>, + structured_data: BTreeMap<(Outpoint, AssignmentType), Vec>, + rights_data: BTreeMap<(Outpoint, AssignmentType), u32>, + fail_global_access: bool, + } + + #[derive(Debug)] + struct MockGlobalStateIter { + data: Vec<(GlobalOrd, RevealedData)>, + current_idx_for_prev: usize, /* For prev(): index of the *next* element to be returned + * by prev() */ + current_idx_for_last: usize, /* For last() after reset: index of the *exact* element to + * be returned */ + original_size: u24, + initial_depth_reset: bool, + } + + impl GlobalStateIter for MockGlobalStateIter { + type Data = RevealedData; + + fn size(&mut self) -> u24 { self.original_size } + + fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { + if self.initial_depth_reset { + // If reset was called, prev() should ideally pick up from where last() would have. + // Or, more simply, disallow prev() after reset if nth() is the only user of + // reset+last. For now, let's make prev() restart if reset was + // called for last(). + self.current_idx_for_prev = self.data.len(); + self.initial_depth_reset = false; + } + if self.current_idx_for_prev > 0 { + self.current_idx_for_prev -= 1; + Some(self.data[self.current_idx_for_prev].clone()) + } else { + None + } + } + + fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { + if self.initial_depth_reset { + if self.current_idx_for_last < self.data.len() { + Some(self.data[self.current_idx_for_last].clone()) + } else { + None + } + } else if !self.data.is_empty() { + Some(self.data[self.data.len() - 1].clone()) + } else { + None + } + } + + fn reset(&mut self, depth: u24) { + self.initial_depth_reset = true; + let depth_u32 = depth.to_u32(); + if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { + self.current_idx_for_last = self.data.len(); // invalid index + } else { + self.current_idx_for_last = self.data.len() - 1 - (depth_u32 as usize); + } + } + } + + impl ContractStateAccess for MockContractState { + fn global( + &self, + ty: GlobalStateType, + ) -> Result, UnknownGlobalStateType> { + if self.fail_global_access { + return Err(UnknownGlobalStateType(ty)); + } + let data_for_type = self.global_data.get(&ty).cloned().unwrap_or_default(); + let size = u24::try_from(data_for_type.len() as u32).unwrap_or(u24::MAX); + let iter = MockGlobalStateIter { + data: data_for_type, + current_idx_for_prev: size.to_usize(), + current_idx_for_last: 0, // Placeholder, reset will set it + original_size: size, + initial_depth_reset: false, + }; + Ok(GlobalContractState::new(iter)) + } + + fn rights(&self, outpoint: Outpoint, ty: AssignmentType) -> u32 { + self.rights_data.get(&(outpoint, ty)).cloned().unwrap_or(0) + } + + fn fungible( + &self, + outpoint: Outpoint, + ty: AssignmentType, + ) -> impl DoubleEndedIterator { + self.fungible_data + .get(&(outpoint, ty)) + .cloned() + .unwrap_or_default() + .into_iter() + } + + fn data( + &self, + outpoint: Outpoint, + ty: AssignmentType, + ) -> impl DoubleEndedIterator> { + self.structured_data + .get(&(outpoint, ty)) + .cloned() + .unwrap_or_default() + .into_iter() + } + } + + fn create_dummy_genesis() -> Genesis { + Genesis { + ffv: Ffv::default(), + schema_id: SchemaId::strict_dumb(), + timestamp: 0, + issuer: Identity::strict_dumb(), + chain_net: ChainNet::BitcoinRegtest, + seal_closing_strategy: SealClosingStrategy::default(), + metadata: Metadata::default(), + globals: GlobalState::default(), + assignments: Assignments::::default(), + } + } + + fn dummy_witness_pos() -> WitnessPos { + WitnessPos::bitcoin(NonZeroU32::new(1).unwrap(), 1231006505).unwrap() + } + + fn dummy_witness_ord_mined() -> WitnessOrd { WitnessOrd::Mined(dummy_witness_pos()) } + + fn create_dummy_transition( + contract_id: ContractId, + signature: Option, + ) -> Transition { + let dummy_opout = Opout::strict_dumb(); + let mut opout_set = BTreeSet::new(); + opout_set.insert(dummy_opout); + let nonempty_opout_set = NonEmptyOrdSet::try_from(opout_set).expect("Should not be empty"); + let inputs = Inputs::from(nonempty_opout_set); + + Transition { + ffv: Ffv::default(), + contract_id, + nonce: 0, + transition_type: schema::TransitionType::strict_dumb(), + metadata: Metadata::default(), + globals: GlobalState::default(), + inputs, + assignments: Assignments::::default(), + signature, + } + } + + fn exec_op_and_assert_st0( + op: ContractOp, + regs: &mut CoreRegs, + context: &VmContext, + expected_st0_ok: bool, + ) { + let step = op.exec(regs, LibSite::default(), context); + assert_eq!( + regs.status(), // Corrected to use regs.status() + expected_st0_ok, + "ST0 flag (is_ok) mismatch for op {:?}", + op + ); + if !expected_st0_ok { + assert_eq!(step, ExecStep::Stop, "ExecStep should be Stop on failure for op {:?}", op); + } else { + assert_eq!(step, ExecStep::Next, "ExecStep should be Next on success for op {:?}", op); + } + } +} From 7e8b576b049caba79a04b9b309f18918de60c18b Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 22 May 2025 16:11:08 +0800 Subject: [PATCH 02/21] refactor(tests): reorganize test module structure --- src/vm/op_contract/tests.rs | 382 ++++++++++++++++++------------------ 1 file changed, 188 insertions(+), 194 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 5b1f030e..0348f242 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -1,221 +1,215 @@ -#[cfg(test)] -mod tests { - use std::borrow::Borrow; - use std::cell::RefCell; - use std::collections::{BTreeMap, BTreeSet}; - use std::iter; - use std::num::NonZeroU32; - use std::rc::Rc; - - use aluvm::data::Number; - use aluvm::isa::{ExecStep, InstructionSet}; - use aluvm::library::LibSite; - use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegS}; - use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob}; - use amplify::num::u24; - use bp::{Outpoint, Txid}; - use commit_verify::StrictHash; - use strict_encoding::StrictDumb; - - use super::*; - use crate::operation::assignments::AssignVec; - use crate::operation::Operation; - use crate::vm::{ - ContractOp, ContractStateAccess, GlobalContractState, GlobalOrd, GlobalStateIter, OpInfo, - OrdOpRef, UnknownGlobalStateType, VmContext, WitnessOrd, WitnessPos, - }; - use crate::{ - schema, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, - FungibleState, Genesis, GenesisSeal, GlobalState, GlobalStateType, GraphSeal, Identity, - Inputs, MetaType, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, - SealClosingStrategy, Signature, Transition, TypedAssigns, - }; - - const DUMMY_ASSIGN_TYPE_FUNGIBLE: AssignmentType = AssignmentType::with(1000); - const DUMMY_ASSIGN_TYPE_DATA: AssignmentType = AssignmentType::with(1001); - const DUMMY_ASSIGN_TYPE_RIGHTS: AssignmentType = AssignmentType::with(1002); - - const DUMMY_GLOBAL_TYPE_A: GlobalStateType = GlobalStateType::with(2000); - const DUMMY_GLOBAL_TYPE_B: GlobalStateType = GlobalStateType::with(2001); - - const DUMMY_META_TYPE_A: MetaType = MetaType::with(3000); - - #[derive(Debug, Default, Clone)] - struct MockContractState { - global_data: BTreeMap>, - fungible_data: BTreeMap<(Outpoint, AssignmentType), Vec>, - structured_data: BTreeMap<(Outpoint, AssignmentType), Vec>, - rights_data: BTreeMap<(Outpoint, AssignmentType), u32>, - fail_global_access: bool, - } +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeMap, BTreeSet}; +use std::iter; +use std::num::NonZeroU32; +use std::rc::Rc; + +use aluvm::data::Number; +use aluvm::isa::{ExecStep, InstructionSet}; +use aluvm::library::LibSite; +use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegS}; +use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob}; +use amplify::num::u24; +use bp::{Outpoint, Txid}; +use commit_verify::StrictHash; +use strict_encoding::StrictDumb; + +use super::*; +use crate::operation::assignments::AssignVec; +use crate::operation::Operation; +use crate::vm::{ + ContractOp, ContractStateAccess, GlobalContractState, GlobalOrd, GlobalStateIter, OpInfo, + OrdOpRef, UnknownGlobalStateType, VmContext, WitnessOrd, WitnessPos, +}; +use crate::{ + schema, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, + FungibleState, Genesis, GenesisSeal, GlobalState, GlobalStateType, GraphSeal, Identity, Inputs, + MetaType, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, SealClosingStrategy, + Signature, Transition, TypedAssigns, +}; + +const DUMMY_ASSIGN_TYPE_FUNGIBLE: AssignmentType = AssignmentType::with(1000); +const DUMMY_ASSIGN_TYPE_DATA: AssignmentType = AssignmentType::with(1001); +const DUMMY_ASSIGN_TYPE_RIGHTS: AssignmentType = AssignmentType::with(1002); + +const DUMMY_GLOBAL_TYPE_A: GlobalStateType = GlobalStateType::with(2000); +const DUMMY_GLOBAL_TYPE_B: GlobalStateType = GlobalStateType::with(2001); + +const DUMMY_META_TYPE_A: MetaType = MetaType::with(3000); + +#[derive(Debug, Default, Clone)] +struct MockContractState { + global_data: BTreeMap>, + fungible_data: BTreeMap<(Outpoint, AssignmentType), Vec>, + structured_data: BTreeMap<(Outpoint, AssignmentType), Vec>, + rights_data: BTreeMap<(Outpoint, AssignmentType), u32>, + fail_global_access: bool, +} - #[derive(Debug)] - struct MockGlobalStateIter { - data: Vec<(GlobalOrd, RevealedData)>, - current_idx_for_prev: usize, /* For prev(): index of the *next* element to be returned - * by prev() */ - current_idx_for_last: usize, /* For last() after reset: index of the *exact* element to - * be returned */ - original_size: u24, - initial_depth_reset: bool, - } +#[derive(Debug)] +struct MockGlobalStateIter { + data: Vec<(GlobalOrd, RevealedData)>, + current_idx_for_prev: usize, /* For prev(): index of the *next* element to be returned + * by prev() */ + current_idx_for_last: usize, /* For last() after reset: index of the *exact* element to + * be returned */ + original_size: u24, + initial_depth_reset: bool, +} - impl GlobalStateIter for MockGlobalStateIter { - type Data = RevealedData; +impl GlobalStateIter for MockGlobalStateIter { + type Data = RevealedData; - fn size(&mut self) -> u24 { self.original_size } + fn size(&mut self) -> u24 { self.original_size } - fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { - if self.initial_depth_reset { - // If reset was called, prev() should ideally pick up from where last() would have. - // Or, more simply, disallow prev() after reset if nth() is the only user of - // reset+last. For now, let's make prev() restart if reset was - // called for last(). - self.current_idx_for_prev = self.data.len(); - self.initial_depth_reset = false; - } - if self.current_idx_for_prev > 0 { - self.current_idx_for_prev -= 1; - Some(self.data[self.current_idx_for_prev].clone()) - } else { - None - } + fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { + if self.initial_depth_reset { + // If reset was called, prev() should ideally pick up from where last() would have. + // Or, more simply, disallow prev() after reset if nth() is the only user of + // reset+last. For now, let's make prev() restart if reset was + // called for last(). + self.current_idx_for_prev = self.data.len(); + self.initial_depth_reset = false; } - - fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { - if self.initial_depth_reset { - if self.current_idx_for_last < self.data.len() { - Some(self.data[self.current_idx_for_last].clone()) - } else { - None - } - } else if !self.data.is_empty() { - Some(self.data[self.data.len() - 1].clone()) - } else { - None - } + if self.current_idx_for_prev > 0 { + self.current_idx_for_prev -= 1; + Some(self.data[self.current_idx_for_prev].clone()) + } else { + None } + } - fn reset(&mut self, depth: u24) { - self.initial_depth_reset = true; - let depth_u32 = depth.to_u32(); - if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { - self.current_idx_for_last = self.data.len(); // invalid index + fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { + if self.initial_depth_reset { + if self.current_idx_for_last < self.data.len() { + Some(self.data[self.current_idx_for_last].clone()) } else { - self.current_idx_for_last = self.data.len() - 1 - (depth_u32 as usize); + None } + } else if !self.data.is_empty() { + Some(self.data[self.data.len() - 1].clone()) + } else { + None } } - impl ContractStateAccess for MockContractState { - fn global( - &self, - ty: GlobalStateType, - ) -> Result, UnknownGlobalStateType> { - if self.fail_global_access { - return Err(UnknownGlobalStateType(ty)); - } - let data_for_type = self.global_data.get(&ty).cloned().unwrap_or_default(); - let size = u24::try_from(data_for_type.len() as u32).unwrap_or(u24::MAX); - let iter = MockGlobalStateIter { - data: data_for_type, - current_idx_for_prev: size.to_usize(), - current_idx_for_last: 0, // Placeholder, reset will set it - original_size: size, - initial_depth_reset: false, - }; - Ok(GlobalContractState::new(iter)) + fn reset(&mut self, depth: u24) { + self.initial_depth_reset = true; + let depth_u32 = depth.to_u32(); + if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { + self.current_idx_for_last = self.data.len(); // invalid index + } else { + self.current_idx_for_last = self.data.len() - 1 - (depth_u32 as usize); } + } +} - fn rights(&self, outpoint: Outpoint, ty: AssignmentType) -> u32 { - self.rights_data.get(&(outpoint, ty)).cloned().unwrap_or(0) +impl ContractStateAccess for MockContractState { + fn global( + &self, + ty: GlobalStateType, + ) -> Result, UnknownGlobalStateType> { + if self.fail_global_access { + return Err(UnknownGlobalStateType(ty)); } + let data_for_type = self.global_data.get(&ty).cloned().unwrap_or_default(); + let size = u24::try_from(data_for_type.len() as u32).unwrap_or(u24::MAX); + let iter = MockGlobalStateIter { + data: data_for_type, + current_idx_for_prev: size.to_usize(), + current_idx_for_last: 0, // Placeholder, reset will set it + original_size: size, + initial_depth_reset: false, + }; + Ok(GlobalContractState::new(iter)) + } - fn fungible( - &self, - outpoint: Outpoint, - ty: AssignmentType, - ) -> impl DoubleEndedIterator { - self.fungible_data - .get(&(outpoint, ty)) - .cloned() - .unwrap_or_default() - .into_iter() - } + fn rights(&self, outpoint: Outpoint, ty: AssignmentType) -> u32 { + self.rights_data.get(&(outpoint, ty)).cloned().unwrap_or(0) + } - fn data( - &self, - outpoint: Outpoint, - ty: AssignmentType, - ) -> impl DoubleEndedIterator> { - self.structured_data - .get(&(outpoint, ty)) - .cloned() - .unwrap_or_default() - .into_iter() - } + fn fungible( + &self, + outpoint: Outpoint, + ty: AssignmentType, + ) -> impl DoubleEndedIterator { + self.fungible_data + .get(&(outpoint, ty)) + .cloned() + .unwrap_or_default() + .into_iter() } - fn create_dummy_genesis() -> Genesis { - Genesis { - ffv: Ffv::default(), - schema_id: SchemaId::strict_dumb(), - timestamp: 0, - issuer: Identity::strict_dumb(), - chain_net: ChainNet::BitcoinRegtest, - seal_closing_strategy: SealClosingStrategy::default(), - metadata: Metadata::default(), - globals: GlobalState::default(), - assignments: Assignments::::default(), - } + fn data( + &self, + outpoint: Outpoint, + ty: AssignmentType, + ) -> impl DoubleEndedIterator> { + self.structured_data + .get(&(outpoint, ty)) + .cloned() + .unwrap_or_default() + .into_iter() } +} - fn dummy_witness_pos() -> WitnessPos { - WitnessPos::bitcoin(NonZeroU32::new(1).unwrap(), 1231006505).unwrap() +fn create_dummy_genesis() -> Genesis { + Genesis { + ffv: Ffv::default(), + schema_id: SchemaId::strict_dumb(), + timestamp: 0, + issuer: Identity::strict_dumb(), + chain_net: ChainNet::BitcoinRegtest, + seal_closing_strategy: SealClosingStrategy::default(), + metadata: Metadata::default(), + globals: GlobalState::default(), + assignments: Assignments::::default(), } +} - fn dummy_witness_ord_mined() -> WitnessOrd { WitnessOrd::Mined(dummy_witness_pos()) } - - fn create_dummy_transition( - contract_id: ContractId, - signature: Option, - ) -> Transition { - let dummy_opout = Opout::strict_dumb(); - let mut opout_set = BTreeSet::new(); - opout_set.insert(dummy_opout); - let nonempty_opout_set = NonEmptyOrdSet::try_from(opout_set).expect("Should not be empty"); - let inputs = Inputs::from(nonempty_opout_set); - - Transition { - ffv: Ffv::default(), - contract_id, - nonce: 0, - transition_type: schema::TransitionType::strict_dumb(), - metadata: Metadata::default(), - globals: GlobalState::default(), - inputs, - assignments: Assignments::::default(), - signature, - } +fn dummy_witness_pos() -> WitnessPos { + WitnessPos::bitcoin(NonZeroU32::new(1).unwrap(), 1231006505).unwrap() +} + +fn dummy_witness_ord_mined() -> WitnessOrd { WitnessOrd::Mined(dummy_witness_pos()) } + +fn create_dummy_transition(contract_id: ContractId, signature: Option) -> Transition { + let dummy_opout = Opout::strict_dumb(); + let mut opout_set = BTreeSet::new(); + opout_set.insert(dummy_opout); + let nonempty_opout_set = NonEmptyOrdSet::try_from(opout_set).expect("Should not be empty"); + let inputs = Inputs::from(nonempty_opout_set); + + Transition { + ffv: Ffv::default(), + contract_id, + nonce: 0, + transition_type: schema::TransitionType::strict_dumb(), + metadata: Metadata::default(), + globals: GlobalState::default(), + inputs, + assignments: Assignments::::default(), + signature, } +} - fn exec_op_and_assert_st0( - op: ContractOp, - regs: &mut CoreRegs, - context: &VmContext, - expected_st0_ok: bool, - ) { - let step = op.exec(regs, LibSite::default(), context); - assert_eq!( - regs.status(), // Corrected to use regs.status() - expected_st0_ok, - "ST0 flag (is_ok) mismatch for op {:?}", - op - ); - if !expected_st0_ok { - assert_eq!(step, ExecStep::Stop, "ExecStep should be Stop on failure for op {:?}", op); - } else { - assert_eq!(step, ExecStep::Next, "ExecStep should be Next on success for op {:?}", op); - } +fn exec_op_and_assert_st0( + op: ContractOp, + regs: &mut CoreRegs, + context: &VmContext, + expected_st0_ok: bool, +) { + let step = op.exec(regs, LibSite::default(), context); + assert_eq!( + regs.status(), // Corrected to use regs.status() + expected_st0_ok, + "ST0 flag (is_ok) mismatch for op {:?}", + op + ); + if !expected_st0_ok { + assert_eq!(step, ExecStep::Stop, "ExecStep should be Stop on failure for op {:?}", op); + } else { + assert_eq!(step, ExecStep::Next, "ExecStep should be Next on success for op {:?}", op); } } From 65321a5b7d0e87260651827635a19d034ca80498 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 22 May 2025 16:24:32 +0800 Subject: [PATCH 03/21] tests: tentatively add test for CnG operation --- src/vm/op_contract/tests.rs | 69 ++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 0348f242..d9953fad 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -5,7 +5,7 @@ use std::iter; use std::num::NonZeroU32; use std::rc::Rc; -use aluvm::data::Number; +use aluvm::data::{MaybeNumber, Number}; use aluvm::isa::{ExecStep, InstructionSet}; use aluvm::library::LibSite; use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegS}; @@ -65,10 +65,6 @@ impl GlobalStateIter for MockGlobalStateIter { fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { if self.initial_depth_reset { - // If reset was called, prev() should ideally pick up from where last() would have. - // Or, more simply, disallow prev() after reset if nth() is the only user of - // reset+last. For now, let's make prev() restart if reset was - // called for last(). self.current_idx_for_prev = self.data.len(); self.initial_depth_reset = false; } @@ -98,7 +94,7 @@ impl GlobalStateIter for MockGlobalStateIter { self.initial_depth_reset = true; let depth_u32 = depth.to_u32(); if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { - self.current_idx_for_last = self.data.len(); // invalid index + self.current_idx_for_last = self.data.len(); } else { self.current_idx_for_last = self.data.len() - 1 - (depth_u32 as usize); } @@ -118,7 +114,7 @@ impl ContractStateAccess for MockContractState { let iter = MockGlobalStateIter { data: data_for_type, current_idx_for_prev: size.to_usize(), - current_idx_for_last: 0, // Placeholder, reset will set it + current_idx_for_last: 0, original_size: size, initial_depth_reset: false, }; @@ -201,15 +197,62 @@ fn exec_op_and_assert_st0( expected_st0_ok: bool, ) { let step = op.exec(regs, LibSite::default(), context); - assert_eq!( - regs.status(), // Corrected to use regs.status() - expected_st0_ok, - "ST0 flag (is_ok) mismatch for op {:?}", - op - ); + assert_eq!(regs.status(), expected_st0_ok, "ST0 flag (is_ok) mismatch for op {:?}", op); if !expected_st0_ok { assert_eq!(step, ExecStep::Stop, "ExecStep should be Stop on failure for op {:?}", op); } else { assert_eq!(step, ExecStep::Next, "ExecStep should be Next on success for op {:?}", op); } } + +fn create_vm_context<'op, S: ContractStateAccess>( + contract_id: ContractId, + op_info: OpInfo<'op>, + contract_state: Rc>, +) -> VmContext<'op, S> { + VmContext { + contract_id, + op_info, + contract_state, + } +} + +fn create_default_op_info_genesis<'genesis>( + genesis_op_ref: &'genesis Genesis, + ord_op_ref: &'genesis OrdOpRef<'genesis>, + empty_assignments: &'genesis Assignments, +) -> OpInfo<'genesis> { + OpInfo { + id: genesis_op_ref.id(), + prev_state: empty_assignments, + op: ord_op_ref, + } +} + +#[test] +fn test_cng_success_single_global() { + let mut regs = CoreRegs::default(); + let mock_contract_state_rc = Rc::new(RefCell::new(MockContractState::default())); + + let mut genesis_op_val = create_dummy_genesis(); + let contract_id = genesis_op_val.contract_id(); + + genesis_op_val + .globals + .add_state(DUMMY_GLOBAL_TYPE_A, RevealedData::new(SmallBlob::try_from(vec![1u8]).unwrap())) + .unwrap(); + + let empty_assignments_for_genesis = Assignments::::default(); + let ord_op_ref_val = OrdOpRef::Genesis(&genesis_op_val); + + let op_info = create_default_op_info_genesis( + &genesis_op_val, + &ord_op_ref_val, + &empty_assignments_for_genesis, + ); + let context = create_vm_context(contract_id, op_info, mock_contract_state_rc.clone()); + + let op = ContractOp::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); + exec_op_and_assert_st0(op, &mut regs, &context, true); + assert_eq!(regs.get_n(RegA::A8, Reg32::Reg0), MaybeNumber::from(Number::from(1u8))); +} From 5c69cebee57940b3acdaf1c82bbe2e24af1c3f09 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 22 May 2025 16:24:55 +0800 Subject: [PATCH 04/21] tests: add create_default_op_info_transition function for OpInfo creation --- src/vm/op_contract/tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index d9953fad..8e4a3bb2 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -229,6 +229,18 @@ fn create_default_op_info_genesis<'genesis>( } } +fn create_default_op_info_transition<'transition>( + transition_op_ref: &'transition Transition, + prev_state_ref: &'transition Assignments, + ord_op_ref: &'transition OrdOpRef<'transition>, +) -> OpInfo<'transition> { + OpInfo { + id: transition_op_ref.id(), + prev_state: prev_state_ref, + op: ord_op_ref, + } +} + #[test] fn test_cng_success_single_global() { let mut regs = CoreRegs::default(); From 935f5e84e57598ec22dfcf664e1ee218f6917737 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 22 May 2025 16:25:51 +0800 Subject: [PATCH 05/21] fix: add missing newline --- src/vm/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 1d1357b1..5a3b4bed 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -30,6 +30,7 @@ mod op_contract; #[macro_use] mod macroasm; mod contract; + pub use aluvm::aluasm_isa; pub use contract::{ ContractStateAccess, ContractStateEvolve, GlobalContractState, GlobalOrd, GlobalStateIter, From 1f8b67361dbefaacd41a8220cf01e70aa9ef1ebe Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Fri, 23 May 2025 11:53:42 +0800 Subject: [PATCH 06/21] refactor(tests): update imports and constants for clarity; update MockContractState and MockGlobalStateIter --- src/vm/op_contract/tests.rs | 39 +++++++++++++++---------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 8e4a3bb2..9d6663b8 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -5,11 +5,11 @@ use std::iter; use std::num::NonZeroU32; use std::rc::Rc; -use aluvm::data::{MaybeNumber, Number}; -use aluvm::isa::{ExecStep, InstructionSet}; +use aluvm::data::{ByteStr, MaybeNumber, Number}; +use aluvm::isa::ExecStep; use aluvm::library::LibSite; -use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegS}; -use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob}; +use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegF, RegR, RegS}; +use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob, SmallOrdMap}; use amplify::num::u24; use bp::{Outpoint, Txid}; use commit_verify::StrictHash; @@ -25,16 +25,16 @@ use crate::vm::{ use crate::{ schema, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, FungibleState, Genesis, GenesisSeal, GlobalState, GlobalStateType, GraphSeal, Identity, Inputs, - MetaType, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, SealClosingStrategy, - Signature, Transition, TypedAssigns, + MetaType, MetaValue, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, + SealClosingStrategy, Signature, Transition, TypedAssigns, }; const DUMMY_ASSIGN_TYPE_FUNGIBLE: AssignmentType = AssignmentType::with(1000); const DUMMY_ASSIGN_TYPE_DATA: AssignmentType = AssignmentType::with(1001); -const DUMMY_ASSIGN_TYPE_RIGHTS: AssignmentType = AssignmentType::with(1002); +const DUMMY_ASSIGN_TYPE_UNUSED: AssignmentType = AssignmentType::with(1003); const DUMMY_GLOBAL_TYPE_A: GlobalStateType = GlobalStateType::with(2000); -const DUMMY_GLOBAL_TYPE_B: GlobalStateType = GlobalStateType::with(2001); +const DUMMY_GLOBAL_TYPE_UNUSED: GlobalStateType = GlobalStateType::with(2002); const DUMMY_META_TYPE_A: MetaType = MetaType::with(3000); @@ -43,30 +43,25 @@ struct MockContractState { global_data: BTreeMap>, fungible_data: BTreeMap<(Outpoint, AssignmentType), Vec>, structured_data: BTreeMap<(Outpoint, AssignmentType), Vec>, - rights_data: BTreeMap<(Outpoint, AssignmentType), u32>, fail_global_access: bool, } #[derive(Debug)] struct MockGlobalStateIter { data: Vec<(GlobalOrd, RevealedData)>, - current_idx_for_prev: usize, /* For prev(): index of the *next* element to be returned - * by prev() */ - current_idx_for_last: usize, /* For last() after reset: index of the *exact* element to - * be returned */ + current_idx_for_prev: usize, + current_idx_for_last: usize, original_size: u24, - initial_depth_reset: bool, + has_been_reset: bool, } impl GlobalStateIter for MockGlobalStateIter { type Data = RevealedData; - fn size(&mut self) -> u24 { self.original_size } - fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { - if self.initial_depth_reset { + if self.has_been_reset { self.current_idx_for_prev = self.data.len(); - self.initial_depth_reset = false; + self.has_been_reset = false; } if self.current_idx_for_prev > 0 { self.current_idx_for_prev -= 1; @@ -75,9 +70,8 @@ impl GlobalStateIter for MockGlobalStateIter { None } } - fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { - if self.initial_depth_reset { + if self.has_been_reset { if self.current_idx_for_last < self.data.len() { Some(self.data[self.current_idx_for_last].clone()) } else { @@ -89,14 +83,13 @@ impl GlobalStateIter for MockGlobalStateIter { None } } - fn reset(&mut self, depth: u24) { - self.initial_depth_reset = true; + self.has_been_reset = true; let depth_u32 = depth.to_u32(); if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { self.current_idx_for_last = self.data.len(); } else { - self.current_idx_for_last = self.data.len() - 1 - (depth_u32 as usize); + self.current_idx_for_last = (self.data.len() - 1).saturating_sub(depth_u32 as usize); } } } From df019e081dd9cc5186b3883caf823392f91e2059 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Fri, 23 May 2025 11:55:29 +0800 Subject: [PATCH 07/21] fix(tests): update MockContractState to include rights_data and rename initial_depth_reset to has_been_reset --- src/vm/op_contract/tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 9d6663b8..c596d0db 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -11,8 +11,10 @@ use aluvm::library::LibSite; use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegF, RegR, RegS}; use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob, SmallOrdMap}; use amplify::num::u24; +use amplify::{Bytes64, Wrapper}; use bp::{Outpoint, Txid}; use commit_verify::StrictHash; +use secp256k1::{generate_keypair, rand, Secp256k1}; use strict_encoding::StrictDumb; use super::*; @@ -41,6 +43,7 @@ const DUMMY_META_TYPE_A: MetaType = MetaType::with(3000); #[derive(Debug, Default, Clone)] struct MockContractState { global_data: BTreeMap>, + rights_data: BTreeMap<(Outpoint, AssignmentType), u32>, fungible_data: BTreeMap<(Outpoint, AssignmentType), Vec>, structured_data: BTreeMap<(Outpoint, AssignmentType), Vec>, fail_global_access: bool, @@ -109,7 +112,7 @@ impl ContractStateAccess for MockContractState { current_idx_for_prev: size.to_usize(), current_idx_for_last: 0, original_size: size, - initial_depth_reset: false, + has_been_reset: false, }; Ok(GlobalContractState::new(iter)) } From 2178d545d8a1bfa9d58bfb945560c42379a2aa99 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Fri, 23 May 2025 11:57:09 +0800 Subject: [PATCH 08/21] refactor(tests): add assignments_from_typed. rename dummy functions for clarity and improve assertion messages --- src/vm/op_contract/tests.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index c596d0db..3b1abc10 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -146,7 +146,7 @@ impl ContractStateAccess for MockContractState { } } -fn create_dummy_genesis() -> Genesis { +fn dummy_genesis() -> Genesis { Genesis { ffv: Ffv::default(), schema_id: SchemaId::strict_dumb(), @@ -166,7 +166,7 @@ fn dummy_witness_pos() -> WitnessPos { fn dummy_witness_ord_mined() -> WitnessOrd { WitnessOrd::Mined(dummy_witness_pos()) } -fn create_dummy_transition(contract_id: ContractId, signature: Option) -> Transition { +fn dummy_transition(contract_id: ContractId, signature: Option) -> Transition { let dummy_opout = Opout::strict_dumb(); let mut opout_set = BTreeSet::new(); opout_set.insert(dummy_opout); @@ -193,7 +193,14 @@ fn exec_op_and_assert_st0( expected_st0_ok: bool, ) { let step = op.exec(regs, LibSite::default(), context); - assert_eq!(regs.status(), expected_st0_ok, "ST0 flag (is_ok) mismatch for op {:?}", op); + assert_eq!( + regs.status(), + expected_st0_ok, + "ST0 flag mismatch for op {:?}. Expected {}, got {}", + op, + expected_st0_ok, + regs.status() + ); if !expected_st0_ok { assert_eq!(step, ExecStep::Stop, "ExecStep should be Stop on failure for op {:?}", op); } else { @@ -213,6 +220,15 @@ fn create_vm_context<'op, S: ContractStateAccess>( } } +fn assignments_from_typed( + map: BTreeMap>, +) -> Assignments { + let mut confined_map = SmallOrdMap::new(); + for (k, v) in map { + confined_map.insert(k, v).unwrap(); + } + Assignments::from_inner(confined_map) +} fn create_default_op_info_genesis<'genesis>( genesis_op_ref: &'genesis Genesis, ord_op_ref: &'genesis OrdOpRef<'genesis>, From 7b7ef098479a643498465ea64ba2be790a2383ed Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Fri, 23 May 2025 11:57:49 +0800 Subject: [PATCH 09/21] feat(tests): add create_fungible_assign_vec and create_structured_assign_vec functions for assignment creation --- src/vm/op_contract/tests.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 3b1abc10..c3950e05 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -25,7 +25,7 @@ use crate::vm::{ OrdOpRef, UnknownGlobalStateType, VmContext, WitnessOrd, WitnessPos, }; use crate::{ - schema, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, + schema, seal, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, FungibleState, Genesis, GenesisSeal, GlobalState, GlobalStateType, GraphSeal, Identity, Inputs, MetaType, MetaValue, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, SealClosingStrategy, Signature, Transition, TypedAssigns, @@ -229,6 +229,40 @@ fn assignments_from_typed( } Assignments::from_inner(confined_map) } + +fn create_fungible_assign_vec( + seal: GraphSeal, + values: Vec, +) -> AssignVec> { + let assigns = values + .into_iter() + .map(|v| Assign::Revealed { + seal, + state: RevealedValue::from(v), + }) + .collect::>(); + if assigns.is_empty() { + panic!("create_fungible_assignments called with empty values"); + } + AssignVec::with(NonEmptyVec::try_from(assigns).unwrap()) +} + +fn create_structured_assign_vec( + seal: GraphSeal, + data_items: Vec>, +) -> AssignVec> { + let assigns = data_items + .into_iter() + .map(|d| Assign::Revealed { + seal, + state: RevealedData::new(SmallBlob::try_from(d).unwrap()), + }) + .collect::>(); + if assigns.is_empty() { + panic!("create_structured_assignments called with empty data"); + } + AssignVec::with(NonEmptyVec::try_from(assigns).unwrap()) +} fn create_default_op_info_genesis<'genesis>( genesis_op_ref: &'genesis Genesis, ord_op_ref: &'genesis OrdOpRef<'genesis>, From 4ffc6be6492355f9779a79e103edf7198e8cd55e Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Mon, 26 May 2025 11:21:10 +0800 Subject: [PATCH 10/21] feat(tests): add TestEnv struct with methods for managing assignments and global state --- src/vm/op_contract/tests.rs | 242 +++++++++++++++++++++++++++++------- 1 file changed, 199 insertions(+), 43 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index c3950e05..4cfc9c25 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -33,14 +33,16 @@ use crate::{ const DUMMY_ASSIGN_TYPE_FUNGIBLE: AssignmentType = AssignmentType::with(1000); const DUMMY_ASSIGN_TYPE_DATA: AssignmentType = AssignmentType::with(1001); +const DUMMY_ASSIGN_TYPE_RIGHTS: AssignmentType = AssignmentType::with(1002); const DUMMY_ASSIGN_TYPE_UNUSED: AssignmentType = AssignmentType::with(1003); const DUMMY_GLOBAL_TYPE_A: GlobalStateType = GlobalStateType::with(2000); +const DUMMY_GLOBAL_TYPE_B: GlobalStateType = GlobalStateType::with(2001); const DUMMY_GLOBAL_TYPE_UNUSED: GlobalStateType = GlobalStateType::with(2002); const DUMMY_META_TYPE_A: MetaType = MetaType::with(3000); -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] struct MockContractState { global_data: BTreeMap>, rights_data: BTreeMap<(Outpoint, AssignmentType), u32>, @@ -90,7 +92,12 @@ impl GlobalStateIter for MockGlobalStateIter { self.has_been_reset = true; let depth_u32 = depth.to_u32(); if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { - self.current_idx_for_last = self.data.len(); + self.current_idx_for_last = 0; // If depth is too large, reset to start or a safe + // default. + // The original implementation had `self.data.len()` + // which could lead to out-of-bounds on next `last()` + // call. Let's + // adjust to be safe for `last()`. } else { self.current_idx_for_last = (self.data.len() - 1).saturating_sub(depth_u32 as usize); } @@ -263,54 +270,203 @@ fn create_structured_assign_vec( } AssignVec::with(NonEmptyVec::try_from(assigns).unwrap()) } -fn create_default_op_info_genesis<'genesis>( - genesis_op_ref: &'genesis Genesis, - ord_op_ref: &'genesis OrdOpRef<'genesis>, - empty_assignments: &'genesis Assignments, -) -> OpInfo<'genesis> { - OpInfo { - id: genesis_op_ref.id(), - prev_state: empty_assignments, - op: ord_op_ref, - } + +struct TestEnv { + contract_id: ContractId, + genesis_val: Option, + transition_val: Option, + prev_assignments_val: Assignments, + owned_assignments_val: Assignments, + globals_val: GlobalState, + metadata_val: Metadata, + mock_contract_state_rc: Rc>, + regs: CoreRegs, } -fn create_default_op_info_transition<'transition>( - transition_op_ref: &'transition Transition, - prev_state_ref: &'transition Assignments, - ord_op_ref: &'transition OrdOpRef<'transition>, -) -> OpInfo<'transition> { - OpInfo { - id: transition_op_ref.id(), - prev_state: prev_state_ref, - op: ord_op_ref, +impl TestEnv { + fn for_genesis() -> Self { + let genesis = dummy_genesis(); + let contract_id = genesis.contract_id(); + Self { + contract_id, + genesis_val: Some(genesis), + transition_val: None, + prev_assignments_val: Assignments::default(), + owned_assignments_val: Assignments::default(), + globals_val: GlobalState::default(), + metadata_val: Metadata::default(), + mock_contract_state_rc: Rc::new(RefCell::new(MockContractState::default())), + regs: CoreRegs::default(), + } } -} -#[test] -fn test_cng_success_single_global() { - let mut regs = CoreRegs::default(); - let mock_contract_state_rc = Rc::new(RefCell::new(MockContractState::default())); + fn for_transition() -> Self { + let contract_id = ContractId::strict_dumb(); + let transition = dummy_transition(contract_id, None); + Self { + contract_id, + genesis_val: None, + transition_val: Some(transition), + prev_assignments_val: Assignments::default(), + owned_assignments_val: Assignments::default(), + globals_val: GlobalState::default(), + metadata_val: Metadata::default(), + mock_contract_state_rc: Rc::new(RefCell::new(MockContractState::default())), + regs: CoreRegs::default(), + } + } - let mut genesis_op_val = create_dummy_genesis(); - let contract_id = genesis_op_val.contract_id(); + fn set_contract_id(mut self, contract_id: ContractId) -> Self { + self.contract_id = contract_id; + if let Some(t) = self.transition_val.as_mut() { + t.contract_id = contract_id; + } + self + } - genesis_op_val - .globals - .add_state(DUMMY_GLOBAL_TYPE_A, RevealedData::new(SmallBlob::try_from(vec![1u8]).unwrap())) - .unwrap(); + fn add_global_current_op(mut self, global_type: GlobalStateType, data: Vec) -> Self { + let revealed_data = RevealedData::new(SmallBlob::try_from(data).unwrap()); + if let Some(g) = self.genesis_val.as_mut() { + g.globals.add_state(global_type, revealed_data).unwrap(); + } else if let Some(t) = self.transition_val.as_mut() { + t.globals.add_state(global_type, revealed_data).unwrap(); + } + self.globals_val = if self.genesis_val.is_some() { + self.genesis_val.as_ref().unwrap().globals.clone() + } else { + self.transition_val.as_ref().unwrap().globals.clone() + }; + self + } - let empty_assignments_for_genesis = Assignments::::default(); - let ord_op_ref_val = OrdOpRef::Genesis(&genesis_op_val); + fn add_metadata_current_op(mut self, meta_type: MetaType, data: Vec) -> Self { + let meta_value = MetaValue::from(SmallBlob::try_from(data).unwrap()); + if let Some(g) = self.genesis_val.as_mut() { + g.metadata.insert(meta_type, meta_value).unwrap(); + } else if let Some(t) = self.transition_val.as_mut() { + t.metadata.insert(meta_type, meta_value).unwrap(); + } + self.metadata_val = if self.genesis_val.is_some() { + self.genesis_val.as_ref().unwrap().metadata.clone() + } else { + self.transition_val.as_ref().unwrap().metadata.clone() + }; + self + } - let op_info = create_default_op_info_genesis( - &genesis_op_val, - &ord_op_ref_val, - &empty_assignments_for_genesis, - ); - let context = create_vm_context(contract_id, op_info, mock_contract_state_rc.clone()); + fn add_prev_assign_fungible(mut self, assign_type: AssignmentType, values: Vec) -> Self { + let typed_assigns = + TypedAssigns::Fungible(create_fungible_assign_vec(GraphSeal::strict_dumb(), values)); + self.prev_assignments_val + .insert(assign_type, typed_assigns) + .unwrap(); + self + } + + fn add_prev_assign_structured( + mut self, + assign_type: AssignmentType, + data_items: Vec>, + ) -> Self { + let typed_assigns = TypedAssigns::Structured(create_structured_assign_vec( + GraphSeal::strict_dumb(), + data_items, + )); + self.prev_assignments_val + .insert(assign_type, typed_assigns) + .unwrap(); + self + } + + fn add_owned_assign_fungible(mut self, assign_type: AssignmentType, values: Vec) -> Self { + let typed_assigns = + TypedAssigns::Fungible(create_fungible_assign_vec(GraphSeal::strict_dumb(), values)); + if let Some(g) = self.genesis_val.as_mut() { + // Note: For Genesis, this requires GenesisSeal, not GraphSeal. + // Adjusting this part if real Genesis owned assignments need to be tested. + // For now, it will panic as stated, or we treat genesis's owned assignments as empty. + panic!( + "add_owned_assign_fungible for Genesis not fully implemented in TestEnv due to \ + Seal type mismatch" + ); + } else if let Some(t) = self.transition_val.as_mut() { + t.assignments.insert(assign_type, typed_assigns).unwrap(); + } + self.owned_assignments_val = self.transition_val.as_ref().unwrap().assignments.clone(); + self + } + + fn add_owned_assign_structured( + mut self, + assign_type: AssignmentType, + data_items: Vec>, + ) -> Self { + let typed_assigns = TypedAssigns::Structured(create_structured_assign_vec( + GraphSeal::strict_dumb(), + data_items, + )); + if let Some(g) = self.genesis_val.as_mut() { + panic!( + "add_owned_assign_structured for Genesis not fully implemented in TestEnv due to \ + Seal type mismatch" + ); + } else if let Some(t) = self.transition_val.as_mut() { + t.assignments.insert(assign_type, typed_assigns).unwrap(); + } + self.owned_assignments_val = self.transition_val.as_ref().unwrap().assignments.clone(); + self + } - let op = ContractOp::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); - exec_op_and_assert_st0(op, &mut regs, &context, true); - assert_eq!(regs.get_n(RegA::A8, Reg32::Reg0), MaybeNumber::from(Number::from(1u8))); + fn set_mock_global_state_history( + mut self, + global_type: GlobalStateType, + history: Vec<(GlobalOrd, RevealedData)>, + ) -> Self { + self.mock_contract_state_rc + .borrow_mut() + .global_data + .insert(global_type, history); + self + } + + fn set_mock_fail_global_access(mut self, fail: bool) -> Self { + self.mock_contract_state_rc.borrow_mut().fail_global_access = fail; + self + } + + fn execute<'this_env>( + &'this_env mut self, + op_code: ContractOp, + expected_st0_ok: bool, + ) where + MockContractState: 'this_env, + { + let op_info: OpInfo; + let ord_op_ref_val_owned: OrdOpRef; + + if let Some(genesis) = &self.genesis_val { + ord_op_ref_val_owned = OrdOpRef::Genesis(genesis); + op_info = OpInfo { + id: genesis.id(), + prev_state: &self.prev_assignments_val, + op: &ord_op_ref_val_owned, + }; + } else if let Some(transition) = &self.transition_val { + let txid = Txid::strict_dumb(); + let bundle_id = BundleId::strict_dumb(); + ord_op_ref_val_owned = + OrdOpRef::Transition(transition, txid, dummy_witness_ord_mined(), bundle_id); + op_info = OpInfo { + id: transition.id(), + prev_state: &self.prev_assignments_val, + op: &ord_op_ref_val_owned, + }; + } else { + panic!("TestEnv not initialized with an operation"); + } + + let context = + create_vm_context(self.contract_id, op_info, self.mock_contract_state_rc.clone()); + exec_op_and_assert_st0(op_code, &mut self.regs, &context, expected_st0_ok); + } } From 5b118888e402354d3f5cc4ca26428d87c23b3e84 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Mon, 26 May 2025 11:27:41 +0800 Subject: [PATCH 11/21] test(tests): add Count Operations tests for CnP, CnS, CnG, and CnC functionalities --- src/vm/op_contract/tests.rs | 152 +++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 4 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 4cfc9c25..994aae05 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -92,7 +92,7 @@ impl GlobalStateIter for MockGlobalStateIter { self.has_been_reset = true; let depth_u32 = depth.to_u32(); if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { - self.current_idx_for_last = 0; // If depth is too large, reset to start or a safe + self.current_idx_for_last = 0; // default. // The original implementation had `self.data.len()` // which could lead to out-of-bounds on next `last()` @@ -382,9 +382,6 @@ impl TestEnv { let typed_assigns = TypedAssigns::Fungible(create_fungible_assign_vec(GraphSeal::strict_dumb(), values)); if let Some(g) = self.genesis_val.as_mut() { - // Note: For Genesis, this requires GenesisSeal, not GraphSeal. - // Adjusting this part if real Genesis owned assignments need to be tested. - // For now, it will panic as stated, or we treat genesis's owned assignments as empty. panic!( "add_owned_assign_fungible for Genesis not fully implemented in TestEnv due to \ Seal type mismatch" @@ -470,3 +467,150 @@ impl TestEnv { exec_op_and_assert_st0(op_code, &mut self.regs, &context, expected_st0_ok); } } + +mod count_ops { + use aluvm::data::{MaybeNumber, Number}; + use aluvm::reg::{Reg32, RegA}; + + use super::*; + + // CnP Tests (Count Previous state) + #[test] + fn test_cnp_found_multiple() { + let mut env = TestEnv::for_transition().add_prev_assign_fungible( + DUMMY_ASSIGN_TYPE_FUNGIBLE, + vec![10, 20, 30], // 3 items + ); + let op_code = ContractOp::CnP(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::from(Number::from(3u16))); + } + + #[test] + fn test_cnp_found_single() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]); + let op_code = ContractOp::CnP(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::from(Number::from(1u16))); + } + + #[test] + fn test_cnp_type_not_found_in_prev_state() { + let mut env = TestEnv::for_transition(); + let op_code = ContractOp::CnP(DUMMY_ASSIGN_TYPE_UNUSED, Reg32::Reg0); + env.execute(op_code, true); + // CnP sets target register to None if the type is not found in prev_state + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::none()); + } + + // CnS Tests (Count Same [owned] state) + #[test] + fn test_cns_found_multiple_transition() { + let mut env = TestEnv::for_transition().add_owned_assign_structured( + DUMMY_ASSIGN_TYPE_DATA, + vec![vec![1], vec![2]], // 2 items + ); + let op_code = ContractOp::CnS(DUMMY_ASSIGN_TYPE_DATA, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::from(Number::from(2u16))); + } + + #[test] + fn test_cns_found_single_transition() { + let mut env = TestEnv::for_transition() + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![vec![1]]); + let op_code = ContractOp::CnS(DUMMY_ASSIGN_TYPE_DATA, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::from(Number::from(1u16))); + } + + #[test] + fn test_cns_type_not_found_in_owned_state_transition() { + let mut env = TestEnv::for_transition(); + let op_code = ContractOp::CnS(DUMMY_ASSIGN_TYPE_UNUSED, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::none()); + } + + // CnS for Genesis (needs specific handling for GenesisSeal if we strictly test owned + // assignments) For simplicity, if `add_owned_assign_...` for genesis is too complex due to + // seal types, we can test with an empty owned assignment for genesis, which is a valid + // case. + #[test] + fn test_cns_genesis_type_not_found() { + let mut env = TestEnv::for_genesis(); + let op_code = ContractOp::CnS(DUMMY_ASSIGN_TYPE_UNUSED, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::none()); + } + + // CnG Tests (Count Next [current op's] Global state) + #[test] + fn test_cng_found_multiple() { + let mut env = TestEnv::for_genesis() + .add_global_current_op(DUMMY_GLOBAL_TYPE_A, vec![1]) + .add_global_current_op(DUMMY_GLOBAL_TYPE_A, vec![2]); + let op_code = ContractOp::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A8, Reg32::Reg0), MaybeNumber::from(Number::from(2u8))); + } + + #[test] + fn test_cng_found_single() { + let mut env = TestEnv::for_genesis().add_global_current_op(DUMMY_GLOBAL_TYPE_A, vec![1]); + let op_code = ContractOp::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A8, Reg32::Reg0), MaybeNumber::from(Number::from(1u8))); + } + + #[test] + fn test_cng_type_not_found_in_globals() { + let mut env = TestEnv::for_genesis(); + let op_code = ContractOp::CnG(DUMMY_GLOBAL_TYPE_UNUSED, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A8, Reg32::Reg0), MaybeNumber::none()); + } + + // CnC Tests (Count Contract's [historical] Global state) + #[test] + fn test_cnc_found_multiple() { + let history = vec![ + (GlobalOrd::genesis(0), RevealedData::new(SmallBlob::try_from(vec![1]).unwrap())), + (GlobalOrd::genesis(1), RevealedData::new(SmallBlob::try_from(vec![2]).unwrap())), + ]; + let mut env = + TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); + let op_code = ContractOp::CnC(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A32, Reg32::Reg0), MaybeNumber::from(Number::from(2u32))); + } + + #[test] + fn test_cnc_found_single() { + let history = + vec![(GlobalOrd::genesis(0), RevealedData::new(SmallBlob::try_from(vec![1]).unwrap()))]; + let mut env = + TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); + let op_code = ContractOp::CnC(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A32, Reg32::Reg0), MaybeNumber::from(Number::from(1u32))); + } + + #[test] + fn test_cnc_type_not_found_in_history() { + let mut env = TestEnv::for_genesis(); + let op_code = ContractOp::CnC(DUMMY_GLOBAL_TYPE_UNUSED, Reg32::Reg0); + env.execute(op_code, true); + // If type not found in BTreeMap, unwrap_or_default gives empty vec, size 0. + assert_eq!(env.regs.get_n(RegA::A32, Reg32::Reg0), MaybeNumber::from(Number::from(0u32))); + } + + #[test] + fn test_cnc_fail_on_global_access_error() { + let mut env = TestEnv::for_genesis().set_mock_fail_global_access(true); + let op_code = ContractOp::CnC(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0); + env.execute(op_code, true); + assert_eq!(env.regs.get_n(RegA::A32, Reg32::Reg0), MaybeNumber::none()); + } +} From 907e0a3734157130e29ccbe51c1f84ad3bd8264f Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Tue, 27 May 2025 13:56:22 +0800 Subject: [PATCH 12/21] fix(tests): assume depth is 1-based and fix related tests --- src/vm/op_contract/tests.rs | 480 ++++++++++++++++++++++++++++++++---- 1 file changed, 428 insertions(+), 52 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 994aae05..f51e9350 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -51,76 +51,89 @@ struct MockContractState { fail_global_access: bool, } -#[derive(Debug)] -struct MockGlobalStateIter { - data: Vec<(GlobalOrd, RevealedData)>, - current_idx_for_prev: usize, - current_idx_for_last: usize, - original_size: u24, - has_been_reset: bool, +/// A mock implementation of `GlobalStateIter` initialized from a `Vec`. +pub struct MockGlobalStateIter<'a> { + data: &'a Vec<(GlobalOrd, RevealedData)>, + current_pos: usize, + last_item_cache: Option<(GlobalOrd, &'a RevealedData)>, + total_size: usize, } -impl GlobalStateIter for MockGlobalStateIter { - type Data = RevealedData; - fn size(&mut self) -> u24 { self.original_size } - fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { - if self.has_been_reset { - self.current_idx_for_prev = self.data.len(); - self.has_been_reset = false; - } - if self.current_idx_for_prev > 0 { - self.current_idx_for_prev -= 1; - Some(self.data[self.current_idx_for_prev].clone()) - } else { - None +impl<'a> MockGlobalStateIter<'a> { + pub fn new(initial_data: &'a Vec<(GlobalOrd, RevealedData)>) -> Self { + MockGlobalStateIter { + data: initial_data, + current_pos: 0, + last_item_cache: None, + total_size: initial_data.len(), } } - fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { - if self.has_been_reset { - if self.current_idx_for_last < self.data.len() { - Some(self.data[self.current_idx_for_last].clone()) - } else { - None - } - } else if !self.data.is_empty() { - Some(self.data[self.data.len() - 1].clone()) +} + +impl<'a> GlobalStateIter for MockGlobalStateIter<'a> { + type Data = &'a RevealedData; + + fn size(&mut self) -> u24 { + u24::try_from(self.total_size as u32).expect("MockGlobalStateIter data size must fit u24") + } + + fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { + if self.current_pos < self.total_size { + let (ord_ref, data_ref) = &self.data[self.current_pos]; + let item_to_return = (*ord_ref, data_ref); + + self.last_item_cache = Some(item_to_return); + self.current_pos += 1; + Some(item_to_return) } else { + self.last_item_cache = None; None } } - fn reset(&mut self, depth: u24) { - self.has_been_reset = true; - let depth_u32 = depth.to_u32(); - if self.data.is_empty() || depth_u32 >= self.original_size.to_u32() { - self.current_idx_for_last = 0; - // default. - // The original implementation had `self.data.len()` - // which could lead to out-of-bounds on next `last()` - // call. Let's - // adjust to be safe for `last()`. + + fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { self.last_item_cache } + + fn reset(&mut self, depth_1based: u24) { + if depth_1based == u24::ZERO { + panic!("MockGlobalStateIter cannot be reset to depth 0"); + } + + let target_idx_for_last_0based = depth_1based.to_usize() - 1; // Convert 1-based to 0-indexed + + if target_idx_for_last_0based < self.total_size { + // The item at target_idx_for_last_0based should become the "last" item. + let (ord_ref, data_ref) = &self.data[target_idx_for_last_0based]; + self.last_item_cache = Some((*ord_ref, data_ref)); + // The next call to prev() should yield the item *after* this one. + self.current_pos = target_idx_for_last_0based + 1; } else { - self.current_idx_for_last = (self.data.len() - 1).saturating_sub(depth_u32 as usize); + // Requested 1-based depth is out of bounds. + // e.g., total_size=1. reset(depth_1based=2). target_idx_for_last_0based=1. + // 1 < 1 is false. Comes here. + self.last_item_cache = None; + self.current_pos = self.total_size; // Exhausted } } } +static EMPTY_GLOBAL_VEC: Vec<(GlobalOrd, RevealedData)> = Vec::new(); + impl ContractStateAccess for MockContractState { fn global( &self, ty: GlobalStateType, - ) -> Result, UnknownGlobalStateType> { + ) -> Result, UnknownGlobalStateType> { if self.fail_global_access { return Err(UnknownGlobalStateType(ty)); } - let data_for_type = self.global_data.get(&ty).cloned().unwrap_or_default(); - let size = u24::try_from(data_for_type.len() as u32).unwrap_or(u24::MAX); - let iter = MockGlobalStateIter { - data: data_for_type, - current_idx_for_prev: size.to_usize(), - current_idx_for_last: 0, - original_size: size, - has_been_reset: false, - }; + + let data_ref: &Vec<(GlobalOrd, RevealedData)> = self + .global_data + .get(&ty) + .map_or(&EMPTY_GLOBAL_VEC, |vec| vec); + + let iter = MockGlobalStateIter::new(data_ref); + Ok(GlobalContractState::new(iter)) } @@ -152,7 +165,6 @@ impl ContractStateAccess for MockContractState { .into_iter() } } - fn dummy_genesis() -> Genesis { Genesis { ffv: Ffv::default(), @@ -415,7 +427,7 @@ impl TestEnv { } fn set_mock_global_state_history( - mut self, + self, global_type: GlobalStateType, history: Vec<(GlobalOrd, RevealedData)>, ) -> Self { @@ -614,3 +626,367 @@ mod count_ops { assert_eq!(env.regs.get_n(RegA::A32, Reg32::Reg0), MaybeNumber::none()); } } + +mod load_ops { + use aluvm::data::Number; + use amplify::confinement::SmallBlob; + use bp::seals::SecretSeal; + + use super::*; + + // LdP (Load Previous structured state) Tests + #[test] + fn test_ldp_success_revealed() { + let data_vec = vec![0xAB, 0xCD, 0xEF]; + let mut env = TestEnv::for_transition() + .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![data_vec.clone()]); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); // index = 0 + + let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); + env.execute(op_code, true); + + assert_eq!(env.regs.s16(RegS::from(0)).unwrap().as_ref(), data_vec.as_slice()); + } + + #[test] + fn test_ldp_success_concealed_seal_loads_state() { + let data_vec = vec![0xAA, 0xBB]; + let revealed_data = RevealedData::new(SmallBlob::try_from(data_vec.clone()).unwrap()); + let concealed_assign = Assign::ConfidentialSeal { + seal: SecretSeal::strict_dumb(), + state: revealed_data, + }; + + let mut prev_map = BTreeMap::new(); + prev_map.insert( + DUMMY_ASSIGN_TYPE_DATA, + TypedAssigns::Structured(AssignVec::with(NonEmptyVec::with(concealed_assign))), + ); + let mut env = TestEnv::for_transition(); + env.prev_assignments_val = assignments_from_typed(prev_map); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + + let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(0)).unwrap().as_ref(), data_vec.as_slice()); + } + + #[test] + fn test_ldp_fail_index_reg_none() { + let mut env = TestEnv::for_transition(); // a16[0] (index_reg) is None + let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(0)).is_none()); + } + + #[test] + fn test_ldp_fail_state_type_missing_in_prev() { + let mut env = TestEnv::for_transition(); // prev_assignments is empty + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(0)).is_none()); + } + + #[test] + fn test_ldp_fail_index_oob() { + // 1 item at index 0 + let mut env = TestEnv::for_transition() + .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![vec![1]]); + // Request index 1 + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(1u16)); + + let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(0)).is_none()); + } + + #[test] + fn test_ldp_fail_wrong_state_type_in_prev() { + // prev_state has DUMMY_ASSIGN_TYPE_DATA, but it's Fungible, not Structured for LdP + let mut env = + TestEnv::for_transition().add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_DATA, vec![100]); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + + let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(0)).is_none()); + } + + // LdS (Load Same/owned structured state) Tests + #[test] + fn test_lds_success_revealed() { + let data_vec = vec![0xBE, 0xEF]; + let mut env = TestEnv::for_transition() + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![data_vec.clone()]); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + + let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(1)).unwrap().as_ref(), data_vec.as_slice()); + } + + #[test] + fn test_lds_fail_index_reg_none() { + let mut env = TestEnv::for_transition(); // a16[0] is None + let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(1)).is_none()); + } + + #[test] + fn test_lds_fail_state_type_missing_in_owned() { + let mut env = TestEnv::for_transition(); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(1)).is_none()); + } + + #[test] + fn test_lds_fail_index_oob() { + let mut env = TestEnv::for_transition() + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![vec![1]]); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(1u16)); // Request index 1 + let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(1)).is_none()); + } + + #[test] + fn test_lds_fail_wrong_state_type_in_owned() { + // Data is fungible + let mut env = + TestEnv::for_transition().add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_DATA, vec![100]); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(1)).is_none()); + } + + // LdF (Load Same/owned Fungible state) Tests + #[test] + fn test_ldf_success_revealed() { + let fungible_val = 777u64; + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![fungible_val]); + // index_reg for source; destination is a64[Reg16::Reg0] + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + + let op_code = ContractOp::LdF(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg16::Reg0, Reg16::Reg0); + env.execute(op_code, true); + assert_eq!( + env.regs.get_n(RegA::A64, Reg32::Reg0), + MaybeNumber::from(Number::from(fungible_val)) + ); + } + + #[test] + fn test_ldf_fail_index_reg_none() { + let mut env = TestEnv::for_transition(); + let op_code = ContractOp::LdF(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg16::Reg0, Reg16::Reg0); + env.execute(op_code, false); + assert!(env.regs.get_n(RegA::A64, Reg32::Reg0).is_none()); + } + + #[test] + fn test_ldf_fail_state_type_missing_in_owned() { + let mut env = TestEnv::for_transition(); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + let op_code = ContractOp::LdF(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg16::Reg0, Reg16::Reg0); + env.execute(op_code, false); + assert!(env.regs.get_n(RegA::A64, Reg32::Reg0).is_none()); + } + + #[test] + fn test_ldf_fail_index_oob() { + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![100]); + // Request index 1 + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(1u16)); + let op_code = ContractOp::LdF(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg16::Reg0, Reg16::Reg0); + env.execute(op_code, false); + assert!(env.regs.get_n(RegA::A64, Reg32::Reg0).is_none()); + } + + #[test] + fn test_ldf_fail_wrong_state_type_in_owned() { + // Data is structured + let mut env = TestEnv::for_transition() + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); + let op_code = ContractOp::LdF(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg16::Reg0, Reg16::Reg0); + env.execute(op_code, false); + assert!(env.regs.get_n(RegA::A64, Reg32::Reg0).is_none()); + } + + // LdG (Load Global state from current op) Tests + #[test] + fn test_ldg_success() { + let data_vec = vec![0xC0, 0xDE]; + let mut env = + TestEnv::for_genesis().add_global_current_op(DUMMY_GLOBAL_TYPE_A, data_vec.clone()); + // index_reg for source (a8) + env.regs.set_n(RegA::A8, Reg32::Reg0, Number::from(0u8)); + + let op_code = ContractOp::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(2)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(2)).unwrap().as_ref(), data_vec.as_slice()); + } + + #[test] + fn test_ldg_success_multiple_items_correct_index() { + let data_vec1 = vec![0xC0]; + let data_vec2 = vec![0xDE]; + let mut env = TestEnv::for_genesis() + .add_global_current_op(DUMMY_GLOBAL_TYPE_A, data_vec1.clone()) + .add_global_current_op(DUMMY_GLOBAL_TYPE_A, data_vec2.clone()); + // index = 1 (for the second item) + env.regs.set_n(RegA::A8, Reg32::Reg0, Number::from(1u8)); + + let op_code = ContractOp::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(2)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(2)).unwrap().as_ref(), data_vec2.as_slice()); + } + + #[test] + fn test_ldg_fail_index_reg_none() { + let mut env = TestEnv::for_genesis().add_global_current_op(DUMMY_GLOBAL_TYPE_A, vec![1, 2]); + // Index register a8[0] is deliberately not set (i.e., None) + let op_code = ContractOp::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(2)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(2)).is_none()); + } + + #[test] + fn test_ldg_fail_state_type_missing_in_globals() { + // Globals are empty for DUMMY_GLOBAL_TYPE_A + let mut env = TestEnv::for_genesis(); + env.regs.set_n(RegA::A8, Reg32::Reg0, Number::from(0u8)); + let op_code = ContractOp::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(2)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(2)).is_none()); + } + + #[test] + fn test_ldg_fail_index_oob() { + // 1 item + let mut env = TestEnv::for_genesis().add_global_current_op(DUMMY_GLOBAL_TYPE_A, vec![1, 2]); + // Request index 1 (OOB for 1 item) + env.regs.set_n(RegA::A8, Reg32::Reg0, Number::from(1u8)); + + let op_code = ContractOp::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(2)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(2)).is_none()); + } + + // LdC (Load Global state from Contract history) Tests + #[test] + fn test_ldc_success() { + let data_vec_hist = vec![0x12, 0x34]; + let history = vec![( + GlobalOrd::genesis(0), // Dummy GlobalOrd + RevealedData::new(SmallBlob::try_from(data_vec_hist.clone()).unwrap()), + )]; + // LdC uses contract_state, not current op's state + let mut env = + TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); + // let the depth eq 0 + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(1u32)); + + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(3)).unwrap().as_ref(), data_vec_hist.as_slice()); + } + + #[test] + fn test_ldc_success_at_depth_one() { + let data_vec_hist1 = vec![0x01, 0x02]; + let data_vec_hist2 = vec![0x03, 0x04]; + let history = vec![ + ( + GlobalOrd::genesis(1), // d = 1 + RevealedData::new(SmallBlob::try_from(data_vec_hist1.clone()).unwrap()), + ), + ( + GlobalOrd::genesis(2), // d = 2 + RevealedData::new(SmallBlob::try_from(data_vec_hist2.clone()).unwrap()), + ), + ]; + let mut env = + TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(1u32)); // depth = 1 + + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(3)).unwrap().as_ref(), data_vec_hist1.as_slice()); + + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(2u32)); // depth = 2 + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(4)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(4)).unwrap().as_ref(), data_vec_hist2.as_slice()); + } + + #[test] + fn test_ldc_fail_mock_global_access_error() { + let mut env = TestEnv::for_genesis().set_mock_fail_global_access(true); + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(0u32)); + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); + // fail!() is called if contract_state.global() errors + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(3)).is_none()); + } + + #[test] + fn test_ldc_fail_depth_reg_none() { + let mut env = TestEnv::for_genesis(); // a32[0] (depth_reg) is None + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(3)).is_none()); + } + + #[test] + fn test_ldc_fail_depth_oob_in_history() { + let history = + vec![(GlobalOrd::genesis(0), RevealedData::new(SmallBlob::try_from(vec![1]).unwrap()))]; // Only 1 item + let mut env = + TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(2u32)); // Request depth 2 (OOB) + + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(3)).is_none()); + } + + #[test] + fn test_ldc_fail_depth_too_large_for_u24() { + let mut env = TestEnv::for_genesis(); + // Set depth register to a value greater than u24::MAX to test saturation/error handling + env.regs + .set_n(RegA::A32, Reg32::Reg0, Number::from(u24::MAX.to_u32() + 1)); + + let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(3)).is_none()); + } + + // LdM (Load Metadata from current op) Tests + #[test] + fn test_ldm_success() { + let meta_bytes = vec![0xDA, 0x7A]; + let mut env = + TestEnv::for_genesis().add_metadata_current_op(DUMMY_META_TYPE_A, meta_bytes.clone()); + + let op_code = ContractOp::LdM(DUMMY_META_TYPE_A, RegS::from(4)); + env.execute(op_code, true); + assert_eq!(env.regs.s16(RegS::from(4)).unwrap().as_ref(), meta_bytes.as_slice()); + } + + #[test] + fn test_ldm_fail_meta_type_missing() { + let mut env = TestEnv::for_genesis(); // metadata is empty by default + let op_code = ContractOp::LdM(DUMMY_META_TYPE_A, RegS::from(4)); + env.execute(op_code, false); + assert!(env.regs.s16(RegS::from(4)).is_none()); + } +} From d5d9c972c56513ef8b1ba92937f06b19592a2ec9 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Wed, 28 May 2025 16:11:21 +0800 Subject: [PATCH 13/21] test(tests): add sum verification tests for ContractOp::Svs functionality --- src/vm/op_contract/tests.rs | 97 ++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index f51e9350..f986d312 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -122,7 +122,7 @@ impl ContractStateAccess for MockContractState { fn global( &self, ty: GlobalStateType, - ) -> Result, UnknownGlobalStateType> { + ) -> Result, UnknownGlobalStateType> { if self.fail_global_access { return Err(UnknownGlobalStateType(ty)); } @@ -990,3 +990,98 @@ mod load_ops { assert!(env.regs.s16(RegS::from(4)).is_none()); } } + + +mod sum_verification_ops { + use aluvm::data::{MaybeNumber, Number}; + use aluvm::reg::{Reg32, RegA}; + + use super::*; + + // Svs (Sum Verify Same state) Tests + #[test] + fn test_svs_success_equal_sum() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]) // Prev sum = 30 + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 25]); // Owned sum = 30 + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, true); + } + + #[test] + fn test_svs_success_zero_sum_both_empty() { + let mut env = TestEnv::for_transition(); // No prev, no owned of this type + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, true); // 0 == 0 + } + + #[test] + fn test_svs_success_zero_sum_with_zero_values() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![0, 0]) + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![0]); + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, true); // 0 == 0 + } + + #[test] + fn test_svs_fail_unequal_sum() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]) // Prev sum = 30 + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 20]); // Owned sum = 25 + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_svs_fail_only_inputs_present() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); // No owned of this type + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); // 30 != 0 + } + + #[test] + fn test_svs_fail_only_outputs_present() { + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 25]); // No prev of this type + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); // 0 != 30 + } + + #[test] + fn test_svs_fail_input_not_fungible() { + let mut env = TestEnv::for_transition() + .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]) // Wrong type for prev + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]); + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_svs_fail_output_not_fungible() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]) + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); // Wrong type for owned + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_svs_fail_input_sum_overflow() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]) // Overflow + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]); + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_svs_fail_output_sum_overflow() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]) + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); // Overflow + let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } +} From 3e73f008589d92616f6afe5a22af02669e1e452e Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Wed, 28 May 2025 16:12:34 +0800 Subject: [PATCH 14/21] test(tests): add SaS (Sum verify Assigned state) tests for ContractOp::Sas functionality --- src/vm/op_contract/tests.rs | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index f986d312..cf52f597 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -1084,4 +1084,85 @@ mod sum_verification_ops { let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); } + + + // SaS (Sum verify Assigned state) Tests + #[test] + fn test_sas_success() { + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); // Owned sum = 30 + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(30u64)); // Expected sum + + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, true); + } + + #[test] + fn test_sas_fail_sum_reg_none() { + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); + // a64[0] is None + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sas_fail_sum_mismatch() { + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); // Owned sum = 30 + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(31u64)); // Expected sum mismatch + + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sas_fail_owned_state_type_missing() { + let mut env = TestEnv::for_transition(); // No owned state of this type + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(1u64)); // Expect non-zero sum + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + // owned sum is 0, a64[0] is 1 -> fail + env.execute(op_code, false); + } + + #[test] + fn test_sas_success_owned_state_type_missing_and_sum_reg_zero() { + let mut env = TestEnv::for_transition(); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); // Expect zero sum + + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + // owned sum is 0, a64[0] is 0 -> success + env.execute(op_code, true); + } + + #[test] + fn test_sas_fail_owned_state_not_fungible() { + let mut env = TestEnv::for_transition() + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); // Wrong type + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); + + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sas_fail_owned_state_contains_zero_value() { + // SaS specifically fails if any of the outputted fungible values are zero + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 0, 20]); // Contains 0 + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(30u64)); + + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sas_fail_owned_sum_overflow() { + let mut env = TestEnv::for_transition() + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); // Overflow + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(10u64)); // Doesn't matter due to overflow + + let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } } From fea78e69a4b93d5bcda35e9ff8b3780bd4c3db1b Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 29 May 2025 10:54:29 +0800 Subject: [PATCH 15/21] test(tests): add SpS tests for ContractOp::Sps functionality --- src/vm/op_contract/tests.rs | 71 ++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index cf52f597..49a792d7 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -1085,7 +1085,6 @@ mod sum_verification_ops { env.execute(op_code, false); } - // SaS (Sum verify Assigned state) Tests #[test] fn test_sas_success() { @@ -1165,4 +1164,74 @@ mod sum_verification_ops { let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); } + + // SpS (Sum verify Previous state) Tests + #[test] + fn test_sps_success() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); // Prev sum = 40 + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(40u64)); // Expected sum + + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, true); + } + + #[test] + fn test_sps_fail_sum_reg_none() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); + // a64[0] is None + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sps_fail_sum_mismatch() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); // Prev sum = 40 + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(41u64)); // Expected different + + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sps_fail_prev_state_type_missing() { + let mut env = TestEnv::for_transition(); // No prev state of this type + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(1u64)); // Expect non-zero sum + + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + // prev sum is 0, a64[0] is 1 -> fail + env.execute(op_code, false); + } + + #[test] + fn test_sps_success_prev_state_type_missing_and_sum_reg_zero() { + let mut env = TestEnv::for_transition(); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); // Expect zero sum + + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + // prev sum is 0, a64[0] is 0 -> success + env.execute(op_code, true); + } + + #[test] + fn test_sps_fail_prev_state_not_fungible() { + let mut env = TestEnv::for_transition() + .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); // Wrong type + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); + + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } + + #[test] + fn test_sps_fail_prev_sum_overflow() { + let mut env = TestEnv::for_transition() + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); // Overflow + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(10u64)); + + let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); + env.execute(op_code, false); + } } From 5d11e350349b5cb77b4cf00bafdec7b1a6bed746 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 29 May 2025 10:57:58 +0800 Subject: [PATCH 16/21] test(tests): add VTS operation tests for ContractOp::Vts functionality --- src/vm/op_contract/tests.rs | 118 ++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 49a792d7..69b4923f 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -1235,3 +1235,121 @@ mod sum_verification_ops { env.execute(op_code, false); } } + +mod vts_op { + use aluvm::data::ByteStr; + use amplify::Bytes64; + use secp256k1::{generate_keypair, rand, Message as SecpMessage, Secp256k1}; + + use super::*; + + #[test] + fn test_vts_success() { + let mut env = TestEnv::for_transition(); + let secp = Secp256k1::new(); + let (secret_key, public_key) = generate_keypair(&mut rand::thread_rng()); + let public_key_bytes_compact = public_key.serialize(); + env.regs + .set_s16(u4::with(0), ByteStr::with(&public_key_bytes_compact[..])); + + let transition_op_val_mut = env.transition_val.as_mut().unwrap(); + let transition_id_bytes = transition_op_val_mut.id().into_inner().into_inner(); + let message = SecpMessage::from_digest_slice(&transition_id_bytes).expect("32 bytes"); + let sig = secp.sign_ecdsa(&message, &secret_key); + transition_op_val_mut.signature = Some(Bytes64::from_array(sig.serialize_compact()).into()); + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, true); + } + + #[test] + fn test_vts_fail_no_signature_in_transition() { + let mut env = TestEnv::for_transition(); + let (_secret_key, public_key) = generate_keypair(&mut rand::thread_rng()); + let public_key_bytes_compact = public_key.serialize(); + env.regs + .set_s16(u4::with(0), ByteStr::with(&public_key_bytes_compact[..])); + // env.transition_val.as_mut().unwrap().signature is already None by dummy_transition + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, false); + } + + #[test] + fn test_vts_fail_pubkey_reg_none() { + let mut env = TestEnv::for_transition(); // s16[0] is None (pubkey reg) + let secp = Secp256k1::new(); + let (secret_key, _public_key) = generate_keypair(&mut rand::thread_rng()); + + let transition_op_val_mut = env.transition_val.as_mut().unwrap(); + let transition_id_bytes = transition_op_val_mut.id().into_inner().into_inner(); + let message = SecpMessage::from_digest_slice(&transition_id_bytes).expect("32 bytes"); + let sig = secp.sign_ecdsa(&message, &secret_key); + transition_op_val_mut.signature = Some(Bytes64::from_array(sig.serialize_compact()).into()); + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, false); + } + + #[test] + fn test_vts_fail_invalid_pubkey_format() { + let mut env = TestEnv::for_transition(); + env.regs + .set_s16(u4::with(0), ByteStr::with(&[0x00, 0x01, 0x02])); + let secp = Secp256k1::new(); + let (secret_key, _public_key) = generate_keypair(&mut rand::thread_rng()); + let transition_op_val_mut = env.transition_val.as_mut().unwrap(); + let transition_id_bytes = transition_op_val_mut.id().into_inner().into_inner(); + let message = SecpMessage::from_digest_slice(&transition_id_bytes).expect("32 bytes"); + let sig = secp.sign_ecdsa(&message, &secret_key); + transition_op_val_mut.signature = Some(Bytes64::from_array(sig.serialize_compact()).into()); + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, false); + } + + #[test] + fn test_vts_fail_invalid_signature_format() { + let mut env = TestEnv::for_transition(); + let (_secret_key, public_key) = generate_keypair(&mut rand::thread_rng()); + let public_key_bytes_compact = public_key.serialize(); + env.regs + .set_s16(u4::with(0), ByteStr::with(&public_key_bytes_compact[..])); + let transition_op_val_mut = env.transition_val.as_mut().unwrap(); + transition_op_val_mut.signature = Some(Bytes64::from_array([0u8; 64]).into()); + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, false); + } + + #[test] + fn test_vts_fail_signature_mismatch() { + let mut env = TestEnv::for_transition(); + let secp = Secp256k1::new(); + let (_sk1, pk1) = generate_keypair(&mut rand::thread_rng()); + let (sk2, _pk2) = generate_keypair(&mut rand::thread_rng()); + env.regs + .set_s16(u4::with(0), ByteStr::with(&pk1.serialize()[..])); + + let transition_op_val_mut = env.transition_val.as_mut().unwrap(); + let transition_id_bytes = transition_op_val_mut.id().into_inner().into_inner(); + let message = SecpMessage::from_digest_slice(&transition_id_bytes).expect("32 bytes"); + let sig = secp.sign_ecdsa(&message, &sk2); + transition_op_val_mut.signature = Some(Bytes64::from_array(sig.serialize_compact()).into()); + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, false); + } + + #[test] + fn test_vts_fail_on_genesis() { + let mut env = TestEnv::for_genesis(); // Operation is Genesis + let (_secret_key, public_key) = generate_keypair(&mut rand::thread_rng()); + let public_key_bytes_compact = public_key.serialize(); + env.regs + .set_s16(u4::with(0), ByteStr::with(&public_key_bytes_compact[..])); + + let op_code = ContractOp::Vts(RegS::from(0)); + env.execute(op_code, false); + } +} From 4fd3870757b3fb4ca11bfe719ae5fa5a9e4dfb44 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 29 May 2025 11:00:43 +0800 Subject: [PATCH 17/21] test(tests): add tests for ContractOp instruction set and failure handling --- src/vm/op_contract/tests.rs | 263 ++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 69b4923f..e5e33747 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -1353,3 +1353,266 @@ mod vts_op { env.execute(op_code, false); } } + +mod fail_op_test { + use aluvm::reg::{Reg32, RegA}; + + use super::*; + + #[test] + fn test_fail_op_unknown_opcode() { + let mut env = TestEnv::for_genesis(); + let unknown_opcode = 0xFF; + let op_code = ContractOp::Fail(unknown_opcode, core::marker::PhantomData); + env.execute(op_code, false); + } +} +mod instruction_set_impl { + use std::collections::BTreeSet; + + use aluvm::isa::InstructionSet; + use aluvm::reg::{Reg, Reg32, RegA, RegS}; + + use super::*; + + #[test] + fn test_isa_ids() { + assert_eq!(ContractOp::::isa_ids(), IsaSeg::with("RGB")); + } + + #[test] + fn test_src_regs() { + + + let op_ldp = ContractOp::::LdP( + DUMMY_ASSIGN_TYPE_DATA, + Reg16::Reg0, + RegS::from(0), + ); + let expected_ldp = bset![Reg::A(RegA::A16, Reg32::Reg0)]; + assert_eq!(op_ldp.src_regs(), expected_ldp); + + + let op_ldf = ContractOp::::LdF( + DUMMY_ASSIGN_TYPE_FUNGIBLE, + Reg16::Reg1, + Reg16::Reg2, + ); + let expected_ldf = bset![Reg::A(RegA::A16, Reg32::Reg1)]; + assert_eq!(op_ldf.src_regs(), expected_ldf); + + + let op_ldg = + ContractOp::::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg3, RegS::from(1)); + let expected_ldg = bset![Reg::A(RegA::A8, Reg32::Reg3)]; + assert_eq!(op_ldg.src_regs(), expected_ldg); + + + let op_ldc = + ContractOp::::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg4, RegS::from(2)); + let expected_ldc = bset![Reg::A(RegA::A32, Reg32::Reg4)]; + assert_eq!(op_ldc.src_regs(), expected_ldc); + + + let ops_empty_src = vec![ + ContractOp::::CnP(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0), + ContractOp::::CnS(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0), + ContractOp::::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0), + ContractOp::::CnC(DUMMY_GLOBAL_TYPE_A, Reg32::Reg0), + ContractOp::::LdM(DUMMY_META_TYPE_A, RegS::from(0)), + ContractOp::::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE), + ContractOp::::Vts(RegS::from(0)), + ContractOp::::Fail( + 0, + core::marker::PhantomData::, + ), + ]; + for op in ops_empty_src { + assert_eq!(op.src_regs(), BTreeSet::new(), "Failed for {:?}", op); + } + + + let ops_a64_src = vec![ + ContractOp::::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE), + ContractOp::::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE), + ]; + let expected_a64_src = bset![Reg::A(RegA::A64, Reg32::Reg0)]; + for op in ops_a64_src { + assert_eq!(op.src_regs(), expected_a64_src, "Failed for {:?}", op); + } + + } + + #[test] + fn test_dst_regs() { + // CnG + let op_cng = ContractOp::::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg5); + let expected_cng = bset![Reg::A(RegA::A8, Reg32::Reg5)]; + assert_eq!(op_cng.dst_regs(), expected_cng); + + // CnP, CnS, CnC + let ops_a16_dst = vec![ + ( + ContractOp::::CnP(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg6), + Reg32::Reg6, + ), + ( + ContractOp::::CnS(DUMMY_ASSIGN_TYPE_DATA, Reg32::Reg7), + Reg32::Reg7, + ), + (ContractOp::::CnC(DUMMY_GLOBAL_TYPE_B, Reg32::Reg8), Reg32::Reg8), + ]; + for (op, reg_idx) in ops_a16_dst { + let expected = bset![Reg::A(RegA::A16, reg_idx)]; + assert_eq!(op.dst_regs(), expected, "Failed for {:?}", op); + } + + // LdF + let op_ldf = ContractOp::::LdF( + DUMMY_ASSIGN_TYPE_FUNGIBLE, + Reg16::Reg0, + Reg16::Reg9, + ); + // Reg16::Reg9 -> Reg32::Reg9 + let expected_ldf = bset![Reg::A(RegA::A64, Reg32::Reg9)]; + assert_eq!(op_ldf.dst_regs(), expected_ldf); + + // LdG, LdS, LdP, LdC, LdM + let ops_s_dst = vec![ + ( + ContractOp::::LdG( + DUMMY_GLOBAL_TYPE_A, + Reg16::Reg0, + RegS::from(10), + ), + RegS::from(10), + ), + ( + ContractOp::::LdS( + DUMMY_ASSIGN_TYPE_DATA, + Reg16::Reg0, + RegS::from(11), + ), + RegS::from(11), + ), + ( + ContractOp::::LdP( + DUMMY_ASSIGN_TYPE_RIGHTS, + Reg16::Reg0, + RegS::from(12), + ), + RegS::from(12), + ), + ( + ContractOp::::LdC( + DUMMY_GLOBAL_TYPE_B, + Reg16::Reg0, + RegS::from(13), + ), + RegS::from(13), + ), + ( + ContractOp::::LdM(DUMMY_META_TYPE_A, RegS::from(14)), + RegS::from(14), + ), + ]; + for (op, reg_s_idx) in ops_s_dst { + let expected = bset![Reg::S(reg_s_idx)]; + assert_eq!(op.dst_regs(), expected, "Failed for {:?}", op); + } + + let ops_empty_dst = vec![ + ContractOp::::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE), + ContractOp::::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE), + ContractOp::::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE), + ContractOp::::Fail( + 0, + core::marker::PhantomData::, + ), + ]; + for op in ops_empty_dst { + assert_eq!(op.dst_regs(), BTreeSet::new(), "Failed for {:?}", op); + } + + // Vts + let op_vts = ContractOp::::Vts(RegS::from(15)); + let expected_vts = bset![Reg::S(RegS::from(15))]; + assert_eq!(op_vts.dst_regs(), expected_vts); + + } + + #[test] + fn test_complexity() { + // Cn* + assert_eq!( + ContractOp::CnP::(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0) + .complexity(), + 2 + ); + // Ld* (except LdM) + assert_eq!( + ContractOp::LdP::( + DUMMY_ASSIGN_TYPE_DATA, + Reg16::Reg0, + RegS::from(0) + ) + .complexity(), + 8 + ); + // LdM + assert_eq!( + ContractOp::LdM::(DUMMY_META_TYPE_A, RegS::from(0)).complexity(), + 6 + ); + // S*s + assert_eq!( + ContractOp::Svs::(DUMMY_ASSIGN_TYPE_FUNGIBLE).complexity(), + 20 + ); + // Vts + assert_eq!(ContractOp::Vts::(RegS::from(0)).complexity(), 512); + // Fail + assert_eq!( + ContractOp::Fail(0, core::marker::PhantomData::).complexity(), + u64::MAX + ); + + assert_eq!( + ContractOp::LdF::( + DUMMY_ASSIGN_TYPE_FUNGIBLE, + Reg16::Reg0, + Reg16::Reg0 + ) + .complexity(), + 8 + ); + assert_eq!( + ContractOp::LdS::( + DUMMY_ASSIGN_TYPE_DATA, + Reg16::Reg0, + RegS::from(0) + ) + .complexity(), + 8 + ); + assert_eq!( + ContractOp::LdG::(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(0)) + .complexity(), + 8 + ); + assert_eq!( + ContractOp::LdC::(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(0)) + .complexity(), + 8 + ); + + assert_eq!( + ContractOp::Sas::(DUMMY_ASSIGN_TYPE_FUNGIBLE).complexity(), + 20 + ); + assert_eq!( + ContractOp::Sps::(DUMMY_ASSIGN_TYPE_FUNGIBLE).complexity(), + 20 + ); + } +} From c9d82443ac13dcc1967b69652b3e9e5cf2587c7b Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 29 May 2025 16:36:33 +0800 Subject: [PATCH 18/21] test(tests): add bytecode implementation tests for ContractOp --- src/vm/op_contract/tests.rs | 204 +++++++++++++++++++++++++++++++++--- 1 file changed, 187 insertions(+), 17 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index e5e33747..0b96a07c 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -991,7 +991,6 @@ mod load_ops { } } - mod sum_verification_ops { use aluvm::data::{MaybeNumber, Number}; use aluvm::reg::{Reg32, RegA}; @@ -1261,7 +1260,7 @@ mod vts_op { let op_code = ContractOp::Vts(RegS::from(0)); env.execute(op_code, true); } - + #[test] fn test_vts_fail_no_signature_in_transition() { let mut env = TestEnv::for_transition(); @@ -1295,7 +1294,7 @@ mod vts_op { fn test_vts_fail_invalid_pubkey_format() { let mut env = TestEnv::for_transition(); env.regs - .set_s16(u4::with(0), ByteStr::with(&[0x00, 0x01, 0x02])); + .set_s16(u4::with(0), ByteStr::with(&[0x00, 0x01, 0x02])); let secp = Secp256k1::new(); let (secret_key, _public_key) = generate_keypair(&mut rand::thread_rng()); let transition_op_val_mut = env.transition_val.as_mut().unwrap(); @@ -1326,19 +1325,19 @@ mod vts_op { fn test_vts_fail_signature_mismatch() { let mut env = TestEnv::for_transition(); let secp = Secp256k1::new(); - let (_sk1, pk1) = generate_keypair(&mut rand::thread_rng()); - let (sk2, _pk2) = generate_keypair(&mut rand::thread_rng()); + let (_sk1, pk1) = generate_keypair(&mut rand::thread_rng()); + let (sk2, _pk2) = generate_keypair(&mut rand::thread_rng()); env.regs .set_s16(u4::with(0), ByteStr::with(&pk1.serialize()[..])); let transition_op_val_mut = env.transition_val.as_mut().unwrap(); let transition_id_bytes = transition_op_val_mut.id().into_inner().into_inner(); let message = SecpMessage::from_digest_slice(&transition_id_bytes).expect("32 bytes"); - let sig = secp.sign_ecdsa(&message, &sk2); + let sig = secp.sign_ecdsa(&message, &sk2); transition_op_val_mut.signature = Some(Bytes64::from_array(sig.serialize_compact()).into()); let op_code = ContractOp::Vts(RegS::from(0)); - env.execute(op_code, false); + env.execute(op_code, false); } #[test] @@ -1382,8 +1381,6 @@ mod instruction_set_impl { #[test] fn test_src_regs() { - - let op_ldp = ContractOp::::LdP( DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, @@ -1392,7 +1389,6 @@ mod instruction_set_impl { let expected_ldp = bset![Reg::A(RegA::A16, Reg32::Reg0)]; assert_eq!(op_ldp.src_regs(), expected_ldp); - let op_ldf = ContractOp::::LdF( DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg16::Reg1, @@ -1401,19 +1397,16 @@ mod instruction_set_impl { let expected_ldf = bset![Reg::A(RegA::A16, Reg32::Reg1)]; assert_eq!(op_ldf.src_regs(), expected_ldf); - let op_ldg = ContractOp::::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg3, RegS::from(1)); let expected_ldg = bset![Reg::A(RegA::A8, Reg32::Reg3)]; assert_eq!(op_ldg.src_regs(), expected_ldg); - let op_ldc = ContractOp::::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg4, RegS::from(2)); let expected_ldc = bset![Reg::A(RegA::A32, Reg32::Reg4)]; assert_eq!(op_ldc.src_regs(), expected_ldc); - let ops_empty_src = vec![ ContractOp::::CnP(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0), ContractOp::::CnS(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0), @@ -1431,7 +1424,6 @@ mod instruction_set_impl { assert_eq!(op.src_regs(), BTreeSet::new(), "Failed for {:?}", op); } - let ops_a64_src = vec![ ContractOp::::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE), ContractOp::::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE), @@ -1440,7 +1432,6 @@ mod instruction_set_impl { for op in ops_a64_src { assert_eq!(op.src_regs(), expected_a64_src, "Failed for {:?}", op); } - } #[test] @@ -1473,7 +1464,7 @@ mod instruction_set_impl { Reg16::Reg0, Reg16::Reg9, ); - // Reg16::Reg9 -> Reg32::Reg9 + // Reg16::Reg9 -> Reg32::Reg9 let expected_ldf = bset![Reg::A(RegA::A64, Reg32::Reg9)]; assert_eq!(op_ldf.dst_regs(), expected_ldf); @@ -1538,7 +1529,6 @@ mod instruction_set_impl { let op_vts = ContractOp::::Vts(RegS::from(15)); let expected_vts = bset![Reg::S(RegS::from(15))]; assert_eq!(op_vts.dst_regs(), expected_vts); - } #[test] @@ -1616,3 +1606,183 @@ mod instruction_set_impl { ); } } + +mod bytecode_impl { + use std::fmt::Debug; + + use aluvm::isa::Bytecode; + use aluvm::library::{CodeEofError, Cursor, LibSeg, Read, Write, WriteError}; + + use super::*; + use crate::vm::{ContractOp, ContractStateAccess}; + + const TEST_BUFFER_SIZE: usize = 1024; + + fn roundtrip_test_raw(op: ContractOp) + where ContractOp: Bytecode + PartialEq + Debug { + let libs = LibSeg::default(); + + let mut encoded_len_bytecode: u16; + let final_data_segment_content: ByteStr; + let mut actual_encoded_bytecode = [0u8; TEST_BUFFER_SIZE]; + + let instr_byte_expected = op.instr_byte(); + println!("Encoding op: {:?}, expected instr_byte: {:#04x}", op, instr_byte_expected); + { + let data_segment_for_encoding = ByteStr::default(); + let mut writer_cursor = + Cursor::with(&mut actual_encoded_bytecode[..], data_segment_for_encoding, &libs); + op.encode(&mut writer_cursor).expect("Failed to encode op"); + let final_byte_pos = writer_cursor.pos(); + let final_bit_pos = writer_cursor.offset().0; + + encoded_len_bytecode = + if final_bit_pos as u8 > 0 { final_byte_pos + 1 } else { final_byte_pos }; + + encoded_len_bytecode = encoded_len_bytecode.min(TEST_BUFFER_SIZE as u16); + + final_data_segment_content = writer_cursor.into_data_segment(); + + println!( + "Encoded bytecode ({} bytes): {:02x?}", + encoded_len_bytecode, + &actual_encoded_bytecode[..encoded_len_bytecode as usize] + ); + if encoded_len_bytecode > 0 { + println!("First encoded byte: {:#04x}", actual_encoded_bytecode[0]); + assert_eq!( + actual_encoded_bytecode[0], instr_byte_expected, + "Mismatch of first byte after encoding" + ); + } + println!("Encoded data segment len: {}", final_data_segment_content.len()); + } + + { + let mut reader_cursor = Cursor::with( + &actual_encoded_bytecode[..encoded_len_bytecode as usize], + final_data_segment_content.as_ref(), + &libs, + ); + + if encoded_len_bytecode > 0 { + let peeked_byte = reader_cursor + .peek_u8() + .expect("Failed to peek byte for decode"); + println!("Byte to be decoded by ContractOp::decode: {:#04x}", peeked_byte); + } + + let decoded_op = match ContractOp::::decode(&mut reader_cursor) { + Ok(d_op) => { + println!("Successfully decoded to: {:?}", d_op); + d_op + } + Err(e) => { + panic!("Failed to decode op: {:?}, error: {:?}", op, e); + } + }; + + assert_eq!(op, decoded_op, "Raw roundtrip failed for op: {:?}", op); + } + } + #[test] + fn test_instr_range() { + let range = ContractOp::::instr_range(); + assert_eq!(range, INSTR_CONTRACT_FROM..=INSTR_CONTRACT_TO); + } + + #[test] + fn test_instr_byte_and_roundtrip() { + // CnP + let op_cnp = ContractOp::::CnP(DUMMY_ASSIGN_TYPE_FUNGIBLE, Reg32::Reg0); + assert_eq!(op_cnp.instr_byte(), INSTR_CNP); + roundtrip_test_raw(op_cnp); + + // CnS + let op_cns = ContractOp::::CnS(DUMMY_ASSIGN_TYPE_DATA, Reg32::Reg1); + assert_eq!(op_cns.instr_byte(), INSTR_CNS); + roundtrip_test_raw(op_cns); + + // CnG + let op_cng = ContractOp::::CnG(DUMMY_GLOBAL_TYPE_A, Reg32::Reg2); + assert_eq!(op_cng.instr_byte(), INSTR_CNG); + roundtrip_test_raw(op_cng); + + // CnC + let op_cnc = ContractOp::::CnC(DUMMY_GLOBAL_TYPE_B, Reg32::Reg3); + assert_eq!(op_cnc.instr_byte(), INSTR_CNC); + roundtrip_test_raw(op_cnc); + + // LdP + let op_ldp = ContractOp::::LdP( + DUMMY_ASSIGN_TYPE_DATA, + Reg16::Reg0, + RegS::from(1), + ); + assert_eq!(op_ldp.instr_byte(), INSTR_LDP); + roundtrip_test_raw(op_ldp); + + // LdS + let op_lds = ContractOp::::LdS( + DUMMY_ASSIGN_TYPE_FUNGIBLE, + Reg16::Reg2, + RegS::from(3), + ); + assert_eq!(op_lds.instr_byte(), INSTR_LDS); + roundtrip_test_raw(op_lds); + + // LdF + let op_ldf = ContractOp::::LdF( + DUMMY_ASSIGN_TYPE_UNUSED, + Reg16::Reg4, + Reg16::Reg5, + ); + assert_eq!(op_ldf.instr_byte(), INSTR_LDF); + roundtrip_test_raw(op_ldf); + + // LdG + let op_ldg = + ContractOp::::LdG(DUMMY_GLOBAL_TYPE_A, Reg16::Reg6, RegS::from(7)); + assert_eq!(op_ldg.instr_byte(), INSTR_LDG); + roundtrip_test_raw(op_ldg); + + // LdC + let op_ldc = ContractOp::::LdC( + DUMMY_GLOBAL_TYPE_UNUSED, + Reg16::Reg8, + RegS::from(9), + ); + assert_eq!(op_ldc.instr_byte(), INSTR_LDC); + roundtrip_test_raw(op_ldc); + + // LdM + let op_ldm = ContractOp::::LdM(DUMMY_META_TYPE_A, RegS::from(10)); + assert_eq!(op_ldm.instr_byte(), INSTR_LDM); + roundtrip_test_raw(op_ldm); + + // Svs + let op_svs = ContractOp::::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); + assert_eq!(op_svs.instr_byte(), INSTR_SVS); + roundtrip_test_raw(op_svs); + + // Sas + let op_sas = ContractOp::::Sas(DUMMY_ASSIGN_TYPE_DATA); + assert_eq!(op_sas.instr_byte(), INSTR_SAS); + roundtrip_test_raw(op_sas); + + // Sps + let op_sps = ContractOp::::Sps(DUMMY_ASSIGN_TYPE_UNUSED); + assert_eq!(op_sps.instr_byte(), INSTR_SPS); + roundtrip_test_raw(op_sps); + + // Vts + let op_vts = ContractOp::::Vts(RegS::from(11)); + assert_eq!(op_vts.instr_byte(), INSTR_VTS); + roundtrip_test_raw(op_vts); + + // Fail + let op_fail = ContractOp::Fail(0xF0, core::marker::PhantomData::); + assert_eq!(op_fail.instr_byte(), 0xF0); + roundtrip_test_raw(op_fail); + } +} From fc07bd99cb38801f44dd0056179e0d0dfa147d3c Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 29 May 2025 16:51:17 +0800 Subject: [PATCH 19/21] refactor(tests): clean up imports and simplify function signatures in contract tests --- src/vm/op_contract/tests.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 0b96a07c..512b5146 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -1,20 +1,17 @@ use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeMap, BTreeSet}; -use std::iter; use std::num::NonZeroU32; use std::rc::Rc; -use aluvm::data::{ByteStr, MaybeNumber, Number}; +use aluvm::data::{ByteStr, MaybeNumber}; use aluvm::isa::ExecStep; use aluvm::library::LibSite; -use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegF, RegR, RegS}; +use aluvm::reg::{CoreRegs, Reg16, Reg32, RegA, RegS}; use amplify::confinement::{NonEmptyOrdSet, NonEmptyVec, SmallBlob, SmallOrdMap}; use amplify::num::u24; -use amplify::{Bytes64, Wrapper}; +use amplify::Wrapper; use bp::{Outpoint, Txid}; -use commit_verify::StrictHash; -use secp256k1::{generate_keypair, rand, Secp256k1}; use strict_encoding::StrictDumb; use super::*; @@ -27,7 +24,7 @@ use crate::vm::{ use crate::{ schema, seal, Assign, AssignmentType, Assignments, BundleId, ChainNet, ContractId, Ffv, FungibleState, Genesis, GenesisSeal, GlobalState, GlobalStateType, GraphSeal, Identity, Inputs, - MetaType, MetaValue, Metadata, OpId, Opout, RevealedData, RevealedValue, SchemaId, + MetaType, MetaValue, Metadata, Opout, RevealedData, RevealedValue, SchemaId, SealClosingStrategy, Signature, Transition, TypedAssigns, }; @@ -227,11 +224,11 @@ fn exec_op_and_assert_st0( } } -fn create_vm_context<'op, S: ContractStateAccess>( +fn create_vm_context( contract_id: ContractId, - op_info: OpInfo<'op>, + op_info: OpInfo<'_>, contract_state: Rc>, -) -> VmContext<'op, S> { +) -> VmContext<'_, S> { VmContext { contract_id, op_info, @@ -438,7 +435,7 @@ impl TestEnv { self } - fn set_mock_fail_global_access(mut self, fail: bool) -> Self { + fn set_mock_fail_global_access(self, fail: bool) -> Self { self.mock_contract_state_rc.borrow_mut().fail_global_access = fail; self } @@ -992,7 +989,7 @@ mod load_ops { } mod sum_verification_ops { - use aluvm::data::{MaybeNumber, Number}; + use aluvm::data::Number; use aluvm::reg::{Reg32, RegA}; use super::*; @@ -1294,7 +1291,7 @@ mod vts_op { fn test_vts_fail_invalid_pubkey_format() { let mut env = TestEnv::for_transition(); env.regs - .set_s16(u4::with(0), ByteStr::with(&[0x00, 0x01, 0x02])); + .set_s16(u4::with(0), ByteStr::with([0x00, 0x01, 0x02])); let secp = Secp256k1::new(); let (secret_key, _public_key) = generate_keypair(&mut rand::thread_rng()); let transition_op_val_mut = env.transition_val.as_mut().unwrap(); @@ -1354,7 +1351,6 @@ mod vts_op { } mod fail_op_test { - use aluvm::reg::{Reg32, RegA}; use super::*; @@ -1611,7 +1607,7 @@ mod bytecode_impl { use std::fmt::Debug; use aluvm::isa::Bytecode; - use aluvm::library::{CodeEofError, Cursor, LibSeg, Read, Write, WriteError}; + use aluvm::library::{Cursor, LibSeg, Read}; use super::*; use crate::vm::{ContractOp, ContractStateAccess}; From 0fd93116566e82953181014d27cd4b3babe2da18 Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 29 May 2025 17:12:52 +0800 Subject: [PATCH 20/21] refactor(tests): remove unused function in contract tests --- src/vm/op_contract/tests.rs | 134 +++++++++++++++++------------------- 1 file changed, 62 insertions(+), 72 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index 512b5146..cbb66af0 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -95,7 +95,7 @@ impl<'a> GlobalStateIter for MockGlobalStateIter<'a> { panic!("MockGlobalStateIter cannot be reset to depth 0"); } - let target_idx_for_last_0based = depth_1based.to_usize() - 1; // Convert 1-based to 0-indexed + let target_idx_for_last_0based = depth_1based.to_usize() - 1; if target_idx_for_last_0based < self.total_size { // The item at target_idx_for_last_0based should become the "last" item. @@ -108,7 +108,7 @@ impl<'a> GlobalStateIter for MockGlobalStateIter<'a> { // e.g., total_size=1. reset(depth_1based=2). target_idx_for_last_0based=1. // 1 < 1 is false. Comes here. self.last_item_cache = None; - self.current_pos = self.total_size; // Exhausted + self.current_pos = self.total_size; } } } @@ -325,14 +325,6 @@ impl TestEnv { } } - fn set_contract_id(mut self, contract_id: ContractId) -> Self { - self.contract_id = contract_id; - if let Some(t) = self.transition_val.as_mut() { - t.contract_id = contract_id; - } - self - } - fn add_global_current_op(mut self, global_type: GlobalStateType, data: Vec) -> Self { let revealed_data = RevealedData::new(SmallBlob::try_from(data).unwrap()); if let Some(g) = self.genesis_val.as_mut() { @@ -483,7 +475,7 @@ mod count_ops { use super::*; - // CnP Tests (Count Previous state) + // CnP (Count Previous state) #[test] fn test_cnp_found_multiple() { let mut env = TestEnv::for_transition().add_prev_assign_fungible( @@ -513,7 +505,7 @@ mod count_ops { assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::none()); } - // CnS Tests (Count Same [owned] state) + // CnS (Count Same [owned] state) #[test] fn test_cns_found_multiple_transition() { let mut env = TestEnv::for_transition().add_owned_assign_structured( @@ -542,10 +534,7 @@ mod count_ops { assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::none()); } - // CnS for Genesis (needs specific handling for GenesisSeal if we strictly test owned - // assignments) For simplicity, if `add_owned_assign_...` for genesis is too complex due to - // seal types, we can test with an empty owned assignment for genesis, which is a valid - // case. + // CnS for Genesis #[test] fn test_cns_genesis_type_not_found() { let mut env = TestEnv::for_genesis(); @@ -554,7 +543,7 @@ mod count_ops { assert_eq!(env.regs.get_n(RegA::A16, Reg32::Reg0), MaybeNumber::none()); } - // CnG Tests (Count Next [current op's] Global state) + // CnG (Count Next [current op's] Global state) #[test] fn test_cng_found_multiple() { let mut env = TestEnv::for_genesis() @@ -581,7 +570,7 @@ mod count_ops { assert_eq!(env.regs.get_n(RegA::A8, Reg32::Reg0), MaybeNumber::none()); } - // CnC Tests (Count Contract's [historical] Global state) + // CnC (Count Contract's [historical] Global state) #[test] fn test_cnc_found_multiple() { let history = vec![ @@ -631,13 +620,13 @@ mod load_ops { use super::*; - // LdP (Load Previous structured state) Tests + // LdP (Load Previous structured state) #[test] fn test_ldp_success_revealed() { let data_vec = vec![0xAB, 0xCD, 0xEF]; let mut env = TestEnv::for_transition() .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![data_vec.clone()]); - env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); // index = 0 + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); env.execute(op_code, true); @@ -670,7 +659,7 @@ mod load_ops { #[test] fn test_ldp_fail_index_reg_none() { - let mut env = TestEnv::for_transition(); // a16[0] (index_reg) is None + let mut env = TestEnv::for_transition(); let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); env.execute(op_code, false); assert!(env.regs.s16(RegS::from(0)).is_none()); @@ -678,7 +667,7 @@ mod load_ops { #[test] fn test_ldp_fail_state_type_missing_in_prev() { - let mut env = TestEnv::for_transition(); // prev_assignments is empty + let mut env = TestEnv::for_transition(); env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); let op_code = ContractOp::LdP(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(0)); env.execute(op_code, false); @@ -700,7 +689,7 @@ mod load_ops { #[test] fn test_ldp_fail_wrong_state_type_in_prev() { - // prev_state has DUMMY_ASSIGN_TYPE_DATA, but it's Fungible, not Structured for LdP + // prev_state has DUMMY_ASSIGN_TYPE_DATA, but it's Fungible, not structured for LdP let mut env = TestEnv::for_transition().add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_DATA, vec![100]); env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(0u16)); @@ -710,7 +699,7 @@ mod load_ops { assert!(env.regs.s16(RegS::from(0)).is_none()); } - // LdS (Load Same/owned structured state) Tests + // LdS (Load Same/owned structured state) #[test] fn test_lds_success_revealed() { let data_vec = vec![0xBE, 0xEF]; @@ -725,7 +714,7 @@ mod load_ops { #[test] fn test_lds_fail_index_reg_none() { - let mut env = TestEnv::for_transition(); // a16[0] is None + let mut env = TestEnv::for_transition(); let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); env.execute(op_code, false); assert!(env.regs.s16(RegS::from(1)).is_none()); @@ -744,7 +733,7 @@ mod load_ops { fn test_lds_fail_index_oob() { let mut env = TestEnv::for_transition() .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_DATA, vec![vec![1]]); - env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(1u16)); // Request index 1 + env.regs.set_n(RegA::A16, Reg32::Reg0, Number::from(1u16)); let op_code = ContractOp::LdS(DUMMY_ASSIGN_TYPE_DATA, Reg16::Reg0, RegS::from(1)); env.execute(op_code, false); assert!(env.regs.s16(RegS::from(1)).is_none()); @@ -761,7 +750,7 @@ mod load_ops { assert!(env.regs.s16(RegS::from(1)).is_none()); } - // LdF (Load Same/owned Fungible state) Tests + // LdF (Load Same/owned Fungible state) #[test] fn test_ldf_success_revealed() { let fungible_val = 777u64; @@ -817,7 +806,7 @@ mod load_ops { assert!(env.regs.get_n(RegA::A64, Reg32::Reg0).is_none()); } - // LdG (Load Global state from current op) Tests + // LdG (Load Global state from current op) #[test] fn test_ldg_success() { let data_vec = vec![0xC0, 0xDE]; @@ -877,7 +866,7 @@ mod load_ops { assert!(env.regs.s16(RegS::from(2)).is_none()); } - // LdC (Load Global state from Contract history) Tests + // LdC (Load Global state from Contract history) #[test] fn test_ldc_success() { let data_vec_hist = vec![0x12, 0x34]; @@ -912,13 +901,13 @@ mod load_ops { ]; let mut env = TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); - env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(1u32)); // depth = 1 + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(1u32)); let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); env.execute(op_code, true); assert_eq!(env.regs.s16(RegS::from(3)).unwrap().as_ref(), data_vec_hist1.as_slice()); - env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(2u32)); // depth = 2 + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(2u32)); let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(4)); env.execute(op_code, true); assert_eq!(env.regs.s16(RegS::from(4)).unwrap().as_ref(), data_vec_hist2.as_slice()); @@ -936,7 +925,7 @@ mod load_ops { #[test] fn test_ldc_fail_depth_reg_none() { - let mut env = TestEnv::for_genesis(); // a32[0] (depth_reg) is None + let mut env = TestEnv::for_genesis(); let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); env.execute(op_code, false); assert!(env.regs.s16(RegS::from(3)).is_none()); @@ -945,10 +934,10 @@ mod load_ops { #[test] fn test_ldc_fail_depth_oob_in_history() { let history = - vec![(GlobalOrd::genesis(0), RevealedData::new(SmallBlob::try_from(vec![1]).unwrap()))]; // Only 1 item + vec![(GlobalOrd::genesis(0), RevealedData::new(SmallBlob::try_from(vec![1]).unwrap()))]; let mut env = TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); - env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(2u32)); // Request depth 2 (OOB) + env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(2u32)); let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); env.execute(op_code, false); @@ -959,6 +948,7 @@ mod load_ops { fn test_ldc_fail_depth_too_large_for_u24() { let mut env = TestEnv::for_genesis(); // Set depth register to a value greater than u24::MAX to test saturation/error handling + // NOTE: depth starts from 1, not 0 !!! env.regs .set_n(RegA::A32, Reg32::Reg0, Number::from(u24::MAX.to_u32() + 1)); @@ -967,7 +957,7 @@ mod load_ops { assert!(env.regs.s16(RegS::from(3)).is_none()); } - // LdM (Load Metadata from current op) Tests + // LdM (Load Metadata from current op) #[test] fn test_ldm_success() { let meta_bytes = vec![0xDA, 0x7A]; @@ -981,7 +971,7 @@ mod load_ops { #[test] fn test_ldm_fail_meta_type_missing() { - let mut env = TestEnv::for_genesis(); // metadata is empty by default + let mut env = TestEnv::for_genesis(); let op_code = ContractOp::LdM(DUMMY_META_TYPE_A, RegS::from(4)); env.execute(op_code, false); assert!(env.regs.s16(RegS::from(4)).is_none()); @@ -994,21 +984,21 @@ mod sum_verification_ops { use super::*; - // Svs (Sum Verify Same state) Tests + // Svs (Sum Verify Same state) #[test] fn test_svs_success_equal_sum() { let mut env = TestEnv::for_transition() .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]) // Prev sum = 30 - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 25]); // Owned sum = 30 + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 25]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, true); } #[test] fn test_svs_success_zero_sum_both_empty() { - let mut env = TestEnv::for_transition(); // No prev, no owned of this type + let mut env = TestEnv::for_transition(); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); - env.execute(op_code, true); // 0 == 0 + env.execute(op_code, true); } #[test] @@ -1017,14 +1007,14 @@ mod sum_verification_ops { .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![0, 0]) .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![0]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); - env.execute(op_code, true); // 0 == 0 + env.execute(op_code, true); } #[test] fn test_svs_fail_unequal_sum() { let mut env = TestEnv::for_transition() .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]) // Prev sum = 30 - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 20]); // Owned sum = 25 + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 20]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); } @@ -1032,17 +1022,17 @@ mod sum_verification_ops { #[test] fn test_svs_fail_only_inputs_present() { let mut env = TestEnv::for_transition() - .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); // No owned of this type + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); - env.execute(op_code, false); // 30 != 0 + env.execute(op_code, false); } #[test] fn test_svs_fail_only_outputs_present() { let mut env = TestEnv::for_transition() - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 25]); // No prev of this type + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![5, 25]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); - env.execute(op_code, false); // 0 != 30 + env.execute(op_code, false); } #[test] @@ -1058,7 +1048,7 @@ mod sum_verification_ops { fn test_svs_fail_output_not_fungible() { let mut env = TestEnv::for_transition() .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]) - .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); // Wrong type for owned + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); } @@ -1076,17 +1066,17 @@ mod sum_verification_ops { fn test_svs_fail_output_sum_overflow() { let mut env = TestEnv::for_transition() .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10]) - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); // Overflow + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); let op_code = ContractOp::Svs(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); } - // SaS (Sum verify Assigned state) Tests + // SaS (Sum verify Assigned state) #[test] fn test_sas_success() { let mut env = TestEnv::for_transition() - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); // Owned sum = 30 - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(30u64)); // Expected sum + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(30u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, true); @@ -1104,8 +1094,8 @@ mod sum_verification_ops { #[test] fn test_sas_fail_sum_mismatch() { let mut env = TestEnv::for_transition() - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); // Owned sum = 30 - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(31u64)); // Expected sum mismatch + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 20]); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(31u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); @@ -1113,8 +1103,8 @@ mod sum_verification_ops { #[test] fn test_sas_fail_owned_state_type_missing() { - let mut env = TestEnv::for_transition(); // No owned state of this type - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(1u64)); // Expect non-zero sum + let mut env = TestEnv::for_transition(); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(1u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); // owned sum is 0, a64[0] is 1 -> fail env.execute(op_code, false); @@ -1123,7 +1113,7 @@ mod sum_verification_ops { #[test] fn test_sas_success_owned_state_type_missing_and_sum_reg_zero() { let mut env = TestEnv::for_transition(); - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); // Expect zero sum + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); // owned sum is 0, a64[0] is 0 -> success @@ -1133,7 +1123,7 @@ mod sum_verification_ops { #[test] fn test_sas_fail_owned_state_not_fungible() { let mut env = TestEnv::for_transition() - .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); // Wrong type + .add_owned_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); @@ -1144,7 +1134,7 @@ mod sum_verification_ops { fn test_sas_fail_owned_state_contains_zero_value() { // SaS specifically fails if any of the outputted fungible values are zero let mut env = TestEnv::for_transition() - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 0, 20]); // Contains 0 + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![10, 0, 20]); env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(30u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); @@ -1154,19 +1144,19 @@ mod sum_verification_ops { #[test] fn test_sas_fail_owned_sum_overflow() { let mut env = TestEnv::for_transition() - .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); // Overflow - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(10u64)); // Doesn't matter due to overflow + .add_owned_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(10u64)); let op_code = ContractOp::Sas(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); } - // SpS (Sum verify Previous state) Tests + // SpS (Sum verify Previous state) #[test] fn test_sps_success() { let mut env = TestEnv::for_transition() - .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); // Prev sum = 40 - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(40u64)); // Expected sum + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(40u64)); let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, true); @@ -1184,8 +1174,8 @@ mod sum_verification_ops { #[test] fn test_sps_fail_sum_mismatch() { let mut env = TestEnv::for_transition() - .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); // Prev sum = 40 - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(41u64)); // Expected different + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![15, 25]); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(41u64)); let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); env.execute(op_code, false); @@ -1193,8 +1183,8 @@ mod sum_verification_ops { #[test] fn test_sps_fail_prev_state_type_missing() { - let mut env = TestEnv::for_transition(); // No prev state of this type - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(1u64)); // Expect non-zero sum + let mut env = TestEnv::for_transition(); + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(1u64)); let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); // prev sum is 0, a64[0] is 1 -> fail @@ -1204,7 +1194,7 @@ mod sum_verification_ops { #[test] fn test_sps_success_prev_state_type_missing_and_sum_reg_zero() { let mut env = TestEnv::for_transition(); - env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); // Expect zero sum + env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); // prev sum is 0, a64[0] is 0 -> success @@ -1214,7 +1204,7 @@ mod sum_verification_ops { #[test] fn test_sps_fail_prev_state_not_fungible() { let mut env = TestEnv::for_transition() - .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); // Wrong type + .add_prev_assign_structured(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![vec![1]]); env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(0u64)); let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); @@ -1224,7 +1214,7 @@ mod sum_verification_ops { #[test] fn test_sps_fail_prev_sum_overflow() { let mut env = TestEnv::for_transition() - .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); // Overflow + .add_prev_assign_fungible(DUMMY_ASSIGN_TYPE_FUNGIBLE, vec![u64::MAX, 1]); env.regs.set_n(RegA::A64, Reg32::Reg0, Number::from(10u64)); let op_code = ContractOp::Sps(DUMMY_ASSIGN_TYPE_FUNGIBLE); @@ -1273,7 +1263,7 @@ mod vts_op { #[test] fn test_vts_fail_pubkey_reg_none() { - let mut env = TestEnv::for_transition(); // s16[0] is None (pubkey reg) + let mut env = TestEnv::for_transition(); let secp = Secp256k1::new(); let (secret_key, _public_key) = generate_keypair(&mut rand::thread_rng()); @@ -1339,7 +1329,7 @@ mod vts_op { #[test] fn test_vts_fail_on_genesis() { - let mut env = TestEnv::for_genesis(); // Operation is Genesis + let mut env = TestEnv::for_genesis(); let (_secret_key, public_key) = generate_keypair(&mut rand::thread_rng()); let public_key_bytes_compact = public_key.serialize(); env.regs From 812accce331aa45dcd8d4b7cf7890bf5d5654a2b Mon Sep 17 00:00:00 2001 From: astral-bitlight Date: Thu, 5 Jun 2025 12:10:40 +0800 Subject: [PATCH 21/21] refactor(tests): improve variable naming and update comments in load_ops tests --- src/vm/op_contract/tests.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/vm/op_contract/tests.rs b/src/vm/op_contract/tests.rs index cbb66af0..33399382 100644 --- a/src/vm/op_contract/tests.rs +++ b/src/vm/op_contract/tests.rs @@ -95,17 +95,17 @@ impl<'a> GlobalStateIter for MockGlobalStateIter<'a> { panic!("MockGlobalStateIter cannot be reset to depth 0"); } - let target_idx_for_last_0based = depth_1based.to_usize() - 1; + let tgt_internal_idx = depth_1based.to_usize() - 1; - if target_idx_for_last_0based < self.total_size { - // The item at target_idx_for_last_0based should become the "last" item. - let (ord_ref, data_ref) = &self.data[target_idx_for_last_0based]; + if tgt_internal_idx < self.total_size { + // The item at internal_idx should become the "last" item. + let (ord_ref, data_ref) = &self.data[tgt_internal_idx]; self.last_item_cache = Some((*ord_ref, data_ref)); // The next call to prev() should yield the item *after* this one. - self.current_pos = target_idx_for_last_0based + 1; + self.current_pos = tgt_internal_idx + 1; } else { // Requested 1-based depth is out of bounds. - // e.g., total_size=1. reset(depth_1based=2). target_idx_for_last_0based=1. + // e.g., total_size=1. reset(depth_1based=2). internal_idx=1. // 1 < 1 is false. Comes here. self.last_item_cache = None; self.current_pos = self.total_size; @@ -619,6 +619,7 @@ mod load_ops { use bp::seals::SecretSeal; use super::*; + use crate::{OpId, TransitionType}; // LdP (Load Previous structured state) #[test] @@ -877,7 +878,7 @@ mod load_ops { // LdC uses contract_state, not current op's state let mut env = TestEnv::for_genesis().set_mock_global_state_history(DUMMY_GLOBAL_TYPE_A, history); - // let the depth eq 0 + // let the depth eq 1 env.regs.set_n(RegA::A32, Reg32::Reg0, Number::from(1u32)); let op_code = ContractOp::LdC(DUMMY_GLOBAL_TYPE_A, Reg16::Reg0, RegS::from(3)); @@ -886,16 +887,24 @@ mod load_ops { } #[test] - fn test_ldc_success_at_depth_one() { + fn test_ldc_success_at_depth_one_and_two() { let data_vec_hist1 = vec![0x01, 0x02]; let data_vec_hist2 = vec![0x03, 0x04]; let history = vec![ ( - GlobalOrd::genesis(1), // d = 1 + // d = 1 + GlobalOrd::genesis(1), RevealedData::new(SmallBlob::try_from(data_vec_hist1.clone()).unwrap()), ), ( - GlobalOrd::genesis(2), // d = 2 + // d = 2 + GlobalOrd::transition( + OpId::from([0; 32]), + 0, + TransitionType::with(0), + 0, + WitnessOrd::Ignored, + ), RevealedData::new(SmallBlob::try_from(data_vec_hist2.clone()).unwrap()), ), ]; @@ -948,7 +957,6 @@ mod load_ops { fn test_ldc_fail_depth_too_large_for_u24() { let mut env = TestEnv::for_genesis(); // Set depth register to a value greater than u24::MAX to test saturation/error handling - // NOTE: depth starts from 1, not 0 !!! env.regs .set_n(RegA::A32, Reg32::Reg0, Number::from(u24::MAX.to_u32() + 1));