From 123415be616f472fce690892811f1d68755b8786 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Thu, 14 Oct 2021 11:24:33 +0100 Subject: [PATCH] feat: Support older aTokens without incentives controller --- contracts/interfaces/IStaticATokenLM.sol | 14 +- .../protocol/tokenization/StaticATokenLM.sol | 46 +- package.json | 3 +- ...ic-atoken-liquidity-mining-rewards.spec.ts | 983 ++++++++++++ .../static-atoken-liquidity-mining.spec.ts | 1329 +++++++++++++++++ .../static-atoken-liquidity-mining.spec.ts | 2 + tsconfig.json | 12 +- 7 files changed, 2380 insertions(+), 9 deletions(-) create mode 100644 test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining-rewards.spec.ts create mode 100644 test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining.spec.ts diff --git a/contracts/interfaces/IStaticATokenLM.sol b/contracts/interfaces/IStaticATokenLM.sol index 15c24744..31027987 100644 --- a/contracts/interfaces/IStaticATokenLM.sol +++ b/contracts/interfaces/IStaticATokenLM.sol @@ -230,13 +230,17 @@ interface IStaticATokenLM is IERC20, IInitializableStaticATokenLM { function getLastRewardBlock() external view returns (uint256); - function LENDING_POOL() external returns (ILendingPool); + function LENDING_POOL() external view returns (ILendingPool); - function INCENTIVES_CONTROLLER() external returns (IAaveIncentivesController); + function INCENTIVES_CONTROLLER() external view returns (IAaveIncentivesController); - function ATOKEN() external returns (IERC20); + function ATOKEN() external view returns (IERC20); - function ASSET() external returns (IERC20); + function ASSET() external view returns (IERC20); - function REWARD_TOKEN() external returns (IERC20); + function REWARD_TOKEN() external view returns (IERC20); + + function UNDERLYING_ASSET_ADDRESS() external view returns (address); + + function getIncentivesController() external view returns (IAaveIncentivesController); } diff --git a/contracts/protocol/tokenization/StaticATokenLM.sol b/contracts/protocol/tokenization/StaticATokenLM.sol index db26941a..6fb61a8d 100644 --- a/contracts/protocol/tokenization/StaticATokenLM.sol +++ b/contracts/protocol/tokenization/StaticATokenLM.sol @@ -92,8 +92,14 @@ contract StaticATokenLM is ASSET = IERC20(IAToken(aToken).UNDERLYING_ASSET_ADDRESS()); ASSET.safeApprove(address(pool), type(uint256).max); - INCENTIVES_CONTROLLER = IAToken(aToken).getIncentivesController(); - REWARD_TOKEN = IERC20(INCENTIVES_CONTROLLER.REWARD_TOKEN()); + try IAToken(aToken).getIncentivesController() returns ( + IAaveIncentivesController incentivesController + ) { + if (address(incentivesController) != address(0)) { + INCENTIVES_CONTROLLER = incentivesController; + REWARD_TOKEN = IERC20(INCENTIVES_CONTROLLER.REWARD_TOKEN()); + } + } catch {} emit Initialized(address(pool), aToken, staticATokenName, staticATokenSymbol); } @@ -355,6 +361,9 @@ contract StaticATokenLM is address to, uint256 amount ) internal override { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } if (from != address(0)) { _updateUser(from); } @@ -367,6 +376,9 @@ contract StaticATokenLM is * @notice Updates virtual internal accounting of rewards. */ function _updateRewards() internal { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } if (block.number > _lastRewardBlock) { _lastRewardBlock = block.number; uint256 supply = totalSupply(); @@ -391,6 +403,10 @@ contract StaticATokenLM is ///@inheritdoc IStaticATokenLM function collectAndUpdateRewards() public override { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } + _lastRewardBlock = block.number; uint256 supply = totalSupply(); @@ -448,6 +464,10 @@ contract StaticATokenLM is address receiver, bool forceUpdate ) external override { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } + require( msg.sender == onBehalfOf || msg.sender == INCENTIVES_CONTROLLER.getClaimer(onBehalfOf), StaticATokenErrors.INVALID_CLAIMER @@ -456,10 +476,16 @@ contract StaticATokenLM is } function claimRewards(address receiver, bool forceUpdate) external override { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } _claimRewardsOnBehalf(msg.sender, receiver, forceUpdate); } function claimRewardsToSelf(bool forceUpdate) external override { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } _claimRewardsOnBehalf(msg.sender, msg.sender, forceUpdate); } @@ -496,6 +522,10 @@ contract StaticATokenLM is uint256 balance, bool fresh ) internal view returns (uint256) { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return 0; + } + if (balance == 0) { return 0; } @@ -538,6 +568,10 @@ contract StaticATokenLM is ///@inheritdoc IStaticATokenLM function getTotalClaimableRewards() external view override returns (uint256) { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return 0; + } + address[] memory assets = new address[](1); assets[0] = address(ATOKEN); uint256 freshRewards = INCENTIVES_CONTROLLER.getRewardsBalance(assets, address(this)); @@ -569,4 +603,12 @@ contract StaticATokenLM is function getLastRewardBlock() external view override returns (uint256) { return _lastRewardBlock; } + + function getIncentivesController() external view override returns (IAaveIncentivesController) { + return INCENTIVES_CONTROLLER; + } + + function UNDERLYING_ASSET_ADDRESS() external view override returns (address) { + return address(ASSET); + } } diff --git a/package.json b/package.json index 8c376b6b..59fe062f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ ], "scripts": { "test:main:lmtoken": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/test-aave/mainnet/static-atoken-lm/*.ts", - "coverage:lmtoken": "npm run compile && npx hardhat coverage --testfiles 'test-suites/test-aave/emptyrun.coverage.ts' && rm -rf coverage.json coverage/ && MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat coverage --testfiles 'test-suites/test-aave/mainnet/static-atoken-lm/*.ts'", + "test:main:lmtoken:noic": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/*.ts", + "coverage:lmtoken": "npm run compile && npx hardhat coverage --testfiles 'test-suites/test-aave/emptyrun.coverage.ts' && rm -rf coverage.json coverage/ && MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat coverage --testfiles 'test-suites/test-aave/mainnet/*/*.ts'", "run-env": "npm i && tail -f /dev/null", "hardhat": "hardhat", "hardhat:kovan": "hardhat --network kovan", diff --git a/test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining-rewards.spec.ts b/test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining-rewards.spec.ts new file mode 100644 index 00000000..cee20bb7 --- /dev/null +++ b/test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining-rewards.spec.ts @@ -0,0 +1,983 @@ +import rawDRE, { ethers } from 'hardhat'; +import { + LendingPoolFactory, + WETH9Factory, + ATokenFactory, + ERC20, + LendingPool, + StaticATokenLMFactory, + ERC20Factory, + WETH9, + AToken, + StaticATokenLM, +} from '../../../../types'; +import { + impersonateAccountsHardhat, + DRE, + waitForTx, + evmRevert, + evmSnapshot, + timeLatest, + advanceTimeAndBlock, +} from '../../../../helpers/misc-utils'; +import { BigNumber, providers, Signer, utils } from 'ethers'; +import { MAX_UINT_AMOUNT, USD_ADDRESS } from '../../../../helpers/constants'; +import { AbiCoder, formatEther, verifyTypedData } from 'ethers/lib/utils'; + +import { _TypedDataEncoder } from 'ethers/lib/utils'; + +import { expect, use } from 'chai'; + +//use(solidity); + +const DEFAULT_GAS_LIMIT = 10000000; +const DEFAULT_GAS_PRICE = utils.parseUnits('100', 'gwei'); + +const defaultTxParams = { gasLimit: DEFAULT_GAS_LIMIT, gasPrice: DEFAULT_GAS_PRICE }; + +const LENDING_POOL = '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9'; + +const STKAAVE = '0x4da27a545c0c5B758a6BA100e3a049001de870f5'; +const AENJ = '0xaC6Df26a590F08dcC95D5a4705ae8abbc88509Ef'; +const ENJ = '0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c'; + +const getUserData = async (_users: Signer[], _debug = false, { staticAToken, stkAave }) => { + let usersData: { + pendingRewards: BigNumber; + stkAaveBalance: BigNumber; + staticBalance: BigNumber; + }[] = []; + if (_debug) { + console.log(`Printing user data:`); + } + for (let i = 0; i < _users.length; i++) { + const userAddress = await _users[i].getAddress(); + usersData.push({ + pendingRewards: await staticAToken.getClaimableRewards(userAddress), + stkAaveBalance: await stkAave.balanceOf(userAddress), + staticBalance: await staticAToken.balanceOf(userAddress), + }); + if (_debug) { + console.log( + `\tUser ${i} pendingRewards: ${formatEther( + usersData[i].pendingRewards + )}, stkAave balance: ${formatEther(usersData[i].stkAaveBalance)}, static bal: ${formatEther( + usersData[i].staticBalance + )} ` + ); + } + } + return usersData; +}; + +const ENJ_WHALE = '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8'; + +describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity mining', () => { + let userSigner: providers.JsonRpcSigner; + let user2Signer: providers.JsonRpcSigner; + let lendingPool: LendingPool; + let stkAave: ERC20; + let enj: ERC20; + let aenj: AToken; + + let staticAToken: StaticATokenLM; + + let snap: string; + + let enjWhale: providers.JsonRpcSigner; + + before(async () => { + await rawDRE.run('set-DRE'); + + const [user1, user2] = await DRE.ethers.getSigners(); + userSigner = DRE.ethers.provider.getSigner(await user1.getAddress()); + user2Signer = DRE.ethers.provider.getSigner(await user2.getAddress()); + enjWhale = DRE.ethers.provider.getSigner(ENJ_WHALE); + lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner); + + enj = ERC20Factory.connect(ENJ, userSigner); + aenj = ATokenFactory.connect(AENJ, userSigner); + stkAave = ERC20Factory.connect(STKAAVE, userSigner); + + staticAToken = await new StaticATokenLMFactory(userSigner).deploy(); + await staticAToken.initialize(LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj'); + + expect(await staticAToken.decimals()).to.be.eq(18); + expect(await staticAToken.name()).to.be.eq('Wrapped aENJ'); + expect(await staticAToken.symbol()).to.be.eq('waaenj'); + expect(await staticAToken.ATOKEN()).to.be.eq(AENJ); + + await impersonateAccountsHardhat([ENJ_WHALE]); + + const balanceWhale = await enj.balanceOf(ENJ_WHALE); + + await waitForTx(await enj.connect(enjWhale).transfer(userSigner._address, balanceWhale)); + + snap = await evmSnapshot(); + }); + + beforeEach(async () => { + await evmRevert(snap); + snap = await evmSnapshot(); + }); + + after(async () => { + await evmRevert(snap); + }); + + describe('Small checks', async () => { + it('No rewards accrue at deposit, update or withdraw', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); + + // Withdrawing all. + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address); + const totPendingRewards4 = await staticAToken.getTotalClaimableRewards(); + const claimedRewards4 = await stkAave.balanceOf(userSigner._address); + const stkAaveStatic4 = await stkAave.balanceOf(staticAToken.address); + + await waitForTx(await staticAToken.connect(userSigner).claimRewardsToSelf(false)); + + const pendingRewards5 = await staticAToken.getClaimableRewards(userSigner._address); + const totPendingRewards5 = await staticAToken.getTotalClaimableRewards(); + const claimedRewards5 = await stkAave.balanceOf(userSigner._address); + const stkAaveStatic5 = await stkAave.balanceOf(staticAToken.address); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + const pendingRewards6 = await staticAToken.getClaimableRewards(userSigner._address); + + // Checks + expect(pendingRewards1).to.be.eq(0); + expect(pendingRewards2).to.be.eq(0); + expect(pendingRewards3).to.be.eq(0); + expect(pendingRewards4).to.be.eq(0); + expect(pendingRewards5).to.be.eq(0); + expect(pendingRewards6).to.be.eq(0); + expect(totPendingRewards4).to.be.eq(0); + expect(totPendingRewards5).to.be.eq(0); + expect(claimedRewards4).to.be.eq(0); + expect(claimedRewards5).to.be.eq(0); + expect(stkAaveStatic4).to.be.eq(0); + expect(stkAaveStatic5).to.be.eq(0); + }); + + it('Check getters', async () => { + const amountToDeposit = utils.parseEther('5'); + + const accRewardsPerTokenPre = await staticAToken.getAccRewardsPerToken(); + const lifetimeRewardsClaimedPre = await staticAToken.getLifetimeRewardsClaimed(); + const lifetimeRewards = await staticAToken.getLifetimeRewards(); + const lastRewardBlock = await staticAToken.getLastRewardBlock(); + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const staticBalance = await staticAToken.balanceOf(userSigner._address); + const dynamicBalance = await staticAToken.dynamicBalanceOf(userSigner._address); + + const dynamicBalanceFromStatic = await staticAToken.staticToDynamicAmount(staticBalance); + const staticBalanceFromDynamic = await staticAToken.dynamicToStaticAmount(dynamicBalance); + + expect(staticBalance).to.be.eq(staticBalanceFromDynamic); + expect(dynamicBalance).to.be.eq(dynamicBalanceFromStatic); + + await staticAToken.collectAndUpdateRewards(); + + expect(await staticAToken.getAccRewardsPerToken()).to.be.eq(0); + expect(accRewardsPerTokenPre).to.be.eq(0); + expect(await staticAToken.getLifetimeRewardsClaimed()).to.be.eq(0); + expect(lifetimeRewardsClaimedPre).to.be.eq(0); + expect(await staticAToken.getLifetimeRewards()).to.be.eq(0); + expect(lifetimeRewards).to.be.eq(0); + expect(await staticAToken.getLastRewardBlock()).to.be.eq(0); + expect(lastRewardBlock).to.be.eq(0); + }); + + it('Multiple deposits in one block (Breaks if GasReport enabled)', async () => { + const amountToDeposit = utils.parseEther('5'); + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + await DRE.network.provider.send('evm_setAutomine', [false]); + + // Depositing + let a = await staticAToken.deposit( + userSigner._address, + amountToDeposit, + 0, + true, + defaultTxParams + ); + + // Depositing + let b = await staticAToken.deposit( + userSigner._address, + amountToDeposit, + 0, + true, + defaultTxParams + ); + + await DRE.network.provider.send('evm_mine', []); + + const aReceipt = await DRE.network.provider.send('eth_getTransactionReceipt', [a.hash]); + const bReceipt = await DRE.network.provider.send('eth_getTransactionReceipt', [b.hash]); + + const aGas = BigNumber.from(aReceipt['gasUsed']); + const bGas = BigNumber.from(bReceipt['gasUsed']); + + expect(aGas).to.be.gt(250000); + expect(bGas).to.be.lt(200000); + + await DRE.network.provider.send('evm_setAutomine', [true]); + await DRE.network.provider.send('evm_mine', []); + }); + + it('Multiple collectAndUpdate in one block (Breaks if GasReport enabled)', async () => { + const amountToDeposit = utils.parseEther('5'); + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + await DRE.network.provider.send('evm_setAutomine', [false]); + + let a = await staticAToken.collectAndUpdateRewards(); + let b = await staticAToken.collectAndUpdateRewards(); + + await DRE.network.provider.send('evm_mine', []); + + const aReceipt = await DRE.network.provider.send('eth_getTransactionReceipt', [a.hash]); + const bReceipt = await DRE.network.provider.send('eth_getTransactionReceipt', [b.hash]); + + const aGas = BigNumber.from(aReceipt['gasUsed']); + const bGas = BigNumber.from(bReceipt['gasUsed']); + + expect(aGas).to.be.lt(25000); + expect(bGas).to.be.lt(25000); + + await DRE.network.provider.send('evm_setAutomine', [true]); + }); + + it('Update and claim', async () => { + const amountToDeposit = utils.parseEther('5'); + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); + const claimedRewards3 = await stkAave.balanceOf(userSigner._address); + + await waitForTx(await staticAToken.connect(userSigner).claimRewardsToSelf(true)); + + const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address); + const claimedRewards4 = await stkAave.balanceOf(userSigner._address); + + expect(pendingRewards1).to.be.eq(0); + expect(pendingRewards2).to.be.eq(0); + expect(pendingRewards3).to.be.eq(0); + expect(pendingRewards4).to.be.eq(0); + expect(claimedRewards3).to.be.eq(0); + expect(claimedRewards4).to.be.eq(0); + }); + + it('Withdraw to other user', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + const recipient = user2Signer._address; + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const userPendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + const recipientPendingRewards1 = await staticAToken.getClaimableRewards(recipient); + + // Withdrawing all + await waitForTx( + await staticAToken.withdraw(recipient, amountToWithdraw, true, defaultTxParams) + ); + + const userPendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + const recipientPendingRewards2 = await staticAToken.getClaimableRewards(recipient); + + expect(userPendingRewards1).to.be.eq(0); + expect(userPendingRewards2).to.be.eq(0); + expect(recipientPendingRewards1).to.be.eq(0); + expect(recipientPendingRewards2).to.be.eq(0); + }); + + it('Deposit, Wait, Withdraw, claim?', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + + await advanceTimeAndBlock(60 * 60); + + const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + + // Withdrawing all. + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + // How will my pending look now + const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); + + await waitForTx(await staticAToken.connect(userSigner).claimRewardsToSelf(true)); + + const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address); + const userBalance4 = await stkAave.balanceOf(userSigner._address); + + expect(pendingRewards1).to.be.eq(0); + expect(pendingRewards2).to.be.eq(0); + expect(pendingRewards3).to.be.eq(0); + expect(pendingRewards4).to.be.eq(0); + }); + + it('Deposit, Wait, Withdraw, claim to other user', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + + await advanceTimeAndBlock(60 * 60); + + const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + + // Withdrawing all. + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + // How will my pending look now + const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); + + const userBalance3 = await stkAave.balanceOf(userSigner._address); + await staticAToken.connect(user2Signer).claimRewards(userSigner._address, true); + const userBalance4 = await stkAave.balanceOf(userSigner._address); + + await waitForTx( + await staticAToken.connect(userSigner).claimRewards(user2Signer._address, true) + ); + + const pendingRewards5 = await staticAToken.getClaimableRewards(userSigner._address); + const user2Balance5 = await stkAave.balanceOf(user2Signer._address); + + expect(pendingRewards1).to.be.eq(0); + expect(pendingRewards2).to.be.eq(0); + expect(pendingRewards3).to.be.eq(0); + + expect(userBalance3).to.be.eq(userBalance4); + expect(pendingRewards5).to.be.eq(0); + expect(user2Balance5).to.be.eq(pendingRewards3); + }); + + it('Deposit, Wait, collectAndUpdate, Withdraw, claim?', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + + await advanceTimeAndBlock(60 * 60); + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + + // Withdrawing all. + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); + + await waitForTx(await staticAToken.connect(userSigner).claimRewardsToSelf(true)); + + const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address); + const userBalance4 = await stkAave.balanceOf(userSigner._address); + + expect(pendingRewards1).to.be.eq(0); + expect(pendingRewards2).to.be.eq(0); + expect(pendingRewards3).to.be.eq(0); + expect(pendingRewards4).to.be.eq(0); + + expect(userBalance4).to.be.eq(pendingRewards3); + }); + + it('Throw away as much as possible: Deposit, collectAndUpdate, wait, Withdraw, claim', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Just preparation + await waitForTx( + await enj.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const pendingRewards1 = await staticAToken.getClaimableRewards(userSigner._address); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + await advanceTimeAndBlock(60 * 60); + + const pendingRewards2 = await staticAToken.getClaimableRewards(userSigner._address); + + // Withdrawing all. + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + // How will my pending look now + const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); + const unclaimedRewards3 = await staticAToken.getUnclaimedRewards(userSigner._address); + + await waitForTx(await staticAToken.connect(userSigner).claimRewardsToSelf(false)); + + const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address); + const userBalance4 = await stkAave.balanceOf(userSigner._address); + const totClaimable4 = await staticAToken.getTotalClaimableRewards(); + const unclaimedRewards4 = await staticAToken.getUnclaimedRewards(userSigner._address); + + expect(pendingRewards1).to.be.eq(0); + expect(pendingRewards2).to.be.eq(0); + expect(pendingRewards3).to.be.eq(0); + expect(pendingRewards4).to.be.eq(0); + expect(totClaimable4).to.be.eq(0); + expect(userBalance4).to.be.eq(0); + expect(unclaimedRewards3).to.be.eq(0); + expect(unclaimedRewards4).to.be.eq(0); + }); + }); + + it('Multiple users deposit ENJ on waaenj, wait 1 hour, update rewards, one user transfer, then claim and update rewards.', async () => { + // In this case, the recipient should have approx 1.5 the rewards of the others. + + // 1. Deposit + // 2. Wait 3600 seconds + // 2-5. Update rewards + // 3. Transfer + // 4. Wait 3600 seconds + // 5. Claim rewards + // 6. Update rewards + + const amountToDeposit = utils.parseEther('5'); + const allusers = await DRE.ethers.getSigners(); + const users = [allusers[0], allusers[1], allusers[2], allusers[3], allusers[4]]; + + const _debugUserData = false; + + for (let i = 0; i < 5; i++) { + let currentUser = users[i]; + // Preparation + await waitForTx( + await enj.connect(allusers[0]).transfer(await currentUser.getAddress(), amountToDeposit) + ); + await waitForTx( + await enj + .connect(currentUser) + .approve(staticAToken.address, amountToDeposit, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(currentUser) + .deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams) + ); + } + + // Advance time to accrue significant rewards. + await advanceTimeAndBlock(60 * 60); + await staticAToken.collectAndUpdateRewards(); + + let staticATokenTotClaimableInitial = await staticAToken.getTotalClaimableRewards(); + let usersDataInitial = await getUserData(users, _debugUserData, { staticAToken, stkAave }); + + await waitForTx( + await staticAToken + .connect(users[0]) + .transfer( + await users[1].getAddress(), + await staticAToken.balanceOf(await users[0].getAddress()) + ) + ); + + await advanceTimeAndBlock(60 * 60); + + for (let i = 0; i < 5; i++) { + // This will claim the first half of the collected tokens (those collected at `collectAndUpdateRewards`) + await waitForTx(await staticAToken.connect(users[i]).claimRewardsToSelf(false)); + } + + let staticATokenTotClaimableAfterTransferAndClaim = await staticAToken.getTotalClaimableRewards(); + let usersDataAfterTransferAndClaim = await getUserData(users, _debugUserData, { + staticAToken, + stkAave, + }); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + let staticATokenTotClaimableFinal = await staticAToken.getTotalClaimableRewards(); + let usersDataFinal = await getUserData(users, _debugUserData, { staticAToken, stkAave }); + + // Time for checks + let pendingRewardsSumInitial = BigNumber.from(0); + let pendingRewardsSumAfter = BigNumber.from(0); + let pendingRewardsSumFinal = BigNumber.from(0); + for (let i = 0; i < 5; i++) { + expect(usersDataInitial[i].stkAaveBalance).to.be.eq(0); + expect(usersDataAfterTransferAndClaim[i].stkAaveBalance).to.be.eq(0); + expect(usersDataFinal[i].stkAaveBalance).to.be.eq(0); + expect(usersDataInitial[i].pendingRewards).to.be.eq(0); + expect(usersDataAfterTransferAndClaim[i].pendingRewards).to.be.eq(0); + expect(usersDataFinal[i].pendingRewards).to.be.eq(0); + + if (i > 1) { + // Expect initial static balance == after transfer == after claiming + expect(usersDataInitial[i].staticBalance).to.be.eq( + usersDataAfterTransferAndClaim[i].staticBalance + ); + expect(usersDataInitial[i].staticBalance).to.be.eq(usersDataFinal[i].staticBalance); + } + + pendingRewardsSumInitial = pendingRewardsSumInitial.add(usersDataInitial[i].pendingRewards); + pendingRewardsSumAfter = pendingRewardsSumAfter.add( + usersDataAfterTransferAndClaim[i].pendingRewards + ); + pendingRewardsSumFinal = pendingRewardsSumFinal.add(usersDataFinal[i].pendingRewards); + } + + // Expect user 0 to accrue zero fees after the transfer + expect(usersDataAfterTransferAndClaim[0].staticBalance).to.be.eq(0); + expect(usersDataAfterTransferAndClaim[0].pendingRewards).to.be.eq(0); + expect(usersDataFinal[0].staticBalance).to.be.eq(0); + expect(usersDataFinal[0].pendingRewards).to.be.eq(0); + + // Expect user 1 to have received funds + expect(usersDataAfterTransferAndClaim[1].staticBalance).to.be.eq( + usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance) + ); + + expect(pendingRewardsSumInitial).to.be.eq(0); + expect(pendingRewardsSumAfter).to.be.eq(0); + expect(pendingRewardsSumFinal).to.be.eq(0); + + expect(staticATokenTotClaimableInitial).to.be.eq(0); + expect(staticATokenTotClaimableAfterTransferAndClaim).to.be.eq(0); + expect(staticATokenTotClaimableFinal).to.be.eq(0); + }); + + it('Multiple users deposit ENJ on waaenj, wait 1 hour, one user transfer, then claim and update rewards.', async () => { + // In this case, the recipient should have approx twice the rewards. + // Note that he has not held the 2x balance for this entire time, but only for one block. + // He have gotten this extra reward from the sender, because there was not a update prior. + + // 1. Deposit + // 2. Wait 3600 seconds + // 3. Transfer + // 4. Wait 3600 seconds + // 5. Claim rewards + // 6. Update rewards + + const amountToDeposit = utils.parseEther('5'); + const allusers = await DRE.ethers.getSigners(); + const users = [allusers[0], allusers[1], allusers[2], allusers[3], allusers[4]]; + + const _debugUserData = false; + + for (let i = 0; i < 5; i++) { + let currentUser = users[i]; + // Preparation + await waitForTx( + await enj.connect(allusers[0]).transfer(await currentUser.getAddress(), amountToDeposit) + ); + await waitForTx( + await enj + .connect(currentUser) + .approve(staticAToken.address, amountToDeposit, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(currentUser) + .deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams) + ); + } + + // Advance time to accrue significant rewards. + await advanceTimeAndBlock(60 * 60); + + let staticATokenTotClaimableInitial = await staticAToken.getTotalClaimableRewards(); + let usersDataInitial = await getUserData(users, _debugUserData, { staticAToken, stkAave }); + + // User 0 transfer full balance of staticATokens to user 1. This will also transfer the rewards since last update as well. + await waitForTx( + await staticAToken + .connect(users[0]) + .transfer( + await users[1].getAddress(), + await staticAToken.balanceOf(await users[0].getAddress()) + ) + ); + + await advanceTimeAndBlock(60 * 60); + + for (let i = 0; i < 5; i++) { + // This will not do anything, hence there is no rewards in the current contract. + await waitForTx(await staticAToken.connect(users[i]).claimRewardsToSelf(false)); + } + + let staticATokenTotClaimableAfterTransfer = await staticAToken.getTotalClaimableRewards(); + let usersDataAfterTransfer = await getUserData(users, _debugUserData, { + staticAToken, + stkAave, + }); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + let staticATokenTotClaimableFinal = await staticAToken.getTotalClaimableRewards(); + let usersDataFinal = await getUserData(users, _debugUserData, { staticAToken, stkAave }); + + // Time for checks + let pendingRewardsSumInitial = BigNumber.from(0); + let pendingRewardsSumAfter = BigNumber.from(0); + let pendingRewardsSumFinal = BigNumber.from(0); + for (let i = 0; i < 5; i++) { + expect(usersDataInitial[i].stkAaveBalance).to.be.eq(0); + expect(usersDataAfterTransfer[i].stkAaveBalance).to.be.eq(0); + expect(usersDataFinal[i].stkAaveBalance).to.be.eq(0); + expect(usersDataInitial[i].pendingRewards).to.be.eq(0); + expect(usersDataAfterTransfer[i].pendingRewards).to.be.eq(0); + expect(usersDataFinal[i].pendingRewards).to.be.eq(0); + if (i > 1) { + // Expect initial static balance == after transfer == after claiming + expect(usersDataInitial[i].staticBalance).to.be.eq(usersDataAfterTransfer[i].staticBalance); + expect(usersDataInitial[i].staticBalance).to.be.eq(usersDataFinal[i].staticBalance); + } + + pendingRewardsSumInitial = pendingRewardsSumInitial.add(usersDataInitial[i].pendingRewards); + pendingRewardsSumAfter = pendingRewardsSumAfter.add(usersDataAfterTransfer[i].pendingRewards); + pendingRewardsSumFinal = pendingRewardsSumFinal.add(usersDataFinal[i].pendingRewards); + } + + expect(await staticAToken.getTotalClaimableRewards()).to.be.eq(0); + expect(await stkAave.balanceOf(staticAToken.address)).to.be.eq(0); + expect(pendingRewardsSumInitial).to.be.eq(0); + expect(pendingRewardsSumAfter).to.be.eq(0); + expect(pendingRewardsSumFinal).to.be.eq(0); + + expect(usersDataAfterTransfer[0].staticBalance).to.be.eq(0); + expect(usersDataFinal[0].staticBalance).to.be.eq(0); + + // Expect user 1 to have received funds + expect(usersDataAfterTransfer[1].staticBalance).to.be.eq( + usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance) + ); + + expect(staticATokenTotClaimableInitial).to.be.eq(0); + expect(staticATokenTotClaimableAfterTransfer).to.be.eq(0); + expect(staticATokenTotClaimableFinal).to.be.eq(0); + + expect(pendingRewardsSumInitial).to.be.eq(0); + expect(pendingRewardsSumAfter).to.be.eq(0); + expect(pendingRewardsSumFinal).to.be.eq(0); + }); + + it('Mass deposit, then mass claim to own account', async () => { + const amountToDeposit = utils.parseEther('1.1'); // 18 decimals should be the worst here //1.135359735917531199 + const users = await DRE.ethers.getSigners(); + + const depositCount = users.length; + + for (let i = 0; i < depositCount; i++) { + let currentUser = users[i % users.length]; + // Preparation + await waitForTx( + await enj.connect(users[0]).transfer(await currentUser.getAddress(), amountToDeposit) + ); + await waitForTx( + await enj + .connect(currentUser) + .approve(staticAToken.address, amountToDeposit, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(currentUser) + .deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams) + ); + } + + // Advance time to accrue significant rewards. + await advanceTimeAndBlock(60 * 60); + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + for (let i = 0; i < users.length; i++) { + const pendingReward = await staticAToken.getClaimableRewards(await users[i].getAddress()); + expect(pendingReward).to.be.eq(0); + } + for (let i = 0; i < users.length; i++) { + await waitForTx(await staticAToken.connect(users[i]).claimRewardsToSelf(false)); + expect(await stkAave.balanceOf(await users[i].getAddress())).to.be.eq(0); + } + expect(await stkAave.balanceOf(staticAToken.address)).to.be.eq(0); + }); + + it('Mass deposit, then mass claim to specified account', async () => { + const amountToDeposit = utils.parseEther('1.1'); // 18 decimals should be the worst here //1.135359735917531199 + const users = await DRE.ethers.getSigners(); + + const depositCount = users.length; + + for (let i = 0; i < depositCount; i++) { + let currentUser = users[i % users.length]; + // Preparation + await waitForTx( + await enj.connect(users[0]).transfer(await currentUser.getAddress(), amountToDeposit) + ); + await waitForTx( + await enj + .connect(currentUser) + .approve(staticAToken.address, amountToDeposit, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(currentUser) + .deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams) + ); + } + + // Advance time to accrue significant rewards. + await advanceTimeAndBlock(60 * 60); + await waitForTx(await staticAToken.collectAndUpdateRewards()); + + const receiverAddress = await users[0].getAddress(); + + for (let i = 0; i < users.length; i++) { + const pendingReward = await staticAToken.getClaimableRewards(await users[i].getAddress()); + expect(pendingReward).to.be.eq(0); + } + for (let i = 0; i < users.length; i++) { + await waitForTx(await staticAToken.connect(users[i]).claimRewards(receiverAddress, false)); + expect(await stkAave.balanceOf(receiverAddress)).to.be.eq(0); + } + expect(await stkAave.balanceOf(staticAToken.address)).to.be.eq(0); + }); + + it('Mass deposits, mass withdraws and mass claims', async () => { + const amountToDeposit = utils.parseEther('1.135359735917531199'); // 18 decimals should be the worst here //1.135359735917531199 + const users = await DRE.ethers.getSigners(); + + const depositCount = users.length; + + for (let i = 0; i < depositCount; i++) { + let currentUser = users[i % users.length]; + // Preparation + await waitForTx( + await enj.connect(users[0]).transfer(await currentUser.getAddress(), amountToDeposit) + ); + await waitForTx( + await enj + .connect(currentUser) + .approve(staticAToken.address, amountToDeposit, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(currentUser) + .deposit(await currentUser.getAddress(), amountToDeposit, 0, true, defaultTxParams) + ); + + await advanceTimeAndBlock(60); + + await waitForTx( + await staticAToken + .connect(currentUser) + .withdraw(await currentUser.getAddress(), MAX_UINT_AMOUNT, true, defaultTxParams) + ); + + const pendingReward = await staticAToken.getClaimableRewards(await users[i].getAddress()); + await waitForTx(await staticAToken.connect(users[i]).claimRewardsToSelf(true)); + expect(pendingReward).to.be.eq(0); + expect(await stkAave.balanceOf(await users[i].getAddress())).to.be.eq(pendingReward); + } + }); + + it('Checks that withdraw and collect in different blocks updates _lifetimeRewardsClaimed as expected', async () => { + const users = await DRE.ethers.getSigners(); + const user = users[0]; + const depositAmount = utils.parseEther('1'); + + // Preparation + await waitForTx( + await enj.connect(user).approve(staticAToken.address, depositAmount, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(user) + .deposit(await user.getAddress(), depositAmount, 0, true, defaultTxParams) + ); + + await advanceTimeAndBlock(60); + + expect(await staticAToken.getLifetimeRewardsClaimed()).to.be.eq(0); + expect(await staticAToken.getClaimableRewards(await user.getAddress())).to.be.eq(0); + expect(await stkAave.balanceOf(await user.getAddress())).to.be.eq(0); + + await waitForTx( + await staticAToken.connect(user).withdraw(await user.getAddress(), MAX_UINT_AMOUNT, true) + ); + await staticAToken.collectAndUpdateRewards(); + await staticAToken.connect(user).claimRewardsToSelf(false); + + expect(await staticAToken.getLifetimeRewardsClaimed()).to.be.eq(0); + expect(await staticAToken.getClaimableRewards(await user.getAddress())).to.be.eq(0); + expect(await stkAave.balanceOf(await user.getAddress())).to.be.eq(0); + }); + + it('Checks that withdraw and collect in the same block updates _lifetimeRewardsClaimed as expected (Breaks if GasReport is enabled)', async () => { + const users = await DRE.ethers.getSigners(); + const user = users[0]; + const depositAmount = utils.parseEther('1'); + + // Preparation + await waitForTx( + await enj.connect(user).approve(staticAToken.address, depositAmount, defaultTxParams) + ); + + // Deposit + await waitForTx( + await staticAToken + .connect(user) + .deposit(await user.getAddress(), depositAmount, 0, true, defaultTxParams) + ); + + await advanceTimeAndBlock(60); + + expect(await staticAToken.getLifetimeRewardsClaimed()).to.be.eq(0); + expect(await staticAToken.getClaimableRewards(await user.getAddress())).to.be.eq(0); + expect(await stkAave.balanceOf(await user.getAddress())).to.be.eq(0); + + await DRE.network.provider.send('evm_setAutomine', [false]); + + await staticAToken.connect(user).withdraw(await user.getAddress(), MAX_UINT_AMOUNT, true); + await staticAToken.collectAndUpdateRewards(); + await staticAToken.connect(user).claimRewardsToSelf(false); + + await DRE.network.provider.send('evm_mine', []); + await DRE.network.provider.send('evm_setAutomine', [true]); + + expect(await staticAToken.getLifetimeRewardsClaimed()).to.be.eq(0); + expect(await staticAToken.getClaimableRewards(await user.getAddress())).to.be.eq(0); + expect(await stkAave.balanceOf(await user.getAddress())).to.be.eq(0); + }); +}); diff --git a/test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining.spec.ts b/test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining.spec.ts new file mode 100644 index 00000000..d40b0dbc --- /dev/null +++ b/test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller/static-atoken-liquidity-mining.spec.ts @@ -0,0 +1,1329 @@ +import rawDRE, { ethers } from 'hardhat'; +import bnjs from 'bignumber.js'; +import { solidity } from 'ethereum-waffle'; +import { + LendingPoolFactory, + WETH9Factory, + StaticATokenFactory, + ATokenFactory, + ERC20, + LendingPool, + StaticATokenLMFactory, + ERC20Factory, + WETH9, + AToken, + StaticAToken, + StaticATokenLM, +} from '../../../../types'; +import { IAaveIncentivesControllerFactory } from '../../../../types/IAaveIncentivesControllerFactory'; +import { + impersonateAccountsHardhat, + DRE, + waitForTx, + evmRevert, + evmSnapshot, + timeLatest, + advanceTimeAndBlock, +} from '../../../../helpers/misc-utils'; +import { BigNumber, providers, Signer, utils } from 'ethers'; +import { rayDiv, rayMul } from '../../../../helpers/ray-math'; +import { MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../../../../helpers/constants'; +import { tEthereumAddress } from '../../../../helpers/types'; + +import { parseEther, _TypedDataEncoder } from 'ethers/lib/utils'; +import { + buildMetaDepositParams, + buildMetaWithdrawParams, + buildPermitParams, + getSignatureFromTypedData, +} from '../../../../helpers/contracts-helpers'; +import { IAaveIncentivesController } from '../../../../types/IAaveIncentivesController'; +import { deploySelfdestructTransferMock } from '../../../../helpers/contracts-deployments'; +import { zeroAddress } from 'ethereumjs-util'; + +const { expect, use } = require('chai'); + +use(solidity); + +const DEFAULT_GAS_LIMIT = 10000000; +const DEFAULT_GAS_PRICE = utils.parseUnits('100', 'gwei'); + +const defaultTxParams = { gasLimit: DEFAULT_GAS_LIMIT, gasPrice: DEFAULT_GAS_PRICE }; + +const LENDING_POOL = '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9'; + +const STKAAVE = '0x4da27a545c0c5B758a6BA100e3a049001de870f5'; +const AENJ = '0xaC6Df26a590F08dcC95D5a4705ae8abbc88509Ef'; +const ENJ = '0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c'; +const ENJ_WHALE = '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8'; + +const INCENTIVES_CONTROLLER = '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5'; +const EMISSION_MANAGER = '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5'; + +const LM_ERRORS = { + INVALID_OWNER: '1', + INVALID_EXPIRATION: '2', + INVALID_SIGNATURE: '3', + INVALID_DEPOSITOR: '4', + INVALID_RECIPIENT: '5', + INVALID_CLAIMER: '6', + ONLY_ONE_AMOUNT_FORMAT_ALLOWED: '7', +}; + +type tBalancesInvolved = { + staticATokenATokenBalance: BigNumber; + staticATokenStkAaveBalance: BigNumber; + staticATokenUnderlyingBalance: BigNumber; + staticATokenScaledBalanceAToken: BigNumber; + staticATokenTotalClaimableRewards: BigNumber; + userStkAaveBalance: BigNumber; + userATokenBalance: BigNumber; + userScaledBalanceAToken: BigNumber; + userUnderlyingBalance: BigNumber; + userStaticATokenBalance: BigNumber; + userDynamicStaticATokenBalance: BigNumber; + userPendingRewards: BigNumber; + user2StkAaveBalance: BigNumber; + user2ATokenBalance: BigNumber; + user2ScaledBalanceAToken: BigNumber; + user2UnderlyingBalance: BigNumber; + user2StaticATokenBalance: BigNumber; + user2DynamicStaticATokenBalance: BigNumber; + user2PendingRewards: BigNumber; + currentRate: BigNumber; + staticATokenSupply: BigNumber; +}; + +type tContextParams = { + staticAToken: StaticATokenLM; + underlying: ERC20; + aToken: ERC20; + stkAave: ERC20; + user: tEthereumAddress; + user2: tEthereumAddress; + lendingPool: LendingPool; +}; + +const getContext = async ({ + staticAToken, + underlying, + aToken, + stkAave, + user, + user2, + lendingPool, +}: tContextParams): Promise => ({ + staticATokenATokenBalance: await aToken.balanceOf(staticAToken.address), + staticATokenStkAaveBalance: await stkAave.balanceOf(staticAToken.address), + staticATokenUnderlyingBalance: await underlying.balanceOf(staticAToken.address), + staticATokenScaledBalanceAToken: await aToken.scaledBalanceOf(staticAToken.address), + staticATokenTotalClaimableRewards: await staticAToken.getTotalClaimableRewards(), + userStaticATokenBalance: await staticAToken.balanceOf(user), + userStkAaveBalance: await stkAave.balanceOf(user), + userATokenBalance: await aToken.balanceOf(user), + userScaledBalanceAToken: await aToken.scaledBalanceOf(user), + userUnderlyingBalance: await underlying.balanceOf(user), + userDynamicStaticATokenBalance: await staticAToken.dynamicBalanceOf(user), + userPendingRewards: await staticAToken.getClaimableRewards(user), + user2StkAaveBalance: await stkAave.balanceOf(user2), + user2ATokenBalance: await aToken.balanceOf(user2), + user2ScaledBalanceAToken: await aToken.scaledBalanceOf(user2), + user2UnderlyingBalance: await underlying.balanceOf(user2), + user2StaticATokenBalance: await staticAToken.balanceOf(user2), + user2DynamicStaticATokenBalance: await staticAToken.dynamicBalanceOf(user2), + user2PendingRewards: await staticAToken.getClaimableRewards(user2), + currentRate: await lendingPool.getReserveNormalizedIncome(underlying.address), + staticATokenSupply: await staticAToken.totalSupply(), +}); + +describe('StaticATokenLM: aToken wrapper with static balances and NO liquidity mining', () => { + let userSigner: providers.JsonRpcSigner; + let user2Signer: providers.JsonRpcSigner; + let lendingPool: LendingPool; + let incentives: IAaveIncentivesController; + let enj: ERC20; + let aenj: AToken; + let stkAave: ERC20; + + let enjWhale: providers.JsonRpcSigner; + + let staticAToken: StaticATokenLM; + + let snap: string; + + let ctxtParams: tContextParams; + + before(async () => { + await rawDRE.run('set-DRE'); + + const [user1, user2] = await DRE.ethers.getSigners(); + userSigner = DRE.ethers.provider.getSigner(await user1.getAddress()); + user2Signer = DRE.ethers.provider.getSigner(await user2.getAddress()); + enjWhale = DRE.ethers.provider.getSigner(ENJ_WHALE); + lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner); + incentives = IAaveIncentivesControllerFactory.connect(INCENTIVES_CONTROLLER, userSigner); + + enj = ERC20Factory.connect(ENJ, userSigner); + aenj = ATokenFactory.connect(AENJ, userSigner); + stkAave = ERC20Factory.connect(STKAAVE, userSigner); + + staticAToken = await new StaticATokenLMFactory(userSigner).deploy(); + await staticAToken.initialize(LENDING_POOL, AENJ, 'Wrapped aENJ', 'waaenj'); + + expect(await staticAToken.getIncentivesController()).to.be.eq(zeroAddress()); + expect(await staticAToken.ASSET()).to.be.eq(await staticAToken.UNDERLYING_ASSET_ADDRESS()); + + ctxtParams = { + staticAToken: staticAToken, + underlying: (enj), + aToken: aenj, + stkAave: stkAave, + user: userSigner._address, + user2: user2Signer._address, + lendingPool, + }; + + await impersonateAccountsHardhat([ENJ_WHALE]); + const balanceWhale = await enj.balanceOf(ENJ_WHALE); + await waitForTx(await enj.connect(enjWhale).transfer(userSigner._address, balanceWhale)); + + snap = await evmSnapshot(); + }); + + beforeEach(async () => { + await evmRevert(snap); + snap = await evmSnapshot(); + }); + + after(async () => { + await evmRevert(snap); + }); + + it('Deposit ENJ on waaenj, then withdraw of the whole balance in underlying', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Just preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + await expect( + staticAToken.deposit(ZERO_ADDRESS, amountToDeposit, 0, true, defaultTxParams) + ).to.be.revertedWith(LM_ERRORS.INVALID_RECIPIENT); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + + await expect( + staticAToken.withdraw(ZERO_ADDRESS, amountToWithdraw, true, defaultTxParams) + ).to.be.revertedWith(LM_ERRORS.INVALID_RECIPIENT); + + // Withdrawing all + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claiming the rewards + await waitForTx( + await staticAToken.connect(userSigner).claimRewards(userSigner._address, false) + ); + + const ctxtAfterClaimNoForce = await getContext(ctxtParams); + + await waitForTx(await staticAToken.connect(userSigner).claimRewards(userSigner._address, true)); + + const ctxtAfterClaimForce = await getContext(ctxtParams); + + // Check that scaledAToken balance is equal to the static aToken supply at every stage. + expect(ctxtInitial.staticATokenScaledBalanceAToken).to.be.eq(ctxtInitial.staticATokenSupply); + expect(ctxtAfterDeposit.staticATokenScaledBalanceAToken).to.be.eq( + ctxtAfterDeposit.staticATokenSupply + ); + expect(ctxtAfterWithdrawal.staticATokenScaledBalanceAToken).to.be.eq( + ctxtAfterWithdrawal.staticATokenSupply + ); + expect(ctxtAfterClaimNoForce.staticATokenScaledBalanceAToken).to.be.eq( + ctxtAfterClaimNoForce.staticATokenSupply + ); + + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtInitial.staticATokenATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq( + ctxtInitial.userUnderlyingBalance.sub(amountToDeposit) + ); + expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq( + ctxtInitial.userDynamicStaticATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq( + ctxtAfterDeposit.staticATokenATokenBalance + ); + expect(ctxtAfterDeposit.staticATokenUnderlyingBalance).to.be.eq( + ctxtInitial.staticATokenUnderlyingBalance + ); + expect(ctxtAfterDeposit.userATokenBalance).to.be.eq(ctxtInitial.userATokenBalance); + expect(ctxtAfterDeposit.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterDeposit.staticATokenStkAaveBalance).to.be.eq(0); + + expect(ctxtAfterWithdrawal.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenUnderlyingBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenStkAaveBalance).to.be.eq(0); + + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.userStkAaveBalance).to.be.eq(0); + + expect(ctxtAfterClaimNoForce.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaimNoForce.staticATokenStkAaveBalance).to.be.eq(0); + + expect(ctxtAfterClaimForce.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaimNoForce.userPendingRewards).to.be.eq(0); + + expect(ctxtAfterClaimForce.staticATokenStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaimNoForce.staticATokenTotalClaimableRewards).to.be.eq(0); + }); + + it('Deposit ENJ on waaenj and then withdraw some balance in underlying', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = utils.parseEther('2.5'); + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + + const expectedATokenWithdraw = await staticAToken.staticToDynamicAmount(amountToWithdraw); + + // Withdraw + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claim + await waitForTx( + await staticAToken.connect(userSigner).claimRewards(userSigner._address, false) + ); + const ctxtAfterClaim = await getContext(ctxtParams); + + await waitForTx(await staticAToken.collectAndUpdateRewards()); + const ctxtAfterUpdate = await getContext(ctxtParams); + + await waitForTx( + await staticAToken.connect(userSigner).claimRewards(userSigner._address, false) + ); + const ctxtAfterClaim2 = await getContext(ctxtParams); + + expect(ctxtInitial.userStaticATokenBalance).to.be.eq(0); + expect(ctxtInitial.staticATokenSupply).to.be.eq(0); + expect(ctxtInitial.staticATokenUnderlyingBalance).to.be.eq(0); + expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq( + ctxtAfterDeposit.staticATokenATokenBalance + ); + expect(ctxtAfterDeposit.userStaticATokenBalance).to.be.eq(ctxtAfterDeposit.staticATokenSupply); + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtAfterDeposit.userDynamicStaticATokenBalance + ); + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq(amountToDeposit); + + expect(ctxtAfterWithdrawal.userDynamicStaticATokenBalance).to.be.eq( + BigNumber.from( + rayMul( + new bnjs(ctxtAfterDeposit.userStaticATokenBalance.sub(amountToWithdraw).toString()), + new bnjs(ctxtAfterWithdrawal.currentRate.toString()) + ).toString() + ) + ); + expect(ctxtAfterWithdrawal.userStaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance.sub(amountToWithdraw) + ); + + expect(ctxtAfterUpdate.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterUpdate.userPendingRewards).to.be.eq(0); + expect(ctxtAfterClaim2.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim2.userPendingRewards).to.be.eq(0); + + // Check that rewards are always covered + expect(ctxtInitial.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtInitial.userPendingRewards).to.be.eq(0); + expect(ctxtAfterDeposit.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterDeposit.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterClaim.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterClaim.userPendingRewards).to.be.eq(0); + expect(ctxtAfterUpdate.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterUpdate.userPendingRewards).to.be.eq(0); + + expect(ctxtAfterClaim2.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterClaim2.userPendingRewards).to.be.eq(0); + // TODO: Look back + }); + + it('Deposit ENJ on waaenj and then withdraw all the balance in aToken', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, false, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + expect(ctxtInitial.staticATokenSupply).to.be.eq(0); + expect(ctxtInitial.userATokenBalance).to.be.eq(0); + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtAfterDeposit.userDynamicStaticATokenBalance + ); + expect(ctxtAfterDeposit.userStaticATokenBalance).to.be.eq(ctxtAfterDeposit.staticATokenSupply); + expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq(amountToDeposit); + expect(ctxtAfterWithdrawal.userATokenBalance).to.be.eq( + rayMul( + ctxtAfterDeposit.userStaticATokenBalance.toString(), + ctxtAfterWithdrawal.currentRate.toString() + ).toString() + ); + expect(ctxtAfterWithdrawal.userStaticATokenBalance).to.be.eq(0); + }); + + it('Deposit aENJ on waaenj and then withdraw some balance in aToken', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = utils.parseEther('2.5'); + + // Preparation + await waitForTx(await enj.approve(lendingPool.address, amountToDeposit, defaultTxParams)); + await waitForTx( + await lendingPool.deposit( + enj.address, + amountToDeposit, + userSigner._address, + 0, + defaultTxParams + ) + ); + const ctxtInitial = await getContext(ctxtParams); + await waitForTx(await aenj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, false, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken.withdraw(userSigner._address, amountToWithdraw, false, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + expect(ctxtInitial.userStaticATokenBalance).to.be.eq(0); + expect(ctxtInitial.userATokenBalance).to.eq(amountToDeposit); + expect(ctxtInitial.staticATokenSupply).to.be.eq(0); + expect(ctxtInitial.staticATokenUnderlyingBalance).to.be.eq(0); + + expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq( + ctxtAfterDeposit.staticATokenATokenBalance + ); + expect(ctxtAfterDeposit.userStaticATokenBalance).to.be.eq(ctxtAfterDeposit.staticATokenSupply); + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq(amountToDeposit); + + expect(ctxtAfterWithdrawal.userStaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance.sub(amountToWithdraw) + ); + + expect(ctxtAfterWithdrawal.userDynamicStaticATokenBalance).to.be.eq( + BigNumber.from( + rayMul( + new bnjs(ctxtAfterDeposit.userStaticATokenBalance.sub(amountToWithdraw).toString()), + new bnjs(ctxtAfterWithdrawal.currentRate.toString()) + ).toString() + ) + ); + + expect(ctxtAfterWithdrawal.userATokenBalance).to.be.eq( + BigNumber.from( + rayMul( + new bnjs(ctxtAfterDeposit.userScaledBalanceAToken.add(amountToWithdraw).toString()), + new bnjs(ctxtAfterWithdrawal.currentRate.toString()) + ).toString() + ) + ); + }); + + it('Transfer with permit()', async () => { + const amountToDeposit = utils.parseEther('5'); + + // Just preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtBeforeTransfer = await getContext(ctxtParams); + + const ownerPrivateKey = require('../../../../test-wallets.js').accounts[0].secretKey; + if (!ownerPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + + const owner = userSigner; + const spender = user2Signer; + + const tokenName = await staticAToken.name(); + + const chainId = DRE.network.config.chainId || 1; + const expiration = MAX_UINT_AMOUNT; + const nonce = (await staticAToken._nonces(owner._address)).toNumber(); + const permitAmount = ethers.utils.parseEther('2').toString(); + const msgParams = buildPermitParams( + chainId, + staticAToken.address, + '1', + tokenName, + owner._address, + spender._address, + nonce, + expiration, + permitAmount + ); + + expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal( + '0', + 'INVALID_ALLOWANCE_BEFORE_PERMIT' + ); + + const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, msgParams); + + await expect( + staticAToken + .connect(spender) + .permit(spender._address, spender._address, permitAmount, expiration, v, r, s) + ).to.be.revertedWith(LM_ERRORS.INVALID_SIGNATURE); + + await waitForTx( + await staticAToken + .connect(spender) + .permit(owner._address, spender._address, permitAmount, expiration, v, r, s) + ); + + expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal( + permitAmount, + 'INVALID_ALLOWANCE_AFTER_PERMIT' + ); + + await waitForTx( + await staticAToken + .connect(spender) + .transferFrom(owner._address, spender._address, permitAmount) + ); + + const ctxtAfterTransfer = await getContext(ctxtParams); + + expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq( + ctxtBeforeTransfer.user2StaticATokenBalance.add(permitAmount) + ); + expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq( + ctxtBeforeTransfer.userStaticATokenBalance.sub(permitAmount) + ); + }); + + it('Transfer with permit() (expect fail)', async () => { + const amountToDeposit = utils.parseEther('5'); + + // Just preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ownerPrivateKey = require('../../../../test-wallets.js').accounts[0].secretKey; + if (!ownerPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + + const owner = userSigner; + const spender = user2Signer; + + const tokenName = await staticAToken.name(); + + const chainId = DRE.network.config.chainId || 1; + const expiration = 0; + const nonce = (await staticAToken._nonces(owner._address)).toNumber(); + const permitAmount = ethers.utils.parseEther('2').toString(); + const msgParams = buildPermitParams( + chainId, + staticAToken.address, + '1', + tokenName, + owner._address, + spender._address, + nonce, + expiration.toFixed(), + permitAmount + ); + + expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal( + '0', + 'INVALID_ALLOWANCE_BEFORE_PERMIT' + ); + + const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, msgParams); + + await expect( + staticAToken + .connect(spender) + .permit(ZERO_ADDRESS, spender._address, permitAmount, expiration, v, r, s) + ).to.be.revertedWith(LM_ERRORS.INVALID_OWNER); + + await expect( + staticAToken + .connect(spender) + .permit(owner._address, spender._address, permitAmount, expiration, v, r, s) + ).to.be.revertedWith(LM_ERRORS.INVALID_EXPIRATION); + + expect((await staticAToken.allowance(owner._address, spender._address)).toString()).to.be.equal( + '0', + 'INVALID_ALLOWANCE_AFTER_PERMIT' + ); + }); + + it('Deposit using metaDeposit()', async () => { + const amountToDeposit = utils.parseEther('5'); + const chainId = DRE.network.config.chainId ? DRE.network.config.chainId : 1; + + const domain = { + name: await staticAToken.name(), + version: '1', + chainId: chainId, + verifyingContract: staticAToken.address, + }; + const domainSeperator = _TypedDataEncoder.hashDomain(domain); + const seperator = await staticAToken.getDomainSeparator(); + expect(seperator).to.be.eq(domainSeperator); + + const userPrivateKey = require('../../../../test-wallets.js').accounts[0].secretKey; + if (!userPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const tokenName = await staticAToken.name(); + const nonce = (await staticAToken._nonces(userSigner._address)).toNumber(); + const value = amountToDeposit.toString(); + const referralCode = 0; + const depositor = userSigner._address; + const recipient = userSigner._address; + const fromUnderlying = true; + const deadline = MAX_UINT_AMOUNT; + + const msgParams = buildMetaDepositParams( + chainId, + staticAToken.address, + '1', + tokenName, + depositor, + recipient, + referralCode, + fromUnderlying, + nonce, + deadline, + value + ); + + const { v, r, s } = getSignatureFromTypedData(userPrivateKey, msgParams); + + const sigParams = { + v, + r, + s, + }; + + const ctxtInitial = await getContext(ctxtParams); + + await expect( + staticAToken + .connect(user2Signer) + .metaDeposit( + ZERO_ADDRESS, + recipient, + value, + referralCode, + fromUnderlying, + deadline, + sigParams + ) + ).to.be.revertedWith(LM_ERRORS.INVALID_DEPOSITOR); + + await expect( + staticAToken + .connect(user2Signer) + .metaDeposit(depositor, recipient, value, referralCode, fromUnderlying, 0, sigParams) + ).to.be.revertedWith(LM_ERRORS.INVALID_EXPIRATION); + + await expect( + staticAToken + .connect(user2Signer) + .metaDeposit( + user2Signer._address, + recipient, + value, + referralCode, + fromUnderlying, + deadline, + sigParams + ) + ).to.be.revertedWith(LM_ERRORS.INVALID_SIGNATURE); + + // Deposit + await waitForTx( + await staticAToken + .connect(user2Signer) + .metaDeposit(depositor, recipient, value, referralCode, fromUnderlying, deadline, sigParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + + expect(ctxtInitial.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterDeposit.userStaticATokenBalance).to.be.eq( + BigNumber.from(rayDiv(value.toString(), ctxtAfterDeposit.currentRate.toString()).toString()) + ); + expect(ctxtAfterDeposit.userDynamicStaticATokenBalance).to.be.eq(value); + }); + + it('Withdraw using withdrawDynamicAmount()', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = utils.parseEther('1'); + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtBeforeWithdrawal = await getContext(ctxtParams); + + // Withdraw dynamic amount + await waitForTx( + await staticAToken.withdrawDynamicAmount( + userSigner._address, + amountToWithdraw, + false, + defaultTxParams + ) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + expect(ctxtBeforeWithdrawal.userATokenBalance).to.be.eq(0); + expect(ctxtBeforeWithdrawal.staticATokenATokenBalance).to.be.closeTo(amountToDeposit, 2); + expect(ctxtAfterWithdrawal.userATokenBalance).to.be.closeTo(amountToWithdraw, 2); + expect(ctxtAfterWithdrawal.userDynamicStaticATokenBalance).to.be.closeTo( + BigNumber.from( + rayMul( + new bnjs(ctxtBeforeWithdrawal.userStaticATokenBalance.toString()), + new bnjs(ctxtAfterWithdrawal.currentRate.toString()) + ).toString() + ).sub(amountToWithdraw), + 2 + ); + + expect(ctxtAfterWithdrawal.userStkAaveBalance).to.be.eq(0); + }); + + it('Withdraw using metaWithdraw()', async () => { + const amountToDeposit = utils.parseEther('5'); + const chainId = DRE.network.config.chainId ? DRE.network.config.chainId : 1; + + const domain = { + name: await staticAToken.name(), + version: '1', + chainId: chainId, + verifyingContract: staticAToken.address, + }; + const domainSeperator = _TypedDataEncoder.hashDomain(domain); + const seperator = await staticAToken.getDomainSeparator(); + expect(seperator).to.be.eq(domainSeperator); + + const userPrivateKey = require('../../../../test-wallets.js').accounts[0].secretKey; + if (!userPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + // Meta withdraw + const tokenName = await staticAToken.name(); + const nonce = (await staticAToken._nonces(userSigner._address)).toNumber(); + const owner = userSigner._address; + const recipient = userSigner._address; + const staticAmount = (await staticAToken.balanceOf(userSigner._address)).toString(); + const dynamicAmount = '0'; + const toUnderlying = true; + const deadline = MAX_UINT_AMOUNT; // (await timeLatest()).plus(60 * 60).toFixed(); + + const msgParams = buildMetaWithdrawParams( + chainId, + staticAToken.address, + '1', + tokenName, + owner, + recipient, + staticAmount, + dynamicAmount, + toUnderlying, + nonce, + deadline + ); + + const { v, r, s } = getSignatureFromTypedData(userPrivateKey, msgParams); + + const sigParams = { + v, + r, + s, + }; + + const ctxtInitial = await getContext(ctxtParams); + + await expect( + staticAToken + .connect(user2Signer) + .metaWithdraw( + ZERO_ADDRESS, + recipient, + staticAmount, + dynamicAmount, + toUnderlying, + deadline, + sigParams + ) + ).to.be.revertedWith(LM_ERRORS.INVALID_OWNER); + + await expect( + staticAToken + .connect(user2Signer) + .metaWithdraw(owner, recipient, staticAmount, dynamicAmount, toUnderlying, 0, sigParams) + ).to.be.revertedWith(LM_ERRORS.INVALID_EXPIRATION); + + await expect( + staticAToken + .connect(user2Signer) + .metaWithdraw( + user2Signer._address, + recipient, + staticAmount, + dynamicAmount, + toUnderlying, + deadline, + sigParams + ) + ).to.be.revertedWith(LM_ERRORS.INVALID_SIGNATURE); + + // Deposit + await waitForTx( + await staticAToken + .connect(user2Signer) + .metaWithdraw( + owner, + recipient, + staticAmount, + dynamicAmount, + toUnderlying, + deadline, + sigParams + ) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + expect(ctxtInitial.userDynamicStaticATokenBalance).to.be.eq(amountToDeposit); + expect(ctxtAfterWithdrawal.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.userDynamicStaticATokenBalance).to.be.eq(0); + }); + + it('Withdraw using metaWithdraw() (expect to fail)', async () => { + const amountToDeposit = utils.parseEther('5'); + const chainId = DRE.network.config.chainId ? DRE.network.config.chainId : 1; + + const domain = { + name: await staticAToken.name(), + version: '1', + chainId: chainId, + verifyingContract: staticAToken.address, + }; + const domainSeperator = _TypedDataEncoder.hashDomain(domain); + const seperator = await staticAToken.getDomainSeparator(); + expect(seperator).to.be.eq(domainSeperator); + + const userPrivateKey = require('../../../../test-wallets.js').accounts[0].secretKey; + if (!userPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + // Meta withdraw + const tokenName = await staticAToken.name(); + const nonce = (await staticAToken._nonces(userSigner._address)).toNumber(); + const owner = userSigner._address; + const recipient = userSigner._address; + const staticAmount = (await staticAToken.balanceOf(userSigner._address)).toString(); + const dynamicAmount = ( + await await staticAToken.dynamicBalanceOf(userSigner._address) + ).toString(); + const toUnderlying = true; + const deadline = MAX_UINT_AMOUNT; + + const msgParams = buildMetaWithdrawParams( + chainId, + staticAToken.address, + '1', + tokenName, + owner, + recipient, + staticAmount, + dynamicAmount, + toUnderlying, + nonce, + deadline + ); + + const { v, r, s } = getSignatureFromTypedData(userPrivateKey, msgParams); + + const sigParams = { + v, + r, + s, + }; + + const ctxtInitial = await getContext(ctxtParams); + + await expect( + staticAToken + .connect(user2Signer) + .metaWithdraw( + owner, + recipient, + staticAmount, + dynamicAmount, + toUnderlying, + deadline, + sigParams + ) + ).to.be.revertedWith(LM_ERRORS.ONLY_ONE_AMOUNT_FORMAT_ALLOWED); + + const ctxtAfterDeposit = await getContext(ctxtParams); + expect(ctxtInitial.userStaticATokenBalance).to.be.eq(ctxtAfterDeposit.userStaticATokenBalance); + }); + + it('Deposit ENJ on waaenj, then transfer and withdraw of the whole balance in underlying, finally claim', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + // Transfer staticATokens to other user + await waitForTx( + await staticAToken.transfer(user2Signer._address, ctxtAfterDeposit.userStaticATokenBalance) + ); + + const ctxtAfterTransfer = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken + .connect(user2Signer) + .withdraw(user2Signer._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claim + await waitForTx( + await staticAToken.connect(user2Signer).claimRewards(user2Signer._address, true) + ); + const ctxtAfterClaim = await getContext(ctxtParams); + + // Checks + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtInitial.staticATokenATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq( + ctxtInitial.userUnderlyingBalance.sub(amountToDeposit) + ); + expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance + ); + expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterTransfer.userPendingRewards).to.be.eq(0); + expect(ctxtAfterTransfer.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.user2StkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq( + ctxtAfterWithdrawal.staticATokenTotalClaimableRewards + ); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq(0); + }); + + it('Deposit ENJ on waaenj, then transfer and withdraw of the whole balance in underlying, finally claimToSelf', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + // Transfer staticATokens to other user + await waitForTx( + await staticAToken.transfer(user2Signer._address, ctxtAfterDeposit.userStaticATokenBalance) + ); + + const ctxtAfterTransfer = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken + .connect(user2Signer) + .withdraw(user2Signer._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claim + await waitForTx(await staticAToken.connect(user2Signer).claimRewardsToSelf(true)); + const ctxtAfterClaim = await getContext(ctxtParams); + + // Checks + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtInitial.staticATokenATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq( + ctxtInitial.userUnderlyingBalance.sub(amountToDeposit) + ); + expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance + ); + expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterTransfer.userPendingRewards).to.be.eq(0); + expect(ctxtAfterTransfer.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + + expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.user2StkAaveBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq( + ctxtAfterWithdrawal.staticATokenTotalClaimableRewards + ); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq(0); + }); + + it('Deposit ENJ on waaenj, then transfer and withdraw of the whole balance in underlying, finally someone claims on behalf', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + const [, , claimer] = await DRE.ethers.getSigners(); + const claimerSigner = DRE.ethers.provider.getSigner(await claimer.getAddress()); + + await impersonateAccountsHardhat([EMISSION_MANAGER]); + const emissionManager = DRE.ethers.provider.getSigner(EMISSION_MANAGER); + + // Fund emissionManager + const selfdestructContract = await deploySelfdestructTransferMock(); + // Selfdestruct the mock, pointing to WETHGateway address + await selfdestructContract + .connect(user2Signer) + .destroyAndTransfer(emissionManager._address, { value: parseEther('1') }); + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Allow another use to claim on behalf of + await waitForTx( + await incentives + .connect(emissionManager) + .setClaimer(user2Signer._address, claimerSigner._address) + ); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + // Transfer staticATokens to other user + await waitForTx( + await staticAToken.transfer(user2Signer._address, ctxtAfterDeposit.userStaticATokenBalance) + ); + + const ctxtAfterTransfer = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken + .connect(user2Signer) + .withdraw(user2Signer._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claim + await waitForTx( + await staticAToken + .connect(claimerSigner) + .claimRewardsOnBehalf(user2Signer._address, user2Signer._address, true) + ); + + const ctxtAfterClaim = await getContext(ctxtParams); + + // Checks + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtInitial.staticATokenATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq( + ctxtInitial.userUnderlyingBalance.sub(amountToDeposit) + ); + expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance + ); + expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterTransfer.userPendingRewards).to.be.eq(0); + expect(ctxtAfterTransfer.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + + expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.user2StkAaveBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq( + ctxtAfterWithdrawal.staticATokenTotalClaimableRewards + ); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq(0); + }); + + it('Deposit ENJ on waaenj, then transfer and withdraw of the whole balance in underlying, finally someone NOT set as claimer claims on behalf', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + const [, , claimer] = await DRE.ethers.getSigners(); + const claimerSigner = DRE.ethers.provider.getSigner(await claimer.getAddress()); + + await impersonateAccountsHardhat([EMISSION_MANAGER]); + const emissionManager = DRE.ethers.provider.getSigner(EMISSION_MANAGER); + + // Fund emissionManager + const selfdestructContract = await deploySelfdestructTransferMock(); + // Selfdestruct the mock, pointing to WETHGateway address + await selfdestructContract + .connect(user2Signer) + .destroyAndTransfer(emissionManager._address, { value: parseEther('1') }); + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + // Transfer staticATokens to other user + await waitForTx( + await staticAToken.transfer(user2Signer._address, ctxtAfterDeposit.userStaticATokenBalance) + ); + + const ctxtAfterTransfer = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken + .connect(user2Signer) + .withdraw(user2Signer._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claim + await waitForTx( + await staticAToken + .connect(claimerSigner) + .claimRewardsOnBehalf(user2Signer._address, user2Signer._address, true) + ); + + const ctxtAfterClaim = await getContext(ctxtParams); + + // Checks + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtInitial.staticATokenATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq( + ctxtInitial.userUnderlyingBalance.sub(amountToDeposit) + ); + expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance + ); + expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterTransfer.userPendingRewards).to.be.eq(0); + expect(ctxtAfterTransfer.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + + expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.user2StkAaveBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq( + ctxtAfterWithdrawal.staticATokenTotalClaimableRewards + ); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq(0); + }); + + it('Deposit ENJ on waaenj, then transfer and withdraw of the whole balance in underlying, finally claims on behalf of self', async () => { + const amountToDeposit = utils.parseEther('5'); + const amountToWithdraw = MAX_UINT_AMOUNT; + + // Preparation + await waitForTx(await enj.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + const ctxtInitial = await getContext(ctxtParams); + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ); + + const ctxtAfterDeposit = await getContext(ctxtParams); + // Transfer staticATokens to other user + await waitForTx( + await staticAToken.transfer(user2Signer._address, ctxtAfterDeposit.userStaticATokenBalance) + ); + + const ctxtAfterTransfer = await getContext(ctxtParams); + + // Withdraw + await waitForTx( + await staticAToken + .connect(user2Signer) + .withdraw(user2Signer._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + // Claim + await waitForTx( + await staticAToken + .connect(user2Signer) + .claimRewardsOnBehalf(user2Signer._address, user2Signer._address, true) + ); + const ctxtAfterClaim = await getContext(ctxtParams); + + // Checks + expect(ctxtAfterDeposit.staticATokenATokenBalance).to.be.eq( + ctxtInitial.staticATokenATokenBalance.add(amountToDeposit) + ); + expect(ctxtAfterDeposit.userUnderlyingBalance).to.be.eq( + ctxtInitial.userUnderlyingBalance.sub(amountToDeposit) + ); + expect(ctxtAfterTransfer.user2StaticATokenBalance).to.be.eq( + ctxtAfterDeposit.userStaticATokenBalance + ); + expect(ctxtAfterTransfer.userStaticATokenBalance).to.be.eq(0); + expect(ctxtAfterTransfer.userPendingRewards).to.be.eq(0); + expect(ctxtAfterTransfer.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenSupply).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenATokenBalance).to.be.eq(0); + expect(ctxtAfterWithdrawal.userPendingRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.staticATokenTotalClaimableRewards).to.be.eq(0); + expect(ctxtAfterWithdrawal.user2PendingRewards).to.be.eq(0); + expect(ctxtAfterClaim.userStkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.user2StkAaveBalance).to.be.eq(0); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq( + ctxtAfterWithdrawal.staticATokenTotalClaimableRewards + ); + expect(ctxtAfterClaim.staticATokenStkAaveBalance).to.be.eq(0); + }); +}); diff --git a/test-suites/test-aave/mainnet/static-atoken-lm/static-atoken-liquidity-mining.spec.ts b/test-suites/test-aave/mainnet/static-atoken-lm/static-atoken-liquidity-mining.spec.ts index 3d297b0c..d5b14dd1 100644 --- a/test-suites/test-aave/mainnet/static-atoken-lm/static-atoken-liquidity-mining.spec.ts +++ b/test-suites/test-aave/mainnet/static-atoken-lm/static-atoken-liquidity-mining.spec.ts @@ -170,6 +170,8 @@ describe('StaticATokenLM: aToken wrapper with static balances and liquidity mini 'stataAAVE' ); + expect(await staticAToken.getIncentivesController()).to.be.eq(INCENTIVES_CONTROLLER); + ctxtParams = { staticAToken: staticAToken, underlying: (weth), diff --git a/tsconfig.json b/tsconfig.json index 44c4df6f..b6d43282 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,17 @@ "noImplicitAny": false, "resolveJsonModule": true }, - "include": ["./scripts", "./test", "./tasks", "./helpers", "test-suites/test-aave/uniswapAdapters.repay.spec.ts", "test-suites/test-aave/upgradeability.spec.ts", "test-suites/test-aave/variable-debt-token.spec.ts", "test-suites/test-aave/weth-gateway.spec.ts"], + "include": [ + "./scripts", + "./test", + "./tasks", + "./helpers", + "test-suites/test-aave/uniswapAdapters.repay.spec.ts", + "test-suites/test-aave/upgradeability.spec.ts", + "test-suites/test-aave/variable-debt-token.spec.ts", + "test-suites/test-aave/weth-gateway.spec.ts", + "test-suites/test-aave/mainnet/static-atoken-lm-no-incentives-controller" + ], "files": [ "./hardhat.config.ts", "./modules/tenderly/tenderly.d.ts",