feat(server::evm): more criterion types

This commit is contained in:
hdbg
2026-03-02 22:02:06 +01:00
parent 191b126462
commit 67fce6f06a
21 changed files with 14105 additions and 485 deletions

View File

@@ -2,14 +2,21 @@ use std::fmt::Display;
use alloy::primitives::{Address, Bytes, ChainId, U256};
use chrono::{DateTime, Duration, Utc};
use diesel::{result::QueryResult, sqlite::Sqlite};
use diesel_async::AsyncConnection;
use diesel::{
ExpressionMethods as _, QueryDsl, SelectableHelper, result::QueryResult,
sqlite::Sqlite,
};
use diesel_async::{AsyncConnection, RunQueryDsl};
use miette::Diagnostic;
use thiserror::Error;
use crate::db::models;
use crate::{
db::models::{self, EvmBasicGrant},
evm::utils,
};
pub mod ether_transfer;
pub mod token_transfers;
pub struct EvalContext {
// Which wallet is this transaction for
@@ -47,17 +54,23 @@ pub enum EvalViolation {
#[error("Transaction is outside of the grant's validity period")]
#[diagnostic(code(arbiter_server::evm::eval_violation::invalid_time))]
InvalidTime,
#[error("Transaction type is not allowed by this grant")]
#[diagnostic(code(arbiter_server::evm::eval_violation::invalid_transaction_type))]
InvalidTransactionType,
}
pub type DatabaseID = i32;
pub struct GrantMetadata {
pub basic_grant_id: DatabaseID,
pub policy_grant_id: DatabaseID,
pub struct Grant<PolicySettings> {
pub id: DatabaseID,
pub shared_grant_id: DatabaseID, // ID of the basic grant for shared-logic checks like rate limits and validity periods
pub shared: SharedGrantSettings,
pub settings: PolicySettings,
}
pub trait Policy: Sized {
type Grant: Send + 'static + Into<SpecificGrant>;
type Settings: Send + 'static + Into<SpecificGrant>;
type Meaning: Display + Send + 'static + Into<SpecificMeaning>;
fn analyze(context: &EvalContext) -> Option<Self::Meaning>;
@@ -65,16 +78,16 @@ pub trait Policy: Sized {
// Evaluate whether a transaction with the given meaning complies with the provided grant, and return any violations if not
// Empty vector means transaction is compliant with the grant
fn evaluate(
context: &EvalContext,
meaning: &Self::Meaning,
grant: &Self::Grant,
meta: &GrantMetadata,
grant: &Grant<Self::Settings>,
db: &mut impl AsyncConnection<Backend = Sqlite>,
) -> impl Future<Output = QueryResult<Vec<EvalViolation>>> + Send;
// Create a new grant in the database based on the provided grant details, and return its ID
fn create_grant(
basic: &models::EvmBasicGrant,
grant: &Self::Grant,
grant: &Self::Settings,
conn: &mut impl AsyncConnection<Backend = Sqlite>,
) -> impl std::future::Future<Output = QueryResult<DatabaseID>> + Send;
@@ -83,20 +96,28 @@ pub trait Policy: Sized {
fn try_find_grant(
context: &EvalContext,
conn: &mut impl AsyncConnection<Backend = Sqlite>,
) -> impl Future<Output = QueryResult<Option<(Self::Grant, GrantMetadata)>>>;
) -> impl Future<Output = QueryResult<Option<Grant<Self::Settings>>>> + Send;
// Records, updates or deletes rate limits
// In other words, records grant-specific things after transaction is executed
fn record_transaction(
context: &EvalContext,
grant: &GrantMetadata,
meaning: &Self::Meaning,
log_id: i32,
grant: &Grant<Self::Settings>,
conn: &mut impl AsyncConnection<Backend = Sqlite>,
) -> impl Future<Output = QueryResult<()>>;
}
pub enum ReceiverTarget {
Specific(Vec<Address>), // only allow transfers to these addresses
Any, // allow transfers to any address
}
// Classification of what transaction does
pub enum SpecificMeaning {
EtherTransfer(ether_transfer::Meaning),
TokenTransfer(token_transfers::Meaning),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -106,7 +127,13 @@ pub struct TransactionRateLimit {
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BasicGrant {
pub struct VolumeRateLimit {
pub max_volume: U256,
pub window: Duration,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SharedGrantSettings {
pub wallet_id: i32,
pub chain: ChainId,
@@ -119,11 +146,53 @@ pub struct BasicGrant {
pub rate_limit: Option<TransactionRateLimit>,
}
impl SharedGrantSettings {
fn try_from_model(model: EvmBasicGrant) -> QueryResult<Self> {
Ok(Self {
wallet_id: model.wallet_id,
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
valid_from: model.valid_from.map(Into::into),
valid_until: model.valid_until.map(Into::into),
max_gas_fee_per_gas: model
.max_gas_fee_per_gas
.map(|b| utils::try_bytes_to_u256(&b))
.transpose()?,
max_priority_fee_per_gas: model
.max_priority_fee_per_gas
.map(|b| utils::try_bytes_to_u256(&b))
.transpose()?,
rate_limit: match (model.rate_limit_count, model.rate_limit_window_secs) {
(Some(count), Some(window_secs)) => Some(TransactionRateLimit {
count: count as u32,
window: Duration::seconds(window_secs as i64),
}),
_ => None,
},
})
}
pub async fn query_by_id(
conn: &mut impl AsyncConnection<Backend = Sqlite>,
id: i32,
) -> diesel::result::QueryResult<Self> {
use crate::db::schema::evm_basic_grant;
let basic_grant: EvmBasicGrant = evm_basic_grant::table
.select(EvmBasicGrant::as_select())
.filter(evm_basic_grant::id.eq(id))
.first::<EvmBasicGrant>(conn)
.await?;
Self::try_from_model(basic_grant)
}
}
pub enum SpecificGrant {
EtherTransfer(ether_transfer::Grant),
EtherTransfer(ether_transfer::Settings),
TokenTransfer(token_transfers::Settings),
}
pub struct FullGrant<PolicyGrant> {
pub basic: BasicGrant,
pub basic: SharedGrantSettings,
pub specific: PolicyGrant,
}
}