Skip to content

Commit

Permalink
Support simulating using the pending timestamp with enso (#2185)
Browse files Browse the repository at this point in the history
# Description
When we simulate a transaction in the driver we only care whether it
would work on the `pending` block and not on the `latest` block. Of
course we can't magically predict the entire state of the blockchain on
the next block but at least we could catch problems that arise of the
next block number, timestamp and basefee.
This is pretty easy to do with `tenderly` and `web3` but unfortunately
the `enso` simulator does not really support simulating on the `pending`
block.

To get closer to the desired behavior I opened a
[PR](EnsoFinance/temper#27) (not yet merged) on
the enso simulator which allows users to provide the timestamp they want
to simulate on and changed our code to produce the needed timestamp in
the `enso` simulator.

# Changes
- added `block_timestamp` to the `enso` dto
- added `current_block_stream` and `network_block_interval` to `enso`
simulator to get the needed timestamp
- added plumbing to configure `network_block_interval` in a config file
- extended `FetchBlock.sol` to also return the blocks timestamp

## How to test
Did a manual test locally. Created a test order and checked that the
enso simulator can handle the timestamp.
Unfortunately the `enso` team did not build a new docker image so I also
tested that our code doesn't break with the old `temper` version so we
should be able to merge this PR without any issues. Will contact the
team to request a new build from them.

Fixes #2174
  • Loading branch information
MartinquaXD authored Jan 9, 2024
1 parent bc2ebeb commit c816a7b
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 7 deletions.
2 changes: 1 addition & 1 deletion crates/contracts/artifacts/FetchBlock.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"}],"bytecode":"0x6080604052348015600f57600080fd5b506000804311601e5760006027565b6027600143607c565b9050804060008260375760006042565b6040600184607c565b405b6040805160208101869052908101849052606081018290529091506000906080016040516020818303038152906040529050805181602001f35b81810381811115609c57634e487b7160e01b600052601160045260246000fd5b9291505056fe","deployedBytecode":"0x6080604052600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}}
{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"}],"bytecode":"0x6080604052348015600f57600080fd5b506000804311601e5760006027565b60276001436084565b9050804060008260375760006042565b60406001846084565b405b604080516020810186905290810184905260608101829052426080820181905291925060009060a0016040516020818303038152906040529050805181602001f35b8181038181111560a457634e487b7160e01b600052601160045260246000fd5b9291505056fe","deployedBytecode":"0x6080604052600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}}
3 changes: 2 additions & 1 deletion crates/contracts/solidity/FetchBlock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ contract FetchBlock {
bytes32 parentHash = blockNumber > 0
? blockhash(blockNumber - 1)
: bytes32(0);
uint timestamp = block.timestamp;

bytes memory result = abi.encode(blockNumber, blockHash, parentHash);
bytes memory result = abi.encode(blockNumber, blockHash, parentHash, timestamp);
assembly {
return(add(32, result), mload(result))
}
Expand Down
4 changes: 4 additions & 0 deletions crates/driver/example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ graph-api-base-url = "https://api.thegraph.com/subgraphs/name/"
# [[liquidity.uniswap-v3]] # Custom Uniswap V3 configuration
# router = "0xE592427A0AEce92De3Edee1F18E0157C05861564"
# max_pools_to_initialize = 100 # how many of the deepest pools to initialise on startup

# [enso]
# url = "http://localhost:8454"
# network-block-interval = "12s"
1 change: 1 addition & 0 deletions crates/driver/src/infra/config/file/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ pub async fn load(network: &blockchain::Network, path: &Path) -> infra::Config {
}
(None, Some(config)) => Some(simulator::Config::Enso(simulator::enso::Config {
url: config.url,
network_block_interval: config.network_block_interval,
})),
(None, None) => None,
(Some(_), Some(_)) => panic!("Cannot configure both Tenderly and Enso"),
Expand Down
4 changes: 4 additions & 0 deletions crates/driver/src/infra/config/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ struct TenderlyConfig {
struct EnsoConfig {
/// URL at which the trade simulator is hosted
url: Url,
/// How often the network produces a new block. If this is not set the
/// system assumes an unpredictable network like proof-of-work.
#[serde(default, with = "humantime_serde")]
network_block_interval: Option<Duration>,
}

#[derive(Clone, Debug, Default, Deserialize)]
Expand Down
2 changes: 2 additions & 0 deletions crates/driver/src/infra/simulator/enso/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct Request {
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_timestamp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_list: Option<AccessList>,
}

Expand Down
38 changes: 35 additions & 3 deletions crates/driver/src/infra/simulator/enso/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use {crate::domain::eth, reqwest::ClientBuilder, thiserror::Error};
use {
crate::domain::eth,
ethrpc::current_block::CurrentBlockStream,
reqwest::ClientBuilder,
std::time::Duration,
thiserror::Error,
};

mod dto;

Expand All @@ -8,23 +14,48 @@ const GAS_LIMIT: u64 = 30_000_000;
pub(super) struct Enso {
url: reqwest::Url,
chain_id: eth::ChainId,
current_block: CurrentBlockStream,
network_block_interval: Option<Duration>,
}

#[derive(Debug, Clone)]
pub struct Config {
/// The URL of the Transaction Simulator API.
pub url: reqwest::Url,
/// The time between new blocks in the network.
pub network_block_interval: Option<Duration>,
}

impl Enso {
pub(super) fn new(config: Config, chain_id: eth::ChainId) -> Self {
pub(super) fn new(
config: Config,
chain_id: eth::ChainId,
current_block: CurrentBlockStream,
) -> Self {
Self {
url: reqwest::Url::parse(&format!("{}api/v1/simulate", config.url)).unwrap(),
chain_id,
current_block,
network_block_interval: config.network_block_interval,
}
}

pub(super) async fn simulate(&self, tx: eth::Tx) -> Result<eth::Gas, Error> {
let current_block = *self.current_block.borrow();

let (block_number, block_timestamp) = match self.network_block_interval {
None => (None, None), // use default values which result in simulation on `latest`
Some(duration) => {
// We would like to simulate on the `pending` block instead of the `latest`
// block. Unfortunately `enso` does not support that so to get closer to
// the actual behavior of the `pending` block we use the block number of
// the `latest` block but the timestamp of the `pending` block.
let block_number = current_block.number;
let next_timestamp = current_block.timestamp + duration.as_secs();
(Some(block_number), Some(next_timestamp))
}
};

let res: dto::Response = ClientBuilder::new()
.build()
.unwrap()
Expand All @@ -36,7 +67,8 @@ impl Enso {
data: tx.input.into(),
value: tx.value.into(),
gas_limit: GAS_LIMIT,
block_number: None,
block_number,
block_timestamp,
access_list: if tx.access_list.is_empty() {
None
} else {
Expand Down
6 changes: 5 additions & 1 deletion crates/driver/src/infra/simulator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ impl Simulator {
/// Uses Ethereum RPC API to generate access lists.
pub fn enso(config: enso::Config, eth: Ethereum) -> Self {
Self {
inner: Inner::Enso(enso::Enso::new(config, eth.network().chain)),
inner: Inner::Enso(enso::Enso::new(
config,
eth.network().chain,
eth.current_block().clone(),
)),
eth,
disable_access_lists: false,
disable_gas: None,
Expand Down
1 change: 1 addition & 0 deletions crates/driver/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ fn simulator(config: &infra::Config, eth: &Ethereum) -> Simulator {
Some(infra::simulator::Config::Enso(enso)) => Simulator::enso(
simulator::enso::Config {
url: enso.url.to_owned(),
network_block_interval: enso.network_block_interval.to_owned(),
},
eth.to_owned(),
),
Expand Down
2 changes: 2 additions & 0 deletions crates/ethrpc/src/current_block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub struct BlockInfo {
pub number: u64,
pub hash: H256,
pub parent_hash: H256,
pub timestamp: u64,
}

impl TryFrom<Block<H256>> for BlockInfo {
Expand All @@ -61,6 +62,7 @@ impl TryFrom<Block<H256>> for BlockInfo {
number: value.number.context("block missing number")?.as_u64(),
hash: value.hash.context("block missing hash")?,
parent_hash: value.parent_hash,
timestamp: value.timestamp.as_u64(),
})
}
}
Expand Down
5 changes: 4 additions & 1 deletion crates/ethrpc/src/current_block/retriever.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl BlockRetrieving for BlockRetriever {
Ok(BlockInfo {
number: fetch.number.saturating_sub(1),
hash: fetch.parent_hash,
timestamp: fetch.timestamp,
parent_hash: call.hash,
})
} else {
Expand All @@ -93,17 +94,19 @@ impl BlockRetrieving for BlockRetriever {
}

/// Decodes the return data from the `FetchBlock` contract.
fn decode(return_data: [u8; 96]) -> Result<BlockInfo> {
fn decode(return_data: [u8; 128]) -> Result<BlockInfo> {
let number = u64::try_from(U256::from_big_endian(&return_data[0..32]))
.ok()
.context("block number overflows u64")?;
let hash = H256::from_slice(&return_data[32..64]);
let parent_hash = H256::from_slice(&return_data[64..96]);
let timestamp = U256::from_big_endian(&return_data[96..128]).as_u64();

Ok(BlockInfo {
number,
hash,
parent_hash,
timestamp,
})
}

Expand Down

0 comments on commit c816a7b

Please sign in to comment.