openzeppelin_relayer/models/relayer/
repository.rsuse serde::{Deserialize, Serialize};
use strum::Display;
use utoipa::ToSchema;
use crate::{
constants::{
DEFAULT_CONVERSION_SLIPPAGE_PERCENTAGE, DEFAULT_EVM_MIN_BALANCE,
DEFAULT_SOLANA_MIN_BALANCE, DEFAULT_STELLAR_MIN_BALANCE, MAX_SOLANA_TX_DATA_SIZE,
},
models::RelayerError,
};
#[derive(Debug, Clone, Serialize, PartialEq, Display, Deserialize, Copy, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum NetworkType {
Evm,
Stellar,
Solana,
}
#[derive(Debug, Serialize, Clone)]
pub enum RelayerNetworkPolicy {
Evm(RelayerEvmPolicy),
Solana(RelayerSolanaPolicy),
Stellar(RelayerStellarPolicy),
}
impl RelayerNetworkPolicy {
pub fn get_evm_policy(&self) -> RelayerEvmPolicy {
match self {
Self::Evm(policy) => policy.clone(),
_ => RelayerEvmPolicy::default(),
}
}
pub fn get_solana_policy(&self) -> RelayerSolanaPolicy {
match self {
Self::Solana(policy) => policy.clone(),
_ => RelayerSolanaPolicy::default(),
}
}
pub fn get_stellar_policy(&self) -> RelayerStellarPolicy {
match self {
Self::Stellar(policy) => policy.clone(),
_ => RelayerStellarPolicy::default(),
}
}
}
#[derive(Debug, Serialize, Clone)]
pub struct RelayerEvmPolicy {
pub gas_price_cap: Option<u128>,
pub whitelist_receivers: Option<Vec<String>>,
pub eip1559_pricing: Option<bool>,
pub private_transactions: bool,
pub min_balance: u128,
}
impl Default for RelayerEvmPolicy {
fn default() -> Self {
Self {
gas_price_cap: None,
whitelist_receivers: None,
eip1559_pricing: None,
private_transactions: false,
min_balance: DEFAULT_EVM_MIN_BALANCE,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
pub struct SolanaAllowedTokensPolicy {
pub mint: String,
#[schema(nullable = false)]
pub decimals: Option<u8>,
#[schema(nullable = false)]
pub symbol: Option<String>,
#[schema(nullable = false)]
pub max_allowed_fee: Option<u64>,
#[schema(nullable = false)]
pub conversion_slippage_percentage: Option<f32>,
}
impl SolanaAllowedTokensPolicy {
pub fn new(
mint: String,
decimals: Option<u8>,
symbol: Option<String>,
max_allowed_fee: Option<u64>,
conversion_slippage_percentage: Option<f32>,
) -> Self {
Self {
mint,
decimals,
symbol,
max_allowed_fee,
conversion_slippage_percentage,
}
}
pub fn new_partial(
mint: String,
max_allowed_fee: Option<u64>,
conversion_slippage_percentage: Option<f32>,
) -> Self {
Self {
mint,
decimals: None,
symbol: None,
max_allowed_fee,
conversion_slippage_percentage,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum SolanaFeePaymentStrategy {
User,
Relayer,
}
#[derive(Debug, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct RelayerSolanaPolicy {
pub fee_payment_strategy: SolanaFeePaymentStrategy,
pub fee_margin_percentage: Option<f32>,
pub min_balance: u64,
pub allowed_tokens: Option<Vec<SolanaAllowedTokensPolicy>>,
pub allowed_programs: Option<Vec<String>>,
pub allowed_accounts: Option<Vec<String>>,
pub disallowed_accounts: Option<Vec<String>>,
pub max_signatures: Option<u8>,
pub max_tx_data_size: u16,
pub max_allowed_fee_lamports: Option<u64>,
}
impl RelayerSolanaPolicy {
pub fn get_allowed_tokens(&self) -> Vec<SolanaAllowedTokensPolicy> {
self.allowed_tokens.clone().unwrap_or_default()
}
pub fn get_allowed_token_entry(&self, mint: &str) -> Option<SolanaAllowedTokensPolicy> {
self.allowed_tokens
.clone()
.unwrap_or_default()
.into_iter()
.find(|entry| entry.mint == mint)
}
pub fn get_allowed_token_decimals(&self, mint: &str) -> Option<u8> {
self.get_allowed_token_entry(mint)
.and_then(|entry| entry.decimals)
}
pub fn get_allowed_token_slippage(&self, mint: &str) -> f32 {
self.get_allowed_token_entry(mint)
.and_then(|entry| entry.conversion_slippage_percentage)
.unwrap_or(DEFAULT_CONVERSION_SLIPPAGE_PERCENTAGE)
}
pub fn get_allowed_programs(&self) -> Vec<String> {
self.allowed_programs.clone().unwrap_or_default()
}
pub fn get_allowed_accounts(&self) -> Vec<String> {
self.allowed_accounts.clone().unwrap_or_default()
}
pub fn get_disallowed_accounts(&self) -> Vec<String> {
self.disallowed_accounts.clone().unwrap_or_default()
}
pub fn get_max_signatures(&self) -> u8 {
self.max_signatures.unwrap_or(1)
}
pub fn get_max_allowed_fee_lamports(&self) -> u64 {
self.max_allowed_fee_lamports.unwrap_or(u64::MAX)
}
pub fn get_max_tx_data_size(&self) -> u16 {
self.max_tx_data_size
}
pub fn get_fee_margin_percentage(&self) -> f32 {
self.fee_margin_percentage.unwrap_or(0.0)
}
pub fn get_fee_payment_strategy(&self) -> SolanaFeePaymentStrategy {
self.fee_payment_strategy.clone()
}
}
impl Default for RelayerSolanaPolicy {
fn default() -> Self {
Self {
fee_payment_strategy: SolanaFeePaymentStrategy::User,
fee_margin_percentage: None,
min_balance: DEFAULT_SOLANA_MIN_BALANCE,
allowed_tokens: None,
allowed_programs: None,
allowed_accounts: None,
disallowed_accounts: None,
max_signatures: None,
max_tx_data_size: MAX_SOLANA_TX_DATA_SIZE,
max_allowed_fee_lamports: None,
}
}
}
#[derive(Debug, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct RelayerStellarPolicy {
pub max_fee: Option<u32>,
pub timeout_seconds: Option<u64>,
pub min_balance: u64,
}
impl Default for RelayerStellarPolicy {
fn default() -> Self {
Self {
max_fee: None,
timeout_seconds: None,
min_balance: DEFAULT_STELLAR_MIN_BALANCE,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RelayerRepoModel {
pub id: String,
pub name: String,
pub network: String,
pub paused: bool,
pub network_type: NetworkType,
pub signer_id: String,
pub policies: RelayerNetworkPolicy,
pub address: String,
pub notification_id: Option<String>,
pub system_disabled: bool,
pub custom_rpc_urls: Option<Vec<String>>,
}
impl RelayerRepoModel {
pub fn validate_active_state(&self) -> Result<(), RelayerError> {
if self.paused {
return Err(RelayerError::RelayerPaused);
}
if self.system_disabled {
return Err(RelayerError::RelayerDisabled);
}
Ok(())
}
}
impl Default for RelayerRepoModel {
fn default() -> Self {
Self {
id: "".to_string(),
name: "".to_string(),
network: "".to_string(),
paused: false,
network_type: NetworkType::Evm,
signer_id: "".to_string(),
policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
address: "0x".to_string(),
notification_id: None,
system_disabled: false,
custom_rpc_urls: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_relayer(paused: bool, system_disabled: bool) -> RelayerRepoModel {
RelayerRepoModel {
id: "test_relayer".to_string(),
name: "Test Relayer".to_string(),
paused,
system_disabled,
network: "test_network".to_string(),
network_type: NetworkType::Evm,
policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
signer_id: "test_signer".to_string(),
address: "0x".to_string(),
notification_id: None,
custom_rpc_urls: None,
}
}
#[test]
fn test_validate_active_state_active() {
let relayer = create_test_relayer(false, false);
assert!(relayer.validate_active_state().is_ok());
}
#[test]
fn test_validate_active_state_paused() {
let relayer = create_test_relayer(true, false);
let result = relayer.validate_active_state();
assert!(matches!(result, Err(RelayerError::RelayerPaused)));
}
#[test]
fn test_validate_active_state_disabled() {
let relayer = create_test_relayer(false, true);
let result = relayer.validate_active_state();
assert!(matches!(result, Err(RelayerError::RelayerDisabled)));
}
#[test]
fn test_validate_active_state_paused_and_disabled() {
let relayer = create_test_relayer(true, true);
let result = relayer.validate_active_state();
assert!(matches!(result, Err(RelayerError::RelayerPaused)));
}
}