Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: general curve meta adapter #61

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
186 changes: 152 additions & 34 deletions src/contracts/adapters/CurveMetaWithSwapperAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
// ╬╬╬╬╬╬╬ ╣╬╬╬╬╠╠╩ ╘╬╬╬╬╬╬╬ ╠╬╬╬╬╬╬╬ ╙╬╬╬╬╬╬╬╬
//

// Supports Curve MIM pool (manually enter base tokens)

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

Expand All @@ -39,62 +37,116 @@ interface ICurveSwapper128 {
contract CurveMetaWithSwapperAdapter is YakAdapter {
using SafeERC20 for IERC20;

address public immutable metaPool;
address public immutable basePool;
address public immutable swapper;
address public immutable metaTkn;
mapping(address => int128) public tokenIndex;
mapping(address => bool) public isPoolToken;
address[] public metaPools;

// These map from the metapool address to its data; this is preferable to using
// structs to represent the metapool data, as mappings within structs are limited.
mapping(address => address) public basePools;
mapping(address => address) public metaTkns;
mapping(address => mapping(address => int128)) public tokenIndices;
mapping(address => mapping(address => bool)) public isPoolToken;

constructor(
string memory _name,
uint256 _swapGasEstimate,
address _metaPool,
address _basePool,
address[] memory _metaPools,
address[] memory _basePools,
address _swapper
) YakAdapter(_name, _swapGasEstimate) {
metaTkn = setMetaTkn(_metaPool, _swapper);
metaPool = _metaPool;
basePool = _basePool;
swapper = _swapper;
_setUnderlyingTokens(_basePool, _swapper);
require(_metaPools.length == _basePools.length, "mismatch between meta and base pools");

for (uint8 i = 0; i < _metaPools.length; i++) {
metaPools.push(_metaPools[i]);
metaTkns[_metaPools[i]] = setMetaTkn(_metaPools[i], _swapper);
_setUnderlyingTokens(_metaPools[i], _basePools[i], _swapper);
}
}

// Mapping indicator which tokens are included in the pool
function _setUnderlyingTokens(address _basePool, address _swapper) internal {
function _setUnderlyingTokens(address _metaPool, address _basePool) internal {
return _setUnderlyingTokens(_metaPool, _basePool, swapper);
}

// this function overload is to avoid reading the immutable swapper variable during contract creation
function _setUnderlyingTokens(
address _metaPool,
address _basePool,
address _swapper
) internal {
for (uint256 i = 0; true; i++) {
try ICurve2(_basePool).underlying_coins(i) returns (address token) {
_setPoolTokenAllowance(token, _swapper);
isPoolToken[token] = true;
tokenIndex[token] = int128(int256(i)) + 1;
isPoolToken[_metaPool][token] = true;
tokenIndices[_metaPool][token] = int128(int256(i)) + 1;
} catch {
break;
}
}
}

function setMetaTkn(address _metaPool) internal returns (address _metaTkn) {
return setMetaTkn(_metaPool, swapper);
}

// this function overload is to avoid reading the immutable swapper variable during contract creation
function setMetaTkn(address _metaPool, address _swapper) internal returns (address _metaTkn) {
_metaTkn = ICurveMeta(_metaPool).coins(0);
_setPoolTokenAllowance(_metaTkn, _swapper);
isPoolToken[_metaTkn] = true;
tokenIndex[_metaTkn] = 0;
isPoolToken[_metaPool][_metaTkn] = true;
tokenIndices[_metaPool][_metaTkn] = 0;
}

function addPool(address _metaPool, address _basePool) public onlyMaintainer {
metaPools.push(_metaPool);
metaTkns[_metaPool] = setMetaTkn(_metaPool);
_setUnderlyingTokens(_metaPool, _basePool);
}

function _setPoolTokenAllowance(address _token, address _target) internal {
IERC20(_token).approve(_target, UINT_MAX);
}

function _query(
// combination of tkn0 and tkn1 is not necessarily unique, so return all meta pools with that combination
function _eligibleMetaPools(address _tkn0, address _tkn1) internal view returns (address[] memory) {
uint8 numEligiblePools;

for (uint8 i = 0; i < metaPools.length; i++) {
if (isPoolToken[metaPools[i]][_tkn0] && isPoolToken[metaPools[i]][_tkn1]) {
numEligiblePools++;
}
}

address[] memory eligiblePools = new address[](numEligiblePools);
uint8 currentEligiblePoolsIndex;
for (uint8 i = 0; i < metaPools.length; i++) {
if (isPoolToken[metaPools[i]][_tkn0] && isPoolToken[metaPools[i]][_tkn1]) {
eligiblePools[currentEligiblePoolsIndex] = metaPools[i];
currentEligiblePoolsIndex++;
}
}

return eligiblePools;
}

function _queryPool(
uint256 _amountIn,
address _tokenIn,
address _tokenOut
) internal view override returns (uint256) {
if (!validInputParams(_amountIn, _tokenIn, _tokenOut)) {
address _tokenOut,
address _metaPool
) internal view returns (uint256) {
if (!validInputParamsFromPool(_amountIn, _tokenIn, _tokenOut, _metaPool)) {
return 0;
}
try ICurveMeta(metaPool).get_dy_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
uint256 amountOut
) {

try
ICurveMeta(_metaPool).get_dy_underlying(
tokenIndices[_metaPool][_tokenIn],
tokenIndices[_metaPool][_tokenOut],
_amountIn
)
returns (uint256 amountOut) {
// `calc_token_amount` in base_pool is used in part of the query
// this method does account for deposit fee which causes discrepancy
// between the query result and the actual swap amount by few bps(0-3.2)
Expand All @@ -106,16 +158,36 @@ contract CurveMetaWithSwapperAdapter is YakAdapter {
}
}

function validInputParams(
function _queryMaxAmountOutFromPools(
uint256 _amountIn,
address _tokenIn,
address _tokenOut
) internal view returns (bool) {
return _amountIn != 0 && _tokenIn != _tokenOut && validPath(_tokenIn, _tokenOut);
address _tokenOut,
address[] memory _metaPools
) internal view returns (address maxAmountOutMetaPool, uint256 maxAmountOut) {
uint256 currentMaxAmountOut = 0;
address currentBestMetaPool;
for (uint8 i = 0; i < _metaPools.length; i++) {
uint256 amountOut = _queryPool(_amountIn, _tokenIn, _tokenOut, _metaPools[i]);
if (amountOut > currentMaxAmountOut) {
currentMaxAmountOut = amountOut;
currentBestMetaPool = _metaPools[i];
}
}

return (currentBestMetaPool, currentMaxAmountOut);
}

function validPath(address tkn0, address tkn1) internal view returns (bool) {
return (tkn0 == metaTkn && isPoolToken[tkn1]) || (tkn1 == metaTkn && isPoolToken[tkn0]);
function _query(
uint256 _amountIn,
address _tokenIn,
address _tokenOut
) internal view override returns (uint256) {
address[] memory eligibleMetaPools = _eligibleMetaPools(_tokenIn, _tokenOut);
if (!validInputParamsFromPools(_amountIn, _tokenIn, _tokenOut, eligibleMetaPools)) {
return 0;
}
(, uint256 amountOut) = _queryMaxAmountOutFromPools(_amountIn, _tokenIn, _tokenOut, eligibleMetaPools);
return amountOut;
}

function _swap(
Expand All @@ -125,15 +197,61 @@ contract CurveMetaWithSwapperAdapter is YakAdapter {
address _tokenOut,
address _to
) internal override {
address[] memory eligibleMetaPools = _eligibleMetaPools(_tokenIn, _tokenOut);
(address bestMetaPool, ) = _queryMaxAmountOutFromPools(_amountIn, _tokenIn, _tokenOut, eligibleMetaPools);

ICurveSwapper128(swapper).exchange_underlying(
metaPool,
tokenIndex[_tokenIn],
tokenIndex[_tokenOut],
bestMetaPool,
tokenIndices[bestMetaPool][_tokenIn],
tokenIndices[bestMetaPool][_tokenOut],
_amountIn,
0
);
uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
require(balThis >= _amountOut, "Insufficient amount-out");
_returnTo(_tokenOut, balThis, _to);
}

// validity checks

function validInputParamsFromPool(
uint256 _amountIn,
address _tokenIn,
address _tokenOut,
address _metaPool
) internal view returns (bool) {
return _amountIn != 0 && _tokenIn != _tokenOut && validPathFromPool(_tokenIn, _tokenOut, _metaPool);
}

function validPathFromPool(
address tkn0,
address tkn1,
address metaPool
) internal view returns (bool) {
return
(tkn0 == metaTkns[metaPool] && isPoolToken[metaPool][tkn1]) ||
(tkn1 == metaTkns[metaPool] && isPoolToken[metaPool][tkn0]);
}

function validInputParamsFromPools(
uint256 _amountIn,
address _tokenIn,
address _tokenOut,
address[] memory _metaPools
) internal view returns (bool) {
return _amountIn != 0 && _tokenIn != _tokenOut && validPathFromPools(_tokenIn, _tokenOut, _metaPools);
}

function validPathFromPools(
address tkn0,
address tkn1,
address[] memory eligibleMetaPools
) internal view returns (bool) {
for (uint8 i = 0; i < eligibleMetaPools.length; i++) {
if (validPathFromPool(tkn0, tkn1, eligibleMetaPools[i])) {
return true;
}
}
return false;
}
}
16 changes: 0 additions & 16 deletions src/deploy/avalanche/adapters/curve/deusdc.js

This file was deleted.

16 changes: 16 additions & 0 deletions src/deploy/avalanche/adapters/curve/meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { deployAdapter, addresses } = require("../../../utils");
const { CurveMim, CurveMore, CurveDeUSDC, CurveAave } = addresses.avalanche.curvelikePools;
const { curveMetaSwapper } = addresses.avalanche.other;

const networkName = "avalanche";
const tags = ["curve", "curveMim", "curveMore", "curveDeUSDC"];
const name = "CurveMetaAdapter";
const contractName = "CurveMetaWithSwapperAdapter";

const metaPools = [CurveMim, CurveMore, CurveDeUSDC];
const basePools = Array(3).fill(CurveAave);
const swapper = curveMetaSwapper;
const gasEstimate = 1_200_000;
const args = [name, gasEstimate, metaPools, basePools, swapper];

module.exports = deployAdapter(networkName, tags, name, contractName, args);
16 changes: 0 additions & 16 deletions src/deploy/avalanche/adapters/curve/mim.js

This file was deleted.

16 changes: 0 additions & 16 deletions src/deploy/avalanche/adapters/curve/more.js

This file was deleted.

Loading