chore: enums for statement and op types (#69)

* Experiment with statement & op enums

* Clean-up & fixes

* More clean-up

* Add argument length checks

* More clean-up

* Place statement and operation logic in submodules
This commit is contained in:
Ahmad Afuni 2025-02-20 19:08:29 +10:00 committed by GitHub
parent 83a4f8969f
commit c2d23b0b1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 891 additions and 606 deletions

View file

@ -1,6 +1,9 @@
//! The frontend includes the user-level abstractions and user-friendly types to define and work
//! with Pods.
mod operation;
mod statement;
use anyhow::Result;
use itertools::Itertools;
use std::collections::HashMap;
@ -13,6 +16,8 @@ use crate::middleware::{
hash_str, Hash, MainPodInputs, NativeOperation, NativeStatement, Params, PodId, PodProver,
PodSigner, SELF,
};
pub use operation::*;
pub use statement::*;
/// This type is just for presentation purposes.
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
@ -34,6 +39,7 @@ pub enum Value {
Dictionary(Dictionary),
Set(Set),
Array(Array),
Raw(middleware::Value),
}
impl From<&str> for Value {
@ -63,6 +69,7 @@ impl From<&Value> for middleware::Value {
Value::Dictionary(d) => middleware::Value(d.commitment().0),
Value::Set(s) => middleware::Value(s.commitment().0),
Value::Array(a) => middleware::Value(a.commitment().0),
Value::Raw(v) => v.clone(),
}
}
}
@ -76,6 +83,7 @@ impl fmt::Display for Value {
Value::Dictionary(d) => write!(f, "dict:{}", d.commitment()),
Value::Set(s) => write!(f, "set:{}", s.commitment()),
Value::Array(a) => write!(f, "arr:{}", a.commitment()),
Value::Raw(v) => write!(f, "{}", v),
}
}
}
@ -159,111 +167,9 @@ impl SignedPod {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AnchoredKey(pub Origin, pub String);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StatementArg {
Literal(Value),
Key(AnchoredKey),
}
impl fmt::Display for StatementArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Literal(v) => write!(f, "{}", v),
Self::Key(r) => write!(f, "{}.{}", r.0 .1, r.1),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Statement(pub NativeStatement, pub Vec<StatementArg>);
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} ", self.0)?;
for (i, arg) in self.1.iter().enumerate() {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{}", arg)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OperationArg {
Statement(Statement),
Key(AnchoredKey),
Literal(Value),
Entry(String, Value),
}
impl fmt::Display for OperationArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OperationArg::Statement(s) => write!(f, "{}", s),
OperationArg::Key(k) => write!(f, "{}.{}", k.0 .1, k.1),
OperationArg::Literal(v) => write!(f, "{}", v),
OperationArg::Entry(k, v) => write!(f, "({}, {})", k, v),
}
}
}
impl From<Value> for OperationArg {
fn from(v: Value) -> Self {
Self::Literal(v)
}
}
impl From<&Value> for OperationArg {
fn from(v: &Value) -> Self {
Self::Literal(v.clone())
}
}
impl From<&str> for OperationArg {
fn from(s: &str) -> Self {
Self::Literal(Value::from(s))
}
}
impl From<i64> for OperationArg {
fn from(v: i64) -> Self {
Self::Literal(Value::from(v))
}
}
impl From<bool> for OperationArg {
fn from(b: bool) -> Self {
Self::Literal(Value::from(b))
}
}
impl From<(Origin, &str)> for OperationArg {
fn from((origin, key): (Origin, &str)) -> Self {
Self::Key(AnchoredKey(origin, key.to_string()))
}
}
impl From<(&SignedPod, &str)> for OperationArg {
fn from((pod, key): (&SignedPod, &str)) -> Self {
Self::Key(AnchoredKey(pod.origin(), key.to_string()))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Operation(pub NativeOperation, pub Vec<OperationArg>);
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} ", self.0)?;
for (i, arg) in self.1.iter().enumerate() {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{}", arg)?;
}
Ok(())
impl From<AnchoredKey> for middleware::AnchoredKey {
fn from(ak: AnchoredKey) -> Self {
middleware::AnchoredKey(ak.0 .1, hash_str(&ak.1))
}
}
@ -329,8 +235,13 @@ impl MainPodBuilder {
let mut st_args = Vec::new();
for arg in args.iter_mut() {
match arg {
OperationArg::Statement(_s) => panic!("can't convert Statement to StatementArg"),
OperationArg::Key(k) => st_args.push(StatementArg::Key(k.clone())),
OperationArg::Statement(s) => {
if s.0 == NativeStatement::ValueOf {
st_args.push(s.1[0].clone())
} else {
panic!("Invalid statement argument.");
}
}
OperationArg::Literal(v) => {
let k = format!("c{}", self.const_cnt);
self.const_cnt += 1;
@ -341,7 +252,7 @@ impl MainPodBuilder {
vec![OperationArg::Entry(k.clone(), v.clone())],
),
);
*arg = OperationArg::Key(AnchoredKey(Origin(PodClass::Main, SELF), k.clone()));
*arg = OperationArg::Statement(value_of_st.clone());
st_args.push(value_of_st.1[0].clone())
}
OperationArg::Entry(k, v) => {
@ -472,10 +383,9 @@ impl MainPodCompiler {
self.operations.push(op);
}
fn compile_op_arg(&self, op_arg: &OperationArg) -> middleware::OperationArg {
fn compile_op_arg(&self, op_arg: &OperationArg) -> Option<middleware::Statement> {
match op_arg {
OperationArg::Statement(s) => middleware::OperationArg::Statement(self.compile_st(s)),
OperationArg::Key(k) => middleware::OperationArg::Key(Self::compile_anchored_key(k)),
OperationArg::Statement(s) => Some(self.compile_st(s)),
OperationArg::Literal(_v) => {
// OperationArg::Literal is a syntax sugar for the frontend. This is translated to
// a new ValueOf statement and it's key used instead.
@ -485,47 +395,29 @@ impl MainPodCompiler {
// OperationArg::Entry is only used in the frontend. The (key, value) will only
// appear in the ValueOf statement in the backend. This is because a new ValueOf
// statement doesn't have any requirement on the key and value.
middleware::OperationArg::None
None
}
}
}
fn compile_anchored_key(key: &AnchoredKey) -> middleware::AnchoredKey {
middleware::AnchoredKey(key.0 .1, hash_str(&key.1))
}
fn compile_st(&self, st: &Statement) -> middleware::Statement {
let mut st_args = Vec::new();
let Statement(front_st_typ, front_st_args) = st;
for front_st_arg in front_st_args {
match front_st_arg {
StatementArg::Literal(v) => {
st_args.push(middleware::StatementArg::Literal(middleware::Value::from(
v,
)));
}
StatementArg::Key(k) => {
let key = Self::compile_anchored_key(k);
st_args.push(middleware::StatementArg::Key(key));
}
};
if st_args.len() > self.params.max_statement_args {
panic!("too many statement st_args");
}
}
st.clone().try_into().unwrap()
}
middleware::Statement(*front_st_typ, st_args)
fn compile_op(&self, op: &Operation) -> middleware::Operation {
// TODO
let mop_code: middleware::NativeOperation = op.0.into();
let mop_args =
op.1.iter()
.flat_map(|arg| self.compile_op_arg(arg).map(|s| s.try_into().unwrap()))
.collect::<Vec<middleware::Statement>>();
middleware::Operation::op(mop_code, &mop_args).unwrap()
}
fn compile_st_op(&mut self, st: &Statement, op: &Operation) {
let middle_st = self.compile_st(st);
self.push_st_op(
middle_st,
middleware::Operation(
op.0,
op.1.iter().map(|arg| self.compile_op_arg(arg)).collect(),
),
);
let middle_op = self.compile_op(op);
self.push_st_op(middle_st, middle_op);
}
pub fn compile<'a>(
@ -593,9 +485,11 @@ pub mod build_utils {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::backends::mock_main::MockProver;
use crate::backends::mock_signed::MockSigner;
use crate::examples::{great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders};
use crate::examples::{
great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder,
zu_kyc_sign_pod_builders,
};
#[test]
fn test_front_zu_kyc() -> Result<()> {