use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::action::{Action, Chain, U256, u256_from_u128};
use crate::deny::SEL_APPROVE;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Allowance {
pub token: String,
pub spender: String,
pub amount: U256,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum HygieneReason {
Unlimited,
SpenderNotAllowlisted,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum Remedy {
Suggest(Action),
Revoked(Action),
Unremediable,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct HygieneFinding {
pub allowance: Allowance,
pub reason: HygieneReason,
pub remedy: Remedy,
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("allowances: {0}")]
pub struct AllowanceError(pub String);
#[async_trait]
pub trait AllowanceSource: Send + Sync {
async fn open_allowances(
&self,
chain: &Chain,
owner: &str,
) -> Result<Vec<Allowance>, AllowanceError>;
}
#[must_use]
pub fn revoke_calldata(spender: &str) -> Option<Vec<u8>> {
let hexed = spender.strip_prefix("0x")?;
if hexed.len() != 40 || !hexed.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut data = SEL_APPROVE.to_be_bytes().to_vec();
let mut word = [0_u8; 32];
hex::decode_to_slice(hexed, &mut word[12..]).ok()?;
data.extend_from_slice(&word);
data.extend_from_slice(&u256_from_u128(0));
Some(data)
}
#[must_use]
pub fn revoke_action(chain_id: u64, allowance: &Allowance) -> Option<Action> {
Some(Action::EvmTx {
chain_id,
to: Some(allowance.token.clone()),
value: u256_from_u128(0),
data: revoke_calldata(&allowance.spender)?,
authorizations: Vec::new(),
})
}