
Originally developed as an internal MEV launchpad, Multiplexer is now open-sourced. It provides a flexible smart contract system for executing complex transaction sequences, featuring a Solidity executor contract and a Rust library for building execution flows.
⚠️ WARNING: This contract has not been audited. Using this contract with real assets could result in permanent loss of funds. Use at your own risk.
# Clone the repository
git clone https://github.com/BitFinding/multiplexer.git
cd multiplexer
# Install dependencies and compile contracts# (This uses build.rs to compile contracts/executor.sol and contracts/proxy.sol)
cargo build
# Run tests (requires a mainnet fork RPC URL)
ETH_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY cargo testThe system consists of:
executor.sol: The core contract that executes sequences of operations based on provided bytecode. It manages memory (txData) and handles callbacks.proxy.sol: A simple immutable proxy contract used to deploy the executor logic, allowing for potential future upgrades (though the current proxy is basic).- Rust Library (
src/): Provides aFlowBuilderutility to easily construct the bytecode sequences for the executor, abstracting away the low-level opcode details.
This example demonstrates how to construct the bytecode for a Morpho flash loan using the Rust FlowBuilder. The goal is to borrow 100 WETH, and the callback data (inner_flow_bytes) will contain the instructions to approve the repayment to Morpho.
use alloy::{
primitives::{address, uint,Address,U256, hex},
sol,
sol_types::SolCall,};use multiplexer_evm::FlowBuilder;// Define necessary constantsconstHUNDRED_ETH:U256 = uint!(100000000000000000000_U256);// 100e18constWETH9:Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");constMORPHO:Address = address!("BBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb");// Simplified Sol definitions for the examplesol!{
interface IERC20{
function approve(address spender, uint256 value) external returns (bool);}
interface IMorpho{
function flashLoan(address token, uint256 assets, bytes calldata data) external;}}/// Generates the bytecode for a Morpho flash loan flow./// The flow borrows 100 WETH and sets up the callback to approve repayment.fngenerate_morpho_flashloan_flow() -> Vec<u8>{// 1. Prepare the inner flow (callback data): Approve WETH repayment.// This flow will be executed by the executor when Morpho calls back into it.// It needs to ensure Morpho can pull the funds back.let approve_calldata = IERC20::approveCall{spender:MORPHO,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().call(WETH9,&approve_calldata,U256::ZERO)// Call WETH9.approve(MORPHO, amount).optimize().build_raw();// Get the raw bytecode for the inner flow.// `build_raw()` produces *only* the sequence of action opcodes.// 2. Prepare the outer flow: Initiate the flash loan.// This is the main flow sent to the executor contract transaction.let flashloan_calldata = IMorpho::flashLoanCall{token:WETH9,// Asset to borrowassets:HUNDRED_ETH,// Amount to borrowdata: inner_flow_bytes.into(),// Pass the repayment flow as callback data}.abi_encode();let main_flow_bytes = FlowBuilder::empty().set_fail()// Revert the entire transaction if any subsequent call fails (including the callback).set_callback(MORPHO)// Set Morpho as the expected callback address. The executor will only// execute the callback data if msg.sender matches this address..call(MORPHO,&flashloan_calldata,U256::ZERO)// Call Morpho.flashLoan(...).optimize()// Apply peephole optimizations.build();// Build the final bytecode sequence.// `build()` prepends the `executeActions()` function selector (0xc94f554d)// to the raw action bytecode generated by `build_raw()`.
main_flow_bytes
}// --- How to use the generated bytecode ---fnmain(){let flow_bytecode = generate_morpho_flashloan_flow();// `flow_bytecode` now contains the sequence:// SETFAIL -> SETCALLBACK(MORPHO) -> CALL(MORPHO, flashLoan(...))// This `flow_bytecode` would be used as the `data` field in an Ethereum transaction// sent to your deployed Executor contract instance.// Important Considerations for Execution:// 1. Funding: The Executor contract must possess enough WETH *after* the flash loan// is granted but *before* the callback completes to successfully execute the// `inner_flow_bytes` (the WETH approval) and allow Morpho to reclaim the funds + fee.// This usually means the Executor needs some initial WETH or the operations *within*// the flash loan (not shown in this basic example) must generate the required WETH.// 2. Gas: Ensure sufficient gas is provided for the main transaction and the callback execution.// 3. Permissions: The transaction sender must be the owner of the Executor contract.println!("Generated Flow Bytecode: 0x{}", hex::encode(&flow_bytecode));}Here's an example sequence that performs a basic contract call using the raw opcodes:
# Assume target_addr = 0x... target contract address
# Assume eth_value = 1 ether (in wei)
# Assume function selector = 0xaabbccdd
# Bytecode Sequence:
0x01 0x0040 # CLEARDATA: Allocate 64 bytes for calldata
0x03 <target_addr> # SETADDR: Set target contract address
0x04 <eth_value> # SETVALUE: Set ETH value to send (1 ether)
0x02 0x0000 0x0004 # SETDATA: Set function selector at offset 0, length 4
aabbccdd # ↳ Function selector bytes
0x0A # SETFAIL: Enable revert on failure
0x06 # CALL: Execute the call
0x00 # EOF: End sequence
- Sequential execution of multiple operations in a single transaction
- Support for flash loans from multiple protocols (Morpho, Aave)
- Low-level operation support (calls, creates, delegate calls)
- Memory management for transaction data
- Fail-safe mechanisms with configurable error handling
The contract supports the following operations, encoded as single-byte opcodes:
| Opcode | Operation | Description | Encoding Format |
|---|---|---|---|
| 0x00 | EOF | End of flow marker | 0x00 |
| 0x01 | CLEARDATA | Clear transaction data buffer | 0x01 + [size: uint16] |
| 0x02 | SETDATA | Set data at specific offset | 0x02 + [offset: uint16] + [size: uint16] + [ bytes] |
| 0x03 | SETADDR | Set target address | 0x03 + [address: bytes20] |
| 0x04 | SETVALUE | Set ETH value for calls | 0x04 + [value: uint256] |
| 0x05 | EXTCODECOPY | Copy external contract code | 0x05 + [addr: bytes20] + [dataOffset: uint16] + [codeOffset: uint16] + [size: uint16] |
| 0x06 | CALL | Perform external call | 0x06 |
| 0x07 | CREATE | Deploy new contract | 0x07 |
| 0x08 | DELEGATECALL | Perform delegate call | 0x08 |
| 0x09 | SETCALLBACK | Set callback address for flash loans | 0x09 + [address: bytes20] |
| 0x0A | SETFAIL | Enable revert on call failure | 0x0A |
| 0x0B | CLEARFAIL | Disable revert on call failure | 0x0B |
The contract maintains a dynamic bytes array (txData) as a working buffer for all operations:
- Memory Layout:
- 0x00-0x20: Length of array (32 bytes)
- 0x20-onwards: Actual data bytes
Operations that interact with this buffer:
- CLEARDATA: Clears and resizes the buffer
- SETDATA: Writes data at specific offsets
- EXTCODECOPY: Copies external contract code into the buffer
- CALL/DELEGATECALL/CREATE: Read from the buffer for execution
The contract implements callbacks for multiple flash loan protocols:
function onMorphoFlashLoan(uint256amount, bytescalldatadata)When Morpho calls this function on the executor, the executor will execute the bytecode passed in data.
function executeOperation(
addressasset,
uint256amount,
uint256premium,
addressinitiator,
bytescalldataparams
)- Owner-only access control
- Callback address validation for flash loans (
SETCALLBACK) - Automatic callback address clearing after use (prevents re-entrancy with old callback data)
- Optional failure handling with
SETFAIL/CLEARFAIL - Memory bounds checking for all operations
The contract is developed in Solidity and includes a comprehensive test suite written in Rust. The tests use Anvil for local blockchain simulation.