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
- Creator
- 0x9f2eFccb40…958977f694
- 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