1use alloc::collections::BTreeMap;
10use alloc::string::String;
11use alloc::vec::Vec;
12use serde::{Deserialize, Serialize};
13
14use crate::seal::SealRef;
15use crate::state::{GlobalState, Metadata, OwnedState, StateAssignment, StateRef, StateTypeId};
16
17#[derive(Debug)]
19#[allow(missing_docs)]
20pub enum VMError {
21 InvalidBytecode(String),
23 ExecutionLimitExceeded { max_steps: u64, actual_steps: u64 },
25 StateNotFound { state_ref: StateRef },
27 InconsistentOutput(String),
29 InvalidSignature(String),
31 SealReplay { seal: SealRef },
33 SchemaViolation(String),
35 ExecutionError(String),
37}
38
39impl core::fmt::Display for VMError {
40 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41 match self {
42 VMError::InvalidBytecode(msg) => write!(f, "Invalid bytecode: {}", msg),
43 VMError::ExecutionLimitExceeded {
44 max_steps,
45 actual_steps,
46 } => {
47 write!(
48 f,
49 "Execution limit exceeded: {} steps (max {})",
50 actual_steps, max_steps
51 )
52 }
53 VMError::StateNotFound { state_ref } => {
54 write!(f, "State not found: {:?}", state_ref)
55 }
56 VMError::InconsistentOutput(msg) => write!(f, "Inconsistent output: {}", msg),
57 VMError::InvalidSignature(msg) => write!(f, "Invalid signature: {}", msg),
58 VMError::SealReplay { seal } => write!(f, "Seal replay detected: {:?}", seal),
59 VMError::SchemaViolation(msg) => write!(f, "Schema violation: {}", msg),
60 VMError::ExecutionError(msg) => write!(f, "Execution error: {}", msg),
61 }
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
70pub struct VMInputs {
71 pub owned_inputs: Vec<OwnedState>,
73 pub global_state: Vec<GlobalState>,
75 pub metadata: Vec<Metadata>,
77 pub seal_data: Vec<u8>,
79}
80
81impl VMInputs {
82 pub fn new(
84 owned_inputs: Vec<OwnedState>,
85 global_state: Vec<GlobalState>,
86 metadata: Vec<Metadata>,
87 seal_data: Vec<u8>,
88 ) -> Self {
89 Self {
90 owned_inputs,
91 global_state,
92 metadata,
93 seal_data,
94 }
95 }
96
97 pub fn global_state_of(&self, type_id: StateTypeId) -> Vec<&GlobalState> {
99 self.global_state
100 .iter()
101 .filter(|s| s.type_id == type_id)
102 .collect()
103 }
104
105 pub fn owned_state_of(&self, type_id: StateTypeId) -> Vec<&OwnedState> {
107 self.owned_inputs
108 .iter()
109 .filter(|s| s.type_id == type_id)
110 .collect()
111 }
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116pub struct VMOutputs {
117 pub owned_outputs: Vec<StateAssignment>,
119 pub global_updates: Vec<GlobalState>,
121 pub metadata_updates: Vec<Metadata>,
123 pub next_seal: Option<SealRef>,
125}
126
127impl VMOutputs {
128 pub fn new(
130 owned_outputs: Vec<StateAssignment>,
131 global_updates: Vec<GlobalState>,
132 metadata_updates: Vec<Metadata>,
133 next_seal: Option<SealRef>,
134 ) -> Self {
135 Self {
136 owned_outputs,
137 global_updates,
138 metadata_updates,
139 next_seal,
140 }
141 }
142
143 pub fn total_by_type(&self) -> BTreeMap<StateTypeId, u64> {
145 let mut totals = BTreeMap::new();
146
147 for assignment in &self.owned_outputs {
148 let value = decode_integer(&assignment.data).unwrap_or(0);
149 *totals.entry(assignment.type_id).or_insert(0) += value;
150 }
151
152 for update in &self.global_updates {
153 let value = decode_integer(&update.data).unwrap_or(0);
154 *totals.entry(update.type_id).or_insert(0) += value;
155 }
156
157 totals
158 }
159}
160
161pub trait DeterministicVM {
168 fn execute(
178 &self,
179 bytecode: &[u8],
180 inputs: VMInputs,
181 signatures: &[Vec<u8>],
182 ) -> Result<VMOutputs, VMError>;
183
184 fn validate_outputs(&self, inputs: &VMInputs, outputs: &VMOutputs) -> Result<(), VMError>;
190}
191
192pub struct PassthroughVM {
198 pub max_steps: u64,
200}
201
202impl PassthroughVM {
203 pub fn new(max_steps: u64) -> Self {
205 Self { max_steps }
206 }
207}
208
209impl Default for PassthroughVM {
210 fn default() -> Self {
211 Self::new(1000)
212 }
213}
214
215impl DeterministicVM for PassthroughVM {
216 fn execute(
217 &self,
218 _bytecode: &[u8],
219 inputs: VMInputs,
220 _signatures: &[Vec<u8>],
221 ) -> Result<VMOutputs, VMError> {
222 let steps = inputs.owned_inputs.len() as u64 + 1;
227 if steps > self.max_steps {
228 return Err(VMError::ExecutionLimitExceeded {
229 max_steps: self.max_steps,
230 actual_steps: steps,
231 });
232 }
233
234 let owned_outputs: Vec<StateAssignment> = inputs
236 .owned_inputs
237 .iter()
238 .map(|state| {
239 StateAssignment::new(
240 state.type_id,
241 state.seal.clone(), state.data.clone(),
243 )
244 })
245 .collect();
246
247 Ok(VMOutputs::new(
248 owned_outputs,
249 Vec::new(), inputs.metadata.clone(),
251 None, ))
253 }
254
255 fn validate_outputs(&self, inputs: &VMInputs, outputs: &VMOutputs) -> Result<(), VMError> {
256 let mut input_totals: BTreeMap<StateTypeId, u64> = BTreeMap::new();
258 for state in &inputs.owned_inputs {
259 let value = decode_integer(&state.data).unwrap_or(0);
260 *input_totals.entry(state.type_id).or_insert(0) += value;
261 }
262 for state in &inputs.global_state {
264 let value = decode_integer(&state.data).unwrap_or(0);
265 *input_totals.entry(state.type_id).or_insert(0) += value;
266 }
267
268 let output_totals = outputs.total_by_type();
269
270 for (type_id, output_total) in &output_totals {
271 let input_total = input_totals.get(type_id).copied().unwrap_or(0);
272 if *output_total > input_total {
273 return Err(VMError::InconsistentOutput(format!(
274 "Output total {} exceeds input total {} for type {}",
275 output_total, input_total, type_id
276 )));
277 }
278 }
279
280 Ok(())
281 }
282}
283
284fn decode_integer(data: &[u8]) -> Option<u64> {
286 if data.len() < 8 {
287 return None;
288 }
289 let mut bytes = [0u8; 8];
290 bytes.copy_from_slice(&data[..8]);
291 Some(u64::from_le_bytes(bytes))
292}
293
294pub fn execute_transition(
298 vm: &impl DeterministicVM,
299 bytecode: &[u8],
300 inputs: VMInputs,
301 signatures: &[Vec<u8>],
302) -> Result<VMOutputs, VMError> {
303 let outputs = vm.execute(bytecode, inputs.clone(), signatures)?;
304 vm.validate_outputs(&inputs, &outputs)?;
305 Ok(outputs)
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::Hash;
312
313 fn test_inputs() -> VMInputs {
314 VMInputs::new(
315 vec![OwnedState::from_hash(
316 10,
317 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
318 Hash::new([1u8; 32]),
319 )],
320 vec![GlobalState::from_hash(1, Hash::new([100u8; 32]))],
321 vec![Metadata::from_string("memo", "test")],
322 vec![0x01, 0x02, 0x03],
323 )
324 }
325
326 #[test]
331 fn test_vm_inputs_creation() {
332 let inputs = test_inputs();
333 assert_eq!(inputs.owned_inputs.len(), 1);
334 assert_eq!(inputs.global_state.len(), 1);
335 }
336
337 #[test]
338 fn test_vm_inputs_global_state_lookup() {
339 let inputs = test_inputs();
340 let states = inputs.global_state_of(1);
341 assert_eq!(states.len(), 1);
342 assert!(inputs.global_state_of(99).is_empty());
343 }
344
345 #[test]
346 fn test_vm_inputs_owned_state_lookup() {
347 let inputs = test_inputs();
348 let states = inputs.owned_state_of(10);
349 assert_eq!(states.len(), 1);
350 assert!(inputs.owned_state_of(99).is_empty());
351 }
352
353 #[test]
358 fn test_vm_outputs_creation() {
359 let outputs = VMOutputs::new(
360 vec![StateAssignment::new(
361 10,
362 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
363 1000u64.to_le_bytes().to_vec(),
364 )],
365 vec![GlobalState::from_hash(1, Hash::new([100u8; 32]))],
366 vec![],
367 None,
368 );
369 assert_eq!(outputs.owned_outputs.len(), 1);
370 }
371
372 #[test]
373 fn test_vm_outputs_total_by_type() {
374 let outputs = VMOutputs::new(
375 vec![
376 StateAssignment::new(
377 10,
378 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
379 600u64.to_le_bytes().to_vec(),
380 ),
381 StateAssignment::new(
382 10,
383 SealRef::new(vec![0xBB; 16], Some(2)).unwrap(),
384 400u64.to_le_bytes().to_vec(),
385 ),
386 ],
387 vec![],
388 vec![],
389 None,
390 );
391 let totals = outputs.total_by_type();
392 assert_eq!(totals.get(&10), Some(&1000));
393 }
394
395 #[test]
400 fn test_passthrough_vm_basic() {
401 let vm = PassthroughVM::default();
402 let inputs = test_inputs();
403 let outputs = vm.execute(&[0x01], inputs.clone(), &[]).unwrap();
404 assert_eq!(outputs.owned_outputs.len(), inputs.owned_inputs.len());
405 }
406
407 #[test]
408 fn test_passthrough_vm_validation_conservation() {
409 let vm = PassthroughVM::default();
410 let inputs = test_inputs();
411 let outputs = vm.execute(&[0x01], inputs.clone(), &[]).unwrap();
412 vm.validate_outputs(&inputs, &outputs).unwrap();
413 }
414
415 #[test]
416 fn test_passthrough_vm_execution_limit() {
417 let vm = PassthroughVM::new(0); let inputs = test_inputs();
419 let result = vm.execute(&[0x01], inputs, &[]);
420 assert!(result.is_err());
421 assert!(matches!(
422 result.unwrap_err(),
423 VMError::ExecutionLimitExceeded { .. }
424 ));
425 }
426
427 #[test]
428 fn test_execute_transition_valid() {
429 let vm = PassthroughVM::default();
430 let inputs = test_inputs();
431 let outputs = execute_transition(&vm, &[0x01], inputs.clone(), &[]).unwrap();
432 assert_eq!(outputs.owned_outputs.len(), 1);
433 }
434
435 #[test]
440 fn test_decode_integer_valid() {
441 let bytes = 1000u64.to_le_bytes();
442 assert_eq!(decode_integer(&bytes), Some(1000));
443 }
444
445 #[test]
446 fn test_decode_integer_too_short() {
447 assert_eq!(decode_integer(&[1, 2, 3]), None);
448 }
449
450 #[test]
451 fn test_decode_integer_extra_bytes() {
452 let mut bytes = 42u64.to_le_bytes().to_vec();
453 bytes.push(0xFF);
454 assert_eq!(decode_integer(&bytes), Some(42));
455 }
456
457 #[test]
462 fn test_vm_error_display() {
463 let err = VMError::InvalidBytecode("bad opcode".to_string());
464 assert!(err.to_string().contains("Invalid bytecode"));
465
466 let err = VMError::ExecutionLimitExceeded {
467 max_steps: 100,
468 actual_steps: 200,
469 };
470 assert!(err.to_string().contains("200"));
471
472 let err = VMError::SealReplay {
473 seal: SealRef::new(vec![1], Some(1)).unwrap(),
474 };
475 assert!(err.to_string().contains("replay"));
476 }
477
478 #[test]
483 fn test_full_transition_with_multiple_inputs() {
484 let vm = PassthroughVM::new(100);
485
486 let inputs = VMInputs::new(
487 vec![
488 OwnedState::from_hash(
489 10,
490 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
491 Hash::new([1u8; 32]),
492 ),
493 OwnedState::from_hash(
494 10,
495 SealRef::new(vec![0xBB; 16], Some(2)).unwrap(),
496 Hash::new([2u8; 32]),
497 ),
498 ],
499 vec![GlobalState::from_hash(1, Hash::new([100u8; 32]))],
500 vec![Metadata::from_string("memo", "multi-input transition")],
501 vec![0xDE, 0xAD],
502 );
503
504 let outputs = execute_transition(&vm, &[0x01, 0x02], inputs, &[]).unwrap();
505 assert_eq!(outputs.owned_outputs.len(), 2);
506 }
507}