Skip to content

Commit

Permalink
Add economic viability check in the driver (#2631)
Browse files Browse the repository at this point in the history
# Description
When the driver simulates the solution, it checks whether
`simulated_gas` > `solver_reported_gas`, and if so, replaces the
proposed solution with the empty one.

# Changes
Return a new type of `Revert` error if the simulated gas hits the gas
limit.

## How to test
1. regression tests

## Related Issues

Fixes #2613
  • Loading branch information
m-lord-renkse authored Apr 25, 2024
1 parent 3c5001e commit 22fe18e
Show file tree
Hide file tree
Showing 11 changed files with 41 additions and 31 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cached = { version = "0.49.3", default-features = false }
chrono = { version = "0.4.38", default-features = false }
clap = { version = "4.5.4", features = ["derive", "env"] }
derivative = "2.2.0"
derive_more = "0.99.17"
ethcontract = { version = "0.25.6", default-features = false, features = ["aws-kms"] }
ethcontract-generate = { version = "0.25.6", default-features = false }
ethcontract-mock = { version = "0.25.6", default-features = false }
Expand Down
4 changes: 2 additions & 2 deletions crates/autopilot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ clap = { workspace = true }
contracts = { path = "../contracts" }
database = { path = "../database" }
derivative = { workspace = true }
derive_more = "0.99.17"
derive_more = { workspace = true }
ethcontract = { workspace = true }
ethrpc = { path = "../ethrpc" }
futures = { workspace = true }
Expand All @@ -46,7 +46,7 @@ prometheus = { workspace = true }
prometheus-metric-storage = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true, features = ["gzip", "json"] }
s3 = {path = "../s3" }
s3 = { path = "../s3" }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions crates/driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ axum = { workspace = true }
bigdecimal = { workspace = true }
chrono = { workspace = true, features = ["clock"], default-features = false }
derivative = { workspace = true }
derive_more = { workspace = true }
ethabi = "18.0"
ethereum-types = { workspace = true }
ethrpc = { path = "../ethrpc" }
Expand Down
19 changes: 11 additions & 8 deletions crates/driver/src/domain/competition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use {
blockchain::Ethereum,
notify,
observe,
simulator::{RevertError, SimulatorError},
solver::{self, SolutionMerging, Solver},
Simulator,
},
Expand Down Expand Up @@ -304,14 +305,16 @@ impl Competition {
&self,
settlement: &Settlement,
) -> Result<(), infra::simulator::Error> {
self.simulator
.gas(
settlement
.transaction(settlement::Internalization::Enable)
.clone(),
)
.await
.map(|_| ())
let tx = settlement.transaction(settlement::Internalization::Enable);
let gas_needed_for_tx = self.simulator.gas(tx).await?;
if gas_needed_for_tx > settlement.gas.limit {
return Err(infra::simulator::Error::Revert(RevertError {
err: SimulatorError::GasExceeded(gas_needed_for_tx, settlement.gas.limit),
tx: tx.clone(),
block: self.eth.current_block().borrow().number.into(),
}));
}
Ok(())
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/driver/src/domain/competition/solution/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl Settlement {
input: Default::default(),
access_list: Default::default(),
};
Result::<_, Error>::Ok(simulator.access_list(tx).await?)
Result::<_, Error>::Ok(simulator.access_list(&tx).await?)
}))
.await?;
let partial_access_list = partial_access_lists
Expand Down Expand Up @@ -203,11 +203,11 @@ impl Settlement {

// Simulate the full access list, passing the partial access
// list into the simulation.
let access_list = simulator.access_list(tx.clone()).await?;
let access_list = simulator.access_list(&tx).await?;
let tx = tx.set_access_list(access_list.clone());

// Simulate the settlement using the full access list and get the gas used.
let gas = simulator.gas(tx.clone()).await;
let gas = simulator.gas(&tx).await;

observe::simulated(eth, &tx, &gas);
Ok((access_list, gas?))
Expand Down
3 changes: 2 additions & 1 deletion crates/driver/src/domain/eth/gas.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use {
super::{Ether, U256},
bigdecimal::Zero,
derive_more::Display,
std::{ops, ops::Add},
};

/// Gas amount in gas units.
///
/// The amount of Ether that is paid in transaction fees is proportional to this
/// amount as well as the transaction's [`EffectiveGasPrice`].
#[derive(Debug, Default, Clone, Copy, Ord, Eq, PartialOrd, PartialEq)]
#[derive(Debug, Default, Display, Clone, Copy, Ord, Eq, PartialOrd, PartialEq)]
pub struct Gas(pub U256);

impl From<U256> for Gas {
Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/domain/mempools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ impl Mempools {
TxStatus::Reverted => return Err(Error::Revert(hash.clone())),
TxStatus::Pending => {
// Check if transaction still simulates
if let Err(err) = self.ethereum.estimate_gas(tx.clone()).await {
if let Err(err) = self.ethereum.estimate_gas(tx).await {
if err.is_revert() {
tracing::info!(
?hash,
Expand Down
6 changes: 3 additions & 3 deletions crates/driver/src/infra/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl Ethereum {
}

/// Estimate gas used by a transaction.
pub async fn estimate_gas(&self, tx: eth::Tx) -> Result<eth::Gas, Error> {
pub async fn estimate_gas(&self, tx: &eth::Tx) -> Result<eth::Gas, Error> {
self.web3
.eth()
.estimate_gas(
Expand All @@ -171,8 +171,8 @@ impl Ethereum {
to: Some(tx.to.into()),
gas_price: Some(eth::U256::zero()),
value: Some(tx.value.into()),
data: Some(tx.input.into()),
access_list: Some(tx.access_list.into()),
data: Some(tx.input.clone().into()),
access_list: Some(tx.access_list.clone().into()),
..Default::default()
},
None,
Expand Down
23 changes: 13 additions & 10 deletions crates/driver/src/infra/simulator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ impl Simulator {
/// Simulate the access list needed by a transaction. If the transaction
/// already has an access list, the returned access list will be a
/// superset of the existing one.
pub async fn access_list(&self, tx: eth::Tx) -> Result<eth::AccessList, Error> {
pub async fn access_list(&self, tx: &eth::Tx) -> Result<eth::AccessList, Error> {
if self.disable_access_lists {
return Ok(tx.access_list);
return Ok(tx.access_list.clone());
}
let block = self.eth.current_block().borrow().number.into();
let access_list = match &self.inner {
Inner::Tenderly(tenderly) => {
tenderly
.simulate(tx.clone(), tenderly::GenerateAccessList::Yes)
.simulate(tx, tenderly::GenerateAccessList::Yes)
.await
.map_err(with(tx.clone(), block))?
.access_list
Expand All @@ -105,34 +105,34 @@ impl Simulator {
.await
.map_err(with(tx.clone(), block))?,
};
Ok(tx.access_list.merge(access_list))
Ok(tx.access_list.clone().merge(access_list))
}

/// Simulate the gas needed by a transaction.
pub async fn gas(&self, tx: eth::Tx) -> Result<eth::Gas, Error> {
pub async fn gas(&self, tx: &eth::Tx) -> Result<eth::Gas, Error> {
if let Some(gas) = self.disable_gas {
return Ok(gas);
}
let block = self.eth.current_block().borrow().number.into();
Ok(match &self.inner {
Inner::Tenderly(tenderly) => {
tenderly
.simulate(tx.clone(), tenderly::GenerateAccessList::No)
.simulate(tx, tenderly::GenerateAccessList::No)
.measure("tenderly_simulate_gas")
.await
.map_err(with(tx, block))?
.map_err(with(tx.clone(), block))?
.gas
}
Inner::Ethereum => self
.eth
.estimate_gas(tx.clone())
.estimate_gas(tx)
.await
.map_err(with(tx, block))?,
.map_err(with(tx.clone(), block))?,
Inner::Enso(enso) => enso
.simulate(tx.clone())
.measure("enso_simulate_gas")
.await
.map_err(with(tx, block))?,
.map_err(with(tx.clone(), block))?,
})
}
}
Expand All @@ -152,6 +152,8 @@ pub enum SimulatorError {
Blockchain(#[from] blockchain::Error),
#[error("enso error: {0:?}")]
Enso(#[from] enso::Error),
#[error("the simulated gas {0} exceeded the gas limit {1} provided in the solution")]
GasExceeded(eth::Gas, eth::Gas),
}

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -192,6 +194,7 @@ where
}
SimulatorError::Enso(enso::Error::Http(_)) => None,
SimulatorError::Enso(enso::Error::Revert(_)) => Some(tx),
SimulatorError::GasExceeded(..) => Some(tx),
};
match tx {
Some(tx) => Error::Revert(RevertError { err, tx, block }),
Expand Down
6 changes: 3 additions & 3 deletions crates/driver/src/infra/simulator/tenderly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl Tenderly {

pub(super) async fn simulate(
&self,
tx: eth::Tx,
tx: &eth::Tx,
generate_access_list: GenerateAccessList,
) -> Result<Simulation, Error> {
let res: dto::Response = self
Expand All @@ -73,15 +73,15 @@ impl Tenderly {
network_id: self.chain_id.to_string(),
from: tx.from.into(),
to: tx.to.into(),
input: tx.input.into(),
input: tx.input.clone().into(),
value: tx.value.into(),
save: self.config.save,
save_if_fails: self.config.save_if_fails,
generate_access_list: generate_access_list == GenerateAccessList::Yes,
access_list: if tx.access_list.is_empty() {
None
} else {
Some(tx.access_list.into())
Some(tx.access_list.clone().into())
},
})
.send()
Expand Down

0 comments on commit 22fe18e

Please sign in to comment.