DexAggregator

0x20f6ee51340adeed01a59b0e65cb3703f3dc860c

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
8,679 bytes
ABI entries
38
8 read · 14 write
License
none

Contract information

Address
0x20f6ee51340adeed01a59b0e65cb3703f3dc860c
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0x58e945fb31…86bdb60173

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (8)

coreAddress()address
fundsManagers(address)bool
internalCallers(address)bool
owner()address
paused()bool
pendingOwner()address
updateAmountIn(bytes, uint256)bytes
weth()address

Events (7)

OwnershipTransferStartedOwnershipTransferredPausedSwapUnpausedUpdateFundsManagerUpdateInternalCaller

ABI

[
  {
    "inputs": [],
    "name": "ApprovalFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FailedWrap",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientAmountOut",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidFundManager",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidNativeAmount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSignature",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFromFailed",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "fromAddress",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "toAddress",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "fromAssetAddress",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "toAssetAddress",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amountIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amountOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "expectedAmountOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amountInSurplus",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amountOutSurplus",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "consumerId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address[]",
        "name": "swapFeeAssetAddresses",
        "type": "address[]"
      },
      {
        "indexed": false,
        "internalType": "address[]",
        "name": "swapFeeReceivers",
        "type": "address[]"
      },
      {
        "indexed": false,
        "internalType": "uint256[]",
        "name": "swapFeeAmounts",
        "type": "uint256[]"
      }
    ],
    "name": "Swap",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "value",
        "type": "bool"
      }
    ],
    "name": "UpdateFundsManager",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "value",
        "type": "bool"
      }
    ],
    "name": "UpdateInternalCaller",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "coreAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "estimateSwapGas",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amountOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "gasUsed",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "fundsManagers",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "internalCallers",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes[]",
        "name": "data",
        "type": "bytes[]"
      }
    ],
    "name": "multicall",
    "outputs": [
      {
        "internalType": "bytes[]",
        "name": "results",
        "type": "bytes[]"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "swapWithBackendSignature",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amountOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "assetAddress",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amount",
            "type": "uint256"
          }
        ],
        "internalType": "struct IDexAggregator.TransferParam",
        "name": "transferParam",
        "type": "tuple"
      }
    ],
    "name": "transfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "assetAddress",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amount",
            "type": "uint256"
          }
        ],
        "internalType": "struct IDexAggregator.TransferParam[]",
        "name": "transferParams",
        "type": "tuple[]"
      }
    ],
    "name": "transferMany",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      },
      {
        "internalType": "uint256",
        "name": "amountIn",
        "type": "uint256"
      }
    ],
    "name": "updateAmountIn",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "value",
        "type": "address"
      }
    ],
    "name": "updateCoreAddress",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "value",
        "type": "bool"
      }
    ],
    "name": "updateFundsManager",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "value",
        "type": "bool"
      }
    ],
    "name": "updateInternalCaller",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "value",
        "type": "address"
      }
    ],
    "name": "updateWeth",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {Ownable2Step} from "openzeppelin-solidity/contracts/access/Ownable2Step.sol";
import {Pausable} from "openzeppelin-solidity/contracts/security/Pausable.sol";
import {Address} from "openzeppelin-solidity/contracts/utils/Address.sol";
import {Math} from "openzeppelin-solidity/contracts/utils/math/Math.sol";
import {IDexAggregator} from "./interfaces/IDexAggregator.sol";
import {LibAsset} from "./libraries/LibAsset.sol";
import {LibRouter, SwapData} from "./libraries/LibRouter.sol";

error InvalidCall();
error InvalidCaller();
error InvalidFundManager();
error InsufficientAmountOut();
error InvalidNativeAmount();

