SwapRouter

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/SwapRouter.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.27;

import "./IVenue.sol";
import "./Utils.sol";
import "./Fees.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@solmate/tokens/ERC20.sol";
import "@solmate/auth/Owned.sol";

/// @title SwapRouter
/// @notice Orchestrates token swaps across multiple venues with fee handling and gas tracking
contract SwapRouter is Owned, Fees {
    /// @notice Parameters for executing a swap operation
    /// @dev Used to pass swap configuration to execute function
    struct SwapParams {
        /// @notice Array of venue contracts to route the swap through
        IVenue[] venues;
        /// @notice 2D array of token pairs representing the swap path for each venue
        Pair[][] paths;
        /// @notice Minimum acceptable output amount (slippage protection)
        uint256 minOutput;
        /// @notice Recipient address for the output tokens
        address to;
        /// @notice Referral address for fee distribution
        address referral;
    }

    /// @notice Result data from a swap execution
    struct Result {
        /// @notice Amount of output tokens received
        uint256 amountOut;
        /// @notice Gas consumed during the swap
        uint256 gas;
    }

    /// @notice Emitted when a swap is successfully executed
    /// @param tokenIn Address of the input token
    /// @param tokenOut Address of the output token
    /// @param amountIn Amount of input tokens swapped
    /// @param amountOut Amount of output tokens received
    /// @param from Address that initiated the swap
    /// @param to Address that received the output tokens
    event Swap(
        address indexed tokenIn,
        address indexed tokenOut,
        uint256 amountIn,
        uint256 amountOut,
        address indexed from,
        address to
    );

    /// @notice Initializes the SwapRouter with an owner
    /// @param _owner Address to be set as the contract owner
    constructor(address _owner) Owned(_owner) {}

    /// @notice Executes a single token swap operation across one or more venues
    /// @dev Handles fee deduction on either input or output tokens and tracks gas consumption
    /// @param params Swap parameters including venues, paths, minimum output, recipient, and referral
    /// @return result Struct containing output amount and gas consumed
    function execute(SwapParams memory params) public payable returns (Result memory result) {
        // Unpack parameters for easier access
        IVenue[] memory venues = params.venues;
        Pair[][] memory paths = params.paths;
        address to = params.to;
        uint256 minOutput = params.minOutput;
        address referral = params.referral;

        // Record initial gas for gas consumption calculation
        uint256 gas0 = gasleft();

        // Extract input and output tokens from the swap path
        address tokenIn = paths[0][0].tokenIn;
        address tokenOut = paths[paths.length - 1][paths[paths.length - 1].length - 1].tokenOut;
        uint256 amountIn = paths[0][0].amountIn;

        // Determine fee location: check if input token has priority fee status
        bool inFee = hasFeePriority(tokenIn);
        bool outFee = inFee ? false : true;

        // Route tokens: if input has fee, receiver is this contract; otherwise send directly to first venue
        address inReceiver = inFee ? address(this) : address(venues[0]);
        // Route output: if output has fee, receiver is this contract; otherwise send directly to recipient
        address outReceiver = outFee ? address(this) : to;

        // Transfer input tokens from sender to appropriate receiver
        UtilsLib.transferFrom(tokenIn, msg.sender, inReceiver, amountIn);

        // Handle input token fee if applicable
        if (inFee) {
            // Deduct router and referral fees from input amount
            paths[0][0].amountIn = _takeFee(tokenIn, amountIn, referral);
            // Withdraw net amount to the first venue
            UtilsLib.withdraw(tokenIn, address(venues[0]), paths[0][0].amountIn);
        }

        // Execute the swap through venues and get output amount
        result.amountOut = IVenue(payable(venues[0])).executeBatch(venues, paths, outReceiver, minOutput);

        // Handle output token fee if applicable
        if (outFee) {
            // Deduct router and referral fees from output amount
            result.amountOut = _takeFee(tokenOut, result.amountOut, referral);
            // Withdraw net amount to the recipient
            UtilsLib.withdraw(tokenOut, to, result.amountOut);
        }

        // Emit swap event with transaction details
        emit Swap(tokenIn, tokenOut, amountIn, result.amountOut, msg.sender, to);

        // Calculate and store gas consumption
        result.gas = gas0 - gasleft();
    }

    /// @notice Executes multiple swap operations in a single transaction
    /// @param batch Array of swap parameters, each representing an independent swap
    /// @return returnData Array of Results, one for each swap in the batch
    function executeBatch(SwapParams[] memory batch) public payable returns (Result[] memory returnData) {
        uint256 length = batch.length;
        returnData = new Result[](length);

        // Execute each swap sequentially and collect results
        for (uint256 i = 0; i < length; i++) {
            returnData[i] = execute(batch[i]);
        }
    }

    /// @notice Sets priority fee status for specified tokens
    /// @dev Only callable by contract owner. Priority tokens pay fees on input amount instead of output
    /// @param tokens Array of token addresses to update
    /// @param enable True to add to priority list, false to remove
    function setPriorityTokens(address[] calldata tokens, bool enable) external onlyOwner {
        _setPriorityTokens(tokens, enable);
    }

    /// @notice Updates the router fee percentage
    /// @dev Only callable by contract owner. Fee is applied to swaps (either input or output)
    /// @param fee New router fee in basis points (e.g., 100 = 1%)
    function setRouterFee(uint32 fee) external onlyOwner {
        _setRouterFee(fee);
    }

    /// @notice Updates the referral fee percentage
    /// @dev Only callable by contract owner. Fee is applied to swaps when referral address is provided
    /// @param fee New referral fee in basis points (e.g., 100 = 1%)
    function setReferralFee(uint32 fee) external onlyOwner {
        _setReferralFee(fee);
    }

    /// @notice Withdraws accumulated fees for a single token
    /// @dev Can be called by owner to withdraw fees held by contract, or by any address to withdraw their own fees
    /// @param token Address of the token to withdraw fees from
    /// @param amount Amount of fees to withdraw
    /// @param to Recipient address for the withdrawn fees
    function withdrawFee(address token, uint256 amount, address to) external {
        // If caller is owner, withdraw from contract; otherwise withdraw from caller's balance
        address from = msg.sender == owner ? address(this) : msg.sender;
        _withdrawFee(token, from, to, amount);
    }

    /// @notice Withdraws accumulated fees for multiple tokens
    /// @dev Can be called by owner to withdraw fees held by contract, or by any address to withdraw their own fees
    /// @param tokens Array of token addresses to withdraw fees from
    /// @param amounts Array of amounts to withdraw for each token (must match tokens array length)
    /// @param to Recipient address for all withdrawn fees
    function withdrawFees(address[] calldata tokens, uint256[] calldata amounts, address to) external {
        require(tokens.length > 0, "EMPTY_LIST");
        require(tokens.length == amounts.length, "BAD_LENGTH");

        // If caller is owner, withdraw from contract; otherwise withdraw from caller's balance
        address from = msg.sender == owner ? address(this) : msg.sender;

        // Withdraw fees for each token
        for (uint256 i = 0; i < tokens.length; i++) {
            _withdrawFee(tokens[i], from, to, amounts[i]);
        }
    }

    /// @notice Allows the contract to receive native currency (ETH)
    /// @dev Required for receiving ETH payments and for operations that may send ETH to this contract
    receive() external payable {}
}
"
    },
    "src/IVenue.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.27;

import "./Utils.sol";
import "./VenueFactory.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@solmate/auth/Owned.sol";

struct Pair {
    address tokenIn;
    address tokenOut;
    uint256 amountIn;
    uint256 amountOut;
    uint256 gas;
    bytes data;
}

interface IFlashLoanRecipient {
    function receiveFlashLoan(
        IERC20[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external;
}

abstract contract IVenue is IFlashLoanRecipient, Owned {
    VenueFactory public factory;

    constructor(address payable _factory) Owned(VenueFactory(_factory).owner()) {
        factory = VenueFactory(_factory);
    }

    modifier onlyFactory() virtual {
        require(msg.sender == address(factory), "UNAUTHORIZED");

        _;
    }

    function execute(Pair[] memory chain, address to) external virtual returns (uint256);

    function executeBatch(IVenue[] memory venues, Pair[][] memory paths, address receiver, uint256 minOutput)
        public
        returns (uint256 outputAmount)
    {
        require(venues.length > 0, "EMPTY_LIST");
        require(paths.length == venues.length, "BAD_LENGTH");
        uint256 amount = paths[0][0].amountIn;
        address tokenIn = paths[0][0].tokenIn;
        address tokenOut = paths[paths.length - 1][paths[paths.length - 1].length - 1].tokenOut;
        uint256 outBalanceBefore = UtilsLib.balanceOf(tokenOut, address(this));
        bool isCycle = false;
        if (tokenIn == tokenOut) {
            outBalanceBefore -= amount;
            isCycle = true;
        }
        address to;
        for (uint256 i = 0; i < venues.length; i++) {
            IVenue venue = venues[i];
            require(factory.isActive(address(venue)), "NOT_ACTIVE");
            if (i < venues.length - 1) {
                to = address(venues[i + 1]);
            } else {
                to = address(this);
            }
            paths[i][0].amountIn = amount;
            amount = venue.execute(paths[i], to);
        }
        uint256 outBalanceAfter = UtilsLib.balanceOf(tokenOut, address(this));
        if (msg.sender == UtilsLib.BALANCER_VAULT) {
            // msg.sender is a Balancer vault, repay loan
            uint256 inBalanceAfter = outBalanceAfter;
            if (!isCycle) {
                inBalanceAfter = UtilsLib.balanceOf(tokenIn, address(this));
            }
            require(inBalanceAfter > paths[0][0].amountIn, "BAD_FLASH_LOAN");
            UtilsLib.withdraw(paths[0][0].tokenIn, msg.sender, paths[0][0].amountIn);
            if (isCycle) {
                outBalanceAfter = UtilsLib.balanceOf(tokenOut, address(this));
            }
        }
        outputAmount = outBalanceAfter - outBalanceBefore;
        require(outputAmount >= minOutput, "LOW_OUTPUT");
        UtilsLib.withdraw(address(tokenOut), receiver, outputAmount);
    }

    function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory, bytes memory userData)
        external
        override
    {
        IVenue[] memory venues;
        Pair[][] memory path;
        address receiver;
        uint256 minOutput;
        (venues, path, receiver, minOutput) = abi.decode(userData, (IVenue[], Pair[][], address, uint256));
        executeBatch(venues, path, receiver, minOutput);
    }

    function setFactory(address payable _factory) external onlyOwner {
        factory = VenueFactory(_factory);
    }

    function withdraw(address token, address to, uint256 amount) public onlyOwner {
        UtilsLib.withdraw(token, to, amount);
    }

    // Function to receive Ether. msg.data must be empty
    receive() external payable {}
}
"
    },
    "src/Utils.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.27;

