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