multiplexer-evm 0.1.1

A Rust library and Solidity contracts for building and executing complex EVM transaction sequences, including flash loans.
Documentation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title Executor Contract
 * @notice A flexible contract that can execute a series of actions including flash loans
 * @dev This contract supports multiple flash loan protocols (Morpho, Aave, ERC3156) and
 *      allows for complex transaction execution with various low-level operations
 */
contract executor {
    /// @notice The address that can initiate actions (set to tx.origin on deployment via proxy)
    address owner;
    /// @notice Address allowed to trigger callback functions
    address callbackAddress;

    /**
     * @notice Creates a new executor instance
     * @dev Sets the contract owner to the transaction origin
     */
    constructor() payable { 
        owner = tx.origin; // Note: tx.origin used intentionally for specific use case
    }

    receive() external payable {}
    fallback() external payable {}

    /**
     * @notice Supported operation types for the executor
     * @dev Each action corresponds to a specific operation in the execution flow
     */
    enum Action {
        EOF,            // End of flow marker
        CLEARDATA,      // Clear the transaction data buffer
        SETDATA,        // Set data at specific offset
        SETADDR,        // Set target address
        SETVALUE,       // Set ETH value for calls
        EXTCODECOPY,    // Copy external contract code
        CALL,           // Perform external call
        CREATE,         // Deploy new contract
        DELEGATECALL,   // Perform delegate call
        SETCALLBACK,    // Set callback address
        SETFAIL,        // Enable revert on call failure
        CLEARFAIL       // Disable revert on call failure
    }

    /**
     * @dev Internal callback handler for flash loan protocols
     * @param calldata_offset The offset in calldata where execution instructions begin
     * @notice Validates callback sender and executes the provided instructions
     */
    function _onCallback(uint256 calldata_offset) internal {
        require(msg.sender == callbackAddress, "Invalid callback sender");
        callbackAddress = address(0); // Reset callback address for security
        _executeActions(calldata_offset);
    }

    /**
     * @notice Morpho flash loan callback handler
     * @dev Calldata offset calculation simplified to constant 100
     * Original calculation: 4 (function selector) + 32 + calldataload(4 + 32)
     * @param amount The amount of tokens borrowed
     * @param data Additional call parameters
     */
    function onMorphoFlashLoan(uint256 amount, bytes calldata data) external {
        // Simplified from dynamic calculation to fixed offset
        // assembly {
        //     calldata_offset := add(4, add(32, calldataload(add(4, mul(1, 32)))))
        // }
        _onCallback(100); // Fixed offset based on Morpho's calldata layout
    }

    /**
     * @notice Aave flash loan callback handler
     * @dev Calldata offset calculation simplified to constant 196
     * Original calculation: 4 (selector) + 32 + calldataload(4 + 4*32)
     * @param asset The address of the flash-borrowed asset
     * @param amount The amount of the flash-borrowed asset
     * @param premium The fee of the flash-borrowed asset
     * @param initiator The address initiating the flash loan
     * @param params Arbitrary packed params to pass to the receiver as extra information
     * @return true if the flash loan was successful
     */
    function executeOperation(
        address asset,
        uint256 amount, 
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        // Simplified from dynamic calculation to fixed offset
        // assembly {
        //     calldata_offset :=  add(4, add(32, calldataload(add(4, mul(4, 32)))))
        // }
        _onCallback(196); // Fixed offset based on Aave's calldata layout
        return true;
    }


    /**
     * @notice Main entry point for executing a series of actions
     * @dev Payable to allow receiving ETH for operations
     */
    function executeActions() external payable {
        _executeActions(4); // Skip function selector (4 bytes)
    }

    /**
     * @notice Internal function to execute a series of actions
     * @dev Processes a byte stream of actions with their parameters
     * 
     * Memory Management:
     * The contract maintains a single dynamic bytes array (txData) that serves as a
     * working buffer for all operations. This buffer is:
     * - Cleared and resized by CLEARDATA
     * - Written to by SETDATA and EXTCODECOPY
     * - Read from by CALL, DELEGATECALL, and CREATE
     * 
     * Memory Layout:
     * txData (bytes array):
     * - 0x00-0x20: Length of array (32 bytes)
     * - 0x20-onwards: Actual data bytes
     * 
     * All operations that write to txData must respect:
     * - Array bounds
     * - Proper offset calculation
     * - Word alignment for 32-byte operations
     * 
     * @param calldata_offset Starting position in calldata to read actions from
     * @custom:security Uses tx.origin intentionally for specific authorization model
     */
    function _executeActions(uint256 calldata_offset) internal {
        // --- Authorization ---
        require(tx.origin == owner, "Unauthorized");

        // --- Execution Setup ---
        bytes calldata data = msg.data[calldata_offset:];
        uint256 offset = 0;
        address target;        // Target address for calls
        uint256 value;        // ETH value for calls
        bool fail = false;    // Fail flag for call operations
        bytes memory txData;  // Transaction data buffer

        // --- Action Loop ---
        unchecked{
            while (offset < data.length) {
                Action op = Action(uint8(data[offset]));
                offset += 1;

                if (op == Action.EOF) {
                    break;
                }
                else if (op == Action.CLEARDATA) {
                    uint256 size;
                    (size, offset) = _parseUint16(data, offset);
                    txData = new bytes(size);
                } 
                else if (op == Action.SETDATA) {
                    uint256 data_offset;
                    uint256 data_size;
                    (data_offset, offset) = _parseUint16(data, offset);
                    (data_size, offset) = _parseUint16(data, offset);               
                    uint256 i;
                    // First loop: Copy full 32-byte words efficiently using assembly
                    for (i = 0; i < data_size/32; i++) {
                        uint256 value_i;
                        (value_i, offset) = _parseUint256(data, offset);
                        assembly{
                            // Memory layout for txData:
                            // txData   : points to array struct
                            // +0x20    : skips length prefix
                            // +offset  : moves to target position
                            // +i*0x20  : moves to current 32-byte word
                            mstore(add(add(add(txData, 0x20), data_offset), mul(i, 0x20)), value_i)
                        }
                    }
                    // Second loop: Copy remaining bytes one by one
                    for (i = ((data_size/32) * 32); i < data_size; i++) {
                        txData[data_offset + i] = data[offset];
                        offset+=1;
                    }
                } else if (op == Action.SETADDR) {
                    (target, offset) = _parseAddress(data, offset);
                } else if (op == Action.SETVALUE) {
                    (value, offset) =  _parseUint256(data, offset);
                } else if (op == Action.EXTCODECOPY) {
                    // Parameters for extcodecopy:
                    // 1. address: 20-byte address of the contract to query
                    // 2. destOffset: memory position where code will be copied
                    // 3. offset: position in contract code to start copying
                    // 4. size: number of bytes to copy
                    address code_contract;
                    uint256 data_offset;
                    uint256 code_offset;
                    uint256 size;
                    (code_contract, offset) = _parseAddress(data, offset);
                    (data_offset, offset) = _parseUint16(data, offset);
                    (code_offset, offset) = _parseUint16(data, offset);
                    (size, offset) = _parseUint16(data, offset);
                    assembly {
                        // Memory layout for destination:
                        // txData   : array pointer
                        // +0x20    : skip length prefix
                        // +offset  : target position in array
                        extcodecopy(
                            code_contract,                    // source contract
                            add(txData, add(data_offset, 0x20)), // destination in memory
                            code_offset,                      // start position in source
                            size                             // number of bytes
                        )
                    }
                } else if (op == Action.CALL) {
                    // Perform external call with current txData buffer
                    // txData contains the complete calldata including:
                    // - function selector (4 bytes)
                    // - encoded parameters (remaining bytes)
                    bool success;
                    (success, ) = target.call{value: value}(txData);
                    if (fail) {
                        require(success, "CALL_FAILED");
                    }
                    value = 0; // Reset value for safety
                } else if (op == Action.CREATE) {                    
                    assembly {
                        // Memory layout for contract creation:
                        // txData    : points to array struct
                        // mload(txData): gets the length of the initialization code
                        // add(txData, 0x20): points to the actual initialization code
                        //
                        // create(value, offset, size):
                        // - value: amount of ETH to send
                        // - offset: memory position of init code
                        // - size: length of init code
                        target := create(
                            value,                  // ETH value for new contract
                            add(txData, 0x20),     // Skip array length word
                            mload(txData)          // Size of initialization code
                        )
                    }
                    value = 0; // Reset value after use
                } else if (op == Action.DELEGATECALL) {
                    // Perform delegatecall using current txData buffer
                    // Note: delegatecall runs code in the context of THIS contract:
                    // - uses this contract's storage
                    // - uses this contract's ETH balance
                    // - msg.sender remains the original caller
                    bool success;
                    (success, ) = target.delegatecall(txData);
                    if (fail) {
                        require(success, "DELCALL_FAILED");
                    }
                } else if (op == Action.SETCALLBACK) {
                    (callbackAddress, offset) = _parseAddress(data, offset);
                } else if (op == Action.SETFAIL) {
                    fail = true;
                } else if (op == Action.CLEARFAIL) {
                    fail = false;
                }

            }
        }
    }

    /**
     * @notice Parse a function selector from byte array
     * @dev Memory layout for bytes array:
     *      - 0x00-0x20: length of array (32 bytes)
     *      - 0x20+: actual bytes data
     *      The assembly loads 32 bytes starting at data[offset]
     * @param data Source byte array
     * @param offset Starting position in the array
     * @return bytes4 The parsed function selector
     * @return uint256 The new offset after parsing
     */
    function _parseFuncId(bytes memory data, uint256 offset) internal pure returns (bytes4, uint256) {
        bytes4 funcId;
        assembly {
            // data points to the bytes array in memory
            // add(data, 0x20) skips the length field
            // add(..., offset) moves to the desired position
            funcId := mload(add(add(data, offset), 0x20))
        }
        return (funcId, offset + 4);
    }

    /**
     * @notice Parse an Ethereum address from byte array
     * @dev Memory layout handling:
     *      1. data points to the bytes array struct in memory
     *      2. First 32 bytes at data contain the array length
     *      3. Actual bytes start at data + 0x20
     *      4. We load 32 bytes but only want last 20 bytes for address
     * 
     * @param data Source byte array
     * @param offset Starting position in the array
     * @return address The parsed address
     * @return uint256 The new offset after parsing
     */
    function _parseAddress(bytes memory data, uint256 offset) internal pure returns (address, uint256) {
        uint256 addr;
        assembly {
            // Load 32 bytes from position (data + 0x20 + offset)
            // -  pointer to bytes array struct
            // - 0x20: skip array length field
            // - offset: position in actual data
            addr := mload(add(add(data, offset), 0x20))
        }
        // Shift right by 96 bits (12 bytes) to get only the last 20 bytes
        // This aligns the address to the least significant bits
        addr = addr >> 96;
        return (address(uint160(addr)), offset + 20);
    }

    /**
     * @notice Parse a uint256 from byte array
     * @dev Uses assembly for efficient memory operations
     * @param data Source byte array
     * @param offset Starting position in the array
     * @return uint256 The parsed value
     * @return uint256 The new offset after parsing
     */
    function _parseUint256(bytes memory data, uint256 offset) internal pure returns (uint256, uint256) {
        uint256 value;
        assembly {
            value := mload(add(add(data, offset),0x20)) 
        }
        return (value, offset + 32);
    }

    /**
     * @notice Parse a uint16 from byte array
     * @dev Combines two bytes into a uint16
     * @param data Source byte array
     * @param offset Starting position in the array
     * @return uint256 The parsed value
     * @return uint256 The new offset after parsing
     */
    function _parseUint16(bytes memory data, uint256 offset) internal pure returns (uint256, uint256) {
        uint256 value = uint256(uint8(data[offset])) << 8 | uint256(uint8(data[offset + 1]));
        return (value, offset + 2);
    }
}