Merge branch '178-add-uniswap-adapters' into 'master'

Resolve "Add Uniswap adapter for liquidity swap and repay with collateral using flashloan"

Closes 

See merge request 
This commit is contained in:
The-3D 2021-01-14 16:07:53 +00:00
commit c85bcbdae9
26 changed files with 5142 additions and 104 deletions

View File

@ -40,4 +40,3 @@ certora-test:
- certoraRun specs/harness/StableDebtTokenHarness.sol:StableDebtTokenHarness --solc_args '--optimize' --verify StableDebtTokenHarness:specs/StableDebtToken.spec --settings -assumeUnwindCond,-b=4 --cache StableDebtToken --cloud
- certoraRun specs/harness/UserConfigurationHarness.sol --verify UserConfigurationHarness:specs/UserConfiguration.spec --solc_args '--optimize' --settings -useBitVectorTheory --cache UserConfiguration --cloud
- certoraRun contracts/protocol/tokenization/VariableDebtToken.sol:VariableDebtToken specs/harness/LendingPoolHarnessForVariableDebtToken.sol --solc_args '--optimize' --link VariableDebtToken:POOL=LendingPoolHarnessForVariableDebtToken --verify VariableDebtToken:specs/VariableDebtToken.spec --settings -assumeUnwindCond,-useNonLinearArithmetic,-b=4 --cache VariableDebtToken --cloud

View File

