use crate::stack_item::StackItem;
use k256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum VMError {
#[error("Stack underflow")]
StackUnderflow,
#[error("Stack overflow: max depth {0} exceeded")]
StackOverflow(usize),
#[error("Invalid opcode: {0}")]
InvalidOpcode(u8),
#[error("Out of gas")]
OutOfGas,
#[error("Division by zero")]
DivisionByZero,
#[error("Invalid type")]
InvalidType,
#[error("Unknown syscall: {0}")]
UnknownSyscall(u32),
#[error("Invalid operation")]
InvalidOperation,
#[error("Invalid script")]
InvalidScript,
#[error("Invalid public key format for CHECKSIG")]
InvalidPublicKey,
#[error("Invalid signature format for CHECKSIG")]
InvalidSignature,
#[error("Signature verification failed")]
SignatureVerificationFailed,
#[error("Invocation depth exceeded: max {0}")]
InvocationDepthExceeded(usize),
}
#[derive(Debug, Clone)]
pub enum VMState {
None,
Halt,
Fault,
Break,
}
#[derive(Debug, Clone)]
pub struct ExecutionContext {
pub script: Vec<u8>,
pub ip: usize,
}
unsafe impl Send for ExecutionContext {}
unsafe impl Sync for ExecutionContext {}
pub mod syscall {
pub const SYSTEM_RUNTIME_LOG: u32 = 0x01;
pub const SYSTEM_RUNTIME_NOTIFY: u32 = 0x02;
pub const SYSTEM_RUNTIME_GETTIME: u32 = 0x03;
pub const SYSTEM_STORAGE_GET: u32 = 0x10;
pub const SYSTEM_STORAGE_PUT: u32 = 0x11;
pub const SYSTEM_STORAGE_DELETE: u32 = 0x12;
}
const GAS_COSTS: [u16; 256] = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
512, 512, 512, 32768, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];
#[inline]
fn get_gas_cost(op: u8) -> u64 {
GAS_COSTS[op as usize] as u64
}
pub const MAX_SCRIPT_SIZE: usize = 1024 * 1024;
pub const DEFAULT_MAX_STACK_DEPTH: usize = 2048;
pub const DEFAULT_MAX_INVOCATION_DEPTH: usize = 1024;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TraceStep {
pub ip: usize,
pub opcode: u8,
pub stack_hash: [u8; 32],
pub gas_consumed: u64,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct ExecutionTrace {
pub steps: Vec<TraceStep>,
pub initial_state_hash: [u8; 32],
pub final_state_hash: [u8; 32],
}
pub struct NeoVM {
pub state: VMState,
pub eval_stack: Vec<StackItem>,
pub invocation_stack: Vec<ExecutionContext>,
pub gas_consumed: u64,
pub gas_limit: u64,
pub max_stack_depth: usize,
pub max_invocation_depth: usize,
pub notifications: Vec<StackItem>,
pub logs: Vec<String>,
pub trace: ExecutionTrace,
pub tracing_enabled: bool,
pub local_slots: Vec<StackItem>,
pub argument_slots: Vec<StackItem>,
pub static_slots: Vec<StackItem>,
}
impl NeoVM {
const DEFAULT_STACK_CAPACITY: usize = 64;
const DEFAULT_INVOCATION_CAPACITY: usize = 8;
#[inline]
pub fn new(gas_limit: u64) -> Self {
Self::with_limits(
gas_limit,
DEFAULT_MAX_STACK_DEPTH,
DEFAULT_MAX_INVOCATION_DEPTH,
)
}
#[inline]
pub fn with_limits(
gas_limit: u64,
max_stack_depth: usize,
max_invocation_depth: usize,
) -> Self {
Self {
state: VMState::None,
eval_stack: Vec::with_capacity(Self::DEFAULT_STACK_CAPACITY),
invocation_stack: Vec::with_capacity(Self::DEFAULT_INVOCATION_CAPACITY),
gas_consumed: 0,
gas_limit,
max_stack_depth,
max_invocation_depth,
notifications: Vec::new(),
logs: Vec::new(),
trace: ExecutionTrace::default(),
tracing_enabled: false,
local_slots: Vec::with_capacity(Self::DEFAULT_STACK_CAPACITY),
argument_slots: Vec::with_capacity(Self::DEFAULT_STACK_CAPACITY),
static_slots: Vec::with_capacity(Self::DEFAULT_STACK_CAPACITY),
}
}
#[inline]
pub fn run(&mut self) {
while !matches!(self.state, VMState::Halt | VMState::Fault) {
if self.execute_next().is_err() {
self.state = VMState::Fault;
break;
}
}
}
#[inline]
pub fn enable_tracing(&mut self) {
self.tracing_enabled = true;
self.trace.initial_state_hash = self.compute_state_hash();
}
#[inline]
fn compute_state_hash(&self) -> [u8; 32] {
use sha2::Digest;
let mut hasher = Sha256::new();
for item in &self.eval_stack {
hasher.update(format!("{:?}", item).as_bytes());
}
hasher.update(self.gas_consumed.to_le_bytes());
hasher.finalize().into()
}
fn read_u8(ctx: &mut ExecutionContext) -> Result<u8, VMError> {
if ctx.ip >= ctx.script.len() {
return Err(VMError::InvalidScript);
}
let byte = ctx.script[ctx.ip];
ctx.ip += 1;
Ok(byte)
}
fn read_i8(ctx: &mut ExecutionContext) -> Result<i8, VMError> {
Ok(Self::read_u8(ctx)? as i8)
}
fn read_u16_le(ctx: &mut ExecutionContext) -> Result<u16, VMError> {
if ctx.ip + 1 >= ctx.script.len() {
return Err(VMError::InvalidScript);
}
let val = u16::from_le_bytes([ctx.script[ctx.ip], ctx.script[ctx.ip + 1]]);
ctx.ip += 2;
Ok(val)
}
fn read_u32_le(ctx: &mut ExecutionContext) -> Result<u32, VMError> {
if ctx.ip + 3 >= ctx.script.len() {
return Err(VMError::InvalidScript);
}
let val = u32::from_le_bytes([
ctx.script[ctx.ip],
ctx.script[ctx.ip + 1],
ctx.script[ctx.ip + 2],
ctx.script[ctx.ip + 3],
]);
ctx.ip += 4;
Ok(val)
}
fn pop_usize_nonneg(&mut self) -> Result<usize, VMError> {
let value = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if value < 0 {
return Err(VMError::InvalidOperation);
}
Ok(value as usize)
}
fn relative_target(base_ip: usize, offset: i8, script_len: usize) -> Result<usize, VMError> {
let target = base_ip as isize + offset as isize;
if target < 0 || target as usize > script_len {
return Err(VMError::InvalidScript);
}
Ok(target as usize)
}
#[inline]
fn push(&mut self, item: StackItem) -> Result<(), VMError> {
if self.eval_stack.len() >= self.max_stack_depth {
return Err(VMError::StackOverflow(self.max_stack_depth));
}
self.eval_stack.push(item);
Ok(())
}
#[inline]
fn check_invocation_depth(&self) -> Result<(), VMError> {
if self.invocation_stack.len() >= self.max_invocation_depth {
return Err(VMError::InvocationDepthExceeded(self.max_invocation_depth));
}
Ok(())
}
#[inline]
pub fn load_script(&mut self, script: Vec<u8>) -> Result<(), VMError> {
if script.len() > MAX_SCRIPT_SIZE {
return Err(VMError::InvalidScript);
}
self.check_invocation_depth()?;
self.invocation_stack
.push(ExecutionContext { script, ip: 0 });
Ok(())
}
pub fn execute_next(&mut self) -> Result<(), VMError> {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
if ctx.ip >= ctx.script.len() {
self.state = VMState::Halt;
if self.tracing_enabled {
self.trace.final_state_hash = self.compute_state_hash();
}
return Ok(());
}
let ip = ctx.ip;
let op = ctx.script[ctx.ip];
ctx.ip += 1;
let gas_cost = get_gas_cost(op);
self.gas_consumed += gas_cost;
if self.gas_consumed > self.gas_limit {
self.state = VMState::Fault;
return Err(VMError::OutOfGas);
}
if self.tracing_enabled {
let step = TraceStep {
ip,
opcode: op,
stack_hash: self.compute_state_hash(),
gas_consumed: self.gas_consumed,
};
self.trace.steps.push(step);
}
if let Err(e) = self.execute_op(op) {
self.state = VMState::Fault;
return Err(e);
}
Ok(())
}
fn execute_op(&mut self, op: u8) -> Result<(), VMError> {
match op {
0x10 => self.push(StackItem::Integer(0))?,
0x11..=0x20 => {
let n = (op - 0x10) as i128;
self.push(StackItem::Integer(n))?;
}
0x0F => self.push(StackItem::Integer(-1))?,
0x0B => self.push(StackItem::Null)?,
0x0C => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let len = Self::read_u8(ctx)? as usize;
if ctx.ip + len > ctx.script.len() {
return Err(VMError::InvalidScript);
}
let data = ctx.script[ctx.ip..ctx.ip + len].to_vec();
ctx.ip += len;
self.push(StackItem::ByteString(data))?;
}
0x0D => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let len = Self::read_u16_le(ctx)? as usize;
if ctx.ip + len > ctx.script.len() {
return Err(VMError::InvalidScript);
}
let data = ctx.script[ctx.ip..ctx.ip + len].to_vec();
ctx.ip += len;
self.push(StackItem::ByteString(data))?;
}
0x00 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let val = Self::read_u8(ctx)? as i8 as i128;
self.push(StackItem::Integer(val))?;
}
0x01 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let val = i16::from_le_bytes(Self::read_u16_le(ctx)?.to_le_bytes()) as i128;
self.push(StackItem::Integer(val))?;
}
0x45 => {
self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
}
0x4A => {
let item = self
.eval_stack
.last()
.ok_or(VMError::StackUnderflow)?
.clone();
self.push(item)?;
}
0x9E => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_add(b).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0x9F => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_sub(b).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xA0 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_mul(b).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xA1 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if b == 0 {
return Err(VMError::DivisionByZero);
}
let result = a.checked_div(b).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xA2 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if b == 0 {
return Err(VMError::DivisionByZero);
}
let result = a.checked_rem(b).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xA3 => {
let exp = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let base = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if exp < 0 {
return Err(VMError::InvalidOperation);
}
let result = base.pow(exp as u32);
self.push(StackItem::Integer(result))?;
}
0xA8 => {
let shift = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let value = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if !(0..=256).contains(&shift) {
return Err(VMError::InvalidOperation);
}
let result = value
.checked_shl(shift as u32)
.ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xA9 => {
let shift = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let value = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if !(0..=256).contains(&shift) {
return Err(VMError::InvalidOperation);
}
let result = value
.checked_shr(shift as u32)
.ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xB9 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Integer(a.min(b)))?;
}
0xBA => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Integer(a.max(b)))?;
}
0xBB => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let x = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a <= x && x < b))?;
}
0x99 => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let sign = if a > 0 {
1
} else if a < 0 {
-1
} else {
0
};
self.push(StackItem::Integer(sign))?;
}
0x9A => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_abs().ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0x9B => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_neg().ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0x9C => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_add(1).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0x9D => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let result = a.checked_sub(1).ok_or(VMError::InvalidOperation)?;
self.push(StackItem::Integer(result))?;
}
0xB5 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a < b))?;
}
0xB6 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a <= b))?;
}
0xB7 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a > b))?;
}
0xB8 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a >= b))?;
}
0x97 => {
let b = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let a = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a == b))?;
}
0x98 => {
let b = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let a = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a != b))?;
}
0xD8 => {
let item = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.eval_stack
.push(StackItem::Boolean(matches!(item, StackItem::Null)));
}
0xB1 => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a != 0))?;
}
0xB3 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a == b))?;
}
0xB4 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(a != b))?;
}
0x90 => {
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Integer(!a))?;
}
0x91 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Integer(a & b))?;
}
0x92 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Integer(a | b))?;
}
0x93 => {
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Integer(a ^ b))?;
}
0xAA => {
let a = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.push(StackItem::Boolean(!a.to_bool()))?;
}
0xAB => {
let b = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let a = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.eval_stack
.push(StackItem::Boolean(a.to_bool() && b.to_bool()));
}
0xAC => {
let b = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let a = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.eval_stack
.push(StackItem::Boolean(a.to_bool() || b.to_bool()));
}
0x50 => {
let len = self.eval_stack.len();
if len < 2 {
return Err(VMError::StackUnderflow);
}
self.eval_stack.swap(len - 1, len - 2);
}
0x51 => {
let len = self.eval_stack.len();
if len < 3 {
return Err(VMError::StackUnderflow);
}
let item = self.eval_stack.remove(len - 3);
self.push(item)?;
}
0x4D => {
let n = self.pop_usize_nonneg()?;
let len = self.eval_stack.len();
if n >= len {
return Err(VMError::StackUnderflow);
}
let item = self.eval_stack[len - 1 - n].clone();
self.push(item)?;
}
0x52 => {
let n = self.pop_usize_nonneg()?;
let len = self.eval_stack.len();
if n >= len {
return Err(VMError::StackUnderflow);
}
let item = self.eval_stack.remove(len - 1 - n);
self.push(item)?;
}
0x4B => {
let len = self.eval_stack.len();
if len < 2 {
return Err(VMError::StackUnderflow);
}
let item = self.eval_stack[len - 2].clone();
self.push(item)?;
}
0x43 => {
let depth = self.eval_stack.len() as i128;
self.push(StackItem::Integer(depth))?;
}
0x46 => {
let len = self.eval_stack.len();
if len < 2 {
return Err(VMError::StackUnderflow);
}
self.eval_stack.remove(len - 2);
}
0x48 => {
let n = self.pop_usize_nonneg()?;
let len = self.eval_stack.len();
if n >= len {
return Err(VMError::StackUnderflow);
}
self.eval_stack.remove(len - 1 - n);
}
0x49 => {
self.eval_stack.clear();
}
0x4E => {
let len = self.eval_stack.len();
if len < 2 {
return Err(VMError::StackUnderflow);
}
let item = self.eval_stack[len - 1].clone();
self.eval_stack.insert(len - 2, item);
}
0x53 => {
let len = self.eval_stack.len();
if len < 3 {
return Err(VMError::StackUnderflow);
}
self.eval_stack.swap(len - 1, len - 3);
}
0x54 => {
let len = self.eval_stack.len();
if len < 4 {
return Err(VMError::StackUnderflow);
}
self.eval_stack.swap(len - 1, len - 4);
self.eval_stack.swap(len - 2, len - 3);
}
0x55 => {
let n = self.pop_usize_nonneg()?;
let len = self.eval_stack.len();
if n > len {
return Err(VMError::StackUnderflow);
}
let start = len - n;
self.eval_stack[start..].reverse();
}
0x57 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let local_count = Self::read_u8(ctx)? as usize;
let arg_count = Self::read_u8(ctx)? as usize;
self.local_slots = vec![StackItem::Null; local_count];
self.argument_slots = Vec::with_capacity(arg_count);
for _ in 0..arg_count {
let arg = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.argument_slots.push(arg);
}
self.argument_slots.reverse();
}
0x66..=0x6C => {
let idx = (op - 0x66) as usize;
let item = self
.local_slots
.get(idx)
.cloned()
.ok_or(VMError::InvalidOperation)?;
self.push(item)?;
}
0x6D => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let idx = Self::read_u8(ctx)? as usize;
let item = self
.local_slots
.get(idx)
.cloned()
.ok_or(VMError::InvalidOperation)?;
self.push(item)?;
}
0x6E..=0x72 => {
let val = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let idx = (op - 0x6E) as usize;
if idx >= self.local_slots.len() {
self.local_slots.resize(idx + 1, StackItem::Null);
}
self.local_slots[idx] = val;
}
0x73 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let idx = Self::read_u8(ctx)? as usize;
let item = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
if idx >= self.local_slots.len() {
return Err(VMError::InvalidOperation);
}
self.local_slots[idx] = item;
}
0x74..=0x79 => {
let idx = (op - 0x74) as usize;
let item = self
.argument_slots
.get(idx)
.cloned()
.ok_or(VMError::InvalidOperation)?;
self.push(item)?;
}
0x7A => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let idx = Self::read_u8(ctx)? as usize;
let item = self
.argument_slots
.get(idx)
.cloned()
.ok_or(VMError::InvalidOperation)?;
self.push(item)?;
}
0x21 => {}
0x39 => {
let cond = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
if !cond.to_bool() {
self.state = VMState::Fault;
return Err(VMError::InvalidOperation);
}
}
0x22 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
0x24 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let cond = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
if cond.to_bool() {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x26 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let cond = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
if !cond.to_bool() {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x28 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if a == b {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x2A => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if a != b {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x2C => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if a > b {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x2E => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if a >= b {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x30 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if a < b {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x32 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let b = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
let a = self
.eval_stack
.pop()
.and_then(|x| x.to_integer())
.ok_or(VMError::StackUnderflow)?;
if a <= b {
ctx.ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
}
}
0x34 => {
self.check_invocation_depth()?;
let (return_ip, target_ip, script) = {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let base_ip = ctx.ip.checked_sub(1).ok_or(VMError::InvalidScript)?;
let offset = Self::read_i8(ctx)?;
let return_ip = ctx.ip;
let target_ip = Self::relative_target(base_ip, offset, ctx.script.len())?;
let script = ctx.script.clone();
(return_ip, target_ip, script)
};
self.invocation_stack.push(ExecutionContext {
script,
ip: target_ip,
});
self.push(StackItem::Pointer(return_ip as u32))?;
}
0xF0 => {
let data = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let bytes = match data {
StackItem::ByteString(b) | StackItem::Buffer(b) => b,
StackItem::Integer(i) => i.to_le_bytes().to_vec(),
_ => return Err(VMError::InvalidType),
};
let mut hasher = Sha256::new();
hasher.update(&bytes);
let result = hasher.finalize().to_vec();
self.push(StackItem::ByteString(result))?;
}
0xF1 => {
let data = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let bytes = match data {
StackItem::ByteString(b) | StackItem::Buffer(b) => b,
StackItem::Integer(i) => i.to_le_bytes().to_vec(),
_ => return Err(VMError::InvalidType),
};
let mut hasher = Ripemd160::new();
hasher.update(&bytes);
let result = hasher.finalize().to_vec();
self.push(StackItem::ByteString(result))?;
}
0xF2 => {
let data = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let bytes = match data {
StackItem::ByteString(b) | StackItem::Buffer(b) => b,
StackItem::Integer(i) => i.to_le_bytes().to_vec(),
_ => return Err(VMError::InvalidType),
};
let sha_result = Sha256::digest(&bytes);
let result = Ripemd160::digest(sha_result).to_vec();
self.push(StackItem::ByteString(result))?;
}
0xF3 => {
let pubkey = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let sig = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let msg = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let pubkey_bytes = match pubkey {
StackItem::ByteString(b) | StackItem::Buffer(b) => b,
_ => return Err(VMError::InvalidType),
};
let sig_bytes = match sig {
StackItem::ByteString(b) | StackItem::Buffer(b) => b,
_ => return Err(VMError::InvalidType),
};
let msg_bytes = match msg {
StackItem::ByteString(b) | StackItem::Buffer(b) => b,
_ => return Err(VMError::InvalidType),
};
let result = VerifyingKey::from_sec1_bytes(&pubkey_bytes)
.map_err(|_| VMError::InvalidPublicKey)?;
let signature =
Signature::from_slice(&sig_bytes).map_err(|_| VMError::InvalidSignature)?;
let msg_hash = Sha256::digest(&msg_bytes);
let verified = result.verify(&msg_hash, &signature).is_ok();
self.push(StackItem::Boolean(verified))?;
}
0x41 => {
let ctx = self
.invocation_stack
.last_mut()
.ok_or(VMError::StackUnderflow)?;
let id = Self::read_u32_le(ctx)?;
self.execute_syscall(id)?;
}
0xC2 => {
self.push(StackItem::Array(Vec::new()))?;
}
0xC3 => {
let n = self.pop_usize_nonneg()?;
let arr = vec![StackItem::Null; n];
self.push(StackItem::Array(arr))?;
}
0xC5 => {
self.push(StackItem::Struct(Vec::new()))?;
}
0xC6 => {
let n = self.pop_usize_nonneg()?;
let s = vec![StackItem::Null; n];
self.push(StackItem::Struct(s))?;
}
0xC8 => {
self.push(StackItem::Map(Vec::new()))?;
}
0xCA => {
let item = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let size = match &item {
StackItem::Array(a) | StackItem::Struct(a) => a.len(),
StackItem::Map(m) => m.len(),
StackItem::ByteString(b) | StackItem::Buffer(b) => b.len(),
_ => return Err(VMError::InvalidType),
};
self.push(StackItem::Integer(size as i128))?;
}
0xCE => {
let key = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let container = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let item = match (container, key) {
(StackItem::Array(a), StackItem::Integer(i)) => a
.get(i as usize)
.cloned()
.ok_or(VMError::InvalidOperation)?,
(StackItem::Struct(s), StackItem::Integer(i)) => s
.get(i as usize)
.cloned()
.ok_or(VMError::InvalidOperation)?,
(StackItem::Map(m), k) => m
.iter()
.find(|(mk, _)| *mk == k)
.map(|(_, v)| v.clone())
.ok_or(VMError::InvalidOperation)?,
_ => return Err(VMError::InvalidType),
};
self.push(item)?;
}
0xD0 => {
let value = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let key = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let container = self.eval_stack.last_mut().ok_or(VMError::StackUnderflow)?;
match (container, key) {
(StackItem::Array(a), StackItem::Integer(i)) => {
let idx = i as usize;
if idx >= a.len() {
return Err(VMError::InvalidOperation);
}
a[idx] = value;
}
(StackItem::Map(m), k) => {
if let Some(entry) = m.iter_mut().find(|(mk, _)| *mk == k) {
entry.1 = value;
} else {
m.push((k, value));
}
}
_ => return Err(VMError::InvalidType),
}
}
0xCF => {
let item = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let container = self.eval_stack.last_mut().ok_or(VMError::StackUnderflow)?;
match container {
StackItem::Array(a) => a.push(item),
_ => return Err(VMError::InvalidType),
}
}
0xD2 => {
let key = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
let container = self.eval_stack.last_mut().ok_or(VMError::StackUnderflow)?;
match (container, key) {
(StackItem::Array(a), StackItem::Integer(i)) => {
let idx = i as usize;
if idx >= a.len() {
return Err(VMError::InvalidOperation);
}
a.remove(idx);
}
(StackItem::Map(m), k) => {
m.retain(|(mk, _)| *mk != k);
}
_ => return Err(VMError::InvalidType),
}
}
0x40 => {
self.invocation_stack
.pop()
.ok_or(VMError::InvalidOperation)?;
if self.invocation_stack.is_empty() {
self.state = VMState::Halt;
}
}
_ => return Err(VMError::InvalidOpcode(op)),
}
Ok(())
}
fn execute_syscall(&mut self, id: u32) -> Result<(), VMError> {
match id {
syscall::SYSTEM_RUNTIME_LOG => {
let msg = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
if let StackItem::ByteString(b) = msg {
if let Ok(s) = String::from_utf8(b) {
self.logs.push(s);
}
}
Ok(())
}
syscall::SYSTEM_RUNTIME_NOTIFY => {
let item = self.eval_stack.pop().ok_or(VMError::StackUnderflow)?;
self.notifications.push(item);
Ok(())
}
syscall::SYSTEM_RUNTIME_GETTIME => {
self.push(StackItem::Integer(0))?;
Ok(())
}
_ => Err(VMError::UnknownSyscall(id)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_push_operations() {
let mut vm = NeoVM::new(1_000_000);
let _ = vm.load_script(vec![0x11, 0x12, 0x13, 0x40]);
while !matches!(vm.state, VMState::Halt | VMState::Fault) {
vm.execute_next().unwrap();
}
assert!(matches!(vm.state, VMState::Halt));
assert_eq!(vm.eval_stack.len(), 3);
}
#[test]
fn test_add_operation() {
let mut vm = NeoVM::new(1_000_000);
let _ = vm.load_script(vec![0x12, 0x13, 0x9E, 0x40]);
while !matches!(vm.state, VMState::Halt | VMState::Fault) {
vm.execute_next().unwrap();
}
assert_eq!(vm.eval_stack.pop(), Some(StackItem::Integer(5)));
}
#[test]
fn test_sub_operation() {
let mut vm = NeoVM::new(1_000_000);
let _ = vm.load_script(vec![0x15, 0x12, 0x9F, 0x40]);
while !matches!(vm.state, VMState::Halt | VMState::Fault) {
vm.execute_next().unwrap();
}
assert_eq!(vm.eval_stack.pop(), Some(StackItem::Integer(3)));
}
#[test]
fn test_mul_operation() {
let mut vm = NeoVM::new(1_000_000);
let _ = vm.load_script(vec![0x13, 0x14, 0xA0, 0x40]);
while !matches!(vm.state, VMState::Halt | VMState::Fault) {
vm.execute_next().unwrap();
}
assert_eq!(vm.eval_stack.pop(), Some(StackItem::Integer(12)));
}
#[test]
fn test_comparison_lt() {
let mut vm = NeoVM::new(1_000_000);
let _ = vm.load_script(vec![0x12, 0x15, 0xB5, 0x40]);
while !matches!(vm.state, VMState::Halt | VMState::Fault) {
vm.execute_next().unwrap();
}
assert_eq!(vm.eval_stack.pop(), Some(StackItem::Boolean(true)));
}
}