Blockchain / Global / Address

Contract

0xC73708aD1471b470415C6b7cccb043Ca21471F1d

Balance
0 SOMI
Transactions sent
3,118
Type
Contract · 1,555 bytes of code

RouterProxyFactory Verified

Compiler
solc 0.8.20+commit.a1b79de6
Language
Solidity
Optimization
Yes, 200 runs
EVM version
paris
License
—
Verified
2026-10-12

src/strategies/routers/RouterProxyFactory.sol

and 5 more files

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import {RouterProxy, AbstractRouter} from "./RouterProxy.sol";

/// @title Mangrove Router Proxy Factory
/// @notice Factory contract for the deployment of RouterProxy instances using CREATE2 for deterministic addresses.
/// @dev Utilizes Ethereum's CREATE2 opcode for deploying contracts with predictable addresses.
contract RouterProxyFactory {
  /// @notice Emitted when a new proxy is deployed through this factory.
  /// @param proxy the deployed proxy contract
  /// @param owner The address which will be the admin and owner of the newly deployed proxy.
  /// @param implementation The address of the router implementation used by the proxy.
  event ProxyDeployed(RouterProxy proxy, address indexed owner, AbstractRouter indexed implementation);

  /// @notice Computes the deterministic address of a proxy deployed for a specific owner using CREATE2.
  /// @param owner The prospective admin and owner of the new proxy contract.
  /// @param routerImplementation router contract which implements routing functions
  /// @return The address where the proxy will be deployed.
  /// @dev The computed address is determined by the owner's address and the factory's address
  function computeProxyAddress(address owner, AbstractRouter routerImplementation)
    public
    view
    returns (address payable)
  {
    bytes memory creationCode = type(RouterProxy).creationCode;
    bytes memory args = abi.encode(routerImplementation);
    bytes32 initcodeHash = keccak256(abi.encodePacked(creationCode, args));
    bytes32 salt = keccak256(abi.encode(owner));
    return _extractAddress(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initcodeHash)));
  }

  /// @notice Converts a padded bytes32 value to a payable address.
  /// @param zeroPaddedAddress The bytes32 value representing an address with padding.
  /// @return res The corresponding payable address.
  function _extractAddress(bytes32 zeroPaddedAddress) private pure returns (address payable res) {
    assembly {
      res := zeroPaddedAddress
    }
  }

  /// @notice Deploys a new RouterProxy for a given owner.
  /// @param owner The address to be set as initial admin and immutable owner of the proxy.
  /// @param routerImplementation router contract which implements routing functions
  /// @return proxy The address of the newly deployed RouterProxy.
  /// @dev Emits a ProxyDeployed event upon successful deployment.
  ///      Note that the deployment can be initiated by any caller, on behalf of `owner`.
  function deployProxy(address owner, AbstractRouter routerImplementation) public returns (RouterProxy proxy) {
    proxy = new RouterProxy{salt: keccak256(abi.encode(owner))}(routerImplementation);
    // TODO: The access controlled admin must maybe be immutable (or this is a vector attack)
    // We will always link one user with a router address anyway
    _afterDeployProxy(proxy, owner);
    emit ProxyDeployed(proxy, owner, routerImplementation);
  }

  /// @notice Hook to be called after a proxy is deployed.
  /// @param proxy the deployed proxy contract
  /// @param owner The address which will be the admin and owner of the newly deployed proxy.
  function _afterDeployProxy(RouterProxy proxy, address owner) internal virtual {
    AbstractRouter(address(proxy)).setAdmin(owner);
  }

  /// @notice Deploys a RouterProxy for a given owner if one has not already been deployed.
  /// @param owner The address to be set as initial admin and immutable owner of the proxy.
  /// @param routerImplementation router contract which implements routing functions
  /// @return proxy The address of the RouterProxy.
  /// @return created A boolean indicating if the proxy was created during this call.
  /// @dev If the proxy already exists at the computed address, the function will not redeploy it.
  ///      The `created` return value indicates whether the proxy was created as a result of this call.
  function instantiate(address owner, AbstractRouter routerImplementation)
    public
    returns (RouterProxy proxy, bool created)
  {
    proxy = RouterProxy(computeProxyAddress(owner, routerImplementation));
    if (address(proxy).code.length == 0) {
      require(deployProxy(owner, routerImplementation) == proxy, "Deployed via create2 failed");
      created = true;
    }
  }
}