@ -0,0 +1,534 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {PercentageMath} from '../protocol/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 {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol';
import {FlashLoanReceiverBase} from '../flashloan/base/FlashLoanReceiverBase.sol';
import {IBaseUniswapAdapter} from './interfaces/IBaseUniswapAdapter.sol';
/**
* @title BaseUniswapAdapter
* @notice Implements the logic for performing assets swaps in Uniswap V2
* @author Aave
**/
abstract contract BaseUniswapAdapter is FlashLoanReceiverBase, IBaseUniswapAdapter, Ownable {
using SafeMath for uint256;
using PercentageMath for uint256;
using SafeERC20 for IERC20;
// Max slippage percent allowed
uint256 public constant override MAX_SLIPPAGE_PERCENT = 3000; // 30%
// FLash Loan fee set in lending pool
uint256 public constant override FLASHLOAN_PREMIUM_TOTAL = 9;
// USD oracle asset address
address public constant override USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96;
address public immutable override WETH_ADDRESS;
IPriceOracleGetter public immutable override ORACLE;
IUniswapV2Router02 public immutable override UNISWAP_ROUTER;
constructor(
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter,
address wethAddress
) public FlashLoanReceiverBase(addressesProvider) {
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
UNISWAP_ROUTER = uniswapRouter;
WETH_ADDRESS = wethAddress;
}
/**
* @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices
* @param amountIn Amount of reserveIn
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @return uint256 Amount out of the reserveOut
* @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals)
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
*/
function getAmountsOut(
uint256 amountIn,
address reserveIn,
address reserveOut
)
external
view
override
returns (
uint256,
uint256,
uint256,
uint256,
address[] memory
)
{
AmountCalc memory results = _getAmountsOutData(reserveIn, reserveOut, amountIn);
return (
results.calculatedAmount,
results.relativePrice,
results.amountInUsd,
results.amountOutUsd,
results.path
);
}
/**
* @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices
* @param amountOut Amount of reserveOut
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @return uint256 Amount in of the reserveIn
* @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals)
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
*/
function getAmountsIn(
uint256 amountOut,
address reserveIn,
address reserveOut
)
external
view
override
returns (
uint256,
uint256,
uint256,
uint256,
address[] memory
)
{
AmountCalc memory results = _getAmountsInData(reserveIn, reserveOut, amountOut);
return (
results.calculatedAmount,
results.relativePrice,
results.amountInUsd,
results.amountOutUsd,
results.path
);
}
/**
* @dev Swaps an exact `amountToSwap` of an asset to another
* @param assetToSwapFrom Origin asset
* @param assetToSwapTo Destination asset
* @param amountToSwap Exact amount of `assetToSwapFrom` to be swapped
* @param minAmountOut the min amount of `assetToSwapTo` to be received from the swap
* @return the amount received from the swap
*/
function _swapExactTokensForTokens(
address assetToSwapFrom,
address assetToSwapTo,
uint256 amountToSwap,
uint256 minAmountOut,
bool useEthPath
) internal returns (uint256) {
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
uint256 fromAssetPrice = _getPrice(assetToSwapFrom);
uint256 toAssetPrice = _getPrice(assetToSwapTo);
uint256 expectedMinAmountOut =
amountToSwap
.mul(fromAssetPrice.mul(10**toAssetDecimals))
.div(toAssetPrice.mul(10**fromAssetDecimals))
.percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(MAX_SLIPPAGE_PERCENT));
require(expectedMinAmountOut < minAmountOut, 'minAmountOut exceed max slippage');
// Approves the transfer for the swap. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), 0);
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), amountToSwap);
address[] memory path;
if (useEthPath) {
path = new address[](3);
path[0] = assetToSwapFrom;
path[1] = WETH_ADDRESS;
path[2] = assetToSwapTo;
} else {
path = new address[](2);
path[0] = assetToSwapFrom;
path[1] = assetToSwapTo;
}
uint256[] memory amounts =
UNISWAP_ROUTER.swapExactTokensForTokens(
amountToSwap,
minAmountOut,
path,
address(this),
block.timestamp
);
emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[amounts.length - 1]);
return amounts[amounts.length - 1];
}
/**
* @dev Receive an exact amount `amountToReceive` of `assetToSwapTo` tokens for as few `assetToSwapFrom` tokens as
* possible.
* @param assetToSwapFrom Origin asset
* @param assetToSwapTo Destination asset
* @param maxAmountToSwap Max amount of `assetToSwapFrom` allowed to be swapped
* @param amountToReceive Exact amount of `assetToSwapTo` to receive
* @return the amount swapped
*/
function _swapTokensForExactTokens(
address assetToSwapFrom,
address assetToSwapTo,
uint256 maxAmountToSwap,
uint256 amountToReceive,
bool useEthPath
) internal returns (uint256) {
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
uint256 fromAssetPrice = _getPrice(assetToSwapFrom);
uint256 toAssetPrice = _getPrice(assetToSwapTo);
uint256 expectedMaxAmountToSwap =
amountToReceive
.mul(toAssetPrice.mul(10**fromAssetDecimals))
.div(fromAssetPrice.mul(10**toAssetDecimals))
.percentMul(PercentageMath.PERCENTAGE_FACTOR.add(MAX_SLIPPAGE_PERCENT));
require(maxAmountToSwap < expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage');
// Approves the transfer for the swap. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), 0);
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), maxAmountToSwap);
address[] memory path;
if (useEthPath) {
path = new address[](3);
path[0] = assetToSwapFrom;
path[1] = WETH_ADDRESS;
path[2] = assetToSwapTo;
} else {
path = new address[](2);
path[0] = assetToSwapFrom;
path[1] = assetToSwapTo;
}
uint256[] memory amounts =
UNISWAP_ROUTER.swapTokensForExactTokens(
amountToReceive,
maxAmountToSwap,
path,
address(this),
block.timestamp
);
emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[amounts.length - 1]);
return amounts[0];
}
/**
* @dev Get the price of the asset from the oracle denominated in eth
* @param asset address
* @return eth price for the asset
*/
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) {
return IERC20Detailed(asset).decimals();
}
/**
* @dev Get the aToken associated to the asset
* @return address of the aToken
*/
function _getReserveData(address asset) internal view returns (DataTypes.ReserveData memory) {
return LENDING_POOL.getReserveData(asset);
}
/**
* @dev Pull the ATokens from the user
* @param reserve address of the asset
* @param reserveAToken address of the aToken of the reserve
* @param user address
* @param amount of tokens to be transferred to the contract
* @param permitSignature struct containing the permit signature
*/
function _pullAToken(
address reserve,
address reserveAToken,
address user,
uint256 amount,
PermitSignature memory permitSignature
) internal {
if (_usePermit(permitSignature)) {
IERC20WithPermit(reserveAToken).permit(
user,
address(this),
permitSignature.amount,
permitSignature.deadline,
permitSignature.v,
permitSignature.r,
permitSignature.s
);
}
// transfer from user to adapter
IERC20(reserveAToken).safeTransferFrom(user, address(this), amount);
// withdraw reserve
LENDING_POOL.withdraw(reserve, amount, address(this));
}
/**
* @dev Tells if the permit method should be called by inspecting if there is a valid signature.
* If signature params are set to 0, then permit won't be called.
* @param signature struct containing the permit signature
* @return whether or not permit should be called
*/
function _usePermit(PermitSignature memory signature) internal pure returns (bool) {
return
!(uint256(signature.deadline) == uint256(signature.v) && uint256(signature.deadline) == 0);
}
/**
* @dev Calculates the value denominated in USD
* @param reserve Address of the reserve
* @param amount Amount of the reserve
* @param decimals Decimals of the reserve
* @return whether or not permit should be called
*/
function _calcUsdValue(
address reserve,
uint256 amount,
uint256 decimals
) internal view returns (uint256) {
uint256 ethUsdPrice = _getPrice(USD_ADDRESS);
uint256 reservePrice = _getPrice(reserve);
return amount.mul(reservePrice).div(10**decimals).mul(ethUsdPrice).div(10**18);
}
/**
* @dev Given an input asset amount, returns the maximum output amount of the other asset
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @param amountIn Amount of reserveIn
* @return Struct containing the following information:
* uint256 Amount out of the reserveOut
* uint256 The price of out amount denominated in the reserveIn currency (18 decimals)
* uint256 In amount of reserveIn value denominated in USD (8 decimals)
* uint256 Out amount of reserveOut value denominated in USD (8 decimals)
*/
function _getAmountsOutData(
address reserveIn,
address reserveOut,
uint256 amountIn
) internal view returns (AmountCalc memory) {
// Subtract flash loan fee
uint256 finalAmountIn = amountIn.sub(amountIn.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000));
address[] memory simplePath = new address[](2);
simplePath[0] = reserveIn;
simplePath[1] = reserveOut;
uint256[] memory amountsWithoutWeth;
uint256[] memory amountsWithWeth;
address[] memory pathWithWeth = new address[](3);
if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) {
pathWithWeth[0] = reserveIn;
pathWithWeth[1] = WETH_ADDRESS;
pathWithWeth[2] = reserveOut;
try UNISWAP_ROUTER.getAmountsOut(finalAmountIn, pathWithWeth) returns (
uint256[] memory resultsWithWeth
) {
amountsWithWeth = resultsWithWeth;
} catch {
amountsWithWeth = new uint256[](3);
}
} else {
amountsWithWeth = new uint256[](3);
}
uint256 bestAmountOut;
try UNISWAP_ROUTER.getAmountsOut(finalAmountIn, simplePath) returns (
uint256[] memory resultAmounts
) {
amountsWithoutWeth = resultAmounts;
bestAmountOut = (amountsWithWeth[2] > amountsWithoutWeth[1])
? amountsWithWeth[2]
: amountsWithoutWeth[1];
} catch {
amountsWithoutWeth = new uint256[](2);
bestAmountOut = amountsWithWeth[2];
}
uint256 reserveInDecimals = _getDecimals(reserveIn);
uint256 reserveOutDecimals = _getDecimals(reserveOut);
uint256 outPerInPrice =
finalAmountIn.mul(10**18).mul(10**reserveOutDecimals).div(
bestAmountOut.mul(10**reserveInDecimals)
);
return
AmountCalc(
bestAmountOut,
outPerInPrice,
_calcUsdValue(reserveIn, amountIn, reserveInDecimals),
_calcUsdValue(reserveOut, bestAmountOut, reserveOutDecimals),
(bestAmountOut == 0) ? new address[](2) : (bestAmountOut == amountsWithoutWeth[1])
? simplePath
: pathWithWeth
);
}
/**
* @dev Returns the minimum input asset amount required to buy the given output asset amount
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @param amountOut Amount of reserveOut
* @return Struct containing the following information:
* uint256 Amount in of the reserveIn
* uint256 The price of in amount denominated in the reserveOut currency (18 decimals)
* uint256 In amount of reserveIn value denominated in USD (8 decimals)
* uint256 Out amount of reserveOut value denominated in USD (8 decimals)
*/
function _getAmountsInData(
address reserveIn,
address reserveOut,
uint256 amountOut
) internal view returns (AmountCalc memory) {
(uint256[] memory amounts, address[] memory path) =
_getAmountsInAndPath(reserveIn, reserveOut, amountOut);
// Add flash loan fee
uint256 finalAmountIn = amounts[0].add(amounts[0].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000));
uint256 reserveInDecimals = _getDecimals(reserveIn);
uint256 reserveOutDecimals = _getDecimals(reserveOut);
uint256 inPerOutPrice =
amountOut.mul(10**18).mul(10**reserveInDecimals).div(
finalAmountIn.mul(10**reserveOutDecimals)
);
return
AmountCalc(
finalAmountIn,
inPerOutPrice,
_calcUsdValue(reserveIn, finalAmountIn, reserveInDecimals),
_calcUsdValue(reserveOut, amountOut, reserveOutDecimals),
path
);
}
/**
* @dev Calculates the input asset amount required to buy the given output asset amount
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @param amountOut Amount of reserveOut
* @return uint256[] amounts Array containing the amountIn and amountOut for a swap
*/
function _getAmountsInAndPath(
address reserveIn,
address reserveOut,
uint256 amountOut
) internal view returns (uint256[] memory, address[] memory) {
address[] memory simplePath = new address[](2);
simplePath[0] = reserveIn;
simplePath[1] = reserveOut;
uint256[] memory amountsWithoutWeth;
uint256[] memory amountsWithWeth;
address[] memory pathWithWeth = new address[](3);
if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) {
pathWithWeth[0] = reserveIn;
pathWithWeth[1] = WETH_ADDRESS;
pathWithWeth[2] = reserveOut;
try UNISWAP_ROUTER.getAmountsIn(amountOut, pathWithWeth) returns (
uint256[] memory resultsWithWeth
) {
amountsWithWeth = resultsWithWeth;
} catch {
amountsWithWeth = new uint256[](3);
}
} else {
amountsWithWeth = new uint256[](3);
}
try UNISWAP_ROUTER.getAmountsIn(amountOut, simplePath) returns (
uint256[] memory resultAmounts
) {
amountsWithoutWeth = resultAmounts;
return
(amountsWithWeth[0] < amountsWithoutWeth[0] && amountsWithWeth[0] != 0)
? (amountsWithWeth, pathWithWeth)
: (amountsWithoutWeth, simplePath);
} catch {
return (amountsWithWeth, pathWithWeth);
}
}
/**
* @dev Calculates the input asset amount required to buy the given output asset amount
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @param amountOut Amount of reserveOut
* @return uint256[] amounts Array containing the amountIn and amountOut for a swap
*/
function _getAmountsIn(
address reserveIn,
address reserveOut,
uint256 amountOut,
bool useEthPath
) internal view returns (uint256[] memory) {
address[] memory path;
if (useEthPath) {
path = new address[](3);
path[0] = reserveIn;
path[1] = WETH_ADDRESS;
path[2] = reserveOut;
} else {
path = new address[](2);
path[0] = reserveIn;
path[1] = reserveOut;
}
return UNISWAP_ROUTER.getAmountsIn(amountOut, path);
}
/**
* @dev Emergency rescue for token stucked on this contract, as failsafe mechanism
* - Funds should never remain in this contract more time than during transactions
* - Only callable by the owner
**/
function rescueTokens(IERC20 token) external onlyOwner {
token.transfer(owner(), token.balanceOf(address(this)));
}
}

View File

