diff --git a/apps/fortuna/Cargo.lock b/apps/fortuna/Cargo.lock index 9da01ca49..78a359166 100644 --- a/apps/fortuna/Cargo.lock +++ b/apps/fortuna/Cargo.lock @@ -1503,7 +1503,7 @@ dependencies = [ [[package]] name = "fortuna" -version = "7.1.0" +version = "7.2.0" dependencies = [ "anyhow", "axum", diff --git a/apps/fortuna/Cargo.toml b/apps/fortuna/Cargo.toml index eb7cf2ded..959fa1c03 100644 --- a/apps/fortuna/Cargo.toml +++ b/apps/fortuna/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fortuna" -version = "7.1.0" +version = "7.2.0" edition = "2021" [dependencies] diff --git a/apps/fortuna/src/config.rs b/apps/fortuna/src/config.rs index 0687d7846..70698f3fa 100644 --- a/apps/fortuna/src/config.rs +++ b/apps/fortuna/src/config.rs @@ -143,20 +143,23 @@ pub struct EthereumConfig { pub escalation_policy: EscalationPolicyConfig, /// The minimum percentage profit to earn as a function of the callback cost. - /// For example, 20 means a profit of 20% over the cost of the callback. + /// For example, 20 means a profit of 20% over the cost of a callback that uses the full gas limit. /// The fee will be raised if the profit is less than this number. - pub min_profit_pct: u64, + /// The minimum value for this is -100. If set to < 0, it means the keeper may lose money on callbacks that use the full gas limit. + pub min_profit_pct: i64, /// The target percentage profit to earn as a function of the callback cost. - /// For example, 20 means a profit of 20% over the cost of the callback. + /// For example, 20 means a profit of 20% over the cost of a callback that uses the full gas limit. /// The fee will be set to this target whenever it falls outside the min/max bounds. - pub target_profit_pct: u64, + /// The minimum value for this is -100. If set to < 0, it means the keeper may lose money on callbacks that use the full gas limit. + pub target_profit_pct: i64, /// The maximum percentage profit to earn as a function of the callback cost. - /// For example, 100 means a profit of 100% over the cost of the callback. + /// For example, 100 means a profit of 100% over the cost of a callback that uses the full gas limit. /// The fee will be lowered if it is more profitable than specified here. /// Must be larger than min_profit_pct. - pub max_profit_pct: u64, + /// The minimum value for this is -100. If set to < 0, it means the keeper may lose money on callbacks that use the full gas limit. + pub max_profit_pct: i64, /// Minimum wallet balance for the keeper. If the balance falls below this level, the keeper will /// withdraw fees from the contract to top up. This functionality requires the keeper to be the fee @@ -182,15 +185,20 @@ fn default_priority_fee_multiplier_pct() -> u64 { #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct EscalationPolicyConfig { - /// The initial gas multiplier to apply to the gas limit. + // The keeper will perform the callback as long as the tx is within this percentage of the configured gas limit. + // Default value is 110, meaning a 10% tolerance over the configured value. + #[serde(default = "default_gas_limit_tolerance_pct")] + pub gas_limit_tolerance_pct: u64, + + /// The initial gas multiplier to apply to the tx gas estimate #[serde(default = "default_initial_gas_multiplier_pct")] pub initial_gas_multiplier_pct: u64, - /// The gas multiplier to apply to the gas limit during backoff retries. + /// The gas multiplier to apply to the tx gas estimate during backoff retries. /// The gas on each successive retry is multiplied by this value, with the maximum multiplier capped at `gas_multiplier_cap_pct`. #[serde(default = "default_gas_multiplier_pct")] pub gas_multiplier_pct: u64, - /// The maximum gas multiplier to apply to the gas limit during backoff retries. + /// The maximum gas multiplier to apply to the tx gas estimate during backoff retries. #[serde(default = "default_gas_multiplier_cap_pct")] pub gas_multiplier_cap_pct: u64, @@ -203,6 +211,10 @@ pub struct EscalationPolicyConfig { pub fee_multiplier_cap_pct: u64, } +fn default_gas_limit_tolerance_pct() -> u64 { + 110 +} + fn default_initial_gas_multiplier_pct() -> u64 { 125 } @@ -226,6 +238,7 @@ fn default_fee_multiplier_cap_pct() -> u64 { impl Default for EscalationPolicyConfig { fn default() -> Self { Self { + gas_limit_tolerance_pct: default_gas_limit_tolerance_pct(), initial_gas_multiplier_pct: default_initial_gas_multiplier_pct(), gas_multiplier_pct: default_gas_multiplier_pct(), gas_multiplier_cap_pct: default_gas_multiplier_cap_pct(), diff --git a/apps/fortuna/src/keeper.rs b/apps/fortuna/src/keeper.rs index 49a518a8d..a836adea4 100644 --- a/apps/fortuna/src/keeper.rs +++ b/apps/fortuna/src/keeper.rs @@ -338,9 +338,13 @@ pub async fn run_keeper_threads( // In the unlikely event that the keeper fees aren't sufficient, the solution to this is to configure the target // fee percentage to be higher on that specific chain. chain_eth_config.gas_limit, - chain_eth_config.min_profit_pct, - chain_eth_config.target_profit_pct, - chain_eth_config.max_profit_pct, + // NOTE: unwrap() here so we panic early if someone configures these values below -100. + u64::try_from(100 + chain_eth_config.min_profit_pct) + .expect("min_profit_pct must be >= -100"), + u64::try_from(100 + chain_eth_config.target_profit_pct) + .expect("target_profit_pct must be >= -100"), + u64::try_from(100 + chain_eth_config.max_profit_pct) + .expect("max_profit_pct must be >= -100"), chain_eth_config.fee, ) .in_current_span(), @@ -435,7 +439,7 @@ pub async fn process_event_with_backoff( &event, &chain_state, &contract, - gas_limit, + gas_limit.saturating_mul(escalation_policy.gas_limit_tolerance_pct.into()) / 100, gas_multiplier_pct, fee_multiplier_pct, metrics.clone(), @@ -1253,15 +1257,15 @@ pub async fn adjust_fee_if_necessary( .await .map_err(|e| anyhow!("Could not estimate transaction cost. error {:?}", e))?; let target_fee_min = std::cmp::max( - (max_callback_cost * (100 + u128::from(min_profit_pct))) / 100, + (max_callback_cost * u128::from(min_profit_pct)) / 100, min_fee_wei, ); let target_fee = std::cmp::max( - (max_callback_cost * (100 + u128::from(target_profit_pct))) / 100, + (max_callback_cost * u128::from(target_profit_pct)) / 100, min_fee_wei, ); let target_fee_max = std::cmp::max( - (max_callback_cost * (100 + u128::from(max_profit_pct))) / 100, + (max_callback_cost * u128::from(max_profit_pct)) / 100, min_fee_wei, );