use async_trait::async_trait;
use serde::Serialize;
use time::OffsetDateTime;
use crate::action::TokenKey;
use crate::kill::Scope;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum Severity {
Info,
Warn,
Critical,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum DenyReason {
KillSwitch(Scope),
HardDeny(HardDeny),
NotAllowlisted(NotAllowlisted),
SimulationFailed,
SimulationReverted,
CapExceeded(CapExceeded),
SlippageExceeded,
SlippageUnbounded,
NoCaps,
InvalidCaps,
NoPrice(TokenKey),
Port(&'static str),
}
impl DenyReason {
#[must_use]
pub fn is_critical(&self) -> bool {
matches!(
self,
DenyReason::KillSwitch(_)
| DenyReason::HardDeny(_)
| DenyReason::SimulationFailed
| DenyReason::SimulationReverted
| DenyReason::Port(_)
)
}
}
impl std::fmt::Display for DenyReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DenyReason::KillSwitch(scope) => write!(f, "kill switch engaged at {}", scope.key()),
DenyReason::HardDeny(h) => write!(f, "hard deny: {h}"),
DenyReason::NotAllowlisted(n) => write!(f, "not allowlisted: {n}"),
DenyReason::SimulationFailed => write!(f, "simulation failed"),
DenyReason::SimulationReverted => write!(f, "simulation reverted"),
DenyReason::CapExceeded(c) => write!(f, "cap exceeded: {c}"),
DenyReason::SlippageExceeded => write!(f, "slippage exceeded"),
DenyReason::SlippageUnbounded => write!(f, "swap with no slippage bound"),
DenyReason::NoCaps => write!(f, "no caps configured"),
DenyReason::InvalidCaps => write!(f, "caps period invalid"),
DenyReason::NoPrice(token) => write!(f, "no price for {}", token.key()),
DenyReason::Port(name) => write!(f, "port error: {name}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum HardDeny {
ApprovalForAll,
UnlimitedApproval,
PermitUnknownSpender(String),
PermitUnlimited,
OwnerReassignment,
NonceAdvance,
Eip7702ZeroChain,
DelegateCall(String),
}
impl std::fmt::Display for HardDeny {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HardDeny::ApprovalForAll => write!(f, "setApprovalForAll"),
HardDeny::UnlimitedApproval => write!(f, "unlimited approval"),
HardDeny::PermitUnknownSpender(s) => write!(f, "permit to unknown spender {s}"),
HardDeny::PermitUnlimited => write!(f, "permit amount unlimited or unbounded"),
HardDeny::OwnerReassignment => write!(f, "owner reassignment"),
HardDeny::NonceAdvance => write!(f, "durable nonce advance"),
HardDeny::Eip7702ZeroChain => write!(f, "EIP-7702 authorization for every chain"),
HardDeny::DelegateCall(to) => write!(f, "delegatecall to {to}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum NotAllowlisted {
Chain,
Contract(String),
Selector(String),
Program(String),
Destination(String),
Spender(String),
Delegate(String),
}
impl std::fmt::Display for NotAllowlisted {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NotAllowlisted::Chain => write!(f, "chain"),
NotAllowlisted::Contract(c) => write!(f, "contract {c}"),
NotAllowlisted::Selector(s) => write!(f, "selector {s}"),
NotAllowlisted::Program(p) => write!(f, "program {p}"),
NotAllowlisted::Destination(d) => write!(f, "destination {d}"),
NotAllowlisted::Spender(s) => write!(f, "spender {s}"),
NotAllowlisted::Delegate(d) => write!(f, "delegate {d}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum CapExceeded {
PerAction,
PerDay,
PerPeriod,
}
impl std::fmt::Display for CapExceeded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CapExceeded::PerAction => write!(f, "per action"),
CapExceeded::PerDay => write!(f, "per UTC day"),
CapExceeded::PerPeriod => write!(f, "per rolling period"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum Verdict {
Allow,
Deny(Vec<DenyReason>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct AuditEntry {
pub id: String,
pub at: OffsetDateTime,
pub venture: String,
pub subject: String,
pub action: String,
pub stage: &'static str,
pub verdict: Verdict,
pub severity: Severity,
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("audit sink: {0}")]
pub struct AuditError(pub String);
#[async_trait]
pub trait AuditSink: Send + Sync {
async fn record(&self, entry: &AuditEntry) -> Result<(), AuditError>;
}