openzeppelin_relayer/domain/relayer/evm/
validations.rsuse thiserror::Error;
use crate::{
models::{RelayerEvmPolicy, U256},
services::EvmProviderTrait,
};
#[derive(Debug, Error)]
pub enum EvmTransactionValidationError {
#[error("Provider error: {0}")]
ProviderError(String),
#[error("Validation error: {0}")]
ValidationError(String),
#[error("Insufficient balance: {0}")]
InsufficientBalance(String),
}
pub struct EvmTransactionValidator {}
impl EvmTransactionValidator {
pub async fn init_balance_validation(
relayer_address: &str,
policy: &RelayerEvmPolicy,
provider: &impl EvmProviderTrait,
) -> Result<(), EvmTransactionValidationError> {
let balance = provider
.get_balance(relayer_address)
.await
.map_err(|e| EvmTransactionValidationError::ProviderError(e.to_string()))?;
let min_balance = U256::from(policy.min_balance);
if balance < min_balance {
return Err(EvmTransactionValidationError::InsufficientBalance(format!(
"Relayer balance {balance} is less than the minimum enforced balance of {}",
policy.min_balance
)));
}
Ok(())
}
pub async fn validate_sufficient_relayer_balance(
balance_to_use: U256,
relayer_address: &str,
policy: &RelayerEvmPolicy,
provider: &impl EvmProviderTrait,
) -> Result<(), EvmTransactionValidationError> {
let balance = provider
.get_balance(relayer_address)
.await
.map_err(|e| EvmTransactionValidationError::ProviderError(e.to_string()))?;
let min_balance = U256::from(policy.min_balance);
let remaining_balance = balance.saturating_sub(balance_to_use);
if balance < balance_to_use {
return Err(EvmTransactionValidationError::InsufficientBalance(format!(
"Relayer balance {balance} is insufficient to cover {balance_to_use}"
)));
}
if !min_balance.is_zero() && remaining_balance < min_balance {
return Err(EvmTransactionValidationError::InsufficientBalance(
format!("Relayer balance {balance} is insufficient to cover {balance_to_use}, with an enforced minimum balance of {}", policy.min_balance)
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::future::ready;
use super::*;
use crate::services::provider::evm::MockEvmProviderTrait;
use mockall::predicate::*;
fn create_test_policy(min_balance: u128) -> RelayerEvmPolicy {
RelayerEvmPolicy {
gas_price_cap: None,
whitelist_receivers: None,
eip1559_pricing: None,
private_transactions: false,
min_balance,
}
}
#[tokio::test]
async fn test_validate_sufficient_balance_routine_check_success() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(200000000000000000u64))))); let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::ZERO,
"0xSender",
&create_test_policy(100000000000000000u128), &mock_provider,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_validate_sufficient_balance_routine_check_failure() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(50000000000000000u64))))); let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::ZERO,
"0xSender",
&create_test_policy(100000000000000000u128), &mock_provider,
)
.await;
assert!(matches!(
result,
Err(EvmTransactionValidationError::InsufficientBalance(_))
));
}
#[tokio::test]
async fn test_validate_sufficient_balance_with_transaction_success() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(300000000000000000u64))))); let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::from(100000000000000000u64), "0xSender",
&create_test_policy(100000000000000000u128), &mock_provider,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_validate_sufficient_balance_with_transaction_failure() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(150000000000000000u64))))); let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::from(100000000000000000u64), "0xSender",
&create_test_policy(100000000000000000u128), &mock_provider,
)
.await;
assert!(matches!(
result,
Err(EvmTransactionValidationError::InsufficientBalance(_))
));
}
#[tokio::test]
async fn test_validate_provider_error() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Err(eyre::eyre!("Provider error")))));
let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::ZERO,
"0xSender",
&create_test_policy(100000000000000000u128),
&mock_provider,
)
.await;
assert!(matches!(
result,
Err(EvmTransactionValidationError::ProviderError(_))
));
}
#[tokio::test]
async fn test_validate_no_min_balance_success() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(100000000000000000u64))))); let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::from(50000000000000000u64), "0xSender",
&create_test_policy(0), &mock_provider,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_validate_no_min_balance_failure() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(100000000000000000u64))))); let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
U256::from(150000000000000000u64), "0xSender",
&create_test_policy(0), &mock_provider,
)
.await;
assert!(matches!(
result,
Err(EvmTransactionValidationError::InsufficientBalance(_))
));
}
#[tokio::test]
async fn test_init_balance_validation_success() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(200000000000000000u64)))));
let result = EvmTransactionValidator::init_balance_validation(
"0xSender",
&create_test_policy(100000000000000000u128),
&mock_provider,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_init_balance_validation_failure() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(50000000000000000u64)))));
let result = EvmTransactionValidator::init_balance_validation(
"0xSender",
&create_test_policy(100000000000000000u128),
&mock_provider,
)
.await;
assert!(matches!(
result,
Err(EvmTransactionValidationError::InsufficientBalance(_))
));
}
#[tokio::test]
async fn test_init_balance_validation_provider_error() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Err(eyre::eyre!("Provider error")))));
let result = EvmTransactionValidator::init_balance_validation(
"0xSender",
&create_test_policy(100000000000000000u128),
&mock_provider,
)
.await;
assert!(matches!(
result,
Err(EvmTransactionValidationError::ProviderError(_))
));
}
#[tokio::test]
async fn test_init_balance_validation_zero_min_balance() {
let mut mock_provider = MockEvmProviderTrait::new();
mock_provider
.expect_get_balance()
.with(eq("0xSender"))
.returning(|_| Box::pin(ready(Ok(U256::from(0u64)))));
let result = EvmTransactionValidator::init_balance_validation(
"0xSender",
&create_test_policy(0), &mock_provider,
)
.await;
assert!(result.is_ok());
}
}