Created
May 28, 2024 14:05
-
-
Save bowd/15db233da63a43c597bf4c0c8443d91d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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