import "@solmate/utils/SafeTransferLib.sol";
import "@solmate/tokens/ERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";

interface WToken is IERC20 {
    function deposit() external payable;

    function withdraw(uint256 amount) external;
}

library UtilsLib {
    address public constant NATIVE_COIN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address public constant WRAPPED_TOKEN_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
    address public constant UNISWAPV2_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    uint256 public constant APPROVE_MUL = 2;
    WToken public constant WTOKEN = WToken(WRAPPED_TOKEN_ADDRESS);

    function isNativeCoin(address asset) internal pure returns (bool) {
        return asset == NATIVE_COIN_ADDRESS;
    }

    function isWrappedToken(address asset) internal pure returns (bool) {
        return asset == WRAPPED_TOKEN_ADDRESS;
    }

    function wrapEth(uint256 amount) internal {
        WTOKEN.deposit{value: amount}();
    }

    function unwrapEth(uint256 amount) internal {
        WTOKEN.withdraw(amount);
    }

    function wrapAndTransfer(address to, uint256 amount) internal {
        wrapEth(amount);
        withdraw(WRAPPED_TOKEN_ADDRESS, to, amount);
    }

    function unwrapAndTransfer(address to, uint256 amount) internal {
        unwrapEth(amount);
        withdrawEth(to, amount);
    }

    function withdrawEth(address to, uint256 amount) internal {
        withdraw(NATIVE_COIN_ADDRESS, to, amount);
    }

    function withdrawOrUnwrapEth(address to, uint256 amount) internal {
        uint256 eth_balance = address(this).balance;
        if (eth_balance < amount) {
            unwrapEth(amount - eth_balance);
        }
        withdrawEth(to, amount);
    }

    function withdraw(address asset, address to, uint256 amount) internal {
        if (address(this) == to) return;
        if (isNativeCoin(asset)) {
            SafeTransferLib.safeTransferETH(to, amount);
        } else {
            SafeTransferLib.safeTransfer(ERC20(asset), to, amount);
        }
    }

    function transferFrom(address asset, address from, address to, uint256 amount) internal {
        if (address(this) == from && address(this) == to) return;
        if (isNativeCoin(asset)) {
            require(msg.value >= amount, "TRANSFER_FROM_ETH_LOW");
            if (to != address(this)) {
                SafeTransferLib.safeTransferETH(to, amount);
            }
        } else {
            SafeTransferLib.safeTransferFrom(ERC20(asset), from, to, amount);
        }
    }

    function balanceOf(address asset, address owner) internal view returns (uint256) {
        if (isNativeCoin(asset)) {
            return owner.balance;
        } else {
            return ERC20(asset).balanceOf(owner);
        }
    }

    function approve(address token, address to, uint256 amount) internal {
        if (ERC20(token).allowance(address(this), to) < amount) {
            SafeTransferLib.safeApprove(ERC20(token), to, 0);
            SafeTransferLib.safeApprove(ERC20(token), to, amount);
        }
    }

    function approveReserve(address token, address to, uint256 amount) internal {
        approve(token, to, amount * APPROVE_MUL);
    }
}
"
    },
    "src/Fees.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.27;