@ -0,0 +1,280 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
/**
* @title UniswapLiquiditySwapAdapter
* @notice Uniswap V2 Adapter to swap liquidity.
* @author Aave
**/
contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter {
struct PermitParams {
uint256[] amount;
uint256[] deadline;
uint8[] v;
bytes32[] r;
bytes32[] s;
}
struct SwapParams {
address[] assetToSwapToList;
uint256[] minAmountsToReceive;
bool[] swapAllBalance;
PermitParams permitParams;
bool[] useEthPath;
}
constructor(
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter,
address wethAddress
) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {}
/**
* @dev Swaps the received reserve amount from the flash loan into the asset specified in the params.
* The received funds from the swap are then deposited into the protocol on behalf of the user.
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and
* repay the flash loan.
* @param assets Address of asset to be swapped
* @param amounts Amount of the asset 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[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited
* uint256[] minAmountsToReceive List of min amounts to be received from the swap
* bool[] swapAllBalance Flag indicating if all the user balance should be swapped
* uint256[] permitAmount List of amounts for the permit signature
* uint256[] deadline List of deadlines for the permit signature
* uint8[] v List of v param for the permit signature
* bytes32[] r List of r param for the permit signature
* bytes32[] s List of s param for the permit signature
*/
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL');
SwapParams memory decodedParams = _decodeParams(params);
require(
assets.length == decodedParams.assetToSwapToList.length &&
assets.length == decodedParams.minAmountsToReceive.length &&
assets.length == decodedParams.swapAllBalance.length &&
assets.length == decodedParams.permitParams.amount.length &&
assets.length == decodedParams.permitParams.deadline.length &&
assets.length == decodedParams.permitParams.v.length &&
assets.length == decodedParams.permitParams.r.length &&
assets.length == decodedParams.permitParams.s.length &&
assets.length == decodedParams.useEthPath.length,
'INCONSISTENT_PARAMS'
);
for (uint256 i = 0; i < assets.length; i++) {
_swapLiquidity(
assets[i],
decodedParams.assetToSwapToList[i],
amounts[i],
premiums[i],
initiator,
decodedParams.minAmountsToReceive[i],
decodedParams.swapAllBalance[i],
PermitSignature(
decodedParams.permitParams.amount[i],
decodedParams.permitParams.deadline[i],
decodedParams.permitParams.v[i],
decodedParams.permitParams.r[i],
decodedParams.permitParams.s[i]
),
decodedParams.useEthPath[i]
);
}
return true;
}
struct SwapAndDepositLocalVars {
uint256 i;
uint256 aTokenInitiatorBalance;
uint256 amountToSwap;
uint256 receivedAmount;
address aToken;
}
/**
* @dev Swaps an amount of an asset to another and deposits the new asset amount on behalf of the user without using
* a flash loan. This method can be used when the temporary transfer of the collateral asset to this contract
* does not affect the user position.
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and
* perform the swap.
* @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. If the amount exceeds the balance, the total balance is used for the swap
* @param minAmountsToReceive List of min amounts to be received from the swap
* @param permitParams List of struct containing the permit signatures
* uint256 permitAmount Amount for the permit signature
* uint256 deadline Deadline for the permit signature
* uint8 v param for the permit signature
* bytes32 r param for the permit signature
* bytes32 s param for the permit signature
* @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
*/
function swapAndDeposit(
address[] calldata assetToSwapFromList,
address[] calldata assetToSwapToList,
uint256[] calldata amountToSwapList,
uint256[] calldata minAmountsToReceive,
PermitSignature[] calldata permitParams,
bool[] calldata useEthPath
) external {
require(
assetToSwapFromList.length == assetToSwapToList.length &&
assetToSwapFromList.length == amountToSwapList.length &&
assetToSwapFromList.length == minAmountsToReceive.length &&
assetToSwapFromList.length == permitParams.length,
'INCONSISTENT_PARAMS'
);
SwapAndDepositLocalVars memory vars;
for (vars.i = 0; vars.i < assetToSwapFromList.length; vars.i++) {
vars.aToken = _getReserveData(assetToSwapFromList[vars.i]).aTokenAddress;
vars.aTokenInitiatorBalance = IERC20(vars.aToken).balanceOf(msg.sender);
vars.amountToSwap = amountToSwapList[vars.i] > vars.aTokenInitiatorBalance
? vars.aTokenInitiatorBalance
: amountToSwapList[vars.i];
_pullAToken(
assetToSwapFromList[vars.i],
vars.aToken,
msg.sender,
vars.amountToSwap,
permitParams[vars.i]
);
vars.receivedAmount = _swapExactTokensForTokens(
assetToSwapFromList[vars.i],
assetToSwapToList[vars.i],
vars.amountToSwap,
minAmountsToReceive[vars.i],
useEthPath[vars.i]
);
// Deposit new reserve
IERC20(assetToSwapToList[vars.i]).safeApprove(address(LENDING_POOL), 0);
IERC20(assetToSwapToList[vars.i]).safeApprove(address(LENDING_POOL), vars.receivedAmount);
LENDING_POOL.deposit(assetToSwapToList[vars.i], vars.receivedAmount, msg.sender, 0);
}
}
/**
* @dev Swaps an `amountToSwap` of an asset to another and deposits the funds on behalf of the initiator.
* @param assetFrom Address of the underlying asset to be swap from
* @param assetTo Address of the underlying asset to be swap to and deposited
* @param amount Amount from flash loan
* @param premium Premium of the flash loan
* @param minAmountToReceive Min amount to be received from the swap
* @param swapAllBalance Flag indicating if all the user balance should be swapped
* @param permitSignature List of struct containing the permit signature
* @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
*/
struct SwapLiquidityLocalVars {
address aToken;
uint256 aTokenInitiatorBalance;
uint256 amountToSwap;
uint256 receivedAmount;
uint256 flashLoanDebt;
uint256 amountToPull;
}
function _swapLiquidity(
address assetFrom,
address assetTo,
uint256 amount,
uint256 premium,
address initiator,
uint256 minAmountToReceive,
bool swapAllBalance,
PermitSignature memory permitSignature,
bool useEthPath
) internal {
SwapLiquidityLocalVars memory vars;
vars.aToken = _getReserveData(assetFrom).aTokenAddress;
vars.aTokenInitiatorBalance = IERC20(vars.aToken).balanceOf(initiator);
vars.amountToSwap =
swapAllBalance && vars.aTokenInitiatorBalance.sub(premium) <= amount
? vars.aTokenInitiatorBalance.sub(premium)
: amount;
vars.receivedAmount =
_swapExactTokensForTokens(assetFrom, assetTo, vars.amountToSwap, minAmountToReceive, useEthPath);
// Deposit new reserve
IERC20(assetTo).safeApprove(address(LENDING_POOL), 0);
IERC20(assetTo).safeApprove(address(LENDING_POOL), vars.receivedAmount);
LENDING_POOL.deposit(assetTo, vars.receivedAmount, initiator, 0);
vars.flashLoanDebt = amount.add(premium);
vars.amountToPull = vars.amountToSwap.add(premium);
_pullAToken(assetFrom, vars.aToken, initiator, vars.amountToPull, permitSignature);
// Repay flash loan
IERC20(assetFrom).safeApprove(address(LENDING_POOL), 0);
IERC20(assetFrom).safeApprove(address(LENDING_POOL), vars.flashLoanDebt);
}
/**
* @dev Decodes the information encoded in the flash loan 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 deposited
* uint256[] minAmountsToReceive List of min amounts to be received from the swap
* bool[] swapAllBalance Flag indicating if all the user balance should be swapped
* uint256[] permitAmount List of amounts for the permit signature
* uint256[] deadline List of deadlines for the permit signature
* uint8[] v List of v param for the permit signature
* bytes32[] r List of r param for the permit signature
* bytes32[] s List of s param for the permit signature
* bool[] useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
* @return SwapParams struct containing decoded params
*/
function _decodeParams(bytes memory params) internal pure returns (SwapParams memory) {
(
address[] memory assetToSwapToList,
uint256[] memory minAmountsToReceive,
bool[] memory swapAllBalance,
uint256[] memory permitAmount,
uint256[] memory deadline,
uint8[] memory v,
bytes32[] memory r,
bytes32[] memory s,
bool[] memory useEthPath
) =
abi.decode(
params,
(address[], uint256[], bool[], uint256[], uint256[], uint8[], bytes32[], bytes32[], bool[])
);
return
SwapParams(
assetToSwapToList,
minAmountsToReceive,
swapAllBalance,
PermitParams(permitAmount, deadline, v, r, s),
useEthPath
);
}
}

View File

