Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion contracts/interfaces/ISimpleRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ interface ISimpleRouter {
error UnsupportedSwap(address _tokenIn, address _tokenOut);
error SlippageExceeded();
error InvalidConfiguration();
error FeeExceedsMaximum(uint256 _feeBips, uint256 _maxFeeBips);
error InvalidFeeCollector(address _feeCollector);

event UpdateFeeBips(uint256 _oldFeeBips, uint256 _newFeeBips);
event UpdateFeeCollector(address _oldFeeCollector, address _newFeeCollector);

struct SwapConfig {
bool useYakSwapRouter;
Expand All @@ -22,7 +27,11 @@ interface ISimpleRouter {
function query(uint256 _amountIn, address _tokenIn, address _tokenOut)
external
view
returns (FormattedOffer memory trade);
returns (FormattedOffer memory offer);

function swap(FormattedOffer memory _trade) external returns (uint256 amountOut);

function swap(uint256 _amountIn, uint256 _amountOutMin, address _tokenIn, address _tokenOut)
external
returns (uint256 amountOut);
}
94 changes: 78 additions & 16 deletions contracts/router/SimpleRouter.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "./../interfaces/ISimpleRouter.sol";
import "./interfaces/IYakRouter.sol";
import "./interfaces/IAdapter.sol";
import "./../interfaces/IERC20.sol";
import "./../lib/Ownable.sol";
import {ISimpleRouter} from "./../interfaces/ISimpleRouter.sol";
import {IYakRouter, FormattedOffer} from "./interfaces/IYakRouter.sol";
import {IAdapter} from "./interfaces/IAdapter.sol";
import {IERC20} from "./../interfaces/IERC20.sol";
import {Ownable} from "./../lib/Ownable.sol";

contract SimpleRouter is ISimpleRouter, Ownable {
bool public yakSwapFallback;
uint256 public maxStepsFallback;
IYakRouter public yakRouter;
address public feeCollector;
uint256 public feeBips;
uint256 internal constant BIPS_DIVISOR = 10000;
uint256 public constant MAX_FEE_BIPS = 1000;

mapping(address => mapping(address => SwapConfig)) public swapConfigurations;

constructor(bool _yakSwapFallback, uint256 _maxStepsFallback, address _yakRouter) {
constructor(
bool _yakSwapFallback,
uint256 _maxStepsFallback,
address _yakRouter,
uint256 _feeBips,
address _feeCollector
) {
configureYakSwapDefaults(_yakSwapFallback, _maxStepsFallback, _yakRouter);
updateFeeBips(_feeBips);
updateFeeCollector(_feeCollector);
}

function updateFeeBips(uint256 _feeBips) public onlyOwner {
if (_feeBips > MAX_FEE_BIPS) {
revert FeeExceedsMaximum(_feeBips, MAX_FEE_BIPS);
}
if (_feeBips != feeBips) {
emit UpdateFeeBips(feeBips, _feeBips);
feeBips = _feeBips;
}
}

function updateFeeCollector(address _feeCollector) public onlyOwner {
if (_feeCollector == address(0)) {
revert InvalidFeeCollector(_feeCollector);
}
if (_feeCollector != feeCollector) {
emit UpdateFeeCollector(feeCollector, _feeCollector);
feeCollector = _feeCollector;
}
}

function updateSwapConfiguration(SwapConfig memory _swapConfig) external onlyOwner {
Expand All @@ -40,36 +72,48 @@ contract SimpleRouter is ISimpleRouter, Ownable {
}

function query(uint256 _amountIn, address _tokenIn, address _tokenOut)
public
external
view
override
returns (FormattedOffer memory offer)
{
SwapConfig storage swapConfig = swapConfigurations[_tokenIn][_tokenOut];
bool routeConfigured = swapConfig.path.adapters.length > 0;
return query(_amountIn, _tokenIn, _tokenOut, swapConfig);
}

if (!routeConfigured && !swapConfig.useYakSwapRouter && !yakSwapFallback) {
function query(uint256 _amountIn, address _tokenIn, address _tokenOut, SwapConfig memory _swapConfig)
public
view
returns (FormattedOffer memory offer)
{
bool routeConfigured = _swapConfig.path.adapters.length > 0;

if (!routeConfigured && !_swapConfig.useYakSwapRouter && !yakSwapFallback) {
return zeroOffer(_tokenIn, _tokenOut);
}

uint256 amountIn = _amountIn;
_amountIn = _amountIn * (BIPS_DIVISOR - feeBips) / BIPS_DIVISOR;

if (routeConfigured) {
offer = queryPredefinedRoute(_amountIn, swapConfig.path.adapters, swapConfig.path.tokens);
offer = queryPredefinedRoute(_amountIn, _swapConfig.path.adapters, _swapConfig.path.tokens);
} else {
offer = queryYakSwap(
_amountIn,
_tokenIn,
_tokenOut,
swapConfig.yakSwapMaxSteps > 0 ? swapConfig.yakSwapMaxSteps : maxStepsFallback
_swapConfig.yakSwapMaxSteps > 0 ? _swapConfig.yakSwapMaxSteps : maxStepsFallback
);
}
offer.amounts[0] = amountIn;
}

function queryPredefinedRoute(uint256 _amountIn, address[] memory _adapters, address[] memory _tokens)
internal
view
returns (FormattedOffer memory offer)
{
uint256[] memory amounts = new uint[](_tokens.length);
uint256[] memory amounts = new uint256[](_tokens.length);
amounts[0] = _amountIn;
for (uint256 i; i < _adapters.length; i++) {
amounts[i + 1] = IAdapter(_adapters[i]).query(amounts[i], _tokens[i], _tokens[i + 1]);
Expand Down Expand Up @@ -107,21 +151,39 @@ contract SimpleRouter is ISimpleRouter, Ownable {
return _swap(tokenIn, _offer);
}

function swap(uint _amountIn, uint _amountOutMin, address _tokenIn, address _tokenOut) external returns (uint amountOut) {
FormattedOffer memory offer = query(_amountIn, _tokenIn, _tokenOut);
function swap(uint256 _amountIn, uint256 _amountOutMin, address _tokenIn, address _tokenOut)
external
returns (uint256 amountOut)
{
SwapConfig memory swapConfig = swapConfigurations[_tokenIn][_tokenOut];
return swap(_amountIn, _amountOutMin, _tokenIn, _tokenOut, swapConfig);
}

function swap(
uint256 _amountIn,
uint256 _amountOutMin,
address _tokenIn,
address _tokenOut,
SwapConfig memory _swapConfig
) public returns (uint256 amountOut) {
uint256 fee = _amountIn * feeBips / BIPS_DIVISOR;
IERC20(_tokenIn).transferFrom(msg.sender, feeCollector, fee);

_amountIn -= fee;
FormattedOffer memory offer = query(_amountIn, _tokenIn, _tokenOut, _swapConfig);

if (offer.adapters.length == 0) {
revert UnsupportedSwap(_tokenIn, _tokenOut);
}

if (offer.amounts[offer.amounts.length -1] < _amountOutMin) {
if (offer.amounts[offer.amounts.length - 1] < _amountOutMin) {
revert SlippageExceeded();
}

return _swap(_tokenIn, offer);
}

function _swap(address _tokenIn, FormattedOffer memory _offer) internal returns(uint amountOut) {
function _swap(address _tokenIn, FormattedOffer memory _offer) internal returns (uint256 amountOut) {
IERC20(_tokenIn).transferFrom(msg.sender, _offer.adapters[0], _offer.amounts[0]);

for (uint256 i; i < _offer.adapters.length; i++) {
Expand Down