Gas enforcement policy requiring payment to meet estimated costs (#1083)
* All compiles * Rename files fwd request -> sponsored call * More scaffolding to get the API key in there * Cleaning up * Rename task_status_call to task_status * Finish rename * rm useForDisabledOriginChains * Introduce TransactionSubmitterType * submitter type -> submission type * Pass around gelato_config instead of the sponsor api key * Final cleanup * Nit * Use default tx submission type * Wip * wip * Getting there * Getting there * Refactor of serial submitter * Some renames * cargo fmt * Some testing * Nits after some testing * A bit of a refactor of policies * Adding some chain enums * Single source of truth for abacus domain IDs * Add local test chains * Get coingecko api key via external-secrets * Move some things around, rm gelato oracle api interaction * Gonna move to abacus-core * Move chain stuff to abacus-core * nit * comment nit * Deploy tooling * MeetsEstimatedCost policy test * PR comments, still need to merge domain enums * Single AbacusDomain enumpull/1094/head
parent
9b66dfebbb
commit
7040e5c8a4
@ -0,0 +1,71 @@ |
|||||||
|
use std::fmt::Debug; |
||||||
|
|
||||||
|
use abacus_core::{ |
||||||
|
db::{AbacusDB, DbError}, |
||||||
|
CommittedMessage, TxCostEstimate, |
||||||
|
}; |
||||||
|
use async_trait::async_trait; |
||||||
|
use ethers::types::U256; |
||||||
|
use eyre::Result; |
||||||
|
|
||||||
|
use crate::settings::GasPaymentEnforcementPolicy; |
||||||
|
|
||||||
|
use self::policies::{ |
||||||
|
GasPaymentPolicyMeetsEstimatedCost, GasPaymentPolicyMinimum, GasPaymentPolicyNone, |
||||||
|
}; |
||||||
|
|
||||||
|
mod policies; |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
pub trait GasPaymentPolicy: Debug + Send + Sync { |
||||||
|
async fn message_meets_gas_payment_requirement( |
||||||
|
&self, |
||||||
|
message: &CommittedMessage, |
||||||
|
current_payment: &U256, |
||||||
|
tx_cost_estimate: &TxCostEstimate, |
||||||
|
) -> Result<bool>; |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct GasPaymentEnforcer { |
||||||
|
policy: Box<dyn GasPaymentPolicy>, |
||||||
|
db: AbacusDB, |
||||||
|
} |
||||||
|
|
||||||
|
impl GasPaymentEnforcer { |
||||||
|
pub fn new(policy_config: GasPaymentEnforcementPolicy, db: AbacusDB) -> Self { |
||||||
|
let policy: Box<dyn GasPaymentPolicy> = match policy_config { |
||||||
|
GasPaymentEnforcementPolicy::None => Box::new(GasPaymentPolicyNone::new()), |
||||||
|
GasPaymentEnforcementPolicy::Minimum { payment } => { |
||||||
|
Box::new(GasPaymentPolicyMinimum::new(payment)) |
||||||
|
} |
||||||
|
GasPaymentEnforcementPolicy::MeetsEstimatedCost { coingeckoapikey } => { |
||||||
|
Box::new(GasPaymentPolicyMeetsEstimatedCost::new(coingeckoapikey)) |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Self { policy, db } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl GasPaymentEnforcer { |
||||||
|
/// Returns (gas payment requirement met, current payment according to the DB)
|
||||||
|
pub async fn message_meets_gas_payment_requirement( |
||||||
|
&self, |
||||||
|
message: &CommittedMessage, |
||||||
|
tx_cost_estimate: &TxCostEstimate, |
||||||
|
) -> Result<(bool, U256)> { |
||||||
|
let current_payment = self.get_message_gas_payment(message.leaf_index)?; |
||||||
|
|
||||||
|
let meets_requirement = self |
||||||
|
.policy |
||||||
|
.message_meets_gas_payment_requirement(message, ¤t_payment, tx_cost_estimate) |
||||||
|
.await?; |
||||||
|
|
||||||
|
Ok((meets_requirement, current_payment)) |
||||||
|
} |
||||||
|
|
||||||
|
fn get_message_gas_payment(&self, msg_leaf_index: u32) -> Result<U256, DbError> { |
||||||
|
self.db.retrieve_gas_payment_for_leaf(msg_leaf_index) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,382 @@ |
|||||||
|
use std::{ |
||||||
|
collections::HashMap, |
||||||
|
time::{Duration, Instant}, |
||||||
|
}; |
||||||
|
|
||||||
|
use abacus_core::{AbacusDomain, CommittedMessage, TxCostEstimate}; |
||||||
|
use async_trait::async_trait; |
||||||
|
use coingecko::CoinGeckoClient; |
||||||
|
use ethers::types::U256; |
||||||
|
use eyre::{eyre, Result}; |
||||||
|
use tokio::sync::RwLock; |
||||||
|
|
||||||
|
use crate::msg::gas_payment::GasPaymentPolicy; |
||||||
|
|
||||||
|
const CACHE_TTL_SECONDS: u64 = 60; |
||||||
|
/// 1 / 100th of a cent
|
||||||
|
const FIXED_POINT_PRECISION: usize = 1000; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
struct CachedValue<T> { |
||||||
|
created_at: Instant, |
||||||
|
value: T, |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> From<T> for CachedValue<T> { |
||||||
|
fn from(value: T) -> Self { |
||||||
|
Self { |
||||||
|
created_at: Instant::now(), |
||||||
|
value, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Given a domain, gets the CoinGecko ID for the native token.
|
||||||
|
/// If the domain isn't a mainnet (and therefore doesn't have a native
|
||||||
|
/// token with a CoinGecko ID), an Err is returned.
|
||||||
|
fn abacus_domain_id_to_native_token_coingecko_id(domain_id: u32) -> Result<&'static str> { |
||||||
|
let abacus_domain = AbacusDomain::try_from(domain_id)?; |
||||||
|
|
||||||
|
Ok(match abacus_domain { |
||||||
|
AbacusDomain::Ethereum => "ethereum", |
||||||
|
AbacusDomain::Polygon => "matic-network", |
||||||
|
AbacusDomain::Avalanche => "avalanche-2", |
||||||
|
// Arbitrum's native token is Ethereum
|
||||||
|
AbacusDomain::Arbitrum => "ethereum", |
||||||
|
// Optimism's native token is Ethereum
|
||||||
|
AbacusDomain::Optimism => "ethereum", |
||||||
|
AbacusDomain::BinanceSmartChain => "binancecoin", |
||||||
|
AbacusDomain::Celo => "celo", |
||||||
|
_ => eyre::bail!("No CoinGecko ID for domain {abacus_domain}"), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets prices from CoinGecko quoted in USD, caching them with a TTL.
|
||||||
|
#[derive(Default)] |
||||||
|
struct CoinGeckoCachingPriceGetter { |
||||||
|
coingecko: CoinGeckoClient, |
||||||
|
cache_ttl: Duration, |
||||||
|
/// Keyed by CoinGecko API ID. RwLock to be thread-safe.
|
||||||
|
cached_usd_prices: RwLock<HashMap<&'static str, CachedValue<f64>>>, |
||||||
|
} |
||||||
|
|
||||||
|
impl std::fmt::Debug for CoinGeckoCachingPriceGetter { |
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||||
|
write!(f, "CoinGeckoCachingPriceGetter {{ .. }}",) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl CoinGeckoCachingPriceGetter { |
||||||
|
pub fn new(cache_ttl: Duration, coingecko_api_key: Option<String>) -> Self { |
||||||
|
let coingecko = if let Some(api_key) = coingecko_api_key { |
||||||
|
CoinGeckoClient::new_with_key("https://pro-api.coingecko.com/api/v3".into(), api_key) |
||||||
|
} else { |
||||||
|
CoinGeckoClient::new("https://api.coingecko.com/api/v3".into()) |
||||||
|
}; |
||||||
|
|
||||||
|
Self { |
||||||
|
cache_ttl, |
||||||
|
coingecko, |
||||||
|
cached_usd_prices: RwLock::default(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_cached_usd_price(&self, coingecko_id: &'static str) -> Option<f64> { |
||||||
|
let cached_usd_prices = self.cached_usd_prices.read().await; |
||||||
|
|
||||||
|
if let Some(cached_value) = cached_usd_prices.get(coingecko_id) { |
||||||
|
if cached_value.created_at.elapsed() <= self.cache_ttl { |
||||||
|
return Some(cached_value.value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
None |
||||||
|
} |
||||||
|
|
||||||
|
async fn set_cached_usd_price(&self, coingecko_id: &'static str, usd_price: f64) { |
||||||
|
let mut cached_usd_prices = self.cached_usd_prices.write().await; |
||||||
|
cached_usd_prices.insert(coingecko_id, usd_price.into()); |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_usd_price(&self, coingecko_id: &'static str) -> Result<f64> { |
||||||
|
if let Some(usd_price) = self.get_cached_usd_price(coingecko_id).await { |
||||||
|
return Ok(usd_price); |
||||||
|
} |
||||||
|
|
||||||
|
// Returns a HashMap keyed by coingecko IDs
|
||||||
|
let api_response = self |
||||||
|
.coingecko |
||||||
|
.price(&[coingecko_id], &["usd"], false, false, false, false) |
||||||
|
.await?; |
||||||
|
let usd_price = api_response |
||||||
|
.get(coingecko_id) |
||||||
|
.and_then(|p| p.usd) |
||||||
|
.ok_or_else(|| { |
||||||
|
eyre!( |
||||||
|
"Unable to get USD price for {} from CoinGecko API response", |
||||||
|
coingecko_id |
||||||
|
) |
||||||
|
})?; |
||||||
|
|
||||||
|
self.set_cached_usd_price(coingecko_id, usd_price).await; |
||||||
|
|
||||||
|
Ok(usd_price) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct GasPaymentPolicyMeetsEstimatedCost { |
||||||
|
coingecko_price_getter: CoinGeckoCachingPriceGetter, |
||||||
|
} |
||||||
|
|
||||||
|
impl GasPaymentPolicyMeetsEstimatedCost { |
||||||
|
pub fn new(coingecko_api_key: Option<String>) -> Self { |
||||||
|
Self { |
||||||
|
coingecko_price_getter: CoinGeckoCachingPriceGetter::new( |
||||||
|
Duration::from_secs(CACHE_TTL_SECONDS), |
||||||
|
coingecko_api_key, |
||||||
|
), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_native_token_usd_price(&self, domain: u32) -> Result<f64> { |
||||||
|
let coingecko_id = abacus_domain_id_to_native_token_coingecko_id(domain)?; |
||||||
|
self.coingecko_price_getter |
||||||
|
.get_usd_price(coingecko_id) |
||||||
|
.await |
||||||
|
} |
||||||
|
|
||||||
|
async fn convert_native_tokens( |
||||||
|
&self, |
||||||
|
amount: U256, |
||||||
|
from_domain: u32, |
||||||
|
to_domain: u32, |
||||||
|
) -> Result<U256> { |
||||||
|
convert_tokens( |
||||||
|
amount, |
||||||
|
self.get_native_token_usd_price(from_domain).await?, |
||||||
|
self.get_native_token_usd_price(to_domain).await?, |
||||||
|
) |
||||||
|
.ok_or_else(|| { |
||||||
|
eyre!( |
||||||
|
"Unable to convert {} native tokens from {} to {}", |
||||||
|
amount, |
||||||
|
from_domain, |
||||||
|
to_domain |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl GasPaymentPolicy for GasPaymentPolicyMeetsEstimatedCost { |
||||||
|
/// Returns (gas payment requirement met, current payment according to the DB)
|
||||||
|
async fn message_meets_gas_payment_requirement( |
||||||
|
&self, |
||||||
|
message: &CommittedMessage, |
||||||
|
current_payment: &U256, |
||||||
|
tx_cost_estimate: &TxCostEstimate, |
||||||
|
) -> Result<bool> { |
||||||
|
// Estimated cost of the process tx, quoted in destination native tokens
|
||||||
|
let destination_token_tx_cost = tx_cost_estimate.gas_limit * tx_cost_estimate.gas_price; |
||||||
|
// Convert the destination token tx cost into origin tokens
|
||||||
|
let origin_token_tx_cost = self |
||||||
|
.convert_native_tokens( |
||||||
|
destination_token_tx_cost, |
||||||
|
message.message.destination, |
||||||
|
message.message.origin, |
||||||
|
) |
||||||
|
.await?; |
||||||
|
|
||||||
|
let meets_requirement = *current_payment >= origin_token_tx_cost; |
||||||
|
tracing::info!( |
||||||
|
message_leaf_index=?message.leaf_index, |
||||||
|
tx_cost_estimate=?tx_cost_estimate, |
||||||
|
destination_token_tx_cost=?destination_token_tx_cost, |
||||||
|
origin_token_tx_cost=?origin_token_tx_cost, |
||||||
|
current_payment=?current_payment, |
||||||
|
meets_requirement=?meets_requirement, |
||||||
|
"Evaluated whether message gas payment meets estimated cost", |
||||||
|
); |
||||||
|
|
||||||
|
Ok(meets_requirement) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn f64_to_fixed_point(f: f64, precision: usize) -> U256 { |
||||||
|
U256::from_f64_lossy(f * precision as f64) |
||||||
|
} |
||||||
|
|
||||||
|
fn convert_tokens(amount: U256, from_price: f64, to_price: f64) -> Option<U256> { |
||||||
|
let from_price = f64_to_fixed_point(from_price, FIXED_POINT_PRECISION); |
||||||
|
let to_price = f64_to_fixed_point(to_price, FIXED_POINT_PRECISION); |
||||||
|
|
||||||
|
amount |
||||||
|
.checked_mul(from_price) |
||||||
|
.and_then(|n| n.checked_div(to_price)) |
||||||
|
} |
||||||
|
|
||||||
|
#[tokio::test] |
||||||
|
async fn test_gas_payment_policy_meets_estimated_cost() { |
||||||
|
use abacus_core::AbacusMessage; |
||||||
|
use ethers::types::H256; |
||||||
|
|
||||||
|
// Using a fake message from Celo -> Polygon, based off
|
||||||
|
// hardcoded tx cost estimates and prices, assert that a payment
|
||||||
|
// that doesn't meet the expected costs returns false, and a payment
|
||||||
|
// that does returns true.
|
||||||
|
|
||||||
|
let celo_price = 5.5f64; |
||||||
|
let polygon_price = 11.0f64; |
||||||
|
let celo_domain_id = u32::from(AbacusDomain::Celo); |
||||||
|
let polygon_domain_id = u32::from(AbacusDomain::Polygon); |
||||||
|
|
||||||
|
// Take advantage of the coingecko_price_getter caching already-stored values
|
||||||
|
// by just writing to them directly.
|
||||||
|
// This is a little sketchy because if the cache TTL does elapse, an API
|
||||||
|
// request could be made. Because this TTL is 60 seconds, this isn't reasonable.
|
||||||
|
let policy = GasPaymentPolicyMeetsEstimatedCost::new(None); |
||||||
|
{ |
||||||
|
let mut usd_prices = policy |
||||||
|
.coingecko_price_getter |
||||||
|
.cached_usd_prices |
||||||
|
.write() |
||||||
|
.await; |
||||||
|
let celo_coingecko_id = |
||||||
|
abacus_domain_id_to_native_token_coingecko_id(celo_domain_id).unwrap(); |
||||||
|
let polygon_coingecko_id = |
||||||
|
abacus_domain_id_to_native_token_coingecko_id(polygon_domain_id).unwrap(); |
||||||
|
|
||||||
|
usd_prices.insert(celo_coingecko_id, celo_price.into()); |
||||||
|
usd_prices.insert(polygon_coingecko_id, polygon_price.into()); |
||||||
|
} |
||||||
|
|
||||||
|
let message = CommittedMessage { |
||||||
|
leaf_index: 10u32, |
||||||
|
message: AbacusMessage { |
||||||
|
origin: celo_domain_id, |
||||||
|
destination: polygon_domain_id, |
||||||
|
sender: H256::zero(), |
||||||
|
recipient: H256::zero(), |
||||||
|
body: vec![], |
||||||
|
}, |
||||||
|
}; |
||||||
|
let tx_cost_estimate = TxCostEstimate { |
||||||
|
// 1M gas
|
||||||
|
gas_limit: U256::from(1000000u32), |
||||||
|
// 15 gwei
|
||||||
|
gas_price: ethers::utils::parse_units("15", "gwei").unwrap(), |
||||||
|
}; |
||||||
|
|
||||||
|
// Expected polygon fee: 1M * 15 gwei = 0.015 MATIC
|
||||||
|
// Converted into Celo, 0.015 MATIC * ($11 / $5.5) = 0.03 CELO
|
||||||
|
let required_celo_payment = ethers::utils::parse_ether("0.03").unwrap(); |
||||||
|
|
||||||
|
// Any less than 0.03 CELO as payment, return false.
|
||||||
|
assert_eq!( |
||||||
|
policy |
||||||
|
.message_meets_gas_payment_requirement( |
||||||
|
&message, |
||||||
|
&(required_celo_payment - U256::one()), |
||||||
|
&tx_cost_estimate, |
||||||
|
) |
||||||
|
.await |
||||||
|
.unwrap(), |
||||||
|
false, |
||||||
|
); |
||||||
|
|
||||||
|
// If the payment is at least 0.03 CELO, return true.
|
||||||
|
assert_eq!( |
||||||
|
policy |
||||||
|
.message_meets_gas_payment_requirement( |
||||||
|
&message, |
||||||
|
&required_celo_payment, |
||||||
|
&tx_cost_estimate, |
||||||
|
) |
||||||
|
.await |
||||||
|
.unwrap(), |
||||||
|
true, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_convert_tokens() { |
||||||
|
// A lowish number
|
||||||
|
|
||||||
|
// Converting to a less valuable token
|
||||||
|
assert_eq!( |
||||||
|
convert_tokens( |
||||||
|
// 1M
|
||||||
|
U256::from(1000000), |
||||||
|
20000.0f64, |
||||||
|
2000.0f64, |
||||||
|
), |
||||||
|
// 10M
|
||||||
|
Some(U256::from(10000000)), |
||||||
|
); |
||||||
|
|
||||||
|
// Converting to a more valuable token
|
||||||
|
assert_eq!( |
||||||
|
convert_tokens( |
||||||
|
// 10M
|
||||||
|
U256::from(10000000), |
||||||
|
2000.0f64, |
||||||
|
20000.0f64, |
||||||
|
), |
||||||
|
// 1M
|
||||||
|
Some(U256::from(1000000)), |
||||||
|
); |
||||||
|
|
||||||
|
// A higher number
|
||||||
|
|
||||||
|
// Converting to a less valuable token
|
||||||
|
assert_eq!( |
||||||
|
convert_tokens( |
||||||
|
// 100 ether
|
||||||
|
ethers::utils::parse_ether(100u32).unwrap(), |
||||||
|
20000.0f64, |
||||||
|
200.0f64, |
||||||
|
), |
||||||
|
// 10000 ether
|
||||||
|
Some(ethers::utils::parse_ether(10000u32).unwrap()), |
||||||
|
); |
||||||
|
|
||||||
|
// Converting to a more valuable token
|
||||||
|
assert_eq!( |
||||||
|
convert_tokens( |
||||||
|
// 10000 ether
|
||||||
|
ethers::utils::parse_ether(10000u32).unwrap(), |
||||||
|
200.0f64, |
||||||
|
20000.0f64, |
||||||
|
), |
||||||
|
// 100 ether
|
||||||
|
Some(ethers::utils::parse_ether(100u32).unwrap()), |
||||||
|
); |
||||||
|
|
||||||
|
// If the to_price is 0
|
||||||
|
assert_eq!( |
||||||
|
convert_tokens( |
||||||
|
// 1M
|
||||||
|
U256::from(1000000), |
||||||
|
20000.0f64, |
||||||
|
0f64, |
||||||
|
), |
||||||
|
None, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_abacus_domain_id_to_native_token_coingecko_id() { |
||||||
|
use abacus_core::AbacusDomainType; |
||||||
|
use strum::IntoEnumIterator; |
||||||
|
|
||||||
|
// Iterate through all AbacusDomains, ensuring all mainnet domains
|
||||||
|
// are included in abacus_domain_id_to_native_token_coingecko_id.
|
||||||
|
for abacus_domain in AbacusDomain::iter() { |
||||||
|
if let AbacusDomainType::Mainnet = abacus_domain.domain_type() { |
||||||
|
assert!( |
||||||
|
abacus_domain_id_to_native_token_coingecko_id(u32::from(abacus_domain)).is_ok() |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
use abacus_core::{CommittedMessage, TxCostEstimate}; |
||||||
|
use async_trait::async_trait; |
||||||
|
use ethers::types::U256; |
||||||
|
use eyre::Result; |
||||||
|
|
||||||
|
use crate::msg::gas_payment::GasPaymentPolicy; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct GasPaymentPolicyMinimum { |
||||||
|
minimum_payment: U256, |
||||||
|
} |
||||||
|
|
||||||
|
impl GasPaymentPolicyMinimum { |
||||||
|
pub fn new(minimum_payment: U256) -> Self { |
||||||
|
Self { minimum_payment } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl GasPaymentPolicy for GasPaymentPolicyMinimum { |
||||||
|
/// Returns (gas payment requirement met, current payment according to the DB)
|
||||||
|
async fn message_meets_gas_payment_requirement( |
||||||
|
&self, |
||||||
|
_message: &CommittedMessage, |
||||||
|
current_payment: &U256, |
||||||
|
_tx_cost_estimate: &TxCostEstimate, |
||||||
|
) -> Result<bool> { |
||||||
|
Ok(*current_payment >= self.minimum_payment) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[tokio::test] |
||||||
|
async fn test_gas_payment_policy_none() { |
||||||
|
use abacus_core::AbacusMessage; |
||||||
|
|
||||||
|
let min = U256::from(1000u32); |
||||||
|
|
||||||
|
let policy = GasPaymentPolicyMinimum::new(min); |
||||||
|
|
||||||
|
let message = CommittedMessage { |
||||||
|
leaf_index: 100, |
||||||
|
message: AbacusMessage::default(), |
||||||
|
}; |
||||||
|
|
||||||
|
// If the payment is less than the minimum, returns false
|
||||||
|
assert_eq!( |
||||||
|
policy |
||||||
|
.message_meets_gas_payment_requirement( |
||||||
|
&message, |
||||||
|
&U256::from(999u32), |
||||||
|
&TxCostEstimate { |
||||||
|
gas_limit: U256::from(100000u32), |
||||||
|
gas_price: U256::from(100000u32), |
||||||
|
}, |
||||||
|
) |
||||||
|
.await |
||||||
|
.unwrap(), |
||||||
|
false, |
||||||
|
); |
||||||
|
|
||||||
|
// If the payment is at least the minimum, returns false
|
||||||
|
assert_eq!( |
||||||
|
policy |
||||||
|
.message_meets_gas_payment_requirement( |
||||||
|
&message, |
||||||
|
&U256::from(1000u32), |
||||||
|
&TxCostEstimate { |
||||||
|
gas_limit: U256::from(100000u32), |
||||||
|
gas_price: U256::from(100000u32), |
||||||
|
}, |
||||||
|
) |
||||||
|
.await |
||||||
|
.unwrap(), |
||||||
|
true, |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
mod meets_estimated_cost; |
||||||
|
mod minimum; |
||||||
|
mod none; |
||||||
|
|
||||||
|
pub(crate) use meets_estimated_cost::GasPaymentPolicyMeetsEstimatedCost; |
||||||
|
pub(crate) use minimum::GasPaymentPolicyMinimum; |
||||||
|
pub(crate) use none::GasPaymentPolicyNone; |
@ -0,0 +1,56 @@ |
|||||||
|
use abacus_core::{CommittedMessage, TxCostEstimate}; |
||||||
|
use async_trait::async_trait; |
||||||
|
use ethers::types::U256; |
||||||
|
use eyre::Result; |
||||||
|
|
||||||
|
use crate::msg::gas_payment::GasPaymentPolicy; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct GasPaymentPolicyNone {} |
||||||
|
|
||||||
|
impl GasPaymentPolicyNone { |
||||||
|
pub fn new() -> Self { |
||||||
|
Self {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl GasPaymentPolicy for GasPaymentPolicyNone { |
||||||
|
/// Returns (gas payment requirement met, current payment according to the DB)
|
||||||
|
async fn message_meets_gas_payment_requirement( |
||||||
|
&self, |
||||||
|
_message: &CommittedMessage, |
||||||
|
_current_payment: &U256, |
||||||
|
_tx_cost_estimate: &TxCostEstimate, |
||||||
|
) -> Result<bool> { |
||||||
|
Ok(true) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[tokio::test] |
||||||
|
async fn test_gas_payment_policy_none() { |
||||||
|
use abacus_core::AbacusMessage; |
||||||
|
|
||||||
|
let policy = GasPaymentPolicyNone::new(); |
||||||
|
|
||||||
|
let message = CommittedMessage { |
||||||
|
leaf_index: 100, |
||||||
|
message: AbacusMessage::default(), |
||||||
|
}; |
||||||
|
|
||||||
|
// Always returns true
|
||||||
|
assert_eq!( |
||||||
|
policy |
||||||
|
.message_meets_gas_payment_requirement( |
||||||
|
&message, |
||||||
|
&U256::zero(), |
||||||
|
&TxCostEstimate { |
||||||
|
gas_limit: U256::from(100000u32), |
||||||
|
gas_price: U256::from(100000u32), |
||||||
|
}, |
||||||
|
) |
||||||
|
.await |
||||||
|
.unwrap(), |
||||||
|
true, |
||||||
|
); |
||||||
|
} |
@ -1,37 +0,0 @@ |
|||||||
use abacus_core::db::{AbacusDB, DbError}; |
|
||||||
use ethers::types::U256; |
|
||||||
|
|
||||||
use crate::settings::GasPaymentEnforcementPolicy; |
|
||||||
|
|
||||||
#[derive(Debug)] |
|
||||||
pub struct GasPaymentEnforcer { |
|
||||||
policy: GasPaymentEnforcementPolicy, |
|
||||||
db: AbacusDB, |
|
||||||
} |
|
||||||
|
|
||||||
impl GasPaymentEnforcer { |
|
||||||
pub fn new(policy: GasPaymentEnforcementPolicy, db: AbacusDB) -> Self { |
|
||||||
Self { policy, db } |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns (gas payment requirement met, current payment according to the DB)
|
|
||||||
pub fn message_meets_gas_payment_requirement( |
|
||||||
&self, |
|
||||||
msg_leaf_index: u32, |
|
||||||
) -> Result<(bool, U256), DbError> { |
|
||||||
let current_payment = self.get_message_gas_payment(msg_leaf_index)?; |
|
||||||
|
|
||||||
let meets_requirement = match self.policy { |
|
||||||
GasPaymentEnforcementPolicy::None => true, |
|
||||||
GasPaymentEnforcementPolicy::Minimum { |
|
||||||
payment: min_payment, |
|
||||||
} => current_payment >= min_payment, |
|
||||||
}; |
|
||||||
|
|
||||||
Ok((meets_requirement, current_payment)) |
|
||||||
} |
|
||||||
|
|
||||||
fn get_message_gas_payment(&self, msg_leaf_index: u32) -> Result<U256, DbError> { |
|
||||||
self.db.retrieve_gas_payment_for_leaf(msg_leaf_index) |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue