From 39ce74624b2298d20f3e83314ff4cbea5df81335 Mon Sep 17 00:00:00 2001 From: eboado Date: Wed, 17 Feb 2021 13:10:24 +0100 Subject: [PATCH] - Added basic flow test of StaticAToken + template of other tests needed --- helpers/ray-math.ts | 73 ++++++ package.json | 26 +- .../test-aave/mainnet/static-atoken.spec.ts | 224 ++++++++++++++++++ 3 files changed, 316 insertions(+), 7 deletions(-) create mode 100644 helpers/ray-math.ts create mode 100644 test-suites/test-aave/mainnet/static-atoken.spec.ts diff --git a/helpers/ray-math.ts b/helpers/ray-math.ts new file mode 100644 index 00000000..943a84f8 --- /dev/null +++ b/helpers/ray-math.ts @@ -0,0 +1,73 @@ +import BigNumber from 'bignumber.js'; + +export type BigNumberValue = string | number | BigNumber; + +export const BigNumberZD = BigNumber.clone({ + DECIMAL_PLACES: 0, + ROUNDING_MODE: BigNumber.ROUND_DOWN, +}); + +export function valueToBigNumber(amount: BigNumberValue): BigNumber { + return new BigNumber(amount); +} + +export function valueToZDBigNumber(amount: BigNumberValue): BigNumber { + return new BigNumberZD(amount); +} + +export const WAD = valueToZDBigNumber(10).pow(18); +export const HALF_WAD = WAD.dividedBy(2); + +export const RAY = valueToZDBigNumber(10).pow(27); +export const HALF_RAY = RAY.dividedBy(2); + +export const WAD_RAY_RATIO = valueToZDBigNumber(10).pow(9); + +export function wadMul(a: BigNumberValue, b: BigNumberValue): BigNumber { + return HALF_WAD.plus(valueToZDBigNumber(a).multipliedBy(b)).div(WAD); +} + +export function wadDiv(a: BigNumberValue, b: BigNumberValue): BigNumber { + const halfB = valueToZDBigNumber(b).div(2); + + return halfB.plus(valueToZDBigNumber(a).multipliedBy(WAD)).div(b); +} + +export function rayMul(a: BigNumberValue, b: BigNumberValue): BigNumber { + return HALF_RAY.plus(valueToZDBigNumber(a).multipliedBy(b)).div(RAY); +} + +export function rayDiv(a: BigNumberValue, b: BigNumberValue): BigNumber { + const halfB = valueToZDBigNumber(b).div(2); + + return halfB.plus(valueToZDBigNumber(a).multipliedBy(RAY)).div(b); +} + +export function rayToWad(a: BigNumberValue): BigNumber { + const halfRatio = valueToZDBigNumber(WAD_RAY_RATIO).div(2); + + return halfRatio.plus(a).div(WAD_RAY_RATIO); +} + +export function wadToRay(a: BigNumberValue): BigNumber { + return valueToZDBigNumber(a).multipliedBy(WAD_RAY_RATIO).decimalPlaces(0); +} + +export function rayPow(a: BigNumberValue, p: BigNumberValue): BigNumber { + let x = valueToZDBigNumber(a); + let n = valueToZDBigNumber(p); + let z = !n.modulo(2).eq(0) ? x : valueToZDBigNumber(RAY); + + for (n = n.div(2); !n.eq(0); n = n.div(2)) { + x = rayMul(x, x); + + if (!n.modulo(2).eq(0)) { + z = rayMul(z, x); + } + } + return z; +} + +export function rayToDecimal(a: BigNumberValue): BigNumber { + return valueToZDBigNumber(a).dividedBy(RAY); +} diff --git a/package.json b/package.json index b9debafc..a60cf368 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,25 @@ "hardhat:mumbai": "hardhat --network mumbai", "hardhat:matic": "hardhat --network matic", "compile": "SKIP_LOAD=true hardhat compile", - "console:fork": "FORK=main hardhat console", - "test": "npm run compile && TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test-suites/test-aave/*.spec.ts", - "test-amm": "npm run compile && TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test-suites/test-amm/*.spec.ts", - "test-amm-scenarios": "npm run compile && TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test-suites/test-amm/__setup.spec.ts test-suites/test-amm/scenario.spec.ts", - "test-scenarios": "npm run compile && npx hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/scenario.spec.ts", - "test-subgraph:scenarios": "npm run compile && hardhat --network hardhatevm_docker test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/subgraph-scenarios.spec.ts", - "test:main:check-list": "npm run compile && FORK=main TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/mainnet/check-list.spec.ts", + "console:fork": "MAINNET_FORK=true hardhat console", + "test": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/*.spec.ts", + "test-scenarios": "npx hardhat test test/__setup.spec.ts test/scenario.spec.ts", + "test-repay-with-collateral": "hardhat test test/__setup.spec.ts test/repay-with-collateral.spec.ts", + "test-liquidate-with-collateral": "hardhat test test/__setup.spec.ts test/flash-liquidation-with-collateral.spec.ts", + "test-liquidate-underlying": "hardhat test test/__setup.spec.ts test/liquidation-underlying.spec.ts", + "test-configurator": "hardhat test test/__setup.spec.ts test/configurator.spec.ts", + "test-transfers": "hardhat test test/__setup.spec.ts test/atoken-transfer.spec.ts", + "test-flash": "hardhat test test/__setup.spec.ts test/flashloan.spec.ts", + "test-liquidate": "hardhat test test/__setup.spec.ts test/liquidation-atoken.spec.ts", + "test-deploy": "hardhat test test/__setup.spec.ts test/test-init.spec.ts", + "test-pausable": "hardhat test test/__setup.spec.ts test/pausable-functions.spec.ts", + "test-permit": "hardhat test test/__setup.spec.ts test/atoken-permit.spec.ts", + "test-stable-and-atokens": "hardhat test test/__setup.spec.ts test/atoken-transfer.spec.ts test/stable-token.spec.ts", + "test-subgraph:scenarios": "hardhat --network hardhatevm_docker test test/__setup.spec.ts test/subgraph-scenarios.spec.ts", + "test-weth": "hardhat test test/__setup.spec.ts test/weth-gateway.spec.ts", + "test-uniswap": "hardhat test test/__setup.spec.ts test/uniswapAdapters*.spec.ts", + "test:main:check-list": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts test/mainnet/check-list.spec.ts", + "test:main:staticAToken": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/mainnet/static-atoken.spec.ts", "dev:coverage": "buidler compile --force && buidler coverage --network coverage", "aave:evm:dev:migration": "npm run compile && hardhat aave:dev", "aave:docker:full:migration": "npm run compile && npm run hardhat:docker -- aave:mainnet --skip-registry", diff --git a/test-suites/test-aave/mainnet/static-atoken.spec.ts b/test-suites/test-aave/mainnet/static-atoken.spec.ts new file mode 100644 index 00000000..776bb4c2 --- /dev/null +++ b/test-suites/test-aave/mainnet/static-atoken.spec.ts @@ -0,0 +1,224 @@ +import rawDRE from 'hardhat'; +import BigNumber from 'bignumber.js'; +import { + LendingPoolFactory, + WETH9Factory, + StaticATokenFactory, + ATokenFactory, + ERC20, + LendingPool, +} from '../../types'; +import { impersonateAccountsHardhat, DRE, waitForTx } from '../../helpers/misc-utils'; +import { utils } from 'ethers'; +import { rayMul } from '../../helpers/ray-math'; +import { MAX_UINT_AMOUNT } from '../../helpers/constants'; +import { tEthereumAddress } from '../../helpers/types'; + +const { expect } = require('chai'); + +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']; + +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()), +}); + +before(async () => { + await rawDRE.run('set-DRE'); + + // Impersonations + await impersonateAccountsHardhat([ETHER_BANK, ...TEST_USERS]); + + const ethHolderSigner = DRE.ethers.provider.getSigner(ETHER_BANK); + for (const recipientOfEth of [...TEST_USERS]) { + await ethHolderSigner.sendTransaction({ + from: ethHolderSigner._address, + to: recipientOfEth, + value: utils.parseEther('100'), + ...defaultTxParams, + }); + } + + console.log('\n***************'); + console.log('Test setup finished'); + console.log('***************\n'); +}); + +describe('StaticAToken: aToken wrapper with static balances', () => { + it('Deposit WETH on stataWETH, then withdraw of the whole balance in underlying', async () => { + const userSigner = DRE.ethers.provider.getSigner(TEST_USERS[0]); + + const lendingPool = LendingPoolFactory.connect(LENDING_POOL, userSigner); + + const weth = WETH9Factory.connect(WETH, userSigner); + + const aweth = ATokenFactory.connect(AWETH, userSigner); + + const amountToDeposit = utils.parseEther('5'); + + await waitForTx(await weth.deposit({ value: amountToDeposit })); + + const staticAToken = await new StaticATokenFactory(userSigner).deploy( + LENDING_POOL, + AWETH, + 'Static Aave Interest Bearing WETH', + 'stataAAVE' + ); + + const ctxtParams: tContextParams = { + staticAToken: staticAToken, + underlying: (weth), + aToken: aweth, + user: userSigner._address, + lendingPool, + }; + + const ctxtBeforeDeposit = await getContext(ctxtParams); + + await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams)); + + await waitForTx( + await staticAToken.deposit(userSigner._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 staticAToken.withdraw(userSigner._address, amountToWithdraw, true, defaultTxParams) + ); + + const ctxtAfterWithdrawal = await getContext(ctxtParams); + + expect( + ctxtAfterWithdrawal.aTokenBalanceStaticAToken.toString(), + 'INVALID_ATOKEN_BALANCE_ON_STATICATOKEN_AFTER_WITHDRAW' + ).to.be.equal( + rayMul( + ctxtAfterWithdrawal.staticATokenSupply.plus(ctxtBeforeWithdrawal.userStaticATokenBalance), + ctxtAfterWithdrawal.currentRate + ) + .minus( + rayMul(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( + ctxtAfterDeposit.underlyingBalanceStaticAToken.toString(), + 'INVALID_UNDERLYNG_BALANCE_OF_STATICATOKEN_AFTER_WITHDRAWAL' + ).to.be.equal(ctxtBeforeDeposit.underlyingBalanceStaticAToken.toString()); + + expect( + ctxtBeforeDeposit.aTokenBalanceUser.toString(), + 'INVALID_ATOKEN_BALANCE_OF_USER_AFTER_WITHDRAWAL' + ).to.be.equal(ctxtAfterDeposit.aTokenBalanceUser.toString()); + }); + + it('Deposit WETH on stataWETH and then withdraw some balance in underlying', async () => {}); + + it('Deposit WETH on stataWETH and then withdraw all the balance in aToken', async () => {}); + + it('Deposit aWETH on stataWETH and then withdraw some balance in aToken', async () => {}); + + it('Deposit using metaDeposit()', async () => {}); + + it('Withdraw using withdrawDynamicAmount()', async () => {}); + + it('Withdraw using metaWithdraw()', async () => {}); +});