Skip to content

Commit

Permalink
Return a proper Evaluation type
Browse files Browse the repository at this point in the history
This helps distinguishing errors from rule violations. It also allows the caller to decide  whether to accept a premint based on the specific results.
  • Loading branch information
ligustah committed Apr 5, 2024
1 parent 28e36f4 commit 7db9571
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 46 deletions.
25 changes: 17 additions & 8 deletions src/premints/zora_premint_v2/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use alloy_primitives::Signature;
use alloy_sol_types::SolStruct;

use crate::premints::zora_premint_v2::types::ZoraPremintV2;
use crate::rules::{Rule, RuleContext};
use crate::rules::Evaluation::{Accept, Reject};
use crate::rules::{Evaluation, Rule, RuleContext};
use crate::typed_rule;
use crate::types::{Premint, PremintTypes};

Expand All @@ -13,11 +14,11 @@ use crate::types::{Premint, PremintTypes};
pub async fn is_authorized_to_create_premint(
premint: ZoraPremintV2,
context: RuleContext,
) -> eyre::Result<bool> {
) -> eyre::Result<Evaluation> {
// * if contract exists, check if the signer is the contract admin
// * if contract does not exist, check if the signer is the proposed contract admin
// * this logic exists as a function on the premint executor contract
Ok(true)
Ok(Accept)
}

// * signatureIsValid ( this can be performed entirely offline )
Expand All @@ -27,7 +28,7 @@ pub async fn is_authorized_to_create_premint(
pub async fn is_valid_signature(
premint: ZoraPremintV2,
context: RuleContext,
) -> eyre::Result<bool> {
) -> eyre::Result<Evaluation> {
// * if contract exists, check if the signer is the contract admin
// * if contract does not exist, check if the signer is the proposed contract admin

Expand All @@ -37,7 +38,14 @@ pub async fn is_valid_signature(
let hash = premint.premint.eip712_signing_hash(&domain);
let signer = signature.recover_address_from_prehash(&hash)?;

Ok(signer == premint.collection.contractAdmin)
if signer != premint.collection.contractAdmin {
return Ok(Reject(format!(
"Invalid signature for contract admin {}",
premint.collection.contractAdmin
)));
}

Ok(Accept)
}

pub fn all_rules() -> Vec<Box<dyn Rule>> {
Expand Down Expand Up @@ -88,8 +96,9 @@ mod test {
#[tokio::test]
async fn test_is_valid_signature() {
let premint: ZoraPremintV2 = serde_json::from_str(PREMINT_JSON).unwrap();
assert!(is_valid_signature(premint, RuleContext {})
.await
.expect("failed to check signature"));
assert!(matches!(
is_valid_signature(premint, RuleContext {}).await,
Ok(Accept)
));
}
}
126 changes: 88 additions & 38 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,46 @@ use futures::future::join_all;

use crate::types::{Premint, PremintTypes};

#[derive(Debug)]
pub enum Evaluation {
Accept,
Ignore,
Reject(String),
}

#[derive(Debug)]
pub struct RuleResult {
pub rule_name: &'static str,
pub result: eyre::Result<Evaluation>,
}

#[derive(Debug)]
pub struct Results(Vec<RuleResult>);

impl Results {
pub fn is_accept(&self) -> bool {
!self.is_reject()
}

pub fn is_reject(&self) -> bool {
!self.is_err()
&& self
.0
.iter()
.any(|r| matches!(r.result, Ok(Evaluation::Reject(_))))
}

pub fn is_err(&self) -> bool {
self.0.iter().any(|r| matches!(r.result, Err(_)))
}
}

#[derive(Clone)]
pub struct RuleContext {}

#[async_trait]
pub trait Rule: Send + Sync {
async fn check(&self, item: PremintTypes, context: RuleContext) -> eyre::Result<bool>;
async fn check(&self, item: PremintTypes, context: RuleContext) -> eyre::Result<Evaluation>;
fn rule_name(&self) -> &'static str;
}

Expand All @@ -18,9 +52,9 @@ pub struct FnRule<T>(pub &'static str, pub T);
impl<T, Fut> Rule for FnRule<T>
where
T: Fn(PremintTypes, RuleContext) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = eyre::Result<bool>> + Send,
Fut: std::future::Future<Output = eyre::Result<Evaluation>> + Send,
{
async fn check(&self, item: PremintTypes, context: RuleContext) -> eyre::Result<bool> {
async fn check(&self, item: PremintTypes, context: RuleContext) -> eyre::Result<Evaluation> {
self.1(item, context).await
}

Expand All @@ -47,7 +81,7 @@ macro_rules! metadata_rule {
&self,
item: crate::types::PremintTypes,
context: crate::rules::RuleContext,
) -> eyre::Result<bool> {
) -> eyre::Result<crate::rules::Evaluation> {
$fn(item.metadata(), context).await
}

Expand All @@ -71,10 +105,10 @@ macro_rules! typed_rule {
&self,
item: crate::types::PremintTypes,
context: crate::rules::RuleContext,
) -> eyre::Result<bool> {
) -> eyre::Result<crate::rules::Evaluation> {
match item {
$t(premint) => $fn(premint, context).await,
_ => Ok(true),
_ => Ok(crate::rules::Evaluation::Ignore),
}
}

Expand Down Expand Up @@ -108,35 +142,30 @@ impl RulesEngine {
self.rules.push(Box::new(rule));
}

pub async fn evaluate(&self, item: PremintTypes, context: RuleContext) -> eyre::Result<bool> {
pub async fn evaluate(&self, item: PremintTypes, context: RuleContext) -> Results {
let results: Vec<_> = self
.rules
.iter()
.map(|rule| rule.check(item.clone(), context.clone()))
.collect();
let all_checks = join_all(results).await;

// TODO: ideally we'd want to return a list of all errors
// so that a caller could determine which rules failed and why
for error in all_checks.into_iter() {
match error {
Err(e) => {
return Err(e);
}
Ok(pass) => {
if !pass {
return Ok(false);
}
}
}
}

Ok(true)
Results(
all_checks
.into_iter()
.zip(self.rules.iter())
.map(|(result, rule)| RuleResult {
rule_name: rule.rule_name(),
result,
})
.collect(),
)
}
}

mod general {
use crate::rules::{Rule, RuleContext};
use crate::rules::Evaluation::{Accept, Reject};
use crate::rules::{Evaluation, Rule, RuleContext};
use crate::types::PremintMetadata;

pub fn all_rules() -> Vec<Box<dyn Rule>> {
Expand All @@ -146,15 +175,23 @@ mod general {
pub async fn token_uri_length(
meta: PremintMetadata,
context: RuleContext,
) -> eyre::Result<bool> {
) -> eyre::Result<Evaluation> {
let max_allowed = if meta.uri.starts_with("data:") {
// allow some more data for data uris
8 * 1024
} else {
2 * 1024
};

Ok(meta.uri.len() <= max_allowed)
Ok(match meta.uri.len() {
0 => Reject("Token URI is empty".to_string()),
_ if meta.uri.len() > max_allowed => Reject(format!(
"Token URI is too long: {} > {}",
meta.uri.len(),
max_allowed
)),
_ => Accept,
})
}
}

Expand All @@ -163,30 +200,43 @@ mod test {
use alloy_primitives::U256;

use crate::premints::zora_premint_v2::types::ZoraPremintV2;
use crate::rules::Evaluation::{Accept, Reject};
use crate::types::SimplePremint;

use super::*;

async fn simple_rule(item: PremintTypes, context: RuleContext) -> eyre::Result<bool> {
Ok(true)
async fn simple_rule(item: PremintTypes, context: RuleContext) -> eyre::Result<Evaluation> {
Ok(Accept)
}

async fn conditional_rule(item: PremintTypes, context: RuleContext) -> eyre::Result<bool> {
async fn conditional_rule(
item: PremintTypes,
context: RuleContext,
) -> eyre::Result<Evaluation> {
match item {
PremintTypes::Simple(s) => Ok(s.metadata().chain_id == U256::default()),
_ => Ok(true),
PremintTypes::Simple(s) => {
if s.metadata().chain_id == U256::default() {
Ok(Accept)
} else {
Ok(Reject("Chain ID is not default".to_string()))
}
}
_ => Ok(Accept),
}
}

async fn simple_typed_rule(item: SimplePremint, context: RuleContext) -> eyre::Result<bool> {
Ok(true)
async fn simple_typed_rule(
item: SimplePremint,
context: RuleContext,
) -> eyre::Result<Evaluation> {
Ok(Accept)
}

async fn simple_typed_zora_rule(
item: ZoraPremintV2,
context: RuleContext,
) -> eyre::Result<bool> {
Ok(true)
) -> eyre::Result<Evaluation> {
Ok(Accept)
}

#[tokio::test]
Expand All @@ -197,7 +247,7 @@ mod test {
.check(PremintTypes::Simple(Default::default()), context)
.await
.unwrap();
assert!(result);
assert!(matches!(result, Accept));
}

#[tokio::test]
Expand All @@ -211,7 +261,7 @@ mod test {
.evaluate(PremintTypes::Simple(Default::default()), context)
.await;

assert!(result.unwrap());
assert!(result.is_accept());
}

#[tokio::test]
Expand All @@ -235,6 +285,6 @@ mod test {
.evaluate(PremintTypes::Simple(Default::default()), context)
.await;

assert!(result.unwrap());
assert!(result.is_accept());
}
}

0 comments on commit 7db9571

Please sign in to comment.