diff --git a/contracts/routers/FullRouter.sol b/contracts/routers/FullRouter.sol index b61fe2b8..e7679484 100644 --- a/contracts/routers/FullRouter.sol +++ b/contracts/routers/FullRouter.sol @@ -11,15 +11,18 @@ import "../interfaces/aave/ILendingPoolAddressesProvider.sol"; import "../libraries/StrategyLibrary.sol"; import "./StrategyRouter.sol"; +struct LeverageItem { + address token; + uint16 percentage; +} + contract FullRouter is StrategyTypes, StrategyRouter { using SafeMath for uint256; using SignedSafeMath for int256; - uint256 internal constant LTV_DIVISOR = 10000; - ILendingPoolAddressesProvider public immutable addressesProvider; address public immutable susd; - mapping(address => mapping(address => int256)) private _tempEstimate; + mapping(int256 => mapping(address => mapping(address => int256))) private _tempEstimate; constructor(address addressesProvider_, address controller_) public StrategyRouter(RouterCategory.LOOP, controller_) { addressesProvider = ILendingPoolAddressesProvider(addressesProvider_); @@ -51,6 +54,7 @@ contract FullRouter is StrategyTypes, StrategyRouter { override onlyController { + _startTempEstimateSession(strategy); (uint256 percentage, uint256 total, int256[] memory estimates) = abi.decode(data, (uint256, uint256, int256[])); @@ -58,28 +62,14 @@ contract FullRouter is StrategyTypes, StrategyRouter { total = total.sub(expectedWeth); address[] memory strategyItems = IStrategy(strategy).items(); - { - address[] memory strategyDebt = IStrategy(strategy).debt(); - // Deleverage debt - for (uint256 i = 0; i < strategyDebt.length; i++) { - int256 estimatedValue = estimates[strategyItems.length + i]; - int256 expectedValue = StrategyLibrary.getExpectedTokenValue(total, strategy, strategyDebt[i]); - if (estimatedValue < expectedValue) { - _repayPath( - IStrategy(strategy).getTradeData(strategyDebt[i]), - uint256(-estimatedValue.sub(expectedValue)), - total, - strategy - ); - } - } - } + // Deleverage debt + _deleverageForWithdraw(strategy, strategyItems, estimates, total); // Sell loop for (uint256 i = 0; i < strategyItems.length; i++) { int256 estimatedValue = estimates[i]; - if (_tempEstimate[strategy][strategyItems[i]] > 0) { - estimatedValue = _tempEstimate[strategy][strategyItems[i]]; - delete _tempEstimate[strategy][strategyItems[i]]; + if (_getTempEstimate(strategy, strategyItems[i]) > 0) { + estimatedValue = _getTempEstimate(strategy, strategyItems[i]); + _removeTempEstimate(strategy, strategyItems[i]); } int256 expectedValue = StrategyLibrary.getExpectedTokenValue(total, strategy, strategyItems[i]); if (estimatedValue > expectedValue) { @@ -94,6 +84,8 @@ contract FullRouter is StrategyTypes, StrategyRouter { } function rebalance(address strategy, bytes calldata data) external override onlyController { + _startTempEstimateSession(strategy); + //_startTempEstimateSession(strategy); (uint256 total, int256[] memory estimates) = abi.decode(data, (uint256, int256[])); address[] memory strategyItems = IStrategy(strategy).items(); address[] memory strategyDebt = IStrategy(strategy).debt(); @@ -111,9 +103,9 @@ contract FullRouter is StrategyTypes, StrategyRouter { for (uint256 i = 0; i < strategyItems.length; i++) { address strategyItem = strategyItems[i]; int256 estimate = estimates[i]; - if (_tempEstimate[strategy][strategyItem] > 0) { - estimate = _tempEstimate[strategy][strategyItem]; - delete _tempEstimate[strategy][strategyItem]; + if (_getTempEstimate(strategy, strategyItem) > 0) { + estimate = _getTempEstimate(strategy, strategyItem); + _removeTempEstimate(strategy, strategyItem); } int256 expected = StrategyLibrary.getExpectedTokenValue(total, strategy, strategyItem); if (!_sellToken( @@ -123,7 +115,7 @@ contract FullRouter is StrategyTypes, StrategyRouter { expected ) ) buy[i] = expected; - // semantic overloading to cache `expected` since it will be used in next loop. + // semantic overloading to cache `expected` since it will be used in next loop. } // Buy loop for (uint256 i = 0; i < strategyItems.length; i++) { @@ -156,6 +148,7 @@ contract FullRouter is StrategyTypes, StrategyRouter { override onlyController { + _startTempEstimateSession(strategy); ( uint256 currentTotal, int256[] memory currentEstimates, @@ -201,9 +194,9 @@ contract FullRouter is StrategyTypes, StrategyRouter { // Convert funds into Ether address strategyItem = strategyItems[i]; int256 estimate = estimates[i]; - if (_tempEstimate[strategy][strategyItem] > 0) { - estimate = _tempEstimate[strategy][strategyItem]; - delete _tempEstimate[strategy][strategyItem]; + if (_getTempEstimate(strategy, strategyItem) > 0) { + estimate = _getTempEstimate(strategy, strategyItem); + _removeTempEstimate(strategy, strategyItem); } if (IStrategy(strategy).getPercentage(strategyItem) == 0) { //Sell all tokens that have 0 percentage @@ -345,7 +338,11 @@ contract FullRouter is StrategyTypes, StrategyRouter { int256 expectedValue ) internal { int256 amount; - if (estimatedValue == 0) { + // Note: it is possible for a restructure to have an estimated value of zero, + // but only if it's expected value is also zero, in which case this function + // will end without making a purchase. So it is safe to set `isDeposit` this way + bool isDeposit = estimatedValue == 0; + if (isDeposit) { amount = expectedValue; } else { int256 rebalanceRange = @@ -406,7 +403,8 @@ contract FullRouter is StrategyTypes, StrategyRouter { ) internal { int256 expectedValue = StrategyLibrary.getExpectedTokenValue(total, strategy, token); int256 amountInWeth; - if (estimatedValue == 0) { + bool isDeposit = estimatedValue == 0; + if (isDeposit) { amountInWeth = expectedValue; } else { int256 rebalanceRange = @@ -424,7 +422,8 @@ contract FullRouter is StrategyTypes, StrategyRouter { tradeData, uint256(-amountInWeth), total, - strategy + strategy, + isDeposit ); } } @@ -441,7 +440,7 @@ contract FullRouter is StrategyTypes, StrategyRouter { require(data.adapters.length == data.path.length, "Incorrect trade data"); ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool()); IOracle oracle = controller.oracle(); - address[] memory leverageItems; + LeverageItem[] memory leverageItems; uint256[] memory leverageLiquidity; if (data.path[data.path.length-1] != weth) { @@ -449,22 +448,27 @@ contract FullRouter is StrategyTypes, StrategyRouter { amount = amount.mul(10**18).div(uint256(oracle.estimateItem(10**18, data.path[data.path.length-1]))); } else if (data.cache.length > 0) { // Deleverage tokens - leverageItems = abi.decode(data.cache, (address[])); + leverageItems = abi.decode(data.cache, (LeverageItem[])); leverageLiquidity = new uint256[](leverageItems.length); if (amount == 0) { // Special case where debt doesn't need to change but the relative amounts of leverage tokens do. We must first deleverage our debt for (uint256 i = 0; i < leverageItems.length; i++) { - leverageLiquidity[i] = _getLeverageRemaining(oracle, strategy, leverageItems[i], total, false); + leverageLiquidity[i] = _getLeverageRemaining(oracle, strategy, leverageItems[i].token, total, false); amount = amount.add(leverageLiquidity[i]); } } else { uint256 leverageAmount = amount; // amount is denominated in weth here for (uint256 i = 0; i < leverageItems.length; i++) { + address token = leverageItems[i].token; if (leverageItems.length > 1) { //If multiple leveraged items, some may have less liquidity than the total amount we need to sell - uint256 liquidity = _getLeverageRemaining(oracle, strategy, leverageItems[i], total, false); + uint256 liquidity = _getLeverageRemaining(oracle, strategy, token, total, false); leverageLiquidity[i] = leverageAmount > liquidity ? liquidity : leverageAmount; } else { leverageLiquidity[i] = leverageAmount; + _setTempEstimate(strategy, token, oracle.estimateItem( + IERC20(token).balanceOf(strategy), + token + )); } leverageAmount = leverageAmount.sub(leverageLiquidity[i]); } @@ -480,7 +484,7 @@ contract FullRouter is StrategyTypes, StrategyRouter { for (uint256 i = 0; i < leverageItems.length; i++) { if (leverageLiquidity[i] > 0 && availableBorrowsETH > 0) { // Only deleverage token when there is a disparity between the expected value and the estimated value - uint256 leverageAmount = _deleverage(oracle, strategy, leverageItems[i], leverageLiquidity[i], availableBorrowsETH); + uint256 leverageAmount = _deleverage(oracle, strategy, leverageItems[i].token, leverageLiquidity[i], availableBorrowsETH); leverageLiquidity[i] = leverageLiquidity[i].sub(leverageAmount); availableBorrowsETH = availableBorrowsETH.sub(leverageAmount); if (leverageLiquidity[i] > 0) isLiquidityRemaining = true; // Liquidity still remaining @@ -535,22 +539,28 @@ contract FullRouter is StrategyTypes, StrategyRouter { TradeData memory data, uint256 amount, // weth uint256 total, - address strategy + address strategy, + bool isDeposit ) internal { // Debt trade paths should have path.length == adapters.length, // since final token can differ from the debt token defined in the strategy require(data.adapters.length == data.path.length, "Incorrect trade data"); ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool()); - address[] memory leverageItems; + LeverageItem[] memory leverageItems; uint256[] memory leverageLiquidity; if (data.path[data.path.length-1] == weth && data.cache.length > 0) { - leverageItems = abi.decode(data.cache, (address[])); + leverageItems = abi.decode(data.cache, (LeverageItem[])); leverageLiquidity = new uint256[](leverageItems.length); - - IOracle oracle = controller.oracle(); - for (uint256 i = 0; i < leverageItems.length; i++) { - leverageLiquidity[i] = _getLeverageRemaining(oracle, strategy, leverageItems[i], total, true); + if (isDeposit) { + for (uint256 i = 0; i < leverageItems.length; i++) { + leverageLiquidity[i] = _getLeveragePercentage(strategy, leverageItems[i].token, leverageItems[i].percentage, total); + } + } else { + IOracle oracle = controller.oracle(); + for (uint256 i = 0; i < leverageItems.length; i++) { + leverageLiquidity[i] = _getLeverageRemaining(oracle, strategy, leverageItems[i].token, total, true); + } } } @@ -590,19 +600,42 @@ contract FullRouter is StrategyTypes, StrategyRouter { ); } } + if (leverageItems.length > 0) { // Leverage tokens: cache can contain an array of tokens that can be purchased with the WETH received from selling debt // Only purchase token when there is a disparity between the expected value and the estimated value for (uint256 i = 0; i < leverageItems.length; i++) { - if (leverageLiquidity[i] > 0) { - uint256 leverageAmount = _leverage(strategy, leverageItems[i], leverageLiquidity[i]); - leverageLiquidity[i] = leverageLiquidity[i].sub(leverageAmount); + // Since we're inside a while loop, the last item will be when `amount` == 0 + bool lastItem = amount == 0 && i == leverageItems.length - 1; + if (leverageLiquidity[i] > 0 || lastItem) { + uint256 leverageAmount = _leverage(strategy, leverageItems[i].token, leverageLiquidity[i], lastItem); + if (leverageAmount > leverageLiquidity[i]) { + // Sometimes we may pay more than needed such as when we reach the lastItem + // and we use the remaining weth (rather than leave it in this contract) so + // just set to zero + leverageLiquidity[i] = 0; + } else { + // If leverageLiqudity remains, it means there wasn't enough weth to reach + // the expected amount, the remained will be handled on subsequent loops of + // the parent while loop + leverageLiquidity[i] = leverageLiquidity[i].sub(leverageAmount); + } } } } } } + function _getLeveragePercentage( + address strategy, + address leverageItem, + uint256 leveragePercentage, + uint256 total + ) internal returns (uint256) { + int256 expected = StrategyLibrary.getExpectedTokenValue(total, strategy, leverageItem); + return uint256(expected).mul(leveragePercentage).div(DIVISOR); + } + function _getLeverageRemaining( IOracle oracle, address strategy, @@ -610,15 +643,15 @@ contract FullRouter is StrategyTypes, StrategyRouter { uint256 total, bool isLeveraging ) internal returns (uint256) { + int256 expected = StrategyLibrary.getExpectedTokenValue(total, strategy, leverageItem); int256 estimate = oracle.estimateItem( - IERC20(leverageItem).balanceOf(strategy), - leverageItem + IERC20(leverageItem).balanceOf(strategy), + leverageItem ); - int256 expected = StrategyLibrary.getExpectedTokenValue(total, strategy, leverageItem); if (isLeveraging) { if (expected > estimate) return uint256(expected.sub(estimate)); } else { - _tempEstimate[strategy][leverageItem] = estimate; // Store this value for _deleverage() + _setTempEstimate(strategy, leverageItem, estimate); // Store this value for _deleverage() if (estimate > expected) return uint256(estimate.sub(expected)); } return 0; @@ -627,11 +660,18 @@ contract FullRouter is StrategyTypes, StrategyRouter { function _leverage( address strategy, address leverageItem, - uint256 leverageLiquidity + uint256 leverageLiquidity, + bool lastItem ) internal returns (uint256) { uint256 wethBalance = IERC20(weth).balanceOf(address(this)); if (wethBalance > 0) { - uint256 leverageAmount = leverageLiquidity > wethBalance ? wethBalance : leverageLiquidity; + uint256 leverageAmount; + if (lastItem) { + // If it is the last item being leveraged, use all remaining weth + leverageAmount = wethBalance; + } else { + leverageAmount = leverageLiquidity > wethBalance ? wethBalance : leverageLiquidity; + } _buyPath( IStrategy(strategy).getTradeData(leverageItem), leverageAmount, @@ -651,7 +691,7 @@ contract FullRouter is StrategyTypes, StrategyRouter { uint256 available ) internal returns (uint256) { uint256 leverageAmount = leverageLiquidity > available ? available : leverageLiquidity; - uint256 leverageEstimate = uint256(_tempEstimate[strategy][leverageItem]); //Set in _getLeverageRemaining + uint256 leverageEstimate = uint256(_getTempEstimate(strategy, leverageItem)); //Set in _getLeverageRemaining require(leverageEstimate > 0, "Insufficient collateral"); _sellPath( IStrategy(strategy).getTradeData(leverageItem), @@ -660,10 +700,58 @@ contract FullRouter is StrategyTypes, StrategyRouter { strategy ); // Update temp estimates with new value since tokens have been sold (it will be needed on later sell loops) - _tempEstimate[strategy][leverageItem] = oracle.estimateItem( + _setTempEstimate(strategy, leverageItem, oracle.estimateItem( IERC20(leverageItem).balanceOf(strategy), leverageItem - ); + )); return leverageAmount; } + + function _deleverageForWithdraw(address strategy, address[] memory strategyItems, int256[] memory estimates, uint256 total) private { + address[] memory strategyDebt = IStrategy(strategy).debt(); + // Deleverage debt + for (uint256 i = 0; i < strategyDebt.length; i++) { + int256 estimatedValue = estimates[strategyItems.length + i]; + int256 expectedValue = StrategyLibrary.getExpectedTokenValue(total, strategy, strategyDebt[i]); + if (estimatedValue < expectedValue) { + _repayPath( + IStrategy(strategy).getTradeData(strategyDebt[i]), + uint256(-estimatedValue.sub(expectedValue)), + total, + strategy + ); + } + } + } + + function _startTempEstimateSession(address strategy) private { + /* + To ensure that a stale "temp" estimate isn't leaked into other function calls + by not being "delete"d in the same external call in which it is set, we + associate to each external call a "session counter" so that it only deals with + temp values corresponding to its own session. + **/ + + ++_tempEstimate[0][strategy][address(0)]; // ++counter + } + + function _getCurrentTempEstimateSession(address strategy) private view returns(int256) { + return _tempEstimate[0][strategy][address(0)]; // counter + } + + function _setTempEstimate(address strategy, address item, int256 value) private { + int256 session = _getCurrentTempEstimateSession(strategy); + _tempEstimate[session][strategy][item] = value; + } + + function _getTempEstimate(address strategy, address item) private view returns(int256) { + int256 session = _getCurrentTempEstimateSession(strategy); + return _tempEstimate[session][strategy][item]; + } + + function _removeTempEstimate(address strategy, address item) private { + int256 session = _getCurrentTempEstimateSession(strategy); + delete _tempEstimate[session][strategy][item]; + } + } diff --git a/hardhat.config.ts b/hardhat.config.ts index f09cd7c8..c434b043 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -67,7 +67,7 @@ function getNetworks(): NetworksUserConfig { if (archiveNode) networks.hardhat.forking = { url: archiveNode, - blockNumber: 14619730, + blockNumber: 14655540, } } if (mnemonic && infuraApiKey) { diff --git a/lib/estimator.ts b/lib/estimator.ts index b01023bc..bb4366b1 100644 --- a/lib/estimator.ts +++ b/lib/estimator.ts @@ -520,6 +520,7 @@ export class Estimator { } else if (tokenOut.toLowerCase() === WETH.toLowerCase()) { // Withdraw const strategy = new Contract(tokenIn, IStrategy.abi, this.signer) + amount = amount.sub(amount.mul(2).div(DIVISOR)) return this.withdraw(strategy, amount) } else { // Meta strategies always have weth as an input or output diff --git a/lib/mainnet.ts b/lib/mainnet.ts index ac20aed9..191cb3eb 100644 --- a/lib/mainnet.ts +++ b/lib/mainnet.ts @@ -55,7 +55,7 @@ export class LiveEnvironment { platform: Platform adapters: LiveAdapters routers: LiveRouters - estimators: Estimators + estimators: Estimators constructor( signer: SignerWithAddress, @@ -75,7 +75,7 @@ export class LiveEnvironment { export type LiveAdapters = { aaveV2: Contract - aaveV2Debt: Contract + aaveV2Debt: Contract balancer: Contract compound: Contract curve: Contract diff --git a/test/aave-adapter.ts b/test/aave-adapter.ts index 540b391e..310c1542 100644 --- a/test/aave-adapter.ts +++ b/test/aave-adapter.ts @@ -12,10 +12,12 @@ import { deployAaveV2Adapter, deployAaveV2DebtAdapter, deployUniswapV2Adapter, + deployMetaStrategyAdapter, deployPlatform, + deployLoopRouter, deployFullRouter } from '../lib/deploy' -import { DEFAULT_DEPOSIT_SLIPPAGE, MAINNET_ADDRESSES } from '../lib/constants' +import { MAINNET_ADDRESSES } from '../lib/constants' //import { displayBalances } from '../lib/logging' import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' @@ -23,6 +25,16 @@ import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' chai.use(solidity) +const STRATEGY_STATE: InitialState = { + timelock: BigNumber.from(60), + rebalanceThreshold: BigNumber.from(10), + rebalanceSlippage: BigNumber.from(997), + restructureSlippage: BigNumber.from(995), + performanceFee: BigNumber.from(0), + social: true, + set: false +} + describe('AaveAdapter', function () { let weth: Contract, usdc: Contract, @@ -33,6 +45,7 @@ describe('AaveAdapter', function () { controller: Contract, oracle: Contract, library: Contract, + whitelist: Contract, uniswapAdapter: Contract, aaveV2Adapter: Contract, aaveV2DebtAdapter: Contract, @@ -54,10 +67,12 @@ describe('AaveAdapter', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle library = platform.library - const whitelist = platform.administration.whitelist + whitelist = platform.administration.whitelist const addressProvider = new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], accounts[0]) await tokens.registerTokens(accounts[0], strategyFactory) + collateralToken = tokens.aWETH + collateralToken2 = tokens.aCRV router = await deployFullRouter(accounts[0], addressProvider, controller, library) await whitelist.connect(accounts[0]).approve(router.address) @@ -70,10 +85,10 @@ describe('AaveAdapter', function () { }) it('Should deploy strategy', async function () { - collateralToken = tokens.aWETH - collateralToken2 = tokens.aWBTC + const name = 'Test Strategy' const symbol = 'TEST' + const positions = [ { token: collateralToken, percentage: BigNumber.from(1000), @@ -87,7 +102,7 @@ describe('AaveAdapter', function () { { token: collateralToken2, percentage: BigNumber.from(1000), adapters: [uniswapAdapter.address, aaveV2Adapter.address], - path: [tokens.wbtc], + path: [tokens.crv], cache: ethers.utils.defaultAbiCoder.encode( ['uint16'], [500] // Multiplier 50% (divisor = 1000). For calculating the amount to purchase based off of the percentage @@ -98,11 +113,15 @@ describe('AaveAdapter', function () { adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], path: [tokens.usdc, tokens.weth], //ending in weth allows for a leverage feedback loop cache: ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [[collateralToken, collateralToken2]] //define what tokens you want to loop back on here + ['tuple(address token, uint16 percentage)[]'], + [[ + { token: collateralToken, percentage: 500 }, + { token: collateralToken2, percentage: 500 } + ]] //define what tokens you want to loop back on here ), } ] + strategyItems = prepareStrategy(positions, uniswapAdapter.address) const strategyState: InitialState = { timelock: BigNumber.from(60), @@ -147,6 +166,13 @@ describe('AaveAdapter', function () { expect(await wrapper.isBalanced()).to.equal(true) }) + it('Should deposit', async function () { + const tx = await controller.connect(accounts[1]).deposit(strategy.address, router.address, 0, '990', '0x', { value: WeiPerEther }) + const receipt = await tx.wait() + console.log('Deposit Gas Used: ', receipt.gasUsed.toString()) + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + it('Should purchase a token, requiring a rebalance of strategy', async function () { // Approve the user to use the adapter const value = WeiPerEther.mul(1000) @@ -193,11 +219,54 @@ describe('AaveAdapter', function () { await expect(strategy.connect(accounts[1]).withdrawAll(amount)).to.be.revertedWith('Cannot withdraw debt') }) + it('Should restructure', async function () { + const positions = [ + { token: collateralToken, + percentage: BigNumber.from(2000), + adapters: [aaveV2Adapter.address], + path: [], + cache: ethers.utils.defaultAbiCoder.encode( + ['uint16'], + [500] // Multiplier 50% (divisor = 1000). For calculating the amount to purchase based off of the percentage + ), + }, + { token: tokens.debtUSDC, + percentage: BigNumber.from(-1000), + adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], + path: [tokens.usdc, tokens.weth], + cache: ethers.utils.defaultAbiCoder.encode( + ['tuple(address token, uint16 percentage)[]'], + [[ + { token: collateralToken, percentage: 500 }, + { token: collateralToken2, percentage: 0 } + ]] //Need to keep collateralToken2 in the cache in order to deleverage it + ), + } + ] + strategyItems = prepareStrategy(positions, uniswapAdapter.address) + await controller.connect(accounts[1]).restructure(strategy.address, strategyItems) + }) + + it('Should finalize structure', async function () { + await controller + .connect(accounts[1]) + .finalizeStructure(strategy.address, router.address, '0x') + + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + + it('Should deposit', async function () { + const tx = await controller.connect(accounts[1]).deposit(strategy.address, router.address, 0, '990', '0x', { value: WeiPerEther }) + const receipt = await tx.wait() + console.log('Deposit Gas Used: ', receipt.gasUsed.toString()) + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + it('Should withdraw ETH', async function () { //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) const amount = BigNumber.from('5000000000000000') const ethBalanceBefore = await accounts[1].getBalance() - const tx = await controller.connect(accounts[1]).withdrawETH(strategy.address, router.address, amount, DEFAULT_DEPOSIT_SLIPPAGE, '0x') + const tx = await controller.connect(accounts[1]).withdrawETH(strategy.address, router.address, amount, '990', '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -209,7 +278,7 @@ describe('AaveAdapter', function () { //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) const amount = BigNumber.from('5000000000000000') const wethBalanceBefore = await weth.balanceOf(accounts[1].address) - const tx = await controller.connect(accounts[1]).withdrawWETH(strategy.address, router.address, amount, DEFAULT_DEPOSIT_SLIPPAGE, '0x') + const tx = await controller.connect(accounts[1]).withdrawWETH(strategy.address, router.address, amount, '990', '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -217,34 +286,360 @@ describe('AaveAdapter', function () { expect(wethBalanceAfter.gt(wethBalanceBefore)).to.equal(true) }) - it('Should restructure', async function () { + it('Should deploy new strategy', async function () { + const name = 'New Strategy' + const symbol = 'NEW' + const positions = [ - { token: collateralToken, + { + token: tokens.aWETH, percentage: BigNumber.from(2000), adapters: [aaveV2Adapter.address], path: [], cache: ethers.utils.defaultAbiCoder.encode( - ['uint16'], - [500] // Multiplier 50% (divisor = 1000). For calculating the amount to purchase based off of the percentage - ), + ['uint16'], + [500], // Multiplier 50% (divisor = 1000). For calculating the amount to purchase based off of the percentage + ), }, - { token: tokens.debtUSDC, + { + token: tokens.debtUSDC, percentage: BigNumber.from(-1000), adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], path: [tokens.usdc, tokens.weth], cache: ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [[collateralToken, collateralToken2]] //Need to keep collateralToken2 in the cache in order to deleverage it - ), + ['tuple(address token, uint16 percentage)[]'], + [[{ token: collateralToken, percentage: 500 }]] //define what tokens you want to loop back on here + ) } ] + strategyItems = prepareStrategy(positions, uniswapAdapter.address) - await controller.connect(accounts[1]).restructure(strategy.address, strategyItems) + const strategyState: InitialState = { + timelock: BigNumber.from(60), + rebalanceThreshold: BigNumber.from(50), + rebalanceSlippage: BigNumber.from(997), + restructureSlippage: BigNumber.from(980), // Restucturing from this strategy requires higher slippage tolerance + performanceFee: BigNumber.from(0), + social: false, + set: false + } + + const tx = await strategyFactory + .connect(accounts[1]) + .createStrategy( + accounts[1].address, + name, + symbol, + strategyItems, + strategyState, + router.address, + '0x', + { value: WeiPerEther } + ) + const receipt = await tx.wait() + console.log('Deployment Gas Used: ', receipt.gasUsed.toString()) + + const strategyAddress = receipt.events.find((ev: Event) => ev.event === 'NewStrategy').args.strategy + const Strategy = await getContractFactory('Strategy') + strategy = await Strategy.attach(strategyAddress) + + expect(await controller.initialized(strategyAddress)).to.equal(true) + + const LibraryWrapper = await getContractFactory('LibraryWrapper', { + libraries: { + StrategyLibrary: library.address + } + }) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + await wrapper.deployed() + + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + expect(await wrapper.isBalanced()).to.equal(true) }) - it('Should finalize structure', async function () { - await controller + it('Should deposit', async function () { + const tx = await controller.connect(accounts[1]).deposit(strategy.address, router.address, 0, '990', '0x', { value: WeiPerEther }) + const receipt = await tx.wait() + console.log('Deposit Gas Used: ', receipt.gasUsed.toString()) + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + + it('Should deploy another strategy', async function () { + const name = 'Another Strategy' + const symbol = 'ANOTHER' + + const positions = [ + { + token: tokens.aWETH, + percentage: BigNumber.from(2000), + adapters: [aaveV2Adapter.address], + path: [], + cache: ethers.utils.defaultAbiCoder.encode( + ["uint16"], + [500], // Multiplier 50% (divisor = 1000). For calculating the amount to purchase based off of the percentage + ), + }, + { + token: tokens.debtUSDC, + percentage: BigNumber.from(-500), + adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], + path: [tokens.usdc, tokens.weth], + cache: ethers.utils.defaultAbiCoder.encode( + ['tuple(address token, uint16 percentage)[]'], + [[{ token: collateralToken, percentage: 250 }]] //define what tokens you want to loop back on here), + ) + }, + { + token: tokens.debtWBTC, + percentage: BigNumber.from(-500), + adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], + path: [tokens.wbtc, tokens.weth], + cache: ethers.utils.defaultAbiCoder.encode( + ['tuple(address token, uint16 percentage)[]'], + [[{ token: collateralToken, percentage: 250 }]] //define what tokens you want to loop back on here), + ) + } + ] + + strategyItems = prepareStrategy(positions, uniswapAdapter.address) + const strategyState: InitialState = { + timelock: BigNumber.from(60), + rebalanceThreshold: BigNumber.from(50), + rebalanceSlippage: BigNumber.from(997), + restructureSlippage: BigNumber.from(980), // Restucturing from this strategy requires higher slippage tolerance + performanceFee: BigNumber.from(0), + social: false, + set: false + } + + const tx = await strategyFactory .connect(accounts[1]) - .finalizeStructure(strategy.address, router.address, '0x') + .createStrategy( + accounts[1].address, + name, + symbol, + strategyItems, + strategyState, + router.address, + '0x', + { value: WeiPerEther } + ) + const receipt = await tx.wait() + console.log('Deployment Gas Used: ', receipt.gasUsed.toString()) + + const strategyAddress = receipt.events.find((ev: Event) => ev.event === 'NewStrategy').args.strategy + const Strategy = await getContractFactory('Strategy') + strategy = await Strategy.attach(strategyAddress) + + expect(await controller.initialized(strategyAddress)).to.equal(true) + + const LibraryWrapper = await getContractFactory('LibraryWrapper', { + libraries: { + StrategyLibrary: library.address + } + }) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + await wrapper.deployed() + + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + expect(await wrapper.isBalanced()).to.equal(true) + }) + + it('Should deposit', async function () { + const tx = await controller.connect(accounts[1]).deposit(strategy.address, router.address, 0, '990', '0x', { value: WeiPerEther }) + const receipt = await tx.wait() + console.log('Deposit Gas Used: ', receipt.gasUsed.toString()) + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + + it('Should deploy ambiguous strategy', async function () { + const name = 'Ambiguous Strategy' + const symbol = 'AMBI' + + const positions = [ + { token: collateralToken, + percentage: BigNumber.from(500), + adapters: [aaveV2Adapter.address], + path: [], + cache: ethers.utils.defaultAbiCoder.encode( + ['uint16'], + [500] + ), + }, + { token: collateralToken2, + percentage: BigNumber.from(1500), + adapters: [uniswapAdapter.address, aaveV2Adapter.address], + path: [tokens.crv], + cache: ethers.utils.defaultAbiCoder.encode( + ['uint16'], + [500] + ), + }, + { token: tokens.debtUSDC, + percentage: BigNumber.from(-875), + adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], + path: [tokens.usdc, tokens.weth], + cache: ethers.utils.defaultAbiCoder.encode( + ['tuple(address token, uint16 percentage)[]'], + [[ + { token: collateralToken, percentage: 250 }, + { token: collateralToken2, percentage: 500 } + ]] //define what tokens you want to loop back on here + ), + }, + { token: tokens.debtWBTC, + percentage: BigNumber.from(-125), + adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], + path: [tokens.wbtc, tokens.weth], + cache: ethers.utils.defaultAbiCoder.encode( + ['tuple(address token, uint16 percentage)[]'], + [[{ token: collateralToken, percentage: 250 }]] //define what tokens you want to loop back on here + ), + } + ] + + strategyItems = prepareStrategy(positions, uniswapAdapter.address) + const strategyState: InitialState = { + timelock: BigNumber.from(60), + rebalanceThreshold: BigNumber.from(50), + rebalanceSlippage: BigNumber.from(997), + restructureSlippage: BigNumber.from(980), // Restucturing from this strategy requires higher slippage tolerance + performanceFee: BigNumber.from(0), + social: false, + set: false + } + + const tx = await strategyFactory + .connect(accounts[1]) + .createStrategy( + accounts[1].address, + name, + symbol, + strategyItems, + strategyState, + router.address, + '0x', + { value: WeiPerEther } + ) + const receipt = await tx.wait() + console.log('Deployment Gas Used: ', receipt.gasUsed.toString()) + + const strategyAddress = receipt.events.find((ev: Event) => ev.event === 'NewStrategy').args.strategy + const Strategy = await getContractFactory('Strategy') + + strategy = await Strategy.attach(strategyAddress) + const LibraryWrapper = await getContractFactory('LibraryWrapper', { + libraries: { + StrategyLibrary: library.address + } + }) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + await wrapper.deployed() + + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + expect(await wrapper.isBalanced()).to.equal(true) + }) + + it('Should deposit', async function () { + const tx = await controller.connect(accounts[1]).deposit(strategy.address, router.address, 0, '990', '0x', { value: WeiPerEther }) + const receipt = await tx.wait() + console.log('Deposit Gas Used: ', receipt.gasUsed.toString()) + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + + it('Should deploy debt meta strategy', async function () { + //await displayBalances(basicWrapper, basicStrategyItems.map((item) => item.item), weth) + + const loopRouter = await deployLoopRouter(accounts[0], controller, library) + await whitelist.connect(accounts[0]).approve(loopRouter.address) + + let name = 'Basic Strategy' + let symbol = 'BASIC' + let positions = [ + { token: weth.address, percentage: BigNumber.from(500) }, + { token: usdc.address, percentage: BigNumber.from(500) }, + ] + const basicStrategyItems = prepareStrategy(positions, uniswapAdapter.address) + + let tx = await strategyFactory + .connect(accounts[1]) + .createStrategy( + accounts[1].address, + name, + symbol, + basicStrategyItems, + STRATEGY_STATE, + loopRouter.address, + '0x', + { value: ethers.BigNumber.from('10000000000000000') } + ) + + let receipt = await tx.wait() + console.log('Deployment Gas Used: ', receipt.gasUsed.toString()) + + let strategyAddress = receipt.events.find((ev: Event) => ev.event === 'NewStrategy').args.strategy + const Strategy = await getContractFactory('Strategy') + const basicStrategy = await Strategy.attach(strategyAddress) + + let metaStrategyAdapter = await deployMetaStrategyAdapter(accounts[0], controller, loopRouter, weth) + await whitelist.connect(accounts[0]).approve(metaStrategyAdapter.address) + + name = 'Debt Meta Strategy' + symbol = 'DMETA' + let positions2 = [ + { token: basicStrategy.address, + percentage: BigNumber.from(400), + adapters: [metaStrategyAdapter.address], + path: [] + }, + { token: tokens.aWETH, + percentage: BigNumber.from(1200), + adapters: [aaveV2Adapter.address], + path: [], + cache: ethers.utils.defaultAbiCoder.encode( + ['uint16'], + [500] // Multiplier 50% (divisor = 1000). For calculating the amount to purchase based off of the percentage + ), + }, + { token: tokens.debtUSDC, + percentage: BigNumber.from(-600), + adapters: [aaveV2DebtAdapter.address, uniswapAdapter.address], + path: [tokens.usdc, weth.address], //ending in weth allows for a leverage feedback loop + cache: ethers.utils.defaultAbiCoder.encode( + ['tuple(address token, uint16 percentage)[]'], + [[{ token: tokens.aWETH, percentage: 500 }]] + ), + }, + ] + + let metaStrategyItems = prepareStrategy(positions2, uniswapAdapter.address) + tx = await strategyFactory + .connect(accounts[1]) + .createStrategy( + accounts[1].address, + name, + symbol, + metaStrategyItems, + STRATEGY_STATE, + router.address, + '0x', + { value: ethers.BigNumber.from('10000000000000000') } + ) + receipt = await tx.wait() + console.log('Deployment Gas Used: ', receipt.gasUsed.toString()) + + strategyAddress = receipt.events.find((ev: Event) => ev.event === 'NewStrategy').args.strategy + expect(await controller.initialized(strategyAddress)).to.equal(true) + + const LibraryWrapper = await getContractFactory('LibraryWrapper', { + libraries: { + StrategyLibrary: library.address + } + }) + let metaWrapper = await LibraryWrapper.connect(accounts[0]).deploy(oracle.address, strategyAddress) + await metaWrapper.deployed() + + //await displayBalances(basicWrapper, basicStrategyItems.map((item) => item.item), weth) + expect(await metaWrapper.isBalanced()).to.equal(true) }) }) diff --git a/test/curve-adapter.ts b/test/curve-adapter.ts index 10971dff..ee466200 100644 --- a/test/curve-adapter.ts +++ b/test/curve-adapter.ts @@ -18,6 +18,7 @@ import { deployLoopRouter } from '../lib/deploy' import { MAINNET_ADDRESSES } from '../lib/constants' +//import { displayBalances } from '../lib/logging' import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' @@ -210,14 +211,14 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { const name = 'Curve ETHBTC Strategy' const symbol = 'ETHBTC' const positions = [ - { token: dai.address, percentage: BigNumber.from(200) }, + { token: dai.address, percentage: BigNumber.from(400) }, { token: tokens.crvREN, percentage: BigNumber.from(400), adapters: [uniswapV2Adapter.address, curveLPAdapter.address], path: [tokens.wbtc] }, { token: tokens.crvSETH, - percentage: BigNumber.from(400), + percentage: BigNumber.from(200), adapters: [uniswapV2Adapter.address, curveLPAdapter.address], path: [tokens.sETH] }, @@ -225,7 +226,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { strategyItems = prepareStrategy(positions, uniswapV2Adapter.address) const strategyState: InitialState = { timelock: BigNumber.from(60), - rebalanceThreshold: BigNumber.from(10), + rebalanceThreshold: BigNumber.from(50), rebalanceSlippage: BigNumber.from(997), restructureSlippage: BigNumber.from(980), // Needs to tolerate more slippage performanceFee: BigNumber.from(0), @@ -320,7 +321,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { strategyItems = prepareStrategy(positions, uniswapV2Adapter.address) const strategyState: InitialState = { timelock: BigNumber.from(60), - rebalanceThreshold: BigNumber.from(10), + rebalanceThreshold: BigNumber.from(50), rebalanceSlippage: BigNumber.from(997), restructureSlippage: BigNumber.from(995), performanceFee: BigNumber.from(0), diff --git a/test/estimator.ts b/test/estimator.ts index 289e52ac..36fc8cfb 100644 --- a/test/estimator.ts +++ b/test/estimator.ts @@ -310,7 +310,8 @@ describe('Estimator', function() { console.log('Expected withdraw value: ', expectedWithdrawValue.toString()) const estimatedWithdrawValue = await estimator.withdraw(metaStrategy, withdrawAmountAfterFee) // NOTE: Fee withdrawn before estimate console.log('Estimated withdraw value: ', estimatedWithdrawValue.toString()) - const slippage = estimatedWithdrawValue.mul(DIVISOR).div(expectedWithdrawValue).sub(1) // subtract 1 for margin of error + let slippage = estimatedWithdrawValue.mul(DIVISOR).div(expectedWithdrawValue).sub(1) // subtract 1 for margin of error + if (slippage.gt(999)) slippage = BigNumber.from(999) await enso.platform.controller.connect(accounts[1]).withdrawWETH(metaStrategy.address, routerAddress, withdrawAmount, slippage, '0x') const wethAfter = await weth.balanceOf(accounts[1].address) console.log('Actual withdraw amount: ', wethAfter.sub(wethBefore).toString()) diff --git a/test/experimental-strategy.ts b/test/experimental-strategy.ts index 0add1d7d..28d34b7c 100644 --- a/test/experimental-strategy.ts +++ b/test/experimental-strategy.ts @@ -141,6 +141,13 @@ describe('Experimental Strategy', function () { expect(await wrapper.isBalanced()).to.equal(true) }) + it('Should deposit', async function () { + const tx = await controller.connect(accounts[1]).deposit(strategy.address, router.address, 0, '990', '0x', { value: WeiPerEther }) + const receipt = await tx.wait() + console.log('Deposit Gas Used: ', receipt.gasUsed.toString()) + //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) + }) + it('Should purchase a token, requiring a rebalance of strategy', async function () { // Approve the user to use the adapter const value = WeiPerEther.mul(10) diff --git a/test/yearn-adapter.ts b/test/yearn-adapter.ts index db824a86..812903f0 100644 --- a/test/yearn-adapter.ts +++ b/test/yearn-adapter.ts @@ -2,7 +2,7 @@ import chai from 'chai' const { expect } = chai import { ethers } from 'hardhat' const { constants, getContractFactory, getSigners } = ethers -const { AddressZero, WeiPerEther } = constants +const { WeiPerEther } = constants import { solidity } from 'ethereum-waffle' import { BigNumber, Contract, Event } from 'ethers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -10,8 +10,10 @@ import { prepareStrategy, StrategyItem, InitialState } from '../lib/encode' import { Tokens } from '../lib/tokens' import { deployYEarnAdapter, + deployCurveAdapter, deployCurveLPAdapter, deployUniswapV2Adapter, + deployUniswapV3Adapter, deployPlatform, deployLoopRouter } from '../lib/deploy' @@ -20,6 +22,7 @@ import { MAINNET_ADDRESSES } from '../lib/constants' import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' +import UniswapV3Factory from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json' chai.use(solidity) @@ -28,14 +31,15 @@ describe('YEarnV2Adapter', function () { crv: Contract, //dai: Contract, accounts: SignerWithAddress[], - uniswapFactory: Contract, router: Contract, strategyFactory: Contract, controller: Contract, oracle: Contract, library: Contract, - uniswapAdapter: Contract, + uniswapV2Adapter: Contract, + uniswapV3Adapter: Contract, curveAdapter: Contract, + curveLPAdapter: Contract, yearnAdapter: Contract, strategy: Contract, strategyItems: StrategyItem[], @@ -48,24 +52,29 @@ describe('YEarnV2Adapter', function () { weth = new Contract(tokens.weth, WETH9.abi, accounts[0]) crv = new Contract(tokens.crv, ERC20.abi, accounts[0]) //dai = new Contract(tokens.crv.dai, ERC20.abi, accounts[0]) - uniswapFactory = new Contract(MAINNET_ADDRESSES.UNISWAP_V2_FACTORY, UniswapV2Factory.abi, accounts[0]) - const platform = await deployPlatform(accounts[0], uniswapFactory, new Contract(AddressZero, [], accounts[0]), weth) + const uniswapV2Factory = new Contract(MAINNET_ADDRESSES.UNISWAP_V2_FACTORY, UniswapV2Factory.abi, accounts[0]) + const uniswapV3Factory = new Contract(MAINNET_ADDRESSES.UNISWAP_V3_FACTORY, UniswapV3Factory.abi, accounts[0]) + const platform = await deployPlatform(accounts[0], uniswapV2Factory, uniswapV3Factory, weth) controller = platform.controller strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle library = platform.library - const { curveDepositZapRegistry, chainlinkRegistry } = platform.oracles.registries - await tokens.registerTokens(accounts[0], strategyFactory, undefined, chainlinkRegistry, curveDepositZapRegistry) + const { curveDepositZapRegistry, chainlinkRegistry, uniswapV3Registry } = platform.oracles.registries + await tokens.registerTokens(accounts[0], strategyFactory, uniswapV3Registry, chainlinkRegistry, curveDepositZapRegistry) const addressProvider = new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], accounts[0]) const whitelist = platform.administration.whitelist router = await deployLoopRouter(accounts[0], controller, library) await whitelist.connect(accounts[0]).approve(router.address) - uniswapAdapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) - await whitelist.connect(accounts[0]).approve(uniswapAdapter.address) - curveAdapter = await deployCurveLPAdapter(accounts[0], addressProvider, curveDepositZapRegistry, weth) + uniswapV2Adapter = await deployUniswapV2Adapter(accounts[0], uniswapV2Factory, weth) + await whitelist.connect(accounts[0]).approve(uniswapV2Adapter.address) + uniswapV3Adapter = await deployUniswapV3Adapter(accounts[0], uniswapV3Registry, new Contract(MAINNET_ADDRESSES.UNISWAP_V3_ROUTER, [], accounts[0]), weth) + await whitelist.connect(accounts[0]).approve(uniswapV3Adapter.address) + curveAdapter = await deployCurveAdapter(accounts[0], addressProvider, weth) await whitelist.connect(accounts[0]).approve(curveAdapter.address) + curveLPAdapter = await deployCurveLPAdapter(accounts[0], addressProvider, curveDepositZapRegistry, weth) + await whitelist.connect(accounts[0]).approve(curveLPAdapter.address) yearnAdapter = await deployYEarnAdapter(accounts[0], weth) await whitelist.connect(accounts[0]).approve(yearnAdapter.address) }) @@ -78,9 +87,13 @@ describe('YEarnV2Adapter', function () { const positions = [ { token: weth.address, percentage: BigNumber.from(0) }, { token: crv.address, percentage: BigNumber.from(500) }, - { token: yearnToken, percentage: BigNumber.from(500), adapters: [uniswapAdapter.address, curveAdapter.address, yearnAdapter.address], path: [tokens.sUSD, tokens.crvSUSD] } + { token: yearnToken, + percentage: BigNumber.from(500), + adapters: [uniswapV3Adapter.address, curveAdapter.address, curveLPAdapter.address, yearnAdapter.address], + path: [tokens.usdc, tokens.sUSD, tokens.crvSUSD] + } ] - strategyItems = prepareStrategy(positions, uniswapAdapter.address) + strategyItems = prepareStrategy(positions, uniswapV2Adapter.address) const strategyState: InitialState = { timelock: BigNumber.from(60), rebalanceThreshold: BigNumber.from(50), @@ -128,8 +141,8 @@ describe('YEarnV2Adapter', function () { // Approve the user to use the adapter const value = WeiPerEther.mul(100) await weth.connect(accounts[19]).deposit({value: value}) - await weth.connect(accounts[19]).approve(uniswapAdapter.address, value) - await uniswapAdapter + await weth.connect(accounts[19]).approve(uniswapV2Adapter.address, value) + await uniswapV2Adapter .connect(accounts[19]) .swap(value, 0, weth.address, crv.address, accounts[19].address, accounts[19].address) @@ -147,8 +160,8 @@ describe('YEarnV2Adapter', function () { it('Should purchase a token, requiring a rebalance of strategy', async function () { // Approve the user to use the adapter const value = await crv.balanceOf(accounts[19].address) - await crv.connect(accounts[19]).approve(uniswapAdapter.address, value) - await uniswapAdapter + await crv.connect(accounts[19]).approve(uniswapV2Adapter.address, value) + await uniswapV2Adapter .connect(accounts[19]) .swap(value, 0, crv.address, weth.address, accounts[19].address, accounts[19].address) expect(await wrapper.isBalanced()).to.equal(false)