contract DexAggregator is IDexAggregator, Ownable2Step, Pausable {
    using LibAsset for address;

    mapping(address => bool) public internalCallers;
    mapping(address => bool) public fundsManagers;
    address public coreAddress;
    address public weth;

    modifier onlyInternalCaller() {
        if (!internalCallers[msg.sender]) {
            revert InvalidCaller();
        }
        _;
    }

    modifier onlyFundManager() {
        if (!fundsManagers[msg.sender]) {
            revert InvalidFundManager();
        }
        _;
    }

    /// @dev See {IDexAggregator-updateInternalCaller}
    function updateInternalCaller(address caller, bool value) external onlyOwner {
        internalCallers[caller] = value;

        emit UpdateInternalCaller(msg.sender, caller, value);
    }

    /// @dev See {IDexAggregator-updateFundsManager}
    function updateFundsManager(address caller, bool value) external onlyOwner {
        fundsManagers[caller] = value;

        emit UpdateFundsManager(msg.sender, caller, value);
    }

    /// @dev See {IDexAggregator-updateCoreAddress}
    function updateCoreAddress(address value) external onlyOwner {
        coreAddress = value;
    }

    /// @dev See {IDexAggregator-updateWeth}
    function updateWeth(address value) external onlyOwner {
        weth = value;
    }

    /// @dev See {IDexAggregator-updateAmountIn}
    function updateAmountIn(bytes memory data, uint256 amountIn) external pure returns (bytes memory) {
        assembly {
            mstore(add(data, 164), amountIn) // 32 + 132
        }

        return data;
    }

    /// @dev See {IDexAggregator-pause}
    function pause() public onlyOwner whenNotPaused {
        _pause();
    }

    /// @dev See {IDexAggregator-unpause}
    function unpause() public onlyOwner whenPaused {
        _unpause();
    }

    /// @dev See {IDexAggregator-transfer}
    function transfer(TransferParam calldata transferParam) external onlyFundManager {
        transferParam.assetAddress.transfer(transferParam.recipient, transferParam.amount);
    }

    /// @dev See {IDexAggregator-transferMany}
    function transferMany(TransferParam[] calldata transferParams) external onlyFundManager {
        for (uint256 i = 0; i < transferParams.length; ) {
            transferParams[i].assetAddress.transfer(transferParams[i].recipient, transferParams[i].amount);
            unchecked {
                i++;
            }
        }
    }

    /// @dev See {IDexAggregator-multicall}
    function multicall(bytes[] calldata data) external onlyOwner returns (bytes[] memory results) {
        results = new bytes[](data.length);
        for (uint256 i = 0; i < data.length; i++) {
            results[i] = Address.functionDelegateCall(address(this), data[i]);
        }
        return results;
    }

    /// @dev Verifies the backend signature for a swap operation.
    /// @param swapData The data structure containing information about the swap.
    /// @return fromAddress The address to be used for the swap operation.
    function verifyBackendSignature(SwapData memory swapData) private view returns (address fromAddress) {
        bool hasSwapFee = swapData.swapFeeReceivers.length > 0;
        uint256 messagePtr;
        uint256 messageLength = hasSwapFee ? 480 : 384;
        assembly {
            messagePtr := mload(0x40)
            mstore(0x40, add(messagePtr, messageLength))
            switch hasSwapFee
            case 1 {
                // keccak256("Swap(address router,address sender,address recipient,address fromAsset,address toAsset,uint256 deadline,uint256 amountOutMin,uint256 expectedAmountOut,bytes32 consumerId,uint256 maxRetentionBps,bool transferFromRouter,address[] swapFeeAssets,address[] swapFeeReceivers,uint256[] swapFeeAmounts)")
                mstore(messagePtr, 0x5044c48dcff133da673c91c7d371f756e221fd81c7b9f02129fcfd724b840e49)
            }
            default {
                // keccak256("Swap(address router,address sender,address recipient,address fromAsset,address toAsset,uint256 deadline,uint256 amountOutMin,uint256 expectedAmountOut,bytes32 consumerId,uint256 maxRetentionBps,bool transferFromRouter)")
                mstore(messagePtr, 0x088852e96193cda3374c3e36c04a8bd173e0c18999b51fb3f5c5ab52d7da1fe0)
            }
        }
        fromAddress = LibRouter.verifySignature(
            // keccak256(bytes("Dex Aggregator")),
            0x19994fa387947b8529d5d57c0c84137061b0be98f1cef929a4710ff4f36aed49,
            // keccak256(bytes("1")),
            0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6,
            swapData,
            messagePtr,
            messageLength,
            2
        );
    }

    function emitSwap(EmitSwapParams memory p) private {
        emit Swap(
            p.fromAddress,
            p.toAddress,
            p.fromAssetAddress,
            p.toAssetAddress,
            p.amountIn - (p.amountIn > p.transferredAmountIn ? p.amountIn - p.transferredAmountIn : 0),
            p.amountOut,
            p.expectedAmountOut,
            p.amountIn > p.transferredAmountIn ? p.amountIn - p.transferredAmountIn : 0,
            p.transferredAmountOut > p.amountOut ? p.transferredAmountOut - p.amountOut : 0,
            p.consumerId,
            p.swapFeeAssetAddresses,
            p.swapFeeReceivers,
            p.swapFeeAmounts
        );
    }

    /// @dev Swaps tokens based on the provided swap data.
    /// @param swapData The data structure containing information about the swap operation.
    /// @param fromAddress The address initiating the swap. This address is responsible for the input assets.
    /// @return amountOut The amount of tokens or assets received after the swap.
    /// @return gasUsed The amount of gas consumed by the recorded operation.
    function swap(
        SwapData memory swapData,
        address fromAddress,
        address fromAssetAddress
    ) private returns (uint256 amountOut, uint256 gasUsed) {
        amountOut = swapData.toAssetAddress.getBalanceOf(swapData.toAddress);
        uint256 transferredAmountOut = swapData.toAssetAddress.getBalance();
        uint256 transferredAmountIn = swapData.amountIn;

        adjustFromAssetAddress(swapData);

        if (fromAssetAddress.isNative()) {
            // Already transferred and wrapped
            if (swapData.transferFromRouter) {
                swapData.fromAssetAddress.approve(coreAddress, swapData.amountIn);
            } else {
                swapData.fromAssetAddress.transfer(coreAddress, swapData.amountIn);
            }
        } else {
            if (swapData.transferFromRouter) {
                swapData.fromAssetAddress.transferFrom(fromAddress, address(this), swapData.amountIn);
                swapData.fromAssetAddress.approve(coreAddress, swapData.amountIn);
            } else {
                swapData.fromAssetAddress.transferFrom(fromAddress, coreAddress, swapData.amountIn);
            }
        }

        address currentCoreAddress = coreAddress;
        assembly {
            let success := 0
            let inputPtr := mload(0x40)
            let inputLength := add(calldataload(36), 68)
            let payloadLength := sub(inputLength, 68)
            mstore(0x40, add(inputPtr, inputLength))
            mstore(inputPtr, 0x158f689400000000000000000000000000000000000000000000000000000000) // swapWithoutSignature
            mstore(add(inputPtr, 4), 32)
            mstore(add(inputPtr, 36), payloadLength)
            let outputPtr := mload(0x40)
            mstore(0x40, add(outputPtr, 64))
            calldatacopy(add(inputPtr, 68), 68, payloadLength)

            // Override amountIn
            mstore(add(inputPtr, 132), mload(add(swapData, 96)))

            // Override fromAssetAddress
            let afterFromAssetAddress := mload(add(inputPtr, 112))
            mstore(add(inputPtr, 92), shl(96, mload(add(swapData, 32))))
            mstore(add(inputPtr, 112), afterFromAssetAddress)

            success := call(gas(), currentCoreAddress, 0, inputPtr, inputLength, outputPtr, 64)

            if eq(success, 1) {
                gasUsed := mload(outputPtr)
            }

            if eq(success, 0) {
                returndatacopy(0, 0, returndatasize())
                revert(0, returndatasize())
            }
        }

        transferredAmountOut = swapData.toAssetAddress.getBalance() - transferredAmountOut;

        if (swapData.transferFromRouter) {
            // Slippage in fromAsset
            swapData.toAssetAddress.transfer(swapData.toAddress, transferredAmountOut);
            uint256 approval = swapData.fromAssetAddress.allowance(address(this), currentCoreAddress);
            transferredAmountIn = swapData.amountIn - approval;
            if (approval > 0) {
                // Reset approval
                swapData.fromAssetAddress.approve(currentCoreAddress, 0);
            }
        } else {
            // Slippage in toAsset
            swapData.toAssetAddress.transfer(
                swapData.toAddress,
                (transferredAmountOut <= swapData.expectedAmountOut || swapData.expectedAmountOut == 0)
                    ? transferredAmountOut
                    : Math.max(
                        swapData.expectedAmountOut,
                        transferredAmountOut - ((transferredAmountOut * swapData.maxRetentionBps) / 10000)
                    )
            );
        }

        amountOut = swapData.toAssetAddress.getBalanceOf(swapData.toAddress) - amountOut;

        if (amountOut < swapData.amountOutMin) {
            revert InsufficientAmountOut();
        }

        emitSwap(
            EmitSwapParams({
                fromAddress: fromAddress,
                toAddress: swapData.toAddress,
                fromAssetAddress: fromAssetAddress,
                toAssetAddress: swapData.toAssetAddress,
                transferredAmountIn: transferredAmountIn,
                transferredAmountOut: transferredAmountOut,
                amountIn: swapData.amountIn,
                amountOut: amountOut,
                expectedAmountOut: swapData.expectedAmountOut,
                consumerId: swapData.consumerId,
                swapFeeAssetAddresses: swapData.swapFeeAssetAddresses,
                swapFeeReceivers: swapData.swapFeeReceivers,
                swapFeeAmounts: swapData.swapFeeAmounts
            })
        );
    }

    function adjustFromAssetAddress(SwapData memory swapData) private {
        if (swapData.fromAssetAddress.isNative()) {
            swapData.fromAssetAddress = weth;
            swapData.fromAssetAddress.wrap(swapData.amountIn);
        }
    }

    function adjustAmountIn(SwapData memory swapData, uint256 transferredNativeFeeAmount) private {
        // Auto
        if (swapData.amountIn == 0) {
            if (swapData.fromAssetAddress.isNative()) {
                swapData.amountIn = msg.value - transferredNativeFeeAmount;
            } else {
                uint256 allowance = swapData.fromAssetAddress.allowance(msg.sender, address(this));
                uint256 balance = swapData.fromAssetAddress.getBalanceOf(msg.sender);
                swapData.amountIn = allowance < balance ? allowance : balance;
            }
        }

        if (swapData.fromAssetAddress.isNative()) {
            if (transferredNativeFeeAmount + swapData.amountIn != msg.value) {
                revert InvalidNativeAmount();
            }
        } else {
            if (transferredNativeFeeAmount != msg.value) {
                revert InvalidNativeAmount();
            }
        }
    }

    /// @dev See {IDexAggregator-estimateSwapGas}
    function estimateSwapGas(
        bytes calldata
    ) external payable whenNotPaused returns (uint256 amountOut, uint256 gasUsed) {
        SwapData memory swapData = LibRouter.getData();
        address fromAssetAddress = swapData.fromAssetAddress;
        address fromAddress = verifyBackendSignature(swapData);
        uint256 transferredNativeFeeAmount = LibRouter.transferFees(swapData, fromAddress);
        adjustAmountIn(swapData, transferredNativeFeeAmount);
        (amountOut, gasUsed) = swap(swapData, fromAddress, fromAssetAddress);
    }

    /// @dev See {IDexAggregator-swapWithBackendSignature}
    function swapWithBackendSignature(bytes calldata) external payable whenNotPaused returns (uint256 amountOut) {
        SwapData memory swapData = LibRouter.getData();
        address fromAssetAddress = swapData.fromAssetAddress;
        address fromAddress = verifyBackendSignature(swapData);
        uint256 transferredNativeFeeAmount = LibRouter.transferFees(swapData, fromAddress);
        adjustAmountIn(swapData, transferredNativeFeeAmount);
        (amountOut, ) = swap(swapData, fromAddress, fromAssetAddress);
    }

    /// @dev Used to receive ethers
    receive() external payable {}
}
Chain explorer5653msChain node82ms