import rawDRE from 'hardhat'; import BigNumber from 'bignumber.js'; import chai, { expect } from 'chai'; import bignumberChai from 'chai-bignumber'; import { solidity } from 'ethereum-waffle'; import { LendingPoolFactory, WETH9Factory, StaticATokenFactory, ATokenFactory, ERC20, LendingPool, StaticATokenMetaTransactionMock, StaticATokenMetaTransactionMockFactory, WETH9, AToken, StaticAToken, } from '../../types'; import { buildPermitParams, buildMetaDepositParams, buildMetaWithdrawParams, getSignatureFromTypedData, } from '../../helpers/contracts-helpers'; import { impersonateAccountsHardhat, DRE, waitForTx } from '../../helpers/misc-utils'; import { utils } from 'ethers'; import { rayDiv, rayMul } from '../../helpers/ray-math'; import { MAX_UINT_AMOUNT } from '../../helpers/constants'; import { tEthereumAddress } from '../../helpers/types'; import { JsonRpcSigner } from '@ethersproject/providers'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; import { parseEther } from '@ethersproject/units'; import { parse } from 'path'; chai.use(bignumberChai()); 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 ETHER_BANK = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const LENDING_POOL = '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9'; const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const AWETH = '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e'; const TEST_USERS = ['0x0F4ee9631f4be0a63756515141281A3E2B293Bbe']; const AWETH_HOLDER = '0x928477dabc0eD2a6CE6c33966a52eA58CbDEA212'; type tBalancesInvolved = { aTokenBalanceStaticAToken: BigNumber; aTokenBalanceUser: BigNumber; underlyingBalanceUser: BigNumber; underlyingBalanceStaticAToken: BigNumber; userStaticATokenBalance: BigNumber; userDynamicStaticATokenBalance: BigNumber; currentRate: BigNumber; staticATokenSupply: BigNumber; }; type tContextParams = { staticAToken: ERC20; underlying: ERC20; aToken: ERC20; user: tEthereumAddress; lendingPool: LendingPool; }; const getContext = async ({ staticAToken, underlying, aToken, user, lendingPool, }: tContextParams): Promise => ({ aTokenBalanceStaticAToken: new BigNumber( (await aToken.balanceOf(staticAToken.address)).toString() ), aTokenBalanceUser: new BigNumber((await aToken.balanceOf(user)).toString()), underlyingBalanceUser: new BigNumber((await underlying.balanceOf(user)).toString()), underlyingBalanceStaticAToken: new BigNumber( (await underlying.balanceOf(staticAToken.address)).toString() ), userStaticATokenBalance: new BigNumber((await staticAToken.balanceOf(user)).toString()), userDynamicStaticATokenBalance: new BigNumber( rayMul( new BigNumber((await staticAToken.balanceOf(user)).toString()), new BigNumber((await lendingPool.getReserveNormalizedIncome(WETH)).toString()) ) ), currentRate: new BigNumber((await lendingPool.getReserveNormalizedIncome(WETH)).toString()), staticATokenSupply: new BigNumber((await staticAToken.totalSupply()).toString()), }); const getInterestAccrued = (ctxBefore: tBalancesInvolved, ctxAfter: tBalancesInvolved) => rayMul( rayDiv(ctxBefore.aTokenBalanceStaticAToken.toString(), ctxBefore.currentRate.toString()), ctxAfter.currentRate.toString() ).minus(ctxBefore.aTokenBalanceStaticAToken.toString()); const getUserInterestAccrued = (ctxBefore: tBalancesInvolved, ctxAfter: tBalancesInvolved) => rayMul( rayDiv(ctxBefore.aTokenBalanceUser.toString(), ctxBefore.currentRate.toString()), ctxAfter.currentRate.toString() ).minus(ctxBefore.aTokenBalanceUser.toString()); describe('StaticAToken: aToken wrapper with static balances', () => { let weth: WETH9; let lendingPool: LendingPool; let aweth: AToken; let user1Signer: JsonRpcSigner; let controlledPkSigner: SignerWithAddress; let staticAWeth: StaticAToken; before(async () => { await rawDRE.run('set-DRE'); // Impersonations await impersonateAccountsHardhat([ETHER_BANK, ...TEST_USERS, AWETH_HOLDER]); const ethHolderSigner = rawDRE.ethers.provider.getSigner(ETHER_BANK); const awethHolderSigner = rawDRE.ethers.provider.getSigner(AWETH_HOLDER); user1Signer = DRE.ethers.provider.getSigner(TEST_USERS[0]); controlledPkSigner = (await rawDRE.ethers.getSigners())[0]; lendingPool = LendingPoolFactory.connect(LENDING_POOL, user1Signer); weth = WETH9Factory.connect(WETH, user1Signer); aweth = ATokenFactory.connect(AWETH, user1Signer); await aweth.connect(awethHolderSigner).transfer(controlledPkSigner.address, parseEther('5.0')); for (const recipientOfEth of [...TEST_USERS]) { await ethHolderSigner.sendTransaction({ from: ethHolderSigner._address, to: recipientOfEth, value: utils.parseEther('100'), ...defaultTxParams, }); } await waitForTx(await weth.deposit({ value: parseEther('5') })); staticAWeth = await new StaticATokenFactory(user1Signer).deploy( LENDING_POOL, AWETH, 'Static Aave Interest Bearing WETH', 'stataAAVE' ); }); it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => { const amountToDeposit = utils.parseEther('5'); const ctxtParams: tContextParams = { staticAToken: staticAWeth, underlying: (weth), aToken: aweth, user: user1Signer._address, lendingPool, }; const ctxtBeforeDeposit = await getContext(ctxtParams); await waitForTx(await weth.approve(staticAWeth.address, amountToDeposit, defaultTxParams)); await waitForTx( await staticAWeth.deposit(user1Signer._address, amountToDeposit, 0, true, defaultTxParams) ); const ctxtAfterDeposit = await getContext(ctxtParams); expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceStaticAToken .plus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceUser .minus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal( ctxtBeforeDeposit.userDynamicStaticATokenBalance .plus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString() ); expect(ctxtBeforeDeposit.aTokenBalanceUser.toString()).to.be.equal( ctxtAfterDeposit.aTokenBalanceUser.toString() ); const ctxtBeforeWithdrawal = await getContext(ctxtParams); const amountToWithdraw = MAX_UINT_AMOUNT; await waitForTx( await staticAWeth.withdraw(user1Signer._address, amountToWithdraw, true, defaultTxParams) ); const ctxtAfterWithdrawal = await getContext(ctxtParams); expect( ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(), 'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW' ).to.be.equal( rayMul( ctxtBeforeWithdrawal.staticATokenSupply.minus(ctxtBeforeWithdrawal.userStaticATokenBalance), ctxtAfterWithdrawal.currentRate ).toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceUser.toString(), 'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.underlyingBalanceUser .plus(rayMul(ctxtBeforeWithdrawal.userStaticATokenBalance, ctxtAfterWithdrawal.currentRate)) .toString() ); expect( ctxtAfterWithdrawal.userStaticATokenBalance.toString(), 'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal('0'); expect( ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(), 'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString()); expect( ctxtAfterWithdrawal.aTokenBalanceUser.toString(), 'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.aTokenBalanceUser.toString()); }); it('Deposit WETH on stataWETH and then withdraw to some balance in underlying', async () => { const amountToDeposit = utils.parseEther('5'); const ctxtParams: tContextParams = { staticAToken: staticAWeth, underlying: (weth), aToken: aweth, user: user1Signer._address, lendingPool, }; const ctxtBeforeDeposit = await getContext(ctxtParams); await waitForTx(await weth.approve(staticAWeth.address, amountToDeposit, defaultTxParams)); await waitForTx( await staticAWeth.deposit(user1Signer._address, amountToDeposit, 0, true, defaultTxParams) ); const ctxtAfterDeposit = await getContext(ctxtParams); expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceStaticAToken .plus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceUser .minus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal( ctxtBeforeDeposit.userDynamicStaticATokenBalance .plus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString() ); expect(ctxtBeforeDeposit.aTokenBalanceUser.toString()).to.be.equal( ctxtAfterDeposit.aTokenBalanceUser.toString() ); const ctxtBeforeWithdrawal = await getContext(ctxtParams); const amountToWithdraw = parseEther('2.0'); await waitForTx( await staticAWeth.withdraw(user1Signer._address, amountToWithdraw, true, defaultTxParams) ); const ctxtAfterWithdrawal = await getContext(ctxtParams); expect( ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(), 'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW' ).to.be.equal( rayMul( ctxtBeforeWithdrawal.staticATokenSupply.minus(amountToWithdraw.toString()), ctxtAfterWithdrawal.currentRate ).toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceUser.toString(), 'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.underlyingBalanceUser .plus(rayMul(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate)) .toString() ); expect( ctxtAfterWithdrawal.userStaticATokenBalance.toString(), 'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.userStaticATokenBalance.minus(amountToWithdraw.toString()).toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(), 'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString()); expect( ctxtAfterWithdrawal.aTokenBalanceUser.toString(), 'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.aTokenBalanceUser.toString()); }); it('Deposit WETH on stataWETH and then withdrawDynamic some balance in aToken', async () => { const amountToDeposit = utils.parseEther('4'); const ctxtParams: tContextParams = { staticAToken: staticAWeth, underlying: (weth), aToken: aweth, user: user1Signer._address, lendingPool, }; const ctxtBeforeDeposit = await getContext(ctxtParams); await waitForTx(await weth.approve(staticAWeth.address, amountToDeposit, defaultTxParams)); await waitForTx( await staticAWeth.deposit(user1Signer._address, amountToDeposit, 0, true, defaultTxParams) ); const ctxtAfterDeposit = await getContext(ctxtParams); const interestAccrued = getInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit); expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceStaticAToken .plus(new BigNumber(amountToDeposit.toString())) .plus(interestAccrued) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceUser .minus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal( ctxtBeforeDeposit.userDynamicStaticATokenBalance .plus(new BigNumber(amountToDeposit.toString())) .plus(interestAccrued) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString() ); expect(ctxtBeforeDeposit.aTokenBalanceUser.toString()).to.be.equal( ctxtAfterDeposit.aTokenBalanceUser.toString() ); const ctxtBeforeWithdrawal = await getContext(ctxtParams); const amountToWithdraw = parseEther('2.0'); await waitForTx( await staticAWeth.withdrawInDynamicAmount( user1Signer._address, amountToWithdraw, false, defaultTxParams ) ); const ctxtAfterWithdrawal = await getContext(ctxtParams); const interestAccruedWithdrawal = getInterestAccrued(ctxtBeforeWithdrawal, ctxtAfterWithdrawal); expect( ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(), 'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW' ).to.be.equal( ctxtBeforeWithdrawal.aTokenBalanceStaticAToken .minus(amountToWithdraw.toString()) .plus(interestAccruedWithdrawal) .plus('1') // rounding issue .toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceUser.toString(), 'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceUser.toString()); expect( ctxtAfterWithdrawal.userStaticATokenBalance.toString(), 'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.userStaticATokenBalance .minus(rayDiv(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate)) .toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(), 'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString()); expect( ctxtAfterWithdrawal.aTokenBalanceUser.toString(), 'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.aTokenBalanceUser.plus(amountToWithdraw.toString()).toString() ); }); // tslint:disable-next-line:max-line-length it('User 2 Deposits aWETH for user 1 and then withdraw some balance to second user in aToken', async () => { const amountToDeposit = utils.parseEther('4'); const ctxtParams: tContextParams = { staticAToken: staticAWeth, underlying: (weth), aToken: aweth, user: user1Signer._address, lendingPool, }; const ctxtBeforeDeposit = await getContext(ctxtParams); await waitForTx( await aweth.connect(controlledPkSigner).approve(staticAWeth.address, MAX_UINT_AMOUNT) ); await waitForTx( await staticAWeth .connect(controlledPkSigner) .deposit(user1Signer._address, amountToDeposit, 0, false, defaultTxParams) ); const ctxtAfterDeposit = await getContext(ctxtParams); const interestAccrued = getInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit); const userInterestAccrued = getUserInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit); expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceStaticAToken .plus(new BigNumber(amountToDeposit.toString())) .plus(interestAccrued) .plus(1) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceUser.toString() ); expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal( ctxtBeforeDeposit.userDynamicStaticATokenBalance .plus(new BigNumber(amountToDeposit.toString())) .plus(interestAccrued) .plus(1) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString() ); expect(ctxtAfterDeposit.aTokenBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceUser.plus(userInterestAccrued).toString() ); const ctxtBeforeWithdrawal = await getContext(ctxtParams); const amountToWithdraw = parseEther('2.0'); await waitForTx( await staticAWeth.withdrawInDynamicAmount( user1Signer._address, amountToWithdraw, false, defaultTxParams ) ); const ctxtAfterWithdrawal = await getContext(ctxtParams); const interestAccruedWithdrawal = getInterestAccrued(ctxtBeforeWithdrawal, ctxtAfterWithdrawal); const userInterestAccruedWithdrawal = getUserInterestAccrued( ctxtBeforeWithdrawal, ctxtAfterWithdrawal ); expect( ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(), 'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW' ).to.be.equal( ctxtBeforeWithdrawal.aTokenBalanceStaticAToken .minus(amountToWithdraw.toString()) .plus(interestAccruedWithdrawal) .plus('1') // rounding issue .toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceUser.toString(), 'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceUser.toString()); expect( ctxtAfterWithdrawal.userStaticATokenBalance.toString(), 'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.userStaticATokenBalance .minus(rayDiv(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate)) .toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(), 'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString()); expect( ctxtAfterWithdrawal.aTokenBalanceUser.toString(), 'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.aTokenBalanceUser .plus(userInterestAccruedWithdrawal) .plus(amountToWithdraw.toString()) .toString() ); }); it('Deposit using permit + metaDeposit()', async () => { const mockFactory = new StaticATokenMetaTransactionMockFactory(controlledPkSigner); const metaTransactionMock = await mockFactory.deploy(); const chainId = DRE.network.config.chainId || 1; const userBalance = await aweth.balanceOf(controlledPkSigner.address); const amountToDeposit = new BigNumber(userBalance.div(2).toString()); const ctxtParams: tContextParams = { staticAToken: staticAWeth, underlying: (weth), aToken: aweth, user: controlledPkSigner.address, lendingPool, }; const ctxtBeforeDeposit = await getContext(ctxtParams); const permitParams = buildPermitParams( 1, // mainnet fork aweth.address, '1', await aweth.name(), controlledPkSigner.address, staticAWeth.address, (await aweth._nonces(controlledPkSigner.address)).toNumber(), MAX_UINT_AMOUNT, userBalance.div(2).toString() ); const depositParams = buildMetaDepositParams( 1, // mainnet fork staticAWeth.address, '1', await staticAWeth.name(), controlledPkSigner.address, controlledPkSigner.address, (await staticAWeth._nonces(controlledPkSigner.address)).toNumber(), MAX_UINT_AMOUNT, false, 0, userBalance.div(2).toString() ); const ownerPrivateKey = require('../../test-wallets.js').accounts[0].secretKey; if (!ownerPrivateKey) { throw new Error('INVALID_OWNER_PK'); } expect( (await aweth.allowance(controlledPkSigner.address, metaTransactionMock.address)).toString() ).to.be.equal('0', 'INVALID_ALLOWANCE_BEFORE_PERMIT'); const { v: permitV, r: permitR, s: permitS } = getSignatureFromTypedData( ownerPrivateKey, permitParams ); const { v: depositV, r: depositR, s: depositS } = getSignatureFromTypedData( ownerPrivateKey, depositParams ); await metaTransactionMock.permitAndDeposit( staticAWeth.address, controlledPkSigner.address, userBalance.div(2), 0, false, MAX_UINT_AMOUNT, { v: permitV, r: permitR, s: permitS, }, { v: depositV, r: depositR, s: depositS, }, 1 ); const ctxtAfterDeposit = await getContext(ctxtParams); const interestAccrued = getInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit); const userInterestAccrued = getUserInterestAccrued(ctxtBeforeDeposit, ctxtAfterDeposit); expect(ctxtAfterDeposit.aTokenBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceStaticAToken .plus(new BigNumber(amountToDeposit.toString())) .plus(interestAccrued) .minus(1) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceUser.toString() ); expect(ctxtAfterDeposit.userDynamicStaticATokenBalance.toString()).to.be.equal( ctxtBeforeDeposit.userDynamicStaticATokenBalance .plus(new BigNumber(amountToDeposit.toString())) .toString() ); expect(ctxtAfterDeposit.underlyingBalanceStaticAToken.toString()).to.be.equal( ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString() ); expect(ctxtAfterDeposit.aTokenBalanceUser.toString()).to.be.equal( ctxtBeforeDeposit.aTokenBalanceUser .plus(userInterestAccrued) .minus(amountToDeposit) .toString() ); }); it('Withdraw using metaWithdraw()', async () => { const ctxtParams: tContextParams = { staticAToken: staticAWeth, underlying: (weth), aToken: aweth, user: controlledPkSigner.address, lendingPool, }; const ctxtBeforeWithdrawal = await getContext(ctxtParams); const amountToWithdraw = parseEther('0.1'); const withdrawParams = buildMetaWithdrawParams( 1, // mainnet fork staticAWeth.address, '1', await staticAWeth.name(), controlledPkSigner.address, controlledPkSigner.address, (await staticAWeth._nonces(controlledPkSigner.address)).toNumber(), MAX_UINT_AMOUNT, false, '0', amountToWithdraw.toString() ); const ownerPrivateKey = require('../../test-wallets.js').accounts[0].secretKey; if (!ownerPrivateKey) { throw new Error('INVALID_OWNER_PK'); } const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, withdrawParams); await waitForTx( await staticAWeth.metaWithdraw( controlledPkSigner.address, controlledPkSigner.address, 0, amountToWithdraw, false, MAX_UINT_AMOUNT, { v, r, s, }, '1' ) ); const ctxtAfterWithdrawal = await getContext(ctxtParams); const interestAccruedWithdrawal = getInterestAccrued(ctxtBeforeWithdrawal, ctxtAfterWithdrawal); const userInterestAccruedWithdrawal = getUserInterestAccrued( ctxtBeforeWithdrawal, ctxtAfterWithdrawal ); expect( ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(), 'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW' ).to.be.equal( ctxtBeforeWithdrawal.aTokenBalanceStaticAToken .minus(amountToWithdraw.toString()) .plus(interestAccruedWithdrawal) .toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceUser.toString(), 'INVALID_UNDERLYING_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceUser.toString()); expect( ctxtAfterWithdrawal.userStaticATokenBalance.toString(), 'INVALID_STATICATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.userStaticATokenBalance .minus(rayDiv(amountToWithdraw.toString(), ctxtAfterWithdrawal.currentRate)) .toString() ); expect( ctxtAfterWithdrawal.underlyingBalanceStaticAToken.toString(), 'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL' ).to.be.equal(ctxtBeforeWithdrawal.underlyingBalanceStaticAToken.toString()); expect( ctxtAfterWithdrawal.aTokenBalanceUser.toString(), 'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' ).to.be.equal( ctxtBeforeWithdrawal.aTokenBalanceUser .plus(userInterestAccruedWithdrawal) .plus(amountToWithdraw.toString()) .toString() ); }); });