Implement support for first order predicates in the backend. Now a statement template can have a predicate hash or a wildcard. ## predicate <-> predicate hash constraints To build the custom predicate table we need to calculate the custom predicate batch id, which uses the serialization of the statement templates before normalization. This serialization uses the predicate hash when the template uses a predicate (instead of a wildcard). Then in normalization we recalculate the predicate hash if it was a Batch Self. This means that the relation between hash and predicate must be checked before and after normalization when the template is not using a wildcard. How this is achieved: - Before normalization: the constructor of StatementTmplTarget forces that if we keep a predicate, it's hash must be equal to the pred_hash when the template has a predicate (and not a wildcard) - After normalization: the predicate hash is calculated in the normalization and replaced in the case of the template using a predicate and it being a BatchSelf. If it was a predicate but not batch self, the old value was used which was constrained via the constructor. See `CircuitBuilder::add_virtual_statement_tmpl` and `normalize_st_tmpl_circuit` ## Wildcard predicate resolution It is done via `make_predicate_from_template_circuit` and is fairly simple as it's contains similar logic to `make_statement_arg_from_template_circuit` but simpler.
1059 lines
36 KiB
Rust
1059 lines
36 KiB
Rust
pub mod error;
|
|
pub mod frontend_ast;
|
|
pub mod frontend_ast_lower;
|
|
pub mod frontend_ast_split;
|
|
pub mod frontend_ast_validate;
|
|
pub mod parser;
|
|
pub mod pretty_print;
|
|
|
|
use std::sync::Arc;
|
|
|
|
pub use error::LangError;
|
|
pub use parser::{parse_podlang, Pairs, ParseError, Rule};
|
|
pub use pretty_print::PrettyPrint;
|
|
|
|
use crate::{
|
|
frontend::PodRequest,
|
|
middleware::{CustomPredicateBatch, Params},
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct PodlangOutput {
|
|
pub custom_batch: Arc<CustomPredicateBatch>,
|
|
pub request: PodRequest,
|
|
}
|
|
|
|
pub fn parse(
|
|
input: &str,
|
|
params: &Params,
|
|
available_batches: &[Arc<CustomPredicateBatch>],
|
|
) -> Result<PodlangOutput, LangError> {
|
|
let pairs = parse_podlang(input)?;
|
|
let document_pair = pairs
|
|
.into_iter()
|
|
.next()
|
|
.expect("parse_podlang should always return at least one pair for a valid document");
|
|
let document = frontend_ast::parse::parse_document(document_pair)?;
|
|
let validated = frontend_ast_validate::validate(document, available_batches)?;
|
|
let lowered = frontend_ast_lower::lower(validated, params, "PodlangBatch".to_string())?;
|
|
|
|
let custom_batch = lowered.batch.unwrap_or_else(|| {
|
|
// If no batch, create an empty one
|
|
CustomPredicateBatch::new(params, "PodlangBatch".to_string(), vec![])
|
|
});
|
|
|
|
let request = lowered.request.unwrap_or_else(|| {
|
|
// If no request, create an empty one
|
|
PodRequest::new(vec![])
|
|
});
|
|
|
|
Ok(PodlangOutput {
|
|
custom_batch,
|
|
request,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use hex::ToHex;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
use crate::{
|
|
backends::plonky2::primitives::ec::schnorr::SecretKey,
|
|
middleware::{
|
|
CustomPredicate, CustomPredicateBatch, CustomPredicateRef, Key, NativePredicate,
|
|
Params, Predicate, PredicateOrWildcard, RawValue, StatementTmpl, StatementTmplArg,
|
|
Value, Wildcard, EMPTY_HASH,
|
|
},
|
|
};
|
|
|
|
// Helper functions
|
|
fn wc(name: &str, index: usize) -> Wildcard {
|
|
Wildcard::new(name.to_string(), index)
|
|
}
|
|
|
|
fn sta_ak(pod_var: (&str, usize), key: &str) -> StatementTmplArg {
|
|
StatementTmplArg::AnchoredKey(wc(pod_var.0, pod_var.1), Key::from(key))
|
|
}
|
|
|
|
fn sta_wc_lit(name: &str, index: usize) -> StatementTmplArg {
|
|
StatementTmplArg::Wildcard(wc(name, index))
|
|
}
|
|
|
|
fn sta_lit(value: impl Into<Value>) -> StatementTmplArg {
|
|
StatementTmplArg::Literal(value.into())
|
|
}
|
|
|
|
fn names(names: &[&str]) -> Vec<String> {
|
|
names.iter().map(|s| s.to_string()).collect()
|
|
}
|
|
|
|
fn pred_lit(pred: Predicate) -> PredicateOrWildcard {
|
|
PredicateOrWildcard::Predicate(pred)
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_simple_predicate() -> Result<(), LangError> {
|
|
let input = r#"
|
|
is_equal(PodA, PodB) = AND(
|
|
Equal(PodA["the_key"], PodB["the_key"])
|
|
)
|
|
"#;
|
|
|
|
let params = Params::default();
|
|
let processed = parse(input, ¶ms, &[])?;
|
|
let batch_result = processed.custom_batch;
|
|
let request_result = processed.request.templates();
|
|
|
|
assert_eq!(request_result.len(), 0);
|
|
assert_eq!(batch_result.predicates.len(), 1);
|
|
|
|
let batch = batch_result;
|
|
|
|
// Expected structure
|
|
let expected_statements = vec![StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("PodA", 0), "the_key"), // PodA["the_key"] -> Wildcard(0), Key("the_key")
|
|
sta_ak(("PodB", 1), "the_key"), // PodB["the_key"] -> Wildcard(1), Key("the_key")
|
|
],
|
|
}];
|
|
let expected_predicate = CustomPredicate::and(
|
|
¶ms,
|
|
"is_equal".to_string(),
|
|
expected_statements,
|
|
2, // args_len (PodA, PodB)
|
|
names(&["PodA", "PodB"]),
|
|
)?;
|
|
let expected_batch = CustomPredicateBatch::new(
|
|
¶ms,
|
|
"PodlangBatch".to_string(),
|
|
vec![expected_predicate],
|
|
);
|
|
|
|
assert_eq!(batch, expected_batch);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_simple_request() -> Result<(), LangError> {
|
|
let input = r#"
|
|
REQUEST(
|
|
Equal(ConstPod["my_val"], Raw(0x0000000000000000000000000000000000000000000000000000000000000001))
|
|
Lt(GovPod["dob"], ConstPod["my_val"])
|
|
)
|
|
"#;
|
|
|
|
let params = Params::default();
|
|
let processed = parse(input, ¶ms, &[])?;
|
|
let batch_result = processed.custom_batch;
|
|
let request_templates = processed.request.templates();
|
|
|
|
assert_eq!(batch_result.predicates.len(), 0);
|
|
assert!(!request_templates.is_empty());
|
|
|
|
// Expected structure
|
|
let expected_templates = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("ConstPod", 0), "my_val"), // ConstPod["my_val"] -> Wildcard(0), Key("my_val")
|
|
sta_lit(RawValue::from(1)),
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Lt)),
|
|
args: vec![
|
|
sta_ak(("GovPod", 1), "dob"), // GovPod["dob"] -> Wildcard(1), Key("dob")
|
|
sta_ak(("ConstPod", 0), "my_val"), // ConstPod["my_val"] -> Wildcard(0), Key("my_val")
|
|
],
|
|
},
|
|
];
|
|
|
|
assert_eq!(request_templates, expected_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_predicate_with_private_var() -> Result<(), LangError> {
|
|
let input = r#"
|
|
uses_private(A, private: Temp) = AND(
|
|
Equal(A["input_key"], Temp["const_key"])
|
|
Equal(Temp["const_key"], "some_value")
|
|
)
|
|
"#;
|
|
|
|
let params = Params::default();
|
|
let processed = parse(input, ¶ms, &[])?;
|
|
let batch_result = processed.custom_batch;
|
|
let request_result = processed.request.templates();
|
|
|
|
assert_eq!(request_result.len(), 0);
|
|
assert_eq!(batch_result.predicates.len(), 1);
|
|
|
|
let batch = batch_result;
|
|
|
|
// Expected structure: Public args: A (index 0). Private args: Temp (index 1)
|
|
let expected_statements = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("A", 0), "input_key"), // A["input_key"] -> Wildcard(0), Key("input_key")
|
|
sta_ak(("Temp", 1), "const_key"), // Temp["const_key"] -> Wildcard(1), Key("const_key")
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("Temp", 1), "const_key"), // Temp["const_key"] -> Wildcard(1), Key("const_key")
|
|
sta_lit("some_value"), // Literal("some_value")
|
|
],
|
|
},
|
|
];
|
|
let expected_predicate = CustomPredicate::and(
|
|
¶ms,
|
|
"uses_private".to_string(),
|
|
expected_statements,
|
|
1, // args_len (A)
|
|
names(&["A", "Temp"]),
|
|
)?;
|
|
let expected_batch = CustomPredicateBatch::new(
|
|
¶ms,
|
|
"PodlangBatch".to_string(),
|
|
vec![expected_predicate],
|
|
);
|
|
|
|
assert_eq!(batch, expected_batch);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_request_with_custom_call() -> Result<(), LangError> {
|
|
let input = r#"
|
|
my_pred(X, Y) = AND(
|
|
Equal(X["val"], Y["val"])
|
|
)
|
|
|
|
REQUEST(
|
|
my_pred(Pod1, Pod2)
|
|
)
|
|
"#;
|
|
|
|
let params = Params::default();
|
|
let processed = parse(input, ¶ms, &[])?;
|
|
let batch_result = processed.custom_batch;
|
|
let request_templates = processed.request.templates();
|
|
|
|
assert_eq!(batch_result.predicates.len(), 1);
|
|
assert!(!request_templates.is_empty());
|
|
|
|
let batch = batch_result;
|
|
|
|
// Expected Batch structure
|
|
let expected_pred_statements = vec![StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("X", 0), "val"), // X["val"] -> Wildcard(0), Key("val")
|
|
sta_ak(("Y", 1), "val"), // Y["val"] -> Wildcard(1), Key("val")
|
|
],
|
|
}];
|
|
let expected_predicate = CustomPredicate::and(
|
|
¶ms,
|
|
"my_pred".to_string(),
|
|
expected_pred_statements,
|
|
2, // args_len (X, Y)
|
|
names(&["X", "Y"]),
|
|
)?;
|
|
let expected_batch = CustomPredicateBatch::new(
|
|
¶ms,
|
|
"PodlangBatch".to_string(),
|
|
vec![expected_predicate],
|
|
);
|
|
|
|
assert_eq!(batch, expected_batch);
|
|
|
|
// Expected Request structure
|
|
// Pod1 -> Wildcard 0, Pod2 -> Wildcard 1
|
|
let expected_request_templates = vec![StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Custom(CustomPredicateRef::new(
|
|
expected_batch,
|
|
0,
|
|
))),
|
|
args: vec![
|
|
StatementTmplArg::Wildcard(wc("Pod1", 0)),
|
|
StatementTmplArg::Wildcard(wc("Pod2", 1)),
|
|
],
|
|
}];
|
|
|
|
assert_eq!(request_templates, expected_request_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_request_with_various_args() -> Result<(), LangError> {
|
|
let input = r#"
|
|
some_pred(A, B, C) = AND( Equal(A["foo"], B["bar"]) )
|
|
|
|
REQUEST(
|
|
some_pred(
|
|
Var1, // Wildcard
|
|
12345, // Int Literal
|
|
"hello_string" // String Literal (Removed invalid AK args)
|
|
)
|
|
Equal(AnotherPod["another_key"], Var1["some_field"])
|
|
)
|
|
"#;
|
|
|
|
let params = Params::default();
|
|
let processed = parse(input, ¶ms, &[])?;
|
|
let batch_result = processed.custom_batch;
|
|
let request_templates = processed.request.templates();
|
|
|
|
assert_eq!(batch_result.predicates.len(), 1); // some_pred is defined
|
|
assert!(!request_templates.is_empty());
|
|
|
|
// Expected Wildcard Indices in Request Scope:
|
|
// Var1 -> 0
|
|
// AnotherPod -> 1
|
|
|
|
// Expected structure
|
|
let expected_templates = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Custom(CustomPredicateRef::new(batch_result, 0))), // Refers to some_pred
|
|
args: vec![
|
|
StatementTmplArg::Wildcard(wc("Var1", 0)), // Var1
|
|
StatementTmplArg::Literal(Value::from(12345i64)), // 12345
|
|
StatementTmplArg::Literal(Value::from("hello_string")), // "hello_string"
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
// AnotherPod["another_key"] -> Wildcard(1), Key("another_key")
|
|
sta_ak(("AnotherPod", 1), "another_key"),
|
|
// Var1["some_field"] -> Wildcard(0), Key("some_field")
|
|
sta_ak(("Var1", 0), "some_field"),
|
|
],
|
|
},
|
|
];
|
|
|
|
assert_eq!(request_templates, expected_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_syntactic_sugar_predicates() -> Result<(), LangError> {
|
|
let input = r#"
|
|
REQUEST(
|
|
GtEq(A["foo"], B["bar"])
|
|
Gt(C["baz"], D["qux"])
|
|
DictContains(A["foo"], B["bar"], C["baz"])
|
|
DictNotContains(A["foo"], B["bar"])
|
|
ArrayContains(A["foo"], B["bar"], C["baz"])
|
|
)
|
|
"#;
|
|
|
|
let params = Params::default();
|
|
let processed = parse(input, ¶ms, &[])?;
|
|
let batch_result = processed.custom_batch;
|
|
let request_templates = processed.request.templates();
|
|
|
|
assert_eq!(batch_result.predicates.len(), 0);
|
|
assert!(!request_templates.is_empty());
|
|
|
|
let expected_templates = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::LtEq)),
|
|
args: vec![sta_ak(("B", 1), "bar"), sta_ak(("A", 0), "foo")],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Lt)),
|
|
args: vec![sta_ak(("D", 3), "qux"), sta_ak(("C", 2), "baz")],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Contains)),
|
|
args: vec![
|
|
sta_ak(("A", 0), "foo"),
|
|
sta_ak(("B", 1), "bar"),
|
|
sta_ak(("C", 2), "baz"),
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::NotContains)),
|
|
args: vec![sta_ak(("A", 0), "foo"), sta_ak(("B", 1), "bar")],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Contains)),
|
|
args: vec![
|
|
sta_ak(("A", 0), "foo"),
|
|
sta_ak(("B", 1), "bar"),
|
|
sta_ak(("C", 2), "baz"),
|
|
],
|
|
},
|
|
];
|
|
|
|
assert_eq!(request_templates, expected_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_zukyc_request_parsing() -> Result<(), LangError> {
|
|
let input = r#"
|
|
REQUEST(
|
|
// Order matters for comparison with the hardcoded templates
|
|
SetNotContains(sanctions["sanctionList"], gov["idNumber"])
|
|
Lt(gov["dateOfBirth"], SELF_HOLDER_18Y["const_18y"])
|
|
Equal(pay["startDate"], SELF_HOLDER_1Y["const_1y"])
|
|
Equal(gov["socialSecurityNumber"], pay["socialSecurityNumber"])
|
|
Equal(SELF_HOLDER_18Y["const_18y"], 1169909388)
|
|
Equal(SELF_HOLDER_1Y["const_1y"], 1706367566)
|
|
)
|
|
"#;
|
|
|
|
// Parse the input string
|
|
let processed = super::parse(input, &Params::default(), &[])?;
|
|
let parsed_templates = processed.request.templates();
|
|
|
|
// Define Expected Templates (Copied from prover/mod.rs)
|
|
let now_minus_18y_val = Value::from(1169909388_i64);
|
|
let now_minus_1y_val = Value::from(1706367566_i64);
|
|
|
|
// Define wildcards and keys for the request
|
|
// Note: Indices must match the order of appearance in the *parsed* request
|
|
// Order: sanctions, gov, SELF_HOLDER_18Y, pay, SELF_HOLDER_1Y
|
|
let wc_sanctions = wc("sanctions", 0);
|
|
let wc_gov = wc("gov", 1);
|
|
let wc_self_18y = wc("SELF_HOLDER_18Y", 2);
|
|
let wc_pay = wc("pay", 3);
|
|
let wc_self_1y = wc("SELF_HOLDER_1Y", 4);
|
|
|
|
let id_num_key = "idNumber";
|
|
let dob_key = "dateOfBirth";
|
|
let const_18y_key = "const_18y";
|
|
let start_date_key = "startDate";
|
|
let const_1y_key = "const_1y";
|
|
let ssn_key = "socialSecurityNumber";
|
|
let sanction_list_key = "sanctionList";
|
|
|
|
// Define the request templates using wildcards for constants
|
|
let expected_templates = vec![
|
|
// 1. NotContains(sanctions["sanctionList"], gov["idNumber"])
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::NotContains)),
|
|
args: vec![
|
|
sta_ak(
|
|
(wc_sanctions.name.as_str(), wc_sanctions.index),
|
|
sanction_list_key,
|
|
),
|
|
sta_ak((wc_gov.name.as_str(), wc_gov.index), id_num_key),
|
|
],
|
|
},
|
|
// 2. Lt(gov["dateOfBirth"], SELF_HOLDER_18Y["const_18y"])
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Lt)),
|
|
args: vec![
|
|
sta_ak((wc_gov.name.as_str(), wc_gov.index), dob_key),
|
|
sta_ak(
|
|
(wc_self_18y.name.as_str(), wc_self_18y.index),
|
|
const_18y_key,
|
|
),
|
|
],
|
|
},
|
|
// 3. Equal(pay["startDate"], SELF_HOLDER_1Y["const_1y"])
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak((wc_pay.name.as_str(), wc_pay.index), start_date_key),
|
|
sta_ak((wc_self_1y.name.as_str(), wc_self_1y.index), const_1y_key),
|
|
],
|
|
},
|
|
// 4. Equal(gov["socialSecurityNumber"], pay["socialSecurityNumber"])
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak((wc_gov.name.as_str(), wc_gov.index), ssn_key),
|
|
sta_ak((wc_pay.name.as_str(), wc_pay.index), ssn_key),
|
|
],
|
|
},
|
|
// 5. Equal(SELF_HOLDER_18Y["const_18y"], 1169909388)
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(
|
|
(wc_self_18y.name.as_str(), wc_self_18y.index),
|
|
const_18y_key,
|
|
),
|
|
sta_lit(now_minus_18y_val.clone()),
|
|
],
|
|
},
|
|
// 6. Equal(SELF_HOLDER_1Y["const_1y"], 1706367566)
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak((wc_self_1y.name.as_str(), wc_self_1y.index), const_1y_key),
|
|
sta_lit(now_minus_1y_val.clone()),
|
|
],
|
|
},
|
|
];
|
|
|
|
assert_eq!(
|
|
parsed_templates, expected_templates,
|
|
"Parsed ZuKYC request templates do not match the expected hard-coded version"
|
|
);
|
|
|
|
assert!(
|
|
processed.custom_batch.predicates.is_empty(),
|
|
"Expected no custom predicates for a REQUEST only input"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_ethdos_predicates() -> Result<(), LangError> {
|
|
let params = Params {
|
|
max_input_pods: 3,
|
|
max_statements: 31,
|
|
max_public_statements: 10,
|
|
max_statement_args: 6,
|
|
max_operation_args: 5,
|
|
max_custom_predicate_arity: 5,
|
|
max_custom_batch_size: 5,
|
|
max_custom_predicate_wildcards: 12,
|
|
..Default::default()
|
|
};
|
|
|
|
let input = r#"
|
|
eth_friend(src, dst, private: attestation_dict) = AND(
|
|
SignedBy(attestation_dict, src)
|
|
Equal(attestation_dict["attestation"], dst)
|
|
)
|
|
|
|
eth_dos_distance_base(src, dst, distance) = AND(
|
|
Equal(src, dst)
|
|
Equal(distance, 0)
|
|
)
|
|
|
|
eth_dos_distance_ind(src, dst, distance, private: shorter_distance, intermed) = AND(
|
|
eth_dos_distance(src, dst, distance)
|
|
SumOf(distance, shorter_distance, 1)
|
|
eth_friend(intermed, dst)
|
|
)
|
|
|
|
eth_dos_distance(src, dst, distance) = OR(
|
|
eth_dos_distance_base(src, dst, distance)
|
|
eth_dos_distance_ind(src, dst, distance)
|
|
)
|
|
"#;
|
|
|
|
let processed = super::parse(input, ¶ms, &[])?;
|
|
|
|
assert!(
|
|
processed.request.templates().is_empty(),
|
|
"Expected no request templates"
|
|
);
|
|
assert_eq!(
|
|
processed.custom_batch.predicates.len(),
|
|
4,
|
|
"Expected 4 custom predicates"
|
|
);
|
|
|
|
// Predicate Order: eth_friend (0), base (1), ind (2), distance (3)
|
|
|
|
// eth_friend (Index 0)
|
|
let expected_friend_stmts = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::SignedBy)),
|
|
args: vec![sta_wc_lit("attestation_dict", 2), sta_wc_lit("src", 0)],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("attestation_dict", 2), "attestation"),
|
|
sta_wc_lit("dst", 1), // Pub arg 1
|
|
],
|
|
},
|
|
];
|
|
let expected_friend_pred = CustomPredicate::new(
|
|
¶ms,
|
|
"eth_friend".to_string(),
|
|
true, // AND
|
|
expected_friend_stmts,
|
|
2, // public_args_len: src, dst
|
|
names(&["src", "dst", "attestation_dict"]),
|
|
)?;
|
|
|
|
// eth_dos_distance_base (Index 1)
|
|
let expected_base_stmts = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_wc_lit("src", 0), sta_wc_lit("dst", 1)],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_wc_lit("distance", 2), sta_lit(0i64)],
|
|
},
|
|
];
|
|
let expected_base_pred = CustomPredicate::new(
|
|
¶ms,
|
|
"eth_dos_distance_base".to_string(),
|
|
true, // AND
|
|
expected_base_stmts,
|
|
3, // public_args_len
|
|
names(&["src", "dst", "distance"]),
|
|
)?;
|
|
|
|
// eth_dos_distance_ind (Index 2)
|
|
// Public args indices: 0-2
|
|
// Private args indices: 3-4 (shorter_distance, intermed)
|
|
let expected_ind_stmts = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::BatchSelf(3)), // Calls eth_dos_distance (index 3)
|
|
args: vec![
|
|
// WildcardLiteral args
|
|
sta_wc_lit("src", 0),
|
|
sta_wc_lit("dst", 1), // private arg
|
|
sta_wc_lit("distance", 2), // private arg
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::SumOf)),
|
|
args: vec![
|
|
sta_wc_lit("distance", 2), // public arg
|
|
sta_wc_lit("shorter_distance", 3), // private arg
|
|
sta_lit(1),
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::BatchSelf(0)), // Calls eth_friend (index 0)
|
|
args: vec![
|
|
// WildcardLiteral args
|
|
sta_wc_lit("intermed", 4), // private arg
|
|
sta_wc_lit("dst", 1), // public arg
|
|
],
|
|
},
|
|
];
|
|
let expected_ind_pred = CustomPredicate::new(
|
|
¶ms,
|
|
"eth_dos_distance_ind".to_string(),
|
|
true, // AND
|
|
expected_ind_stmts,
|
|
3, // public_args_len
|
|
names(&["src", "dst", "distance", "shorter_distance", "intermed"]),
|
|
)?;
|
|
|
|
// eth_dos_distance (Index 3)
|
|
let expected_dist_stmts = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::BatchSelf(1)), // Calls eth_dos_distance_base (index 1)
|
|
args: vec![
|
|
// WildcardLiteral args
|
|
sta_wc_lit("src", 0),
|
|
sta_wc_lit("dst", 1),
|
|
sta_wc_lit("distance", 2),
|
|
],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::BatchSelf(2)), // Calls eth_dos_distance_ind (index 2)
|
|
args: vec![
|
|
// WildcardLiteral args
|
|
sta_wc_lit("src", 0),
|
|
sta_wc_lit("dst", 1),
|
|
sta_wc_lit("distance", 2),
|
|
],
|
|
},
|
|
];
|
|
let expected_dist_pred = CustomPredicate::new(
|
|
¶ms,
|
|
"eth_dos_distance".to_string(),
|
|
false, // OR
|
|
expected_dist_stmts,
|
|
3, // public_args_len
|
|
names(&["src", "dst", "distance"]),
|
|
)?;
|
|
|
|
let expected_batch = CustomPredicateBatch::new(
|
|
¶ms,
|
|
"PodlangBatch".to_string(),
|
|
vec![
|
|
expected_friend_pred,
|
|
expected_base_pred,
|
|
expected_ind_pred,
|
|
expected_dist_pred,
|
|
],
|
|
);
|
|
|
|
assert_eq!(
|
|
processed.custom_batch, expected_batch,
|
|
"Processed ETHDoS predicates do not match expected structure"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_use_statement() -> Result<(), LangError> {
|
|
let params = Params::default();
|
|
|
|
// 1. Create a batch to be imported
|
|
let imported_pred_stmts = vec![StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![
|
|
sta_ak(("A", 0), "foo"), // A["foo"]
|
|
sta_ak(("B", 1), "bar"), // B["bar"]
|
|
],
|
|
}];
|
|
let imported_predicate = CustomPredicate::and(
|
|
¶ms,
|
|
"imported_equal".to_string(),
|
|
imported_pred_stmts,
|
|
2,
|
|
names(&["A", "B"]),
|
|
)?;
|
|
let available_batch =
|
|
CustomPredicateBatch::new(¶ms, "MyBatch".to_string(), vec![imported_predicate]);
|
|
let available_batches = vec![available_batch.clone()];
|
|
|
|
// 2. Create the input string that uses the batch
|
|
let batch_id_str = available_batch.id().encode_hex::<String>();
|
|
let input = format!(
|
|
r#"
|
|
use batch imported_pred from 0x{}
|
|
|
|
REQUEST(
|
|
imported_pred(Pod1, Pod2)
|
|
)
|
|
"#,
|
|
batch_id_str
|
|
);
|
|
|
|
// 3. Parse the input
|
|
let processed = parse(&input, ¶ms, &available_batches)?;
|
|
let request_templates = processed.request.templates();
|
|
|
|
assert!(
|
|
processed.custom_batch.predicates.is_empty(),
|
|
"No custom predicates should be defined in the main input"
|
|
);
|
|
assert_eq!(request_templates.len(), 1, "Expected one request template");
|
|
|
|
// 4. Check the resulting request template
|
|
let expected_request_templates = vec![StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Custom(CustomPredicateRef::new(
|
|
available_batch,
|
|
0,
|
|
))),
|
|
args: vec![
|
|
StatementTmplArg::Wildcard(wc("Pod1", 0)),
|
|
StatementTmplArg::Wildcard(wc("Pod2", 1)),
|
|
],
|
|
}];
|
|
|
|
assert_eq!(request_templates, expected_request_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_use_statement_complex() -> Result<(), LangError> {
|
|
let params = Params::default();
|
|
|
|
// 1. Create a batch with multiple predicates
|
|
let pred1 = CustomPredicate::and(¶ms, "p1".into(), vec![], 1, names(&["A"]))?;
|
|
let pred2 = CustomPredicate::and(¶ms, "p2".into(), vec![], 2, names(&["B", "C"]))?;
|
|
let pred3 = CustomPredicate::and(¶ms, "p3".into(), vec![], 1, names(&["D"]))?;
|
|
|
|
let available_batch =
|
|
CustomPredicateBatch::new(¶ms, "MyBatch".to_string(), vec![pred1, pred2, pred3]);
|
|
let available_batches = vec![available_batch.clone()];
|
|
|
|
// 2. Create the input string that uses the batch with skips
|
|
let batch_id_str = available_batch.id().encode_hex::<String>();
|
|
|
|
let input = format!(
|
|
r#"
|
|
use batch pred_one, _, pred_three from 0x{}
|
|
|
|
REQUEST(
|
|
pred_one(Pod1)
|
|
pred_three(Pod2)
|
|
)
|
|
"#,
|
|
batch_id_str
|
|
);
|
|
|
|
// 3. Parse the input
|
|
let processed = parse(&input, ¶ms, &available_batches)?;
|
|
let request_templates = processed.request.templates();
|
|
|
|
assert_eq!(request_templates.len(), 2, "Expected two request templates");
|
|
|
|
// 4. Check the resulting request templates
|
|
let expected_templates = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Custom(CustomPredicateRef::new(
|
|
available_batch.clone(),
|
|
0,
|
|
))),
|
|
args: vec![StatementTmplArg::Wildcard(wc("Pod1", 0))],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Custom(CustomPredicateRef::new(
|
|
available_batch,
|
|
2,
|
|
))),
|
|
args: vec![StatementTmplArg::Wildcard(wc("Pod2", 1))],
|
|
},
|
|
];
|
|
|
|
assert_eq!(request_templates, expected_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_custom_predicate_uses_import() -> Result<(), LangError> {
|
|
let params = Params::default();
|
|
|
|
// 1. Create a batch with a predicate to be imported
|
|
let imported_pred_stmts = vec![StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("A", 0), "foo"), sta_ak(("B", 1), "bar")],
|
|
}];
|
|
let imported_predicate = CustomPredicate::and(
|
|
¶ms,
|
|
"imported_equal".to_string(),
|
|
imported_pred_stmts,
|
|
2,
|
|
names(&["A", "B"]),
|
|
)?;
|
|
let available_batch =
|
|
CustomPredicateBatch::new(¶ms, "MyBatch".to_string(), vec![imported_predicate]);
|
|
let available_batches = vec![available_batch.clone()];
|
|
|
|
// 2. Create the input string that defines a new predicate using the imported one
|
|
let batch_id_str = available_batch.id().encode_hex::<String>();
|
|
|
|
let input = format!(
|
|
r#"
|
|
use batch imported_eq from 0x{}
|
|
|
|
wrapper_pred(X, Y) = AND(
|
|
imported_eq(X, Y)
|
|
)
|
|
"#,
|
|
batch_id_str
|
|
);
|
|
|
|
// 3. Parse the input
|
|
let processed = parse(&input, ¶ms, &available_batches)?;
|
|
|
|
assert!(
|
|
processed.request.templates().is_empty(),
|
|
"No request should be defined"
|
|
);
|
|
assert_eq!(
|
|
processed.custom_batch.predicates.len(),
|
|
1,
|
|
"Expected one custom predicate to be defined"
|
|
);
|
|
|
|
// 4. Check the resulting predicate definition
|
|
let defined_pred = &processed.custom_batch.predicates[0];
|
|
assert_eq!(defined_pred.name, "wrapper_pred");
|
|
assert_eq!(defined_pred.statements.len(), 1);
|
|
|
|
let expected_statement = StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Custom(CustomPredicateRef::new(
|
|
available_batch.clone(),
|
|
0,
|
|
))),
|
|
args: vec![
|
|
StatementTmplArg::Wildcard(wc("X", 0)),
|
|
StatementTmplArg::Wildcard(wc("Y", 1)),
|
|
],
|
|
};
|
|
|
|
assert_eq!(defined_pred.statements[0], expected_statement);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_intro_import_parsing() -> Result<(), LangError> {
|
|
let params = Params::default();
|
|
|
|
let intro_hash = EMPTY_HASH.encode_hex::<String>();
|
|
let input = format!(
|
|
r#"
|
|
use intro empty() from 0x{intro_hash}
|
|
|
|
REQUEST(
|
|
empty()
|
|
)
|
|
"#,
|
|
);
|
|
|
|
let processed = parse(&input, ¶ms, &[])?;
|
|
let request_templates = processed.request.templates();
|
|
assert_eq!(request_templates.len(), 1);
|
|
|
|
if let PredicateOrWildcard::Predicate(Predicate::Intro(intro_ref)) =
|
|
&request_templates[0].pred_or_wc
|
|
{
|
|
assert_eq!(intro_ref.name, "empty");
|
|
assert_eq!(intro_ref.args_len, 0);
|
|
assert_eq!(intro_ref.verifier_data_hash, EMPTY_HASH);
|
|
} else {
|
|
panic!("Expected Intro predicate");
|
|
}
|
|
|
|
assert!(request_templates[0].args.is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_literals() -> Result<(), LangError> {
|
|
let pk = crate::backends::plonky2::primitives::ec::curve::Point::generator();
|
|
let raw = RawValue::from(1);
|
|
let string = "hello";
|
|
let int = 123;
|
|
let bool = true;
|
|
let sk = SecretKey::new_rand();
|
|
|
|
let input = format!(
|
|
r#"
|
|
REQUEST(
|
|
Equal(A["pk"], {})
|
|
Equal(B["raw"], {})
|
|
Equal(C["string"], {})
|
|
Equal(D["int"], {})
|
|
Equal(E["bool"], {})
|
|
Equal(F["sk"], {})
|
|
)
|
|
"#,
|
|
Value::from(pk).to_podlang_string(),
|
|
Value::from(raw).to_podlang_string(),
|
|
Value::from(string).to_podlang_string(),
|
|
Value::from(int).to_podlang_string(),
|
|
Value::from(bool).to_podlang_string(),
|
|
Value::from(sk.clone()).to_podlang_string()
|
|
);
|
|
/*
|
|
REQUEST(
|
|
Equal(A["pk"], PublicKey(3t9fNuU194n7mSJPRdeaJRMqw6ZQCUddzvECWNe1k2b1rdBezXpJxF))
|
|
Equal(C["raw"], Raw(0x0000000000000000000000000000000000000000000000000000000000000001))
|
|
Equal(D["string"], "hello")
|
|
Equal(E["int"], 123)
|
|
Equal(F["bool"], true)
|
|
Equal(G["sk"], SecretKey(random_secret_key_base_64))
|
|
Equal(H["self"], SELF)
|
|
)
|
|
*/
|
|
|
|
let params = Params::default();
|
|
let processed = parse(&input, ¶ms, &[])?;
|
|
let request_templates = processed.request.templates();
|
|
|
|
let expected_templates = vec![
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("A", 0), "pk"), sta_lit(Value::from(pk))],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("B", 1), "raw"), sta_lit(Value::from(raw))],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("C", 2), "string"), sta_lit(Value::from(string))],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("D", 3), "int"), sta_lit(Value::from(int))],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("E", 4), "bool"), sta_lit(Value::from(bool))],
|
|
},
|
|
StatementTmpl {
|
|
pred_or_wc: pred_lit(Predicate::Native(NativePredicate::Equal)),
|
|
args: vec![sta_ak(("F", 5), "sk"), sta_lit(Value::from(sk))],
|
|
},
|
|
];
|
|
|
|
assert_eq!(request_templates, expected_templates);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_use_unknown_batch() {
|
|
let params = Params::default();
|
|
let available_batches = &[];
|
|
|
|
let unknown_batch_id = format!("0x{}", "a".repeat(64));
|
|
|
|
let input = format!(
|
|
r#"
|
|
use batch some_pred from {}
|
|
"#,
|
|
unknown_batch_id
|
|
);
|
|
|
|
let result = parse(&input, ¶ms, available_batches);
|
|
|
|
assert!(result.is_err());
|
|
|
|
match result.err().unwrap() {
|
|
LangError::Validation(e) => match *e {
|
|
frontend_ast_validate::ValidationError::BatchNotFound { id, .. } => {
|
|
assert_eq!(id, unknown_batch_id);
|
|
}
|
|
_ => panic!("Expected BatchNotFound error, but got {:?}", e),
|
|
},
|
|
e => panic!("Expected LangError::Validation, but got {:?}", e),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_undefined_wildcard() {
|
|
let params = Params::default();
|
|
let available_batches = &[];
|
|
|
|
let input = r#"
|
|
identity_verified(username, private: identity_dict) = AND(
|
|
Equal(identity_dict["username"], username)
|
|
Equal(identity_dict["user_public_key"], user_public_key)
|
|
)
|
|
"#
|
|
.to_string();
|
|
|
|
let result = parse(&input, ¶ms, available_batches);
|
|
|
|
assert!(result.is_err());
|
|
|
|
match result.err().unwrap() {
|
|
LangError::Validation(e) => match *e {
|
|
frontend_ast_validate::ValidationError::UndefinedWildcard {
|
|
name,
|
|
pred_name,
|
|
..
|
|
} => {
|
|
assert_eq!(name, "user_public_key");
|
|
assert_eq!(pred_name, "identity_verified");
|
|
}
|
|
_ => panic!("Expected UndefinedWildcard error, but got {:?}", e),
|
|
},
|
|
e => panic!("Expected LangError::Validation, but got {:?}", e),
|
|
}
|
|
}
|
|
}
|