Added mainnet check list. Remove deployed contracts. Update README.md

This commit is contained in:
David Racero 2020-11-16 19:22:22 +01:00
parent 0d40682054
commit 27365697ac
16 changed files with 645 additions and 1373 deletions

View File

@ -83,6 +83,8 @@ npm run aave:kovan:full:migration
### Mainnet fork deployment
You can deploy Aave Protocol v2 in a forked Mainnet chain using Hardhat built-in feature:
```
# In one terminal, run a hardhat note with mainnet fork enabled
MAINNET_FORK=true npx hardhat node
@ -95,4 +97,27 @@ docker-compose exec contracts-env bash
# A new Bash terminal is prompted, connected to the container
npm run aave:fork:main
# Contracts are now deployed at Hardhat node with Mainnet fork.
# You can interact with them via Hardhat console
MAINNET_FORK=true npx hardhat console
# Or your custom Hardhat task
MAINNET_FORK=true npx hardhat your-custom-task
```
### Mainnet fork - Run the check list
For testing the deployment scripts for Mainnet release, you can run the check-list tests in a Mainnet fork using Hardhat built-in feature:
```
# In another terminal, run docker-compose
docker-compose up
# Open another tab or terminal
docker-compose exec contracts-env bash
# A new Bash terminal is prompted, connected to the container
npm run test:main:check-list
```

View File

@ -10,11 +10,15 @@ import {ReserveConfiguration} from '../libraries/configuration/ReserveConfigurat
import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol';
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
import 'hardhat/console.sol';
contract AaveProtocolDataProvider {
using ReserveConfiguration for ReserveConfiguration.Map;
using UserConfiguration for UserConfiguration.Map;
address constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2;
address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
struct TokenData {
string symbol;
address tokenAddress;
@ -31,10 +35,16 @@ contract AaveProtocolDataProvider {
address[] memory reserves = pool.getReservesList();
TokenData[] memory reservesTokens = new TokenData[](reserves.length);
for (uint256 i = 0; i < reserves.length; i++) {
if (reserves[i] == MKR) {
reservesTokens[i] = TokenData({symbol: 'MKR', tokenAddress: reserves[i]});
continue;
}
if (reserves[i] == ETH) {
reservesTokens[i] = TokenData({symbol: 'ETH', tokenAddress: reserves[i]});
continue;
}
reservesTokens[i] = TokenData({
symbol: (reserves[i] == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
? 'ETH'
: IERC20Detailed(reserves[i]).symbol(),
symbol: IERC20Detailed(reserves[i]).symbol(),
tokenAddress: reserves[i]
});
}

View File

@ -0,0 +1,160 @@
pragma solidity >=0.6.2;
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
)
external
returns (
uint256 amountA,
uint256 amountB,
uint256 liquidity
);
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
returns (
uint256 amountToken,
uint256 amountETH,
uint256 liquidity
);
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountToken, uint256 amountETH);
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapETHForExactTokens(
uint256 amountOut,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) external pure returns (uint256 amountB);
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountOut);
function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountIn);
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
returns (uint256[] memory amounts);
function getAmountsIn(uint256 amountOut, address[] calldata path)
external
view
returns (uint256[] memory amounts);
}

View File

@ -0,0 +1,50 @@
pragma solidity >=0.6.2;
import './IUniswapV2Router01.sol';
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
}

File diff suppressed because it is too large Load Diff

View File

