Apply feedback fixes

This commit is contained in:
Gerardo Nardelli 2020-10-30 16:59:25 -03:00
parent 8278d2e6d8
commit 0eddff4933
4 changed files with 438 additions and 242 deletions

View File

@ -5,15 +5,14 @@ pragma experimental ABIEncoderV2;
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
/**
* @title BaseUniswapAdapter
* @notice Implements the logic for performing assets swaps in Uniswap V2
@ -23,23 +22,24 @@ contract BaseUniswapAdapter {
using SafeMath for uint256;
using PercentageMath for uint256;
using SafeERC20 for IERC20;
using ReserveConfiguration for ReserveConfiguration.Map;
enum LeftoverAction {DEPOSIT, TRANSFER}
// Max slippage percent allow by param
uint256 public constant MAX_SLIPPAGE_PERCENT = 3000; // 30%
// Min slippage percent allow by param
uint256 public constant MIN_SLIPPAGE_PERCENT = 10; // 0,1%
ILendingPoolAddressesProvider public immutable addressesProvider;
IUniswapV2Router02 public immutable uniswapRouter;
ILendingPool public immutable pool;
ILendingPool public immutable POOL;
IPriceOracleGetter public immutable ORACLE;
IUniswapV2Router02 public immutable UNISWAP_ROUTER;
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
constructor(ILendingPoolAddressesProvider _addressesProvider, IUniswapV2Router02 _uniswapRouter) public {
addressesProvider = _addressesProvider;
pool = ILendingPool(_addressesProvider.getLendingPool());
uniswapRouter = _uniswapRouter;
constructor(ILendingPoolAddressesProvider addressesProvider, IUniswapV2Router02 uniswapRouter) public {
POOL = ILendingPool(addressesProvider.getLendingPool());
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
UNISWAP_ROUTER = uniswapRouter;
}
/**
@ -58,7 +58,7 @@ contract BaseUniswapAdapter {
path[0] = reserveIn;
path[1] = reserveOut;
uint256[] memory amounts = uniswapRouter.getAmountsOut(amountIn, path);
uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(amountIn, path);
return amounts[1];
}
@ -79,7 +79,7 @@ contract BaseUniswapAdapter {
path[0] = reserveIn;
path[1] = reserveOut;
uint256[] memory amounts = uniswapRouter.getAmountsIn(amountOut, path);
uint256[] memory amounts = UNISWAP_ROUTER.getAmountsIn(amountOut, path);
return amounts[0];
}
@ -101,24 +101,23 @@ contract BaseUniswapAdapter {
internal
returns (uint256)
{
uint256 fromAssetDecimals = getDecimals(assetToSwapFrom);
uint256 toAssetDecimals = getDecimals(assetToSwapTo);
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
(uint256 fromAssetPrice, uint256 toAssetPrice) = getPrices(assetToSwapFrom, assetToSwapTo);
uint256 fromAssetPrice = _getPrice(assetToSwapFrom);
uint256 toAssetPrice = _getPrice(assetToSwapTo);
uint256 amountOutMin = amountToSwap
.mul(fromAssetPrice.mul(10**toAssetDecimals))
.div(toAssetPrice.mul(10**fromAssetDecimals))
.percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(slippage));
IERC20(assetToSwapFrom).approve(address(uniswapRouter), amountToSwap);
IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), amountToSwap);
address[] memory path = new address[](2);
path[0] = assetToSwapFrom;
path[1] = assetToSwapTo;
uint256[] memory amounts = uniswapRouter.swapExactTokensForTokens(amountToSwap, amountOutMin, path, address(this), block.timestamp);
require(amounts[1] >= amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT');
uint256[] memory amounts = UNISWAP_ROUTER.swapExactTokensForTokens(amountToSwap, amountOutMin, path, address(this), block.timestamp);
emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]);
@ -143,10 +142,11 @@ contract BaseUniswapAdapter {
internal
returns (uint256)
{
uint256 fromAssetDecimals = getDecimals(assetToSwapFrom);
uint256 toAssetDecimals = getDecimals(assetToSwapTo);
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
(uint256 fromAssetPrice, uint256 toAssetPrice) = getPrices(assetToSwapFrom, assetToSwapTo);
uint256 fromAssetPrice = _getPrice(assetToSwapFrom);
uint256 toAssetPrice = _getPrice(assetToSwapTo);
uint256 expectedMaxAmountToSwap = amountToReceive
.mul(toAssetPrice.mul(10**fromAssetDecimals))
@ -155,14 +155,12 @@ contract BaseUniswapAdapter {
require(maxAmountToSwap < expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage');
IERC20(assetToSwapFrom).approve(address(uniswapRouter), maxAmountToSwap);
IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), maxAmountToSwap);
address[] memory path = new address[](2);
path[0] = assetToSwapFrom;
path[1] = assetToSwapTo;
uint256[] memory amounts = uniswapRouter.swapTokensForExactTokens(amountToReceive, maxAmountToSwap, path, address(this), block.timestamp);
require(amounts[1] >= amountToReceive, 'INSUFFICIENT_OUTPUT_AMOUNT');
uint256[] memory amounts = UNISWAP_ROUTER.swapTokensForExactTokens(amountToReceive, maxAmountToSwap, path, address(this), block.timestamp);
emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]);
@ -170,34 +168,20 @@ contract BaseUniswapAdapter {
}
/**
* @dev Get assets prices from the oracle denominated in eth
* @param assetToSwapFrom first asset
* @param assetToSwapTo second asset
* @return fromAssetPrice eth price for the first asset
* @return toAssetPrice eth price for the second asset
* @dev Get the price of the asset from the oracle denominated in eth
* @param asset address
* @return eth price for the asset
*/
function getPrices(
address assetToSwapFrom,
address assetToSwapTo
)
internal
view
returns (uint256 fromAssetPrice, uint256 toAssetPrice)
{
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
fromAssetPrice = oracle.getAssetPrice(assetToSwapFrom);
toAssetPrice = oracle.getAssetPrice(assetToSwapTo);
function _getPrice(address asset) internal view returns (uint256) {
return ORACLE.getAssetPrice(asset);
}
/**
* @dev Get the decimals of an asset
* @return number of decimals of the asset
*/
function getDecimals(address asset) internal view returns (uint256) {
ReserveConfiguration.Map memory configuration = pool.getConfiguration(asset);
(, , , uint256 decimals, ) = configuration.getParamsMemory();
return decimals;
function _getDecimals(address asset) internal view returns (uint256) {
return IERC20Detailed(asset).decimals();
}
/**
@ -205,7 +189,7 @@ contract BaseUniswapAdapter {
* @return address of the aToken
*/
function getAToken(address asset) internal view returns (address) {
ReserveLogic.ReserveData memory reserve = pool.getReserveData(asset);
ReserveLogic.ReserveData memory reserve = POOL.getReserveData(asset);
return reserve.aTokenAddress;
}
@ -213,19 +197,19 @@ contract BaseUniswapAdapter {
* @dev Take action with the swap left overs as configured in the parameters
* @param asset address of the asset
* @param reservedAmount Amount reserved to be used by the contract to repay the flash loan
* @param leftOverAction Flag indicating what to do with the left over balance from the swap:
* @param leftOverAction enum indicating what to do with the left over balance from the swap:
* (0) Deposit back
* (1) Direct transfer to user
* @param user address
*/
function sendLeftOver(address asset, uint256 reservedAmount, uint256 leftOverAction, address user) internal {
function sendLeftovers(address asset, uint256 reservedAmount, LeftoverAction leftOverAction, address user) internal {
uint256 balance = IERC20(asset).balanceOf(address(this));
uint256 assetLeftOver = balance.sub(reservedAmount);
if (assetLeftOver > 0) {
if (leftOverAction == 0) {
IERC20(asset).approve(address(pool), balance);
pool.deposit(asset, assetLeftOver, user, 0);
if (leftOverAction == LeftoverAction.DEPOSIT) {
IERC20(asset).approve(address(POOL), balance);
POOL.deposit(asset, assetLeftOver, user, 0);
} else {
IERC20(asset).transfer(user, assetLeftOver);
}
@ -249,7 +233,7 @@ contract BaseUniswapAdapter {
IERC20(reserveAToken).safeTransferFrom(user, address(this), amount);
// withdraw reserve
pool.withdraw(reserve, amount);
POOL.withdraw(reserve, amount, address(this));
}
/**
@ -266,6 +250,6 @@ contract BaseUniswapAdapter {
pullAToken(reserve, user, flashLoanDebt);
// Repay flashloan
IERC20(reserve).approve(address(pool), flashLoanDebt);
IERC20(reserve).approve(address(POOL), flashLoanDebt);
}
}

View File

@ -16,11 +16,11 @@ import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
constructor(
ILendingPoolAddressesProvider _addressesProvider,
IUniswapV2Router02 _uniswapRouter
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter
)
public
BaseUniswapAdapter(_addressesProvider, _uniswapRouter)
BaseUniswapAdapter(addressesProvider, uniswapRouter)
{}
/**
@ -31,32 +31,34 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* @param assets Address to be swapped
* @param amounts Amount of the reserve to be swapped
* @param premiums Fee of the flash loan
* @param initiator Address of the user
* @param params Additional variadic field to include extra params. Expected parameters:
* address assetToSwapTo Address of the reserve to be swapped to and deposited
* address user The address of the user
* address[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited
* uint256 slippage The max slippage percentage allowed for the swap
*/
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
(
address assetToSwapTo,
address user,
uint256 slippage
) = abi.decode(params, (address, address, uint256));
require(msg.sender == address(POOL), "CALLER_MUST_BE_LENDING_POOL");
(address[] memory assetToSwapToList, uint256 slippage) = abi.decode(params, (address[], uint256));
require(slippage < MAX_SLIPPAGE_PERCENT && slippage >= MIN_SLIPPAGE_PERCENT, 'SLIPPAGE_OUT_OF_RANGE');
require(assetToSwapToList.length == assets.length, 'INCONSISTENT_PARAMS');
uint256 receivedAmount = swapExactTokensForTokens(assets[0], assetToSwapTo, amounts[0], slippage);
for (uint256 i = 0; i < assets.length; i++) {
uint256 receivedAmount = swapExactTokensForTokens(assets[i], assetToSwapToList[i], amounts[i], slippage);
// Deposit new reserve
IERC20(assetToSwapTo).approve(address(pool), receivedAmount);
pool.deposit(assetToSwapTo, receivedAmount, user, 0);
// Deposit new reserve
IERC20(assetToSwapToList[i]).approve(address(POOL), receivedAmount);
POOL.deposit(assetToSwapToList[i], receivedAmount, initiator, 0);
uint256 flashLoanDebt = amounts[0].add(premiums[0]);
pullATokenAndRepayFlashLoan(assets[0], user, flashLoanDebt);
uint256 flashLoanDebt = amounts[i].add(premiums[i]);
pullATokenAndRepayFlashLoan(assets[i], initiator, flashLoanDebt);
}
return true;
}
@ -66,25 +68,35 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* This method can be used when the user has no debts.
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and
* perform the swap.
* @param assetToSwapFrom Address of the underlying asset to be swap from
* @param assetToSwapTo Address of the underlying asset to be swap to and deposited
* @param amountToSwap How much `assetToSwapFrom` needs to be swapped
* @param user Address that will be pulling the swapped funds
* @param assetToSwapFromList List of addresses of the underlying asset to be swap from
* @param assetToSwapToList List of addresses of the underlying asset to be swap to and deposited
* @param amountToSwapList List of amounts to be swapped
* @param slippage The max slippage percentage allowed for the swap
*/
function swapAndDeposit(
address assetToSwapFrom,
address assetToSwapTo,
uint256 amountToSwap,
address user,
address[] calldata assetToSwapFromList,
address[] calldata assetToSwapToList,
uint256[] calldata amountToSwapList,
uint256 slippage
) external {
pullAToken(assetToSwapFrom, user, amountToSwap);
require(
assetToSwapFromList.length == assetToSwapToList.length && assetToSwapFromList.length == amountToSwapList.length,
'INCONSISTENT_PARAMS'
);
uint256 receivedAmount = swapExactTokensForTokens(assetToSwapFrom, assetToSwapTo, amountToSwap, slippage);
for (uint256 i = 0; i < assetToSwapFromList.length; i++) {
pullAToken(assetToSwapFromList[i], msg.sender, amountToSwapList[i]);
// Deposit new reserve
IERC20(assetToSwapTo).approve(address(pool), receivedAmount);
pool.deposit(assetToSwapTo, receivedAmount, user, 0);
uint256 receivedAmount = swapExactTokensForTokens(
assetToSwapFromList[i],
assetToSwapToList[i],
amountToSwapList[i],
slippage
);
// Deposit new reserve
IERC20(assetToSwapToList[i]).approve(address(POOL), receivedAmount);
POOL.deposit(assetToSwapToList[i], receivedAmount, msg.sender, 0);
}
}
}

View File

@ -15,12 +15,19 @@ import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
**/
contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
struct RepayParams {
address[] assetToSwapToList;
LeftoverAction leftOverAction;
uint256[] repayAmounts;
uint256[] rateModes;
}
constructor(
ILendingPoolAddressesProvider _addressesProvider,
IUniswapV2Router02 _uniswapRouter
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter
)
public
BaseUniswapAdapter(_addressesProvider, _uniswapRouter)
BaseUniswapAdapter(addressesProvider, uniswapRouter)
{}
/**
@ -31,41 +38,102 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* @param assets Address to be swapped
* @param amounts Amount of the reserve to be swapped
* @param premiums Fee of the flash loan
* @param initiator Address of the user
* @param params Additional variadic field to include extra params. Expected parameters:
* address assetToSwapTo Address of the reserve to be swapped to and deposited
* address user The address of the user
* address[] assetToSwapToList List of the addresses of the reserve to be swapped to and repay
* uint256 leftOverAction Flag indicating what to do with the left over balance from the swap:
* (0) Deposit back
* (1) Direct transfer to user
* uint256 repayAmount Amount of debt to be repaid
* uint256 rateMode The rate modes of the debt to be repaid
* uint256[] repayAmounts List of amounts of debt to be repaid
* uint256[] rateModes List of the rate modes of the debt to be repaid
*/
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
(
address assetToSwapTo,
address user,
uint256 leftOverAction,
uint256 repayAmount,
uint256 rateMode
) = abi.decode(params, (address, address, uint256, uint256, uint256));
require(msg.sender == address(POOL), "CALLER_MUST_BE_LENDING_POOL");
swapTokensForExactTokens(assets[0], assetToSwapTo, amounts[0], repayAmount);
RepayParams memory decodedParams = _decodeParams(params);
// Repay debt
IERC20(assetToSwapTo).approve(address(pool), repayAmount);
pool.repay(assetToSwapTo, repayAmount, rateMode, user);
require(
assets.length == decodedParams.assetToSwapToList.length
&& assets.length == decodedParams.repayAmounts.length
&& assets.length == decodedParams.rateModes.length,
'INCONSISTENT_PARAMS');
uint256 flashLoanDebt = amounts[0].add(premiums[0]);
pullATokenAndRepayFlashLoan(assets[0], user, flashLoanDebt);
// Take care of reserve leftover from the swap
sendLeftOver(assets[0], flashLoanDebt, leftOverAction, user);
for (uint256 i = 0; i < assets.length; i++) {
_swapAndRepay(
assets[i],
decodedParams.assetToSwapToList[i],
amounts[i],
decodedParams.repayAmounts[i],
decodedParams.rateModes[i],
initiator,
decodedParams.leftOverAction,
premiums[i]
);
}
return true;
}
/**
* @dev Perform the swap, the repay of the debt and send back the left overs
*
* @param assetFrom Address of token to be swapped
* @param assetTo Address of token to be received
* @param amount Amount of the reserve to be swapped
* @param repayAmount Amount of the debt to be repaid
* @param rateMode Rate mode of the debt to be repaid
* @param initiator Address of the user
* @param leftOverAction enum indicating what to do with the left over balance from the swap
* @param premium Fee of the flash loan
*/
function _swapAndRepay(
address assetFrom,
address assetTo,
uint256 amount,
uint256 repayAmount,
uint256 rateMode,
address initiator,
LeftoverAction leftOverAction,
uint256 premium
) internal {
swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount);
// Repay debt
IERC20(assetTo).approve(address(POOL), repayAmount);
POOL.repay(assetTo, repayAmount, rateMode, initiator);
uint256 flashLoanDebt = amount.add(premium);
pullATokenAndRepayFlashLoan(assetFrom, initiator, flashLoanDebt);
// Take care of reserve leftover from the swap
sendLeftovers(assetFrom, flashLoanDebt, leftOverAction, initiator);
}
/**
* @dev Decodes debt information encoded in flashloan params
* @param params Additional variadic field to include extra params. Expected parameters:
* address[] assetToSwapToList List of the addresses of the reserve to be swapped to and repay
* uint256 leftOverAction Flag indicating what to do with the left over balance from the swap:
* (0) Deposit back
* (1) Direct transfer to user
* uint256[] repayAmounts List of amounts of debt to be repaid
* uint256[] rateModes List of the rate modes of the debt to be repaid
* @return RepayParams struct containing decoded params
*/
function _decodeParams(bytes memory params) internal returns (RepayParams memory) {
(
address[] memory assetToSwapToList,
LeftoverAction leftOverAction,
uint256[] memory repayAmounts,
uint256[] memory rateModes
) = abi.decode(params, (address[], LeftoverAction, uint256[], uint256[]));
return RepayParams(assetToSwapToList, leftOverAction, repayAmounts, rateModes);
}
}

View File

@ -121,8 +121,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
// 0,5% slippage
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256'],
[dai.address, userAddress, 50]
['address[]', 'uint256'],
[[dai.address], 50]
);
await expect(
@ -132,7 +132,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapLiquiditySwapAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -158,6 +158,90 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap));
});
it('should revert if inconsistent params', async () => {
const {users, weth, oracle, dai, aWETH, pool, uniswapLiquiditySwapAdapter} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
await mockUniswapRouter.setAmountToReturn(expectedDaiAmount);
// User will swap liquidity 10 aEth to aDai
const liquidityToSwap = parseEther('10');
await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
// 0,5% slippage
const params = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256'],
[[dai.address, weth.address], 50]
);
await expect(
pool
.connect(user)
.flashLoan(
uniswapLiquiditySwapAdapter.address,
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params,
0
)
).to.be.revertedWith('INCONSISTENT_PARAMS');
});
it('should revert if caller not lending pool', async () => {
const {users, weth, oracle, dai, aWETH, uniswapLiquiditySwapAdapter} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
await mockUniswapRouter.setAmountToReturn(expectedDaiAmount);
// User will swap liquidity 10 aEth to aDai
const liquidityToSwap = parseEther('10');
await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
// 0,5% slippage
const params = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256'],
[[dai.address, weth.address], 50]
);
await expect(
uniswapLiquiditySwapAdapter
.connect(user)
.executeOperation(
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params
)
).to.be.revertedWith('CALLER_MUST_BE_LENDING_POOL');
});
it('should work correctly with tokens of different decimals', async () => {
const {
users,
@ -215,8 +299,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
// 0,5% slippage
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256'],
[dai.address, userAddress, 50]
['address[]', 'uint256'],
[[dai.address], 50]
);
await expect(
@ -226,7 +310,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapLiquiditySwapAdapter.address,
[usdc.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -275,14 +359,14 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
// 30% slippage
const params1 = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256'],
[dai.address, userAddress, 3000]
['address[]', 'uint256'],
[[dai.address], 3000]
);
// 0,05% slippage
const params2 = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256'],
[dai.address, userAddress, 5]
['address[]', 'uint256'],
[[dai.address], 5]
);
await expect(
@ -292,7 +376,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapLiquiditySwapAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params1,
0
@ -305,62 +389,13 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapLiquiditySwapAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params2,
0
)
).to.be.revertedWith('SLIPPAGE_OUT_OF_RANGE');
});
it('should revert when swap exceed slippage', async () => {
const {users, weth, oracle, dai, aWETH, pool, uniswapLiquiditySwapAdapter} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
await weth.connect(user).mint(amountWETHtoSwap);
await weth.connect(user).transfer(uniswapLiquiditySwapAdapter.address, amountWETHtoSwap);
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
// 1,5% slippage
const returnedDaiAmountWithBigSlippage = new BigNumber(expectedDaiAmount.toString())
.multipliedBy(0.985)
.toFixed(0);
await mockUniswapRouter.connect(user).setAmountToReturn(returnedDaiAmountWithBigSlippage);
// User will swap liquidity 10 aEth to aDai
const liquidityToSwap = parseEther('10');
await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
// 0,5% slippage
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256'],
[dai.address, userAddress, 50]
);
await expect(
pool
.connect(user)
.flashLoan(
uniswapLiquiditySwapAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
userAddress,
params,
0
)
).to.be.revertedWith('INSUFFICIENT_OUTPUT_AMOUNT');
});
});
describe('swapAndDeposit', () => {
@ -400,13 +435,9 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
const userAEthBalanceBefore = await aWETH.balanceOf(userAddress);
await expect(
uniswapLiquiditySwapAdapter.swapAndDeposit(
weth.address,
dai.address,
amountWETHtoSwap,
userAddress,
50
)
uniswapLiquiditySwapAdapter
.connect(user)
.swapAndDeposit([weth.address], [dai.address], [amountWETHtoSwap], 50)
)
.to.emit(uniswapLiquiditySwapAdapter, 'Swapped')
.withArgs(weth.address, dai.address, amountWETHtoSwap.toString(), expectedDaiAmount);
@ -427,6 +458,30 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
expect(userAEthBalance).to.be.lt(userAEthBalanceBefore);
expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap));
});
it('should revert if inconsistent params', async () => {
const {users, weth, dai, uniswapLiquiditySwapAdapter} = testEnv;
const user = users[0].signer;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
await expect(
uniswapLiquiditySwapAdapter
.connect(user)
.swapAndDeposit([weth.address, dai.address], [dai.address], [amountWETHtoSwap], 50)
).to.be.revertedWith('INCONSISTENT_PARAMS');
await expect(
uniswapLiquiditySwapAdapter
.connect(user)
.swapAndDeposit([weth.address], [dai.address, weth.address], [amountWETHtoSwap], 50)
).to.be.revertedWith('INCONSISTENT_PARAMS');
await expect(
uniswapLiquiditySwapAdapter
.connect(user)
.swapAndDeposit([weth.address], [dai.address], [amountWETHtoSwap, amountWETHtoSwap], 50)
).to.be.revertedWith('INCONSISTENT_PARAMS');
});
});
});
@ -506,8 +561,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 0, expectedDaiAmount, 1]
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1]]
);
await expect(
@ -517,7 +572,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -539,6 +594,132 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap));
});
it('should revert if inconsistent params', async () => {
const {users, pool, weth, aWETH, oracle, dai, uniswapRepayAdapter} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
// Open user Debt
await pool.connect(user).borrow(dai.address, expectedDaiAmount, 1, 0, userAddress);
const liquidityToSwap = amountWETHtoSwap;
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(flashloanAmount);
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params1 = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address, weth.address], 0, [expectedDaiAmount], [1]]
);
await expect(
pool
.connect(user)
.flashLoan(
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params1,
0
)
).to.be.revertedWith('INCONSISTENT_PARAMS');
const params2 = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount, expectedDaiAmount], [1]]
);
await expect(
pool
.connect(user)
.flashLoan(
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params2,
0
)
).to.be.revertedWith('INCONSISTENT_PARAMS');
const params3 = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1, 1]]
);
await expect(
pool
.connect(user)
.flashLoan(
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params3,
0
)
).to.be.revertedWith('INCONSISTENT_PARAMS');
});
it('should revert if caller not lending pool', async () => {
const {users, pool, weth, aWETH, oracle, dai, uniswapRepayAdapter} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
// Open user Debt
await pool.connect(user).borrow(dai.address, expectedDaiAmount, 1, 0, userAddress);
const liquidityToSwap = amountWETHtoSwap;
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(flashloanAmount);
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params = ethers.utils.defaultAbiCoder.encode(
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1]]
);
await expect(
uniswapRepayAdapter
.connect(user)
.executeOperation(
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params
)
).to.be.revertedWith('CALLER_MUST_BE_LENDING_POOL');
});
it('should revert if there is not debt to repay with the specified rate mode', async () => {
const {users, pool, weth, oracle, dai, uniswapRepayAdapter, aWETH} = testEnv;
const user = users[0].signer;
@ -568,8 +749,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 0, expectedDaiAmount, 1]
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1]]
);
await expect(
@ -579,7 +760,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -613,8 +794,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 0, expectedDaiAmount, 1]
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1]]
);
await expect(
@ -624,7 +805,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -632,55 +813,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
).to.be.reverted;
});
it('should revert when the received amount is less than expected', async () => {
const {users, pool, weth, oracle, dai, aWETH, uniswapRepayAdapter} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
// Open user Debt
await pool.connect(user).borrow(dai.address, expectedDaiAmount, 1, 0, userAddress);
const liquidityToSwap = amountWETHtoSwap;
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
const insufficientOutput = new BigNumber(expectedDaiAmount.toString())
.multipliedBy(0.985)
.toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(flashloanAmount);
await mockUniswapRouter.connect(user).setAmountToReturn(insufficientOutput);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 0, expectedDaiAmount, 1]
);
await expect(
pool
.connect(user)
.flashLoan(
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
userAddress,
params,
0
)
).to.be.revertedWith('INSUFFICIENT_OUTPUT_AMOUNT');
});
it('should revert when max amount allowed to swap is bigger than max slippage', async () => {
const {users, pool, weth, oracle, dai, aWETH, uniswapRepayAdapter} = testEnv;
const user = users[0].signer;
@ -707,8 +839,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 0, expectedDaiAmount, 1]
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1]]
);
await expect(
@ -718,7 +850,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -779,8 +911,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 0, expectedDaiAmount, 1]
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 0, [expectedDaiAmount], [1]]
);
await expect(
@ -790,7 +922,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0
@ -870,8 +1002,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
const wethBalanceBefore = await weth.balanceOf(userAddress);
const params = ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256'],
[dai.address, userAddress, 1, expectedDaiAmount, 1]
['address[]', 'uint256', 'uint256[]', 'uint256[]'],
[[dai.address], 1, [expectedDaiAmount], [1]]
);
await expect(
@ -881,7 +1013,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
0,
[0],
userAddress,
params,
0