Files
smom-dbis-138/contracts/bridge/trustless/EnhancedSwapRouterV2.sol

497 lines
19 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./LiquidityPoolETH.sol";
import "./RouteTypesV2.sol";
import "./interfaces/IRouteExecutorAdapter.sol";
import "./interfaces/IWETH.sol";
contract EnhancedSwapRouterV2 is AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant ROUTING_MANAGER_ROLE = keccak256("ROUTING_MANAGER_ROLE");
bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE");
struct ProviderRouteConfig {
address target;
bytes providerData;
bool enabled;
}
address public immutable weth;
address public immutable usdt;
address public immutable usdc;
address public immutable dai;
mapping(uint8 => address) public providerAdapters;
mapping(uint8 => bool) public providerEnabled;
mapping(address => mapping(address => mapping(uint8 => ProviderRouteConfig))) private providerRoutes;
mapping(uint256 => RouteTypesV2.Provider[]) private sizeBasedRouting;
event ProviderAdapterSet(RouteTypesV2.Provider indexed provider, address indexed adapter);
event ProviderRouteSet(
address indexed tokenIn,
address indexed tokenOut,
RouteTypesV2.Provider indexed provider,
address target,
bool enabled
);
event ProviderToggled(RouteTypesV2.Provider indexed provider, bool enabled);
event RoutingConfigUpdated(uint256 indexed sizeCategory, RouteTypesV2.Provider[] providers);
event RouteExecuted(
bytes32 indexed routeHash,
address indexed caller,
address indexed recipient,
address inputToken,
address outputToken,
uint256 amountIn,
uint256 amountOut
);
error ZeroAddress();
error ZeroAmount();
error InvalidPlan();
error InvalidStablecoin();
error ProviderDisabled();
error ProviderAdapterNotConfigured();
error RouteNotConfigured();
error RouteValidationFailed(string reason);
error InsufficientOutput();
constructor(address _weth, address _usdt, address _usdc, address _dai) {
if (_weth == address(0) || _usdt == address(0) || _usdc == address(0) || _dai == address(0)) {
revert ZeroAddress();
}
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ROUTING_MANAGER_ROLE, msg.sender);
weth = _weth;
usdt = _usdt;
usdc = _usdc;
dai = _dai;
providerEnabled[uint8(RouteTypesV2.Provider.Dodo)] = true;
providerEnabled[uint8(RouteTypesV2.Provider.UniswapV3)] = true;
providerEnabled[uint8(RouteTypesV2.Provider.Balancer)] = true;
providerEnabled[uint8(RouteTypesV2.Provider.Curve)] = true;
providerEnabled[uint8(RouteTypesV2.Provider.OneInch)] = false;
providerEnabled[uint8(RouteTypesV2.Provider.Partner)] = false;
providerEnabled[uint8(RouteTypesV2.Provider.DodoV3)] = true;
_initializeDefaultRouting();
}
function setProviderAdapter(RouteTypesV2.Provider provider, address adapter)
external
onlyRole(ROUTING_MANAGER_ROLE)
{
if (adapter == address(0)) revert ZeroAddress();
providerAdapters[uint8(provider)] = adapter;
emit ProviderAdapterSet(provider, adapter);
}
function setProviderRoute(
address tokenIn,
address tokenOut,
RouteTypesV2.Provider provider,
address target,
bytes calldata providerData,
bool enabled
) external onlyRole(ROUTING_MANAGER_ROLE) {
if (tokenIn == address(0) || tokenOut == address(0) || target == address(0)) {
revert ZeroAddress();
}
providerRoutes[tokenIn][tokenOut][uint8(provider)] =
ProviderRouteConfig({target: target, providerData: providerData, enabled: enabled});
emit ProviderRouteSet(tokenIn, tokenOut, provider, target, enabled);
}
function getProviderRoute(address tokenIn, address tokenOut, RouteTypesV2.Provider provider)
external
view
returns (address target, bytes memory providerData, bool enabled)
{
ProviderRouteConfig storage config = providerRoutes[tokenIn][tokenOut][uint8(provider)];
return (config.target, config.providerData, config.enabled);
}
function setProviderEnabled(RouteTypesV2.Provider provider, bool enabled) external onlyRole(ROUTING_MANAGER_ROLE) {
providerEnabled[uint8(provider)] = enabled;
emit ProviderToggled(provider, enabled);
}
function setRoutingConfig(uint256 sizeCategory, RouteTypesV2.Provider[] calldata providers)
external
onlyRole(ROUTING_MANAGER_ROLE)
{
require(sizeCategory < 3, "EnhancedSwapRouterV2: invalid size category");
require(providers.length > 0, "EnhancedSwapRouterV2: empty providers");
delete sizeBasedRouting[sizeCategory];
for (uint256 i = 0; i < providers.length; i++) {
sizeBasedRouting[sizeCategory].push(providers[i]);
}
emit RoutingConfigUpdated(sizeCategory, providers);
}
function executeRoute(RouteTypesV2.RoutePlan calldata plan)
external
payable
nonReentrant
returns (uint256 amountOut)
{
RouteTypesV2.RoutePlan memory memoryPlan = _copyPlan(plan);
return _executeRoute(memoryPlan, msg.sender, msg.value);
}
function quoteConfiguredProviders(address tokenIn, address tokenOut, uint256 amountIn)
external
view
returns (RouteTypesV2.ProviderQuote[] memory quotes)
{
RouteTypesV2.ProviderQuote[] memory temp =
new RouteTypesV2.ProviderQuote[](uint256(type(RouteTypesV2.Provider).max) + 1);
uint256 count = 0;
for (uint8 providerId = 0; providerId <= uint8(type(RouteTypesV2.Provider).max); providerId++) {
if (!providerEnabled[providerId]) {
continue;
}
address adapter = providerAdapters[providerId];
if (adapter == address(0)) {
continue;
}
ProviderRouteConfig storage routeConfig = providerRoutes[tokenIn][tokenOut][providerId];
if (!routeConfig.enabled || routeConfig.target == address(0)) {
continue;
}
RouteTypesV2.ProviderQuote memory quote = _quoteConfiguredProvider(
tokenIn, tokenOut, amountIn, RouteTypesV2.Provider(providerId), routeConfig, adapter
);
if (quote.executable) {
temp[count] = quote;
count++;
}
}
quotes = new RouteTypesV2.ProviderQuote[](count);
for (uint256 i = 0; i < count; i++) {
quotes[i] = temp[i];
}
}
function quoteConfiguredProvider(
address tokenIn,
address tokenOut,
uint256 amountIn,
RouteTypesV2.Provider provider
) external view returns (RouteTypesV2.ProviderQuote memory quote) {
uint8 providerId = uint8(provider);
if (!providerEnabled[providerId]) revert ProviderDisabled();
address adapter = providerAdapters[providerId];
if (adapter == address(0)) revert ProviderAdapterNotConfigured();
ProviderRouteConfig storage routeConfig = providerRoutes[tokenIn][tokenOut][providerId];
if (!routeConfig.enabled || routeConfig.target == address(0)) revert RouteNotConfigured();
return _quoteConfiguredProvider(tokenIn, tokenOut, amountIn, provider, routeConfig, adapter);
}
function _quoteConfiguredProvider(
address tokenIn,
address tokenOut,
uint256 amountIn,
RouteTypesV2.Provider provider,
ProviderRouteConfig storage routeConfig,
address adapter
) internal view returns (RouteTypesV2.ProviderQuote memory quote) {
RouteTypesV2.RouteLeg memory leg = RouteTypesV2.RouteLeg({
provider: provider,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountSource: RouteTypesV2.AmountSource.UserInput,
minAmountOut: 0,
target: routeConfig.target,
providerData: routeConfig.providerData
});
(bool ok,) = IRouteExecutorAdapter(adapter).validate(leg);
if (!ok) {
return RouteTypesV2.ProviderQuote({
provider: provider,
target: routeConfig.target,
amountOut: 0,
estimatedGas: 0,
executable: false,
providerData: routeConfig.providerData
});
}
(uint256 amountOut, uint256 estimatedGas) = IRouteExecutorAdapter(adapter).quote(leg, amountIn);
return RouteTypesV2.ProviderQuote({
provider: provider,
target: routeConfig.target,
amountOut: amountOut,
estimatedGas: estimatedGas,
executable: amountOut > 0,
providerData: routeConfig.providerData
});
}
function swapTokenToToken(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin)
external
returns (uint256 amountOut)
{
return swapTokenToToken(tokenIn, tokenOut, amountIn, amountOutMin, _defaultPreferredProvider());
}
function swapTokenToToken(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOutMin,
RouteTypesV2.Provider preferredProvider
) public returns (uint256 amountOut) {
if (amountIn == 0) revert ZeroAmount();
ProviderRouteConfig memory routeConfig = _getRouteConfig(tokenIn, tokenOut, preferredProvider);
RouteTypesV2.RouteLeg[] memory legs = new RouteTypesV2.RouteLeg[](1);
legs[0] = RouteTypesV2.RouteLeg({
provider: preferredProvider,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountSource: RouteTypesV2.AmountSource.UserInput,
minAmountOut: amountOutMin,
target: routeConfig.target,
providerData: routeConfig.providerData
});
RouteTypesV2.RoutePlan memory plan = RouteTypesV2.RoutePlan({
chainId: block.chainid,
inputToken: tokenIn,
outputToken: tokenOut,
amountIn: amountIn,
minAmountOut: amountOutMin,
recipient: msg.sender,
deadline: block.timestamp + 300,
legs: legs
});
return _executeRoute(plan, msg.sender, 0);
}
function swapToStablecoin(
LiquidityPoolETH.AssetType inputAsset,
address stablecoinToken,
uint256 amountIn,
uint256 amountOutMin
) external payable returns (uint256 amountOut) {
return swapToStablecoin(inputAsset, stablecoinToken, amountIn, amountOutMin, _defaultPreferredProvider());
}
function swapToStablecoin(
LiquidityPoolETH.AssetType inputAsset,
address stablecoinToken,
uint256 amountIn,
uint256 amountOutMin,
RouteTypesV2.Provider preferredProvider
) public payable returns (uint256 amountOut) {
if (amountIn == 0) revert ZeroAmount();
if (!_isValidStablecoin(stablecoinToken)) revert InvalidStablecoin();
address inputToken = inputAsset == LiquidityPoolETH.AssetType.ETH ? address(0) : weth;
ProviderRouteConfig memory routeConfig = _getRouteConfig(weth, stablecoinToken, preferredProvider);
RouteTypesV2.RouteLeg[] memory legs = new RouteTypesV2.RouteLeg[](1);
legs[0] = RouteTypesV2.RouteLeg({
provider: preferredProvider,
tokenIn: weth,
tokenOut: stablecoinToken,
amountSource: RouteTypesV2.AmountSource.UserInput,
minAmountOut: amountOutMin,
target: routeConfig.target,
providerData: routeConfig.providerData
});
RouteTypesV2.RoutePlan memory plan = RouteTypesV2.RoutePlan({
chainId: block.chainid,
inputToken: inputToken,
outputToken: stablecoinToken,
amountIn: amountIn,
minAmountOut: amountOutMin,
recipient: msg.sender,
deadline: block.timestamp + 300,
legs: legs
});
return _executeRoute(plan, msg.sender, msg.value);
}
function _executeRoute(RouteTypesV2.RoutePlan memory plan, address payer, uint256 nativeValue)
internal
returns (uint256 amountOut)
{
_validatePlan(plan);
address currentToken = plan.inputToken;
uint256 currentAmount = plan.amountIn;
if (currentToken == address(0)) {
require(nativeValue == currentAmount, "EnhancedSwapRouterV2: native amount mismatch");
IWETH(weth).deposit{value: currentAmount}();
currentToken = weth;
} else {
IERC20(currentToken).safeTransferFrom(payer, address(this), currentAmount);
}
for (uint256 i = 0; i < plan.legs.length; i++) {
RouteTypesV2.RouteLeg memory leg = plan.legs[i];
if (leg.tokenIn != currentToken) revert InvalidPlan();
if (i == 0 && leg.amountSource != RouteTypesV2.AmountSource.UserInput) revert InvalidPlan();
if (i > 0 && leg.amountSource != RouteTypesV2.AmountSource.PreviousLeg) revert InvalidPlan();
uint8 providerId = uint8(leg.provider);
if (!providerEnabled[providerId]) revert ProviderDisabled();
address adapter = providerAdapters[providerId];
if (adapter == address(0)) revert ProviderAdapterNotConfigured();
(bool ok, string memory reason) = IRouteExecutorAdapter(adapter).validate(leg);
if (!ok) revert RouteValidationFailed(reason);
IERC20(currentToken).safeTransfer(adapter, currentAmount);
uint256 balanceBefore = IERC20(leg.tokenOut).balanceOf(address(this));
uint256 reportedAmountOut = IRouteExecutorAdapter(adapter).execute(leg, currentAmount);
uint256 balanceAfter = IERC20(leg.tokenOut).balanceOf(address(this));
uint256 actualAmountOut = balanceAfter - balanceBefore;
if (actualAmountOut == 0) {
actualAmountOut = reportedAmountOut;
}
if (actualAmountOut < leg.minAmountOut) revert InsufficientOutput();
currentToken = leg.tokenOut;
currentAmount = actualAmountOut;
}
if (currentToken != plan.outputToken) revert InvalidPlan();
if (currentAmount < plan.minAmountOut) revert InsufficientOutput();
address recipient = plan.recipient == address(0) ? payer : plan.recipient;
IERC20(currentToken).safeTransfer(recipient, currentAmount);
emit RouteExecuted(
keccak256(abi.encode(plan.chainId, plan.inputToken, plan.outputToken, plan.amountIn, plan.deadline)),
payer,
recipient,
plan.inputToken == address(0) ? weth : plan.inputToken,
currentToken,
plan.amountIn,
currentAmount
);
return currentAmount;
}
function _getRouteConfig(address tokenIn, address tokenOut, RouteTypesV2.Provider preferredProvider)
internal
view
returns (ProviderRouteConfig memory)
{
RouteTypesV2.Provider[] memory providers = _getRoutingProviders(preferredProvider);
for (uint256 i = 0; i < providers.length; i++) {
uint8 providerId = uint8(providers[i]);
ProviderRouteConfig storage config = providerRoutes[tokenIn][tokenOut][providerId];
if (config.enabled && config.target != address(0) && providerEnabled[providerId]) {
return ProviderRouteConfig({
target: config.target, providerData: config.providerData, enabled: config.enabled
});
}
}
revert RouteNotConfigured();
}
function _getRoutingProviders(RouteTypesV2.Provider preferredProvider)
internal
view
returns (RouteTypesV2.Provider[] memory providers)
{
if (providerEnabled[uint8(preferredProvider)]) {
providers = new RouteTypesV2.Provider[](1);
providers[0] = preferredProvider;
return providers;
}
providers = sizeBasedRouting[0];
if (providers.length == 0) {
providers = new RouteTypesV2.Provider[](5);
providers[0] = RouteTypesV2.Provider.Dodo;
providers[1] = RouteTypesV2.Provider.UniswapV3;
providers[2] = RouteTypesV2.Provider.Balancer;
providers[3] = RouteTypesV2.Provider.Curve;
providers[4] = RouteTypesV2.Provider.OneInch;
}
}
function _validatePlan(RouteTypesV2.RoutePlan memory plan) internal view {
if (plan.amountIn == 0 || plan.legs.length == 0) revert InvalidPlan();
if (plan.outputToken == address(0)) revert InvalidPlan();
if (plan.chainId != block.chainid) revert InvalidPlan();
if (plan.deadline < block.timestamp) revert InvalidPlan();
}
function _initializeDefaultRouting() internal {
sizeBasedRouting[0].push(RouteTypesV2.Provider.Dodo);
sizeBasedRouting[0].push(RouteTypesV2.Provider.UniswapV3);
sizeBasedRouting[1].push(RouteTypesV2.Provider.Dodo);
sizeBasedRouting[1].push(RouteTypesV2.Provider.Balancer);
sizeBasedRouting[1].push(RouteTypesV2.Provider.UniswapV3);
sizeBasedRouting[2].push(RouteTypesV2.Provider.Dodo);
sizeBasedRouting[2].push(RouteTypesV2.Provider.Curve);
sizeBasedRouting[2].push(RouteTypesV2.Provider.Balancer);
}
function _copyPlan(RouteTypesV2.RoutePlan calldata plan)
internal
pure
returns (RouteTypesV2.RoutePlan memory copied)
{
copied.chainId = plan.chainId;
copied.inputToken = plan.inputToken;
copied.outputToken = plan.outputToken;
copied.amountIn = plan.amountIn;
copied.minAmountOut = plan.minAmountOut;
copied.recipient = plan.recipient;
copied.deadline = plan.deadline;
copied.legs = new RouteTypesV2.RouteLeg[](plan.legs.length);
for (uint256 i = 0; i < plan.legs.length; i++) {
copied.legs[i] = RouteTypesV2.RouteLeg({
provider: plan.legs[i].provider,
tokenIn: plan.legs[i].tokenIn,
tokenOut: plan.legs[i].tokenOut,
amountSource: plan.legs[i].amountSource,
minAmountOut: plan.legs[i].minAmountOut,
target: plan.legs[i].target,
providerData: plan.legs[i].providerData
});
}
}
function _defaultPreferredProvider() internal pure returns (RouteTypesV2.Provider) {
return RouteTypesV2.Provider.Dodo;
}
function _isValidStablecoin(address token) internal view returns (bool) {
return token == usdt || token == usdc || token == dai;
}
receive() external payable {}
}