import rawDRE, { ethers } from 'hardhat'; import bnjs from 'bignumber.js'; import { solidity } from 'ethereum-waffle'; 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 } from '../../../helpers/constants'; import { tEthereumAddress } from '../../../helpers/types'; import { AbiCoder, formatEther, verifyTypedData } from 'ethers/lib/utils'; import { _TypedDataEncoder } from 'ethers/lib/utils'; import { expect, use } from 'chai'; import { stat } from 'fs'; import { getCurrentBlock } from '../../../helpers/contracts-helpers'; //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 WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const STKAAVE = '0x4da27a545c0c5B758a6BA100e3a049001de870f5'; const AWETH = '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e'; 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 DUST = 100; describe('StaticATokenLM: aToken wrapper with static balances and liquidity mining', () => { let userSigner: providers.JsonRpcSigner; let user2Signer: providers.JsonRpcSigner; let lendingPool: LendingPool; let weth: WETH9; let aweth: AToken; let stkAave: ERC20; let staticAToken: StaticATokenLM; let snap: string; before(async () => { await rawDRE.run('set-DRE'); console.log(`Initial block number: ${await getCurrentBlock()}`); const [user1, user2] = await DRE.ethers.getSigners(); userSigner = DRE.ethers.provider.getSigner(await user1.getAddress()); user2Signer = DRE.ethers.provider.getSigner(await user2.getAddress()); lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner); weth = WETH9Factory.connect(WETH, userSigner); aweth = ATokenFactory.connect(AWETH, userSigner); stkAave = ERC20Factory.connect(STKAAVE, userSigner); staticAToken = await new StaticATokenLMFactory(userSigner).deploy( LENDING_POOL, AWETH, 'Static Aave Interest Bearing WETH', 'stataAAVE' ); snap = await evmSnapshot(); }); beforeEach(async () => { await evmRevert(snap); snap = await evmSnapshot(); }); after(async () => { await evmRevert(snap); }); describe('Small checks', async () => { it('Rewards increase at deposit, update and withdraw and set to 0 at claim', async () => { const amountToDeposit = utils.parseEther('5'); const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes: // Just preparation await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) })); await waitForTx( await weth.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.updateRewards()); 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 claimedRewards4 = await stkAave.balanceOf(userSigner._address); await waitForTx(await staticAToken.claimRewards(userSigner._address)); const pendingRewards5 = await staticAToken.getClaimableRewards(userSigner._address); expect(pendingRewards2).to.be.gt(pendingRewards1); expect(pendingRewards3).to.be.gt(pendingRewards2); expect(pendingRewards4).to.be.gt(pendingRewards3); expect(pendingRewards5).to.be.eq(0); }); it('Check getters', async () => { const amountToDeposit = utils.parseEther('5'); const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes: // Just preparation await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) })); await waitForTx( await weth.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); }); it('Update and claim', async () => { const amountToDeposit = utils.parseEther('5'); const amountToWithdraw = MAX_UINT_AMOUNT; // Still need to figure out why this works :eyes: // Just preparation await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) })); await waitForTx( await weth.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.updateRewards()); const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address); const claimedRewards3 = await stkAave.balanceOf(userSigner._address); await waitForTx(await staticAToken.updateAndClaimRewards(userSigner._address)); const pendingRewards4 = await staticAToken.getClaimableRewards(userSigner._address); const claimedRewards4 = await stkAave.balanceOf(userSigner._address); expect(pendingRewards1).to.be.eq(0); expect(pendingRewards2).to.be.gt(pendingRewards1); expect(pendingRewards3).to.be.gt(pendingRewards2); expect(pendingRewards4).to.be.eq(0); expect(claimedRewards3).to.be.eq(0); expect(claimedRewards4).to.be.gt(pendingRewards3); }); 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 weth.deposit({ value: amountToDeposit.mul(2) })); await waitForTx( await weth.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); // Check that the recipient have gotten the rewards expect(userPendingRewards2).to.be.gt(userPendingRewards1); expect(recipientPendingRewards1).to.be.eq(0); expect(recipientPendingRewards2).to.be.eq(0); }); // Those that checks that subs could not be wrong or something other }); it('Multiple users deposit WETH on stataWETH, wait 1 hour, update rewards, 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. // Only diff here is if we wait, transfer, wait // 1. Deposit // 2. Wait 3600 seconds // 2-5. Update rewards // 3. Transfer // 4. Wait 3600 seconds // 5. Claim rewards // 6. Update rewards // When doing so, it should be clear that the recipient also gets the 'uncollected' rewards to the protocol that the value has accrued since last update. // The thought is that since it is expensive to retrieve these rewards, a small holder may rather want to give away the extra rewards (if rewards < gas). 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 weth.connect(currentUser).deposit({ value: amountToDeposit })); await waitForTx( await weth .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.updateRewards(); let staticATokenStkAaveBalInitial = await stkAave.balanceOf(staticAToken.address); 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++) { await waitForTx(await staticAToken.claimRewards(await users[i].getAddress())); } let staticATokenStkAaveBalAfterTransferAndClaim = await stkAave.balanceOf(staticAToken.address); let usersDataAfterTransferAndClaim = await getUserData(users, _debugUserData, { staticAToken, stkAave, }); await waitForTx(await staticAToken.updateRewards()); let staticATokenStkAaveBalFinal = await stkAave.balanceOf(staticAToken.address); 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); // Everyone else than i == 1, should have no change in pending rewards. // i == 1, will get additional rewards that have accrue expect(usersDataAfterTransferAndClaim[i].stkAaveBalance).to.be.eq( usersDataInitial[i].pendingRewards ); 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(usersDataFinal[0].pendingRewards).to.be.eq(0); expect(usersDataAfterTransferAndClaim[0].staticBalance).to.be.eq(0); expect(usersDataFinal[0].staticBalance).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 user 1 to have accrued more than twice in pending rewards. * note that we get very little rewards in the transfer, because of the fresh update. */ // Expect the pending of user to be a lot expect(usersDataFinal[1].pendingRewards).to.be.gt(usersDataFinal[2].pendingRewards.mul(2)); // Expect his total fees to be almost 1.5 as large. Because of the small initial diff expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.gt( usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(145).div(100) ); expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.lt( usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(155).div(100) ); // Expect there to be excess stkAave in the contract. Expect it to be dust. This ensure that everyone can claim full amount of rewards. expect(pendingRewardsSumInitial).to.be.lte(staticATokenStkAaveBalInitial); expect(staticATokenStkAaveBalInitial.sub(pendingRewardsSumInitial)).to.be.lte(DUST); expect(pendingRewardsSumAfter).to.be.lte(staticATokenStkAaveBalAfterTransferAndClaim); expect(staticATokenStkAaveBalAfterTransferAndClaim.sub(pendingRewardsSumAfter)).to.be.lte(DUST); expect(pendingRewardsSumFinal).to.be.lte(staticATokenStkAaveBalFinal); expect(staticATokenStkAaveBalFinal.sub(pendingRewardsSumFinal)).to.be.lte(DUST); expect(usersDataInitial[0].pendingRewards).to.be.eq( usersDataAfterTransferAndClaim[0].stkAaveBalance ); expect(usersDataAfterTransferAndClaim[0].pendingRewards).to.be.eq(0); expect(usersDataAfterTransferAndClaim[1].staticBalance).to.be.eq( usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance) ); }); it('Multiple users deposit WETH on stataWETH, 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. // Only diff here is if we wait, transfer, wait // 1. Deposit // 2. Wait 3600 seconds // 3. Transfer // 4. Wait 3600 seconds // 5. Claim rewards // 6. Update rewards // When doing so, it should be clear that the recipient also gets the 'uncollected' rewards to the protocol that the value has accrued since last update. // The thought is that since it is expensive to retrieve these rewards, a small holder may rather want to give away the extra rewards (if rewards < gas). const amountToDeposit = utils.parseEther('5'); //'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 weth.connect(currentUser).deposit({ value: amountToDeposit })); await waitForTx( await weth .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 staticATokenStkAaveBalInitial = await stkAave.balanceOf(staticAToken.address); 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++) { await waitForTx(await staticAToken.claimRewards(await users[i].getAddress())); } let staticATokenStkAaveBalAfterTransfer = await stkAave.balanceOf(staticAToken.address); let usersDataAfterTransfer = await getUserData(users, _debugUserData, { staticAToken, stkAave, }); await waitForTx(await staticAToken.updateRewards()); let staticATokenStkAaveBalFinal = await stkAave.balanceOf(staticAToken.address); 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); // Everyone else than i == 1, should have no change in pending rewards. // i == 1, will get additional rewards that have accrue expect(usersDataAfterTransfer[i].stkAaveBalance).to.be.eq(usersDataInitial[i].pendingRewards); 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 user 0 to accrue zero fees after the transfer expect(usersDataFinal[0].pendingRewards).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 user 1 to have pending more than twice the rewards as the last user. * Note that he should have accrued this, even though he did not have 2x bal for the full time, * as he also received the "uncollected" rewards from user1 at the transfer. */ expect(usersDataFinal[1].pendingRewards).to.be.gt(usersDataFinal[2].pendingRewards.mul(2)); // Expect his total fees to be almost twice as large. Because of the small initial diff expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.gt( usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(195).div(100) ); expect(usersDataFinal[1].pendingRewards.add(usersDataFinal[1].stkAaveBalance)).to.be.lt( usersDataFinal[2].pendingRewards.add(usersDataFinal[2].stkAaveBalance).mul(205).div(100) ); // Expect there to be excess stkAave in the contract. // Expect it to be dust. This ensure that everyone can claim full amount of rewards. expect(pendingRewardsSumInitial).to.be.lte(staticATokenStkAaveBalInitial); expect(staticATokenStkAaveBalInitial.sub(pendingRewardsSumInitial)).to.be.lte(DUST); expect(pendingRewardsSumAfter).to.be.lte(staticATokenStkAaveBalAfterTransfer); expect(staticATokenStkAaveBalAfterTransfer.sub(pendingRewardsSumAfter)).to.be.lte(DUST); // We got an error here, pendingRewardsSumFinal = actual + 1 expect(pendingRewardsSumFinal).to.be.lte(staticATokenStkAaveBalFinal); expect(staticATokenStkAaveBalFinal.sub(pendingRewardsSumFinal)).to.be.lte(DUST); // How small should we say dust is? // Expect zero rewards after all is claimed. But there is some dust left. expect(usersDataInitial[0].pendingRewards).to.be.eq(usersDataAfterTransfer[0].stkAaveBalance); expect(usersDataAfterTransfer[0].pendingRewards).to.be.eq(0); expect(usersDataAfterTransfer[1].staticBalance).to.be.eq( usersDataInitial[1].staticBalance.add(usersDataInitial[0].staticBalance) ); }); it('Mass deposit, then mass claim', async () => { const amountToDeposit = utils.parseEther('1.1'); // 18 decimals should be the worst here //1.135359735917531199 const users = await DRE.ethers.getSigners(); const depositCount = 50; for (let i = 0; i < depositCount; i++) { if (i % 50 == 0 && i > 0) { console.log('50 deposits'); } let currentUser = users[i % users.length]; // Preparation await waitForTx(await weth.connect(currentUser).deposit({ value: amountToDeposit })); await waitForTx( await weth .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.updateRewards()); for (let i = 0; i < users.length; i++) { const pendingReward = await staticAToken.getClaimableRewards(await users[i].getAddress()); await waitForTx(await staticAToken.claimRewards(await users[i].getAddress())); // We have a mistake here. Rounding of the pendingReward seems to be the issue // console.log(`What user: ${i}, with pending reward: ${pendingReward}`); expect(await stkAave.balanceOf(await users[i].getAddress())).to.be.eq(pendingReward); } expect(await stkAave.balanceOf(staticAToken.address)).to.be.lt(DUST); }); });