From 9d7bf388a6afac723b313c499034c15ab8de5163 Mon Sep 17 00:00:00 2001 From: andyk Date: Wed, 9 Sep 2020 13:47:27 +0300 Subject: [PATCH] initial changes + test --- contracts/interfaces/ILendingPool.sol | 4 ++- contracts/lendingpool/LendingPool.sol | 33 +++++++++++++------------ package.json | 2 +- test/atoken-transfer.spec.ts | 9 +++++-- test/configurator.spec.ts | 4 +-- test/flashloan.spec.ts | 13 +++++----- test/helpers/actions.ts | 25 +++++++++++++------ test/helpers/scenario-engine.ts | 16 ++++++++++-- test/helpers/scenarios/deposit.json | 35 ++++++++++++++++++++++++++- test/helpers/utils/helpers.ts | 5 ++-- test/liquidation-atoken.spec.ts | 16 +++++++++--- test/liquidation-underlying.spec.ts | 20 +++++++++++---- 12 files changed, 132 insertions(+), 50 deletions(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index a7a5e1ca..239787ed 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -15,7 +15,8 @@ interface ILendingPool { **/ event Deposit( address indexed reserve, - address indexed user, + address user, + address indexed onBehalfOf, uint256 amount, uint16 indexed referral ); @@ -139,6 +140,7 @@ interface ILendingPool { function deposit( address reserve, uint256 amount, + address onBehalfOf, uint16 referralCode ) external; diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 9e4e6707..a4f62ce8 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -89,6 +89,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { function deposit( address asset, uint256 amount, + address onBehalfOf, uint16 referralCode ) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; @@ -100,18 +101,18 @@ contract LendingPool is VersionedInitializable, ILendingPool { reserve.updateCumulativeIndexesAndTimestamp(); reserve.updateInterestRates(asset, aToken, amount, 0); - bool isFirstDeposit = IAToken(aToken).balanceOf(msg.sender) == 0; + bool isFirstDeposit = IAToken(aToken).balanceOf(onBehalfOf) == 0; if (isFirstDeposit) { - _usersConfig[msg.sender].setUsingAsCollateral(reserve.index, true); + _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.index, true); } //minting AToken to user 1:1 with the specific exchange rate - IAToken(aToken).mint(msg.sender, amount); + IAToken(aToken).mint(onBehalfOf, amount); //transfer to the aToken contract IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); - emit Deposit(asset, msg.sender, amount, referralCode); + emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode); } /** @@ -450,15 +451,13 @@ contract LendingPool is VersionedInitializable, ILendingPool { vars.amountPlusPremium = amount.add(vars.premium); if (debtMode == ReserveLogic.InterestRateMode.NONE) { - IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium); - + reserve.updateCumulativeIndexesAndTimestamp(); reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium); reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0); - - emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); + emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); } else { // If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer. _executeBorrow( @@ -728,13 +727,11 @@ contract LendingPool is VersionedInitializable, ILendingPool { oracle ); - uint256 reserveIndex = reserve.index; if (!userConfig.isBorrowing(reserveIndex)) { userConfig.setBorrowing(reserveIndex, true); } - reserve.updateCumulativeIndexesAndTimestamp(); //caching the current stable borrow rate @@ -754,13 +751,17 @@ contract LendingPool is VersionedInitializable, ILendingPool { IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount); } - reserve.updateInterestRates(vars.asset, vars.aTokenAddress, 0, vars.releaseUnderlying ? vars.amount : 0); - - if(vars.releaseUnderlying){ - IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount); + reserve.updateInterestRates( + vars.asset, + vars.aTokenAddress, + 0, + vars.releaseUnderlying ? vars.amount : 0 + ); + + if (vars.releaseUnderlying) { + IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount); } - - + emit Borrow( vars.asset, msg.sender, diff --git a/package.json b/package.json index 606aaaf9..ab5f751a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'", "test": "buidler test", "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", - "test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts", + "test-flash": "buidler test test/__setup.spec.ts test/collateral-swap.spec.ts", "dev:coverage": "buidler coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", diff --git a/test/atoken-transfer.spec.ts b/test/atoken-transfer.spec.ts index 1c161608..bd113ce3 100644 --- a/test/atoken-transfer.spec.ts +++ b/test/atoken-transfer.spec.ts @@ -33,7 +33,9 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { //user 1 deposits 1000 DAI const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(users[0].signer).deposit(dai.address, amountDAItoDeposit, '0'); + await pool + .connect(users[0].signer) + .deposit(dai.address, amountDAItoDeposit, users[0].address, '0'); await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit); @@ -94,12 +96,15 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => { const {users, pool, weth} = testEnv; + const userAddress = await pool.signer.getAddress(); await weth.connect(users[0].signer).mint(await convertToCurrencyDecimals(weth.address, '1')); await weth.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(users[0].signer).deposit(weth.address, ethers.utils.parseEther('1.0'), '0'); + await pool + .connect(users[0].signer) + .deposit(weth.address, ethers.utils.parseEther('1.0'), userAddress, '0'); await expect( pool .connect(users[1].signer) diff --git a/test/configurator.spec.ts b/test/configurator.spec.ts index a6513e54..6a85791c 100644 --- a/test/configurator.spec.ts +++ b/test/configurator.spec.ts @@ -234,7 +234,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => { const {dai, pool, configurator} = testEnv; - + const userAddress = await pool.signer.getAddress(); await dai.mint(await convertToCurrencyDecimals(dai.address, '1000')); //approve protocol to access depositor wallet @@ -242,7 +242,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); //user 1 deposits 1000 DAI - await pool.deposit(dai.address, amountDAItoDeposit, '0'); + await pool.deposit(dai.address, amountDAItoDeposit, userAddress, '0'); await expect( configurator.deactivateReserve(dai.address), diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 79db4bcb..724be7ef 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -30,13 +30,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { it('Deposits ETH into the reserve', async () => { const {pool, weth} = testEnv; + const userAddress = await pool.signer.getAddress(); const amountToDeposit = ethers.utils.parseEther('1'); await weth.mint(amountToDeposit); await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.deposit(weth.address, amountToDeposit, '0'); + await pool.deposit(weth.address, amountToDeposit, userAddress, '0'); }); it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => { @@ -143,7 +144,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0'); + await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0'); await _mockFlashLoanReceiver.setFailExecutionTransfer(true); @@ -210,6 +211,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { it('Deposits USDC into the reserve', async () => { const {usdc, pool} = testEnv; + const userAddress = await pool.signer.getAddress(); await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000')); @@ -217,7 +219,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.deposit(usdc.address, amountToDeposit, '0'); + await pool.deposit(usdc.address, amountToDeposit, userAddress, '0'); }); it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => { @@ -284,7 +286,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(weth.address, '5'); - await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, '0'); + await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, caller.address, '0'); await _mockFlashLoanReceiver.setFailExecutionTransfer(true); @@ -307,7 +309,6 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { it('Caller deposits 1000 DAI as collateral, Takes a WETH flashloan with mode = 0, does not approve the transfer of the funds', async () => { const {dai, pool, weth, users} = testEnv; - const caller = users[3]; await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000')); @@ -316,7 +317,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0'); + await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0'); const flashAmount = ethers.utils.parseEther('0.8'); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index be0daac5..76d4d907 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -133,7 +133,8 @@ export const approve = async (reserveSymbol: string, user: SignerWithAddress, te export const deposit = async ( reserveSymbol: string, amount: string, - user: SignerWithAddress, + sender: SignerWithAddress, + onBehalfOf: tEthereumAddress, sendValue: string, expectedResult: string, testEnv: TestEnv, @@ -149,8 +150,9 @@ export const deposit = async ( const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( reserve, - user.address, - testEnv + onBehalfOf, + testEnv, + sender.address ); if (sendValue) { @@ -158,14 +160,16 @@ export const deposit = async ( } if (expectedResult === 'success') { const txResult = await waitForTx( - await await pool.connect(user.signer).deposit(reserve, amountToDeposit, '0', txOptions) + await pool + .connect(sender.signer) + .deposit(reserve, amountToDeposit, onBehalfOf, '0', txOptions) ); const { reserveData: reserveDataAfter, userData: userDataAfter, timestamp, - } = await getContractsData(reserve, user.address, testEnv); + } = await getContractsData(reserve, onBehalfOf, testEnv, sender.address); const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); @@ -198,7 +202,7 @@ export const deposit = async ( // }); } else if (expectedResult === 'revert') { await expect( - pool.connect(user.signer).deposit(reserve, amountToDeposit, '0', txOptions), + pool.connect(sender.signer).deposit(reserve, amountToDeposit, onBehalfOf, '0', txOptions), revertMessage ).to.be.reverted; } @@ -845,10 +849,15 @@ const getTxCostAndTimestamp = async (tx: ContractReceipt) => { return {txCost, txTimestamp}; }; -const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => { +const getContractsData = async ( + reserve: string, + user: string, + testEnv: TestEnv, + sender?: string +) => { const {pool} = testEnv; const reserveData = await getReserveData(pool, reserve); - const userData = await getUserData(pool, reserve, user); + const userData = await getUserData(pool, reserve, user, sender || user); const timestamp = await timeLatest(); return { diff --git a/test/helpers/scenario-engine.ts b/test/helpers/scenario-engine.ts index 735d3b84..260e87e5 100644 --- a/test/helpers/scenario-engine.ts +++ b/test/helpers/scenario-engine.ts @@ -92,13 +92,25 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv case 'deposit': { - const {amount, sendValue} = action.args; + const {amount, sendValue, onBehalfOf: onBehalfOfIndex} = action.args; + const onBehalfOf = onBehalfOfIndex + ? users[parseInt(onBehalfOfIndex)].address + : user.address; if (!amount || amount === '') { throw `Invalid amount to deposit into the ${reserve} reserve`; } - await deposit(reserve, amount, user, sendValue, expected, testEnv, revertMessage); + await deposit( + reserve, + amount, + user, + onBehalfOf, + sendValue, + expected, + testEnv, + revertMessage + ); } break; diff --git a/test/helpers/scenarios/deposit.json b/test/helpers/scenarios/deposit.json index 34f9c9e9..2456d931 100644 --- a/test/helpers/scenarios/deposit.json +++ b/test/helpers/scenarios/deposit.json @@ -206,7 +206,6 @@ "name": "deposit", "args": { "reserve": "WETH", - "amount": "0", "user": "1" }, @@ -229,6 +228,40 @@ "revertMessage": "Amount must be greater than 0" } ] + }, + { + "description": "User 1 deposits 100 DAI on behalf of user 2, user 2 tries to borrow 0.1 WETH", + "actions": [ + { + "name": "mint", + "args": { + "reserve": "DAI", + "amount": "100", + "user": "1" + }, + "expected": "success" + }, + { + "name": "deposit", + "args": { + "reserve": "DAI", + "amount": "100", + "user": "1", + "onBehalfOf": "2" + }, + "expected": "success" + }, + { + "name": "borrow", + "args": { + "reserve": "WETH", + "amount": "0.1", + "borrowRateMode": "variable", + "user": "2" + }, + "expected": "success" + } + ] } ] } diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index c20c67ac..9dac685b 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -61,7 +61,8 @@ export const getReserveData = async ( export const getUserData = async ( pool: LendingPool, reserve: string, - user: string + user: tEthereumAddress, + sender?: tEthereumAddress ): Promise => { const [userData, aTokenData] = await Promise.all([ pool.getUserReserveData(reserve, user), @@ -77,7 +78,7 @@ export const getUserData = async ( ] = aTokenData; const token = await getMintableErc20(reserve); - const walletBalance = new BigNumber((await token.balanceOf(user)).toString()); + const walletBalance = new BigNumber((await token.balanceOf(sender || user)).toString()); return { principalATokenBalance: new BigNumber(principalATokenBalance), diff --git a/test/liquidation-atoken.spec.ts b/test/liquidation-atoken.spec.ts index 921114f0..90293397 100644 --- a/test/liquidation-atoken.spec.ts +++ b/test/liquidation-atoken.spec.ts @@ -32,7 +32,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => //user 1 deposits 1000 DAI const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(dai.address, amountDAItoDeposit, depositor.address, '0'); const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -43,7 +45,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); //user 2 deposits 1 WETH - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //user 2 borrows const userGlobalData = await pool.getUserAccountData(borrower.address); @@ -224,7 +228,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => //user 3 deposits 1000 USDC const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0'); //user 4 deposits 1 ETH const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -235,7 +241,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => //approve protocol to access borrower wallet await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //user 4 borrows const userGlobalData = await pool.getUserAccountData(borrower.address); diff --git a/test/liquidation-underlying.spec.ts b/test/liquidation-underlying.spec.ts index 064e3856..4be6097f 100644 --- a/test/liquidation-underlying.spec.ts +++ b/test/liquidation-underlying.spec.ts @@ -29,7 +29,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //user 1 deposits 1000 DAI const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(dai.address, amountDAItoDeposit, depositor.address, '0'); //user 2 deposits 1 ETH const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -39,7 +41,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //approve protocol to access the borrower wallet await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //user 2 borrows @@ -194,7 +198,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //depositor deposits 1000 USDC const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0'); //borrower deposits 1 ETH const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -205,7 +211,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //approve protocol to access the borrower wallet await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //borrower borrows const userGlobalData = await pool.getUserAccountData(borrower.address); @@ -334,7 +342,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //borrower deposits 1000 LEND const amountLENDtoDeposit = await convertToCurrencyDecimals(lend.address, '1000'); - await pool.connect(borrower.signer).deposit(lend.address, amountLENDtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(lend.address, amountLENDtoDeposit, borrower.address, '0'); const usdcPrice = await oracle.getAssetPrice(usdc.address); //drops HF below 1