diff --git a/crates/contracts/artifacts/FetchBlock.json b/crates/contracts/artifacts/FetchBlock.json index f42c413206..a952327598 100644 --- a/crates/contracts/artifacts/FetchBlock.json +++ b/crates/contracts/artifacts/FetchBlock.json @@ -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":{}}} diff --git a/crates/contracts/solidity/FetchBlock.sol b/crates/contracts/solidity/FetchBlock.sol index d9a478e4df..a38458028a 100644 --- a/crates/contracts/solidity/FetchBlock.sol +++ b/crates/contracts/solidity/FetchBlock.sol @@ -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)) } diff --git a/crates/driver/example.toml b/crates/driver/example.toml index 4db4cc011b..6595f5dd21 100644 --- a/crates/driver/example.toml +++ b/crates/driver/example.toml @@ -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" diff --git a/crates/driver/src/infra/config/file/load.rs b/crates/driver/src/infra/config/file/load.rs index 5d75387842..1a370877cc 100644 --- a/crates/driver/src/infra/config/file/load.rs +++ b/crates/driver/src/infra/config/file/load.rs @@ -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"), diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index 0e50a83308..434c172ea7 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -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, } #[derive(Clone, Debug, Default, Deserialize)] diff --git a/crates/driver/src/infra/simulator/enso/dto.rs b/crates/driver/src/infra/simulator/enso/dto.rs index 6687cb4b2a..065bcf03a4 100644 --- a/crates/driver/src/infra/simulator/enso/dto.rs +++ b/crates/driver/src/infra/simulator/enso/dto.rs @@ -20,6 +20,8 @@ pub struct Request { #[serde(skip_serializing_if = "Option::is_none")] pub block_number: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub block_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub access_list: Option, } diff --git a/crates/driver/src/infra/simulator/enso/mod.rs b/crates/driver/src/infra/simulator/enso/mod.rs index e5076f481e..b9ff7b53c8 100644 --- a/crates/driver/src/infra/simulator/enso/mod.rs +++ b/crates/driver/src/infra/simulator/enso/mod.rs @@ -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; @@ -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, } #[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, } 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 { + 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() @@ -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 { diff --git a/crates/driver/src/infra/simulator/mod.rs b/crates/driver/src/infra/simulator/mod.rs index e91d09a8c4..9a4d345e74 100644 --- a/crates/driver/src/infra/simulator/mod.rs +++ b/crates/driver/src/infra/simulator/mod.rs @@ -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, diff --git a/crates/driver/src/run.rs b/crates/driver/src/run.rs index 2fa0e35a4f..cd407fcf4e 100644 --- a/crates/driver/src/run.rs +++ b/crates/driver/src/run.rs @@ -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(), ), diff --git a/crates/ethrpc/src/current_block/mod.rs b/crates/ethrpc/src/current_block/mod.rs index 854d972f88..622f3e5c08 100644 --- a/crates/ethrpc/src/current_block/mod.rs +++ b/crates/ethrpc/src/current_block/mod.rs @@ -51,6 +51,7 @@ pub struct BlockInfo { pub number: u64, pub hash: H256, pub parent_hash: H256, + pub timestamp: u64, } impl TryFrom> for BlockInfo { @@ -61,6 +62,7 @@ impl TryFrom> 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(), }) } } diff --git a/crates/ethrpc/src/current_block/retriever.rs b/crates/ethrpc/src/current_block/retriever.rs index e17227a9ec..b17851913a 100644 --- a/crates/ethrpc/src/current_block/retriever.rs +++ b/crates/ethrpc/src/current_block/retriever.rs @@ -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 { @@ -93,17 +94,19 @@ impl BlockRetrieving for BlockRetriever { } /// Decodes the return data from the `FetchBlock` contract. -fn decode(return_data: [u8; 96]) -> Result { +fn decode(return_data: [u8; 128]) -> Result { 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, }) }