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"
}
]