From c214391ac78113577c8687799864c906155d48ef Mon Sep 17 00:00:00 2001 From: Felipe Manzano Date: Sun, 1 Mar 2026 14:39:38 -0300 Subject: [PATCH] refactor: maintainability cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Collapse opcodes.rs from 215 to 18 lines (remove struct boilerplate) - Refactor FlowBuilder: Action enum with struct variants, inline encoding - build()/build_raw() now take &self instead of &mut self - Peephole optimizer: O(n) retain instead of O(n²) reverse-remove - Replace hardcoded selector with documented EXECUTE_ACTIONS_SELECTOR const - Fix off-by-one assert in call() (< u16::MAX -> <= u16::MAX) - Upgrade alloy dev-dep 1.2.1 -> 1.7, migrate test API accordingly - Rename ONEHUNDRED_ETH -> TEN_ETH (value was 10e18, not 100e18) - Fix typos and remove debug comments in tests - Add test_execute_actions_selector (selector drift guard) - Add test_flow_builder_max_data_length (off-by-one regression guard) - Fix CONTRIBUTING.md placeholder URLs --- CONTRIBUTING.md | 8 +- Cargo.toml | 4 +- README.md | 16 +- src/flow_builder.rs | 375 +++++++++++++++++++++++++++----------------- src/opcodes.rs | 205 +----------------------- src/test.rs | 324 ++++++++++++++++++++++++-------------- 6 files changed, 457 insertions(+), 475 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30dc97a..60f9d09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to Executor Contract +# Contributing to Multiplexer -We love your input! We want to make contributing to Executor Contract as easy and transparent as possible, whether it's: +We love your input! We want to make contributing to Multiplexer as easy and transparent as possible, whether it's: - Reporting a bug - Discussing the current state of the code @@ -24,8 +24,8 @@ Pull requests are the best way to propose changes to the codebase. We actively w ## Any contributions you make will be under the MIT Software License In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. -## Report bugs using Github's [issue tracker](https://github.com/yourusername/executor-contract/issues) -We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/yourusername/executor-contract/issues/new); it's that easy! +## Report bugs using Github's [issue tracker](https://github.com/BitFinding/multiplexer/issues) +We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/BitFinding/multiplexer/issues/new); it's that easy! ## Write bug reports with detail, background, and sample code diff --git a/Cargo.toml b/Cargo.toml index 6e73d35..f3f85c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ readme = "README.md" alloy-primitives = { version = "1.5.2" } [dev-dependencies] -tokio = { version = "1.44", features = ["rt", "macros"] } -alloy = { version = "1.2.1", features = ["full", "node-bindings"] } +tokio = { version = "1", features = ["rt", "macros"] } +alloy = { version = "1.7", features = ["full", "node-bindings"] } [build-dependencies] hex = "0.4.3" diff --git a/README.md b/README.md index 19bc6d8..7008bcd 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,14 @@ This example demonstrates how to construct the bytecode for a Morpho flash loan ```rust use alloy::{ - primitives::{address, uint, Address, Bytes, U256, hex}, + primitives::{address, uint, Address, U256, hex}, sol, - sol_types::{SolCall}, + sol_types::SolCall, }; -use multiplexer::FlowBuilder; +use multiplexer_evm::FlowBuilder; // Define necessary constants -const ONEHUNDRED_ETH: U256 = uint!(100000000000000000000_U256); // 100e18 +const HUNDRED_ETH: U256 = uint!(100000000000000000000_U256); // 100e18 const WETH9: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const MORPHO: Address = address!("BBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"); @@ -74,9 +74,9 @@ fn generate_morpho_flashloan_flow() -> Vec { // It needs to ensure Morpho can pull the funds back. let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, // Approve the exact loan amount. - // Note: A real flash loan requires approving amount + fee. - // The executor must hold sufficient WETH *before* this approval runs. + value: HUNDRED_ETH, // Approve the exact loan amount. + // Note: A real flash loan requires approving amount + fee. + // The executor must hold sufficient WETH *before* this approval runs. }.abi_encode(); let inner_flow_bytes = FlowBuilder::empty() @@ -89,7 +89,7 @@ fn generate_morpho_flashloan_flow() -> Vec { // This is the main flow sent to the executor contract transaction. let flashloan_calldata = IMorpho::flashLoanCall { token: WETH9, // Asset to borrow - assets: ONEHUNDRED_ETH, // Amount to borrow + assets: HUNDRED_ETH, // Amount to borrow data: inner_flow_bytes.into(), // Pass the repayment flow as callback data }.abi_encode(); diff --git a/src/flow_builder.rs b/src/flow_builder.rs index ae9541c..3c9dd6f 100644 --- a/src/flow_builder.rs +++ b/src/flow_builder.rs @@ -1,43 +1,116 @@ use alloy_primitives::{Address, U256}; -use crate::opcodes::{ - Call, ClearData, Create, DelegateCall, ExtCodeCopy, SetAddr, SetData, SetValue, SetCallback, SetFail, ClearFail, -}; +use crate::opcodes::*; -// Enum for all opcode actions +/// Function selector for `executeActions()`. +/// Derived from `keccak256("executeActions()")[..4]`. +/// Verified by `test_execute_actions_selector` in `test.rs`. +const EXECUTE_ACTIONS_SELECTOR: [u8; 4] = [0xc9, 0x4f, 0x55, 0x4d]; + +// --------------------------------------------------------------------------- +// Action enum — single source of truth for opcode encoding +// --------------------------------------------------------------------------- + +/// A single operation in the executor bytecode stream. +/// +/// Each variant maps 1:1 to an opcode constant in [`crate::opcodes`]. enum Action { - ClearData(ClearData), - SetData(SetData), - SetAddr(SetAddr), - SetValue(SetValue), - ExtCodeCopy(ExtCodeCopy), - Call(Call), - Create(Create), - DelegateCall(DelegateCall), - SetFail(SetFail), - ClearFail(ClearFail), - SetCallback(SetCallback) + ClearData { + size: u16, + }, + SetData { + offset: u16, + data: Vec, + }, + SetAddr { + addr: Address, + }, + SetValue { + value: U256, + }, + ExtCodeCopy { + source: Address, + data_offset: u16, + code_offset: u16, + size: u16, + }, + Call, + /// `created_address` is **optimizer-only metadata**: it tells the peephole + /// optimizer which address the CREATE will produce so it can elide a + /// subsequent redundant `SetAddr`. It is *not* encoded into the bytecode. + Create { + created_address: Address, + }, + DelegateCall, + SetCallback { + callback_address: Address, + }, + SetFail, + ClearFail, } impl Action { fn encode(&self) -> Vec { match self { - Action::ClearData(cd) => cd.encode(), - Action::SetData(sd) => sd.encode(), - Action::SetAddr(sa) => sa.encode(), - Action::SetValue(sv) => sv.encode(), - Action::ExtCodeCopy(ecc) => ecc.encode(), - Action::Call(c) => c.encode(), - Action::Create(c) => c.encode(), - Action::DelegateCall(dc) => dc.encode(), - Action::SetFail(sf) => sf.encode(), - Action::ClearFail(cf) => cf.encode(), - Action::SetCallback(scb) => scb.encode(), + Action::ClearData { size } => { + let mut buf = vec![OP_CLEARDATA]; + buf.extend(&size.to_be_bytes()); + buf + } + Action::SetData { offset, data } => { + let data_size = data.len() as u16; + let mut buf = vec![OP_SETDATA]; + buf.extend(&offset.to_be_bytes()); + buf.extend(&data_size.to_be_bytes()); + buf.extend(data); + buf + } + Action::SetAddr { addr } => { + let mut buf = vec![OP_SETADDR]; + buf.extend(addr.as_slice()); + buf + } + Action::SetValue { value } => { + let mut buf = vec![OP_SETVALUE]; + buf.extend(&value.to_be_bytes::<32>()); + buf + } + Action::ExtCodeCopy { + source, + data_offset, + code_offset, + size, + } => { + let mut buf = vec![OP_EXTCODECOPY]; + buf.extend(source.as_slice()); + buf.extend(&data_offset.to_be_bytes()); + buf.extend(&code_offset.to_be_bytes()); + buf.extend(&size.to_be_bytes()); + buf + } + Action::Call => vec![OP_CALL], + Action::Create { .. } => vec![OP_CREATE], + Action::DelegateCall => vec![OP_DELEGATECALL], + Action::SetCallback { callback_address } => { + let mut buf = vec![OP_SETCALLBACK]; + buf.extend(callback_address.as_slice()); + buf + } + Action::SetFail => vec![OP_SETFAIL], + Action::ClearFail => vec![OP_CLEARFAIL], } } } -// FlowBuilder to manage the actions +// --------------------------------------------------------------------------- +// FlowBuilder +// --------------------------------------------------------------------------- + +/// Builder for constructing executor bytecode action sequences. +/// +/// Methods that add actions return `&mut Self` for chaining. +/// Call [`optimize`](Self::optimize) before [`build`](Self::build) to remove +/// redundant operations. #[derive(Default)] pub struct FlowBuilder { actions: Vec, @@ -49,136 +122,81 @@ impl FlowBuilder { Self::default() } - /// A simple optimizer that will remove redundant sets - fn peephole_opt(&mut self) { - let mut ops_to_remove = Vec::new(); - let mut last_value = U256::ZERO; - let mut last_target = Address::ZERO; - let mut last_data: Vec = Vec::new(); - let mut last_fail = false; + // -- Low-level opcode pushers ------------------------------------------ - for (idx, action) in self.actions.iter().enumerate() { - let to_remove = match action { - Action::SetFail(_) => { - if last_fail { - true - } else { - last_fail = true; - false - } - } - Action::ClearFail(_) => { - if last_fail { - last_fail = false; - false - } else { - true - } - } - Action::Call(_) => { - last_value = U256::ZERO; - false - } - Action::Create(Create { created_address }) => { - last_target = *created_address; - last_value = U256::ZERO; - false - } - Action::SetAddr(SetAddr { addr }) => { - let res = last_target == *addr; - last_target = *addr; - res - } - Action::SetValue(SetValue { value }) => { - let res = last_value == *value; - last_value = *value; - res - } - Action::ClearData(ClearData { size }) => { - let res = last_data.len() == *size as usize; - last_data = vec![0; *size as usize]; - res - } - Action::SetData(SetData { offset, data }) => { - let offset_uz = *offset as usize; - let mut new_data = last_data.clone(); - new_data.splice(offset_uz..offset_uz + data.len(), data.to_owned()); - let res = last_data == new_data; - last_data = new_data; - res - } - _ => false, - }; - if to_remove { - ops_to_remove.push(idx); - } - } - - for idx in ops_to_remove.into_iter().rev() { - self.actions.remove(idx); - } + /// Adds a `CLEARDATA` operation to the action list. + pub fn set_cleardata_op(&mut self, size: u16) -> &mut Self { + self.actions.push(Action::ClearData { size }); + self } - /// Adds an `EXTCODECOPY` operation to the action list. - pub fn set_extcodecopy_op(&mut self, source: Address, data_offset: u16, code_offset: u16, size: u16) -> &mut Self { - self.actions.push(Action::ExtCodeCopy(ExtCodeCopy { - source, - data_offset, - code_offset, - size, - })); + /// Adds a `SETDATA` operation to the action list. + pub fn set_data_op(&mut self, offset: u16, data: &[u8]) -> &mut Self { + self.actions.push(Action::SetData { + offset, + data: data.to_owned(), + }); self } /// Adds a `SETADDR` operation to the action list. pub fn set_addr_op(&mut self, addr: Address) -> &mut Self { - self.actions.push(Action::SetAddr(SetAddr { addr })); + self.actions.push(Action::SetAddr { addr }); self } /// Adds a `SETVALUE` operation to the action list. pub fn set_value_op(&mut self, value: U256) -> &mut Self { - self.actions.push(Action::SetValue(SetValue { value })); + self.actions.push(Action::SetValue { value }); self } - /// Adds a `SETDATA` operation to the action list. - pub fn set_data_op(&mut self, offset: u16, data: &[u8]) -> &mut Self { - self.actions.push(Action::SetData(SetData { - offset, - data: data.to_owned(), - })); - self - } - - /// Adds a `CLEARDATA` operation to the action list. - pub fn set_cleardata_op(&mut self, size: u16) -> &mut Self { - self.actions.push(Action::ClearData(ClearData { size })); + /// Adds an `EXTCODECOPY` operation to the action list. + pub fn set_extcodecopy_op( + &mut self, + source: Address, + data_offset: u16, + code_offset: u16, + size: u16, + ) -> &mut Self { + self.actions.push(Action::ExtCodeCopy { + source, + data_offset, + code_offset, + size, + }); self } /// Adds a `CALL` operation to the action list. pub fn call_op(&mut self) -> &mut Self { - self.actions.push(Action::Call(Call::new())); + self.actions.push(Action::Call); self } /// Adds a `CREATE` operation to the action list. + /// + /// `created_address` is the expected address of the deployed contract, + /// used by the peephole optimizer to eliminate redundant `SETADDR` ops. pub fn create_op(&mut self, created_address: Address) -> &mut Self { - self.actions.push(Action::Create(Create { created_address })); + self.actions.push(Action::Create { created_address }); self } /// Adds a `DELEGATECALL` operation to the action list. pub fn delegatecall_op(&mut self) -> &mut Self { - self.actions.push(Action::DelegateCall(DelegateCall::new())); + self.actions.push(Action::DelegateCall); self } - /// Prepares a `CALL` operation with the specified target, data, and value. - pub fn call(&mut self, target: Address, data: &[u8], value: U256) -> &mut Self { - assert!(data.len() < u16::MAX as usize, "datalen exceeds 0xffff"); + // -- High-level helpers ------------------------------------------------ + /// Prepares a `CALL`: sets target, value, data buffer, then executes. + pub fn call(&mut self, target: Address, data: &[u8], value: U256) -> &mut Self { + assert!( + data.len() <= u16::MAX as usize, + "data length exceeds u16::MAX" + ); self.set_addr_op(target) .set_value_op(value) .set_cleardata_op(data.len() as u16) @@ -186,7 +204,7 @@ impl FlowBuilder { .call_op() } - /// Prepares a `DELEGATECALL` operation with the specified target and data. + /// Prepares a `DELEGATECALL`: sets target, data buffer, then executes. pub fn delegatecall(&mut self, target: Address, data: &[u8]) -> &mut Self { self.set_addr_op(target) .set_cleardata_op(data.len() as u16) @@ -194,7 +212,7 @@ impl FlowBuilder { .delegatecall_op() } - /// Prepares a `CREATE` operation with the specified address, data, and value. + /// Prepares a `CREATE`: sets value, data buffer, then deploys. pub fn create(&mut self, created_address: Address, data: &[u8], value: U256) -> &mut Self { self.set_value_op(value) .set_cleardata_op(data.len() as u16) @@ -202,46 +220,117 @@ impl FlowBuilder { .create_op(created_address) } - /// prepare set callback + /// Sets the callback address for flash loan handlers. pub fn set_callback(&mut self, callback_address: Address) -> &mut Self { - self.actions.push(Action::SetCallback(SetCallback::new(callback_address))); + self.actions.push(Action::SetCallback { callback_address }); self } - /// Prepares a `SETFAIL` operation. + /// Marks subsequent calls as must-succeed (revert on failure). pub fn set_fail(&mut self) -> &mut Self { - self.actions.push(Action::SetFail(SetFail::new())); + self.actions.push(Action::SetFail); self } - /// Prepares a `CLEARFAIL` operation. + /// Clears the must-succeed flag. pub fn clear_fail(&mut self) -> &mut Self { - self.actions.push(Action::ClearFail(ClearFail::new())); + self.actions.push(Action::ClearFail); self } - /// Optimizes the sequence of operations. + // -- Optimizer --------------------------------------------------------- + + /// Runs the peephole optimizer to remove redundant operations. pub fn optimize(&mut self) -> &mut Self { self.peephole_opt(); self } - /// Builds the sequence of operations into a byte vector, optionally optimizing it. - pub fn build_raw(&mut self) -> Vec { - let mut res = Vec::new(); - for action in &self.actions { - res.extend(&action.encode()); + /// Single-pass peephole optimizer. Marks redundant actions, then removes + /// them via `retain` in O(n). + fn peephole_opt(&mut self) { + let mut last_value = U256::ZERO; + let mut last_target = Address::ZERO; + let mut last_data: Vec = Vec::new(); + let mut last_fail = false; + + let mut keep = vec![true; self.actions.len()]; + + for (idx, action) in self.actions.iter().enumerate() { + let redundant = match action { + Action::SetFail => { + if last_fail { + true + } else { + last_fail = true; + false + } + } + Action::ClearFail => { + if last_fail { + last_fail = false; + false + } else { + true + } + } + Action::Call => { + last_value = U256::ZERO; + false + } + Action::Create { created_address } => { + last_target = *created_address; + last_value = U256::ZERO; + false + } + Action::SetAddr { addr } => { + let res = last_target == *addr; + last_target = *addr; + res + } + Action::SetValue { value } => { + let res = last_value == *value; + last_value = *value; + res + } + Action::ClearData { size } => { + let res = last_data.len() == *size as usize; + last_data = vec![0; *size as usize]; + res + } + Action::SetData { offset, data } => { + let offset_uz = *offset as usize; + let mut new_data = last_data.clone(); + new_data.splice(offset_uz..offset_uz + data.len(), data.to_owned()); + let res = last_data == new_data; + last_data = new_data; + res + } + _ => false, + }; + if redundant { + keep[idx] = false; + } } - res + + let mut i = 0; + self.actions.retain(|_| { + let k = keep[i]; + i += 1; + k + }); } + // -- Encoding ---------------------------------------------------------- + + /// Encodes the action list into raw bytecode (no function selector). + pub fn build_raw(&self) -> Vec { + self.actions.iter().flat_map(|a| a.encode()).collect() + } - /// Builds the sequence of operations into a byte vector, optionally optimizing it. - pub fn build(&mut self) -> Vec { - // ======= executor.sol:executor ======= - // Function signatures: - // c94f554d: executeActions() - let mut res = vec![0xc9, 0x4f, 0x55, 0x4d]; + /// Encodes the action list into calldata for `executeActions()`. + pub fn build(&self) -> Vec { + let mut res = EXECUTE_ACTIONS_SELECTOR.to_vec(); res.extend(self.build_raw()); res } diff --git a/src/opcodes.rs b/src/opcodes.rs index 2c485b4..4c78921 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -1,6 +1,8 @@ -use alloy_primitives::{Address, U256}; +/// Operation opcodes for the executor bytecode format. +/// +/// Each constant represents a single-byte opcode consumed by the +/// on-chain `executor` contract's `_executeActions` interpreter loop. -// Operation opcodes as constants pub const OP_EOF: u8 = 0x00; pub const OP_CLEARDATA: u8 = 0x01; pub const OP_SETDATA: u8 = 0x02; @@ -13,202 +15,3 @@ pub const OP_DELEGATECALL: u8 = 0x08; pub const OP_SETCALLBACK: u8 = 0x09; pub const OP_SETFAIL: u8 = 0x0a; pub const OP_CLEARFAIL: u8 = 0x0b; - - -// Struct for the CLEARDATA operation -pub struct ClearData { - pub size: u16, -} - -impl ClearData { - pub fn new(size: u16) -> Self { - ClearData { size } - } - - pub fn encode(&self) -> Vec { - let mut encoded = Vec::new(); - encoded.push(OP_CLEARDATA); // Opcode - encoded.extend(&self.size.to_be_bytes()); // Size - encoded - } -} - -// Struct for the SETDATA operation -pub struct SetData { - pub offset: u16, - pub data: Vec, -} - -impl SetData { - pub fn new(offset: u16, data: Vec) -> Self { - SetData { offset, data } - } - - pub fn encode(&self) -> Vec { - let data_size = self.data.len() as u16; - let mut encoded = Vec::new(); - encoded.push(OP_SETDATA); // Opcode - encoded.extend(&self.offset.to_be_bytes()); // Offset - encoded.extend(&data_size.to_be_bytes()); // Data Size - encoded.extend(&self.data); // Data - - encoded - } -} - -// Struct for the SETADDR operation -pub struct SetAddr { - pub addr: Address, // 20-byte address -} - -impl SetAddr { - pub fn new(addr: Address) -> Self { - SetAddr { addr } - } - - pub fn encode(&self) -> Vec { - let mut encoded = Vec::new(); - encoded.push(OP_SETADDR); // Opcode - encoded.extend(&self.addr); // Address - encoded - } -} - -// Struct for the SETVALUE operation -#[derive(Clone, Debug)] -pub struct SetValue { - pub value: U256, -} - -impl SetValue { - pub fn new(value: U256) -> Self { - SetValue { value } - } - - pub fn encode(&self) -> Vec { - let mut encoded = Vec::new(); - encoded.push(OP_SETVALUE); // Opcode - encoded.extend(&self.value.to_be_bytes::<32>()); // Value - encoded - } -} - -// Struct for the EXTCODECOPY operation -pub struct ExtCodeCopy { - pub source: Address, // Address of contract to copy code from - pub data_offset: u16, // Offset in the data to copy the code to - pub code_offset: u16, // Offset in the code to copy from - pub size: u16, // Size of the code to copy -} - -impl ExtCodeCopy { - pub fn new(source: Address, data_offset: u16, code_offset: u16, size: u16) -> Self { - ExtCodeCopy { - source, - data_offset, - code_offset, - size, - } - } - - pub fn encode(&self) -> Vec { - let mut encoded = Vec::new(); - encoded.push(OP_EXTCODECOPY); // Opcode - encoded.extend(&self.source); // Source address - encoded.extend(&self.data_offset.to_be_bytes()); // Offset - encoded.extend(&self.code_offset.to_be_bytes()); // Offset - encoded.extend(&self.size.to_be_bytes()); // Size - encoded - } -} - -// Struct for the CALL operation -#[derive(Default)] -pub struct Call {} - -impl Call { - pub fn new() -> Self { - Call {} - } - - pub fn encode(&self) -> Vec { - vec![OP_CALL] // Opcode - } -} - -// Struct for the CREATE operation -#[derive(Default)] -pub struct Create { - pub created_address: Address, -} - -impl Create { - pub fn new(created_address: Address) -> Self { - Self { created_address } - } - - pub fn encode(&self) -> Vec { - vec![OP_CREATE] // Opcode - } -} - -// Struct for the DELEGATECALL operation -#[derive(Default)] -pub struct DelegateCall {} - -impl DelegateCall { - pub fn new() -> Self { - DelegateCall {} - } - - pub fn encode(&self) -> Vec { - vec![OP_DELEGATECALL] // Opcode - } -} - -// Struct for the SETCALLBACK operation -#[derive(Default)] -pub struct SetCallback { - pub callback_address: Address -} - -impl SetCallback { - pub fn new(callback_address: Address) -> Self { - SetCallback {callback_address} - } - - pub fn encode(&self) -> Vec { - let mut encoded = vec![OP_SETCALLBACK]; // Opcode - encoded.extend(&self.callback_address); // Offset - encoded - } -} - -// Struct for the SETFAIL operation -#[derive(Default)] -pub struct SetFail {} - -impl SetFail { - pub fn new() -> Self { - SetFail {} - } - - pub fn encode(&self) -> Vec { - vec![OP_SETFAIL] // Opcode - } -} - -// Struct for the CLEARFAIL operation -#[derive(Default)] -pub struct ClearFail {} - -impl ClearFail { - pub fn new() -> Self { - ClearFail {} - } - - pub fn encode(&self) -> Vec { - vec![OP_CLEARFAIL] // Opcode - } -} - diff --git a/src/test.rs b/src/test.rs index 46cc6e7..d33478c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,42 +1,25 @@ - use crate::{FlowBuilder, DELEGATE_PROXY_INIT, EXECUTOR_INIT}; use alloy::{ hex, - network::TransactionBuilder, // Added Ethereum + network::{Ethereum, TransactionBuilder}, primitives::{address, bytes, uint, Address, U256}, - // Removed EthereumSigner, node_bindings::Anvil - providers::{ - ext::AnvilApi, - fillers::{BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller}, // Corrected filler paths - layers::AnvilProvider, - Identity, Provider, ProviderBuilder, RootProvider, - }, + providers::{ext::AnvilApi, Provider, ProviderBuilder}, rpc::types::TransactionRequest, sol, sol_types::{SolCall, SolConstructor}, }; -use core::str; - -// Type alias for the complex provider type using LocalWallet as signer -type AnvilTestProvider = FillProvider< - JoinFill< - Identity, - JoinFill>> - >, - AnvilProvider, ->; // Constants const BUDGET: U256 = uint!(1000000000000000000000_U256); // 1000e18 const TWO_ETH: U256 = uint!(2000000000000000000_U256); // 2e18 -const ONEHUNDRED_ETH: U256 = uint!(10000000000000000000_U256); +const TEN_ETH: U256 = uint!(10000000000000000000_U256); // 10e18 const WALLET: Address = Address::repeat_byte(0x41); const BOB: Address = Address::repeat_byte(0x42); const WETH9: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const MORPHO: Address = address!("BBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"); // Test helpers -async fn setup_provider() -> AnvilTestProvider { +async fn setup_provider() -> impl Provider + AnvilApi + Clone { let provider = get_provider(); provider .anvil_set_balance(WALLET, BUDGET + U256::from(10u64.pow(18))) @@ -49,7 +32,7 @@ async fn setup_provider() -> AnvilTestProvider { provider } -async fn deploy_executor(provider: &AnvilTestProvider) -> Address { +async fn deploy_executor(provider: &(impl Provider + AnvilApi)) -> Address { let tx = TransactionRequest::default() .with_from(WALLET) .with_deploy_code(EXECUTOR_INIT) @@ -65,10 +48,13 @@ async fn deploy_executor(provider: &AnvilTestProvider) -> Address { assert!(receipt.status()); receipt.contract_address.unwrap() } -fn get_provider() -> AnvilTestProvider { - ProviderBuilder::new().on_anvil_with_config(|anvil| { + +fn get_provider() -> impl Provider + AnvilApi + Clone { + ProviderBuilder::new().connect_anvil_with_config(|anvil| { anvil - .fork(std::env::var("ETH_RPC_URL").expect("failed to retrieve ETH_RPC_URL url from env")) + .fork( + std::env::var("ETH_RPC_URL").expect("failed to retrieve ETH_RPC_URL url from env"), + ) .fork_block_number(20_000_000) }) } @@ -106,7 +92,30 @@ sol! { sol! { interface IMorpho { function flashLoan(address token, uint256 assets, bytes calldata data) external; - } + } +} + +#[test] +fn test_execute_actions_selector() { + // Verify the hardcoded selector in FlowBuilder matches the keccak256 of the signature + use alloy::primitives::keccak256; + let hash = keccak256("executeActions()"); + let expected_selector = &hash[..4]; + let built = FlowBuilder::empty().build(); + assert_eq!( + &built[..4], + expected_selector, + "selector drift: update EXECUTE_ACTIONS_SELECTOR" + ); +} + +#[test] +fn test_flow_builder_max_data_length() { + // Verify that data of exactly u16::MAX bytes is accepted (off-by-one regression guard) + let data = vec![0xABu8; u16::MAX as usize]; + let addr = Address::repeat_byte(0x01); + // Should not panic + let _calldata = FlowBuilder::empty().call(addr, &data, U256::ZERO).build(); } #[test] @@ -166,7 +175,7 @@ fn test_flow_builder_combined_operations() { #[tokio::test] async fn test_bob_cannot_interact() { // A random account can not interact with multiplexer - let provider = ProviderBuilder::new().on_anvil(); + let provider = ProviderBuilder::new().connect_anvil(); provider .anvil_set_balance(WALLET, BUDGET + U256::from(10u64.pow(18))) .await @@ -216,7 +225,7 @@ async fn test_bob_cannot_interact() { #[tokio::test] async fn test_wallet_can_interact() { - let provider = ProviderBuilder::new().on_anvil(); + let provider = ProviderBuilder::new().connect_anvil(); provider .anvil_set_balance(WALLET, BUDGET + U256::from(10u64.pow(18))) .await @@ -268,7 +277,7 @@ async fn test_wallet_can_interact() { async fn test_weth_deposit_through_executor() { let provider = setup_provider().await; let executor = deploy_executor(&provider).await; - + // Initial balance check let executor_balance = provider.get_balance(executor).await.unwrap(); let weth9_contract = IERC20::new(WETH9, provider.clone()); @@ -277,7 +286,10 @@ async fn test_weth_deposit_through_executor() { assert_eq!(executor_weth_balance, U256::ZERO); // Deposit ETH to get WETH - let fb = FlowBuilder::empty().call(WETH9, &bytes!(""), TWO_ETH).optimize().build(); + let fb = FlowBuilder::empty() + .call(WETH9, &bytes!(""), TWO_ETH) + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) .with_to(executor) @@ -286,7 +298,11 @@ async fn test_weth_deposit_through_executor() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); // Verify balances after deposit @@ -300,9 +316,12 @@ async fn test_weth_deposit_through_executor() { async fn test_weth_withdraw_through_executor() { let provider = setup_provider().await; let executor = deploy_executor(&provider).await; - + // First deposit WETH - let fb = FlowBuilder::empty().call(WETH9, &bytes!(""), TWO_ETH).optimize().build(); + let fb = FlowBuilder::empty() + .call(WETH9, &bytes!(""), TWO_ETH) + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) .with_to(executor) @@ -310,10 +329,13 @@ async fn test_weth_withdraw_through_executor() { .with_input(fb); let _tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - + // Then withdraw it back to ETH let withdraw_calldata = IWETH::withdrawCall { amount: TWO_ETH }.abi_encode(); - let fb = FlowBuilder::empty().call(WETH9, &withdraw_calldata, U256::ZERO).optimize().build(); + let fb = FlowBuilder::empty() + .call(WETH9, &withdraw_calldata, U256::ZERO) + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) .with_to(executor) @@ -321,7 +343,11 @@ async fn test_weth_withdraw_through_executor() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); // Verify final balances @@ -338,7 +364,7 @@ async fn test_wallet_can_proxy_create_small() { // reality check let weth_balance = provider.get_balance(WETH9).await.unwrap(); - assert_eq!(format!("{}", weth_balance), "2933633723194923479377016"); + assert_eq!(format!("{}", weth_balance), "2933633723194923479377016"); // sanity check fork state // test WALLETs // 0x4141414141..4141414141 with 1001 eth @@ -349,7 +375,7 @@ async fn test_wallet_can_proxy_create_small() { .unwrap(); let wallet_balance = provider.get_balance(WALLET).await.unwrap(); - assert_eq!(wallet_balance, BUDGET + U256::from(1e18 as u64)); // executor shoud shave sent the value to WETH9 + assert_eq!(wallet_balance, BUDGET + U256::from(1e18 as u64)); provider .anvil_set_balance(BOB, BUDGET + U256::from(1e18 as u64)) @@ -390,10 +416,12 @@ async fn test_wallet_can_proxy_create_small() { executor.create(1), &FlowBuilder::empty() .call(WETH9, &vec![], TWO_ETH) - .optimize().build(), + .optimize() + .build(), TWO_ETH, ) - .optimize().build(); + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) @@ -414,7 +442,7 @@ async fn test_wallet_can_proxy_create_small() { assert!(receipt.status()); let executor_balance = provider.get_balance(executor).await.unwrap(); - assert_eq!(executor_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(executor_balance, U256::ZERO); // ETH was forwarded to proxy assert_eq!( address!("c84f9705070281e8c800c57d92dbab053a80a2d0"), executor.create(1) @@ -424,18 +452,17 @@ async fn test_wallet_can_proxy_create_small() { // 0 eth // 0 weth let executor_balance = provider.get_balance(executor).await.unwrap(); - assert_eq!(executor_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(executor_balance, U256::ZERO); let weth9_contract = IERC20::new(WETH9, provider.clone()); let executor_weth_balance = weth9_contract.balanceOf(executor).call().await.unwrap(); - assert_eq!(executor_weth_balance, U256::ZERO); // executor should have 2 eth worth of weth + assert_eq!(executor_weth_balance, U256::ZERO); - // Proxy created via executor that points to the executor ?? ?AHHH - // 0 eth - // 2 weth + // Proxy has the WETH (created via executor, delegating to executor) + // 0 ETH, 2 WETH let proxy_balance = provider.get_balance(executor.create(1)).await.unwrap(); - assert_eq!(proxy_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(proxy_balance, U256::ZERO); let weth9_contract = IERC20::new(WETH9, provider.clone()); let proxy_weth_balance = weth9_contract @@ -451,13 +478,17 @@ async fn test_wallet_can_proxy_create_small() { let withdraw_calldata = IWETH::withdrawCall { amount: TWO_ETH }.abi_encode(); let multiplexed_withdraw_calldata = FlowBuilder::empty() .call(WETH9, &withdraw_calldata, U256::ZERO) - .optimize().build(); // multiplexed withdraw from weth + .optimize() + .build(); // multiplexed withdraw from weth - let fb = FlowBuilder::empty().call( - executor.create(1), - &multiplexed_withdraw_calldata, - U256::ZERO, - ).optimize().build(); // this should send 2 eth to weth and assign the same weth value to the executor + let fb = FlowBuilder::empty() + .call( + executor.create(1), + &multiplexed_withdraw_calldata, + U256::ZERO, + ) + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) @@ -481,18 +512,17 @@ async fn test_wallet_can_proxy_create_small() { // 0 eth // 0 weth let executor_balance = provider.get_balance(executor).await.unwrap(); - assert_eq!(executor_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(executor_balance, U256::ZERO); let weth9_contract = IERC20::new(WETH9, provider.clone()); let executor_weth_balance = weth9_contract.balanceOf(executor).call().await.unwrap(); assert_eq!(executor_weth_balance, U256::ZERO); - // Proxy created via executor that points to the executor ?? ?AHHH - // 2 eth - // 0 weth + // Proxy now holds ETH (withdrew WETH back to ETH) + // 2 ETH, 0 WETH let proxy_balance = provider.get_balance(executor.create(1)).await.unwrap(); - assert_eq!(proxy_balance, TWO_ETH); // executor shoud shave sent the value to WETH9 + assert_eq!(proxy_balance, TWO_ETH); let weth9_contract = IERC20::new(WETH9, provider.clone()); let proxy_weth_balance = weth9_contract @@ -500,7 +530,7 @@ async fn test_wallet_can_proxy_create_small() { .call() .await .unwrap(); - assert_eq!(proxy_weth_balance, U256::ZERO); + assert_eq!(proxy_weth_balance, U256::ZERO); // bob -> executor -> ?? :fail: // bob -> proxy :fail: @@ -513,7 +543,7 @@ async fn test_wallet_can_proxy_create_ultimate() { // reality check let weth9_contract = IERC20::new(WETH9, provider.clone()); let weth_balance = provider.get_balance(WETH9).await.unwrap(); - assert_eq!(format!("{}", weth_balance), "2933633723194923479377016"); + assert_eq!(format!("{}", weth_balance), "2933633723194923479377016"); // sanity check fork state // test WALLETs // 0x4141414141..4141414141 with 1001 eth @@ -578,7 +608,10 @@ async fn test_wallet_can_proxy_create_ultimate() { // Deposit weth in the proxy account // Use the deployed Proxy(Executor) contract (WALLET is the owner) to deposit weth let deposit_calldata = []; - let fb = FlowBuilder::empty().call(WETH9, &deposit_calldata, TWO_ETH).optimize().build(); + let fb = FlowBuilder::empty() + .call(WETH9, &deposit_calldata, TWO_ETH) + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) @@ -600,27 +633,30 @@ async fn test_wallet_can_proxy_create_ultimate() { // 0 eth // 0 weth let executor_balance = provider.get_balance(executor).await.unwrap(); - assert_eq!(executor_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(executor_balance, U256::ZERO); let executor_weth_balance = weth9_contract.balanceOf(executor).call().await.unwrap(); - assert_eq!(executor_weth_balance, U256::ZERO); // executor should have 2 eth worth of weth + assert_eq!(executor_weth_balance, U256::ZERO); // Proxy(Executor) account has 2 weth // 0 eth // 2 weth let proxy_executor_balance = provider.get_balance(proxy_executor).await.unwrap(); - assert_eq!(proxy_executor_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(proxy_executor_balance, U256::ZERO); let proxy_executor_weth_balance = weth9_contract .balanceOf(proxy_executor) .call() .await .unwrap(); - assert_eq!(proxy_executor_weth_balance, TWO_ETH); // executor should have 2 eth worth of weth + assert_eq!(proxy_executor_weth_balance, TWO_ETH); //////////////////////////////////////////////////////////// // Whithdraw weth from the proxy account // Use the deployed Proxy(Executor) contract (WALLET is the owner) to deposit weth let withdraw_calldata = IWETH::withdrawCall { amount: TWO_ETH }.abi_encode(); - let fb = FlowBuilder::empty().call(WETH9, &withdraw_calldata, U256::ZERO).optimize().build(); + let fb = FlowBuilder::empty() + .call(WETH9, &withdraw_calldata, U256::ZERO) + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) @@ -642,9 +678,9 @@ async fn test_wallet_can_proxy_create_ultimate() { // 0 eth // 0 weth let executor_balance = provider.get_balance(executor).await.unwrap(); - assert_eq!(executor_balance, U256::ZERO); // executor shoud shave sent the value to WETH9 + assert_eq!(executor_balance, U256::ZERO); let executor_weth_balance = weth9_contract.balanceOf(executor).call().await.unwrap(); - assert_eq!(executor_weth_balance, U256::ZERO); // executor should have 2 eth worth of weth + assert_eq!(executor_weth_balance, U256::ZERO); // Proxy(Executor) account has 2 weth // 2 eth @@ -730,7 +766,8 @@ async fn test_extcodecopy() { .set_cleardata_op(flipper_init.len() as u16) .set_data_op(0, &flipper_init) .create_op(flipper1) - .optimize().build(); + .optimize() + .build(); // create normal flipper account. Using data ops let tx = TransactionRequest::default() @@ -770,7 +807,8 @@ async fn test_extcodecopy() { created_flipper_runtime.len() as u16, ) .create_op(flipper2) - .optimize().build(); + .optimize() + .build(); let tx = TransactionRequest::default() .with_from(WALLET) @@ -795,7 +833,7 @@ async fn test_extcodecopy() { assert_eq!(created_flipper2_runtime, created_flipper_runtime); } -// This test test a simple flashloan with morpho +// This test test a simple flashloan with morpho #[tokio::test] async fn test_flashloan_success_with_callback() { let provider = setup_provider().await; @@ -803,8 +841,9 @@ async fn test_flashloan_success_with_callback() { let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, - }.abi_encode(); + value: TEN_ETH, + } + .abi_encode(); let fb = FlowBuilder::empty() .call(WETH9, &approve_calldata, U256::ZERO) @@ -813,9 +852,10 @@ async fn test_flashloan_success_with_callback() { let flashloan_calldata = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb.into(), - }.abi_encode(); + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -832,7 +872,11 @@ async fn test_flashloan_success_with_callback() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); } @@ -843,8 +887,9 @@ async fn test_flashloan_fails_without_callback() { let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, - }.abi_encode(); + value: TEN_ETH, + } + .abi_encode(); let fb = FlowBuilder::empty() .call(WETH9, &approve_calldata, U256::ZERO) @@ -853,9 +898,10 @@ async fn test_flashloan_fails_without_callback() { let flashloan_calldata = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb.into(), - }.abi_encode(); + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -871,7 +917,11 @@ async fn test_flashloan_fails_without_callback() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(!receipt.status()); } @@ -882,8 +932,9 @@ async fn test_multiple_flashloans_with_callback_reset() { let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, - }.abi_encode(); + value: TEN_ETH, + } + .abi_encode(); let fb = FlowBuilder::empty() .call(WETH9, &approve_calldata, U256::ZERO) @@ -892,9 +943,10 @@ async fn test_multiple_flashloans_with_callback_reset() { let flashloan_calldata = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb.into(), - }.abi_encode(); + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -913,7 +965,11 @@ async fn test_multiple_flashloans_with_callback_reset() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); } @@ -924,8 +980,9 @@ async fn test_multiple_flashloans_fails_without_callback_reset() { let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, - }.abi_encode(); + value: TEN_ETH, + } + .abi_encode(); let fb = FlowBuilder::empty() .call(WETH9, &approve_calldata, U256::ZERO) @@ -934,9 +991,10 @@ async fn test_multiple_flashloans_fails_without_callback_reset() { let flashloan_calldata = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb.into(), - }.abi_encode(); + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -954,7 +1012,11 @@ async fn test_multiple_flashloans_fails_without_callback_reset() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(!receipt.status()); } @@ -965,8 +1027,9 @@ async fn test_nested_flashloan_success() { let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, - }.abi_encode(); + value: TEN_ETH, + } + .abi_encode(); let fb_approve = FlowBuilder::empty() .call(WETH9, &approve_calldata, U256::ZERO) @@ -975,9 +1038,10 @@ async fn test_nested_flashloan_success() { let flashloan_calldata_inner = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb_approve.into(), - }.abi_encode(); + } + .abi_encode(); let fb_inner = FlowBuilder::empty() .set_fail() @@ -989,9 +1053,10 @@ async fn test_nested_flashloan_success() { let flashloan_calldata_outer = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb_inner.into(), - }.abi_encode(); + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -1008,7 +1073,11 @@ async fn test_nested_flashloan_success() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); } @@ -1019,8 +1088,9 @@ async fn test_nested_flashloan_fails_without_callback() { let approve_calldata = IERC20::approveCall { spender: MORPHO, - value: ONEHUNDRED_ETH, - }.abi_encode(); + value: TEN_ETH, + } + .abi_encode(); let fb_approve = FlowBuilder::empty() .call(WETH9, &approve_calldata, U256::ZERO) @@ -1029,9 +1099,10 @@ async fn test_nested_flashloan_fails_without_callback() { let flashloan_calldata_inner = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb_approve.into(), - }.abi_encode(); + } + .abi_encode(); let fb_inner = FlowBuilder::empty() .set_fail() @@ -1042,9 +1113,10 @@ async fn test_nested_flashloan_fails_without_callback() { let flashloan_calldata_outer = IMorpho::flashLoanCall { token: WETH9, - assets: ONEHUNDRED_ETH, + assets: TEN_ETH, data: fb_inner.into(), - }.abi_encode(); + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -1061,7 +1133,11 @@ async fn test_nested_flashloan_fails_without_callback() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(!receipt.status()); } @@ -1077,13 +1153,13 @@ async fn test_flashloan_aave3_success_with_callback() { // Aave V3 Pool contract on mainnet const AAVE3_POOL: Address = address!("87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"); // Flash loan premium rate (0.05%) - const PREMIUM_FACTOR: U256 = uint!(500000000000000_U256); - + const PREMIUM_FACTOR: U256 = uint!(500000000000000_U256); // 0.05% + let provider = setup_provider().await; let executor = deploy_executor(&provider).await; // Calculate premium for 100 ETH flash loan - let premium = ONEHUNDRED_ETH * PREMIUM_FACTOR / uint!(1000000000000000000_U256); + let premium = TEN_ETH * PREMIUM_FACTOR / uint!(1000000000000000000_U256); // First send premium amount to executor so it can repay the flash loan let tx = TransactionRequest::default() @@ -1093,7 +1169,11 @@ async fn test_flashloan_aave3_success_with_callback() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); sol! { @@ -1113,10 +1193,15 @@ async fn test_flashloan_aave3_success_with_callback() { // First deposit ETH to get WETH for the premium .call(WETH9, &[], premium) // Then transfer full amount back to Aave - .call(WETH9, &IERC20::approveCall { - spender: AAVE3_POOL, - value: ONEHUNDRED_ETH + premium, - }.abi_encode(), U256::ZERO) + .call( + WETH9, + &IERC20::approveCall { + spender: AAVE3_POOL, + value: TEN_ETH + premium, + } + .abi_encode(), + U256::ZERO, + ) .optimize() .build_raw(); @@ -1124,10 +1209,11 @@ async fn test_flashloan_aave3_success_with_callback() { let flashloan_calldata = IAavePool::flashLoanSimpleCall { receiverAddress: executor, asset: WETH9, - amount: ONEHUNDRED_ETH, + amount: TEN_ETH, params: repay_fb.into(), - referralCode: 0 - }.abi_encode(); + referralCode: 0, + } + .abi_encode(); let fb = FlowBuilder::empty() .set_fail() @@ -1144,6 +1230,10 @@ async fn test_flashloan_aave3_success_with_callback() { let tx_hash = provider.eth_send_unsigned_transaction(tx).await.unwrap(); provider.evm_mine(None).await.unwrap(); - let receipt = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let receipt = provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert!(receipt.status()); }