mod util;
use constraint::mut_keys_set;
use essential_state_read_vm::{
asm::{self, Op},
constraint,
types::solution::{Mutation, Solution, SolutionData},
Access, BytecodeMapped, Gas, GasLimit, SolutionAccess, StateSlots, Vm,
};
use util::*;
#[tokio::test]
async fn no_yield() {
let mut vm = Vm::default();
let ops = &[
asm::Stack::Push(6).into(),
asm::Stack::Push(7).into(),
asm::Alu::Mul.into(),
asm::TotalControlFlow::Halt.into(),
];
let op_gas_cost = &|_: &Op| 1;
let spent = vm
.exec_ops(
ops,
*test_access(),
&State::EMPTY,
op_gas_cost,
GasLimit::UNLIMITED,
)
.await
.unwrap();
assert_eq!(spent, ops.iter().map(op_gas_cost).sum::<Gas>());
assert_eq!(vm.pc, ops.len() - 1);
assert_eq!(&vm.stack[..], &[42]);
}
#[tokio::test]
async fn yield_per_op() {
let mut vm = Vm::default();
let ops = &[
asm::Stack::Push(6).into(),
asm::Stack::Push(7).into(),
asm::Alu::Mul.into(),
asm::TotalControlFlow::Halt.into(),
];
let op_gas_cost = |_op: &_| GasLimit::DEFAULT_PER_YIELD;
let state = State::EMPTY;
let mut future = vm.exec_ops(
ops,
*test_access(),
&state,
&op_gas_cost,
GasLimit::UNLIMITED,
);
let mut yield_count = 0;
let spent = {
let mut future = std::pin::pin!(future);
loop {
match futures::poll!(&mut future) {
std::task::Poll::Pending => yield_count += 1,
std::task::Poll::Ready(res) => break res.unwrap(),
}
}
};
assert_eq!(yield_count, ops.len() - 1);
assert_eq!(spent, ops.iter().map(op_gas_cost).sum::<Gas>());
assert_eq!(vm.pc, ops.len() - 1);
assert_eq!(&vm.stack[..], &[42]);
}
#[tokio::test]
async fn continue_execution() {
let mut vm = Vm::default();
let ops = &[
asm::Stack::Push(6).into(),
asm::Stack::Push(7).into(),
asm::Alu::Mul.into(),
asm::TotalControlFlow::Halt.into(),
];
let op_gas_cost = &|_: &Op| 1;
let spent = vm
.exec_ops(
ops,
*test_access(),
&State::EMPTY,
op_gas_cost,
GasLimit::UNLIMITED,
)
.await
.unwrap();
assert_eq!(spent, ops.iter().map(op_gas_cost).sum::<Gas>());
assert_eq!(vm.pc, ops.len() - 1);
assert_eq!(&vm.stack[..], &[42]);
vm.pc = 0;
let ops = &[
asm::Stack::Push(6).into(),
asm::Alu::Div.into(),
asm::TotalControlFlow::Halt.into(),
];
let spent = vm
.exec_ops(
ops,
*test_access(),
&State::EMPTY,
&op_gas_cost,
GasLimit::UNLIMITED,
)
.await
.unwrap();
assert_eq!(spent, ops.iter().map(op_gas_cost).sum::<Gas>());
assert_eq!(vm.pc, ops.len() - 1);
assert_eq!(&vm.stack[..], &[7]);
}
#[tokio::test]
async fn exec_method_behaviours_match() {
let ops: &[Op] = &[
asm::Stack::Push(6).into(),
asm::Stack::Push(7).into(),
asm::Alu::Mul.into(),
asm::TotalControlFlow::Halt.into(),
];
let mut vm_ops = Vm::default();
let spent_ops = vm_ops
.exec_ops(
ops,
*test_access(),
&State::EMPTY,
&|_: &Op| 1,
GasLimit::UNLIMITED,
)
.await
.unwrap();
let mapped: BytecodeMapped = ops.iter().copied().collect();
let mut vm_bc = Vm::default();
let spent_bc = vm_bc
.exec_bytecode(
&mapped,
*test_access(),
&State::EMPTY,
&|_: &Op| 1,
GasLimit::UNLIMITED,
)
.await
.unwrap();
assert_eq!(spent_ops, spent_bc);
assert_eq!(vm_ops, vm_bc);
let bc_iter = mapped.bytecode().iter().copied();
let mut vm_bc_iter = Vm::default();
let spent_bc_iter = vm_bc_iter
.exec_bytecode_iter(
bc_iter,
*test_access(),
&State::EMPTY,
&|_: &Op| 1,
GasLimit::UNLIMITED,
)
.await
.unwrap();
assert_eq!(spent_ops, spent_bc_iter);
assert_eq!(vm_ops, vm_bc_iter);
}
#[tokio::test]
async fn read_pre_post_state_and_check_constraints() {
let predicate_addr = TEST_PREDICATE_ADDR;
let pre_state = State::new(vec![(
predicate_addr.contract.clone(),
vec![(vec![0, 0, 0, 0], vec![40]), (vec![0, 0, 0, 2], vec![42])],
)]);
let solution = Solution {
data: vec![SolutionData {
predicate_to_solve: predicate_addr.clone(),
decision_variables: vec![],
state_mutations: vec![Mutation {
key: vec![0, 0, 0, 1],
value: vec![41],
}],
}],
};
let predicate_index = 0;
let mutable_keys = mut_keys_set(&solution, predicate_index);
let mut access = Access {
solution: SolutionAccess::new(&solution, predicate_index, &mutable_keys),
state_slots: StateSlots::EMPTY,
};
let ops = &[
asm::Stack::Push(3).into(),
asm::StateMemory::AllocSlots.into(),
asm::Stack::Push(0).into(), asm::Stack::Push(0).into(), asm::Stack::Push(0).into(), asm::Stack::Push(0).into(), asm::Stack::Push(4).into(), asm::Stack::Push(3).into(), asm::Stack::Push(0).into(), asm::StateRead::KeyRange,
];
let mut vm = Vm::default();
vm.exec_ops(ops, access, &pre_state, &|_: &Op| 1, GasLimit::UNLIMITED)
.await
.unwrap();
let pre_state_slots = vm.into_state_slots();
let mut post_state = pre_state.clone();
for data in &solution.data {
let contract_addr = &data.predicate_to_solve.contract;
for Mutation { key, value } in &data.state_mutations {
post_state.set(contract_addr.clone(), key, value.clone());
}
}
let mut vm = Vm::default();
vm.exec_ops(ops, access, &post_state, &|_: &Op| 1, GasLimit::UNLIMITED)
.await
.unwrap();
let post_state_slots = vm.into_state_slots();
assert_eq!(&pre_state_slots[..], &[vec![40], vec![], vec![42]]);
assert_eq!(&post_state_slots[..], &[vec![40], vec![41], vec![42]]);
access.state_slots = StateSlots {
pre: &pre_state_slots[..],
post: &post_state_slots[..],
};
let constraints: &[Vec<u8>] = &[
constraint::asm::to_bytes(vec![
asm::Stack::Push(0).into(), asm::Stack::Push(0).into(),
asm::Stack::Push(1).into(),
asm::Stack::Push(0).into(), asm::Access::State.into(),
asm::Stack::Push(0).into(), asm::Stack::Push(0).into(),
asm::Stack::Push(1).into(),
asm::Stack::Push(1).into(), asm::Access::State.into(),
asm::Pred::Eq.into(),
])
.collect(),
constraint::asm::to_bytes(vec![
asm::Stack::Push(1).into(), asm::Stack::Push(0).into(), asm::Access::StateLen.into(),
asm::Stack::Push(0).into(),
asm::Pred::Eq.into(),
asm::Stack::Push(1).into(), asm::Stack::Push(1).into(), asm::Access::StateLen.into(),
asm::Stack::Push(0).into(),
asm::Pred::Eq.into(),
asm::Pred::Not.into(),
asm::Pred::And.into(),
])
.collect(),
constraint::asm::to_bytes(vec![
asm::Stack::Push(2).into(), asm::Stack::Push(0).into(),
asm::Stack::Push(1).into(),
asm::Stack::Push(0).into(), asm::Access::State.into(),
asm::Stack::Push(2).into(), asm::Stack::Push(0).into(),
asm::Stack::Push(1).into(),
asm::Stack::Push(1).into(), asm::Access::State.into(),
asm::Pred::Eq.into(),
])
.collect(),
];
constraint::check_predicate(constraints, access).unwrap();
}
#[tokio::test]
async fn test_halt() {
let mut vm = Vm::default();
let ops = &[
asm::Stack::Push(6).into(),
asm::Stack::Push(7).into(),
asm::Alu::Mul.into(),
];
let op_gas_cost = &|_: &Op| 1;
vm.exec_ops(
ops,
*test_access(),
&State::EMPTY,
op_gas_cost,
GasLimit::UNLIMITED,
)
.await
.unwrap();
assert_eq!(&vm.stack[..], &[42]);
}