mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-29 10:03:22 +03:00
Aleo VM interpretation and misc
This commit is contained in:
parent
148183fb96
commit
b2f7fd718b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1576,6 +1576,7 @@ dependencies = [
|
||||
"rand_chacha",
|
||||
"snarkvm",
|
||||
"snarkvm-circuit",
|
||||
"snarkvm-synthesizer-program",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -111,7 +111,6 @@ default-features = false
|
||||
version = "1.11.1"
|
||||
|
||||
[workspace.dependencies.snarkvm]
|
||||
# path = "../SnarkVM"
|
||||
version = "1.0.0"
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
|
@ -18,11 +18,14 @@ license = "GPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.82.0"
|
||||
|
||||
[dependencies.snarkvm]
|
||||
workspace = true
|
||||
|
||||
[dependencies.snarkvm-circuit]
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies.snarkvm]
|
||||
workspace = true
|
||||
[dependencies.snarkvm-synthesizer-program]
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies.leo-ast]
|
||||
workspace = true
|
||||
|
@ -38,7 +38,10 @@ use leo_span::{Span, Symbol, sym};
|
||||
|
||||
use snarkvm::prelude::{
|
||||
CastLossy as _,
|
||||
Closure as SvmClosure,
|
||||
Double as _,
|
||||
Finalize as SvmFinalize,
|
||||
Function as SvmFunctionParam,
|
||||
Inverse as _,
|
||||
Network as _,
|
||||
Pow as _,
|
||||
@ -49,16 +52,20 @@ use snarkvm::prelude::{
|
||||
ToBits,
|
||||
};
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use rand::Rng as _;
|
||||
use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
|
||||
use std::{cmp::Ordering, collections::HashMap, fmt, mem, str::FromStr as _};
|
||||
|
||||
pub type Closure = SvmClosure<TestnetV0>;
|
||||
pub type Finalize = SvmFinalize<TestnetV0>;
|
||||
pub type SvmFunction = SvmFunctionParam<TestnetV0>;
|
||||
|
||||
/// Names associated to values in a function being executed.
|
||||
#[derive(Clone, Debug)]
|
||||
struct FunctionContext {
|
||||
pub struct FunctionContext {
|
||||
program: Symbol,
|
||||
caller: SvmAddress,
|
||||
pub caller: SvmAddress,
|
||||
names: HashMap<Symbol, Value>,
|
||||
accumulated_futures: Future,
|
||||
is_async: bool,
|
||||
@ -117,7 +124,7 @@ impl ContextStack {
|
||||
self.last_mut().unwrap().names.insert(symbol, value);
|
||||
}
|
||||
|
||||
fn add_future(&mut self, future: Future) {
|
||||
pub fn add_future(&mut self, future: Future) {
|
||||
assert!(self.current_len > 0);
|
||||
self.contexts[self.current_len - 1].accumulated_futures.0.extend(future.0);
|
||||
}
|
||||
@ -128,11 +135,11 @@ impl ContextStack {
|
||||
self.last().unwrap().is_async
|
||||
}
|
||||
|
||||
fn current_program(&self) -> Option<Symbol> {
|
||||
pub fn current_program(&self) -> Option<Symbol> {
|
||||
self.last().map(|c| c.program)
|
||||
}
|
||||
|
||||
fn last(&self) -> Option<&FunctionContext> {
|
||||
pub fn last(&self) -> Option<&FunctionContext> {
|
||||
self.len().checked_sub(1).and_then(|i| self.contexts.get(i))
|
||||
}
|
||||
|
||||
@ -141,8 +148,15 @@ impl ContextStack {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Leo construct to be evauated.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AleoContext<'a> {
|
||||
Closure(&'a Closure),
|
||||
Function(&'a SvmFunction),
|
||||
Finalize(&'a Finalize),
|
||||
}
|
||||
|
||||
/// A Leo construct to be evauated.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Element<'a> {
|
||||
/// A Leo statement.
|
||||
Statement(&'a Statement),
|
||||
@ -163,6 +177,12 @@ pub enum Element<'a> {
|
||||
function_body: bool,
|
||||
},
|
||||
|
||||
AleoExecution {
|
||||
context: AleoContext<'a>,
|
||||
registers: IndexMap<u64, Value>,
|
||||
instruction_index: usize,
|
||||
},
|
||||
|
||||
DelayedCall(GlobalId),
|
||||
}
|
||||
|
||||
@ -173,7 +193,7 @@ impl Element<'_> {
|
||||
Statement(statement) => statement.span(),
|
||||
Expression(expression) => expression.span(),
|
||||
Block { block, .. } => block.span(),
|
||||
DelayedCall(..) => Default::default(),
|
||||
AleoExecution { .. } | DelayedCall(..) => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,6 +221,13 @@ impl fmt::Display for GlobalId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FunctionVariant<'a> {
|
||||
Leo(&'a Function),
|
||||
AleoClosure(&'a Closure),
|
||||
AleoFunction(&'a SvmFunction),
|
||||
}
|
||||
|
||||
/// Tracks the current execution state - a cursor into the running program.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Cursor<'a> {
|
||||
@ -213,7 +240,7 @@ pub struct Cursor<'a> {
|
||||
pub values: Vec<Value>,
|
||||
|
||||
/// All functions (or transitions or inlines) in any program being interpreted.
|
||||
pub functions: HashMap<GlobalId, &'a Function>,
|
||||
pub functions: HashMap<GlobalId, FunctionVariant<'a>>,
|
||||
|
||||
/// Consts are stored here.
|
||||
pub globals: HashMap<GlobalId, Value>,
|
||||
@ -227,13 +254,13 @@ pub struct Cursor<'a> {
|
||||
|
||||
pub contexts: ContextStack,
|
||||
|
||||
signer: SvmAddress,
|
||||
pub signer: SvmAddress,
|
||||
|
||||
rng: ChaCha20Rng,
|
||||
|
||||
block_height: u32,
|
||||
pub block_height: u32,
|
||||
|
||||
really_async: bool,
|
||||
pub really_async: bool,
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
@ -255,6 +282,23 @@ impl<'a> Cursor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_step(&mut self) {
|
||||
let Some(Frame { step, .. }) = self.frames.last_mut() else {
|
||||
panic!("frame expected");
|
||||
};
|
||||
*step += 1;
|
||||
}
|
||||
|
||||
fn new_caller(&self) -> SvmAddress {
|
||||
if let Some(function_context) = self.contexts.last() {
|
||||
let program_id = ProgramID::<TestnetV0>::from_str(&format!("{}.aleo", function_context.program))
|
||||
.expect("should be able to create ProgramID");
|
||||
program_id.to_address().expect("should be able to convert to address")
|
||||
} else {
|
||||
self.signer
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_value(&mut self) -> Result<Value> {
|
||||
match self.values.pop() {
|
||||
Some(v) => Ok(v),
|
||||
@ -292,7 +336,7 @@ impl<'a> Cursor<'a> {
|
||||
self.mappings.get_mut(&GlobalId { program, name })
|
||||
}
|
||||
|
||||
fn lookup_function(&self, program: Symbol, name: Symbol) -> Option<&'a Function> {
|
||||
fn lookup_function(&self, program: Symbol, name: Symbol) -> Option<FunctionVariant<'a>> {
|
||||
self.functions.get(&GlobalId { program, name }).cloned()
|
||||
}
|
||||
|
||||
@ -652,6 +696,359 @@ impl<'a> Cursor<'a> {
|
||||
|
||||
let span = function.span();
|
||||
|
||||
let value = self.evaluate_core_function(core_function, &function.arguments, span)?;
|
||||
|
||||
Some(value)
|
||||
}
|
||||
Expression::Array(array) if step == 0 => {
|
||||
array.elements.iter().rev().for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Array(array) if step == 1 => {
|
||||
let len = self.values.len();
|
||||
let array_values = self.values.drain(len - array.elements.len()..).collect();
|
||||
Some(Value::Array(array_values))
|
||||
}
|
||||
Expression::Binary(binary) if step == 0 => {
|
||||
push!()(&binary.right);
|
||||
push!()(&binary.left);
|
||||
None
|
||||
}
|
||||
Expression::Binary(binary) if step == 1 => {
|
||||
let rhs = self.pop_value()?;
|
||||
let lhs = self.pop_value()?;
|
||||
Some(evaluate_binary(binary.span, binary.op, lhs, rhs)?)
|
||||
}
|
||||
Expression::Call(call) if step == 0 => {
|
||||
call.arguments.iter().rev().for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Call(call) if step == 1 => {
|
||||
let len = self.values.len();
|
||||
let (program, name) = match &*call.function {
|
||||
Expression::Identifier(id) => {
|
||||
let program = call.program.unwrap_or_else(|| {
|
||||
self.contexts.current_program().expect("there should be a current program")
|
||||
});
|
||||
(program, id.name)
|
||||
}
|
||||
Expression::Locator(locator) => (locator.program.name.name, locator.name),
|
||||
_ => tc_fail!(),
|
||||
};
|
||||
// It's a bit cheesy to collect the arguments into a Vec first, but it's the easiest way
|
||||
// to handle lifetimes here.
|
||||
let arguments: Vec<Value> = self.values.drain(len - call.arguments.len()..).collect();
|
||||
self.do_call(
|
||||
program,
|
||||
name,
|
||||
arguments.into_iter(),
|
||||
false, // finalize
|
||||
call.span(),
|
||||
)?;
|
||||
None
|
||||
}
|
||||
Expression::Call(_call) if step == 2 => Some(self.pop_value()?),
|
||||
Expression::Cast(cast) if step == 0 => {
|
||||
push!()(&*cast.expression);
|
||||
None
|
||||
}
|
||||
Expression::Cast(cast) if step == 1 => {
|
||||
let span = cast.span();
|
||||
let arg = self.pop_value()?;
|
||||
match arg.cast(&cast.type_) {
|
||||
Some(value) => Some(value),
|
||||
None => return Err(InterpreterHalt::new_spanned("cast failure".to_string(), span).into()),
|
||||
}
|
||||
}
|
||||
Expression::Err(_) => todo!(),
|
||||
Expression::Identifier(identifier) if step == 0 => {
|
||||
Some(self.lookup(identifier.name).expect_tc(identifier.span())?)
|
||||
}
|
||||
Expression::Literal(literal) if step == 0 => Some(match literal {
|
||||
Literal::Boolean(b, ..) => Value::Bool(*b),
|
||||
Literal::Integer(IntegerType::U8, s, ..) => Value::U8(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U16, s, ..) => Value::U16(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U32, s, ..) => Value::U32(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U64, s, ..) => Value::U64(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U128, s, ..) => Value::U128(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I8, s, ..) => Value::I8(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I16, s, ..) => Value::I16(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I32, s, ..) => Value::I32(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I64, s, ..) => Value::I64(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I128, s, ..) => Value::I128(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Field(s, ..) => Value::Field(format!("{s}field").parse().expect_tc(literal.span())?),
|
||||
Literal::Group(group_literal) => match &**group_literal {
|
||||
GroupLiteral::Single(s, ..) => Value::Group(format!("{s}group").parse().expect_tc(literal.span())?),
|
||||
GroupLiteral::Tuple(_group_tuple) => todo!(),
|
||||
},
|
||||
Literal::Address(s, ..) => Value::Address(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Scalar(s, ..) => Value::Scalar(format!("{s}scalar").parse().expect_tc(literal.span())?),
|
||||
Literal::String(..) => tc_fail!(),
|
||||
}),
|
||||
Expression::Locator(_locator) => todo!(),
|
||||
Expression::Struct(struct_) if step == 0 => {
|
||||
struct_.members.iter().rev().flat_map(|init| init.expression.as_ref()).for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Struct(struct_) if step == 1 => {
|
||||
// Collect all the key/value pairs into a HashMap.
|
||||
let mut contents_tmp = HashMap::with_capacity(struct_.members.len());
|
||||
for initializer in struct_.members.iter() {
|
||||
let name = initializer.identifier.name;
|
||||
let value = if initializer.expression.is_some() {
|
||||
self.pop_value()?
|
||||
} else {
|
||||
self.lookup(name).expect_tc(struct_.span())?
|
||||
};
|
||||
contents_tmp.insert(name, value);
|
||||
}
|
||||
|
||||
// And now put them into an IndexMap in the correct order.
|
||||
let program = self.contexts.current_program().expect("there should be a current program");
|
||||
let id = GlobalId { program, name: struct_.name.name };
|
||||
let struct_type = self.structs.get(&id).expect_tc(struct_.span())?;
|
||||
let contents = struct_type
|
||||
.iter()
|
||||
.map(|sym| (*sym, contents_tmp.remove(sym).expect("we just inserted this")))
|
||||
.collect();
|
||||
|
||||
Some(Value::Struct(StructContents { name: struct_.name.name, contents }))
|
||||
}
|
||||
Expression::Ternary(ternary) if step == 0 => {
|
||||
push!()(&*ternary.condition);
|
||||
None
|
||||
}
|
||||
Expression::Ternary(ternary) if step == 1 => {
|
||||
let condition = self.pop_value()?;
|
||||
match condition {
|
||||
Value::Bool(true) => push!()(&*ternary.if_true),
|
||||
Value::Bool(false) => push!()(&*ternary.if_false),
|
||||
_ => tc_fail!(),
|
||||
}
|
||||
None
|
||||
}
|
||||
Expression::Ternary(_) if step == 2 => Some(self.pop_value()?),
|
||||
Expression::Tuple(tuple) if step == 0 => {
|
||||
tuple.elements.iter().rev().for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Tuple(tuple) if step == 1 => {
|
||||
let len = self.values.len();
|
||||
let tuple_values = self.values.drain(len - tuple.elements.len()..).collect();
|
||||
Some(Value::Tuple(tuple_values))
|
||||
}
|
||||
Expression::Unary(unary) if step == 0 => {
|
||||
push!()(&*unary.receiver);
|
||||
None
|
||||
}
|
||||
Expression::Unary(unary) if step == 1 => {
|
||||
let value = self.pop_value()?;
|
||||
Some(evaluate_unary(unary.span, unary.op, value)?)
|
||||
}
|
||||
Expression::Unit(_) if step == 0 => Some(Value::Unit),
|
||||
_ => unreachable!(),
|
||||
} {
|
||||
assert_eq!(self.frames.len(), len);
|
||||
self.frames.pop();
|
||||
self.values.push(value);
|
||||
Ok(true)
|
||||
} else {
|
||||
self.frames[len - 1].step += 1;
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute one step of the current element.
|
||||
///
|
||||
/// Many Leo constructs require multiple steps. For instance, when executing a conditional,
|
||||
/// the first step will push the condition expression to the stack. Once that has executed
|
||||
/// and we've returned to the conditional, we push the `then` or `otherwise` block to the
|
||||
/// stack. Once that has executed and we've returned to the conditional, the final step
|
||||
/// does nothing.
|
||||
pub fn step(&mut self) -> Result<StepResult> {
|
||||
if self.frames.is_empty() {
|
||||
return Err(InterpreterHalt::new("no execution frames available".into()).into());
|
||||
}
|
||||
|
||||
let Frame { element, step, user_initiated } = self.frames.last().expect("there should be a frame");
|
||||
let user_initiated = *user_initiated;
|
||||
match element {
|
||||
Element::Block { block, function_body } => {
|
||||
let finished = self.step_block(block, *function_body, *step);
|
||||
Ok(StepResult { finished, value: None })
|
||||
}
|
||||
Element::Statement(statement) => {
|
||||
let finished = self.step_statement(statement, *step)?;
|
||||
Ok(StepResult { finished, value: None })
|
||||
}
|
||||
Element::Expression(expression) => {
|
||||
let finished = self.step_expression(expression, *step)?;
|
||||
let value = match (finished, user_initiated) {
|
||||
(false, _) => None,
|
||||
(true, false) => self.values.last().cloned(),
|
||||
(true, true) => self.values.pop(),
|
||||
};
|
||||
let maybe_future = if let Some(Value::Tuple(vals)) = &value { vals.last() } else { value.as_ref() };
|
||||
|
||||
if let Some(Value::Future(future)) = &maybe_future {
|
||||
if user_initiated && !future.0.is_empty() {
|
||||
self.futures.push(future.clone());
|
||||
}
|
||||
}
|
||||
Ok(StepResult { finished, value })
|
||||
}
|
||||
Element::AleoExecution { .. } => {
|
||||
self.step_aleo()?;
|
||||
Ok(StepResult { finished: true, value: None })
|
||||
}
|
||||
Element::DelayedCall(gid) if *step == 0 => {
|
||||
match self.lookup_function(gid.program, gid.name).expect("function should exist") {
|
||||
FunctionVariant::Leo(function) => {
|
||||
assert!(function.variant == Variant::AsyncFunction);
|
||||
let len = self.values.len();
|
||||
let values_iter = self.values.drain(len - function.input.len()..);
|
||||
self.contexts.push(
|
||||
gid.program,
|
||||
self.signer,
|
||||
true, // is_async
|
||||
);
|
||||
let param_names = function.input.iter().map(|input| input.identifier.name);
|
||||
for (name, value) in param_names.zip(values_iter) {
|
||||
self.contexts.set(name, value);
|
||||
}
|
||||
self.frames.last_mut().unwrap().step = 1;
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::Block { block: &function.block, function_body: true },
|
||||
user_initiated: false,
|
||||
});
|
||||
Ok(StepResult { finished: false, value: None })
|
||||
}
|
||||
FunctionVariant::AleoFunction(function) => {
|
||||
let Some(finalize_f) = function.finalize_logic() else {
|
||||
panic!("must have finalize logic for a delayed call");
|
||||
};
|
||||
let len = self.values.len();
|
||||
let values_iter = self.values.drain(len - finalize_f.inputs().len()..);
|
||||
self.contexts.push(
|
||||
gid.program,
|
||||
self.signer,
|
||||
true, // is_async
|
||||
);
|
||||
self.frames.last_mut().unwrap().step = 1;
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::AleoExecution {
|
||||
context: AleoContext::Finalize(finalize_f),
|
||||
registers: values_iter.enumerate().map(|(i, v)| (i as u64, v)).collect(),
|
||||
instruction_index: 0,
|
||||
},
|
||||
user_initiated: false,
|
||||
});
|
||||
Ok(StepResult { finished: false, value: None })
|
||||
}
|
||||
FunctionVariant::AleoClosure(..) => panic!("A call to a closure can't be delayed"),
|
||||
}
|
||||
}
|
||||
Element::DelayedCall(_gid) => {
|
||||
assert_eq!(*step, 1);
|
||||
let value = self.values.pop();
|
||||
let Some(Value::Future(future)) = value else {
|
||||
panic!("Delayed calls should always be to async functions");
|
||||
};
|
||||
if !future.0.is_empty() {
|
||||
self.futures.push(future.clone());
|
||||
}
|
||||
self.frames.pop();
|
||||
Ok(StepResult { finished: true, value: Some(Value::Future(future)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_call(
|
||||
&mut self,
|
||||
function_program: Symbol,
|
||||
function_name: Symbol,
|
||||
arguments: impl Iterator<Item = Value>,
|
||||
finalize: bool,
|
||||
span: Span,
|
||||
) -> Result<()> {
|
||||
let Some(function_variant) = self.lookup_function(function_program, function_name) else {
|
||||
halt!(span, "unknown function {function_program}.aleo/{function_name}");
|
||||
};
|
||||
match function_variant {
|
||||
FunctionVariant::Leo(function) => {
|
||||
let caller = if matches!(function.variant, Variant::Transition | Variant::AsyncTransition) {
|
||||
self.new_caller()
|
||||
} else {
|
||||
self.signer
|
||||
};
|
||||
if self.really_async && function.variant == Variant::AsyncFunction {
|
||||
// Don't actually run the call now.
|
||||
let async_ex = AsyncExecution {
|
||||
function: GlobalId { name: function_name, program: function_program },
|
||||
arguments: arguments.collect(),
|
||||
};
|
||||
self.values.push(Value::Future(Future(vec![async_ex])));
|
||||
} else {
|
||||
let is_async = function.variant == Variant::AsyncFunction;
|
||||
self.contexts.push(function_program, caller, is_async);
|
||||
let param_names = function.input.iter().map(|input| input.identifier.name);
|
||||
for (name, value) in param_names.zip(arguments) {
|
||||
self.contexts.set(name, value);
|
||||
}
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::Block { block: &function.block, function_body: true },
|
||||
user_initiated: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
FunctionVariant::AleoClosure(closure) => {
|
||||
self.contexts.push(function_program, self.signer, false);
|
||||
let context = AleoContext::Closure(closure);
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::AleoExecution {
|
||||
context,
|
||||
registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(),
|
||||
instruction_index: 0,
|
||||
},
|
||||
user_initiated: false,
|
||||
});
|
||||
}
|
||||
FunctionVariant::AleoFunction(function) => {
|
||||
let caller = self.new_caller();
|
||||
self.contexts.push(function_program, caller, false);
|
||||
let context = if finalize {
|
||||
let Some(finalize_f) = function.finalize_logic() else {
|
||||
panic!("finalize call with no finalize logic");
|
||||
};
|
||||
AleoContext::Finalize(finalize_f)
|
||||
} else {
|
||||
AleoContext::Function(function)
|
||||
};
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::AleoExecution {
|
||||
context,
|
||||
registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(),
|
||||
instruction_index: 0,
|
||||
},
|
||||
user_initiated: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn evaluate_core_function(
|
||||
&mut self,
|
||||
core_function: CoreFunction,
|
||||
arguments: &[Expression],
|
||||
span: Span,
|
||||
) -> Result<Value> {
|
||||
macro_rules! apply {
|
||||
($func: expr, $value: ident, $to: ident) => {{
|
||||
let v = self.pop_value()?;
|
||||
@ -1616,20 +2013,20 @@ impl<'a> Cursor<'a> {
|
||||
),
|
||||
CoreFunction::MappingGet => {
|
||||
let key = self.values.pop().expect_tc(span)?;
|
||||
let (program, name) = match &function.arguments[0] {
|
||||
let (program, name) = match &arguments[0] {
|
||||
Expression::Identifier(id) => (None, id.name),
|
||||
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
|
||||
_ => tc_fail!(),
|
||||
};
|
||||
match self.lookup_mapping(program, name).and_then(|mapping| mapping.get(&key)) {
|
||||
Some(v) => v.clone(),
|
||||
None => halt!(function.span(), "map lookup failure"),
|
||||
None => halt!(span, "map lookup failure"),
|
||||
}
|
||||
}
|
||||
CoreFunction::MappingGetOrUse => {
|
||||
let use_value = self.values.pop().expect_tc(span)?;
|
||||
let key = self.values.pop().expect_tc(span)?;
|
||||
let (program, name) = match &function.arguments[0] {
|
||||
let (program, name) = match &arguments[0] {
|
||||
Expression::Identifier(id) => (None, id.name),
|
||||
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
|
||||
_ => tc_fail!(),
|
||||
@ -1642,7 +2039,7 @@ impl<'a> Cursor<'a> {
|
||||
CoreFunction::MappingSet => {
|
||||
let value = self.pop_value()?;
|
||||
let key = self.pop_value()?;
|
||||
let (program, name) = match &function.arguments[0] {
|
||||
let (program, name) = match &arguments[0] {
|
||||
Expression::Identifier(id) => (None, id.name),
|
||||
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
|
||||
_ => tc_fail!(),
|
||||
@ -1656,7 +2053,7 @@ impl<'a> Cursor<'a> {
|
||||
}
|
||||
CoreFunction::MappingRemove => {
|
||||
let key = self.pop_value()?;
|
||||
let (program, name) = match &function.arguments[0] {
|
||||
let (program, name) = match &arguments[0] {
|
||||
Expression::Identifier(id) => (None, id.name),
|
||||
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
|
||||
_ => tc_fail!(),
|
||||
@ -1670,7 +2067,7 @@ impl<'a> Cursor<'a> {
|
||||
}
|
||||
CoreFunction::MappingContains => {
|
||||
let key = self.pop_value()?;
|
||||
let (program, name) = match &function.arguments[0] {
|
||||
let (program, name) = match &arguments[0] {
|
||||
Expression::Identifier(id) => (None, id.name),
|
||||
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
|
||||
_ => tc_fail!(),
|
||||
@ -1702,261 +2099,8 @@ impl<'a> Cursor<'a> {
|
||||
Value::Unit
|
||||
}
|
||||
};
|
||||
Some(value)
|
||||
}
|
||||
Expression::Array(array) if step == 0 => {
|
||||
array.elements.iter().rev().for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Array(array) if step == 1 => {
|
||||
let len = self.values.len();
|
||||
let array_values = self.values.drain(len - array.elements.len()..).collect();
|
||||
Some(Value::Array(array_values))
|
||||
}
|
||||
Expression::Binary(binary) if step == 0 => {
|
||||
push!()(&binary.right);
|
||||
push!()(&binary.left);
|
||||
None
|
||||
}
|
||||
Expression::Binary(binary) if step == 1 => {
|
||||
let rhs = self.pop_value()?;
|
||||
let lhs = self.pop_value()?;
|
||||
Some(evaluate_binary(binary.span, binary.op, lhs, rhs)?)
|
||||
}
|
||||
Expression::Call(call) if step == 0 => {
|
||||
call.arguments.iter().rev().for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Call(call) if step == 1 => {
|
||||
let len = self.values.len();
|
||||
let (program, name) = match &*call.function {
|
||||
Expression::Identifier(id) => {
|
||||
let program = call.program.unwrap_or_else(|| {
|
||||
self.contexts.current_program().expect("there should be a current program")
|
||||
});
|
||||
(program, id.name)
|
||||
}
|
||||
Expression::Locator(locator) => (locator.program.name.name, locator.name),
|
||||
_ => tc_fail!(),
|
||||
};
|
||||
let function = self.lookup_function(program, name).expect_tc(call.span())?;
|
||||
let values_iter = self.values.drain(len - call.arguments.len()..);
|
||||
if self.really_async && function.variant == Variant::AsyncFunction {
|
||||
// Don't actually run the call now.
|
||||
let async_ex =
|
||||
AsyncExecution { function: GlobalId { name, program }, arguments: values_iter.collect() };
|
||||
self.values.push(Value::Future(Future(vec![async_ex])));
|
||||
} else {
|
||||
let is_async = function.variant == Variant::AsyncFunction;
|
||||
let caller = if matches!(function.variant, Variant::Transition | Variant::AsyncTransition) {
|
||||
if let Some(function_context) = self.contexts.last() {
|
||||
let program_id = ProgramID::<TestnetV0>::from_str(&format!("{}", function_context.program))
|
||||
.expect("should be able to create ProgramID");
|
||||
program_id.to_address().expect("should be able to convert to address")
|
||||
} else {
|
||||
self.signer
|
||||
}
|
||||
} else {
|
||||
self.signer
|
||||
};
|
||||
self.contexts.push(program, caller, is_async);
|
||||
let param_names = function.input.iter().map(|input| input.identifier.name);
|
||||
for (name, value) in param_names.zip(values_iter) {
|
||||
self.contexts.set(name, value);
|
||||
}
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::Block { block: &function.block, function_body: true },
|
||||
user_initiated: false,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
Expression::Call(_call) if step == 2 => Some(self.pop_value()?),
|
||||
Expression::Cast(cast) if step == 0 => {
|
||||
push!()(&*cast.expression);
|
||||
None
|
||||
}
|
||||
Expression::Cast(cast) if step == 1 => {
|
||||
let span = cast.span();
|
||||
let arg = self.pop_value()?;
|
||||
match arg.cast(&cast.type_) {
|
||||
Some(value) => Some(value),
|
||||
None => return Err(InterpreterHalt::new_spanned("cast failure".to_string(), span).into()),
|
||||
}
|
||||
}
|
||||
Expression::Err(_) => todo!(),
|
||||
Expression::Identifier(identifier) if step == 0 => {
|
||||
Some(self.lookup(identifier.name).expect_tc(identifier.span())?)
|
||||
}
|
||||
Expression::Literal(literal) if step == 0 => Some(match literal {
|
||||
Literal::Boolean(b, ..) => Value::Bool(*b),
|
||||
Literal::Integer(IntegerType::U8, s, ..) => Value::U8(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U16, s, ..) => Value::U16(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U32, s, ..) => Value::U32(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U64, s, ..) => Value::U64(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::U128, s, ..) => Value::U128(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I8, s, ..) => Value::I8(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I16, s, ..) => Value::I16(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I32, s, ..) => Value::I32(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I64, s, ..) => Value::I64(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Integer(IntegerType::I128, s, ..) => Value::I128(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Field(s, ..) => Value::Field(format!("{s}field").parse().expect_tc(literal.span())?),
|
||||
Literal::Group(group_literal) => match &**group_literal {
|
||||
GroupLiteral::Single(s, ..) => Value::Group(format!("{s}group").parse().expect_tc(literal.span())?),
|
||||
GroupLiteral::Tuple(_group_tuple) => todo!(),
|
||||
},
|
||||
Literal::Address(s, ..) => Value::Address(s.parse().expect_tc(literal.span())?),
|
||||
Literal::Scalar(s, ..) => Value::Scalar(format!("{s}scalar").parse().expect_tc(literal.span())?),
|
||||
Literal::String(..) => tc_fail!(),
|
||||
}),
|
||||
Expression::Locator(_locator) => todo!(),
|
||||
Expression::Struct(struct_) if step == 0 => {
|
||||
struct_.members.iter().rev().flat_map(|init| init.expression.as_ref()).for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Struct(struct_) if step == 1 => {
|
||||
// Collect all the key/value pairs into a HashMap.
|
||||
let mut contents_tmp = HashMap::with_capacity(struct_.members.len());
|
||||
for initializer in struct_.members.iter() {
|
||||
let name = initializer.identifier.name;
|
||||
let value = if initializer.expression.is_some() {
|
||||
self.pop_value()?
|
||||
} else {
|
||||
self.lookup(name).expect_tc(struct_.span())?
|
||||
};
|
||||
contents_tmp.insert(name, value);
|
||||
}
|
||||
|
||||
// And now put them into an IndexMap in the correct order.
|
||||
let program = self.contexts.current_program().expect("there should be a current program");
|
||||
let id = GlobalId { program, name: struct_.name.name };
|
||||
let struct_type = self.structs.get(&id).expect_tc(struct_.span())?;
|
||||
let contents = struct_type
|
||||
.iter()
|
||||
.map(|sym| (*sym, contents_tmp.remove(sym).expect("we just inserted this")))
|
||||
.collect();
|
||||
|
||||
Some(Value::Struct(StructContents { name: struct_.name.name, contents }))
|
||||
}
|
||||
Expression::Ternary(ternary) if step == 0 => {
|
||||
push!()(&*ternary.condition);
|
||||
None
|
||||
}
|
||||
Expression::Ternary(ternary) if step == 1 => {
|
||||
let condition = self.pop_value()?;
|
||||
match condition {
|
||||
Value::Bool(true) => push!()(&*ternary.if_true),
|
||||
Value::Bool(false) => push!()(&*ternary.if_false),
|
||||
_ => tc_fail!(),
|
||||
}
|
||||
None
|
||||
}
|
||||
Expression::Ternary(_) if step == 2 => Some(self.pop_value()?),
|
||||
Expression::Tuple(tuple) if step == 0 => {
|
||||
tuple.elements.iter().rev().for_each(push!());
|
||||
None
|
||||
}
|
||||
Expression::Tuple(tuple) if step == 1 => {
|
||||
let len = self.values.len();
|
||||
let tuple_values = self.values.drain(len - tuple.elements.len()..).collect();
|
||||
Some(Value::Tuple(tuple_values))
|
||||
}
|
||||
Expression::Unary(unary) if step == 0 => {
|
||||
push!()(&*unary.receiver);
|
||||
None
|
||||
}
|
||||
Expression::Unary(unary) if step == 1 => {
|
||||
let value = self.pop_value()?;
|
||||
Some(evaluate_unary(unary.span, unary.op, value)?)
|
||||
}
|
||||
Expression::Unit(_) if step == 0 => Some(Value::Unit),
|
||||
_ => unreachable!(),
|
||||
} {
|
||||
assert_eq!(self.frames.len(), len);
|
||||
self.frames.pop();
|
||||
self.values.push(value);
|
||||
Ok(true)
|
||||
} else {
|
||||
self.frames[len - 1].step += 1;
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute one step of the current element.
|
||||
///
|
||||
/// Many Leo constructs require multiple steps. For instance, when executing a conditional,
|
||||
/// the first step will push the condition expression to the stack. Once that has executed
|
||||
/// and we've returned to the conditional, we push the `then` or `otherwise` block to the
|
||||
/// stack. Once that has executed and we've returned to the conditional, the final step
|
||||
/// does nothing.
|
||||
pub fn step(&mut self) -> Result<StepResult> {
|
||||
if self.frames.is_empty() {
|
||||
return Err(InterpreterHalt::new("no execution frames available".into()).into());
|
||||
}
|
||||
|
||||
let Frame { element, step, user_initiated } = self.frames.last().expect("there should be a frame");
|
||||
let user_initiated = *user_initiated;
|
||||
match element {
|
||||
Element::Block { block, function_body } => {
|
||||
let finished = self.step_block(block, *function_body, *step);
|
||||
Ok(StepResult { finished, value: None })
|
||||
}
|
||||
Element::Statement(statement) => {
|
||||
let finished = self.step_statement(statement, *step)?;
|
||||
Ok(StepResult { finished, value: None })
|
||||
}
|
||||
Element::Expression(expression) => {
|
||||
let finished = self.step_expression(expression, *step)?;
|
||||
let value = match (finished, user_initiated) {
|
||||
(false, _) => None,
|
||||
(true, false) => self.values.last().cloned(),
|
||||
(true, true) => self.values.pop(),
|
||||
};
|
||||
let maybe_future = if let Some(Value::Tuple(vals)) = &value { vals.last() } else { value.as_ref() };
|
||||
|
||||
if let Some(Value::Future(future)) = &maybe_future {
|
||||
if user_initiated && !future.0.is_empty() {
|
||||
self.futures.push(future.clone());
|
||||
}
|
||||
}
|
||||
Ok(StepResult { finished, value })
|
||||
}
|
||||
Element::DelayedCall(gid) if *step == 0 => {
|
||||
let function = self.lookup_function(gid.program, gid.name).expect("function should exist");
|
||||
assert!(function.variant == Variant::AsyncFunction);
|
||||
let len = self.values.len();
|
||||
let values_iter = self.values.drain(len - function.input.len()..);
|
||||
self.contexts.push(
|
||||
gid.program,
|
||||
self.signer,
|
||||
true, // is_async
|
||||
);
|
||||
let param_names = function.input.iter().map(|input| input.identifier.name);
|
||||
for (name, value) in param_names.zip(values_iter) {
|
||||
self.contexts.set(name, value);
|
||||
}
|
||||
self.frames.last_mut().unwrap().step = 1;
|
||||
self.frames.push(Frame {
|
||||
step: 0,
|
||||
element: Element::Block { block: &function.block, function_body: true },
|
||||
user_initiated: false,
|
||||
});
|
||||
Ok(StepResult { finished: false, value: None })
|
||||
}
|
||||
Element::DelayedCall(_gid) => {
|
||||
assert_eq!(*step, 1);
|
||||
let value = self.values.pop();
|
||||
let Some(Value::Future(future)) = value else {
|
||||
panic!("Delayed calls should always be to async functions");
|
||||
};
|
||||
if !future.0.is_empty() {
|
||||
self.futures.push(future.clone());
|
||||
}
|
||||
self.frames.pop();
|
||||
Ok(StepResult { finished: true, value: Some(Value::Future(future)) })
|
||||
}
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1970,7 +2114,7 @@ pub struct StepResult {
|
||||
}
|
||||
|
||||
/// Evaluate a binary operation.
|
||||
fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> Result<Value> {
|
||||
pub fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> Result<Value> {
|
||||
let value = match op {
|
||||
BinaryOperation::Add => {
|
||||
let Some(value) = (match (lhs, rhs) {
|
||||
@ -2389,7 +2533,7 @@ fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> R
|
||||
}
|
||||
|
||||
/// Evaluate a unary operation.
|
||||
fn evaluate_unary(span: Span, op: UnaryOperation, value: Value) -> Result<Value> {
|
||||
pub fn evaluate_unary(span: Span, op: UnaryOperation, value: Value) -> Result<Value> {
|
||||
let value_result = match op {
|
||||
UnaryOperation::Abs => match value {
|
||||
Value::I8(x) => {
|
||||
|
886
interpreter/src/cursor_aleo.rs
Normal file
886
interpreter/src/cursor_aleo.rs
Normal file
@ -0,0 +1,886 @@
|
||||
// Copyright (C) 2019-2024 Aleo Systems Inc.
|
||||
// This file is part of the Leo library.
|
||||
|
||||
// The Leo library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// The Leo library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use leo_ast::{BinaryOperation, CoreFunction, IntegerType, Type, UnaryOperation};
|
||||
|
||||
use snarkvm::{
|
||||
prelude::{Boolean, Identifier, Literal, LiteralType, PlaintextType, Register, TestnetV0, integers::Integer},
|
||||
synthesizer::{Command, Instruction},
|
||||
};
|
||||
use snarkvm_synthesizer_program::{CallOperator, CastType, Operand};
|
||||
|
||||
use std::mem;
|
||||
|
||||
impl Cursor<'_> {
|
||||
fn get_register(&self, reg: Register<TestnetV0>) -> &Value {
|
||||
let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last() else {
|
||||
panic!();
|
||||
};
|
||||
match reg {
|
||||
Register::Locator(index) => {
|
||||
registers.get(&index).expect("valid .aleo code doesn't access undefined registers")
|
||||
}
|
||||
Register::Access(_, _) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_register(&mut self, reg: Register<TestnetV0>, value: Value) {
|
||||
let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last_mut() else {
|
||||
panic!();
|
||||
};
|
||||
|
||||
match reg {
|
||||
Register::Locator(index) => {
|
||||
registers.insert(index, value);
|
||||
}
|
||||
Register::Access(_, _) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn instructions_len(&self) -> usize {
|
||||
let Some(Frame { element: Element::AleoExecution { context, .. }, .. }) = self.frames.last() else {
|
||||
panic!();
|
||||
};
|
||||
match context {
|
||||
AleoContext::Closure(closure) => closure.instructions().len(),
|
||||
AleoContext::Function(function) => function.instructions().len(),
|
||||
AleoContext::Finalize(finalize) => finalize.commands().len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_instruction_index(&mut self) {
|
||||
let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last_mut()
|
||||
else {
|
||||
panic!();
|
||||
};
|
||||
*instruction_index += 1;
|
||||
}
|
||||
|
||||
fn execution_complete(&self) -> bool {
|
||||
let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last() else {
|
||||
panic!();
|
||||
};
|
||||
*instruction_index >= self.instructions_len()
|
||||
}
|
||||
|
||||
fn next_instruction(&self) -> Option<&Instruction<TestnetV0>> {
|
||||
let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last()
|
||||
else {
|
||||
panic!();
|
||||
};
|
||||
match context {
|
||||
AleoContext::Closure(closure) => closure.instructions().get(*instruction_index),
|
||||
AleoContext::Function(function) => function.instructions().get(*instruction_index),
|
||||
AleoContext::Finalize(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_command(&self) -> Option<&Command<TestnetV0>> {
|
||||
let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last()
|
||||
else {
|
||||
panic!();
|
||||
};
|
||||
match context {
|
||||
AleoContext::Closure(_) | AleoContext::Function(_) => None,
|
||||
AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index),
|
||||
}
|
||||
}
|
||||
|
||||
fn operand_value(&self, operand: &Operand<TestnetV0>) -> Value {
|
||||
match operand {
|
||||
Operand::Literal(literal) => match literal {
|
||||
Literal::Address(x) => Value::Address(*x),
|
||||
Literal::Boolean(x) => Value::Bool(**x),
|
||||
Literal::Field(x) => Value::Field(*x),
|
||||
Literal::Group(x) => Value::Group(*x),
|
||||
Literal::I8(x) => Value::I8(**x),
|
||||
Literal::I16(x) => Value::I16(**x),
|
||||
Literal::I32(x) => Value::I32(**x),
|
||||
Literal::I64(x) => Value::I64(**x),
|
||||
Literal::I128(x) => Value::I128(**x),
|
||||
Literal::U8(x) => Value::U8(**x),
|
||||
Literal::U16(x) => Value::U16(**x),
|
||||
Literal::U32(x) => Value::U32(**x),
|
||||
Literal::U64(x) => Value::U64(**x),
|
||||
Literal::U128(x) => Value::U128(**x),
|
||||
Literal::Scalar(x) => Value::Scalar(*x),
|
||||
Literal::Signature(_) => todo!(),
|
||||
Literal::String(_) => todo!(),
|
||||
},
|
||||
Operand::Register(register) => self.get_register(register.clone()).clone(),
|
||||
Operand::ProgramID(_) => todo!(),
|
||||
Operand::Signer => Value::Address(self.signer),
|
||||
Operand::Caller => {
|
||||
if let Some(function_context) = self.contexts.last() {
|
||||
Value::Address(function_context.caller)
|
||||
} else {
|
||||
Value::Address(self.signer)
|
||||
}
|
||||
}
|
||||
Operand::BlockHeight => Value::U32(self.block_height),
|
||||
Operand::NetworkID => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn step_aleo_instruction(&mut self, instruction: Instruction<TestnetV0>) -> Result<()> {
|
||||
// The Aleo VM code is a linear sequence of instructions, so we don't need to keep
|
||||
// a stack of Elements (except for calls). Just run instructions in order.
|
||||
use Instruction::*;
|
||||
|
||||
let Some(Frame { step, .. }) = self.frames.last() else {
|
||||
panic!("frame expected");
|
||||
};
|
||||
|
||||
macro_rules! unary {
|
||||
($svm_op: expr, $op: ident) => {{
|
||||
let operand = self.operand_value(&$svm_op.operands()[0]);
|
||||
let value = evaluate_unary(Default::default(), UnaryOperation::$op, operand)?;
|
||||
self.increment_instruction_index();
|
||||
(value, $svm_op.destinations()[0].clone())
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! binary {
|
||||
($svm_op: expr, $op: ident) => {{
|
||||
let operand0 = self.operand_value(&$svm_op.operands()[0]);
|
||||
let operand1 = self.operand_value(&$svm_op.operands()[1]);
|
||||
let value = evaluate_binary(Default::default(), BinaryOperation::$op, operand0, operand1)?;
|
||||
self.increment_instruction_index();
|
||||
(value, $svm_op.destinations()[0].clone())
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! commit_function {
|
||||
($commit: expr,
|
||||
$to_address: ident,
|
||||
$to_field: ident,
|
||||
$to_group: ident,
|
||||
) => {{
|
||||
let core_function = match $commit.destination_type() {
|
||||
LiteralType::Address => CoreFunction::$to_address,
|
||||
LiteralType::Field => CoreFunction::$to_field,
|
||||
LiteralType::Group => CoreFunction::$to_group,
|
||||
_ => panic!("invalid commit destination type"),
|
||||
};
|
||||
|
||||
let randomizer_value = self.operand_value(&$commit.operands()[0]);
|
||||
let operand_value = self.operand_value(&$commit.operands()[1]);
|
||||
self.values.push(randomizer_value);
|
||||
self.values.push(operand_value);
|
||||
let value = self.evaluate_core_function(core_function, &[], Span::default())?;
|
||||
self.increment_instruction_index();
|
||||
(value, $commit.destinations()[0].clone())
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! hash_function {
|
||||
($hash: expr,
|
||||
$to_address: ident,
|
||||
$to_field: ident,
|
||||
$to_group: ident,
|
||||
$to_i8: ident,
|
||||
$to_i16: ident,
|
||||
$to_i32: ident,
|
||||
$to_i64: ident,
|
||||
$to_i128: ident,
|
||||
$to_u8: ident,
|
||||
$to_u16: ident,
|
||||
$to_u32: ident,
|
||||
$to_u64: ident,
|
||||
$to_u128: ident,
|
||||
$to_scalar: ident,
|
||||
) => {{
|
||||
let core_function = match $hash.destination_type() {
|
||||
PlaintextType::Literal(LiteralType::Address) => CoreFunction::$to_address,
|
||||
PlaintextType::Literal(LiteralType::Field) => CoreFunction::$to_field,
|
||||
PlaintextType::Literal(LiteralType::Group) => CoreFunction::$to_group,
|
||||
PlaintextType::Literal(LiteralType::I8) => CoreFunction::$to_i8,
|
||||
PlaintextType::Literal(LiteralType::I16) => CoreFunction::$to_i16,
|
||||
PlaintextType::Literal(LiteralType::I32) => CoreFunction::$to_i32,
|
||||
PlaintextType::Literal(LiteralType::I64) => CoreFunction::$to_i64,
|
||||
PlaintextType::Literal(LiteralType::I128) => CoreFunction::$to_i128,
|
||||
PlaintextType::Literal(LiteralType::U8) => CoreFunction::$to_u8,
|
||||
PlaintextType::Literal(LiteralType::U16) => CoreFunction::$to_u16,
|
||||
PlaintextType::Literal(LiteralType::U32) => CoreFunction::$to_u32,
|
||||
PlaintextType::Literal(LiteralType::U64) => CoreFunction::$to_u64,
|
||||
PlaintextType::Literal(LiteralType::U128) => CoreFunction::$to_u128,
|
||||
PlaintextType::Literal(LiteralType::Scalar) => CoreFunction::$to_scalar,
|
||||
_ => panic!("invalid hash destination type"),
|
||||
};
|
||||
let operand_value = self.operand_value(&$hash.operands()[0]);
|
||||
self.values.push(operand_value);
|
||||
let value = self.evaluate_core_function(core_function, &[], Span::default())?;
|
||||
self.increment_instruction_index();
|
||||
(value, $hash.destinations()[0].clone())
|
||||
}};
|
||||
}
|
||||
|
||||
let (value, destination) = match instruction {
|
||||
Abs(abs) => unary!(abs, Abs),
|
||||
AbsWrapped(abs_wrapped) => unary!(abs_wrapped, AbsWrapped),
|
||||
Add(add) => binary!(add, Add),
|
||||
AddWrapped(add_wrapped) => binary!(add_wrapped, AddWrapped),
|
||||
And(and) => binary!(and, BitwiseAnd),
|
||||
AssertEq(assert_eq) => {
|
||||
let operand0 = self.operand_value(&assert_eq.operands()[0]);
|
||||
let operand1 = self.operand_value(&assert_eq.operands()[1]);
|
||||
if operand0.neq(&operand1) {
|
||||
halt_no_span!("assertion failure: {operand0} != {operand1}");
|
||||
}
|
||||
self.increment_instruction_index();
|
||||
return Ok(());
|
||||
}
|
||||
AssertNeq(assert_neq) => {
|
||||
let operand0 = self.operand_value(&assert_neq.operands()[0]);
|
||||
let operand1 = self.operand_value(&assert_neq.operands()[1]);
|
||||
if operand0.eq(&operand1) {
|
||||
halt_no_span!("assertion failure: {operand0} != {operand1}");
|
||||
}
|
||||
self.increment_instruction_index();
|
||||
return Ok(());
|
||||
}
|
||||
Async(async_) if *step == 0 => {
|
||||
let program = self.contexts.current_program().expect("there should be a program");
|
||||
let name = snarkvm_identifier_to_symbol(async_.function_name());
|
||||
let arguments: Vec<Value> = async_.operands().iter().map(|op| self.operand_value(op)).collect();
|
||||
if self.really_async {
|
||||
let async_ex = AsyncExecution { function: GlobalId { name, program }, arguments };
|
||||
(Value::Future(Future(vec![async_ex])), async_.destinations()[0].clone())
|
||||
} else {
|
||||
self.do_call(
|
||||
program,
|
||||
name,
|
||||
arguments.into_iter(),
|
||||
true, // finalize
|
||||
Span::default(),
|
||||
)?;
|
||||
self.increment_step();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Call(call) if *step == 0 => {
|
||||
let (program, name) = match call.operator() {
|
||||
CallOperator::Locator(locator) => (
|
||||
snarkvm_identifier_to_symbol(locator.resource()),
|
||||
snarkvm_identifier_to_symbol(locator.program_id().name()),
|
||||
),
|
||||
CallOperator::Resource(id) => (
|
||||
snarkvm_identifier_to_symbol(id),
|
||||
self.contexts.current_program().expect("there should be a program"),
|
||||
),
|
||||
};
|
||||
let arguments: Vec<Value> = call.operands().iter().map(|op| self.operand_value(op)).collect();
|
||||
self.do_call(
|
||||
program,
|
||||
name,
|
||||
arguments.into_iter(),
|
||||
false, // finalize
|
||||
Span::default(),
|
||||
)?;
|
||||
self.increment_step();
|
||||
return Ok(());
|
||||
}
|
||||
Async(async_) if *step == 1 => {
|
||||
// We've done a call, and the result is on the value stack.
|
||||
self.values.pop();
|
||||
self.set_register(async_.destinations()[0].clone(), Value::Future(Future(Vec::new())));
|
||||
self.increment_instruction_index();
|
||||
return Ok(());
|
||||
}
|
||||
Call(call) if *step == 1 => {
|
||||
// We've done a call, and the result is on the value stack.
|
||||
let Some(result) = self.values.pop() else {
|
||||
panic!("should have a result");
|
||||
};
|
||||
if call.destinations().len() == 1 {
|
||||
self.set_register(call.destinations()[0].clone(), result);
|
||||
} else {
|
||||
let Value::Tuple(tuple) = result else {
|
||||
panic!("function returning multiple values should create a tuple");
|
||||
};
|
||||
for (dest, value) in call.destinations().iter().zip(tuple.into_iter()) {
|
||||
self.set_register(dest.clone(), value);
|
||||
}
|
||||
}
|
||||
self.increment_instruction_index();
|
||||
return Ok(());
|
||||
}
|
||||
Call(_) | Async(_) => unreachable!("all cases covered above"),
|
||||
Cast(cast) => {
|
||||
let destination = cast.destinations()[0].clone();
|
||||
|
||||
self.increment_instruction_index();
|
||||
|
||||
let make_struct = |program, name_identifier| {
|
||||
let name = snarkvm_identifier_to_symbol(name_identifier);
|
||||
let id = GlobalId { program, name };
|
||||
let struct_type = self.structs.get(&id).expect("struct type should exist");
|
||||
let operands = cast.operands().iter().map(|op| self.operand_value(op));
|
||||
Value::Struct(StructContents {
|
||||
name,
|
||||
contents: struct_type.iter().cloned().zip(operands).collect(),
|
||||
})
|
||||
};
|
||||
|
||||
match cast.cast_type() {
|
||||
CastType::GroupXCoordinate => {
|
||||
let Value::Group(g) = self.operand_value(&cast.operands()[0]) else {
|
||||
tc_fail!();
|
||||
};
|
||||
let value = Value::Field(g.to_x_coordinate());
|
||||
(value, destination)
|
||||
}
|
||||
CastType::GroupYCoordinate => {
|
||||
let Value::Group(g) = self.operand_value(&cast.operands()[0]) else {
|
||||
tc_fail!();
|
||||
};
|
||||
let value = Value::Field(g.to_y_coordinate());
|
||||
(value, destination)
|
||||
}
|
||||
CastType::Plaintext(PlaintextType::Array(_array)) => {
|
||||
let value = Value::Array(cast.operands().iter().map(|op| self.operand_value(op)).collect());
|
||||
(value, destination)
|
||||
}
|
||||
CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
|
||||
let operand = self.operand_value(&cast.operands()[0]);
|
||||
let value = match operand.cast(&snarkvm_literal_type_to_type(*literal_type)) {
|
||||
Some(value) => value,
|
||||
None => halt_no_span!("cast failure"),
|
||||
};
|
||||
(value, destination)
|
||||
}
|
||||
CastType::Record(struct_name) | CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
|
||||
let program = self.contexts.current_program().expect("there should be a current program");
|
||||
let value = make_struct(program, struct_name);
|
||||
(value, destination)
|
||||
}
|
||||
CastType::ExternalRecord(locator) => {
|
||||
let program = snarkvm_identifier_to_symbol(locator.program_id().name());
|
||||
let value = make_struct(program, locator.name());
|
||||
(value, destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
CastLossy(cast_lossy) => {
|
||||
match cast_lossy.cast_type() {
|
||||
CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
|
||||
// This is the only variant supported for lossy casts.
|
||||
let operand = self.operand_value(&cast_lossy.operands()[0]);
|
||||
let operand_literal = value_to_snarkvm_literal(operand);
|
||||
let result_literal = match operand_literal.cast_lossy(*literal_type) {
|
||||
Ok(result_literal) => result_literal,
|
||||
Err(_) => halt_no_span!("cast failure"),
|
||||
};
|
||||
let destination = cast_lossy.destinations()[0].clone();
|
||||
self.increment_instruction_index();
|
||||
(snarkvm_literal_to_value(result_literal), destination)
|
||||
}
|
||||
_ => tc_fail!(),
|
||||
}
|
||||
}
|
||||
CommitBHP256(commit) => {
|
||||
commit_function!(commit, BHP256CommitToAddress, BHP256CommitToField, BHP256CommitToGroup,)
|
||||
}
|
||||
CommitBHP512(commit) => {
|
||||
commit_function!(commit, BHP512CommitToAddress, BHP512CommitToField, BHP512CommitToGroup,)
|
||||
}
|
||||
CommitBHP768(commit) => {
|
||||
commit_function!(commit, BHP768CommitToAddress, BHP768CommitToField, BHP768CommitToGroup,)
|
||||
}
|
||||
CommitBHP1024(commit) => {
|
||||
commit_function!(commit, BHP1024CommitToAddress, BHP1024CommitToField, BHP1024CommitToGroup,)
|
||||
}
|
||||
CommitPED64(commit) => {
|
||||
commit_function!(commit, Pedersen64CommitToAddress, Pedersen64CommitToField, Pedersen64CommitToGroup,)
|
||||
}
|
||||
CommitPED128(commit) => {
|
||||
commit_function!(commit, Pedersen128CommitToAddress, Pedersen128CommitToField, Pedersen128CommitToGroup,)
|
||||
}
|
||||
Div(div) => binary!(div, Div),
|
||||
DivWrapped(div_wrapped) => binary!(div_wrapped, DivWrapped),
|
||||
Double(double) => unary!(double, Double),
|
||||
GreaterThan(gt) => binary!(gt, Gt),
|
||||
GreaterThanOrEqual(gte) => binary!(gte, Gte),
|
||||
HashBHP256(hash) => hash_function!(
|
||||
hash,
|
||||
BHP256HashToAddress,
|
||||
BHP256HashToField,
|
||||
BHP256HashToGroup,
|
||||
BHP256HashToI8,
|
||||
BHP256HashToI16,
|
||||
BHP256HashToI32,
|
||||
BHP256HashToI64,
|
||||
BHP256HashToI128,
|
||||
BHP256HashToU8,
|
||||
BHP256HashToU16,
|
||||
BHP256HashToU32,
|
||||
BHP256HashToU64,
|
||||
BHP256HashToU128,
|
||||
BHP256HashToScalar,
|
||||
),
|
||||
HashBHP512(hash) => hash_function!(
|
||||
hash,
|
||||
BHP512HashToAddress,
|
||||
BHP512HashToField,
|
||||
BHP512HashToGroup,
|
||||
BHP512HashToI8,
|
||||
BHP512HashToI16,
|
||||
BHP512HashToI32,
|
||||
BHP512HashToI64,
|
||||
BHP512HashToI128,
|
||||
BHP512HashToU8,
|
||||
BHP512HashToU16,
|
||||
BHP512HashToU32,
|
||||
BHP512HashToU64,
|
||||
BHP512HashToU128,
|
||||
BHP512HashToScalar,
|
||||
),
|
||||
HashBHP768(hash) => hash_function!(
|
||||
hash,
|
||||
BHP768HashToAddress,
|
||||
BHP768HashToField,
|
||||
BHP768HashToGroup,
|
||||
BHP768HashToI8,
|
||||
BHP768HashToI16,
|
||||
BHP768HashToI32,
|
||||
BHP768HashToI64,
|
||||
BHP768HashToI128,
|
||||
BHP768HashToU8,
|
||||
BHP768HashToU16,
|
||||
BHP768HashToU32,
|
||||
BHP768HashToU64,
|
||||
BHP768HashToU128,
|
||||
BHP768HashToScalar,
|
||||
),
|
||||
HashBHP1024(hash) => hash_function!(
|
||||
hash,
|
||||
BHP1024HashToAddress,
|
||||
BHP1024HashToField,
|
||||
BHP1024HashToGroup,
|
||||
BHP1024HashToI8,
|
||||
BHP1024HashToI16,
|
||||
BHP1024HashToI32,
|
||||
BHP1024HashToI64,
|
||||
BHP1024HashToI128,
|
||||
BHP1024HashToU8,
|
||||
BHP1024HashToU16,
|
||||
BHP1024HashToU32,
|
||||
BHP1024HashToU64,
|
||||
BHP1024HashToU128,
|
||||
BHP1024HashToScalar,
|
||||
),
|
||||
HashKeccak256(hash) => hash_function!(
|
||||
hash,
|
||||
Keccak256HashToAddress,
|
||||
Keccak256HashToField,
|
||||
Keccak256HashToGroup,
|
||||
Keccak256HashToI8,
|
||||
Keccak256HashToI16,
|
||||
Keccak256HashToI32,
|
||||
Keccak256HashToI64,
|
||||
Keccak256HashToI128,
|
||||
Keccak256HashToU8,
|
||||
Keccak256HashToU16,
|
||||
Keccak256HashToU32,
|
||||
Keccak256HashToU64,
|
||||
Keccak256HashToU128,
|
||||
Keccak256HashToScalar,
|
||||
),
|
||||
HashKeccak384(hash) => hash_function!(
|
||||
hash,
|
||||
Keccak384HashToAddress,
|
||||
Keccak384HashToField,
|
||||
Keccak384HashToGroup,
|
||||
Keccak384HashToI8,
|
||||
Keccak384HashToI16,
|
||||
Keccak384HashToI32,
|
||||
Keccak384HashToI64,
|
||||
Keccak384HashToI128,
|
||||
Keccak384HashToU8,
|
||||
Keccak384HashToU16,
|
||||
Keccak384HashToU32,
|
||||
Keccak384HashToU64,
|
||||
Keccak384HashToU128,
|
||||
Keccak384HashToScalar,
|
||||
),
|
||||
HashKeccak512(hash) => hash_function!(
|
||||
hash,
|
||||
Keccak512HashToAddress,
|
||||
Keccak512HashToField,
|
||||
Keccak512HashToGroup,
|
||||
Keccak512HashToI8,
|
||||
Keccak512HashToI16,
|
||||
Keccak512HashToI32,
|
||||
Keccak512HashToI64,
|
||||
Keccak512HashToI128,
|
||||
Keccak512HashToU8,
|
||||
Keccak512HashToU16,
|
||||
Keccak512HashToU32,
|
||||
Keccak512HashToU64,
|
||||
Keccak512HashToU128,
|
||||
Keccak512HashToScalar,
|
||||
),
|
||||
HashPED64(hash) => hash_function!(
|
||||
hash,
|
||||
Pedersen64HashToAddress,
|
||||
Pedersen64HashToField,
|
||||
Pedersen64HashToGroup,
|
||||
Pedersen64HashToI8,
|
||||
Pedersen64HashToI16,
|
||||
Pedersen64HashToI32,
|
||||
Pedersen64HashToI64,
|
||||
Pedersen64HashToI128,
|
||||
Pedersen64HashToU8,
|
||||
Pedersen64HashToU16,
|
||||
Pedersen64HashToU32,
|
||||
Pedersen64HashToU64,
|
||||
Pedersen64HashToU128,
|
||||
Pedersen64HashToScalar,
|
||||
),
|
||||
HashPED128(hash) => hash_function!(
|
||||
hash,
|
||||
Pedersen128HashToAddress,
|
||||
Pedersen128HashToField,
|
||||
Pedersen128HashToGroup,
|
||||
Pedersen128HashToI8,
|
||||
Pedersen128HashToI16,
|
||||
Pedersen128HashToI32,
|
||||
Pedersen128HashToI64,
|
||||
Pedersen128HashToI128,
|
||||
Pedersen128HashToU8,
|
||||
Pedersen128HashToU16,
|
||||
Pedersen128HashToU32,
|
||||
Pedersen128HashToU64,
|
||||
Pedersen128HashToU128,
|
||||
Pedersen128HashToScalar,
|
||||
),
|
||||
HashPSD2(hash) => hash_function!(
|
||||
hash,
|
||||
Poseidon2HashToAddress,
|
||||
Poseidon2HashToField,
|
||||
Poseidon2HashToGroup,
|
||||
Poseidon2HashToI8,
|
||||
Poseidon2HashToI16,
|
||||
Poseidon2HashToI32,
|
||||
Poseidon2HashToI64,
|
||||
Poseidon2HashToI128,
|
||||
Poseidon2HashToU8,
|
||||
Poseidon2HashToU16,
|
||||
Poseidon2HashToU32,
|
||||
Poseidon2HashToU64,
|
||||
Poseidon2HashToU128,
|
||||
Poseidon2HashToScalar,
|
||||
),
|
||||
HashPSD4(hash) => hash_function!(
|
||||
hash,
|
||||
Poseidon4HashToAddress,
|
||||
Poseidon4HashToField,
|
||||
Poseidon4HashToGroup,
|
||||
Poseidon4HashToI8,
|
||||
Poseidon4HashToI16,
|
||||
Poseidon4HashToI32,
|
||||
Poseidon4HashToI64,
|
||||
Poseidon4HashToI128,
|
||||
Poseidon4HashToU8,
|
||||
Poseidon4HashToU16,
|
||||
Poseidon4HashToU32,
|
||||
Poseidon4HashToU64,
|
||||
Poseidon4HashToU128,
|
||||
Poseidon4HashToScalar,
|
||||
),
|
||||
HashPSD8(hash) => hash_function!(
|
||||
hash,
|
||||
Poseidon8HashToAddress,
|
||||
Poseidon8HashToField,
|
||||
Poseidon8HashToGroup,
|
||||
Poseidon8HashToI8,
|
||||
Poseidon8HashToI16,
|
||||
Poseidon8HashToI32,
|
||||
Poseidon8HashToI64,
|
||||
Poseidon8HashToI128,
|
||||
Poseidon8HashToU8,
|
||||
Poseidon8HashToU16,
|
||||
Poseidon8HashToU32,
|
||||
Poseidon8HashToU64,
|
||||
Poseidon8HashToU128,
|
||||
Poseidon8HashToScalar,
|
||||
),
|
||||
HashSha3_256(hash) => hash_function!(
|
||||
hash,
|
||||
SHA3_256HashToAddress,
|
||||
SHA3_256HashToField,
|
||||
SHA3_256HashToGroup,
|
||||
SHA3_256HashToI8,
|
||||
SHA3_256HashToI16,
|
||||
SHA3_256HashToI32,
|
||||
SHA3_256HashToI64,
|
||||
SHA3_256HashToI128,
|
||||
SHA3_256HashToU8,
|
||||
SHA3_256HashToU16,
|
||||
SHA3_256HashToU32,
|
||||
SHA3_256HashToU64,
|
||||
SHA3_256HashToU128,
|
||||
SHA3_256HashToScalar,
|
||||
),
|
||||
HashSha3_384(hash) => hash_function!(
|
||||
hash,
|
||||
SHA3_384HashToAddress,
|
||||
SHA3_384HashToField,
|
||||
SHA3_384HashToGroup,
|
||||
SHA3_384HashToI8,
|
||||
SHA3_384HashToI16,
|
||||
SHA3_384HashToI32,
|
||||
SHA3_384HashToI64,
|
||||
SHA3_384HashToI128,
|
||||
SHA3_384HashToU8,
|
||||
SHA3_384HashToU16,
|
||||
SHA3_384HashToU32,
|
||||
SHA3_384HashToU64,
|
||||
SHA3_384HashToU128,
|
||||
SHA3_384HashToScalar,
|
||||
),
|
||||
HashSha3_512(hash) => hash_function!(
|
||||
hash,
|
||||
SHA3_512HashToAddress,
|
||||
SHA3_512HashToField,
|
||||
SHA3_512HashToGroup,
|
||||
SHA3_512HashToI8,
|
||||
SHA3_512HashToI16,
|
||||
SHA3_512HashToI32,
|
||||
SHA3_512HashToI64,
|
||||
SHA3_512HashToI128,
|
||||
SHA3_512HashToU8,
|
||||
SHA3_512HashToU16,
|
||||
SHA3_512HashToU32,
|
||||
SHA3_512HashToU64,
|
||||
SHA3_512HashToU128,
|
||||
SHA3_512HashToScalar,
|
||||
),
|
||||
HashManyPSD2(_) | HashManyPSD4(_) | HashManyPSD8(_) => panic!("these instructions don't exist yet"),
|
||||
Inv(inv) => unary!(inv, Inverse),
|
||||
IsEq(eq) => binary!(eq, Eq),
|
||||
IsNeq(neq) => binary!(neq, Neq),
|
||||
LessThan(lt) => binary!(lt, Lt),
|
||||
LessThanOrEqual(lte) => binary!(lte, Lte),
|
||||
Modulo(modulo) => binary!(modulo, Mod),
|
||||
Mul(mul) => binary!(mul, Mul),
|
||||
MulWrapped(mul_wrapped) => binary!(mul_wrapped, MulWrapped),
|
||||
Nand(nand) => binary!(nand, Nand),
|
||||
Neg(neg) => unary!(neg, Negate),
|
||||
Nor(nor) => binary!(nor, Nor),
|
||||
Not(not) => unary!(not, Not),
|
||||
Or(or) => binary!(or, BitwiseOr),
|
||||
Pow(pow) => binary!(pow, Pow),
|
||||
PowWrapped(pow_wrapped) => binary!(pow_wrapped, PowWrapped),
|
||||
Rem(rem) => binary!(rem, Rem),
|
||||
RemWrapped(rem_wrapped) => binary!(rem_wrapped, RemWrapped),
|
||||
Shl(shl) => binary!(shl, Shl),
|
||||
ShlWrapped(shl_wrapped) => binary!(shl_wrapped, ShlWrapped),
|
||||
Shr(shr) => binary!(shr, Shr),
|
||||
ShrWrapped(shr_wrapped) => binary!(shr_wrapped, ShrWrapped),
|
||||
SignVerify(_) => todo!(),
|
||||
Square(square) => unary!(square, Square),
|
||||
SquareRoot(sqrt) => unary!(sqrt, SquareRoot),
|
||||
Sub(sub) => binary!(sub, Sub),
|
||||
SubWrapped(sub_wrapped) => binary!(sub_wrapped, SubWrapped),
|
||||
Ternary(ternary) => {
|
||||
let condition = self.operand_value(&ternary.operands()[0]);
|
||||
let result = match condition {
|
||||
Value::Bool(true) => &ternary.operands()[1],
|
||||
Value::Bool(false) => &ternary.operands()[2],
|
||||
_ => panic!(),
|
||||
};
|
||||
self.increment_instruction_index();
|
||||
(self.operand_value(result), ternary.destinations()[0].clone())
|
||||
}
|
||||
Xor(xor) => binary!(xor, Xor),
|
||||
};
|
||||
|
||||
self.set_register(destination, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn outputs(&self) -> Vec<Value> {
|
||||
let Some(Frame { element, .. }) = self.frames.last() else {
|
||||
panic!("frame expected");
|
||||
};
|
||||
let Element::AleoExecution { context, .. } = element else {
|
||||
panic!("aleo execution expected");
|
||||
};
|
||||
|
||||
let mut result = match context {
|
||||
AleoContext::Closure(closure) => {
|
||||
closure.outputs().iter().map(|output| self.operand_value(output.operand())).collect()
|
||||
}
|
||||
AleoContext::Function(function) => {
|
||||
function.outputs().iter().map(|output| self.operand_value(output.operand())).collect()
|
||||
}
|
||||
AleoContext::Finalize(_finalize) => Vec::new(),
|
||||
};
|
||||
|
||||
if result.is_empty() {
|
||||
result.push(Value::Unit);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn step_aleo_command(&mut self, command: Command<TestnetV0>) -> Result<()> {
|
||||
use Command::*;
|
||||
match command {
|
||||
Instruction(instruction) => self.step_aleo_instruction(instruction)?,
|
||||
Await(await_) => {
|
||||
let Value::Future(future) = self.get_register(await_.register().clone()) else {
|
||||
halt_no_span!("attempted to await a non-future");
|
||||
};
|
||||
self.contexts.add_future(future.clone());
|
||||
self.increment_instruction_index();
|
||||
}
|
||||
Contains(_) => todo!(),
|
||||
Get(_) => todo!(),
|
||||
GetOrUse(_) => todo!(),
|
||||
RandChaCha(_) => todo!(),
|
||||
Remove(_) => todo!(),
|
||||
Set(_) => todo!(),
|
||||
BranchEq(branch_eq) => {
|
||||
let first = self.operand_value(branch_eq.first());
|
||||
let second = self.operand_value(branch_eq.second());
|
||||
if first.eq(&second) {
|
||||
self.branch(branch_eq.position());
|
||||
} else {
|
||||
self.increment_instruction_index();
|
||||
}
|
||||
}
|
||||
BranchNeq(branch_neq) => {
|
||||
let first = self.operand_value(branch_neq.first());
|
||||
let second = self.operand_value(branch_neq.second());
|
||||
if first.neq(&second) {
|
||||
self.branch(branch_neq.position());
|
||||
} else {
|
||||
self.increment_instruction_index();
|
||||
}
|
||||
}
|
||||
Position(_) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn branch(&mut self, label: &Identifier<TestnetV0>) {
|
||||
let Some(Frame {
|
||||
element: Element::AleoExecution { instruction_index, context: AleoContext::Finalize(finalize), .. },
|
||||
..
|
||||
}) = self.frames.last_mut()
|
||||
else {
|
||||
panic!();
|
||||
};
|
||||
for (i, cmd) in finalize.commands().iter().enumerate() {
|
||||
if let Command::Position(position) = cmd {
|
||||
if position.name() == label {
|
||||
*instruction_index = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("branch to nonexistent label {}", label);
|
||||
}
|
||||
|
||||
pub fn step_aleo(&mut self) -> Result<()> {
|
||||
if let Some(command) = self.next_command().cloned() {
|
||||
self.step_aleo_command(command)?;
|
||||
} else if let Some(instruction) = self.next_instruction().cloned() {
|
||||
self.step_aleo_instruction(instruction)?;
|
||||
}
|
||||
|
||||
if self.execution_complete() {
|
||||
let mut outputs = self.outputs();
|
||||
self.frames.pop();
|
||||
if outputs.len() > 1 {
|
||||
self.values.push(Value::Tuple(outputs));
|
||||
} else {
|
||||
self.values.push(mem::take(&mut outputs[0]));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn snarkvm_literal_type_to_type(snarkvm_type: LiteralType) -> Type {
|
||||
use Type::*;
|
||||
match snarkvm_type {
|
||||
LiteralType::Address => Address,
|
||||
LiteralType::Boolean => Boolean,
|
||||
LiteralType::Field => Field,
|
||||
LiteralType::Group => Group,
|
||||
LiteralType::I8 => Integer(IntegerType::I8),
|
||||
LiteralType::I16 => Integer(IntegerType::I16),
|
||||
LiteralType::I32 => Integer(IntegerType::I32),
|
||||
LiteralType::I64 => Integer(IntegerType::I64),
|
||||
LiteralType::I128 => Integer(IntegerType::I128),
|
||||
LiteralType::U8 => Integer(IntegerType::U8),
|
||||
LiteralType::U16 => Integer(IntegerType::U16),
|
||||
LiteralType::U32 => Integer(IntegerType::U32),
|
||||
LiteralType::U64 => Integer(IntegerType::U64),
|
||||
LiteralType::U128 => Integer(IntegerType::U128),
|
||||
LiteralType::Scalar => Scalar,
|
||||
LiteralType::Signature => todo!(),
|
||||
LiteralType::String => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn snarkvm_literal_to_value(literal: Literal<TestnetV0>) -> Value {
|
||||
match literal {
|
||||
Literal::Address(x) => Value::Address(x),
|
||||
Literal::Boolean(x) => Value::Bool(*x),
|
||||
Literal::Field(x) => Value::Field(x),
|
||||
Literal::Group(x) => Value::Group(x),
|
||||
Literal::I8(x) => Value::I8(*x),
|
||||
Literal::I16(x) => Value::I16(*x),
|
||||
Literal::I32(x) => Value::I32(*x),
|
||||
Literal::I64(x) => Value::I64(*x),
|
||||
Literal::I128(x) => Value::I128(*x),
|
||||
Literal::U8(x) => Value::U8(*x),
|
||||
Literal::U16(x) => Value::U16(*x),
|
||||
Literal::U32(x) => Value::U32(*x),
|
||||
Literal::U64(x) => Value::U64(*x),
|
||||
Literal::U128(x) => Value::U128(*x),
|
||||
Literal::Scalar(x) => Value::Scalar(x),
|
||||
Literal::Signature(_) | Literal::String(_) => tc_fail!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_snarkvm_literal(value: Value) -> Literal<TestnetV0> {
|
||||
match value {
|
||||
Value::Bool(x) => Literal::Boolean(Boolean::new(x)),
|
||||
Value::U8(x) => Literal::U8(Integer::new(x)),
|
||||
Value::U16(x) => Literal::U16(Integer::new(x)),
|
||||
Value::U32(x) => Literal::U32(Integer::new(x)),
|
||||
Value::U64(x) => Literal::U64(Integer::new(x)),
|
||||
Value::U128(x) => Literal::U128(Integer::new(x)),
|
||||
Value::I8(x) => Literal::I8(Integer::new(x)),
|
||||
Value::I16(x) => Literal::I16(Integer::new(x)),
|
||||
Value::I32(x) => Literal::I32(Integer::new(x)),
|
||||
Value::I64(x) => Literal::I64(Integer::new(x)),
|
||||
Value::I128(x) => Literal::I128(Integer::new(x)),
|
||||
Value::Group(x) => Literal::Group(x),
|
||||
Value::Field(x) => Literal::Field(x),
|
||||
Value::Scalar(x) => Literal::Scalar(x),
|
||||
Value::Address(x) => Literal::Address(x),
|
||||
Value::Array(_) | Value::Tuple(_) | Value::Unit | Value::Future(_) | Value::Struct(_) => tc_fail!(),
|
||||
}
|
||||
}
|
@ -18,12 +18,12 @@ use leo_ast::{AccessExpression, AssertVariant, Ast, ConsoleFunction, Expression,
|
||||
use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler};
|
||||
use leo_span::{Span, source_map::FileName, symbol::with_session_globals};
|
||||
|
||||
use snarkvm::prelude::TestnetV0;
|
||||
use snarkvm::prelude::{Program, TestnetV0};
|
||||
|
||||
use colored::*;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
fmt::{Display, Write as _},
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
@ -34,6 +34,8 @@ use util::*;
|
||||
mod cursor;
|
||||
use cursor::*;
|
||||
|
||||
mod cursor_aleo;
|
||||
|
||||
mod value;
|
||||
use value::*;
|
||||
|
||||
@ -61,6 +63,7 @@ enum InterpreterAction {
|
||||
LeoInterpretOver(String),
|
||||
RunFuture(usize),
|
||||
Breakpoint(Breakpoint),
|
||||
PrintRegister(u64),
|
||||
Into,
|
||||
Over,
|
||||
Step,
|
||||
@ -68,12 +71,18 @@ enum InterpreterAction {
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
fn new<'a, P: 'a + AsRef<Path>>(
|
||||
source_files: impl IntoIterator<Item = &'a P>,
|
||||
fn new<'a, P: 'a + AsRef<Path>, Q: 'a + AsRef<Path>>(
|
||||
leo_source_files: impl IntoIterator<Item = &'a P>,
|
||||
aleo_source_files: impl IntoIterator<Item = &'a Q>,
|
||||
signer: SvmAddress,
|
||||
block_height: u32,
|
||||
) -> Result<Self> {
|
||||
Self::new_impl(&mut source_files.into_iter().map(|p| p.as_ref()), signer, block_height)
|
||||
Self::new_impl(
|
||||
&mut leo_source_files.into_iter().map(|p| p.as_ref()),
|
||||
&mut aleo_source_files.into_iter().map(|p| p.as_ref()),
|
||||
signer,
|
||||
block_height,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result<Ast> {
|
||||
@ -83,7 +92,12 @@ impl Interpreter {
|
||||
leo_parser::parse_ast::<TestnetV0>(handler, node_builder, &text, source_file.start_pos)
|
||||
}
|
||||
|
||||
fn new_impl(source_files: &mut dyn Iterator<Item = &Path>, signer: SvmAddress, block_height: u32) -> Result<Self> {
|
||||
fn new_impl(
|
||||
leo_source_files: &mut dyn Iterator<Item = &Path>,
|
||||
aleo_source_files: &mut dyn Iterator<Item = &Path>,
|
||||
signer: SvmAddress,
|
||||
block_height: u32,
|
||||
) -> Result<Self> {
|
||||
let handler = Handler::default();
|
||||
let node_builder = Default::default();
|
||||
let mut cursor: Cursor<'_> = Cursor::new(
|
||||
@ -92,14 +106,14 @@ impl Interpreter {
|
||||
block_height,
|
||||
);
|
||||
let mut filename_to_program = HashMap::new();
|
||||
for path in source_files {
|
||||
for path in leo_source_files {
|
||||
let ast = Self::get_ast(path, &handler, &node_builder)?;
|
||||
// TODO: This leak is silly.
|
||||
let ast = Box::leak(Box::new(ast));
|
||||
for (&program, scope) in ast.ast.program_scopes.iter() {
|
||||
filename_to_program.insert(path.to_path_buf(), program.to_string());
|
||||
for (name, function) in scope.functions.iter() {
|
||||
cursor.functions.insert(GlobalId { program, name: *name }, function);
|
||||
cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function));
|
||||
}
|
||||
|
||||
for (name, composite) in scope.structs.iter() {
|
||||
@ -126,6 +140,46 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
for path in aleo_source_files {
|
||||
let aleo_program = Self::get_aleo_program(path)?;
|
||||
// TODO: Another goofy leak.
|
||||
let aleo_program = Box::leak(Box::new(aleo_program));
|
||||
let program = snarkvm_identifier_to_symbol(aleo_program.id().name());
|
||||
filename_to_program.insert(path.to_path_buf(), program.to_string());
|
||||
|
||||
for (name, struct_type) in aleo_program.structs().iter() {
|
||||
cursor.structs.insert(
|
||||
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
|
||||
struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
for (name, record_type) in aleo_program.records().iter() {
|
||||
cursor.structs.insert(
|
||||
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
|
||||
record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
for (name, _mapping) in aleo_program.mappings().iter() {
|
||||
cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new());
|
||||
}
|
||||
|
||||
for (name, function) in aleo_program.functions().iter() {
|
||||
cursor.functions.insert(
|
||||
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
|
||||
FunctionVariant::AleoFunction(function),
|
||||
);
|
||||
}
|
||||
|
||||
for (name, closure) in aleo_program.closures().iter() {
|
||||
cursor.functions.insert(
|
||||
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
|
||||
FunctionVariant::AleoClosure(closure),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Interpreter {
|
||||
cursor,
|
||||
handler,
|
||||
@ -136,6 +190,12 @@ impl Interpreter {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aleo_program(path: &Path) -> Result<Program<TestnetV0>> {
|
||||
let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?;
|
||||
let program = text.parse()?;
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
fn action(&mut self, act: InterpreterAction) -> Result<Option<Value>> {
|
||||
use InterpreterAction::*;
|
||||
|
||||
@ -196,6 +256,19 @@ impl Interpreter {
|
||||
StepResult { finished: false, value: None }
|
||||
}
|
||||
|
||||
PrintRegister(register_index) => {
|
||||
let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last()
|
||||
else {
|
||||
halt_no_span!("cannot print register - not currently interpreting Aleo VM code");
|
||||
};
|
||||
|
||||
if let Some(value) = registers.get(register_index) {
|
||||
StepResult { finished: false, value: Some(value.clone()) }
|
||||
} else {
|
||||
halt_no_span!("no such register {register_index}");
|
||||
}
|
||||
}
|
||||
|
||||
Run => {
|
||||
while !self.cursor.frames.is_empty() {
|
||||
if let Some((program, line)) = self.current_program_and_line() {
|
||||
@ -226,10 +299,60 @@ impl Interpreter {
|
||||
Element::Expression(expression) => format!("{expression}"),
|
||||
Element::Block { block, .. } => format!("{block}"),
|
||||
Element::DelayedCall(gid) => format!("Delayed call to {gid}"),
|
||||
Element::AleoExecution { context, instruction_index, .. } => match context {
|
||||
AleoContext::Closure(closure) => closure.instructions().get(instruction_index).map(|i| format!("{i}")),
|
||||
AleoContext::Function(function) => {
|
||||
function.instructions().get(instruction_index).map(|i| format!("{i}"))
|
||||
}
|
||||
AleoContext::Finalize(finalize) => finalize.commands().get(instruction_index).map(|i| format!("{i}")),
|
||||
}
|
||||
.unwrap_or_else(|| "...".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn view_current_in_context(&self) -> Option<impl Display> {
|
||||
if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) =
|
||||
self.cursor.frames.last()
|
||||
{
|
||||
// For Aleo VM code, there are no spans; just print out the code without referring to the source code.
|
||||
|
||||
fn write_all<I: Display>(items: impl Iterator<Item = I>, instruction_index: usize) -> String {
|
||||
let mut result = String::new();
|
||||
for (i, item) in items.enumerate() {
|
||||
if i == instruction_index {
|
||||
let temp = format!(" {item}").red();
|
||||
writeln!(&mut result, "{temp}").expect("write");
|
||||
} else {
|
||||
writeln!(&mut result, " {item}").expect("write");
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
let (heading, inputs, instructions, outputs) = match context {
|
||||
AleoContext::Closure(closure) => (
|
||||
format!("closure {}\n", closure.name()),
|
||||
write_all(closure.inputs().iter(), usize::MAX),
|
||||
write_all(closure.instructions().iter(), *instruction_index),
|
||||
write_all(closure.outputs().iter(), usize::MAX),
|
||||
),
|
||||
AleoContext::Function(function) => (
|
||||
format!("function {}\n", function.name()),
|
||||
write_all(function.inputs().iter(), usize::MAX),
|
||||
write_all(function.instructions().iter(), *instruction_index),
|
||||
write_all(function.outputs().iter(), usize::MAX),
|
||||
),
|
||||
AleoContext::Finalize(finalize) => (
|
||||
format!("finalize {}\n", finalize.name()),
|
||||
write_all(finalize.inputs().iter(), usize::MAX),
|
||||
write_all(finalize.commands().iter(), *instruction_index),
|
||||
"".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
Some(format!("{heading}{inputs}{instructions}{outputs}"))
|
||||
} else {
|
||||
// For Leo code, we use spans to print the original source code.
|
||||
let span = self.current_span()?;
|
||||
if span == Default::default() {
|
||||
return None;
|
||||
@ -246,6 +369,7 @@ impl Interpreter {
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn current_program_and_line(&self) -> Option<(String, usize)> {
|
||||
if let Some(span) = self.current_span() {
|
||||
@ -396,8 +520,13 @@ fn kill_span_expression(expression: &mut Expression) {
|
||||
|
||||
/// Load all the Leo source files indicated and open the interpreter
|
||||
/// to commands from the user.
|
||||
pub fn interpret(filenames: &[PathBuf], signer: SvmAddress, block_height: u32) -> Result<()> {
|
||||
let mut interpreter = Interpreter::new(filenames.iter(), signer, block_height)?;
|
||||
pub fn interpret(
|
||||
leo_filenames: &[PathBuf],
|
||||
aleo_filenames: &[PathBuf],
|
||||
signer: SvmAddress,
|
||||
block_height: u32,
|
||||
) -> Result<()> {
|
||||
let mut interpreter = Interpreter::new(leo_filenames.iter(), aleo_filenames.iter(), signer, block_height)?;
|
||||
let mut buffer = String::new();
|
||||
println!("{}", INSTRUCTIONS);
|
||||
loop {
|
||||
@ -437,6 +566,15 @@ pub fn interpret(filenames: &[PathBuf], signer: SvmAddress, block_height: u32) -
|
||||
InterpreterAction::Breakpoint(breakpoint)
|
||||
} else if let Some(rest) = s.strip_prefix("#into ").or(s.strip_prefix("#i ")) {
|
||||
InterpreterAction::LeoInterpretInto(rest.trim().into())
|
||||
} else if let Some(rest) = s.strip_prefix("#print ").or(s.strip_prefix("#p ")) {
|
||||
let trimmed = rest.trim();
|
||||
let without_r = trimmed.strip_prefix("r").unwrap_or(trimmed);
|
||||
if let Ok(num) = without_r.parse::<u64>() {
|
||||
InterpreterAction::PrintRegister(num)
|
||||
} else {
|
||||
println!("failed to parse register number {trimmed}");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
InterpreterAction::LeoInterpretOver(s.trim().into())
|
||||
}
|
||||
|
@ -15,7 +15,9 @@
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use leo_errors::{InterpreterHalt, Result};
|
||||
use leo_span::Span;
|
||||
use leo_span::{Span, Symbol};
|
||||
|
||||
use snarkvm::prelude::{Identifier, TestnetV0};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! tc_fail {
|
||||
@ -24,6 +26,13 @@ macro_rules! tc_fail {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! halt_no_span {
|
||||
($($x:tt)*) => {
|
||||
return Err(InterpreterHalt::new(format!($($x)*)).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! halt {
|
||||
($span: expr) => {
|
||||
@ -59,3 +68,8 @@ impl<T, U: std::fmt::Debug> ExpectTc for Result<T, U> {
|
||||
self.map_err(|_e| InterpreterHalt::new_spanned("type failure".into(), span).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snarkvm_identifier_to_symbol(id: &Identifier<TestnetV0>) -> Symbol {
|
||||
let s = id.to_string();
|
||||
Symbol::intern(&s)
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ use super::*;
|
||||
/// Debugs an Aleo program through the interpreter.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct LeoDebug {
|
||||
#[arg(long, help = "Use these source files instead of finding source files through the project structure.", num_args = 1..)]
|
||||
pub(crate) paths: Vec<String>,
|
||||
|
||||
#[arg(long, help = "The block height, accessible via block.height.", default_value = "0")]
|
||||
pub(crate) block_height: u32,
|
||||
|
||||
@ -83,6 +86,7 @@ fn handle_debug<N: Network>(command: &LeoDebug, context: Context) -> Result<()>
|
||||
let private_key = context.get_private_key(&None)?;
|
||||
let address = Address::try_from(&private_key)?;
|
||||
|
||||
if command.paths.is_empty() {
|
||||
// Retrieve all local dependencies in post order
|
||||
let main_sym = Symbol::intern(&program_id.name().to_string());
|
||||
let mut retriever = Retriever::<N>::new(
|
||||
@ -106,5 +110,21 @@ fn handle_debug<N: Network>(command: &LeoDebug, context: Context) -> Result<()>
|
||||
})
|
||||
.collect();
|
||||
|
||||
leo_interpreter::interpret(&paths, address, command.block_height)
|
||||
leo_interpreter::interpret(&paths, &[], address, command.block_height)
|
||||
} else {
|
||||
let leo_paths: Vec<PathBuf> = command
|
||||
.paths
|
||||
.iter()
|
||||
.filter(|path_str| path_str.ends_with(".leo"))
|
||||
.map(|path_str| path_str.into())
|
||||
.collect();
|
||||
let aleo_paths: Vec<PathBuf> = command
|
||||
.paths
|
||||
.iter()
|
||||
.filter(|path_str| !path_str.ends_with(".leo"))
|
||||
.map(|path_str| path_str.into())
|
||||
.collect();
|
||||
|
||||
leo_interpreter::interpret(&leo_paths, &aleo_paths, address, command.block_height)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user