1
0
Fork 0
mirror of synced 2025-09-24 04:40:05 +00:00

Merge pull request #1012 from Zokrates/assertion-messages

Add optional message to assert statement
This commit is contained in:
Thibaut Schaeffer 2021-10-14 20:31:03 +03:00 committed by GitHub
commit f7b6f2de56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 419 additions and 277 deletions

View file

@ -0,0 +1 @@
Add optional message to assert statement

View file

@ -346,8 +346,11 @@ impl<'ast> From<pest::AssertionStatement<'ast>> for absy::StatementNode<'ast> {
fn from(statement: pest::AssertionStatement<'ast>) -> absy::StatementNode<'ast> {
use crate::absy::NodeValue;
absy::Statement::Assertion(absy::ExpressionNode::from(statement.expression))
.span(statement.span)
absy::Statement::Assertion(
absy::ExpressionNode::from(statement.expression),
statement.message.map(|m| m.value),
)
.span(statement.span)
}
}

View file

@ -337,7 +337,7 @@ pub enum Statement<'ast> {
Return(ExpressionListNode<'ast>),
Declaration(VariableNode<'ast>),
Definition(AssigneeNode<'ast>, ExpressionNode<'ast>),
Assertion(ExpressionNode<'ast>),
Assertion(ExpressionNode<'ast>, Option<String>),
For(
VariableNode<'ast>,
ExpressionNode<'ast>,
@ -355,7 +355,13 @@ impl<'ast> fmt::Display for Statement<'ast> {
Statement::Return(ref expr) => write!(f, "return {}", expr),
Statement::Declaration(ref var) => write!(f, "{}", var),
Statement::Definition(ref lhs, ref rhs) => write!(f, "{} = {}", lhs, rhs),
Statement::Assertion(ref e) => write!(f, "assert({})", e),
Statement::Assertion(ref e, ref message) => {
write!(f, "assert({}", e)?;
match message {
Some(m) => write!(f, ", \"{}\")", m),
None => write!(f, ")"),
}
}
Statement::For(ref var, ref start, ref stop, ref list) => {
writeln!(f, "for {} in {}..{} do", var, start, stop)?;
for l in list {

View file

@ -43,18 +43,18 @@ pub enum RuntimeError {
Euclidean,
ShaXor,
Division,
Source,
SourceAssertion(String),
ArgumentBitness,
SelectRangeCheck,
}
impl RuntimeError {
fn is_malicious(&self) -> bool {
pub(crate) fn is_malicious(&self) -> bool {
use RuntimeError::*;
!matches!(
self,
Source | Inverse | LtSum | SelectRangeCheck | ArgumentBitness
SourceAssertion(_) | Inverse | LtSum | SelectRangeCheck | ArgumentBitness
)
}
}
@ -87,19 +87,12 @@ impl fmt::Display for RuntimeError {
Euclidean => "Euclidean check failed",
ShaXor => "Internal Sha check failed",
Division => "Division check failed",
Source => "User assertion failed",
SourceAssertion(m) => m.as_str(),
ArgumentBitness => "Argument bitness check failed",
SelectRangeCheck => "Out of bounds array access",
};
write!(f, "{}", msg)?;
if self.is_malicious() {
writeln!(f)?;
write!(f, "The default ZoKrates interpreter should not yield this error. Please open an issue")?;
}
write!(f, "")
write!(f, "{}", msg)
}
}

View file

@ -12,7 +12,7 @@ use crate::ir::Interpreter;
use crate::compile::CompileConfig;
use crate::embed::FlatEmbed;
use crate::flat_absy::*;
use crate::flat_absy::{RuntimeError, *};
use crate::solvers::Solver;
use crate::zir::types::{Type, UBitwidth};
use crate::zir::*;
@ -146,6 +146,15 @@ impl<T: Field> FlatUExpression<T> {
}
}
impl From<crate::zir::RuntimeError> for RuntimeError {
fn from(error: crate::zir::RuntimeError) -> Self {
match error {
crate::zir::RuntimeError::SourceAssertion(s) => RuntimeError::SourceAssertion(s),
crate::zir::RuntimeError::SelectRangeCheck => RuntimeError::SelectRangeCheck,
}
}
}
impl<'ast, T: Field> Flattener<'ast, T> {
pub fn flatten(p: ZirProgram<'ast, T>, config: &CompileConfig) -> FlatProg<T> {
Flattener::new(config).flatten_program(p)
@ -2370,13 +2379,13 @@ impl<'ast, T: Field> Flattener<'ast, T> {
.insert(FlatExpression::Identifier(var), bits);
}
}
ZirStatement::Assertion(e) => {
ZirStatement::Assertion(e, error) => {
match e {
BooleanExpression::And(..) => {
for boolean in e.into_conjunction_iterator() {
self.flatten_statement(
statements_flattened,
ZirStatement::Assertion(boolean),
ZirStatement::Assertion(boolean, error.clone()),
)
}
}
@ -2384,7 +2393,12 @@ impl<'ast, T: Field> Flattener<'ast, T> {
let lhs = self.flatten_field_expression(statements_flattened, lhs);
let rhs = self.flatten_field_expression(statements_flattened, rhs);
self.flatten_equality_assertion(statements_flattened, lhs, rhs)
self.flatten_equality_assertion(
statements_flattened,
lhs,
rhs,
error.into(),
)
}
BooleanExpression::UintEq(box lhs, box rhs) => {
let lhs = self
@ -2394,13 +2408,23 @@ impl<'ast, T: Field> Flattener<'ast, T> {
.flatten_uint_expression(statements_flattened, rhs)
.get_field_unchecked();
self.flatten_equality_assertion(statements_flattened, lhs, rhs)
self.flatten_equality_assertion(
statements_flattened,
lhs,
rhs,
error.into(),
)
}
BooleanExpression::BoolEq(box lhs, box rhs) => {
let lhs = self.flatten_boolean_expression(statements_flattened, lhs);
let rhs = self.flatten_boolean_expression(statements_flattened, rhs);
self.flatten_equality_assertion(statements_flattened, lhs, rhs)
self.flatten_equality_assertion(
statements_flattened,
lhs,
rhs,
error.into(),
)
}
_ => {
// naive approach: flatten the boolean to a single field element and constrain it to 1
@ -2410,14 +2434,14 @@ impl<'ast, T: Field> Flattener<'ast, T> {
statements_flattened.push(FlatStatement::Condition(
e,
FlatExpression::Number(T::from(1)),
RuntimeError::Source,
error.into(),
));
} else {
// swap so that left side is linear
statements_flattened.push(FlatStatement::Condition(
FlatExpression::Number(T::from(1)),
e,
RuntimeError::Source,
error.into(),
));
}
}
@ -2529,6 +2553,7 @@ impl<'ast, T: Field> Flattener<'ast, T> {
statements_flattened: &mut FlatStatements<T>,
lhs: FlatExpression<T>,
rhs: FlatExpression<T>,
error: RuntimeError,
) {
let (lhs, rhs) = match (lhs, rhs) {
(FlatExpression::Mult(box x, box y), z) | (z, FlatExpression::Mult(box x, box y)) => (
@ -2546,7 +2571,7 @@ impl<'ast, T: Field> Flattener<'ast, T> {
),
),
};
statements_flattened.push(FlatStatement::Condition(lhs, rhs, RuntimeError::Source));
statements_flattened.push(FlatStatement::Condition(lhs, rhs, error));
}
/// Identifies a non-linear expression by assigning it to a new identifier.
@ -2653,6 +2678,7 @@ impl<'ast, T: Field> Flattener<'ast, T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::zir;
use crate::zir::types::Signature;
use crate::zir::types::Type;
use zokrates_field::Bn128Field;
@ -2679,10 +2705,13 @@ mod tests {
Variable::boolean("y".into()),
BooleanExpression::Value(true).into(),
),
ZirStatement::Assertion(BooleanExpression::BoolEq(
box BooleanExpression::Identifier("x".into()),
box BooleanExpression::Identifier("y".into()),
)),
ZirStatement::Assertion(
BooleanExpression::BoolEq(
box BooleanExpression::Identifier("x".into()),
box BooleanExpression::Identifier("y".into()),
),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -2711,7 +2740,7 @@ mod tests {
box FlatExpression::Identifier(FlatVariable::new(0)),
box FlatExpression::Number(Bn128Field::from(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};
@ -2741,13 +2770,16 @@ mod tests {
Variable::field_element("y"),
FieldElementExpression::Number(Bn128Field::from(2)).into(),
),
ZirStatement::Assertion(BooleanExpression::FieldEq(
box FieldElementExpression::Add(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Number(Bn128Field::from(1)),
ZirStatement::Assertion(
BooleanExpression::FieldEq(
box FieldElementExpression::Add(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Number(Bn128Field::from(1)),
),
box FieldElementExpression::Identifier("y".into()),
),
box FieldElementExpression::Identifier("y".into()),
)),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -2779,7 +2811,7 @@ mod tests {
),
box FlatExpression::Number(Bn128Field::from(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};
@ -2811,12 +2843,15 @@ mod tests {
.metadata(metadata.clone()),
),
),
ZirStatement::Assertion(BooleanExpression::UintEq(
box UExpressionInner::Identifier("x".into())
.annotate(32)
.metadata(metadata.clone()),
box UExpressionInner::Value(42).annotate(32).metadata(metadata),
)),
ZirStatement::Assertion(
BooleanExpression::UintEq(
box UExpressionInner::Identifier("x".into())
.annotate(32)
.metadata(metadata.clone()),
box UExpressionInner::Value(42).annotate(32).metadata(metadata),
),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -2841,7 +2876,7 @@ mod tests {
box FlatExpression::Identifier(FlatVariable::new(0)),
box FlatExpression::Number(Bn128Field::from(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};
@ -2871,10 +2906,13 @@ mod tests {
Variable::field_element("y"),
FieldElementExpression::Number(Bn128Field::from(2)).into(),
),
ZirStatement::Assertion(BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
)),
ZirStatement::Assertion(
BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -2903,7 +2941,7 @@ mod tests {
box FlatExpression::Identifier(FlatVariable::new(0)),
box FlatExpression::Number(Bn128Field::from(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};
@ -2939,13 +2977,16 @@ mod tests {
Variable::field_element("z"),
FieldElementExpression::Number(Bn128Field::from(4)).into(),
),
ZirStatement::Assertion(BooleanExpression::FieldEq(
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
ZirStatement::Assertion(
BooleanExpression::FieldEq(
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
),
box FieldElementExpression::Identifier("z".into()),
),
box FieldElementExpression::Identifier("z".into()),
)),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -2978,7 +3019,7 @@ mod tests {
box FlatExpression::Identifier(FlatVariable::new(0)),
box FlatExpression::Identifier(FlatVariable::new(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};
@ -3014,13 +3055,16 @@ mod tests {
Variable::field_element("z"),
FieldElementExpression::Number(Bn128Field::from(4)).into(),
),
ZirStatement::Assertion(BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("z".into()),
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
ZirStatement::Assertion(
BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("z".into()),
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
),
),
)),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -3053,7 +3097,7 @@ mod tests {
box FlatExpression::Identifier(FlatVariable::new(0)),
box FlatExpression::Identifier(FlatVariable::new(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};
@ -3096,16 +3140,19 @@ mod tests {
Variable::field_element("t"),
FieldElementExpression::Number(Bn128Field::from(2)).into(),
),
ZirStatement::Assertion(BooleanExpression::FieldEq(
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
ZirStatement::Assertion(
BooleanExpression::FieldEq(
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
),
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("z".into()),
box FieldElementExpression::Identifier("t".into()),
),
),
box FieldElementExpression::Mult(
box FieldElementExpression::Identifier("z".into()),
box FieldElementExpression::Identifier("t".into()),
),
)),
zir::RuntimeError::mock(),
),
],
signature: Signature {
inputs: vec![],
@ -3149,7 +3196,7 @@ mod tests {
box FlatExpression::Identifier(FlatVariable::new(0)),
box FlatExpression::Identifier(FlatVariable::new(1)),
),
RuntimeError::Source,
zir::RuntimeError::mock().into(),
),
],
};

View file

@ -1,4 +1,5 @@
use crate::flat_absy::flat_variable::FlatVariable;
use crate::flat_absy::RuntimeError;
use crate::ir::{LinComb, Prog, QuadComb, Statement, Witness};
use crate::solvers::Solver;
use serde::{Deserialize, Serialize};
@ -37,7 +38,7 @@ impl Interpreter {
for statement in program.statements.iter() {
match statement {
Statement::Constraint(quad, lin, message) => match lin.is_assignee(&witness) {
Statement::Constraint(quad, lin, error) => match lin.is_assignee(&witness) {
true => {
let val = quad.evaluate(&witness).unwrap();
witness.insert(lin.0.get(0).unwrap().0, val);
@ -47,12 +48,7 @@ impl Interpreter {
let rhs_value = lin.evaluate(&witness).unwrap();
if lhs_value != rhs_value {
return Err(Error::UnsatisfiedConstraint {
left: lhs_value.to_dec_string(),
right: rhs_value.to_dec_string(),
message: message
.as_ref()
.map(|m| m.to_string())
.unwrap_or_else(|| "Unknown".to_string()),
error: error.to_owned(),
});
}
}
@ -275,26 +271,32 @@ impl<T: Field> QuadComb<T> {
#[derive(PartialEq, Serialize, Deserialize, Clone)]
pub enum Error {
UnsatisfiedConstraint {
left: String,
right: String,
message: String,
},
UnsatisfiedConstraint { error: Option<RuntimeError> },
Solver,
WrongInputCount {
expected: usize,
received: usize,
},
WrongInputCount { expected: usize, received: usize },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::UnsatisfiedConstraint {
ref left,
ref right,
ref message,
} => write!(f, "{}: expected {} to equal {}", message, left, right),
Error::UnsatisfiedConstraint { ref error } => {
write!(
f,
"{}",
error
.as_ref()
.map(|m| m.to_string())
.expect("Found an unsatisfied constraint without an attached error.")
)?;
match error {
Some(e) if e.is_malicious() => {
writeln!(f)?;
write!(f, "The default ZoKrates interpreter should not yield this error. Please open an issue.")
}
_ => write!(f, ""),
}
}
Error::Solver => write!(f, ""),
Error::WrongInputCount { expected, received } => write!(
f,

View file

@ -1,6 +1,6 @@
use std::fmt;
#[derive(Clone, PartialEq, Copy)]
#[derive(Clone, PartialEq, Eq, Copy, Hash, Default, PartialOrd, Ord)]
pub struct Position {
pub line: usize,
pub col: usize,

View file

@ -1733,13 +1733,20 @@ impl<'ast, T: Field> Checker<'ast, T> {
.map(|rhs| TypedStatement::Definition(var, rhs))
.map_err(|e| vec![e])
}
Statement::Assertion(e) => {
Statement::Assertion(e, message) => {
let e = self
.check_expression(e, module_id, types)
.map_err(|e| vec![e])?;
match e {
TypedExpression::Boolean(e) => Ok(TypedStatement::Assertion(e)),
TypedExpression::Boolean(e) => Ok(TypedStatement::Assertion(
e,
RuntimeError::SourceAssertion(AssertionMetadata {
file: module_id.display().to_string(),
position: pos.0,
message,
}),
)),
e => Err(ErrorInner {
pos: Some(pos),
message: format!(
@ -4602,6 +4609,7 @@ mod tests {
.mock(),
)
.mock(),
None,
)
.mock(),
Statement::Return(
@ -5014,6 +5022,7 @@ mod tests {
.mock(),
)
.mock(),
None,
)
.mock(),
Statement::Return(

View file

@ -379,9 +379,15 @@ fn fold_statement<'ast, T: Field>(
typed_absy::TypedStatement::Declaration(..) => {
unreachable!()
}
typed_absy::TypedStatement::Assertion(e) => {
typed_absy::TypedStatement::Assertion(e, error) => {
let e = f.fold_boolean_expression(statements_buffer, e);
vec![zir::ZirStatement::Assertion(e)]
let error = match error {
typed_absy::RuntimeError::SourceAssertion(metadata) => {
zir::RuntimeError::SourceAssertion(metadata.to_string())
}
typed_absy::RuntimeError::SelectRangeCheck => zir::RuntimeError::SelectRangeCheck,
};
vec![zir::ZirStatement::Assertion(e, error)]
}
typed_absy::TypedStatement::For(..) => unreachable!(),
typed_absy::TypedStatement::MultipleDefinition(variables, elist) => {

View file

@ -620,15 +620,14 @@ impl<'ast, 'a, T: Field> ResultFolder<'ast, T> for Propagator<'ast, 'a, T> {
Ok(statements)
}
TypedStatement::Assertion(e) => {
TypedStatement::Assertion(e, ty) => {
let e_str = e.to_string();
let expr = self.fold_boolean_expression(e)?;
match expr {
BooleanExpression::Value(v) if !v => Err(Error::AssertionFailed(format!(
"Assertion failed on expression `{}`",
e_str
))),
_ => Ok(vec![TypedStatement::Assertion(expr)]),
BooleanExpression::Value(v) if !v => {
Err(Error::AssertionFailed(format!("{}: ({})", ty, e_str)))
}
_ => Ok(vec![TypedStatement::Assertion(expr, ty)]),
}
}
s @ TypedStatement::PushCallLog(..) => Ok(vec![s]),

View file

@ -531,7 +531,7 @@ impl<'ast, T: Field> Folder<'ast, T> for UintOptimizer<'ast, T> {
}
}
}
ZirStatement::Assertion(BooleanExpression::UintEq(box left, box right)) => {
ZirStatement::Assertion(BooleanExpression::UintEq(box left, box right), metadata) => {
let left = self.fold_uint_expression(left);
let right = self.fold_uint_expression(right);
@ -539,9 +539,10 @@ impl<'ast, T: Field> Folder<'ast, T> for UintOptimizer<'ast, T> {
let left = force_reduce(left);
let right = force_reduce(right);
vec![ZirStatement::Assertion(BooleanExpression::UintEq(
box left, box right,
))]
vec![ZirStatement::Assertion(
BooleanExpression::UintEq(box left, box right),
metadata,
)]
}
s => fold_statement(self, s),
}

View file

@ -49,6 +49,7 @@ impl<'ast> VariableWriteRemover {
Access::Select(head) => {
statements.insert(TypedStatement::Assertion(
BooleanExpression::UintLt(box head.clone(), box size.into()),
RuntimeError::SelectRangeCheck,
));
ArrayExpressionInner::Value(

View file

@ -51,9 +51,9 @@ impl<'ast, T: Field> ResultFolder<'ast, T> for ZirPropagator<'ast, T> {
s: ZirStatement<'ast, T>,
) -> Result<Vec<ZirStatement<'ast, T>>, Self::Error> {
match s {
ZirStatement::Assertion(e) => match self.fold_boolean_expression(e)? {
ZirStatement::Assertion(e, error) => match self.fold_boolean_expression(e)? {
BooleanExpression::Value(true) => Ok(vec![]),
e => Ok(vec![ZirStatement::Assertion(e)]),
e => Ok(vec![ZirStatement::Assertion(e, error)]),
},
ZirStatement::Definition(a, e) => {
let e = self.fold_expression(e)?;
@ -654,21 +654,31 @@ impl<'ast, T: Field> ResultFolder<'ast, T> for ZirPropagator<'ast, T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::zir::RuntimeError;
use zokrates_field::Bn128Field;
impl RuntimeError {
pub fn mock() -> Self {
RuntimeError::SourceAssertion(String::default())
}
}
#[test]
fn propagation() {
// assert([x, 1] == [y, 1])
let statements = vec![ZirStatement::Assertion(BooleanExpression::And(
box BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
let statements = vec![ZirStatement::Assertion(
BooleanExpression::And(
box BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
),
box BooleanExpression::FieldEq(
box FieldElementExpression::Number(Bn128Field::from(1)),
box FieldElementExpression::Number(Bn128Field::from(1)),
),
),
box BooleanExpression::FieldEq(
box FieldElementExpression::Number(Bn128Field::from(1)),
box FieldElementExpression::Number(Bn128Field::from(1)),
),
))];
RuntimeError::mock(),
)];
let mut propagator = ZirPropagator::default();
let statements: Vec<ZirStatement<_>> = statements
@ -682,10 +692,13 @@ mod tests {
assert_eq!(
statements,
vec![ZirStatement::Assertion(BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
))]
vec![ZirStatement::Assertion(
BooleanExpression::FieldEq(
box FieldElementExpression::Identifier("x".into()),
box FieldElementExpression::Identifier("y".into()),
),
RuntimeError::mock()
)]
);
}

View file

@ -461,7 +461,9 @@ pub fn fold_statement<'ast, T: Field, F: Folder<'ast, T>>(
TypedStatement::Definition(f.fold_assignee(a), f.fold_expression(e))
}
TypedStatement::Declaration(v) => TypedStatement::Declaration(f.fold_variable(v)),
TypedStatement::Assertion(e) => TypedStatement::Assertion(f.fold_boolean_expression(e)),
TypedStatement::Assertion(e, error) => {
TypedStatement::Assertion(f.fold_boolean_expression(e), error)
}
TypedStatement::For(v, from, to, statements) => TypedStatement::For(
f.fold_variable(v),
f.fold_uint_expression(from),

View file

@ -24,6 +24,7 @@ pub use self::types::{
DeclarationSignature, DeclarationStructType, DeclarationType, GArrayType, GStructType, GType,
GenericIdentifier, IntoTypes, Signature, StructType, Type, Types, UBitwidth,
};
use crate::parser::Position;
use crate::typed_absy::types::ConcreteGenericsAssignment;
pub use self::variable::{ConcreteVariable, DeclarationVariable, GVariable, Variable};
@ -575,6 +576,38 @@ impl<'ast, T: fmt::Display> fmt::Display for TypedAssignee<'ast, T> {
}
}
#[derive(Clone, Debug, PartialEq, Hash, Eq, Default, PartialOrd, Ord)]
pub struct AssertionMetadata {
pub file: String,
pub position: Position,
pub message: Option<String>,
}
impl fmt::Display for AssertionMetadata {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Assertion failed at {}:{}", self.file, self.position)?;
match &self.message {
Some(m) => write!(f, ": \"{}\"", m),
None => write!(f, ""),
}
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub enum RuntimeError {
SourceAssertion(AssertionMetadata),
SelectRangeCheck,
}
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RuntimeError::SourceAssertion(metadata) => write!(f, "{}", metadata),
RuntimeError::SelectRangeCheck => write!(f, "Range check on array access"),
}
}
}
/// A statement in a `TypedFunction`
#[allow(clippy::large_enum_variant)]
#[derive(Clone, PartialEq, Debug, Hash, Eq, PartialOrd, Ord)]
@ -582,7 +615,7 @@ pub enum TypedStatement<'ast, T> {
Return(Vec<TypedExpression<'ast, T>>),
Definition(TypedAssignee<'ast, T>, TypedExpression<'ast, T>),
Declaration(Variable<'ast, T>),
Assertion(BooleanExpression<'ast, T>),
Assertion(BooleanExpression<'ast, T>, RuntimeError),
For(
Variable<'ast, T>,
UExpression<'ast, T>,
@ -630,7 +663,16 @@ impl<'ast, T: fmt::Display> fmt::Display for TypedStatement<'ast, T> {
}
TypedStatement::Declaration(ref var) => write!(f, "{}", var),
TypedStatement::Definition(ref lhs, ref rhs) => write!(f, "{} = {}", lhs, rhs),
TypedStatement::Assertion(ref e) => write!(f, "assert({})", e),
TypedStatement::Assertion(ref e, ref error) => {
write!(f, "assert({}", e)?;
match error {
RuntimeError::SourceAssertion(metadata) => match &metadata.message {
Some(m) => write!(f, ", \"{}\")", m),
None => write!(f, ")"),
},
error => write!(f, ") // {}", error),
}
}
TypedStatement::For(ref var, ref start, ref stop, ref list) => {
writeln!(f, "for {} in {}..{} do", var, start, stop)?;
for l in list {

View file

@ -458,7 +458,9 @@ pub fn fold_statement<'ast, T: Field, F: ResultFolder<'ast, T>>(
TypedStatement::Definition(f.fold_assignee(a)?, f.fold_expression(e)?)
}
TypedStatement::Declaration(v) => TypedStatement::Declaration(f.fold_variable(v)?),
TypedStatement::Assertion(e) => TypedStatement::Assertion(f.fold_boolean_expression(e)?),
TypedStatement::Assertion(e, error) => {
TypedStatement::Assertion(f.fold_boolean_expression(e)?, error)
}
TypedStatement::For(v, from, to, statements) => TypedStatement::For(
f.fold_variable(v)?,
f.fold_uint_expression(from)?,

View file

@ -115,7 +115,9 @@ pub fn fold_statement<'ast, T: Field, F: Folder<'ast, T>>(
.flat_map(|e| f.fold_statement(e))
.collect(),
),
ZirStatement::Assertion(e) => ZirStatement::Assertion(f.fold_boolean_expression(e)),
ZirStatement::Assertion(e, error) => {
ZirStatement::Assertion(f.fold_boolean_expression(e), error)
}
ZirStatement::MultipleDefinition(variables, elist) => ZirStatement::MultipleDefinition(
variables.into_iter().map(|v| f.fold_variable(v)).collect(),
f.fold_expression_list(elist),

View file

@ -19,7 +19,6 @@ use std::fmt;
use zokrates_field::Field;
pub use self::folder::Folder;
pub use self::identifier::{Identifier, SourceIdentifier};
/// A typed program as a collection of modules, one of them being the main
@ -86,8 +85,23 @@ impl<'ast, T: fmt::Debug> fmt::Debug for ZirFunction<'ast, T> {
pub type ZirAssignee<'ast> = Variable<'ast>;
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub enum RuntimeError {
SourceAssertion(String),
SelectRangeCheck,
}
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RuntimeError::SourceAssertion(message) => write!(f, "{}", message),
RuntimeError::SelectRangeCheck => write!(f, "Range check on array access"),
}
}
}
/// A statement in a `ZirFunction`
#[derive(Clone, PartialEq, Hash, Eq)]
#[derive(Clone, PartialEq, Hash, Eq, Debug)]
pub enum ZirStatement<'ast, T> {
Return(Vec<ZirExpression<'ast, T>>),
Definition(ZirAssignee<'ast>, ZirExpression<'ast, T>),
@ -96,37 +110,10 @@ pub enum ZirStatement<'ast, T> {
Vec<ZirStatement<'ast, T>>,
Vec<ZirStatement<'ast, T>>,
),
Assertion(BooleanExpression<'ast, T>),
Assertion(BooleanExpression<'ast, T>, RuntimeError),
MultipleDefinition(Vec<ZirAssignee<'ast>>, ZirExpressionList<'ast, T>),
}
impl<'ast, T: fmt::Debug> fmt::Debug for ZirStatement<'ast, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ZirStatement::Return(ref exprs) => {
write!(f, "Return(")?;
for (i, expr) in exprs.iter().enumerate() {
write!(f, "{:?}", expr)?;
if i < exprs.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
ZirStatement::Definition(ref consequence, ref alternative) => {
write!(f, "Definition({:?}, {:?})", consequence, alternative)
}
ZirStatement::IfElse(ref condition, ref lhs, ref rhs) => {
write!(f, "IfElse({:?}, {:?}, {:?})", condition, lhs, rhs)
}
ZirStatement::Assertion(ref e) => write!(f, "Assertion({:?})", e),
ZirStatement::MultipleDefinition(ref lhs, ref rhs) => {
write!(f, "MultipleDefinition({:?}, {:?})", lhs, rhs)
}
}
}
}
impl<'ast, T: fmt::Display> fmt::Display for ZirStatement<'ast, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
@ -158,7 +145,13 @@ impl<'ast, T: fmt::Display> fmt::Display for ZirStatement<'ast, T> {
.join("\n")
)
}
ZirStatement::Assertion(ref e) => write!(f, "assert({})", e),
ZirStatement::Assertion(ref e, ref error) => {
write!(f, "assert({}", e)?;
match error {
RuntimeError::SourceAssertion(message) => write!(f, ", \"{}\")", message),
error => write!(f, ") // {}", error),
}
}
ZirStatement::MultipleDefinition(ref ids, ref rhs) => {
for (i, id) in ids.iter().enumerate() {
write!(f, "{}", id)?;

View file

@ -137,7 +137,9 @@ pub fn fold_statement<'ast, T: Field, F: ResultFolder<'ast, T>>(
.flatten()
.collect(),
),
ZirStatement::Assertion(e) => ZirStatement::Assertion(f.fold_boolean_expression(e)?),
ZirStatement::Assertion(e, error) => {
ZirStatement::Assertion(f.fold_boolean_expression(e)?, error)
}
ZirStatement::MultipleDefinition(variables, elist) => ZirStatement::MultipleDefinition(
variables
.into_iter()

View file

@ -31,7 +31,7 @@
"UnsatisfiedConstraint": {
"left": "4",
"right": "2",
"message": "Argument bitness check failed"
"error": "ArgumentBitness"
}
}
}

View file

@ -42,7 +42,7 @@
"UnsatisfiedConstraint": {
"left": "1",
"right": "0",
"message": "Out of bounds array access"
"error": "SelectRangeCheck"
}
}
}

View file

@ -1,20 +1,22 @@
{
"entry_point": "./tests/tests/assert_one.zok",
"curves": ["Bn128", "Bls12_381", "Bls12_377", "Bw6_761"],
"tests": [
{
"input": {
"values": ["0"]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"message": "User assertion failed"
}
}
}
}
]
"entry_point": "./tests/tests/assert_one.zok",
"curves": ["Bn128", "Bls12_381", "Bls12_377", "Bw6_761"],
"tests": [
{
"input": {
"values": ["0"]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/assert_one.zok:2:2"
}
}
}
}
}
]
}

View file

@ -1,7 +1,7 @@
{
"entry_point": "./tests/tests/panics/conditional_bound_throw.zok",
"curves": ["Bn128"],
"tests": [
"tests": [
{
"input": {
"values": [
@ -9,13 +9,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/conditional_bound_throw.zok:2:5"
}
}
}
}
},
{
@ -25,13 +27,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "1",
"right": "0",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "1",
"right": "0",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/conditional_bound_throw.zok:2:5"
}
}
}
}
},
{
@ -41,13 +45,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "2",
"right": "0",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "2",
"right": "0",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/conditional_bound_throw.zok:2:5"
}
}
}
}
}
]

View file

@ -1,7 +1,7 @@
{
"entry_point": "./tests/tests/panics/deep_branch.zok",
"curves": ["Bn128"],
"tests": [
"tests": [
{
"input": {
"values": [
@ -9,13 +9,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/deep_branch.zok:2:5"
}
}
}
}
}
]

View file

@ -1,7 +1,7 @@
{
"entry_point": "./tests/tests/panics/internal_panic.zok",
"curves": ["Bn128"],
"tests": [
"tests": [
{
"input": {
"values": [
@ -9,9 +9,9 @@
]
},
"output": {
"Ok": {
"values": ["1"]
}
"Ok": {
"values": ["1"]
}
}
},
{
@ -21,13 +21,13 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"message": "Division by zero"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"error": "Inverse"
}
}
}
}
]

View file

@ -1,7 +1,7 @@
{
"entry_point": "./tests/tests/panics/loop_bound.zok",
"curves": ["Bn128", "Bls12_381", "Bls12_377", "Bw6_761"],
"tests": [
"tests": [
{
"input": {
"values": [
@ -9,13 +9,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "0",
"right": "1",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/loop_bound.zok:2:3"
}
}
}
}
},
{
@ -25,9 +27,9 @@
]
},
"output": {
"Ok": {
"values": []
}
"Ok": {
"values": []
}
}
}
]

View file

@ -5,7 +5,7 @@
"isolate_branches": true
},
"curves": ["Bn128"],
"tests": [
"tests": [
{
"input": {
"values": [
@ -16,13 +16,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "1",
"right": "21888242871839275222246405745257275088548364400416034343698204186575808495577",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "1",
"right": "21888242871839275222246405745257275088548364400416034343698204186575808495577",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/panic_isolation.zok:18:5"
}
}
}
}
},
{
@ -35,14 +37,14 @@
]
},
"output": {
"Ok": {
"values": [
"Ok": {
"values": [
"1",
"1",
"1",
"1"
]
}
]
}
}
},
{
@ -55,14 +57,14 @@
]
},
"output": {
"Ok": {
"values": [
"Ok": {
"values": [
"0",
"2",
"2",
"0"
]
}
]
}
}
}
]

View file

@ -5,7 +5,7 @@
"isolate_branches": false
},
"curves": ["Bn128"],
"tests": [
"tests": [
{
"input": {
"values": [
@ -16,13 +16,15 @@
]
},
"output": {
"Err": {
"UnsatisfiedConstraint": {
"left": "1",
"right": "0",
"message": "User assertion failed"
}
}
"Err": {
"UnsatisfiedConstraint": {
"left": "1",
"right": "0",
"error": {
"SourceAssertion": "Assertion failed at ./tests/tests/panics/panic_isolation.zok:14:5"
}
}
}
}
}
]

View file

@ -31,7 +31,7 @@
"UnsatisfiedConstraint": {
"left": "9",
"right": "3",
"message": "Argument bitness check failed"
"error": "ArgumentBitness"
}
}
}

View file

@ -3,13 +3,14 @@ file = { SOI ~ NEWLINE* ~ pragma? ~ NEWLINE* ~ symbol_declaration* ~ EOI }
pragma = { "#pragma" ~ "curve" ~ curve }
curve = @{ (ASCII_ALPHANUMERIC | "_") * }
string = @{(!"\"" ~ ANY)*}
quoted_string = _{ "\"" ~ string ~ "\"" }
symbol_declaration = { (import_directive | ty_struct_definition | const_definition | function_definition) ~ NEWLINE* }
import_directive = { main_import_directive | from_import_directive }
from_import_directive = { "from" ~ "\"" ~ import_source ~ "\"" ~ "import" ~ import_symbol_list ~ NEWLINE* }
main_import_directive = { "import" ~ "\"" ~ import_source ~ "\"" ~ ("as" ~ identifier)? ~ NEWLINE+ }
import_source = @{(!"\"" ~ ANY)*}
from_import_directive = { "from" ~ quoted_string ~ "import" ~ import_symbol_list ~ NEWLINE* }
main_import_directive = { "import" ~ quoted_string ~ ("as" ~ identifier)? ~ NEWLINE+ }
import_symbol = { identifier ~ ("as" ~ identifier)? }
import_symbol_list = _{ import_symbol ~ ("," ~ import_symbol)* }
function_definition = {"def" ~ identifier ~ constant_generics_declaration? ~ "(" ~ parameter_list ~ ")" ~ return_types ~ ":" ~ NEWLINE* ~ statement* }
@ -55,7 +56,7 @@ statement = { (return_statement // does not require subsequent newline
iteration_statement = { "for" ~ ty ~ identifier ~ "in" ~ expression ~ ".." ~ expression ~ "do" ~ NEWLINE* ~ statement* ~ "endfor"}
return_statement = { "return" ~ expression_list}
definition_statement = { typed_identifier_or_assignee_list ~ "=" ~ expression } // declare and assign, so only identifiers are allowed, unlike `assignment_statement`
expression_statement = {"assert" ~ "(" ~ expression ~ ")"}
expression_statement = {"assert" ~ "(" ~ expression ~ ("," ~ quoted_string)? ~ ")"}
typed_identifier_or_assignee_list = _{ typed_identifier_or_assignee ~ ("," ~ typed_identifier_or_assignee)* }
typed_identifier_or_assignee = { typed_identifier | assignee } // we don't use { ty? ~ identifier } as with a single token, it gets parsed as `ty` but we want `identifier`

View file

@ -13,7 +13,7 @@ pub use ast::{
CallAccess, ConstantDefinition, ConstantGenericValue, DecimalLiteralExpression, DecimalNumber,
DecimalSuffix, DefinitionStatement, ExplicitGenerics, Expression, FieldType, File,
FromExpression, FunctionDefinition, HexLiteralExpression, HexNumberExpression,
IdentifierExpression, ImportDirective, ImportSource, ImportSymbol, InlineArrayExpression,
IdentifierExpression, ImportDirective, ImportSymbol, InlineArrayExpression,
InlineStructExpression, InlineStructMember, IterationStatement, LiteralExpression, Parameter,
PostfixExpression, Range, RangeOrExpression, ReturnStatement, Span, Spread, SpreadOrExpression,
Statement, StructDefinition, StructField, SymbolDeclaration, TernaryExpression, ToExpression,
@ -194,7 +194,7 @@ mod ast {
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::main_import_directive))]
pub struct MainImportDirective<'ast> {
pub source: ImportSource<'ast>,
pub source: AnyString<'ast>,
pub alias: Option<IdentifierExpression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
@ -212,21 +212,12 @@ mod ast {
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::from_import_directive))]
pub struct FromImportDirective<'ast> {
pub source: ImportSource<'ast>,
pub source: AnyString<'ast>,
pub symbols: Vec<ImportSymbol<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::import_source))]
pub struct ImportSource<'ast> {
#[pest_ast(outer(with(span_into_str)))]
pub value: String,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::ty))]
pub enum Type<'ast> {
@ -357,10 +348,20 @@ mod ast {
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::string))]
pub struct AnyString<'ast> {
#[pest_ast(outer(with(span_into_str)))]
pub value: String,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::expression_statement))]
pub struct AssertionStatement<'ast> {
pub expression: Expression<'ast>,
pub message: Option<AnyString<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
@ -1097,7 +1098,7 @@ mod tests {
pragma: None,
declarations: vec![
SymbolDeclaration::Import(ImportDirective::Main(MainImportDirective {
source: ImportSource {
source: AnyString {
value: String::from("foo"),
span: Span::new(source, 8, 11).unwrap()
},
@ -1158,7 +1159,7 @@ mod tests {
pragma: None,
declarations: vec![
SymbolDeclaration::Import(ImportDirective::Main(MainImportDirective {
source: ImportSource {
source: AnyString {
value: String::from("foo"),
span: Span::new(source, 8, 11).unwrap()
},
@ -1243,7 +1244,7 @@ mod tests {
pragma: None,
declarations: vec![
SymbolDeclaration::Import(ImportDirective::Main(MainImportDirective {
source: ImportSource {
source: AnyString {
value: String::from("foo"),
span: Span::new(source, 8, 11).unwrap()
},