diff --git a/src/contracts/adapters/CurveMetaWithSwapperAdapter.sol b/src/contracts/adapters/CurveMetaWithSwapperAdapter.sol index 504c8203..401461a8 100644 --- a/src/contracts/adapters/CurveMetaWithSwapperAdapter.sol +++ b/src/contracts/adapters/CurveMetaWithSwapperAdapter.sol @@ -15,8 +15,6 @@ // ╬╬╬╬╬╬╬ ╣╬╬╬╬╠╠╩ ╘╬╬╬╬╬╬╬ ╠╬╬╬╬╬╬╬ ╙╬╬╬╬╬╬╬╬ // -// Supports Curve MIM pool (manually enter base tokens) - // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.0; @@ -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) @@ -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( @@ -125,10 +197,13 @@ 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 ); @@ -136,4 +211,47 @@ contract CurveMetaWithSwapperAdapter is YakAdapter { 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; + } } diff --git a/src/deploy/avalanche/adapters/curve/deusdc.js b/src/deploy/avalanche/adapters/curve/deusdc.js deleted file mode 100755 index f3755901..00000000 --- a/src/deploy/avalanche/adapters/curve/deusdc.js +++ /dev/null @@ -1,16 +0,0 @@ -const { deployAdapter, addresses } = require('../../../utils') -const { CurveDeUSDC, CurveAave } = addresses.avalanche.curvelikePools -const { curveMetaSwapper } = addresses.avalanche.other - -const networkName = 'avalanche' -const tags = [ 'curve', 'curveDeUSDC' ] -const name = 'CurveDeUSDCAdapter' -const contractName = 'CurveMetaWithSwapperAdapter' - -const metaPool = CurveDeUSDC -const basePool = CurveAave -const swapper = curveMetaSwapper -const gasEstimate = 1_100_000 -const args = [ name, gasEstimate, metaPool, basePool, swapper ] - -module.exports = deployAdapter(networkName, tags, name, contractName, args) \ No newline at end of file diff --git a/src/deploy/avalanche/adapters/curve/meta.js b/src/deploy/avalanche/adapters/curve/meta.js new file mode 100644 index 00000000..126207b5 --- /dev/null +++ b/src/deploy/avalanche/adapters/curve/meta.js @@ -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); diff --git a/src/deploy/avalanche/adapters/curve/mim.js b/src/deploy/avalanche/adapters/curve/mim.js deleted file mode 100755 index 4acb0ab7..00000000 --- a/src/deploy/avalanche/adapters/curve/mim.js +++ /dev/null @@ -1,16 +0,0 @@ -const { deployAdapter, addresses } = require('../../../utils') -const { CurveMim, CurveAave } = addresses.avalanche.curvelikePools -const { curveMetaSwapper } = addresses.avalanche.other - -const networkName = 'avalanche' -const tags = [ 'curve', 'curveMim' ] -const name = 'CurveMimAdapter' -const contractName = 'CurveMetaWithSwapperAdapter' - -const metaPool = CurveMim -const basePool = CurveAave -const swapper = curveMetaSwapper -const gasEstimate = 1_100_000 -const args = [ name, gasEstimate, metaPool, basePool, swapper ] - -module.exports = deployAdapter(networkName, tags, name, contractName, args) \ No newline at end of file diff --git a/src/deploy/avalanche/adapters/curve/more.js b/src/deploy/avalanche/adapters/curve/more.js deleted file mode 100755 index f1854a9f..00000000 --- a/src/deploy/avalanche/adapters/curve/more.js +++ /dev/null @@ -1,16 +0,0 @@ -const { deployAdapter, addresses } = require('../../../utils') -const { CurveMore, CurveAave } = addresses.avalanche.curvelikePools -const { curveMetaSwapper } = addresses.avalanche.other - -const networkName = 'avalanche' -const tags = [ 'curve', 'curveMore' ] -const name = 'CurveMoreAdapter' -const contractName = 'CurveMetaWithSwapperAdapter' - -const metaPool = CurveMore -const basePool = CurveAave -const swapper = curveMetaSwapper -const gasEstimate = 1_100_000 -const args = [ name, gasEstimate, metaPool, basePool, swapper ] - -module.exports = deployAdapter(networkName, tags, name, contractName, args) \ No newline at end of file diff --git a/src/test/spec/avalanche/adapters/curve.spec.js b/src/test/spec/avalanche/adapters/curve.spec.js index 27db6023..2d094fdb 100644 --- a/src/test/spec/avalanche/adapters/curve.spec.js +++ b/src/test/spec/avalanche/adapters/curve.spec.js @@ -1,650 +1,464 @@ -const { expect } = require('chai') -const { setTestEnv, addresses } = require('../../../utils/test-env') -const { curvelikePools } = addresses.avalanche +const { expect } = require("chai"); +const { setTestEnv, addresses } = require("../../../utils/test-env"); +const { curvelikePools } = addresses.avalanche; +describe("YakAdapter - Curve", () => { + const MaxDustWei = ethers.utils.parseUnits("1", "wei"); -describe('YakAdapter - Curve', () => { + let testEnv; + let tkns; + let ate; // adapter-test-env - const MaxDustWei = ethers.utils.parseUnits('1', 'wei') - - let testEnv - let tkns - let ate // adapter-test-env + before(async () => { + const networkName = "avalanche"; + const forkBlockNumber = 19595355; + testEnv = await setTestEnv(networkName, forkBlockNumber); + tkns = testEnv.supportedTkns; + }); + beforeEach(async () => { + testEnv.updateTrader(); + }); + + describe("aave", () => { + const MaxErrBps = 1; + + before(async () => { + const contractName = "Curve2Adapter"; + const adapterArgs = ["CurveAaveAdapter", curvelikePools.CurveAave, 770_000]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 1 bps error", async () => { + it("10000 USDCe -> DAIe", async () => { + await ate.checkSwapMatchesQueryWithErr("10000", tkns.USDCe, tkns.DAIe, MaxErrBps); + }); + it("22222 USDTe -> DAIe", async () => { + await ate.checkSwapMatchesQueryWithErr("22222", tkns.USDTe, tkns.DAIe, MaxErrBps); + }); + it("22222 USDCe -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("22222", tkns.USDCe, tkns.USDTe, MaxDustWei, MaxErrBps); + }); + it("22222 USDTe -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("22222", tkns.USDTe, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + it("3333 DAIe -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("3333", tkns.DAIe, tkns.USDTe, MaxDustWei, MaxErrBps); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.MIM; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.USDCe, tkns.DAIe], + ["1", tkns.DAIe, tkns.USDTe], + ["1", tkns.USDTe, tkns.USDCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("atricrypto", () => { + const MaxErrBps = 5; + + before(async () => { + const contractName = "Curve1Adapter"; + const adapterArgs = ["CurveAtricrypto", curvelikePools.CurveAtricrypto, 1_500_000]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 5 bps err", async () => { + it("100 WBTCe -> DAIe", async () => { + await ate.checkSwapMatchesQueryWithErr("1", tkns.WBTCe, tkns.DAIe, MaxErrBps); + }); + it("32 WETHe -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithErr("32", tkns.WETHe, tkns.USDCe, MaxErrBps); + }); + it("22222 USDCe -> WBTCe", async () => { + await ate.checkSwapMatchesQueryWithErr("22222", tkns.USDCe, tkns.WBTCe, MaxErrBps); + }); + it("22222 DAIe -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithErr("22222", tkns.DAIe, tkns.USDTe, MaxErrBps); + }); + it("3333 USDTe -> WETHe", async () => { + await ate.checkSwapMatchesQueryWithErr("3333", tkns.USDTe, tkns.WETHe, MaxErrBps); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.WETHe; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.USDCe, tkns.DAIe], + ["1", tkns.DAIe, tkns.WETHe], + ["1", tkns.USDTe, tkns.WBTCe], + ["1", tkns.WETHe, tkns.USDTe], + ["1", tkns.WBTCe, tkns.USDCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("ren", () => { + const MaxErrBps = 1; + + before(async () => { + const contractName = "Curve2Adapter"; + const adapterArgs = ["CurveRenAdapter", curvelikePools.CurveRen, 530_000]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 1 bps err", async () => { + it("20 WBTCe -> renBTC", async () => { + await ate.checkSwapMatchesQueryWithErr("20", tkns.WBTCe, tkns.renBTC, MaxErrBps); + }); + it("2 renBTC -> WBTCe", async () => { + await ate.checkSwapMatchesQueryWithErr("2", tkns.renBTC, tkns.WBTCe, MaxErrBps); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.WBTCe; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.WBTCe, tkns.renBTC], + ["1", tkns.renBTC, tkns.WBTCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("3poolV2", () => { + before(async () => { + const contractName = "CurvePlain128Adapter"; + const adapterArgs = ["Curve3poolV2Adapter", curvelikePools.Curve3poolV2, 250_000]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query", async () => { + it("20 MIM -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDust("20", tkns.MIM, tkns.USDCe, MaxDustWei); + }); + it("2333 USDCe -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithDust("2333", tkns.USDCe, tkns.USDTe, MaxDustWei); + }); + it("233 USDTe -> MIM", async () => { + await ate.checkSwapMatchesQueryWithDust("233", tkns.USDTe, tkns.MIM, MaxDustWei); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.MIM; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.MIM, tkns.USDTe], + ["1", tkns.USDCe, tkns.MIM], + ["1", tkns.USDTe, tkns.USDCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("yusd", () => { + before(async () => { + const contractName = "CurvePlain128Adapter"; + const adapterArgs = ["CurveYUSDAdapter", curvelikePools.CurveYUSD, 280_000]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query", async () => { + it("20 YUSD -> USDC", async () => { + await ate.checkSwapMatchesQueryWithDust("20", tkns.YUSD, tkns.USDC, MaxDustWei); + }); + it("2333 USDC -> USDt", async () => { + await ate.checkSwapMatchesQueryWithDust("2333", tkns.USDC, tkns.USDt, MaxDustWei); + }); + it("233 USDt -> YUSD", async () => { + await ate.checkSwapMatchesQueryWithDust("233", tkns.USDt, tkns.YUSD, MaxDustWei); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.USDt; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.YUSD, tkns.USDt], + ["1", tkns.USDC, tkns.YUSD], + ["1", tkns.USDt, tkns.USDC], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("usdc", () => { before(async () => { - const networkName = 'avalanche' - const forkBlockNumber = 19595355 - testEnv = await setTestEnv(networkName, forkBlockNumber) - tkns = testEnv.supportedTkns - }) - - beforeEach(async () => { - testEnv.updateTrader() - }) - - describe('aave', () => { - - const MaxErrBps = 1 - - before(async () => { - const contractName = 'Curve2Adapter' - const adapterArgs = [ - 'CurveAaveAdapter', - curvelikePools.CurveAave, - 770_000 - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query :: 1 bps error', async () => { - - it('10000 USDCe -> DAIe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '10000', - tkns.USDCe, - tkns.DAIe, - MaxErrBps - ) - }) - it('22222 USDTe -> DAIe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '22222', - tkns.USDTe, - tkns.DAIe, - MaxErrBps - ) - }) - it('22222 USDCe -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '22222', - tkns.USDCe, - tkns.USDTe, - MaxDustWei, - MaxErrBps - ) - }) - it('22222 USDTe -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '22222', - tkns.USDTe, - tkns.USDCe, - MaxDustWei, - MaxErrBps - ) - }) - it('3333 DAIe -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '3333', - tkns.DAIe, - tkns.USDTe, - MaxDustWei, - MaxErrBps - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.MIM - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.USDCe, tkns.DAIe ], - [ '1', tkns.DAIe, tkns.USDTe ], - [ '1', tkns.USDTe, tkns.USDCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('atricrypto', () => { - - const MaxErrBps = 5 - - before(async () => { - const contractName = 'Curve1Adapter' - const adapterArgs = [ - 'CurveAtricrypto', - curvelikePools.CurveAtricrypto, - 1_500_000 - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query :: 5 bps err', async () => { - - it('100 WBTCe -> DAIe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '1', - tkns.WBTCe, - tkns.DAIe, - MaxErrBps - ) - }) - it('32 WETHe -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '32', - tkns.WETHe, - tkns.USDCe, - MaxErrBps - ) - }) - it('22222 USDCe -> WBTCe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '22222', - tkns.USDCe, - tkns.WBTCe, - MaxErrBps - ) - }) - it('22222 DAIe -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '22222', - tkns.DAIe, - tkns.USDTe, - MaxErrBps - ) - }) - it('3333 USDTe -> WETHe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '3333', - tkns.USDTe, - tkns.WETHe, - MaxErrBps - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.WETHe - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.USDCe, tkns.DAIe ], - [ '1', tkns.DAIe, tkns.WETHe ], - [ '1', tkns.USDTe, tkns.WBTCe ], - [ '1', tkns.WETHe, tkns.USDTe ], - [ '1', tkns.WBTCe, tkns.USDCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('ren', () => { - - const MaxErrBps = 1 - - before(async () => { - const contractName = 'Curve2Adapter' - const adapterArgs = [ - 'CurveRenAdapter', - curvelikePools.CurveRen, - 530_000 - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query :: 1 bps err', async () => { - - it('20 WBTCe -> renBTC', async () => { - await ate.checkSwapMatchesQueryWithErr( - '20', - tkns.WBTCe, - tkns.renBTC, - MaxErrBps - ) - }) - it('2 renBTC -> WBTCe', async () => { - await ate.checkSwapMatchesQueryWithErr( - '2', - tkns.renBTC, - tkns.WBTCe, - MaxErrBps - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.WBTCe - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.WBTCe, tkns.renBTC ], - [ '1', tkns.renBTC, tkns.WBTCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('3poolV2', () => { - - before(async () => { - const contractName = 'CurvePlain128Adapter' - const adapterArgs = [ - 'Curve3poolV2Adapter', - curvelikePools.Curve3poolV2, - 250_000 - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query', async () => { - - it('20 MIM -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithDust( - '20', - tkns.MIM, - tkns.USDCe, - MaxDustWei - ) - }) - it('2333 USDCe -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithDust( - '2333', - tkns.USDCe, - tkns.USDTe, - MaxDustWei - ) - }) - it('233 USDTe -> MIM', async () => { - await ate.checkSwapMatchesQueryWithDust( - '233', - tkns.USDTe, - tkns.MIM, - MaxDustWei - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.MIM - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.MIM, tkns.USDTe ], - [ '1', tkns.USDCe, tkns.MIM ], - [ '1', tkns.USDTe, tkns.USDCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('yusd', () => { - - before(async () => { - const contractName = 'CurvePlain128Adapter' - const adapterArgs = [ - 'CurveYUSDAdapter', - curvelikePools.CurveYUSD, - 280_000 - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query', async () => { - - it('20 YUSD -> USDC', async () => { - await ate.checkSwapMatchesQueryWithDust( - '20', - tkns.YUSD, - tkns.USDC, - MaxDustWei - ) - }) - it('2333 USDC -> USDt', async () => { - await ate.checkSwapMatchesQueryWithDust( - '2333', - tkns.USDC, - tkns.USDt, - MaxDustWei - ) - }) - it('233 USDt -> YUSD', async () => { - await ate.checkSwapMatchesQueryWithDust( - '233', - tkns.USDt, - tkns.YUSD, - MaxDustWei - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.USDt - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.YUSD, tkns.USDt ], - [ '1', tkns.USDC, tkns.YUSD ], - [ '1', tkns.USDt, tkns.USDC ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('usdc', () => { - - before(async () => { - const contractName = 'CurvePlain128Adapter' - const adapterArgs = [ - 'CurveUSDCAdapter', - curvelikePools.CurveUSDC, - 280_000 - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query', async () => { - - it('20 USDCe -> USDC', async () => { - await ate.checkSwapMatchesQueryWithDust( - '20', - tkns.USDCe, - tkns.USDC, - MaxDustWei - ) - }) - it('2333 USDC -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithDust( - '2333', - tkns.USDC, - tkns.USDCe, - MaxDustWei - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.USDt - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.USDC, tkns.USDCe ], - [ '1', tkns.USDCe, tkns.USDC ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('mim', () => { - - const MaxErrBps = 5 - - before(async () => { - const contractName = 'CurveMetaWithSwapperAdapter' - const adapterArgs = [ - 'CurveMimAdapter', - 1_100_000, - curvelikePools.CurveMim, - curvelikePools.CurveAave, - addresses.avalanche.other.curveMetaSwapper, - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query :: 5 bps error', async () => { - - it('44444 MIM -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '44444', - tkns.MIM, - tkns.USDCe, - MaxDustWei, - MaxErrBps, - ) - }) - - it('1 USDCe -> MIM', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '1', - tkns.USDCe, - tkns.MIM, - MaxDustWei, - MaxErrBps, - ) - }) - - it('3134 MIM -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '3134', - tkns.MIM, - tkns.USDTe, - MaxDustWei, - MaxErrBps, - ) - }) - - it('20 USDTe -> MIM', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '20', - tkns.USDTe, - tkns.MIM, - MaxDustWei, - MaxErrBps, - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.MIM - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Query returns zero if both in&out token are underlying', async () => { - const dy = await ate.query( - ethers.utils.parseUnits('1', 6), - tkns.USDTe.address, - tkns.USDCe.address, - ) - expect(dy).to.eq(0) - - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.MIM, tkns.USDTe ], - [ '1', tkns.USDCe, tkns.MIM ], - [ '1', tkns.MIM, tkns.USDCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('more', () => { - - const MaxErrBps = 5 - - before(async () => { - const contractName = 'CurveMetaWithSwapperAdapter' - const adapterArgs = [ - 'CurveMoreAdapter', - 1_100_000, - curvelikePools.CurveMore, - curvelikePools.CurveAave, - addresses.avalanche.other.curveMetaSwapper, - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query :: 5 bps error', async () => { - - it('44444 MONEY -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '44444', - tkns.MONEY, - tkns.USDCe, - MaxDustWei, - MaxErrBps, - ) - }) - - it('1 USDCe -> MONEY', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '1', - tkns.USDCe, - tkns.MONEY, - MaxDustWei, - MaxErrBps, - ) - }) - - it('3134 MONEY -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '3134', - tkns.MONEY, - tkns.USDTe, - MaxDustWei, - MaxErrBps, - ) - }) - - it('20 USDTe -> MONEY', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '20', - tkns.USDTe, - tkns.MONEY, - MaxDustWei, - MaxErrBps, - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.MONEY - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Query returns zero if both in&out token are underlying', async () => { - const dy = await ate.query( - ethers.utils.parseUnits('1', 6), - tkns.USDTe.address, - tkns.USDCe.address, - ) - expect(dy).to.eq(0) - - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.MONEY, tkns.USDTe ], - [ '1', tkns.USDCe, tkns.MONEY ], - [ '1', tkns.MONEY, tkns.USDCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - - describe('deusdc', () => { - - const MaxErrBps = 5 - - before(async () => { - const contractName = 'CurveMetaWithSwapperAdapter' - const adapterArgs = [ - 'CurveDeusdcAdapter', - 1_100_000, - curvelikePools.CurveDeUSDC, - curvelikePools.CurveAave, - addresses.avalanche.other.curveMetaSwapper, - ] - ate = await testEnv.setAdapterEnv(contractName, adapterArgs) - }) - - describe('Swapping matches query :: 5 bps error', async () => { - - it('44444 deUSDC -> USDCe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '44444', - tkns.deUSDC, - tkns.USDCe, - MaxDustWei, - MaxErrBps, - ) - }) - - it('1 USDCe -> deUSDC', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '1', - tkns.USDCe, - tkns.deUSDC, - MaxDustWei, - MaxErrBps, - ) - }) - - it('3134 deUSDC -> USDTe', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '3134', - tkns.deUSDC, - tkns.USDTe, - MaxDustWei, - MaxErrBps, - ) - }) - - it('20 USDTe -> deUSDC', async () => { - await ate.checkSwapMatchesQueryWithDustWithErr( - '20', - tkns.USDTe, - tkns.deUSDC, - MaxDustWei, - MaxErrBps, - ) - }) - - }) - - it('Query returns zero if tokens not found', async () => { - const supportedTkn = tkns.deUSDC - ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn) - }) - - it('Query returns zero if both in&out token are underlying', async () => { - const dy = await ate.query( - ethers.utils.parseUnits('1', 6), - tkns.USDTe.address, - tkns.USDCe.address, - ) - expect(dy).to.eq(0) - - }) - - it('Gas-estimate is between max-gas-used and 110% max-gas-used', async () => { - const options = [ - [ '1', tkns.deUSDC, tkns.USDTe ], - [ '1', tkns.USDCe, tkns.deUSDC ], - [ '1', tkns.deUSDC, tkns.USDCe ], - ] - await ate.checkGasEstimateIsSensible(options) - }) - - }) - -}) \ No newline at end of file + const contractName = "CurvePlain128Adapter"; + const adapterArgs = ["CurveUSDCAdapter", curvelikePools.CurveUSDC, 280_000]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query", async () => { + it("20 USDCe -> USDC", async () => { + await ate.checkSwapMatchesQueryWithDust("20", tkns.USDCe, tkns.USDC, MaxDustWei); + }); + it("2333 USDC -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDust("2333", tkns.USDC, tkns.USDCe, MaxDustWei); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.USDt; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.USDC, tkns.USDCe], + ["1", tkns.USDCe, tkns.USDC], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("CurveMetaWithSwapperAdapter", () => { + describe("mim", () => { + const MaxErrBps = 5; + + before(async () => { + const contractName = "CurveMetaWithSwapperAdapter"; + const adapterArgs = [ + "CurveMimAdapter", + 1_200_000, + [curvelikePools.CurveMim], + [curvelikePools.CurveAave], + addresses.avalanche.other.curveMetaSwapper, + ]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 5 bps error", async () => { + it("44444 MIM -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.MIM, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("1 USDCe -> MIM", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("1", tkns.USDCe, tkns.MIM, MaxDustWei, MaxErrBps); + }); + + it("3134 MIM -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("3134", tkns.MIM, tkns.USDTe, MaxDustWei, MaxErrBps); + }); + + it("20 USDTe -> MIM", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("20", tkns.USDTe, tkns.MIM, MaxDustWei, MaxErrBps); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.MIM; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Query returns zero if both in&out token are underlying", async () => { + const dy = await ate.query(ethers.utils.parseUnits("1", 6), tkns.USDTe.address, tkns.USDCe.address); + expect(dy).to.eq(0); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.MIM, tkns.USDTe], + ["1", tkns.USDCe, tkns.MIM], + ["1", tkns.MIM, tkns.USDCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("more", () => { + const MaxErrBps = 5; + + before(async () => { + const contractName = "CurveMetaWithSwapperAdapter"; + const adapterArgs = [ + "CurveMoreAdapter", + 1_200_000, + [curvelikePools.CurveMore], + [curvelikePools.CurveAave], + addresses.avalanche.other.curveMetaSwapper, + ]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 5 bps error", async () => { + it("44444 MONEY -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.MONEY, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("1 USDCe -> MONEY", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("1", tkns.USDCe, tkns.MONEY, MaxDustWei, MaxErrBps); + }); + + it("3134 MONEY -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("3134", tkns.MONEY, tkns.USDTe, MaxDustWei, MaxErrBps); + }); + + it("20 USDTe -> MONEY", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("20", tkns.USDTe, tkns.MONEY, MaxDustWei, MaxErrBps); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.MONEY; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Query returns zero if both in&out token are underlying", async () => { + const dy = await ate.query(ethers.utils.parseUnits("1", 6), tkns.USDTe.address, tkns.USDCe.address); + expect(dy).to.eq(0); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.MONEY, tkns.USDTe], + ["1", tkns.USDCe, tkns.MONEY], + ["1", tkns.MONEY, tkns.USDCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + + describe("deusdc", () => { + const MaxErrBps = 5; + + before(async () => { + const contractName = "CurveMetaWithSwapperAdapter"; + const adapterArgs = [ + "CurveDeusdcAdapter", + 1_200_000, + [curvelikePools.CurveDeUSDC], + [curvelikePools.CurveAave], + addresses.avalanche.other.curveMetaSwapper, + ]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 5 bps error", async () => { + it("44444 deUSDC -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.deUSDC, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("1 USDCe -> deUSDC", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("1", tkns.USDCe, tkns.deUSDC, MaxDustWei, MaxErrBps); + }); + + it("3134 deUSDC -> USDTe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("3134", tkns.deUSDC, tkns.USDTe, MaxDustWei, MaxErrBps); + }); + + it("20 USDTe -> deUSDC", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("20", tkns.USDTe, tkns.deUSDC, MaxDustWei, MaxErrBps); + }); + }); + + it("Query returns zero if tokens not found", async () => { + const supportedTkn = tkns.deUSDC; + ate.checkQueryReturnsZeroForUnsupportedTkns(supportedTkn); + }); + + it("Query returns zero if both in&out token are underlying", async () => { + const dy = await ate.query(ethers.utils.parseUnits("1", 6), tkns.USDTe.address, tkns.USDCe.address); + expect(dy).to.eq(0); + }); + + it("Gas-estimate is between max-gas-used and 110% max-gas-used", async () => { + const options = [ + ["1", tkns.deUSDC, tkns.USDTe], + ["1", tkns.USDCe, tkns.deUSDC], + ["1", tkns.deUSDC, tkns.USDCe], + ]; + await ate.checkGasEstimateIsSensible(options); + }); + }); + }); + + describe("mim, more, deusdc", () => { + const MaxErrBps = 5; + describe("initialized with mim, more and deusdc added after", () => { + before(async () => { + const contractName = "CurveMetaWithSwapperAdapter"; + + // start with mim pool + const adapterArgs = [ + "CurveMetaAdapter", + 1_200_000, + [curvelikePools.CurveMim], + [curvelikePools.CurveAave], + addresses.avalanche.other.curveMetaSwapper, + ]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + + // add other metapools + await ate.Adapter.addPool(curvelikePools.CurveDeUSDC, curvelikePools.CurveAave); + await ate.Adapter.addPool(curvelikePools.CurveMore, curvelikePools.CurveAave); + }); + + describe("Swapping matches query :: 5 bps error", async () => { + it("44444 MIM -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.MIM, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("44444 deUSDC -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.deUSDC, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("44444 MONEY -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.MONEY, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + }); + }); + describe("initialized with mim, more, and deusdc", () => { + before(async () => { + const contractName = "CurveMetaWithSwapperAdapter"; + + const adapterArgs = [ + "CurveMetaAdapter", + 1_200_000, + [curvelikePools.CurveMim, curvelikePools.CurveMore, curvelikePools.CurveDeUSDC], + [curvelikePools.CurveAave, curvelikePools.CurveAave, curvelikePools.CurveAave], + addresses.avalanche.other.curveMetaSwapper, + ]; + ate = await testEnv.setAdapterEnv(contractName, adapterArgs); + }); + + describe("Swapping matches query :: 5 bps error", async () => { + it("44444 MIM -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.MIM, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("44444 deUSDC -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.deUSDC, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + + it("44444 MONEY -> USDCe", async () => { + await ate.checkSwapMatchesQueryWithDustWithErr("44444", tkns.MONEY, tkns.USDCe, MaxDustWei, MaxErrBps); + }); + }); + }); + }); +});