@ -0,0 +1,259 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
/**
* @title UniswapRepayAdapter
* @notice Uniswap V2 Adapter to perform a repay of a debt with collateral.
* @author Aave
**/
contract UniswapRepayAdapter is BaseUniswapAdapter {
struct RepayParams {
address collateralAsset;
uint256 collateralAmount;
uint256 rateMode;
PermitSignature permitSignature;
bool useEthPath;
}
constructor(
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter,
address wethAddress
) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {}
/**
* @dev Uses the received funds from the flash loan to repay a debt on the protocol on behalf of the user. Then pulls
* the collateral from the user and swaps it to the debt asset to repay the flash loan.
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset, swap it
* and repay the flash loan.
* Supports only one asset on the flash loan.
* @param assets Address of debt asset
* @param amounts Amount of the debt to be repaid
* @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 collateralAsset Address of the reserve to be swapped
* uint256 collateralAmount Amount of reserve to be swapped
* uint256 rateMode Rate modes of the debt to be repaid
* uint256 permitAmount Amount for the permit signature
* uint256 deadline Deadline for the permit signature
* uint8 v V param for the permit signature
* bytes32 r R param for the permit signature
* bytes32 s S param for the permit signature
*/
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL');
RepayParams memory decodedParams = _decodeParams(params);
_swapAndRepay(
decodedParams.collateralAsset,
assets[0],
amounts[0],
decodedParams.collateralAmount,
decodedParams.rateMode,
initiator,
premiums[0],
decodedParams.permitSignature,
decodedParams.useEthPath
);
return true;
}
/**
* @dev Swaps the user collateral for the debt asset and then repay the debt on the protocol on behalf of the user
* without using flash loans. This method can be used when the temporary transfer of the collateral asset to this
* contract does not affect the user position.
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset
* @param collateralAsset Address of asset to be swapped
* @param debtAsset Address of debt asset
* @param collateralAmount Amount of the collateral to be swapped
* @param debtRepayAmount Amount of the debt to be repaid
* @param debtRateMode Rate mode of the debt to be repaid
* @param permitSignature struct containing the permit signature
* @param useEthPath struct containing the permit signature
*/
function swapAndRepay(
address collateralAsset,
address debtAsset,
uint256 collateralAmount,
uint256 debtRepayAmount,
uint256 debtRateMode,
PermitSignature calldata permitSignature,
bool useEthPath
) external {
DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset);
DataTypes.ReserveData memory debtReserveData = _getReserveData(debtAsset);
address debtToken =
DataTypes.InterestRateMode(debtRateMode) == DataTypes.InterestRateMode.STABLE
? debtReserveData.stableDebtTokenAddress
: debtReserveData.variableDebtTokenAddress;
uint256 currentDebt = IERC20(debtToken).balanceOf(msg.sender);
uint256 amountToRepay = debtRepayAmount <= currentDebt ? debtRepayAmount : currentDebt;
if (collateralAsset != debtAsset) {
uint256 maxCollateralToSwap = collateralAmount;
if (amountToRepay < debtRepayAmount) {
maxCollateralToSwap = maxCollateralToSwap.mul(amountToRepay).div(debtRepayAmount);
}
// Get exact collateral needed for the swap to avoid leftovers
uint256[] memory amounts =
_getAmountsIn(collateralAsset, debtAsset, amountToRepay, useEthPath);
require(amounts[0] <= maxCollateralToSwap, 'slippage too high');
// Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
msg.sender,
amounts[0],
permitSignature
);
// Swap collateral for debt asset
_swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], amountToRepay, useEthPath);
} else {
// Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
msg.sender,
amountToRepay,
permitSignature
);
}
// Repay debt. Approves 0 first to comply with tokens that implement the anti frontrunning approval fix
IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0);
IERC20(debtAsset).safeApprove(address(LENDING_POOL), amountToRepay);
LENDING_POOL.repay(debtAsset, amountToRepay, debtRateMode, msg.sender);
}
/**
* @dev Perform the repay of the debt, pulls the initiator collateral and swaps to repay the flash loan
*
* @param collateralAsset Address of token to be swapped
* @param debtAsset Address of debt token to be received from the swap
* @param amount Amount of the debt to be repaid
* @param collateralAmount Amount of the reserve to be swapped
* @param rateMode Rate mode of the debt to be repaid
* @param initiator Address of the user
* @param premium Fee of the flash loan
* @param permitSignature struct containing the permit signature
*/
function _swapAndRepay(
address collateralAsset,
address debtAsset,
uint256 amount,
uint256 collateralAmount,
uint256 rateMode,
address initiator,
uint256 premium,
PermitSignature memory permitSignature,
bool useEthPath
) internal {
DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset);
// Repay debt. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0);
IERC20(debtAsset).safeApprove(address(LENDING_POOL), amount);
uint256 repaidAmount = IERC20(debtAsset).balanceOf(address(this));
LENDING_POOL.repay(debtAsset, amount, rateMode, initiator);
repaidAmount = repaidAmount.sub(IERC20(debtAsset).balanceOf(address(this)));
if (collateralAsset != debtAsset) {
uint256 maxCollateralToSwap = collateralAmount;
if (repaidAmount < amount) {
maxCollateralToSwap = maxCollateralToSwap.mul(repaidAmount).div(amount);
}
uint256 neededForFlashLoanDebt = repaidAmount.add(premium);
uint256[] memory amounts =
_getAmountsIn(collateralAsset, debtAsset, neededForFlashLoanDebt, useEthPath);
require(amounts[0] <= maxCollateralToSwap, 'slippage too high');
// Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
initiator,
amounts[0],
permitSignature
);
// Swap collateral asset to the debt asset
_swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], neededForFlashLoanDebt, useEthPath);
} else {
// Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
initiator,
repaidAmount.add(premium),
permitSignature
);
}
// Repay flashloan. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0);
IERC20(debtAsset).safeApprove(address(LENDING_POOL), amount.add(premium));
}
/**
* @dev Decodes debt information encoded in the flash loan params
* @param params Additional variadic field to include extra params. Expected parameters:
* address collateralAsset Address of the reserve to be swapped
* uint256 collateralAmount Amount of reserve to be swapped
* uint256 rateMode Rate modes of the debt to be repaid
* uint256 permitAmount Amount for the permit signature
* uint256 deadline Deadline for the permit signature
* uint8 v V param for the permit signature
* bytes32 r R param for the permit signature
* bytes32 s S param for the permit signature
* @return RepayParams struct containing decoded params
*/
function _decodeParams(bytes memory params) internal pure returns (RepayParams memory) {
(
address collateralAsset,
uint256 collateralAmount,
uint256 rateMode,
uint256 permitAmount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s,
bool useEthPath
) =
abi.decode(
params,
(address, uint256, uint256, uint256, uint256, uint8, bytes32, bytes32, bool)
);
return
RepayParams(
collateralAsset,
collateralAmount,
rateMode,
PermitSignature(permitAmount, deadline, v, r, s),
useEthPath
);
}
}

View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol';
interface IBaseUniswapAdapter {
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
struct PermitSignature {
uint256 amount;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
struct AmountCalc {
uint256 calculatedAmount;
uint256 relativePrice;
uint256 amountInUsd;
uint256 amountOutUsd;
address[] path;
}
function WETH_ADDRESS() external returns (address);
function MAX_SLIPPAGE_PERCENT() external returns (uint256);
function FLASHLOAN_PREMIUM_TOTAL() external returns (uint256);
function USD_ADDRESS() external returns (address);
function ORACLE() external returns (IPriceOracleGetter);
function UNISWAP_ROUTER() external returns (IUniswapV2Router02);
/**
* @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices
* @param amountIn Amount of reserveIn
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @return uint256 Amount out of the reserveOut
* @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals)
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
* @return address[] The exchange path
*/
function getAmountsOut(
uint256 amountIn,
address reserveIn,
address reserveOut
)
external
view
returns (
uint256,
uint256,
uint256,
uint256,
address[] memory
);
/**
* @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices
* @param amountOut Amount of reserveOut
* @param reserveIn Address of the asset to be swap from
* @param reserveOut Address of the asset to be swap to
* @return uint256 Amount in of the reserveIn
* @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals)
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
* @return address[] The exchange path
*/
function getAmountsIn(
uint256 amountOut,
address reserveIn,
address reserveOut
)
external
view
returns (
uint256,
uint256,
uint256,
uint256,
address[] memory
);
}

View File

@ -0,0 +1,16 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
interface IERC20WithPermit is IERC20 {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
interface IUniswapV2Router02 {
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint256[] memory amounts);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

View File

@ -0,0 +1,106 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {MintableERC20} from '../tokens/MintableERC20.sol';
contract MockUniswapV2Router02 is IUniswapV2Router02 {
mapping(address => uint256) internal _amountToReturn;
mapping(address => uint256) internal _amountToSwap;
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsIn;
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsOut;
uint256 internal defaultMockValue;
function setAmountToReturn(address reserve, uint256 amount) public {
_amountToReturn[reserve] = amount;
}
function setAmountToSwap(address reserve, uint256 amount) public {
_amountToSwap[reserve] = amount;
}
function swapExactTokensForTokens(
uint256 amountIn,
uint256, /* amountOutMin */
address[] calldata path,
address to,
uint256 /* deadline */
) external override returns (uint256[] memory amounts) {
IERC20(path[0]).transferFrom(msg.sender, address(this), amountIn);
MintableERC20(path[1]).mint(_amountToReturn[path[0]]);
IERC20(path[1]).transfer(to, _amountToReturn[path[0]]);
amounts = new uint256[](path.length);
amounts[0] = amountIn;
amounts[1] = _amountToReturn[path[0]];
}
function swapTokensForExactTokens(
uint256 amountOut,
uint256, /* amountInMax */
address[] calldata path,
address to,
uint256 /* deadline */
) external override returns (uint256[] memory amounts) {
IERC20(path[0]).transferFrom(msg.sender, address(this), _amountToSwap[path[0]]);
MintableERC20(path[1]).mint(amountOut);
IERC20(path[1]).transfer(to, amountOut);
amounts = new uint256[](path.length);
amounts[0] = _amountToSwap[path[0]];
amounts[1] = amountOut;
}
function setAmountOut(
uint256 amountIn,
address reserveIn,
address reserveOut,
uint256 amountOut
) public {
_amountsOut[reserveIn][reserveOut][amountIn] = amountOut;
}
function setAmountIn(
uint256 amountOut,
address reserveIn,
address reserveOut,
uint256 amountIn
) public {
_amountsIn[reserveIn][reserveOut][amountOut] = amountIn;
}
function setDefaultMockValue(uint256 value) public {
defaultMockValue = value;
}
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
override
returns (uint256[] memory)
{
uint256[] memory amounts = new uint256[](path.length);
amounts[0] = amountIn;
amounts[1] = _amountsOut[path[0]][path[1]][amountIn] > 0
? _amountsOut[path[0]][path[1]][amountIn]
: defaultMockValue;
return amounts;
}
function getAmountsIn(uint256 amountOut, address[] calldata path)
external
view
override
returns (uint256[] memory)
{
uint256[] memory amounts = new uint256[](path.length);
amounts[0] = _amountsIn[path[0]][path[1]][amountOut] > 0
? _amountsIn[path[0]][path[1]][amountOut]
: defaultMockValue;
amounts[1] = amountOut;
return amounts;
}
}

View File

@ -6,6 +6,8 @@ import { accounts } from './test-wallets.js';
import { eEthereumNetwork } from './helpers/types';
import { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/buidler-constants';
require('dotenv').config();
import '@nomiclabs/hardhat-ethers';
import '@nomiclabs/hardhat-waffle';
import 'temp-hardhat-etherscan';
@ -16,7 +18,7 @@ import '@tenderly/hardhat-tenderly';
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
const DEFAULT_BLOCK_GAS_LIMIT = 12450000;
const DEFAULT_GAS_MUL = 5;
const DEFAULT_GAS_PRICE = 2000000000;
const DEFAULT_GAS_PRICE = 65000000000;
const HARDFORK = 'istanbul';
const INFURA_KEY = process.env.INFURA_KEY || '';
const ALCHEMY_KEY = process.env.ALCHEMY_KEY || '';
@ -27,14 +29,16 @@ const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
// Prevent to load scripts before compilation and typechain
if (!SKIP_LOAD) {
['misc', 'migrations', 'dev', 'full', 'verifications', 'helpers'].forEach((folder) => {
const tasksPath = path.join(__dirname, 'tasks', folder);
fs.readdirSync(tasksPath)
.filter((pth) => pth.includes('.ts'))
.forEach((task) => {
require(`${tasksPath}/${task}`);
});
});
['misc', 'migrations', 'dev', 'full', 'verifications', 'deployments', 'helpers'].forEach(
(folder) => {
const tasksPath = path.join(__dirname, 'tasks', folder);
fs.readdirSync(tasksPath)
.filter((pth) => pth.includes('.ts'))
.forEach((task) => {
require(`${tasksPath}/${task}`);
});
}
);
}
require(`${path.join(__dirname, 'tasks/misc')}/set-bre.ts`);

View File

@ -38,10 +38,13 @@ import {
MockFlashLoanReceiverFactory,
MockStableDebtTokenFactory,
MockVariableDebtTokenFactory,
MockUniswapV2Router02Factory,
PriceOracleFactory,
ReserveLogicFactory,
SelfdestructTransferFactory,
StableDebtTokenFactory,
UniswapLiquiditySwapAdapterFactory,
UniswapRepayAdapterFactory,
VariableDebtTokenFactory,
WalletBalanceProviderFactory,
WETH9MockedFactory,
@ -318,7 +321,7 @@ export const deployVariableDebtToken = async (
);
export const deployGenericAToken = async (
[poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol,incentivesController]: [
[poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController]: [
tEthereumAddress,
tEthereumAddress,
tEthereumAddress,
@ -335,7 +338,6 @@ export const deployGenericAToken = async (
string,
tEthereumAddress,
tEthereumAddress
] = [poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController];
return withSaveAndVerify(
await new ATokenFactory(await getFirstSigner()).deploy(...args),
@ -493,3 +495,33 @@ export const deploySelfdestructTransferMock = async (verify?: boolean) =>
[],
verify
);
export const deployMockUniswapRouter = async (verify?: boolean) =>
withSaveAndVerify(
await new MockUniswapV2Router02Factory(await getFirstSigner()).deploy(),
eContractid.MockUniswapV2Router02,
[],
verify
);
export const deployUniswapLiquiditySwapAdapter = async (
args: [tEthereumAddress, tEthereumAddress, tEthereumAddress],
verify?: boolean
) =>
withSaveAndVerify(
await new UniswapLiquiditySwapAdapterFactory(await getFirstSigner()).deploy(...args),
eContractid.UniswapLiquiditySwapAdapter,
args,
verify
);
export const deployUniswapRepayAdapter = async (
args: [tEthereumAddress, tEthereumAddress, tEthereumAddress],
verify?: boolean
) =>
withSaveAndVerify(
await new UniswapRepayAdapterFactory(await getFirstSigner()).deploy(...args),
eContractid.UniswapRepayAdapter,
args,
verify
);

View File

@ -17,11 +17,14 @@ import {
MockFlashLoanReceiverFactory,
MockStableDebtTokenFactory,
MockVariableDebtTokenFactory,
MockUniswapV2Router02Factory,
PriceOracleFactory,
ReserveLogicFactory,
SelfdestructTransferFactory,
StableAndVariableTokensHelperFactory,
StableDebtTokenFactory,
UniswapLiquiditySwapAdapterFactory,
UniswapRepayAdapterFactory,
VariableDebtTokenFactory,
WalletBalanceProviderFactory,
WETH9MockedFactory,
@ -328,3 +331,26 @@ export const getAaveOracle = async (address?: tEthereumAddress) =>
address || (await getDb().get(`${eContractid.AaveOracle}.${DRE.network.name}`).value()).address,
await getFirstSigner()
);
export const getMockUniswapRouter = async (address?: tEthereumAddress) =>
await MockUniswapV2Router02Factory.connect(
address ||
(await getDb().get(`${eContractid.MockUniswapV2Router02}.${DRE.network.name}`).value())
.address,
await getFirstSigner()
);
export const getUniswapLiquiditySwapAdapter = async (address?: tEthereumAddress) =>
await UniswapLiquiditySwapAdapterFactory.connect(
address ||
(await getDb().get(`${eContractid.UniswapLiquiditySwapAdapter}.${DRE.network.name}`).value())
.address,
await getFirstSigner()
);
export const getUniswapRepayAdapter = async (address?: tEthereumAddress) =>
await UniswapRepayAdapterFactory.connect(
address ||
(await getDb().get(`${eContractid.UniswapRepayAdapter}.${DRE.network.name}`).value()).address,
await getFirstSigner()
);

View File

@ -1,4 +1,4 @@
import { Contract, Signer, utils, ethers } from 'ethers';
import { Contract, Signer, utils, ethers, BigNumberish } from 'ethers';
import { signTypedData_v4 } from 'eth-sig-util';
import { fromRpcSig, ECDSASignature } from 'ethereumjs-util';
import BigNumber from 'bignumber.js';
@ -232,3 +232,57 @@ export const getSignatureFromTypedData = (
});
return fromRpcSig(signature);
};
export const buildLiquiditySwapParams = (
assetToSwapToList: tEthereumAddress[],
minAmountsToReceive: BigNumberish[],
swapAllBalances: BigNumberish[],
permitAmounts: BigNumberish[],
deadlines: BigNumberish[],
v: BigNumberish[],
r: (string | Buffer)[],
s: (string | Buffer)[],
useEthPath: boolean[]
) => {
return ethers.utils.defaultAbiCoder.encode(
[
'address[]',
'uint256[]',
'bool[]',
'uint256[]',
'uint256[]',
'uint8[]',
'bytes32[]',
'bytes32[]',
'bool[]',
],
[
assetToSwapToList,
minAmountsToReceive,
swapAllBalances,
permitAmounts,
deadlines,
v,
r,
s,
useEthPath,
]
);
};
export const buildRepayAdapterParams = (
collateralAsset: tEthereumAddress,
collateralAmount: BigNumberish,
rateMode: BigNumberish,
permitAmount: BigNumberish,
deadline: BigNumberish,
v: BigNumberish,
r: string | Buffer,
s: string | Buffer,
useEthPath: boolean
) => {
return ethers.utils.defaultAbiCoder.encode(
['address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32', 'bool'],
[collateralAsset, collateralAmount, rateMode, permitAmount, deadline, v, r, s, useEthPath]
);
};

View File

@ -67,6 +67,9 @@ export enum eContractid {
LendingPoolImpl = 'LendingPoolImpl',
LendingPoolConfiguratorImpl = 'LendingPoolConfiguratorImpl',
LendingPoolCollateralManagerImpl = 'LendingPoolCollateralManagerImpl',
MockUniswapV2Router02 = 'MockUniswapV2Router02',
UniswapLiquiditySwapAdapter = 'UniswapLiquiditySwapAdapter',
UniswapRepayAdapter = 'UniswapRepayAdapter',
}
/*

62
package-lock.json generated
View File

@ -9504,14 +9504,6 @@
"prr": "~1.0.1",
"semver": "~5.4.1",
"xtend": "~4.0.0"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
}
}
},
"merkle-patricia-tree": {
@ -9901,14 +9893,6 @@
"prr": "~1.0.1",
"semver": "~5.4.1",
"xtend": "~4.0.0"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
}
}
},
"merkle-patricia-tree": {
@ -10200,14 +10184,6 @@
"prr": "~1.0.1",
"semver": "~5.4.1",
"xtend": "~4.0.0"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
}
}
},
"merkle-patricia-tree": {
@ -10459,14 +10435,6 @@
"prr": "~1.0.1",
"semver": "~5.4.1",
"xtend": "~4.0.0"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
}
}
},
"merkle-patricia-tree": {
@ -12428,6 +12396,17 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"safe-buffer": {
@ -12436,6 +12415,11 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -14250,6 +14234,12 @@
"integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==",
"dev": true
},
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
},
"semver-greatest-satisfied-range": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz",
@ -16306,14 +16296,6 @@
"prr": "~1.0.1",
"semver": "~5.4.1",
"xtend": "~4.0.0"
},
"dependencies": {
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
"dev": true
}
}
},
"merkle-patricia-tree": {

View File

@ -27,6 +27,7 @@
"test-stable-and-atokens": "hardhat test test/__setup.spec.ts test/atoken-transfer.spec.ts test/stable-token.spec.ts",
"test-subgraph:scenarios": "hardhat --network hardhatevm_docker test test/__setup.spec.ts test/subgraph-scenarios.spec.ts",
"test-weth": "hardhat test test/__setup.spec.ts test/weth-gateway.spec.ts",
"test-uniswap": "hardhat test test/__setup.spec.ts test/uniswapAdapters*.spec.ts",
"test:main:check-list": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts test/mainnet/check-list.spec.ts",
"dev:coverage": "buidler compile --force && buidler coverage --network coverage",
"aave:evm:dev:migration": "npm run compile && hardhat aave:dev",
@ -45,6 +46,10 @@
"print-contracts:main": "npm run hardhat:main -- print-contracts",
"print-contracts:ropsten": "npm run hardhat:main -- print-contracts",
"dev:deployUIProvider": "npm run hardhat:kovan deploy-UiPoolDataProvider",
"dev:deployUniswapRepayAdapter": "hardhat --network kovan deploy-UniswapRepayAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c",
"dev:UniswapLiquiditySwapAdapter": "hardhat --network kovan deploy-UniswapLiquiditySwapAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c",
"main:deployUniswapRepayAdapter": "hardhat --network main deploy-UniswapRepayAdapter --provider 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 --router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"main:UniswapLiquiditySwapAdapter": "hardhat --network main deploy-UniswapLiquiditySwapAdapter --provider 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 --router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"kovan:verify": "npm run hardhat:kovan verify:general -- --all --pool Aave",
"ropsten:verify": "npm run hardhat:ropsten verify:general -- --all --pool Aave",
"mainnet:verify": "npm run hardhat:main verify:general -- --all --pool Aave",

View File

@ -96,17 +96,17 @@ contract LendingPoolHarnessForVariableDebtToken is ILendingPool {
}
function getUserAccountData(address user)
external
view
override
returns (
uint256 totalCollateralETH,
uint256 totalDebtETH,
uint256 availableBorrowsETH,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
)
external
view
override
returns (
uint256 totalCollateralETH,
uint256 totalDebtETH,
uint256 availableBorrowsETH,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
)
{
return originalPool.getUserAccountData(user);
}

View File

@ -11,24 +11,23 @@ contract StableDebtTokenHarness is StableDebtToken {
string memory symbol,
address incentivesController
) public StableDebtToken(pool, underlyingAsset, name, symbol, incentivesController) {}
/**
/**
Simplification: The user accumulates no interest (the balance increase is always 0).
**/
function balanceOf(address account) public override view returns (uint256) {
function balanceOf(address account) public view override returns (uint256) {
return IncentivizedERC20.balanceOf(account);
}
function _calcTotalSupply(uint256 avgRate) internal override view returns (uint256) {
function _calcTotalSupply(uint256 avgRate) internal view override returns (uint256) {
return IncentivizedERC20.totalSupply();
}
function getIncentivesController() public view returns (address) {
return address(_incentivesController);
}
function rayWadMul(uint256 aRay, uint256 bWad) external view returns(uint256) {
return aRay.rayMul(bWad.wadToRay());
function rayWadMul(uint256 aRay, uint256 bWad) external view returns (uint256) {
return aRay.rayMul(bWad.wadToRay());
}
}

View File

@ -1,35 +1,26 @@
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {UserConfiguration} from '../../contracts/protocol/libraries/configuration/UserConfiguration.sol';
import {
UserConfiguration
} from '../../contracts/protocol/libraries/configuration/UserConfiguration.sol';
import {DataTypes} from '../../contracts/protocol/libraries/types/DataTypes.sol';
/*
A wrapper contract for calling functions from the library UserConfiguration.
*/
contract UserConfigurationHarness {
DataTypes.UserConfigurationMap internal usersConfig;
function setBorrowing(
uint256 reserveIndex,
bool borrowing
) public {
function setBorrowing(uint256 reserveIndex, bool borrowing) public {
UserConfiguration.setBorrowing(usersConfig, reserveIndex, borrowing);
}
function setUsingAsCollateral(
uint256 reserveIndex,
bool _usingAsCollateral
) public {
function setUsingAsCollateral(uint256 reserveIndex, bool _usingAsCollateral) public {
UserConfiguration.setUsingAsCollateral(usersConfig, reserveIndex, _usingAsCollateral);
}
function isUsingAsCollateralOrBorrowing(uint256 reserveIndex)
public
view
returns (bool)
{
function isUsingAsCollateralOrBorrowing(uint256 reserveIndex) public view returns (bool) {
return UserConfiguration.isUsingAsCollateralOrBorrowing(usersConfig, reserveIndex);
}

View File

@ -1,13 +1,13 @@
import {task} from '@nomiclabs/buidler/config';
import { task } from 'hardhat/config';
import {UiPoolDataProviderFactory} from '../../types';
import {verifyContract} from '../../helpers/etherscan-verification';
import {eContractid} from '../../helpers/types';
import { UiPoolDataProviderFactory } from '../../types';
import { verifyContract } from '../../helpers/etherscan-verification';
import { eContractid } from '../../helpers/types';
task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider contract`)
.addFlag('verify', 'Verify UiPoolDataProvider contract via Etherscan API.')
.setAction(async ({verify}, localBRE) => {
await localBRE.run('set-bre');
.setAction(async ({ verify }, localBRE) => {
await localBRE.run('set-DRE');
if (!localBRE.network.config.chainId) {
throw new Error('INVALID_CHAIN_ID');
@ -21,7 +21,7 @@ task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider
).deploy();
await uiPoolDataProvider.deployTransaction.wait();
console.log('uiPoolDataProvider.address', uiPoolDataProvider.address);
await verifyContract(eContractid.UiPoolDataProvider, uiPoolDataProvider.address, []);
await verifyContract(uiPoolDataProvider.address, []);
console.log(`\tFinished UiPoolDataProvider proxy and implementation deployment`);
});

View File

@ -0,0 +1,35 @@
import { task } from 'hardhat/config';
import { UniswapLiquiditySwapAdapterFactory } from '../../types';
import { verifyContract } from '../../helpers/etherscan-verification';
import { getFirstSigner } from '../../helpers/contracts-getters';
const CONTRACT_NAME = 'UniswapLiquiditySwapAdapter';
task(`deploy-${CONTRACT_NAME}`, `Deploys the ${CONTRACT_NAME} contract`)
.addParam('provider', 'Address of the LendingPoolAddressesProvider')
.addParam('router', 'Address of the uniswap router')
.addParam('weth', 'Address of the weth token')
.addFlag('verify', `Verify ${CONTRACT_NAME} contract via Etherscan API.`)
.setAction(async ({ provider, router, weth, verify }, localBRE) => {
await localBRE.run('set-DRE');
if (!localBRE.network.config.chainId) {
throw new Error('INVALID_CHAIN_ID');
}
console.log(`\n- ${CONTRACT_NAME} deployment`);
/*const args = [
'0x88757f2f99175387aB4C6a4b3067c77A695b0349', // lending provider kovan address
'0xfcd87315f0e4067070ade8682fcdbc3006631441', // uniswap router address
];
*/
const uniswapRepayAdapter = await new UniswapLiquiditySwapAdapterFactory(
await getFirstSigner()
).deploy(provider, router, weth);
await uniswapRepayAdapter.deployTransaction.wait();
console.log(`${CONTRACT_NAME}.address`, uniswapRepayAdapter.address);
await verifyContract(uniswapRepayAdapter.address, [provider, router, weth]);
console.log(`\tFinished ${CONTRACT_NAME} proxy and implementation deployment`);
});

View File

@ -0,0 +1,38 @@
import { task } from 'hardhat/config';
import { UniswapRepayAdapterFactory } from '../../types';
import { verifyContract } from '../../helpers/etherscan-verification';
import { getFirstSigner } from '../../helpers/contracts-getters';
const CONTRACT_NAME = 'UniswapRepayAdapter';
task(`deploy-${CONTRACT_NAME}`, `Deploys the ${CONTRACT_NAME} contract`)
.addParam('provider', 'Address of the LendingPoolAddressesProvider')
.addParam('router', 'Address of the uniswap router')
.addParam('weth', 'Address of the weth token')
.addFlag('verify', `Verify ${CONTRACT_NAME} contract via Etherscan API.`)
.setAction(async ({ provider, router, weth, verify }, localBRE) => {
await localBRE.run('set-DRE');
if (!localBRE.network.config.chainId) {
throw new Error('INVALID_CHAIN_ID');
}
console.log(`\n- ${CONTRACT_NAME} deployment`);
// const args = [
// '0x88757f2f99175387aB4C6a4b3067c77A695b0349', // lending provider kovan address
// '0xfcd87315f0e4067070ade8682fcdbc3006631441', // uniswap router address
// ];
const uniswapRepayAdapter = await new UniswapRepayAdapterFactory(await getFirstSigner()).deploy(
provider,
router,
weth
);
await uniswapRepayAdapter.deployTransaction.wait();
console.log(`${CONTRACT_NAME}.address`, uniswapRepayAdapter.address);
await verifyContract(uniswapRepayAdapter.address, [provider, router, weth]);
console.log(
`\tFinished ${CONTRACT_NAME}${CONTRACT_NAME}lDataProvider proxy and implementation deployment`
);
});

View File

@ -22,11 +22,19 @@ import {
deployATokensAndRatesHelper,
deployWETHGateway,
deployWETHMocked,
deployMockUniswapRouter,
deployUniswapLiquiditySwapAdapter,
deployUniswapRepayAdapter,
} from '../helpers/contracts-deployments';
import { Signer } from 'ethers';
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../helpers/types';
import { MintableERC20 } from '../types/MintableERC20';
import { ConfigNames, getReservesConfigByPool, getTreasuryAddress, loadPoolConfig } from '../helpers/configuration';
import {
ConfigNames,
getReservesConfigByPool,
getTreasuryAddress,
loadPoolConfig,
} from '../helpers/configuration';
import { initializeMakeSuite } from './helpers/make-suite';
import {
@ -35,10 +43,7 @@ import {
setInitialMarketRatesInRatesOracleByHelper,
} from '../helpers/oracles-helpers';
import { DRE, waitForTx } from '../helpers/misc-utils';
import {
initReservesByHelper,
configureReservesByHelper,
} from '../helpers/init-helpers';
import { initReservesByHelper, configureReservesByHelper } from '../helpers/init-helpers';
import AaveConfig from '../markets/aave';
import { ZERO_ADDRESS } from '../helpers/constants';
import {
@ -213,13 +218,15 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
const treasuryAddress = await getTreasuryAddress(config);
await initReservesByHelper(reservesParams, allReservesAddresses, admin, treasuryAddress, ZERO_ADDRESS, false);
await configureReservesByHelper(
await initReservesByHelper(
reservesParams,
allReservesAddresses,
testHelpers,
admin
admin,
treasuryAddress,
ZERO_ADDRESS,
false
);
await configureReservesByHelper(reservesParams, allReservesAddresses, testHelpers, admin);
const collateralManager = await deployLendingPoolCollateralManager();
await waitForTx(
@ -229,6 +236,26 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address);
await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address);
const mockUniswapRouter = await deployMockUniswapRouter();
await insertContractAddressInDb(eContractid.MockUniswapV2Router02, mockUniswapRouter.address);
const UniswapLiquiditySwapAdapter = await deployUniswapLiquiditySwapAdapter([
addressesProvider.address,
mockUniswapRouter.address,
mockTokens.WETH.address,
]);
await insertContractAddressInDb(
eContractid.UniswapLiquiditySwapAdapter,
UniswapLiquiditySwapAdapter.address
);
const UniswapRepayAdapter = await deployUniswapRepayAdapter([
addressesProvider.address,
mockUniswapRouter.address,
mockTokens.WETH.address,
]);
await insertContractAddressInDb(eContractid.UniswapRepayAdapter, UniswapRepayAdapter.address);
await deployWalletBalancerProvider();
await deployWETHGateway([mockTokens.WETH.address, lendingPoolAddress]);

View File

@ -11,6 +11,8 @@ import {
getLendingPoolAddressesProviderRegistry,
getWETHMocked,
getWETHGateway,
getUniswapLiquiditySwapAdapter,
getUniswapRepayAdapter,
} from '../../helpers/contracts-getters';
import { eEthereumNetwork, tEthereumAddress } from '../../helpers/types';
import { LendingPool } from '../../types/LendingPool';
@ -26,7 +28,10 @@ import { almostEqual } from './almost-equal';
import { PriceOracle } from '../../types/PriceOracle';
import { LendingPoolAddressesProvider } from '../../types/LendingPoolAddressesProvider';
import { LendingPoolAddressesProviderRegistry } from '../../types/LendingPoolAddressesProviderRegistry';
import { getEthersSigners, getParamPerNetwork } from '../../helpers/contracts-helpers';
import { getEthersSigners } from '../../helpers/contracts-helpers';
import { UniswapLiquiditySwapAdapter } from '../../types/UniswapLiquiditySwapAdapter';
import { UniswapRepayAdapter } from '../../types/UniswapRepayAdapter';
import { getParamPerNetwork } from '../../helpers/contracts-helpers';
import { WETH9Mocked } from '../../types/WETH9Mocked';
import { WETHGateway } from '../../types/WETHGateway';
import { solidity } from 'ethereum-waffle';
@ -54,6 +59,8 @@ export interface TestEnv {
usdc: MintableERC20;
aave: MintableERC20;
addressesProvider: LendingPoolAddressesProvider;
uniswapLiquiditySwapAdapter: UniswapLiquiditySwapAdapter;
uniswapRepayAdapter: UniswapRepayAdapter;
registry: LendingPoolAddressesProviderRegistry;
wethGateway: WETHGateway;
}
@ -79,6 +86,8 @@ const testEnv: TestEnv = {
usdc: {} as MintableERC20,
aave: {} as MintableERC20,
addressesProvider: {} as LendingPoolAddressesProvider,
uniswapLiquiditySwapAdapter: {} as UniswapLiquiditySwapAdapter,
uniswapRepayAdapter: {} as UniswapRepayAdapter,
registry: {} as LendingPoolAddressesProviderRegistry,
wethGateway: {} as WETHGateway,
} as TestEnv;
@ -141,6 +150,9 @@ export async function initializeMakeSuite() {
testEnv.aave = await getMintableERC20(aaveAddress);
testEnv.weth = await getWETHMocked(wethAddress);
testEnv.wethGateway = await getWETHGateway();
testEnv.uniswapLiquiditySwapAdapter = await getUniswapLiquiditySwapAdapter();
testEnv.uniswapRepayAdapter = await getUniswapRepayAdapter();
}
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {

View File

@ -0,0 +1,227 @@
import { makeSuite, TestEnv } from './helpers/make-suite';
import { convertToCurrencyDecimals } from '../helpers/contracts-helpers';
import { getMockUniswapRouter } from '../helpers/contracts-getters';
import { MockUniswapV2Router02 } from '../types/MockUniswapV2Router02';
import BigNumber from 'bignumber.js';
import { evmRevert, evmSnapshot } from '../helpers/misc-utils';
import { ethers } from 'ethers';
import { USD_ADDRESS } from '../helpers/constants';
const { parseEther } = ethers.utils;
const { expect } = require('chai');
makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
let mockUniswapRouter: MockUniswapV2Router02;
let evmSnapshotId: string;
before(async () => {
mockUniswapRouter = await getMockUniswapRouter();
});
beforeEach(async () => {
evmSnapshotId = await evmSnapshot();
});
afterEach(async () => {
await evmRevert(evmSnapshotId);
});
describe('BaseUniswapAdapter', () => {
describe('getAmountsOut', () => {
it('should return the estimated amountOut and prices for the asset swap', async () => {
const { weth, dai, uniswapLiquiditySwapAdapter, oracle } = testEnv;
const amountIn = parseEther('1');
const flashloanPremium = amountIn.mul(9).div(10000);
const amountToSwap = amountIn.sub(flashloanPremium);
const wethPrice = await oracle.getAssetPrice(weth.address);
const daiPrice = await oracle.getAssetPrice(dai.address);
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountToSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
const outPerInPrice = amountToSwap
.mul(parseEther('1'))
.mul(parseEther('1'))
.div(expectedDaiAmount.mul(parseEther('1')));
const ethUsdValue = amountIn
.mul(wethPrice)
.div(parseEther('1'))
.mul(usdPrice)
.div(parseEther('1'));
const daiUsdValue = expectedDaiAmount
.mul(daiPrice)
.div(parseEther('1'))
.mul(usdPrice)
.div(parseEther('1'));
await mockUniswapRouter.setAmountOut(
amountToSwap,
weth.address,
dai.address,
expectedDaiAmount
);
const result = await uniswapLiquiditySwapAdapter.getAmountsOut(
amountIn,
weth.address,
dai.address
);
expect(result['0']).to.be.eq(expectedDaiAmount);
expect(result['1']).to.be.eq(outPerInPrice);
expect(result['2']).to.be.eq(ethUsdValue);
expect(result['3']).to.be.eq(daiUsdValue);
});
it('should work correctly with different decimals', async () => {
const { aave, usdc, uniswapLiquiditySwapAdapter, oracle } = testEnv;
const amountIn = parseEther('10');
const flashloanPremium = amountIn.mul(9).div(10000);
const amountToSwap = amountIn.sub(flashloanPremium);
const aavePrice = await oracle.getAssetPrice(aave.address);
const usdcPrice = await oracle.getAssetPrice(usdc.address);
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
const expectedUSDCAmount = await convertToCurrencyDecimals(
usdc.address,
new BigNumber(amountToSwap.toString()).div(usdcPrice.toString()).toFixed(0)
);
const outPerInPrice = amountToSwap
.mul(parseEther('1'))
.mul('1000000') // usdc 6 decimals
.div(expectedUSDCAmount.mul(parseEther('1')));
const aaveUsdValue = amountIn
.mul(aavePrice)
.div(parseEther('1'))
.mul(usdPrice)
.div(parseEther('1'));
const usdcUsdValue = expectedUSDCAmount
.mul(usdcPrice)
.div('1000000') // usdc 6 decimals
.mul(usdPrice)
.div(parseEther('1'));
await mockUniswapRouter.setAmountOut(
amountToSwap,
aave.address,
usdc.address,
expectedUSDCAmount
);
const result = await uniswapLiquiditySwapAdapter.getAmountsOut(
amountIn,
aave.address,
usdc.address
);
expect(result['0']).to.be.eq(expectedUSDCAmount);
expect(result['1']).to.be.eq(outPerInPrice);
expect(result['2']).to.be.eq(aaveUsdValue);
expect(result['3']).to.be.eq(usdcUsdValue);
});
});
describe('getAmountsIn', () => {
it('should return the estimated required amountIn for the asset swap', async () => {
const { weth, dai, uniswapLiquiditySwapAdapter, oracle } = testEnv;
const amountIn = parseEther('1');
const flashloanPremium = amountIn.mul(9).div(10000);
const amountToSwap = amountIn.add(flashloanPremium);
const wethPrice = await oracle.getAssetPrice(weth.address);
const daiPrice = await oracle.getAssetPrice(dai.address);
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
const amountOut = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountIn.toString()).div(daiPrice.toString()).toFixed(0)
);
const inPerOutPrice = amountOut
.mul(parseEther('1'))
.mul(parseEther('1'))
.div(amountToSwap.mul(parseEther('1')));
const ethUsdValue = amountToSwap
.mul(wethPrice)
.div(parseEther('1'))
.mul(usdPrice)
.div(parseEther('1'));
const daiUsdValue = amountOut
.mul(daiPrice)
.div(parseEther('1'))
.mul(usdPrice)
.div(parseEther('1'));
await mockUniswapRouter.setAmountIn(amountOut, weth.address, dai.address, amountIn);
const result = await uniswapLiquiditySwapAdapter.getAmountsIn(
amountOut,
weth.address,
dai.address
);
expect(result['0']).to.be.eq(amountToSwap);
expect(result['1']).to.be.eq(inPerOutPrice);
expect(result['2']).to.be.eq(ethUsdValue);
expect(result['3']).to.be.eq(daiUsdValue);
});
it('should work correctly with different decimals', async () => {
const { aave, usdc, uniswapLiquiditySwapAdapter, oracle } = testEnv;
const amountIn = parseEther('10');
const flashloanPremium = amountIn.mul(9).div(10000);
const amountToSwap = amountIn.add(flashloanPremium);
const aavePrice = await oracle.getAssetPrice(aave.address);
const usdcPrice = await oracle.getAssetPrice(usdc.address);
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
const amountOut = await convertToCurrencyDecimals(
usdc.address,
new BigNumber(amountToSwap.toString()).div(usdcPrice.toString()).toFixed(0)
);
const inPerOutPrice = amountOut
.mul(parseEther('1'))
.mul(parseEther('1'))
.div(amountToSwap.mul('1000000')); // usdc 6 decimals
const aaveUsdValue = amountToSwap
.mul(aavePrice)
.div(parseEther('1'))
.mul(usdPrice)
.div(parseEther('1'));
const usdcUsdValue = amountOut
.mul(usdcPrice)
.div('1000000') // usdc 6 decimals
.mul(usdPrice)
.div(parseEther('1'));
await mockUniswapRouter.setAmountIn(amountOut, aave.address, usdc.address, amountIn);
const result = await uniswapLiquiditySwapAdapter.getAmountsIn(
amountOut,
aave.address,
usdc.address
);
expect(result['0']).to.be.eq(amountToSwap);
expect(result['1']).to.be.eq(inPerOutPrice);
expect(result['2']).to.be.eq(aaveUsdValue);
expect(result['3']).to.be.eq(usdcUsdValue);
});
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff