Optional dot syntax for anchored keys (#423)

This commit is contained in:
Andrew Twyman 2025-09-11 12:23:46 -07:00 committed by GitHub
parent f95a27b412
commit 7e04eb51ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 36 additions and 18 deletions

View file

@ -12,10 +12,13 @@ some of its arguments is proved, it means that a valid Merkle proof of the
value behind it exists and was used at some point to construct a `Contains`
statement that introduced that anchored key.
In PODLang, anchored key indexing can use subscript syntax `foo["bar"]` which
allows any string key, or dot syntax `foo.bar` if the key is a valid identifier.
For example:
```
0: None
1: Contains(foo, bar, 42) <- ContainsFromEntries 0 0 0 mt_proof
2: Lt(foo[bar], 100) <- LtFromEntries 1 0
3: NotEqual(foo[bar], 100) <- LtToNotEqual 2
1: Contains(foo, "bar", 42) <- ContainsFromEntries 0 0 0 mt_proof
2: Lt(foo["bar"], 100) <- LtFromEntries 1 0
3: NotEqual(foo.bar, 100) <- LtToNotEqual 2
```

View file

@ -59,7 +59,7 @@ Equals(a, c)
First, we need to decompose all the anchored keys as (dict, key) pairs. This is the frontend description of the deduction rule.
```
IF
Equals(a_or[a_key], b_or[b_key)
Equals(a_or[a_key], b_or[b_key])
AND
Equals(b_or[b_key], c_or[c_key])
THEN

View file

@ -82,10 +82,10 @@ pub fn zu_kyc_pod_request(gov_signer: &Value, pay_signer: &Value) -> Result<PodR
let input = format!(
r#"
REQUEST(
SetNotContains({sanction_set}, ?gov["idNumber"])
Lt(?gov["dateOfBirth"], {ZU_KYC_NOW_MINUS_18Y})
Equal(?pay["startDate"], {ZU_KYC_NOW_MINUS_1Y})
Equal(?gov["socialSecurityNumber"], ?pay["socialSecurityNumber"])
SetNotContains({sanction_set}, ?gov.idNumber)
Lt(?gov.dateOfBirth, {ZU_KYC_NOW_MINUS_18Y})
Equal(?pay.startDate, {ZU_KYC_NOW_MINUS_1Y})
Equal(?gov.socialSecurityNumber, ?pay.socialSecurityNumber)
SignedBy(?gov, {gov_signer})
SignedBy(?pay, {pay_signer})
// TODO: Ownership check and watermarking

View file

@ -54,8 +54,11 @@ statement_arg_list = { statement_arg ~ ("," ~ statement_arg)* }
statement = { identifier ~ "(" ~ statement_arg_list? ~ ")" }
// Anchored Key: ?Var["key_literal"]
anchored_key = { wildcard ~ "[" ~ literal_string ~ "]" }
// Anchored Key: ?Var["key_literal"] or ?Var.key_identifier
anchored_key = {
(wildcard ~ "[" ~ literal_string ~ "]")
| (wildcard ~ "." ~ identifier)
}
// Literal Values (ordered to avoid ambiguity, e.g., string before int)
literal_value = {

View file

@ -89,11 +89,16 @@ mod tests {
#[test]
fn test_parse_anchored_key() {
assert_parses(Rule::anchored_key, "?PodVar[\"literal_key\"]");
assert_parses(Rule::anchored_key, "?PodVar[\"literal key\"]");
assert_parses(Rule::anchored_key, "?PodVar.literal_key");
assert_fails(Rule::anchored_key, "PodVar[\"key\"]"); // Needs wildcard for pod
assert_fails(Rule::anchored_key, "?PodVar[invalid_key]"); // Key must be literal string or wildcard
assert_fails(Rule::anchored_key, "PodVar.key"); // Needs wildcard for pod
assert_fails(Rule::anchored_key, "?PodVar[invalid_key]"); // Key must be literal string
assert_fails(Rule::anchored_key, "?PodVar.123"); // Key must be valid identifier
assert_fails(Rule::anchored_key, "?PodVar[]"); // Key cannot be empty
assert_fails(Rule::anchored_key, "?PodVar."); // Key cannot be empty
assert_fails(Rule::anchored_key, "?PodVar[?key]"); // Key cannot be wildcard
assert_fails(Rule::anchored_key, "?PodVar.?key"); // Key cannot be wildcard
}
#[test]
@ -214,7 +219,7 @@ mod tests {
ValueOf(?ConstVal["min_age"], 18)
Gt(?UserPod["age"], ?ConstVal["min_age"])
// User must not be banned
NotContains(?_BANNED_USERS["list"], ?UserPod["userId"])
NotContains(?_BANNED_USERS.list, ?UserPod.userId)
)
REQUEST(

View file

@ -356,7 +356,14 @@ fn pest_pair_to_builder_arg(
}
let key_part_pair = inner_ak_pairs.next().unwrap();
let key_str = parse_pest_string_literal(&key_part_pair)?;
let key_str = match key_part_pair.as_rule() {
Rule::literal_string => parse_pest_string_literal(&key_part_pair)?,
Rule::identifier => key_part_pair.as_str().to_string(),
_ => unreachable!(
"unknown key type in anchored key: {:?}",
key_part_pair.as_rule()
),
};
Ok(BuilderArg::Key(root_wc_str.to_string(), key_str))
}
_ => unreachable!("Unexpected rule: {:?}", arg_content_pair.as_rule()),
@ -1065,7 +1072,7 @@ mod processor_tests {
#[test]
fn test_fp_only_request() -> Result<(), ProcessorError> {
let input = "REQUEST( Equal(?A[\"k\"],?B[\"k\"]) )"; // Escaped quotes
let input = "REQUEST( Equal(?A[\"k\"],?B.k) )"; // Escaped quotes
let pairs = get_document_content_pairs(input)?;
let params = Params::default();
let mut ctx = ProcessingContext::new(&params);
@ -1082,7 +1089,7 @@ mod processor_tests {
#[test]
fn test_fp_simple_predicate() -> Result<(), ProcessorError> {
let input = "my_pred(A, B) = AND( Equal(?A[\"k\"],?B[\"k\"]) )"; // Escaped quotes
let input = "my_pred(A, B) = AND( Equal(?A[\"k\"],?B.k) )"; // Escaped quotes
let pairs = get_document_content_pairs(input)?;
let params = Params::default();
let mut ctx = ProcessingContext::new(&params);
@ -1104,7 +1111,7 @@ mod processor_tests {
#[test]
fn test_fp_multiple_predicates() -> Result<(), ProcessorError> {
let input = r#"
pred1(X) = AND( Equal(?X["k"],?X["k"]) )
pred1(X) = AND( Equal(?X["k"],?X.k) )
pred2(Y, Z) = OR( Equal(?Y["v"], 123) )
"#;
let pairs = get_document_content_pairs(input)?;
@ -1253,7 +1260,7 @@ mod processor_tests {
// Native predicate names are case-sensitive
let input = r#"
REQUEST(
EQUAL(?A["b"], ?C["d"])
EQUAL(?A["b"], ?C.d)
)
"#;
let pairs = get_document_content_pairs(input)?;