Other files

  • node_modules/@mangrovedao/mangrove-core/lib/IERC20.sol
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.2;
    
    interface IERC20 {
      function totalSupply() external view returns (uint);
    
      function balanceOf(address account) external view returns (uint);
    
      function transfer(address recipient, uint amount) external returns (bool);
    
      function allowance(address owner, address spender) external view returns (uint);
    
      function approve(address spender, uint amount) external returns (bool);
    
      function transferFrom(address sender, address recipient, uint amount) external returns (bool);
    
      function symbol() external view returns (string memory);
    
      event Transfer(address indexed from, address indexed to, uint value);
      event Approval(address indexed owner, address indexed spender, uint value);
    
      function decimals() external view returns (uint8);
    
      function name() external view returns (string memory);
    }
    
  • src/strategies/routers/RouterProxy.sol
    // SPDX-License-Identifier:	MIT
    pragma solidity ^0.8.18;
    
    import {AbstractRouter} from "./abstract/AbstractRouter.sol";
    
    /// @title Minimalistic Mangrove Router Proxy
    /// @notice A proxy contract that delegates calls to an instance of an AbstractRouter contract.
    ///         It does not allow updates of implementation contract.
    /// @dev it assumes the IMPLEMENTATION uses first storage slot to hold the admin address (this is guaranteed as long as `AbstractRouter` is `AccessControlled`)
    contract RouterProxy {
      /// @notice The address of the deployed SmartRouter contract acting as the delegate implementation.
      /// @dev The SmartRouter instance must be AccessControlled to ensure the storage layout is matched.
      AbstractRouter public immutable IMPLEMENTATION;
    
      /// @notice Emitted when the admin of the proxy is set.
      /// * This is a copy of the AccessControlled event to set without calling the implementation.
      /// @param admin The address of the admin of the proxy.
      event SetAdmin(address admin);
    
      /// @notice Deploys a Proxy for the SmartRouter that handles incoming transactions and delegates them to the implementation.
      /// @param implementation The address of the deployed SmartRouter contract to which calls will be delegated.
      /// @dev Initializes the contract with an AccessControlled base to set up access control.
      constructor(AbstractRouter implementation) {
        IMPLEMENTATION = implementation;
        // store the msg sender at address 0 (_admin storage slot on access controlled)
        assembly {
          sstore(0, caller())
        }
        emit SetAdmin(msg.sender);
      }
    
      /// @notice Fallback function to delegate calls to the implementation contract.
      fallback() external {
        AbstractRouter implementation = IMPLEMENTATION;
        assembly {
          // Copy msg.data. We take full control of memory in this inline assembly
          // block because it will not return to Solidity code. We overwrite the
          // Solidity scratch pad at memory position 0.
          calldatacopy(0, 0, calldatasize())
    
          // Call the implementation.
          // out and outsize are 0 because we don't know the size yet.
          let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
    
          // Copy the returned data.
          returndatacopy(0, 0, returndatasize())
    
          switch result
          // delegatecall returns 0 on error.
          case 0 { revert(0, returndatasize()) }
          default { return(0, returndatasize()) }
        }
      }
    }
    
  • src/strategies/routers/abstract/AbstractRouter.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.10;
    
    import {AccessControlled} from "@mgv-strats/src/strategies/utils/AccessControlled.sol";
    import {RoutingOrderLib as RL} from "./RoutingOrderLib.sol";
    
    /// @title AbstractRouter
    /// @notice Partial implementation and requirements for liquidity routers.
    
    abstract contract AbstractRouter is AccessControlled(msg.sender) {
      ///@notice This modifier verifies that `msg.sender` an allowed caller of this router.
      modifier onlyBound() {
        require(isBound(msg.sender), "AccessControlled/Invalid");
        _;
      }
    
      ///@notice This modifier verifies that `msg.sender` is the admin or an allowed caller of this router.
      modifier boundOrAdmin() {
        require(msg.sender == admin() || isBound(msg.sender), "AccessControlled/Invalid");
        _;
      }
    
      ///@notice logging bound maker contract
      ///@param maker the maker address. This is indexed, so that RPC calls can filter on it.
      ///@notice by emitting this data, an indexer will be able to keep track of what maker contracts are allowed to call this router.
      event MakerBind(address indexed maker);
    
      ///@notice logging unbound maker contract
      ///@param maker the maker address. This is indexed, so that RPC calls can filter on it.
      ///@notice by emitting this data, an indexer will be able to keep track of what maker contracts are allowed to call this router.
      event MakerUnbind(address indexed maker);
    
      ///@notice getter for the `makers: addr => bool` mapping
      ///@param mkr the address of a maker contract
      ///@return true if `mkr` is authorized to call this router.
      function isBound(address mkr) public view virtual returns (bool) {
        return RL.boundMakerContracts()[mkr];
      }
    
      ///@notice pulls liquidity from the reserve and sends it to the calling maker contract.
      ///@param routingOrder the arguments of the pull order
      ///@param amount of token that needs to be routed
      ///@param strict if false the router may pull at more than `amount` to msg.sender. Otherwise it pulls at least `amount`.
      ///@return pulled the amount of `routingOrder.token` that has been sent to `msg.sender`
      function pull(RL.RoutingOrder calldata routingOrder, uint amount, bool strict)
        external
        onlyBound
        returns (uint pulled)
      {
        if (strict && amount == 0) {
          return 0;
        }
        pulled = __pull__(routingOrder, amount, strict);
      }
    
      ///@notice router dependent hook to customize pull orders.
      ///@param routingOrder the arguments of the pull order
      ///@param amount of token that needs to be routed
      ///@param strict if false the router may pull at more than `amount` to msg.sender. Otherwise it pulls at least `amount`.
      ///@return pulled the amount of `routingOrder.token` that has been sent to `msg.sender`
      function __pull__(RL.RoutingOrder memory routingOrder, uint amount, bool strict) internal virtual returns (uint);
    
      ///@notice pushes liquidity from msg.sender to the reserve
      ///@param routingOrder the arguments of the push order
      ///@param amount of token that needs to be routed
      ///@return pushed the amount of `routingOrder.token` that has been taken from `msg.sender`
      function push(RL.RoutingOrder calldata routingOrder, uint amount) external onlyBound returns (uint pushed) {
        if (amount == 0) {
          return 0;
        }
        pushed = __push__(routingOrder, amount);
      }
    
      ///@notice router dependent hook to customize pull orders.
      ///@param routingOrder the arguments of the pull order
      ///@param amount of token that needs to be routed
      ///@return pushed the amount of `routingOrder.token` that has been sent to `msg.sender`
      function __push__(RL.RoutingOrder memory routingOrder, uint amount) internal virtual returns (uint pushed);
    
      ///@notice iterative `push` of the whole maker contract's balance
      ///@dev minimizes external calls in case several assets needs to be pushed via the router.
      ///@param routingOrders to be executed
      function flush(RL.RoutingOrder[] memory routingOrders) external onlyBound {
        for (uint i = 0; i < routingOrders.length; ++i) {
          uint amount = routingOrders[i].token.balanceOf(msg.sender);
          if (amount > 0) {
            require(__push__(routingOrders[i], amount) == amount, "router/flushFailed");
          }
        }
      }
    
      ///@notice adds a maker contract address to the allowed makers of this router
      ///@dev this function is callable by router's admin to bootstrap, but later on an allowed maker contract can add another address
      ///@param makerContract the maker contract address
      function bind(address makerContract) public onlyAdmin {
        RL.boundMakerContracts()[makerContract] = true;
        emit MakerBind(makerContract);
      }
    
      ///@notice removes a maker contract address from the allowed makers of this router
      ///@param makerContract the maker contract address
      function _unbind(address makerContract) internal {
        RL.boundMakerContracts()[makerContract] = false;
        emit MakerUnbind(makerContract);
      }
    
      ///@notice removes `msg.sender` from the allowed makers of this router
      function unbind() external onlyBound {
        _unbind(msg.sender);
      }
    
      ///@notice removes a makerContract from the allowed makers of this router
      ///@param makerContract the maker contract address
      function unbind(address makerContract) external onlyAdmin {
        _unbind(makerContract);
      }
    
      /**
       * @notice Returns the token balance available for a specific routing order
       * @param routingOrder The routing order to check the balance for
       * @return balance The balance of the token in the routing order
       */
      function tokenBalanceOf(RL.RoutingOrder calldata routingOrder) public view virtual returns (uint balance);
    }
    
  • src/strategies/routers/abstract/RoutingOrderLib.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.10;
    
    import {IERC20} from "@mgv/lib/IERC20.sol";
    
    /// @title Library to obtain Routing orders of various kind
    
    library RoutingOrderLib {
      ///@notice pointer to first storage slot used for randomized access
      bytes32 private constant OFFSET = keccak256("MangroveStrats.RoutingOrderLib.Layout");
    
      ///@notice Argument to pull/push
      ///@param token the asset to be routed
      ///@param olKeyHash the id of the market that triggered the calling offer logic. Is bytes32(0) when routing is done outside offer logic.
      ///@param offerId the id of the offer that triggered the calling offer logic. Is uint(0) when routing is done outsider offer logic.
      ///@param fundOwner the owner of the routed funds. If calling router is a proxy, it address is determined by `fundOwner`.
      struct RoutingOrder {
        IERC20 token;
        bytes32 olKeyHash;
        uint offerId;
        address fundOwner;
      }
    
      ///@notice helper to create a RoutingOrder struct without zero'ed fields for market coordinates.
      ///@param token the asset to be routed
      ///@param fundOwner the owner of the routed funds
      ///@return ro the routing order struct
      function createOrder(IERC20 token, address fundOwner) internal pure returns (RoutingOrder memory ro) {
        ro.token = token;
        ro.fundOwner = fundOwner;
      }
    
      ///@notice the bound maker contracts which are allowed to call this router.
      struct Layout {
        mapping(address => bool) boundMakerContracts;
      }
    
      ///@notice allowed pullers/pushers for this router
      ///@return bound contracts to this router, in the form of a storage mapping
      function boundMakerContracts() internal view returns (mapping(address => bool) storage) {
        bytes32 offset = OFFSET;
        Layout storage st;
        assembly ("memory-safe") {
          st.slot := offset
        }
        return st.boundMakerContracts;
      }
    }
    
  • src/strategies/utils/AccessControlled.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.10;
    
    /// @title This contract is used to restrict access to privileged functions of inheriting contracts through modifiers.
    /// @notice The contract stores an admin address which is checked against `msg.sender` in the `onlyAdmin` modifier.
    /// @notice Additionally, a specific `msg.sender` can be verified with the `onlyCaller` modifier.
    contract AccessControlled {
      /**
       * @notice logs new `admin` of `this`
       * @param admin The new admin.
       * @notice By emitting this data, an indexer will be able to keep track of what address is the admin of this contract.
       */
      event SetAdmin(address admin);
      /**
       * @notice The admin address.
       */
    
      address internal _admin;
    
      /**
       * @notice `AccessControlled`'s constructor
       * @param admin_ The address of the admin that can access privileged functions and also allowed to change the admin. Cannot be `address(0)`.
       */
      constructor(address admin_) {
        _setAdmin(admin_);
      }
    
      /**
       * @notice This modifier verifies that `msg.sender` is the admin.
       */
      modifier onlyAdmin() {
        require(msg.sender == _admin, "AccessControlled/Invalid");
        _;
      }
    
      /**
       * @notice This modifier verifies that `msg.sender` is the caller.
       * @param caller The address of the caller that can access the modified function.
       */
      modifier onlyCaller(address caller) {
        require(msg.sender == caller, "AccessControlled/Invalid");
        _;
      }
    
      /**
       * @notice This modifier verifies that `msg.sender` is either caller or the admin
       * @param caller The address of a caller that can access the modified function.
       */
      modifier adminOrCaller(address caller) {
        // test _admin second to save a storage read when possible
        require(msg.sender == caller || msg.sender == _admin, "AccessControlled/Invalid");
        _;
      }
    
      /**
       * @notice Retrieves the current admin.
       * @return current admin.
       */
      function admin() public view returns (address current) {
        return _admin;
      }
    
      /**
       * @notice This function is called to add logic on admin change for inheriting contracts.
       * @param admin_ The new admin. Cannot be `address(0)`.
       */
      function _onAdminChange(address admin_) internal virtual {}
    
      /**
       * @notice This sets and log a new admin
       * @param admin_ The new admin. Cannot be `address(0)`.
       */
      function _setAdmin(address admin_) internal {
        require(admin_ != address(0), "AccessControlled/0xAdmin");
        _admin = admin_;
        emit SetAdmin(admin_);
        _onAdminChange(admin_);
      }
    
      /**
       * @notice This sets the admin. Only the current admin can change the admin.
       * @param admin_ The new admin. Cannot be `address(0)`.
       */
      function setAdmin(address admin_) external onlyAdmin {
        _setAdmin(admin_);
      }
    }
    
ABI · 4 entries
json
[
  {
    "name": "ProxyDeployed",
    "type": "event",
    "inputs": [
      {
        "name": "proxy",
        "type": "address",
        "indexed": false,
        "internalType": "contract RouterProxy"
      },
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "implementation",
        "type": "address",
        "indexed": true,
        "internalType": "contract AbstractRouter"
      }
    ],
    "anonymous": false
  },
  {
    "name": "computeProxyAddress",
    "type": "function",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "routerImplementation",
        "type": "address",
        "internalType": "contract AbstractRouter"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address payable"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "deployProxy",
    "type": "function",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "routerImplementation",
        "type": "address",
        "internalType": "contract AbstractRouter"
      }
    ],
    "outputs": [
      {
        "name": "proxy",
        "type": "address",
        "internalType": "contract RouterProxy"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "instantiate",
    "type": "function",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "routerImplementation",
        "type": "address",
        "internalType": "contract AbstractRouter"
      }
    ],
    "outputs": [
      {
        "name": "proxy",
        "type": "address",
        "internalType": "contract RouterProxy"
      },
      {
        "name": "created",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  }
]