Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions crates/simulator/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use {
WrapperConfig,
},
alloy_primitives::{Address, B256, Bytes, U256, b256, keccak256},
alloy_provider::{DynProvider, Provider},
alloy_rpc_types::state::{AccountOverride, StateOverride},
alloy_sol_types::SolCall,
app_data::AppDataHash,
Expand Down Expand Up @@ -341,6 +342,34 @@ pub fn encode_wrapper_data(wrappers: &[WrapperCall]) -> Bytes {
wrapper_data.into()
}

/// Ensures every wrapper address in the chain has code deployed at the
/// simulation block. A `call` to a code-less address returns success with
/// empty output, so the inner settlement (and the `storeBalance` interactions
/// that record trader balances) would silently no-op. The trade verifier
/// expects a fixed-shape response and reads garbage when the helper
/// short-circuits this way.
async fn ensure_wrappers_have_code(
provider: &DynProvider,
wrappers: &[WrapperCall],
block: u64,
) -> Result<(), BuildError> {
let lookups = wrappers.iter().map(|w| async move {
let address = w.address;
let code = provider
.get_code_at(address)
.block_id(block.into())
.await
.map_err(|source| BuildError::WrapperCodeFetch { address, source })?;
if code.is_empty() {
Err(BuildError::WrapperHasNoCode { address })
} else {
Ok(())
}
});
futures::future::try_join_all(lookups).await?;
Ok(())
}
Comment thread
squadgazzz marked this conversation as resolved.

pub(crate) async fn finish_simulation_builder(
mut builder: SimulationBuilder,
) -> Result<EthCallInputs, BuildError> {
Expand Down Expand Up @@ -454,6 +483,7 @@ pub(crate) async fn finish_simulation_builder(
let wrapper = builder.wrapper;
let (to, calldata) = match wrapper {
WrapperConfig::Custom(wrappers) if !wrappers.is_empty() => {
ensure_wrappers_have_code(&builder.simulator.0.provider, &wrappers, block).await?;
encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty")
}
Comment thread
squadgazzz marked this conversation as resolved.
WrapperConfig::Flashloan(loans) => {
Expand Down Expand Up @@ -729,3 +759,72 @@ fn apply_account_override(
Ok(())
}
}

#[cfg(test)]
mod tests {
use {
super::*,
alloy_provider::{ProviderBuilder, mock::Asserter},
};

fn wrapper(address: u8) -> WrapperCall {
WrapperCall {
address: Address::repeat_byte(address),
data: Bytes::new(),
}
}

fn provider_returning_codes(codes: &[Bytes]) -> DynProvider {
let asserter = Asserter::new();
for code in codes {
asserter.push_success(code);
}
ProviderBuilder::new()
.connect_mocked_client(asserter)
.erased()
}

#[tokio::test]
async fn accepts_wrappers_with_code() {
let wrappers = [wrapper(1), wrapper(2)];
let provider =
provider_returning_codes(&[Bytes::from_static(&[0x60]), Bytes::from_static(&[0x60])]);

ensure_wrappers_have_code(&provider, &wrappers, 0)
.await
.expect("all wrappers have code");
}

#[tokio::test]
async fn rejects_inner_wrapper_without_code() {
let wrappers = [wrapper(0xAA), wrapper(0xBB)];
// First wrapper has code, second is an EOA (empty code).
let provider = provider_returning_codes(&[Bytes::from_static(&[0x60]), Bytes::new()]);

let err = ensure_wrappers_have_code(&provider, &wrappers, 0)
.await
.expect_err("inner EOA wrapper must be rejected");

match err {
BuildError::WrapperHasNoCode { address } => {
assert_eq!(address, Address::repeat_byte(0xBB));
}
other => panic!("expected WrapperHasNoCode, got {other:?}"),
}
}

#[tokio::test]
async fn rejects_outer_wrapper_without_code() {
let wrappers = [wrapper(0xCC)];
let provider = provider_returning_codes(&[Bytes::new()]);

let err = ensure_wrappers_have_code(&provider, &wrappers, 0)
.await
.expect_err("outer EOA wrapper must be rejected");

assert!(matches!(
err,
BuildError::WrapperHasNoCode { address } if address == Address::repeat_byte(0xCC)
));
}
}
8 changes: 8 additions & 0 deletions crates/simulator/src/simulation_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,14 @@ pub enum BuildError {
AppDataParse(#[from] serde_json::Error),
#[error("both wrappers and flashloans cannot be encoded in the same settlement")]
FlashloanWrappersIncompatible,
#[error("wrapper {address} has no code on-chain")]
WrapperHasNoCode { address: Address },
#[error("failed to fetch wrapper code for {address}: {source}")]
WrapperCodeFetch {
address: Address,
#[source]
source: alloy_transport::TransportError,
},
}

/// Error returned when two [`AccountOverride`]s set the same field for the same
Expand Down
Loading