use std::collections::BTreeMap;
use alloy::{
primitives::Address,
providers::Provider,
};
use crate::{
artifacts::Artifacts,
deploy::{
deploy_contract_from,
Libraries,
},
error::{
Error,
Result,
},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Circuit {
BearerLink,
OidcGoogle,
}
impl Circuit {
pub const ALL: [Self; 2] = [Self::BearerLink, Self::OidcGoogle];
pub const fn name(self) -> &'static str {
match self {
Self::BearerLink => "bearer-link",
Self::OidcGoogle => "oidc-google",
}
}
pub const fn contract(self) -> &'static str {
match self {
Self::BearerLink => "BearerLinkHonkVerifier",
Self::OidcGoogle => "OidcGoogleHonkVerifier",
}
}
}
pub const LIBRARIES: [&str; 2] = ["RelationsLib", "ZKTranscriptLib"];
pub fn version(artifacts: &Artifacts) -> Result<String> {
let pin: serde_json::Value = artifacts.read_json("circuits.json")?;
pin["version"]
.as_str()
.map(str::to_owned)
.ok_or_else(|| Error::Artifact {
detail: "circuits.json has no version".into(),
})
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HonkVerifiers {
pub verifiers: BTreeMap<Circuit, Address>,
pub libraries: Libraries,
}
pub async fn deploy_honk_verifiers<P: Provider>(
provider: &P,
artifacts: &Artifacts,
circuits: &[Circuit],
sender: Option<Address>,
) -> Result<HonkVerifiers> {
let contracts: Vec<(&str, &str)> = circuits
.iter()
.map(|circuit| (circuit.contract(), circuit.contract()))
.collect();
let libraries = Libraries::deploy(provider, artifacts, &contracts, sender).await?;
let mut verifiers = BTreeMap::new();
for circuit in circuits {
if verifiers.contains_key(circuit) {
continue;
}
let contract = circuit.contract();
let bytecode = libraries.link(artifacts, contract, contract)?;
let address = deploy_contract_from(
provider,
bytecode,
&format!("{contract} ({} circuit)", circuit.name()),
sender,
)
.await?;
verifiers.insert(*circuit, address);
}
Ok(HonkVerifiers {
verifiers,
libraries,
})
}
pub async fn deploy_honk_verifier<P: Provider>(
provider: &P,
artifacts: &Artifacts,
circuit: Circuit,
sender: Option<Address>,
) -> Result<Address> {
let honk = deploy_honk_verifiers(provider, artifacts, &[circuit], sender).await?;
honk.verifiers
.get(&circuit)
.copied()
.ok_or_else(|| Error::Rpc {
detail: format!("{} verifier deploy recorded no address", circuit.name()),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::artifacts::COVERED;
#[test]
fn every_verifier_and_its_libraries_are_covered() {
let artifacts = Artifacts::embedded();
for circuit in Circuit::ALL {
let contract = circuit.contract();
assert!(
COVERED.contains(&(contract, contract)),
"{contract} is not in COVERED"
);
for library in LIBRARIES {
assert!(
COVERED.contains(&(contract, library)),
"{contract}.sol:{library} is not in COVERED"
);
}
let refs = artifacts.link_references(contract, contract).unwrap();
let mut linked: Vec<(String, String)> = refs
.iter()
.flat_map(|(path, libs)| {
let stem = std::path::Path::new(path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or_else(|| panic!("{contract}: bad library path {path}"))
.to_owned();
libs.as_object()
.into_iter()
.flatten()
.map(move |(name, _)| (stem.clone(), name.clone()))
})
.collect();
linked.sort();
let mut expected: Vec<(String, String)> = LIBRARIES
.iter()
.map(|lib| (contract.to_owned(), (*lib).to_owned()))
.collect();
expected.sort();
assert_eq!(linked, expected, "{contract} links something else");
}
}
#[test]
fn the_two_circuits_are_different_artifacts() {
let artifacts = Artifacts::embedded();
let [bearer, oidc] = Circuit::ALL;
assert_ne!(bearer.contract(), oidc.contract());
assert_ne!(
artifacts
.bytecode_hex(bearer.contract(), bearer.contract())
.unwrap(),
artifacts
.bytecode_hex(oidc.contract(), oidc.contract())
.unwrap()
);
}
#[test]
fn the_enum_matches_the_pin() {
let artifacts = Artifacts::embedded();
let pin: serde_json::Value = artifacts.read_json("circuits.json").unwrap();
let circuits = pin["circuits"].as_object().expect("circuits object");
assert_eq!(circuits.len(), Circuit::ALL.len());
for circuit in Circuit::ALL {
let entry = &circuits[circuit.name()];
assert_eq!(entry["contract"].as_str(), Some(circuit.contract()));
let digest = entry["sha256"].as_str().expect("sha256 string");
assert_eq!(digest.len(), 64, "{}: not a sha256", circuit.name());
}
let version = version(&artifacts).unwrap();
assert!(
version.split('.').count() == 3,
"circuits.json version '{version}' is not major.minor.patch"
);
}
}