use alloy::{
primitives::{Address, Bytes, FixedBytes},
providers::ProviderBuilder,
sol,
};
use thiserror::Error;
sol! {
#[sol(rpc)]
#[allow(missing_docs)]
interface IConfidentialDataRegistry {
function getConfidentialData(
address policyClient,
bytes32 domain
) external view returns (address provider, string dataRefId, uint64 version);
function getGrantedDomains(
address policyClient
) external view returns (bytes32[] memory);
}
}
#[derive(Debug, Clone)]
pub struct ConfidentialDataRef {
pub data_ref_id: String,
pub provider: Address,
pub version: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfidentialDomain {
Blacklist,
Allowlist,
}
impl ConfidentialDomain {
pub fn from_bytes32(domain: &FixedBytes<32>) -> Option<Self> {
let blacklist_hash = alloy::primitives::keccak256(b"blacklist");
let blacklist_namespaced_hash = alloy::primitives::keccak256(b"newton.confidential.blacklist");
let allowlist_hash = alloy::primitives::keccak256(b"allowlist");
let allowlist_namespaced_hash = alloy::primitives::keccak256(b"newton.confidential.allowlist");
if *domain == blacklist_hash || *domain == blacklist_namespaced_hash {
return Some(Self::Blacklist);
}
if *domain == allowlist_hash || *domain == allowlist_namespaced_hash {
return Some(Self::Allowlist);
}
None
}
pub fn rego_namespace(&self) -> &'static str {
match self {
Self::Blacklist => "blacklist",
Self::Allowlist => "allowlist",
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Blacklist => "blacklist",
Self::Allowlist => "allowlist",
}
}
}
#[derive(Debug, Error)]
pub enum ConfidentialDataError {
#[error("registry call failed: {0}")]
RegistryCallFailed(String),
#[error("no confidential data registered for policy_client={0} domain={1}")]
NotFound(String, String),
#[error("too many confidential domains for policy_client={policy_client}: got {count}, limit {limit}")]
TooManyDomains {
policy_client: String,
count: usize,
limit: usize,
},
}
pub async fn fetch_confidential_data(
rpc_url: &str,
confidential_data_registry: &Address,
policy_client: &Address,
domain: FixedBytes<32>,
) -> Result<ConfidentialDataRef, ConfidentialDataError> {
let url: alloy::transports::http::reqwest::Url = rpc_url
.parse()
.map_err(|e| ConfidentialDataError::RegistryCallFailed(format!("invalid rpc_url: {e}")))?;
let provider = ProviderBuilder::new().connect_http(url);
let registry = IConfidentialDataRegistry::new(*confidential_data_registry, provider);
let result = registry
.getConfidentialData(*policy_client, domain)
.call()
.await
.map_err(|e| ConfidentialDataError::RegistryCallFailed(e.to_string()))?;
if result.dataRefId.is_empty() {
return Err(ConfidentialDataError::NotFound(
policy_client.to_string(),
domain.to_string(),
));
}
Ok(ConfidentialDataRef {
data_ref_id: result.dataRefId,
provider: result.provider,
version: result.version,
})
}
pub async fn fetch_all_confidential_data(
rpc_url: &str,
confidential_data_registry: &Address,
policy_client: &Address,
) -> Result<Vec<(FixedBytes<32>, ConfidentialDataRef)>, ConfidentialDataError> {
let url: alloy::transports::http::reqwest::Url = rpc_url
.parse()
.map_err(|e| ConfidentialDataError::RegistryCallFailed(format!("invalid rpc_url: {e}")))?;
let provider = ProviderBuilder::new().connect_http(url);
let registry = IConfidentialDataRegistry::new(*confidential_data_registry, provider);
let domains = registry
.getGrantedDomains(*policy_client)
.call()
.await
.map_err(|e| ConfidentialDataError::RegistryCallFailed(format!("getGrantedDomains failed: {e}")))?;
const MAX_DOMAINS_PER_REQUEST: usize = 256;
if domains.len() > MAX_DOMAINS_PER_REQUEST {
return Err(ConfidentialDataError::TooManyDomains {
policy_client: policy_client.to_string(),
count: domains.len(),
limit: MAX_DOMAINS_PER_REQUEST,
});
}
let mut results = Vec::new();
for domain in domains {
let result = registry
.getConfidentialData(*policy_client, domain)
.call()
.await
.map_err(|e| ConfidentialDataError::RegistryCallFailed(e.to_string()))?;
if !result.dataRefId.is_empty() {
results.push((
domain,
ConfidentialDataRef {
data_ref_id: result.dataRefId,
provider: result.provider,
version: result.version,
},
));
}
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::keccak256;
#[test]
fn blacklist_domain_roundtrip() {
let hash = keccak256(b"blacklist");
let domain = ConfidentialDomain::from_bytes32(&hash);
assert_eq!(domain, Some(ConfidentialDomain::Blacklist));
assert_eq!(domain.unwrap().rego_namespace(), "blacklist");
}
#[test]
fn allowlist_domain_roundtrip() {
let hash = keccak256(b"allowlist");
let domain = ConfidentialDomain::from_bytes32(&hash);
assert_eq!(domain, Some(ConfidentialDomain::Allowlist));
assert_eq!(domain.unwrap().rego_namespace(), "allowlist");
}
#[test]
fn blacklist_namespaced_domain_roundtrip() {
let hash = keccak256(b"newton.confidential.blacklist");
let domain = ConfidentialDomain::from_bytes32(&hash);
assert_eq!(domain, Some(ConfidentialDomain::Blacklist));
assert_eq!(domain.unwrap().rego_namespace(), "blacklist");
}
#[test]
fn unknown_domain_returns_none() {
let hash = keccak256(b"unknown_domain_xyz");
assert_eq!(ConfidentialDomain::from_bytes32(&hash), None);
}
}