-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: equalizer router adapter (#146)
- Loading branch information
Showing
7 changed files
with
521 additions
and
5 deletions.
There are no files selected for viewing
188 changes: 188 additions & 0 deletions
188
contracts/adapters/equalizer/EqualizerRouterAdapter.sol
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
// Gearbox Protocol. Generalized leverage for DeFi protocols | ||
// (c) Gearbox Foundation, 2023. | ||
pragma solidity ^0.8.17; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
|
||
import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; | ||
|
||
import {AbstractAdapter} from "../AbstractAdapter.sol"; | ||
import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol"; | ||
|
||
import {IEqualizerRouter, Route} from "../../integrations/equalizer/IEqualizerRouter.sol"; | ||
import { | ||
IEqualizerRouterAdapter, | ||
EqualizerPoolStatus, | ||
EqualizerPool | ||
} from "../../interfaces/equalizer/IEqualizerRouterAdapter.sol"; | ||
|
||
/// @title Equalizer Router adapter | ||
/// @notice Implements logic allowing CAs to perform swaps via Equalizer | ||
contract EqualizerRouterAdapter is AbstractAdapter, IEqualizerRouterAdapter { | ||
using EnumerableSet for EnumerableSet.Bytes32Set; | ||
|
||
AdapterType public constant override _gearboxAdapterType = AdapterType.EQUALIZER_ROUTER; | ||
uint16 public constant override _gearboxAdapterVersion = 3_00; | ||
|
||
/// @dev Mapping from hash(token0, token1, stable) to whether the pool can be traded through the adapter | ||
mapping(address => mapping(address => mapping(bool => bool))) internal _poolStatus; | ||
|
||
/// @dev Mapping from hash(token0, token1, stable) to respective tuple | ||
mapping(bytes32 => EqualizerPool) internal _hashToPool; | ||
|
||
/// @dev Set of hashes of (token0, token1, stable) for all supported pools | ||
EnumerableSet.Bytes32Set internal _supportedPoolHashes; | ||
|
||
/// @notice Constructor | ||
/// @param _creditManager Credit manager address | ||
/// @param _router Equalizer Router address | ||
constructor(address _creditManager, address _router) AbstractAdapter(_creditManager, _router) {} | ||
|
||
/// @notice Swap given amount of input token to output token | ||
/// @param amountIn Amount of input token to spend | ||
/// @param amountOutMin Minumum amount of output token to receive | ||
/// @param routes Array of Route structs representing a swap path, must have at most 3 elements | ||
/// @param deadline Maximum timestamp until which the transaction is valid | ||
/// @dev Parameter `to` is ignored since swap recipient can only be the credit account | ||
function swapExactTokensForTokens( | ||
uint256 amountIn, | ||
uint256 amountOutMin, | ||
Route[] calldata routes, | ||
address, | ||
uint256 deadline | ||
) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { | ||
address creditAccount = _creditAccount(); | ||
|
||
(bool valid, address tokenIn, address tokenOut) = _validatePath(routes); | ||
if (!valid) revert InvalidPathException(); | ||
|
||
(tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( | ||
tokenIn, | ||
tokenOut, | ||
abi.encodeCall( | ||
IEqualizerRouter.swapExactTokensForTokens, (amountIn, amountOutMin, routes, creditAccount, deadline) | ||
), | ||
false | ||
); | ||
} | ||
|
||
/// @notice Swap the entire balance of input token to output token, except the specified amount | ||
/// @param leftoverAmount Amount of tokenIn to keep on the account | ||
/// @param rateMinRAY Minimum exchange rate between input and output tokens, scaled by 1e27 | ||
/// @param routes Array of Route structs representing a swap path, must have at most 3 elements | ||
/// @param deadline Maximum timestamp until which the transaction is valid | ||
function swapDiffTokensForTokens( | ||
uint256 leftoverAmount, | ||
uint256 rateMinRAY, | ||
Route[] calldata routes, | ||
uint256 deadline | ||
) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { | ||
address creditAccount = _creditAccount(); | ||
|
||
address tokenIn; | ||
address tokenOut; | ||
|
||
{ | ||
bool valid; | ||
(valid, tokenIn, tokenOut) = _validatePath(routes); | ||
if (!valid) revert InvalidPathException(); | ||
} | ||
|
||
uint256 amount = IERC20(tokenIn).balanceOf(creditAccount); | ||
if (amount <= leftoverAmount) return (0, 0); | ||
|
||
unchecked { | ||
amount -= leftoverAmount; | ||
} | ||
|
||
(tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( | ||
tokenIn, | ||
tokenOut, | ||
abi.encodeCall( | ||
IEqualizerRouter.swapExactTokensForTokens, | ||
(amount, (amount * rateMinRAY) / RAY, routes, creditAccount, deadline) | ||
), | ||
leftoverAmount <= 1 | ||
); | ||
} | ||
|
||
// ------------- // | ||
// CONFIGURATION // | ||
// ------------- // | ||
|
||
/// @notice Returns whether the (token0, token1) pair is allowed to be traded through the adapter | ||
function isPoolAllowed(address token0, address token1, bool stable) public view override returns (bool) { | ||
(token0, token1) = _sortTokens(token0, token1); | ||
return _poolStatus[token0][token1][stable]; | ||
} | ||
|
||
function supportedPools() public view returns (EqualizerPool[] memory pools) { | ||
bytes32[] memory poolHashes = _supportedPoolHashes.values(); | ||
uint256 len = poolHashes.length; | ||
pools = new EqualizerPool[](len); | ||
for (uint256 i = 0; i < len; ++i) { | ||
pools[i] = _hashToPool[poolHashes[i]]; | ||
} | ||
} | ||
|
||
/// @notice Sets status for a batch of pools | ||
/// @param pools Array of `EqualizerPoolStatus` objects | ||
function setPoolStatusBatch(EqualizerPoolStatus[] calldata pools) external override configuratorOnly { | ||
uint256 len = pools.length; | ||
unchecked { | ||
for (uint256 i; i < len; ++i) { | ||
(address token0, address token1) = _sortTokens(pools[i].token0, pools[i].token1); | ||
_poolStatus[token0][token1][pools[i].stable] = pools[i].allowed; | ||
|
||
bytes32 poolHash = keccak256(abi.encode(token0, token1, pools[i].stable)); | ||
if (pools[i].allowed) { | ||
_supportedPoolHashes.add(poolHash); | ||
_hashToPool[poolHash] = EqualizerPool({token0: token0, token1: token1, stable: pools[i].stable}); | ||
} else { | ||
_supportedPoolHashes.remove(poolHash); | ||
delete _hashToPool[poolHash]; | ||
} | ||
|
||
emit SetPoolStatus(token0, token1, pools[i].stable, pools[i].allowed); | ||
} | ||
} | ||
} | ||
|
||
// ------- // | ||
// HELPERS // | ||
// ------- // | ||
|
||
/// @dev Performs sanity check on a swap path, if path is valid also returns input and output tokens | ||
/// - Path length must be no more than 4 (i.e., at most 3 hops) | ||
/// - Each swap must be through an allowed pool | ||
function _validatePath(Route[] memory routes) | ||
internal | ||
view | ||
returns (bool valid, address tokenIn, address tokenOut) | ||
{ | ||
uint256 len = routes.length; | ||
if (len < 1 || len > 3) return (false, tokenIn, tokenOut); | ||
|
||
tokenIn = routes[0].from; | ||
tokenOut = routes[len - 1].to; | ||
valid = isPoolAllowed(routes[0].from, routes[0].to, routes[0].stable); | ||
if (valid && len > 1) { | ||
valid = isPoolAllowed(routes[1].from, routes[1].to, routes[1].stable) && (routes[0].to == routes[1].from); | ||
if (valid && len > 2) { | ||
valid = | ||
isPoolAllowed(routes[2].from, routes[2].to, routes[2].stable) && (routes[1].to == routes[2].from); | ||
} | ||
} | ||
} | ||
|
||
/// @dev Sorts two token addresses | ||
function _sortTokens(address token0, address token1) internal pure returns (address, address) { | ||
if (uint160(token0) < uint160(token1)) { | ||
return (token0, token1); | ||
} else { | ||
return (token1, token0); | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.17; | ||
|
||
struct Route { | ||
address from; | ||
address to; | ||
bool stable; | ||
} | ||
|
||
interface IEqualizerRouter { | ||
/// @notice Address of the factory | ||
function factory() external view returns (address); | ||
|
||
/// @notice Perform chained getAmountOut calculations on any number of pools | ||
function getAmountsOut(uint256 amountIn, Route[] memory routes) external view returns (uint256[] memory amounts); | ||
|
||
/// @notice Swap one token for another | ||
/// @param amountIn Amount of token in | ||
/// @param amountOutMin Minimum amount of desired token received | ||
/// @param routes Array of trade routes used in the swap | ||
/// @param to Recipient of the tokens received | ||
/// @param deadline Deadline to receive tokens | ||
/// @return amounts Array of amounts returned per route | ||
function swapExactTokensForTokens( | ||
uint256 amountIn, | ||
uint256 amountOutMin, | ||
Route[] calldata routes, | ||
address to, | ||
uint256 deadline | ||
) external returns (uint256[] memory amounts); | ||
} |
58 changes: 58 additions & 0 deletions
58
contracts/interfaces/equalizer/IEqualizerRouterAdapter.sol
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Gearbox Protocol. Generalized leverage for DeFi protocols | ||
// (c) Gearbox Foundation, 2023. | ||
pragma solidity ^0.8.17; | ||
|
||
import {IAdapter} from "@gearbox-protocol/core-v2/contracts/interfaces/IAdapter.sol"; | ||
import {Route} from "../../integrations/equalizer/IEqualizerRouter.sol"; | ||
|
||
struct EqualizerPool { | ||
address token0; | ||
address token1; | ||
bool stable; | ||
} | ||
|
||
struct EqualizerPoolStatus { | ||
address token0; | ||
address token1; | ||
bool stable; | ||
bool allowed; | ||
} | ||
|
||
interface IEqualizerRouterAdapterEvents { | ||
/// @notice Emited when new status is set for a pair | ||
event SetPoolStatus(address indexed token0, address indexed token1, bool stable, bool allowed); | ||
} | ||
|
||
interface IEqualizerRouterAdapterExceptions { | ||
/// @notice Thrown when sanity checks on a swap path fail | ||
error InvalidPathException(); | ||
} | ||
|
||
/// @title Equalizer Router adapter interface | ||
interface IEqualizerRouterAdapter is IAdapter, IEqualizerRouterAdapterEvents, IEqualizerRouterAdapterExceptions { | ||
function swapExactTokensForTokens( | ||
uint256 amountIn, | ||
uint256 amountOutMin, | ||
Route[] calldata routes, | ||
address, | ||
uint256 deadline | ||
) external returns (uint256 tokensToEnable, uint256 tokensToDisable); | ||
|
||
function swapDiffTokensForTokens( | ||
uint256 leftoverAmount, | ||
uint256 rateMinRAY, | ||
Route[] calldata routes, | ||
uint256 deadline | ||
) external returns (uint256 tokensToEnable, uint256 tokensToDisable); | ||
|
||
// ------------- // | ||
// CONFIGURATION // | ||
// ------------- // | ||
|
||
function isPoolAllowed(address token0, address token1, bool stable) external view returns (bool); | ||
|
||
function setPoolStatusBatch(EqualizerPoolStatus[] calldata pools) external; | ||
|
||
function supportedPools() external view returns (EqualizerPool[] memory pools); | ||
} |
18 changes: 18 additions & 0 deletions
18
contracts/test/unit/adapters/equalizer/EqualizerRouterAdapter.harness.sol
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
// Gearbox Protocol. Generalized leverage for DeFi protocols | ||
// (c) Gearbox Foundation, 2023. | ||
pragma solidity ^0.8.17; | ||
|
||
import {EqualizerRouterAdapter, Route} from "../../../../adapters/equalizer/EqualizerRouterAdapter.sol"; | ||
|
||
contract EqualizerRouterAdapterHarness is EqualizerRouterAdapter { | ||
constructor(address creditManager, address router) EqualizerRouterAdapter(creditManager, router) {} | ||
|
||
function validatePath(Route[] memory routes) | ||
external | ||
view | ||
returns (bool valid, address tokenIn, address tokenOut) | ||
{ | ||
return _validatePath(routes); | ||
} | ||
} |
Oops, something went wrong.