@ -109,7 +109,7 @@ export const getLendingRateOracles = (poolConfig: ICommonConfiguration) => {
ReserveAssets,
} = poolConfig;
const MAINNET_FORK = process.env.MAINNET_FORK;
const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
const network = MAINNET_FORK ? 'main' : DRE.network.name;
return filterMapBy(LendingRateOracleRatesCommon, (key) =>
Object.keys(ReserveAssets[network]).includes(key)

View File

@ -22,7 +22,7 @@ export type MockTokenMap = {[symbol: string]: MintableERC20};
export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => {
const currentNetwork = DRE.network.name;
const MAINNET_FORK = process.env.MAINNET_FORK;
const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
if (MAINNET_FORK || (currentNetwork !== 'hardhat' && !currentNetwork.includes('coverage'))) {
console.log(`*** ${contractId} ***\n`);
console.log(`Network: ${currentNetwork}`);
@ -134,7 +134,7 @@ export const getParamPerNetwork = <T>(
{kovan, ropsten, main, buidlerevm, coverage, tenderlyMain}: iParamsPerNetwork<T>,
network: eEthereumNetwork
) => {
const MAINNET_FORK = process.env.MAINNET_FORK;
const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
if (MAINNET_FORK) {
return main;
}

View File

@ -11,21 +11,17 @@
"hardhat:main": "hardhat --network main",
"hardhat:docker": "hardhat --network hardhatevm_docker",
"compile": "SKIP_LOAD=true hardhat compile",
"test": "SKIP_LOAD=true TS_NODE_TRANSPILE_ONLY=1 hardhat test",
"test": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/*.spec.ts",
"test-scenarios": "npm run test -- test/__setup.spec.ts test/scenario.spec.ts",
"aave:evm:dev:migration": "hardhat aave:dev",
"aave:evm:full:migration": "hardhat aave:full",
"aave:docker:dev:migration": "npm run hardhat:docker -- aave:dev",
"aave:docker:full:migration": "npm run hardhat:docker -- aave:full",
"aave:kovan:dev:migration": "npm run hardhat:kovan -- aave:dev --verify",
"aave:kovan:full:migration": "npm run hardhat:kovan -- aave:full --verify",
"aave:kovan:full:initialize": "npm run hardhat:kovan -- full:initialize-lending-pool --verify --pool Aave",
"aave:ropsten:dev:migration": "npm run hardhat:ropsten -- aave:dev --verify",
"aave:ropsten:full:migration": "npm run hardhat:ropsten -- aave:full --verify",
"aave:fork:main:tenderly": "npm run hardhat:tenderly-main -- aave:full:fork",
"aave:fork:main": "MAINNET_FORK=true hardhat aave:full:fork",
"aave:main:dev:migration": "npm run hardhat:main -- aave:dev --verify",
"aave:main:full:migration": "npm run hardhat:main -- aave:full --verify",
"aave:fork:main:tenderly": "npm run hardhat:tenderly-main -- aave:mainnet",
"aave:fork:main": "MAINNET_FORK=true hardhat aave:mainnet",
"test:main:check-list": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts test/mainnet/check-list.spec.ts",
"aave:main:full:migration": "npm run hardhat:main -- aave:mainnet --verify",
"uniswap:evm:dev:migration": "hardhat uniswap:dev",
"uniswap:evm:full:migration": "hardhat uniswap:full --verify",
"uniswap:kovan:dev:migration": "npm run hardhat:kovan -- uniswap:dev --verify",

View File

@ -20,10 +20,10 @@ task(
)
.addFlag('verify', 'Verify contracts at Etherscan')
.addParam('pool', `Pool name to retrieve configuration, supported: ${Object.values(ConfigNames)}`)
.setAction(async ({verify, pool}, localBRE) => {
await localBRE.run('set-DRE');
console.log('addresses', await getEthersSignersAddresses());
const network = <eEthereumNetwork>localBRE.network.name;
.setAction(async ({verify, pool}, DRE) => {
await DRE.run('set-DRE');
const network = <eEthereumNetwork>DRE.network.name;
const poolConfig = loadPoolConfig(pool);
const {ProviderId} = poolConfig;
@ -31,7 +31,6 @@ task(
// Deploy address provider and set genesis manager
const addressesProvider = await deployLendingPoolAddressesProvider(verify);
console.log('prox', addressesProvider.address);
await waitForTx(await addressesProvider.setPoolAdmin(await getGenesisPoolAdmin(poolConfig)));
await waitForTx(await addressesProvider.setEmergencyAdmin(await getEmergencyAdmin(poolConfig)));

View File

@ -23,16 +23,13 @@ task('full:deploy-lending-pool', 'Deploy lending pool for dev enviroment')
try {
await DRE.run('set-DRE');
console.log('addresses', await getEthersSignersAddresses());
const addressesProvider = await getLendingPoolAddressesProvider();
// Deploy lending pool
const lendingPoolImpl = await deployLendingPool(verify);
console.log('set lend poool', addressesProvider.address);
// Set lending pool impl to address provider
await waitForTx(await addressesProvider.setLendingPoolImpl(lendingPoolImpl.address));
console.log('setted');
const address = await addressesProvider.getLendingPool();
const lendingPoolProxy = await getLendingPool(address);

View File

@ -27,7 +27,6 @@ task('full:deploy-oracles', 'Deploy oracles for dev enviroment')
.addParam('pool', `Pool name to retrieve configuration, supported: ${Object.values(ConfigNames)}`)
.setAction(async ({verify, pool}, DRE) => {
try {
console.log('addresses', await getEthersSignersAddresses());
await DRE.run('set-DRE');
const network = <eEthereumNetwork>DRE.network.name;
const poolConfig = loadPoolConfig(pool);
@ -64,12 +63,6 @@ task('full:deploy-oracles', 'Deploy oracles for dev enviroment')
const {USD, ...tokensAddressesWithoutUsd} = tokensToWatch;
if (!lendingRateOracleAddress) {
console.log(
lendingRateOracles,
tokensAddressesWithoutUsd,
lendingRateOracle.address,
aggregators
);
await setInitialMarketRatesInRatesOracleByHelper(
lendingRateOracles,
tokensAddressesWithoutUsd,

View File

@ -25,7 +25,10 @@ task('aave:full', 'Deploy development enviroment')
console.log('2. Deploy lending pool');
await localBRE.run('full:deploy-lending-pool');
console.log('3. Initialize lending pool');
console.log('3. Deploy data provider');
await localBRE.run('full:data-provider');
console.log('4. Initialize lending pool');
await localBRE.run('full:initialize-lending-pool', {pool: POOL_NAME});
if (verify) {

View File

@ -5,7 +5,7 @@ import {ConfigNames} from '../../helpers/configuration';
import {EthereumNetworkNames} from '../../helpers/types';
import {printContracts} from '../../helpers/misc-utils';
task('aave:full:fork', 'Deploy development enviroment')
task('aave:mainnet', 'Deploy development enviroment')
.addFlag('verify', 'Verify contracts at Etherscan')
.setAction(async ({verify}, DRE) => {
const POOL_NAME = ConfigNames.Aave;
@ -17,7 +17,6 @@ task('aave:full:fork', 'Deploy development enviroment')
checkVerification();
}
// Set the ethers provider to the one we initialized so it targets the correct backend
if (network.includes('tenderly')) {
console.log('- Setting up Tenderly provider');
await DRE.tenderlyRPC.initializeFork();
@ -36,7 +35,10 @@ task('aave:full:fork', 'Deploy development enviroment')
console.log('3. Deploy oracles');
await DRE.run('full:deploy-oracles', {pool: POOL_NAME});
console.log('4. Initialize lending pool');
console.log('4. Deploy Data Provider');
await DRE.run('full:data-provider', {pool: POOL_NAME});
console.log('5. Initialize lending pool');
await DRE.run('full:initialize-lending-pool', {pool: POOL_NAME});
if (verify) {

View File

@ -273,8 +273,15 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
before(async () => {
await rawBRE.run('set-DRE');
const [deployer, secondaryWallet] = await getEthersSigners();
console.log('-> Deploying test environment...');
await buildTestEnv(deployer, secondaryWallet);
const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
if (MAINNET_FORK) {
await rawBRE.run('aave:mainnet');
} else {
console.log('-> Deploying test environment...');
await buildTestEnv(deployer, secondaryWallet);
}
await initializeMakeSuite();
console.log('\n***************');
console.log('Setup and snapshot finished');

View File

@ -106,14 +106,17 @@ export async function initializeMakeSuite() {
testEnv.helpersContract = await getAaveProtocolDataProvider();
console.log('het cpmtra');
const allTokens = await testEnv.helpersContract.getAllATokens();
console.log('tokann');
const aDaiAddress = allTokens.find((aToken) => aToken.symbol === 'aDAI')?.tokenAddress;
const aWEthAddress = allTokens.find((aToken) => aToken.symbol === 'aWETH')?.tokenAddress;
console.log('priah');
const reservesTokens = await testEnv.helpersContract.getAllReservesTokens();
console.log('all tokan');
const daiAddress = reservesTokens.find((token) => token.symbol === 'DAI')?.tokenAddress;
const usdcAddress = reservesTokens.find((token) => token.symbol === 'USDC')?.tokenAddress;
const aaveAddress = reservesTokens.find((token) => token.symbol === 'AAVE')?.tokenAddress;
@ -136,6 +139,7 @@ export async function initializeMakeSuite() {
testEnv.aave = await getMintableErc20(aaveAddress);
testEnv.weth = await getWETHMocked(wethAddress);
testEnv.wethGateway = await getWETHGateway();
console.log('laa');
}
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {

View File

@ -0,0 +1,362 @@
import {MAX_UINT_AMOUNT} from '../../helpers/constants';
import {convertToCurrencyDecimals} from '../../helpers/contracts-helpers';
import {makeSuite, TestEnv} from '../helpers/make-suite';
import {parseEther} from 'ethers/lib/utils';
import {DRE, waitForTx} from '../../helpers/misc-utils';
import {BigNumber} from 'ethers';
import {getStableDebtToken, getVariableDebtToken} from '../../helpers/contracts-getters';
import {deploySelfdestructTransferMock} from '../../helpers/contracts-deployments';
import {IUniswapV2Router02Factory} from '../../types/IUniswapV2Router02Factory';
const {expect} = require('chai');
const UNISWAP_ROUTER = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D';
makeSuite('Mainnet Check list', (testEnv: TestEnv) => {
const zero = BigNumber.from('0');
const depositSize = parseEther('5');
it('Deposit WETH', async () => {
const {users, wethGateway, aWETH, pool} = testEnv;
const user = users[1];
// Deposit with native ETH
await wethGateway.connect(user.signer).depositETH(user.address, '0', {value: depositSize});
const aTokensBalance = await aWETH.balanceOf(user.address);
expect(aTokensBalance).to.be.gt(zero);
expect(aTokensBalance).to.be.gte(depositSize);
});
it('Withdraw WETH - Partial', async () => {
const {users, wethGateway, aWETH, pool} = testEnv;
const user = users[1];
const priorEthersBalance = await user.signer.getBalance();
const aTokensBalance = await aWETH.balanceOf(user.address);
expect(aTokensBalance).to.be.gt(zero, 'User should have aTokens.');
// Partially withdraw native ETH
const partialWithdraw = await convertToCurrencyDecimals(aWETH.address, '2');
// Approve the aTokens to Gateway so Gateway can withdraw and convert to Ether
const approveTx = await aWETH
.connect(user.signer)
.approve(wethGateway.address, MAX_UINT_AMOUNT);
const {gasUsed: approveGas} = await waitForTx(approveTx);
// Partial Withdraw and send native Ether to user
const {gasUsed: withdrawGas} = await waitForTx(
await wethGateway.connect(user.signer).withdrawETH(partialWithdraw, user.address)
);
const afterPartialEtherBalance = await user.signer.getBalance();
const afterPartialATokensBalance = await aWETH.balanceOf(user.address);
const gasCosts = approveGas.add(withdrawGas).mul(approveTx.gasPrice);
expect(afterPartialEtherBalance).to.be.equal(
priorEthersBalance.add(partialWithdraw).sub(gasCosts),
'User ETHER balance should contain the partial withdraw'
);
expect(afterPartialATokensBalance).to.be.equal(
aTokensBalance.sub(partialWithdraw),
'User aWETH balance should be substracted'
);
});
it('Withdraw WETH - Full', async () => {
const {users, aWETH, wethGateway, pool} = testEnv;
const user = users[1];
const priorEthersBalance = await user.signer.getBalance();
const aTokensBalance = await aWETH.balanceOf(user.address);
expect(aTokensBalance).to.be.gt(zero, 'User should have aTokens.');
// Approve the aTokens to Gateway so Gateway can withdraw and convert to Ether
const approveTx = await aWETH
.connect(user.signer)
.approve(wethGateway.address, MAX_UINT_AMOUNT);
const {gasUsed: approveGas} = await waitForTx(approveTx);
// Full withdraw
const {gasUsed: withdrawGas} = await waitForTx(
await wethGateway.connect(user.signer).withdrawETH(MAX_UINT_AMOUNT, user.address)
);
const afterFullEtherBalance = await user.signer.getBalance();
const afterFullATokensBalance = await aWETH.balanceOf(user.address);
const gasCosts = approveGas.add(withdrawGas).mul(approveTx.gasPrice);
expect(afterFullEtherBalance).to.be.eq(
priorEthersBalance.add(aTokensBalance).sub(gasCosts),
'User ETHER balance should contain the full withdraw'
);
expect(afterFullATokensBalance).to.be.eq(0, 'User aWETH balance should be zero');
});
it('Borrow stable WETH and Full Repay with ETH', async () => {
const {users, wethGateway, aWETH, weth, pool, helpersContract} = testEnv;
const borrowSize = parseEther('1');
const repaySize = borrowSize.add(borrowSize.mul(5).div(100));
const user = users[1];
const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address);
const stableDebtToken = await getStableDebtToken(stableDebtTokenAddress);
// Deposit with native ETH
await wethGateway.connect(user.signer).depositETH(user.address, '0', {value: depositSize});
const aTokensBalance = await aWETH.balanceOf(user.address);
expect(aTokensBalance).to.be.gt(zero);
expect(aTokensBalance).to.be.gte(depositSize);
// Borrow WETH with WETH as collateral
await waitForTx(
await pool.connect(user.signer).borrow(weth.address, borrowSize, '1', '0', user.address)
);
const debtBalance = await stableDebtToken.balanceOf(user.address);
expect(debtBalance).to.be.gt(zero);
// Full Repay WETH with native ETH
await waitForTx(
await wethGateway
.connect(user.signer)
.repayETH(MAX_UINT_AMOUNT, '1', user.address, {value: repaySize})
);
const debtBalanceAfterRepay = await stableDebtToken.balanceOf(user.address);
expect(debtBalanceAfterRepay).to.be.eq(zero);
});
it('Borrow variable WETH and Full Repay with ETH', async () => {
const {users, wethGateway, aWETH, weth, pool, helpersContract} = testEnv;
const borrowSize = parseEther('1');
const repaySize = borrowSize.add(borrowSize.mul(5).div(100));
const user = users[1];
const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(
weth.address
);
const varDebtToken = await getVariableDebtToken(variableDebtTokenAddress);
// Deposit with native ETH
await wethGateway.connect(user.signer).depositETH(user.address, '0', {value: depositSize});
const aTokensBalance = await aWETH.balanceOf(user.address);
expect(aTokensBalance).to.be.gt(zero);
expect(aTokensBalance).to.be.gte(depositSize);
// Borrow WETH with WETH as collateral
await waitForTx(
await pool.connect(user.signer).borrow(weth.address, borrowSize, '2', '0', user.address)
);
const debtBalance = await varDebtToken.balanceOf(user.address);
expect(debtBalance).to.be.gt(zero);
// Partial Repay WETH loan with native ETH
const partialPayment = repaySize.div(2);
await waitForTx(
await wethGateway
.connect(user.signer)
.repayETH(partialPayment, '2', user.address, {value: partialPayment})
);
const debtBalanceAfterPartialRepay = await varDebtToken.balanceOf(user.address);
expect(debtBalanceAfterPartialRepay).to.be.lt(debtBalance);
// Full Repay WETH loan with native ETH
await waitForTx(
await wethGateway
.connect(user.signer)
.repayETH(MAX_UINT_AMOUNT, '2', user.address, {value: repaySize})
);
const debtBalanceAfterFullRepay = await varDebtToken.balanceOf(user.address);
expect(debtBalanceAfterFullRepay).to.be.eq(zero);
});
it('Borrow ETH via delegateApprove ETH and repays back', async () => {
const {users, wethGateway, aWETH, weth, helpersContract} = testEnv;
const borrowSize = parseEther('1');
const user = users[2];
const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(
weth.address
);
const varDebtToken = await getVariableDebtToken(variableDebtTokenAddress);
const priorDebtBalance = await varDebtToken.balanceOf(user.address);
expect(priorDebtBalance).to.be.eq(zero);
// Deposit WETH with native ETH
await wethGateway.connect(user.signer).depositETH(user.address, '0', {value: depositSize});
const aTokensBalance = await aWETH.balanceOf(user.address);
expect(aTokensBalance).to.be.gt(zero);
expect(aTokensBalance).to.be.gte(depositSize);
// Delegates borrowing power of WETH to WETHGateway
await waitForTx(
await varDebtToken.connect(user.signer).approveDelegation(wethGateway.address, borrowSize)
);
// Borrows ETH with WETH as collateral
await waitForTx(await wethGateway.connect(user.signer).borrowETH(borrowSize, '2', '0'));
const debtBalance = await varDebtToken.balanceOf(user.address);
expect(debtBalance).to.be.gt(zero);
// Full Repay WETH loan with native ETH
await waitForTx(
await wethGateway
.connect(user.signer)
.repayETH(MAX_UINT_AMOUNT, '2', user.address, {value: borrowSize.mul(2)})
);
const debtBalanceAfterFullRepay = await varDebtToken.balanceOf(user.address);
expect(debtBalanceAfterFullRepay).to.be.eq(zero);
});
it('Should revert if receiver function receives Ether if not WETH', async () => {
const {users, wethGateway} = testEnv;
const user = users[0];
const amount = parseEther('1');
// Call receiver function (empty data + value)
await expect(
user.signer.sendTransaction({
to: wethGateway.address,
value: amount,
gasLimit: DRE.network.config.gas,
})
).to.be.revertedWith('Receive not allowed');
});
it('Should revert if fallback functions is called with Ether', async () => {
const {users, wethGateway} = testEnv;
const user = users[0];
const amount = parseEther('1');
const fakeABI = ['function wantToCallFallback()'];
const abiCoder = new DRE.ethers.utils.Interface(fakeABI);
const fakeMethodEncoded = abiCoder.encodeFunctionData('wantToCallFallback', []);
// Call fallback function with value
await expect(
user.signer.sendTransaction({
to: wethGateway.address,
data: fakeMethodEncoded,
value: amount,
gasLimit: DRE.network.config.gas,
})
).to.be.revertedWith('Fallback not allowed');
});
it('Should revert if fallback functions is called', async () => {
const {users, wethGateway} = testEnv;
const user = users[0];
const fakeABI = ['function wantToCallFallback()'];
const abiCoder = new DRE.ethers.utils.Interface(fakeABI);
const fakeMethodEncoded = abiCoder.encodeFunctionData('wantToCallFallback', []);
// Call fallback function without value
await expect(
user.signer.sendTransaction({
to: wethGateway.address,
data: fakeMethodEncoded,
gasLimit: DRE.network.config.gas,
})
).to.be.revertedWith('Fallback not allowed');
});
it('Getters should retrieve correct state', async () => {
const {aWETH, weth, pool, wethGateway} = testEnv;
const WETHAddress = await wethGateway.getWETHAddress();
const aWETHAddress = await wethGateway.getAWETHAddress();
const poolAddress = await wethGateway.getLendingPoolAddress();
expect(WETHAddress).to.be.equal(weth.address);
expect(aWETHAddress).to.be.equal(aWETH.address);
expect(poolAddress).to.be.equal(pool.address);
});
it('Owner can do emergency token recovery', async () => {
const {users, weth, dai, wethGateway, deployer} = testEnv;
const user = users[0];
const amount = parseEther('1');
const uniswapRouter = IUniswapV2Router02Factory.connect(UNISWAP_ROUTER, user.signer);
await uniswapRouter.swapETHForExactTokens(
amount, // 1 DAI
[weth.address, dai.address], // Uniswap paths WETH - DAI
user.address,
(await DRE.ethers.provider.getBlock('latest')).timestamp + 300,
{
value: amount, // 1 Ether, we get refund of the unneeded Ether to buy 1 DAI
}
);
const daiBalanceAfterMint = await dai.balanceOf(user.address);
await dai.connect(user.signer).transfer(wethGateway.address, amount);
const daiBalanceAfterBadTransfer = await dai.balanceOf(user.address);
expect(daiBalanceAfterBadTransfer).to.be.eq(
daiBalanceAfterMint.sub(amount),
'User should have lost the funds here.'
);
await wethGateway
.connect(deployer.signer)
.emergencyTokenTransfer(dai.address, user.address, amount);
const daiBalanceAfterRecovery = await dai.balanceOf(user.address);
expect(daiBalanceAfterRecovery).to.be.eq(
daiBalanceAfterMint,
'User should recover the funds due emergency token transfer'
);
});
it('Owner can do emergency native ETH recovery', async () => {
const {users, wethGateway, deployer} = testEnv;
const user = users[0];
const amount = parseEther('1');
const userBalancePriorCall = await user.signer.getBalance();
// Deploy contract with payable selfdestruct contract
const selfdestructContract = await deploySelfdestructTransferMock();
// Selfdestruct the mock, pointing to WETHGateway address
const callTx = await selfdestructContract
.connect(user.signer)
.destroyAndTransfer(wethGateway.address, {value: amount});
const {gasUsed} = await waitForTx(callTx);
const gasFees = gasUsed.mul(callTx.gasPrice);
const userBalanceAfterCall = await user.signer.getBalance();
expect(userBalanceAfterCall).to.be.eq(userBalancePriorCall.sub(amount).sub(gasFees), '');
'User should have lost the funds';
// Recover the funds from the contract and sends back to the user
await wethGateway.connect(deployer.signer).emergencyEtherTransfer(user.address, amount);
const userBalanceAfterRecovery = await user.signer.getBalance();
const wethGatewayAfterRecovery = await DRE.ethers.provider.getBalance(wethGateway.address);
expect(userBalanceAfterRecovery).to.be.eq(
userBalancePriorCall.sub(gasFees),
'User should recover the funds due emergency eth transfer.'
);
expect(wethGatewayAfterRecovery).to.be.eq('0', 'WETHGateway ether balance should be zero.');
});
});