import "./Utils.sol";

abstract contract Fees {
    event FeeChanged(uint32 owner, uint32 referral);
    event FeeTaken(address indexed token, address indexed referral, uint256 referralFee, uint256 routerFee);
    event FeeWithdrawal(address indexed token, address indexed referral, address to, uint256 amount);

    mapping(address => bool) private priorityTokens;
    // referral => token => balance
    mapping(address => mapping(address => uint256)) private balances;
    uint32 public constant FEE_DENOMINATOR = 1000000;
    uint32 public ROUTER_FEE = 500;
    uint32 public REFERRAL_FEE = 500;

    function _setPriorityTokens(address[] calldata tokens, bool enable) internal {
        for (uint256 i = 0; i < tokens.length; i++) {
            priorityTokens[tokens[i]] = enable;
        }
    }

    function _setRouterFee(uint32 fee) internal {
        require(fee + REFERRAL_FEE < FEE_DENOMINATOR, "MAX_FEE");
        ROUTER_FEE = fee;
        emit FeeChanged(fee, REFERRAL_FEE);
    }

    function _setReferralFee(uint32 fee) internal {
        require(fee + ROUTER_FEE < FEE_DENOMINATOR, "MAX_FEE");
        REFERRAL_FEE = fee;
        emit FeeChanged(ROUTER_FEE, fee);
    }

    function _takeFee(address token, uint256 amount, address referral) internal returns (uint256 output) {
        uint256 routerFee = amount * ROUTER_FEE / FEE_DENOMINATOR;
        uint256 referralFee = 0;
        balances[address(this)][token] += routerFee;
        uint256 overallFee = routerFee;
        if (referral != address(0x0)) {
            referralFee = amount * REFERRAL_FEE / FEE_DENOMINATOR;
            balances[referral][token] += referralFee;
            overallFee += referralFee;
        }
        emit FeeTaken(token, referral, referralFee, routerFee);
        output = amount - overallFee;
    }

    function _withdrawFee(address token, address from, address to, uint256 amount) internal {
        require(amount > 0);
        uint256 balance = balances[from][token];
        require(balance >= amount);
        balances[from][token] -= amount;
        UtilsLib.withdraw(token, to, amount);
        emit FeeWithdrawal(token, from, to, amount);
    }

    function hasFeePriority(address token) public view returns (bool) {
        return priorityTokens[token];
    }

    function feeBalance(address owner, address token) public view returns (uint256) {
        return balances[owner][token];
    }

    function feeBalance(address owner, address[] calldata tokens) public view returns (uint256[] memory fees) {
        fees = new uint256[](tokens.length);
        for (uint256 i = 0; i < tokens.length; i++) {
            fees[i] = balances[owner][tokens[i]];
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
"
    },
    "lib/solmate/src/tokens/ERC20.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}
"
    },
    "lib/solmate/src/auth/Owned.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OwnershipTransferred(address indexed user, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}
"
    },
    "src/VenueFactory.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.27;

import "./Utils.sol";
import "./IVenue.sol";
import "@solmate/auth/Owned.sol";
import "@solmate/utils/CREATE3.sol";

contract VenueFactory is Owned {
    event VenueCreated(address indexed venue);

    mapping(address => uint256) private venues;

    constructor(address _owner) Owned(_owner) {}

    function getDeployed(bytes32 salt) external view returns (address) {
        return CREATE3.getDeployed(salt);
    }

    function create(bytes memory creationCode, bytes32 salt)
        external
        payable
        onlyOwner
        returns (address payable venue)
    {
        venue = payable(CREATE3.getDeployed(salt));
        require(!isDeployed(venue), "COLLISION");
        venues[venue] = toState(true, true);
        venue = payable(CREATE3.deploy(salt, creationCode, msg.value));
        emit VenueCreated(venue);
    }

    function toState(bool deployed, bool active) internal pure returns (uint256) {
        return uint256(deployed ? 1 : 0) | (uint256(active ? 1 : 0) << 1);
    }

    function fromState(uint256 state) internal pure returns (bool deployed, bool active) {
        deployed = bool(state & 1 == 1 ? true : false);
        active = bool(state & 2 == 2 ? true : false);
    }

    function isActive(address venue) public view returns (bool) {
        (, bool active) = fromState(venues[venue]);
        return active;
    }

    function isDeployed(address venue) public view returns (bool) {
        (bool deployed,) = fromState(venues[venue]);
        return deployed;
    }

    function deactivate(address venue) external onlyOwner {
        venues[venue] = toState(true, false);
    }

    function activate(address venue) external onlyOwner {
        venues[venue] = toState(true, true);
    }

    function withdraw(address token, address to, uint256 amount) public onlyOwner {
        UtilsLib.withdraw(token, to, amount);
    }

    receive() external payable {}
}
"
    },
    "lib/solmate/src/utils/SafeTransferLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/solmate/src/utils/CREATE3.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {Bytes32AddressLib} from "./Bytes32AddressLib.sol";

/// @notice Deploy to deterministic addresses without an initcode factor.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/CREATE3.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/create3/blob/master/contracts/Create3.sol)
library CREATE3 {
    using Bytes32AddressLib for bytes32;

    //--------------------------------------------------------------------------------//
    // Opcode     | Opcode + Arguments    | Description      | Stack View             //
    //--------------------------------------------------------------------------------//
    // 0x36       |  0x36                 | CALLDATASIZE     | size                   //
    // 0x3d       |  0x3d                 | RETURNDATASIZE   | 0 size                 //
    // 0x3d       |  0x3d                 | RETURNDATASIZE   | 0 0 size               //
    // 0x37       |  0x37                 | CALLDATACOPY     |                        //
    // 0x36       |  0x36                 | CALLDATASIZE     | size                   //
    // 0x3d       |  0x3d                 | RETURNDATASIZE   | 0 size                 //
    // 0x34       |  0x34                 | CALLVALUE        | value 0 size           //
    // 0xf0       |  0xf0                 | CREATE           | newContract            //
    //--------------------------------------------------------------------------------//
    // Opcode     | Opcode + Arguments    | Description      | Stack View             //
    //--------------------------------------------------------------------------------//
    // 0x67       |  0x67XXXXXXXXXXXXXXXX | PUSH8 bytecode   | bytecode               //
    // 0x3d       |  0x3d                 | RETURNDATASIZE   | 0 bytecode             //
    // 0x52       |  0x52                 | MSTORE           |                        //
    // 0x60       |  0x6008               | PUSH1 08         | 8                      //
    // 0x60       |  0x6018               | PUSH1 18         | 24 8                   //
    // 0xf3       |  0xf3                 | RETURN           |                        //
    //--------------------------------------------------------------------------------//
    bytes internal constant PROXY_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";

    bytes32 internal constant PROXY_BYTECODE_HASH = keccak256(PROXY_BYTECODE);

    function deploy(
        bytes32 salt,
        bytes memory creationCode,
        uint256 value
    ) internal returns (address deployed) {
        bytes memory proxyChildBytecode = PROXY_BYTECODE;

        address proxy;
        /// @solidity memory-safe-assembly
        assembly {
            // Deploy a new contract with our pre-made bytecode via CREATE2.
            // We start 32 bytes into the code to avoid copying the byte length.
            proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), salt)
        }
        require(proxy != address(0), "DEPLOYMENT_FAILED");

        deployed = getDeployed(salt);
        (bool success, ) = proxy.call{value: value}(creationCode);
        require(success && deployed.code.length != 0, "INITIALIZATION_FAILED");
    }

    function getDeployed(bytes32 salt) internal view returns (address) {
        return getDeployed(salt, address(this));
    }

    function getDeployed(bytes32 salt, address creator) internal pure returns (address) {
        address proxy = keccak256(
            abi.encodePacked(
                // Prefix:
                bytes1(0xFF),
                // Creator:
                creator,
                // Salt:
                salt,
                // Bytecode hash:
                PROXY_BYTECODE_HASH
            )
        ).fromLast20Bytes();

        return
            keccak256(
                abi.encodePacked(
                    // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01)
                    // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex)
                    hex"d6_94",
                    proxy,
                    hex"01" // Nonce of the proxy contract (1)
                )
            ).fromLast20Bytes();
    }
}
"
    },
    "lib/solmate/src/utils/Bytes32AddressLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Library for converting between addresses and bytes32 values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Bytes32AddressLib.sol)
library Bytes32AddressLib {
    function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) {
        return address(uint160(uint256(bytesValue)));
    }

    function fillLast12Bytes(address addressValue) internal pure returns (bytes32) {
        return bytes32(bytes20(addressValue));
    }
}
"
    }
  },
  "settings": {
    "remappings": [
      "@uniswap/v3-core/=lib/v3-core/",
      "@uniswap/v3-periphery/=lib/v3-periphery/",
      "@uniswap/v2-core/=lib/v2-core/",
      "@uniswap/v2-periphery/=lib/v2-periphery/",
      "@openzeppelin/=lib/openzeppelin-contracts/",
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "ds-test/=lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "forge-std/=lib/forge-std/src/",
      "@forge-std/=lib/forge-std/src/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/",
      "v3-core/=lib/v3-core/",
      "v3-periphery/=lib/v3-periphery/contracts/",
      "solmate/=lib/solmate/src/",
      "@solmate/=lib/solmate/src/",
      "@balancer-labs/=lib/balancer-v2-monorepo/pkg/",
      "v2-periphery/=lib/v2-periphery/contracts/",
      "v2-core/=lib/v2-core/contracts/",
      "balancer-v2-monorepo/=lib/balancer-v2-monorepo/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 100000
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "cancun",
    "viaIR": true
  }
}}

Tags:
ERC20, Multisig, Swap, Upgradeable, Multi-Signature, Factory|addr:0x7f73d2fbff6a7178bac953d780897ed6d77c3acf|verified:true|block:23710797|tx:0xeaf1b2fb1d193c01079c5881ed4b7d24efd67d7b19d37d402d3a56c76178578a|first_check:1762083745

Submitted on: 2025-11-02 12:42:27

Comments

Log in to comment.

No comments yet.