Skip to content

Instantly share code, notes, and snippets.

@bowd
Created May 28, 2024 14:05
Show Gist options
  • Save bowd/15db233da63a43c597bf4c0c8443d91d to your computer and use it in GitHub Desktop.
Save bowd/15db233da63a43c597bf4c0c8443d91d to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >0.5.13 <0.9;
pragma experimental ABIEncoderV2;
/*
* @title Broker Interface for trader functions
* @notice The broker is responsible for executing swaps and keeping track of trading limits.
*/
interface IBroker {
/**
* @notice Execute a token swap with fixed amountIn.
* @param exchangeProvider the address of the exchange provider for the pair.
* @param exchangeId The id of the exchange to use.
* @param tokenIn The token to be sold.
* @param tokenOut The token to be bought.
* @param amountIn The amount of tokenIn to be sold.
* @param amountOutMin Minimum amountOut to be received - controls slippage.
* @return amountOut The amount of tokenOut to be bought.
*/
function swapIn(
address exchangeProvider,
bytes32 exchangeId,
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256 amountOut);
/**
* @notice Execute a token swap with fixed amountOut.
* @param exchangeProvider the address of the exchange provider for the pair.
* @param exchangeId The id of the exchange to use.
* @param tokenIn The token to be sold.
* @param tokenOut The token to be bought.
* @param amountOut The amount of tokenOut to be bought.
* @param amountInMax Maximum amount of tokenIn that can be traded.
* @return amountIn The amount of tokenIn to be sold.
*/
function swapOut(
address exchangeProvider,
bytes32 exchangeId,
address tokenIn,
address tokenOut,
uint256 amountOut,
uint256 amountInMax
) external returns (uint256 amountIn);
/**
* @notice Calculate amountOut of tokenOut received for a given amountIn of tokenIn.
* @param exchangeProvider the address of the exchange provider for the pair.
* @param exchangeId The id of the exchange to use.
* @param tokenIn The token to be sold.
* @param tokenOut The token to be bought.
* @param amountIn The amount of tokenIn to be sold.
* @return amountOut The amount of tokenOut to be bought.
*/
function getAmountOut(
address exchangeProvider,
bytes32 exchangeId,
address tokenIn,
address tokenOut,
uint256 amountIn
) external view returns (uint256 amountOut);
/**
* @notice Calculate amountIn of tokenIn needed for a given amountOut of tokenOut.
* @param exchangeProvider the address of the exchange provider for the pair.
* @param exchangeId The id of the exchange to use.
* @param tokenIn The token to be sold.
* @param tokenOut The token to be bought.
* @param amountOut The amount of tokenOut to be bought.
* @return amountIn The amount of tokenIn to be sold.
*/
function getAmountIn(
address exchangeProvider,
bytes32 exchangeId,
address tokenIn,
address tokenOut,
uint256 amountOut
) external view returns (uint256 amountIn);
/**
* @notice Get the list of registered exchange providers.
* @dev This can be used by UI or clients to discover all pairs.
* @return exchangeProviders the addresses of all exchange providers.
*/
function getExchangeProviders()
external
view
returns (address[] memory exchangeProviders);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;
import {IBroker} from "./IBroker.sol";
import {TransferHelper} from "./TransferHelper.sol";
contract MentoRouter {
struct Step {
address exchangeProvider;
bytes32 exchangeId;
address assetIn;
address assetOut;
}
IBroker immutable broker;
constructor(address _broker) {
broker = IBroker(_broker);
}
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
Step[] calldata path
) external returns (uint256[] memory amounts) {
amounts = getAmountsOut(amountIn, path);
require(
amounts[amounts.length - 1] >= amountOutMin,
"MentoRouter: INSUFFICIENT_OUTPUT_AMOUNT"
);
TransferHelper.safeTransferFrom(
path[0].assetIn,
msg.sender,
address(this),
amounts[0]
);
swap(amounts, path);
}
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
Step[] calldata path
) external returns (uint[] memory amounts) {
amounts = getAmountsIn(amountOut, path);
require(
amounts[0] <= amountInMax,
"MentoRouter: EXCESSIVE_INPUT_AMOUNT"
);
TransferHelper.safeTransferFrom(
path[0].assetIn,
msg.sender,
address(this),
amounts[0]
);
swap(amounts, path);
}
function swap(
uint256[] memory amounts,
Step[] memory path
) internal virtual {
for (uint i; i <= path.length - 1; i++) {
TransferHelper.safeApprove(
path[i].assetIn,
address(broker),
amounts[i]
);
broker.swapIn(
path[i].exchangeProvider,
path[i].exchangeId,
path[i].assetIn,
path[i].assetOut,
amounts[i],
amounts[i + 1]
);
}
TransferHelper.safeTransfer(
path[path.length - 1].assetOut,
msg.sender,
amounts[amounts.length - 1]
);
}
function getAmountsOut(
uint256 amountIn,
Step[] memory path
) internal view returns (uint256[] memory amounts) {
require(path.length >= 2, "MentoRouter: INVALID_PATH");
amounts = new uint256[](path.length + 1);
amounts[0] = amountIn;
for (uint i; i <= path.length - 1; i++) {
amounts[i + 1] = broker.getAmountOut(
path[i].exchangeProvider,
path[i].exchangeId,
path[i].assetIn,
path[i].assetOut,
amounts[i]
);
}
return amounts;
}
function getAmountsIn(
uint256 amountOut,
Step[] memory path
) internal view returns (uint256[] memory amounts) {
require(path.length >= 2, "MentoRouter: INVALID_PATH");
amounts = new uint256[](path.length + 1);
amounts[amounts.length] = amountOut;
for (uint i = path.length; i > 0; i--) {
amounts[i - 1] = broker.getAmountIn(
path[i - 1].exchangeProvider,
path[i - 1].exchangeId,
path[i - 1].assetIn,
path[i - 1].assetOut,
amounts[i]
);
}
return amounts;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.0;
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(address token, address to, uint256 value) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(0x095ea7b3, to, value)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::safeApprove: approve failed"
);
}
function safeTransfer(address token, address to, uint256 value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(0xa9059cbb, to, value)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::safeTransfer: transfer failed"
);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(0x23b872dd, from, to, value)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::transferFrom: transferFrom failed"
);
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(
success,
"TransferHelper::safeTransferETH: ETH transfer failed"
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment