From 31eccec7ab11edf53d73c3176e8d98d94356d275 Mon Sep 17 00:00:00 2001 From: cedephrase <130931230+cedephrase@users.noreply.github.com> Date: Wed, 10 May 2023 13:48:11 -0400 Subject: [PATCH] test: Add additional Foundry test coverage (#322) * chore: add Foundry initial test environment mocks * chore: remove mint at test * chore: add tests and borrow fuzz to GhoVariableDebtToken * chore: add initial test engine for gho borrow * chore: add percent calc function * fix: Remove discount lock period mechanism (#293) * fix: remove discount lock period from GhoDebtToken * test: Update tests for new rebalance behaviour * fix: Optimize gho debt balanceof (#297) * fix: Simplify GhoToken.addFacilitator function (#295) * fix: Remove Facilitator struct from addFacilitator function * test: Adjusts test for new addFacilitator parameters * fix: Make facilitatorLabel calldata-like parameter * feat: Add helper contract to manage risk params of GHO reserve (#291) * setup modifier * setup gho manager and tests * remove atoken changes * use interfaces * adds test for ghomanager as pooladmin * fix: Remove updateLockDiscountPeriod calls from GhoManager (#298) * chore: initial repay engine * chore: add makefile with coverage html report and ignore mocks, add rebalance discount tests and onbehalf borrow * chore: add 100% coverage for GhoVariableDebtToken contract * chore: add GhoAToken.sol tests * fix: Stop prettier formatting dependencies and cache * feat: Initial flashminter setup * misc: Slight optimisation * test: Add Gho FlashMinter unit tests * test: Add flash minter unit tests * fix: Resolve VSCode import callback issue with forge-std/ds-test * test: Add GhoAToken tests for transfer and self-rescue reverts * fix: exclude foundry-test for hardhat coverage tests * test: Add initial GhoToken unit tests * fix: remove stkAave mocks, replace with BGD repo and update impacted tests * fix: resolved error in testUpdateDiscountTokenToZero test * fix: increase node testrunner heap alloc to avoid OOM * fix: setup stkAave behind proxy * test: add GhoOracle unit tests * test: add GhoDiscountRateStrategy unit/fuzz tests * test: add GhoInterestRateStrategy fuzz test * test: add GhoManager unit tests, mocked PoolConfigurator * fix: remove unnecessary stopPranks that fail in latest forge * test: added nonce/domain sep unit tests for GhoAToken * test: add GhoStableDebtToken unit tests * feat: add combined coverage from hardhat/foundry * fix: refactor initial foundry tests * fix: move MockFlashBorrower to common location * feat: add EmptyDiscountRateStrategy fuzz test * feat: add UiGhoDataProvider unit tests * feat: complete GhoToken unit tests * feat: complete GhoVariableDebtToken unit tests * feat: add last unit test for GhoVariableDebtToken * fix: update ordering in constant/event test files * fix: remove unnecessary prank on proxy init * fix: naming of fuzz tests to be more descriptive * fix: re-remove old tests that got re-merged * fix: Remove hardhat unitTests folder * fix: Fix constants value of tests * test: Add couple of test cases * test: Rename test titles * fix: Rename EmptyDiscountRateStrategy to ZeroDiscountRateStrategy * fix: Remove unneeded import * feat: add genhtml back to combined coverage * fix: add error messages on test reverts * fix: Fix package scripts * fix: Fix deploy script of GhoStableDebtToken --------- Co-authored-by: David Racero Co-authored-by: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Co-authored-by: Mark Hinschberger Co-authored-by: Zer0dot Co-authored-by: miguelmtzinf --- .gitignore | 3 +- Makefile | 4 - combined-coverage.sh | 35 +++ foundry.toml | 2 +- package.json | 6 +- .../ZeroDiscountRateStrategy.sol} | 7 +- src/contracts/foundry-test/TestEnv.sol | 137 ---------- .../TestGhoAToken.t.sol} | 136 +++++----- .../GhoActions.sol => test/TestGhoBase.t.sol} | 209 +++++++++++++--- .../test/TestGhoDiscountRateStrategy.t.sol | 109 ++++++++ .../TestGhoFlashMinter.t.sol} | 83 +++---- .../test/TestGhoInterestRateStrategy.t.sol | 17 ++ src/contracts/test/TestGhoManager.t.sol | 48 ++++ src/contracts/test/TestGhoOracle.t.sol | 16 ++ .../test/TestGhoStableDebtToken.t.sol | 194 +++++++++++++++ .../TestGhoToken.t.sol} | 191 +++++++++----- .../TestGhoVariableDebtToken.t.sol | 233 +++++++++--------- .../test/TestUiGhoDataProvider.t.sol | 83 +++++++ .../test/TestZeroDiscountRateStrategy.t.sol | 19 ++ src/contracts/test/helpers/Constants.sol | 23 ++ .../libraries => test/helpers}/DebtUtils.sol | 8 + src/contracts/test/helpers/Events.sol | 75 ++++++ .../mocks/MockFlashBorrower.sol | 7 +- .../mocks/MockedAclManager.sol | 5 + .../test/mocks/MockedConfigurator.sol | 34 +++ .../mocks/MockedPool.sol | 18 ++ .../mocks/MockedProvider.sol | 5 + src/helpers/contract-getters.ts | 3 +- src/test/discount-rebalance.test.ts | 4 +- .../{unitTests => }/gho-token-permit.test.ts | 8 +- .../{unitTests => }/gho-token-unit.test.ts | 8 +- src/test/unitTests/gho.oracle-unit.test.ts | 38 --- 32 files changed, 1241 insertions(+), 527 deletions(-) delete mode 100644 Makefile create mode 100755 combined-coverage.sh rename src/contracts/facilitators/aave/{mocks/EmptyDiscountRateStrategy.sol => interestStrategy/ZeroDiscountRateStrategy.sol} (76%) delete mode 100644 src/contracts/foundry-test/TestEnv.sol rename src/contracts/{foundry-test/TestGhoAToken.sol => test/TestGhoAToken.t.sol} (67%) rename src/contracts/{foundry-test/libraries/GhoActions.sol => test/TestGhoBase.t.sol} (61%) create mode 100644 src/contracts/test/TestGhoDiscountRateStrategy.t.sol rename src/contracts/{foundry-test/TestGhoFlashMinter.sol => test/TestGhoFlashMinter.t.sol} (73%) create mode 100644 src/contracts/test/TestGhoInterestRateStrategy.t.sol create mode 100644 src/contracts/test/TestGhoManager.t.sol create mode 100644 src/contracts/test/TestGhoOracle.t.sol create mode 100644 src/contracts/test/TestGhoStableDebtToken.t.sol rename src/contracts/{foundry-test/TestGhoToken.sol => test/TestGhoToken.t.sol} (51%) rename src/contracts/{foundry-test => test}/TestGhoVariableDebtToken.t.sol (57%) create mode 100644 src/contracts/test/TestUiGhoDataProvider.t.sol create mode 100644 src/contracts/test/TestZeroDiscountRateStrategy.t.sol create mode 100644 src/contracts/test/helpers/Constants.sol rename src/contracts/{foundry-test/libraries => test/helpers}/DebtUtils.sol (86%) create mode 100644 src/contracts/test/helpers/Events.sol rename src/contracts/{facilitators/flashMinter => test}/mocks/MockFlashBorrower.sol (93%) rename src/contracts/{foundry-test => test}/mocks/MockedAclManager.sol (74%) create mode 100644 src/contracts/test/mocks/MockedConfigurator.sol rename src/contracts/{foundry-test => test}/mocks/MockedPool.sol (85%) rename src/contracts/{foundry-test => test}/mocks/MockedProvider.sol (67%) rename src/test/{unitTests => }/gho-token-permit.test.ts (97%) rename src/test/{unitTests => }/gho-token-unit.test.ts (98%) delete mode 100644 src/test/unitTests/gho.oracle-unit.test.ts diff --git a/.gitignore b/.gitignore index 45885c9b..5d837c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ .DS_Store forge-cache /report -lcov.info \ No newline at end of file +lcov.info +combined-lcov.info diff --git a/Makefile b/Makefile deleted file mode 100644 index 5d327ec8..00000000 --- a/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -test :; forge test -vvv - -# install lcov via $ apt install lcov or $ brew install lcov to be able to ignore tests and mock files -coverage :; forge coverage --report lcov && lcov --remove ./lcov.info -o ./lcov.info.pruned 'src/contracts/foundry-test/*' 'src/contracts/facilitators/flashMinter/mocks/*' src/contracts/facilitators/aave/mocks/* && mv lcov.info.pruned lcov.info && genhtml ./lcov.info -o report --branch-coverage diff --git a/combined-coverage.sh b/combined-coverage.sh new file mode 100755 index 00000000..493a81c6 --- /dev/null +++ b/combined-coverage.sh @@ -0,0 +1,35 @@ + +#!/bin/bash + +# @dev +# This bash script creates coverage reports via Hardhat and Foundry +# and then merges them, removing uninteresting files + +export NODE_OPTIONS="--max_old_space_size=8192" + +set -e + +npm run hardhat coverage +forge coverage --report lcov + +sed -i -e 's/\/.*gho-core.//g' coverage/lcov.info + +lcov \ + --rc lcov_branch_coverage=1 \ + --add-tracefile coverage/lcov.info \ + --add-tracefile lcov.info \ + --output-file merged-lcov.info + +lcov \ + --rc lcov_branch_coverage=1 \ + --remove merged-lcov.info \ + --output-file combined-lcov.info \ + "*node_modules*" "*test*" "*mock*" + +rm merged-lcov.info + +lcov \ + --rc lcov_branch_coverage=1 \ + --list combined-lcov.info + +genhtml ./combined-lcov.info -o report --branch-coverage \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index c9ce9104..82163e3f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,7 +1,7 @@ [profile.default] src = 'src/contracts' out = 'out' -test = 'src/contracts/foundry-test' +test = 'src/contracts/test' cache_path = 'forge-cache' libs = ["node_modules", "lib"] remappings = [ diff --git a/package.json b/package.json index 496b50b0..d1be0382 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,13 @@ "prettier:write": "prettier --write .", "prepare": "husky install", "compile": "rm -rf ./artifacts ./cache ./types && SKIP_LOAD=true hardhat compile", - "test": ". ./setup-test-env.sh && hardhat test ./src/test/*.ts ./src/test/**/*.ts", + "test": ". ./setup-test-env.sh && NODE_OPTIONS=--max-old-space-size=4096 hardhat test ./src/test/*.ts", "forge-test": "forge test -vvv --no-match-test 'skip'", "test-goerli:fork": ". ./setup-test-env.sh && FORK=goerli npm run test --no-compile", "test-goerli:fork:skip-deploy": ". ./setup-test-env.sh && FORK=goerli SKIP_DEPLOY=true npm run test", - "test:stkAave": ". ./setup-test-env.sh && hardhat test ./src/test/__setup.test.ts ./src/test/stkAave-upgrade.test.ts", - "test-unit": ". ./setup-test-env.sh && hardhat test ./src/test/unitTests/*.ts", + "test:stkAave": ". ./setup-test-env.sh && NODE_OPTIONS=--max-old-space-size=4096 hardhat test ./src/test/__setup.test.ts ./src/test/stkAave-upgrade.test.ts", "coverage": ". ./setup-test-env.sh && hardhat coverage", + "combined-coverage": ". ./setup-test-env.sh && ./combined-coverage.sh", "deploy-testnet": ". ./setup-test-env.sh && hardhat deploy-and-setup", "deploy-testnet:goerli": "HARDHAT_NETWORK=goerli npm run deploy-testnet", "deploy-testnet:goerli:fork": "FORK=goerli npm run deploy-testnet", diff --git a/src/contracts/facilitators/aave/mocks/EmptyDiscountRateStrategy.sol b/src/contracts/facilitators/aave/interestStrategy/ZeroDiscountRateStrategy.sol similarity index 76% rename from src/contracts/facilitators/aave/mocks/EmptyDiscountRateStrategy.sol rename to src/contracts/facilitators/aave/interestStrategy/ZeroDiscountRateStrategy.sol index a2a087f0..417cac7a 100644 --- a/src/contracts/facilitators/aave/mocks/EmptyDiscountRateStrategy.sol +++ b/src/contracts/facilitators/aave/interestStrategy/ZeroDiscountRateStrategy.sol @@ -3,7 +3,12 @@ pragma solidity ^0.8.10; import {IGhoDiscountRateStrategy} from '../interestStrategy/interfaces/IGhoDiscountRateStrategy.sol'; -contract EmptyDiscountRateStrategy is IGhoDiscountRateStrategy { +/** + * @title ZeroDiscountRateStrategy + * @author Aave + * @notice Discount Rate Strategy that always return zero discount rate. + */ +contract ZeroDiscountRateStrategy is IGhoDiscountRateStrategy { /** * @dev Calculates the interest rates depending on the reserve's state and configurations * @param debtBalance The address of the reserve diff --git a/src/contracts/foundry-test/TestEnv.sol b/src/contracts/foundry-test/TestEnv.sol deleted file mode 100644 index 9ae5c26a..00000000 --- a/src/contracts/foundry-test/TestEnv.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; - -import {WETH9Mock} from '@aave/periphery-v3/contracts/mocks/WETH9Mock.sol'; -import {ERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/ERC20.sol'; -import {GhoAToken} from '../facilitators/aave/tokens/GhoAToken.sol'; -import {GhoToken} from '../gho/GhoToken.sol'; -import {MockedPool} from './mocks/MockedPool.sol'; -import {MockedProvider} from './mocks/MockedProvider.sol'; -import {MockedAclManager} from './mocks/MockedAclManager.sol'; -import {GhoVariableDebtToken} from '../facilitators/aave/tokens/GhoVariableDebtToken.sol'; -import {GhoFlashMinter} from '../facilitators/flashMinter/GhoFlashMinter.sol'; -import {MockFlashBorrower} from '../facilitators/flashMinter/mocks/MockFlashBorrower.sol'; -import {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol'; -import {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol'; -import {IGhoToken} from '../gho/interfaces/IGhoToken.sol'; -import {GhoDiscountRateStrategy} from '../facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol'; -import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; -import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol'; -import {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol'; -import {TestnetERC20} from '@aave/periphery-v3/contracts/mocks/testnet-helpers/TestnetERC20.sol'; -import {StakedAaveV3} from 'aave-stk-v1-5/src/contracts/StakedAaveV3.sol'; -import {IStakedAaveV3} from 'aave-stk-v1-5/src/interfaces/IStakedAaveV3.sol'; -import {IERC20} from 'aave-stk-v1-5/src/interfaces/IERC20.sol'; -import {IGhoVariableDebtTokenTransferHook} from 'aave-stk-v1-5/src/interfaces/IGhoVariableDebtTokenTransferHook.sol'; - -contract TestEnv is Test { - address constant faucet = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; - address constant treasury = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; - address constant stkAaveExecutor = 0xEE56e2B3D491590B5b31738cC34d5232F378a8D5; - uint256 constant DEFAULT_FLASH_FEE = 9; // 0.09% - uint128 constant DEFAULT_CAPACITY = 100_000_000e18; - - address[3] users = [ - 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, - 0x90F79bf6EB2c4f870365E785982E1f101E93b906, - 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 - ]; - GhoToken GHO_TOKEN; - TestnetERC20 AAVE_TOKEN; - IStakedAaveV3 STK_TOKEN; - MockedPool POOL; - MockedAclManager ACL_MANAGER; - MockedProvider PROVIDER; - WETH9Mock WETH; - GhoVariableDebtToken GHO_DEBT_TOKEN; - GhoAToken GHO_ATOKEN; - GhoFlashMinter GHO_FLASH_MINTER; - GhoDiscountRateStrategy GHO_DISCOUNT_STRATEGY; - MockFlashBorrower FLASH_BORROWER; - - function setupGho() public { - bytes memory empty; - ACL_MANAGER = new MockedAclManager(); - PROVIDER = new MockedProvider(address(ACL_MANAGER)); - POOL = new MockedPool(IPoolAddressesProvider(address(PROVIDER))); - GHO_TOKEN = new GhoToken(); - AAVE_TOKEN = new TestnetERC20('AAVE', 'AAVE', 18, faucet); - StakedAaveV3 stkAave = new StakedAaveV3( - IERC20(address(AAVE_TOKEN)), - IERC20(address(AAVE_TOKEN)), - 1, - address(0), - address(0), - 1 - ); - STK_TOKEN = IStakedAaveV3(address(stkAave)); - address ghoToken = address(GHO_TOKEN); - address discountToken = address(STK_TOKEN); - IPool iPool = IPool(address(POOL)); - WETH = new WETH9Mock('Wrapped Ether', 'WETH', faucet); - GHO_DEBT_TOKEN = new GhoVariableDebtToken(iPool); - GHO_ATOKEN = new GhoAToken(iPool); - GHO_DEBT_TOKEN.initialize( - iPool, - ghoToken, - IAaveIncentivesController(address(0)), - 18, - 'GHO Variable Debt', - 'GHOVarDebt', - empty - ); - GHO_ATOKEN.initialize( - iPool, - treasury, - ghoToken, - IAaveIncentivesController(address(0)), - 18, - 'GHO AToken', - 'aGHO', - empty - ); - GHO_ATOKEN.updateGhoTreasury(treasury); - GHO_DEBT_TOKEN.updateDiscountToken(discountToken); - GHO_DISCOUNT_STRATEGY = new GhoDiscountRateStrategy(); - GHO_DEBT_TOKEN.updateDiscountRateStrategy(address(GHO_DISCOUNT_STRATEGY)); - GHO_DEBT_TOKEN.setAToken(address(GHO_ATOKEN)); - GHO_ATOKEN.setVariableDebtToken(address(GHO_DEBT_TOKEN)); - vm.prank(stkAaveExecutor); - STK_TOKEN.setGHODebtToken(IGhoVariableDebtTokenTransferHook(address(GHO_DEBT_TOKEN))); - IGhoToken(ghoToken).addFacilitator(address(GHO_ATOKEN), 'Gho Atoken Market', DEFAULT_CAPACITY); - POOL.setGhoTokens(GHO_DEBT_TOKEN, GHO_ATOKEN); - - GHO_FLASH_MINTER = new GhoFlashMinter( - address(GHO_TOKEN), - treasury, - DEFAULT_FLASH_FEE, - address(PROVIDER) - ); - FLASH_BORROWER = new MockFlashBorrower(IERC3156FlashLender(GHO_FLASH_MINTER)); - - IGhoToken(ghoToken).addFacilitator( - address(GHO_FLASH_MINTER), - 'Gho Flash Minter', - DEFAULT_CAPACITY - ); - IGhoToken(ghoToken).addFacilitator( - address(FLASH_BORROWER), - 'Gho Flash Borrower', - DEFAULT_CAPACITY - ); - - IGhoToken(ghoToken).addFacilitator(faucet, 'Faucet Facilitator', DEFAULT_CAPACITY); - } - - function ghoFaucet(address to, uint256 amount) public { - vm.stopPrank(); - vm.prank(faucet); - GHO_TOKEN.mint(to, amount); - } - - constructor() { - setupGho(); - } -} diff --git a/src/contracts/foundry-test/TestGhoAToken.sol b/src/contracts/test/TestGhoAToken.t.sol similarity index 67% rename from src/contracts/foundry-test/TestGhoAToken.sol rename to src/contracts/test/TestGhoAToken.t.sol index 67193424..cb5361a9 100644 --- a/src/contracts/foundry-test/TestGhoAToken.sol +++ b/src/contracts/test/TestGhoAToken.t.sol @@ -1,35 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import 'forge-std/Test.sol'; - -import './TestEnv.sol'; -import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; -import {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol'; -import {DebtUtils} from './libraries/DebtUtils.sol'; -import {GhoActions} from './libraries/GhoActions.sol'; - -contract TestGhoAToken is Test, GhoActions { - address public alice; - address public bob; - address public carlos; - uint256 borrowAmount = 200e18; - - event VariableDebtTokenSet(address indexed variableDebtToken); - event FeesDistributedToTreasury( - address indexed ghoTreasury, - address indexed asset, - uint256 amount - ); - event GhoTreasuryUpdated(address indexed oldGhoTreasury, address indexed newGhoTreasury); - - function setUp() public { - alice = users[0]; - bob = users[1]; - carlos = users[2]; - mintAndStakeDiscountToken(bob, 10_000e18); - } +import './TestGhoBase.t.sol'; +contract TestGhoAToken is TestGhoBase { function testConstructor() public { GhoAToken aToken = new GhoAToken(IPool(address(POOL))); assertEq(aToken.name(), 'GHO_ATOKEN_IMPL', 'Wrong default ERC20 name'); @@ -39,12 +13,12 @@ contract TestGhoAToken is Test, GhoActions { function testInitialize() public { GhoAToken aToken = new GhoAToken(IPool(address(POOL))); - string memory tokenName = 'GHO AToken'; + string memory tokenName = 'Aave GHO'; string memory tokenSymbol = 'aGHO'; bytes memory empty; aToken.initialize( IPool(address(POOL)), - treasury, + TREASURY, address(GHO_TOKEN), IAaveIncentivesController(address(0)), 18, @@ -59,7 +33,7 @@ contract TestGhoAToken is Test, GhoActions { } function testInitializePoolRevert() public { - string memory tokenName = 'GHO AToken'; + string memory tokenName = 'Aave GHO'; string memory tokenSymbol = 'aGHO'; bytes memory empty; @@ -67,7 +41,7 @@ contract TestGhoAToken is Test, GhoActions { vm.expectRevert(bytes(Errors.POOL_ADDRESSES_DO_NOT_MATCH)); aToken.initialize( IPool(address(0)), - treasury, + TREASURY, address(GHO_TOKEN), IAaveIncentivesController(address(0)), 18, @@ -78,14 +52,14 @@ contract TestGhoAToken is Test, GhoActions { } function testReInitRevert() public { - string memory tokenName = 'GHO AToken'; + string memory tokenName = 'Aave GHO'; string memory tokenSymbol = 'aGHO'; bytes memory empty; vm.expectRevert(bytes('Contract instance has already been initialized')); GHO_ATOKEN.initialize( IPool(address(POOL)), - treasury, + TREASURY, address(GHO_TOKEN), IAaveIncentivesController(address(0)), 18, @@ -111,27 +85,27 @@ contract TestGhoAToken is Test, GhoActions { ); } - function testMintByOther() public { - vm.startPrank(alice); + function testUnauthorizedMint() public { + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL)); - GHO_ATOKEN.mint(alice, alice, 0, 0); + GHO_ATOKEN.mint(ALICE, ALICE, 0, 0); } - function testBurnByOther() public { - vm.startPrank(alice); + function testUnauthorizedBurn() public { + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL)); - GHO_ATOKEN.burn(alice, alice, 0, 0); + GHO_ATOKEN.burn(ALICE, ALICE, 0, 0); } - function testSetVariableDebtTokenByOther() public { + function testUnauthorizedSetVariableDebtToken() public { GhoAToken aToken = new GhoAToken(IPool(address(POOL))); - vm.startPrank(alice); + vm.startPrank(ALICE); ACL_MANAGER.setState(false); vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN)); - aToken.setVariableDebtToken(alice); + aToken.setVariableDebtToken(ALICE); } function testSetVariableDebtToken() public { @@ -144,15 +118,15 @@ contract TestGhoAToken is Test, GhoActions { } function testUpdateVariableDebtToken() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes('VARIABLE_DEBT_TOKEN_ALREADY_SET')); - GHO_ATOKEN.setVariableDebtToken(alice); + GHO_ATOKEN.setVariableDebtToken(ALICE); } function testZeroVariableDebtToken() public { GhoAToken aToken = new GhoAToken(IPool(address(POOL))); - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID')); aToken.setVariableDebtToken(address(0)); } @@ -160,7 +134,7 @@ contract TestGhoAToken is Test, GhoActions { function testMintRevert() public { vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); vm.prank(address(POOL)); - GHO_ATOKEN.mint(carlos, carlos, 1, 1); + GHO_ATOKEN.mint(CHARLES, CHARLES, 1, 1); } function testPermitRevert() public { @@ -168,13 +142,13 @@ contract TestGhoAToken is Test, GhoActions { vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); vm.prank(address(POOL)); - GHO_ATOKEN.permit(carlos, carlos, 1, 1, 1, empty, empty); + GHO_ATOKEN.permit(CHARLES, CHARLES, 1, 1, 1, empty, empty); } function testBurnRevert() public { vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); vm.prank(address(POOL)); - GHO_ATOKEN.burn(carlos, carlos, 1, 1); + GHO_ATOKEN.burn(CHARLES, CHARLES, 1, 1); } function testMintToTreasuryRevert() public { @@ -186,17 +160,17 @@ contract TestGhoAToken is Test, GhoActions { function testTransferOnLiquidationRevert() public { vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); vm.prank(address(POOL)); - GHO_ATOKEN.transferOnLiquidation(carlos, carlos, 1); + GHO_ATOKEN.transferOnLiquidation(CHARLES, CHARLES, 1); } function testStandardTransferRevert() public { vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - vm.prank(carlos); - GHO_ATOKEN.transfer(alice, 0); + vm.prank(CHARLES); + GHO_ATOKEN.transfer(ALICE, 0); } function testBalanceOfAlwaysZero() public { - uint256 balance = GHO_ATOKEN.balanceOf(carlos); + uint256 balance = GHO_ATOKEN.balanceOf(CHARLES); assertEq(balance, 0, 'AToken balance should always be zero'); } @@ -208,22 +182,22 @@ contract TestGhoAToken is Test, GhoActions { function testReserveTreasuryAddress() public { assertEq( GHO_ATOKEN.RESERVE_TREASURY_ADDRESS(), - treasury, + TREASURY, 'AToken treasury address should match the initalized address' ); } function testDistributeFees() public { - borrowAction(carlos, 1000e18); + borrowAction(CHARLES, 1000e18); vm.warp(block.timestamp + 640000); - ghoFaucet(carlos, 5e18); + ghoFaucet(CHARLES, 5e18); - repayAction(carlos, GHO_DEBT_TOKEN.balanceOf(carlos)); + repayAction(CHARLES, GHO_DEBT_TOKEN.balanceOf(CHARLES)); vm.expectEmit(true, true, true, true, address(GHO_ATOKEN)); emit FeesDistributedToTreasury( - treasury, + TREASURY, address(GHO_TOKEN), GHO_TOKEN.balanceOf(address(GHO_ATOKEN)) ); @@ -231,18 +205,18 @@ contract TestGhoAToken is Test, GhoActions { } function testRescueToken() public { - vm.prank(faucet); + vm.prank(FAUCET); AAVE_TOKEN.mint(address(GHO_ATOKEN), 1); - GHO_ATOKEN.rescueTokens(address(AAVE_TOKEN), carlos, 1); + GHO_ATOKEN.rescueTokens(address(AAVE_TOKEN), CHARLES, 1); - assertEq(AAVE_TOKEN.balanceOf(carlos), 1, 'Token rescue should transfer 1 wei'); + assertEq(AAVE_TOKEN.balanceOf(CHARLES), 1, 'Token rescue should transfer 1 wei'); } function testRescueTokenRevertIfUnderlying() public { vm.expectRevert(bytes(Errors.UNDERLYING_CANNOT_BE_RESCUED)); - vm.prank(faucet); - GHO_ATOKEN.rescueTokens(address(GHO_TOKEN), carlos, 1); + vm.prank(FAUCET); + GHO_ATOKEN.rescueTokens(address(GHO_TOKEN), CHARLES, 1); } function testUpdateGhoTreasuryRevertIfZero() public { @@ -252,18 +226,42 @@ contract TestGhoAToken is Test, GhoActions { function testUpdateGhoTreasury() public { vm.expectEmit(true, true, true, true, address(GHO_ATOKEN)); - emit GhoTreasuryUpdated(treasury, alice); - GHO_ATOKEN.updateGhoTreasury(alice); + emit GhoTreasuryUpdated(TREASURY, ALICE); + GHO_ATOKEN.updateGhoTreasury(ALICE); - assertEq(GHO_ATOKEN.getGhoTreasury(), alice); + assertEq(GHO_ATOKEN.getGhoTreasury(), ALICE); } - function testUpdateGhoTreasuryRevertByOther() public { + function testUnauthorizedUpdateGhoTreasuryRevert() public { ACL_MANAGER.setState(false); - vm.prank(alice); + vm.prank(ALICE); vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN)); - GHO_ATOKEN.updateGhoTreasury(alice); + GHO_ATOKEN.updateGhoTreasury(ALICE); + } + + function testDomainSeparator() public { + bytes32 EIP712_DOMAIN = keccak256( + 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' + ); + bytes memory EIP712_REVISION = bytes('1'); + bytes32 expected = keccak256( + abi.encode( + EIP712_DOMAIN, + keccak256(bytes(GHO_ATOKEN.name())), + keccak256(EIP712_REVISION), + block.chainid, + address(GHO_ATOKEN) + ) + ); + bytes32 result = GHO_ATOKEN.DOMAIN_SEPARATOR(); + assertEq(result, expected, 'Unexpected domain separator'); + } + + function testNonces() public { + assertEq(GHO_ATOKEN.nonces(ALICE), 0, 'Unexpected non-zero nonce'); + assertEq(GHO_ATOKEN.nonces(BOB), 0, 'Unexpected non-zero nonce'); + assertEq(GHO_ATOKEN.nonces(CHARLES), 0, 'Unexpected non-zero nonce'); } } diff --git a/src/contracts/foundry-test/libraries/GhoActions.sol b/src/contracts/test/TestGhoBase.t.sol similarity index 61% rename from src/contracts/foundry-test/libraries/GhoActions.sol rename to src/contracts/test/TestGhoBase.t.sol index 6e9d424e..fd95ce01 100644 --- a/src/contracts/foundry-test/libraries/GhoActions.sol +++ b/src/contracts/test/TestGhoBase.t.sol @@ -3,17 +3,60 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; -import '../TestEnv.sol'; -import {DebtUtils} from '../libraries/DebtUtils.sol'; +// helpers +import {Constants} from './helpers/Constants.sol'; +import {DebtUtils} from './helpers/DebtUtils.sol'; +import {Events} from './helpers/Events.sol'; + +// generic libs +import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol'; +import {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol'; +import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; import {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol'; import {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -contract GhoActions is Test, TestEnv { +// mocks +import {MockedAclManager} from './mocks/MockedAclManager.sol'; +import {MockedConfigurator} from './mocks/MockedConfigurator.sol'; +import {MockFlashBorrower} from './mocks/MockFlashBorrower.sol'; +import {MockedPool} from './mocks/MockedPool.sol'; +import {MockedProvider} from './mocks/MockedProvider.sol'; +import {TestnetERC20} from '@aave/periphery-v3/contracts/mocks/testnet-helpers/TestnetERC20.sol'; +import {WETH9Mock} from '@aave/periphery-v3/contracts/mocks/WETH9Mock.sol'; + +// interfaces +import {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol'; +import {IERC20} from 'aave-stk-v1-5/src/interfaces/IERC20.sol'; +import {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol'; +import {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol'; +import {IGhoToken} from '../gho/interfaces/IGhoToken.sol'; +import {IGhoVariableDebtTokenTransferHook} from 'aave-stk-v1-5/src/interfaces/IGhoVariableDebtTokenTransferHook.sol'; +import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; +import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol'; +import {IStakedAaveV3} from 'aave-stk-v1-5/src/interfaces/IStakedAaveV3.sol'; + +// non-GHO contracts +import {AdminUpgradeabilityProxy} from '@aave/core-v3/contracts/dependencies/openzeppelin/upgradeability/AdminUpgradeabilityProxy.sol'; +import {ERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/ERC20.sol'; +import {StakedAaveV3} from 'aave-stk-v1-5/src/contracts/StakedAaveV3.sol'; + +// GHO contracts +import {GhoAToken} from '../facilitators/aave/tokens/GhoAToken.sol'; +import {GhoDiscountRateStrategy} from '../facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol'; +import {GhoFlashMinter} from '../facilitators/flashMinter/GhoFlashMinter.sol'; +import {GhoInterestRateStrategy} from '../facilitators/aave/interestStrategy/GhoInterestRateStrategy.sol'; +import {GhoManager} from '../facilitators/aave/misc/GhoManager.sol'; +import {GhoOracle} from '../facilitators/aave/oracle/GhoOracle.sol'; +import {GhoStableDebtToken} from '../facilitators/aave/tokens/GhoStableDebtToken.sol'; +import {GhoToken} from '../gho/GhoToken.sol'; +import {GhoVariableDebtToken} from '../facilitators/aave/tokens/GhoVariableDebtToken.sol'; + +contract TestGhoBase is Test, Constants, Events { using WadRayMath for uint256; using SafeCast for uint256; using PercentageMath for uint256; + // helper for state tracking struct BorrowState { uint256 supplyBeforeAction; uint256 debtSupplyBeforeAction; @@ -27,35 +70,141 @@ contract GhoActions is Test, TestEnv { uint256 discountPercent; } - // Events to listen - event Transfer(address indexed from, address indexed to, uint256 value); - event Mint( - address indexed caller, - address indexed onBehalfOf, - uint256 value, - uint256 balanceIncrease, - uint256 index - ); - event Burn( - address indexed from, - address indexed target, - uint256 value, - uint256 balanceIncrease, - uint256 index - ); - event DiscountPercentUpdated( - address indexed user, - uint256 oldDiscountPercent, - uint256 indexed newDiscountPercent - ); + GhoToken GHO_TOKEN; + TestnetERC20 AAVE_TOKEN; + IStakedAaveV3 STK_TOKEN; + MockedPool POOL; + MockedAclManager ACL_MANAGER; + MockedProvider PROVIDER; + MockedConfigurator CONFIGURATOR; + WETH9Mock WETH; + GhoVariableDebtToken GHO_DEBT_TOKEN; + GhoStableDebtToken GHO_STABLE_DEBT_TOKEN; + GhoAToken GHO_ATOKEN; + GhoFlashMinter GHO_FLASH_MINTER; + GhoDiscountRateStrategy GHO_DISCOUNT_STRATEGY; + MockFlashBorrower FLASH_BORROWER; + GhoOracle GHO_ORACLE; + GhoManager GHO_MANAGER; + + constructor() { + setupGho(); + } + + function test_coverage_ignore() public virtual { + // Intentionally left blank. + // Excludes contract from coverage. + } + + function setupGho() public { + bytes memory empty; + ACL_MANAGER = new MockedAclManager(); + PROVIDER = new MockedProvider(address(ACL_MANAGER)); + POOL = new MockedPool(IPoolAddressesProvider(address(PROVIDER))); + CONFIGURATOR = new MockedConfigurator(IPool(POOL)); + GHO_ORACLE = new GhoOracle(); + GHO_MANAGER = new GhoManager(); + GHO_TOKEN = new GhoToken(); + AAVE_TOKEN = new TestnetERC20('AAVE', 'AAVE', 18, FAUCET); + StakedAaveV3 stkAave = new StakedAaveV3( + IERC20(address(AAVE_TOKEN)), + IERC20(address(AAVE_TOKEN)), + 1, + address(0), + address(0), + 1 + ); + AdminUpgradeabilityProxy stkAaveProxy = new AdminUpgradeabilityProxy( + address(stkAave), + STKAAVE_PROXY_ADMIN, + '' + ); + StakedAaveV3(address(stkAaveProxy)).initialize( + STKAAVE_PROXY_ADMIN, + STKAAVE_PROXY_ADMIN, + STKAAVE_PROXY_ADMIN, + 0, + 1 + ); + STK_TOKEN = IStakedAaveV3(address(stkAaveProxy)); + address ghoToken = address(GHO_TOKEN); + address discountToken = address(STK_TOKEN); + IPool iPool = IPool(address(POOL)); + WETH = new WETH9Mock('Wrapped Ether', 'WETH', FAUCET); + GHO_DEBT_TOKEN = new GhoVariableDebtToken(iPool); + GHO_STABLE_DEBT_TOKEN = new GhoStableDebtToken(iPool); + GHO_ATOKEN = new GhoAToken(iPool); + GHO_DEBT_TOKEN.initialize( + iPool, + ghoToken, + IAaveIncentivesController(address(0)), + 18, + 'Aave Variable Debt GHO', + 'variableDebtGHO', + empty + ); + GHO_STABLE_DEBT_TOKEN.initialize( + iPool, + ghoToken, + IAaveIncentivesController(address(0)), + 18, + 'Aave Stable Debt GHO', + 'stableDebtGHO', + empty + ); + GHO_ATOKEN.initialize( + iPool, + TREASURY, + ghoToken, + IAaveIncentivesController(address(0)), + 18, + 'Aave GHO', + 'aGHO', + empty + ); + GHO_ATOKEN.updateGhoTreasury(TREASURY); + GHO_DEBT_TOKEN.updateDiscountToken(discountToken); + GHO_DISCOUNT_STRATEGY = new GhoDiscountRateStrategy(); + GHO_DEBT_TOKEN.updateDiscountRateStrategy(address(GHO_DISCOUNT_STRATEGY)); + GHO_DEBT_TOKEN.setAToken(address(GHO_ATOKEN)); + GHO_ATOKEN.setVariableDebtToken(address(GHO_DEBT_TOKEN)); + vm.prank(SHORT_EXECUTOR); + STK_TOKEN.setGHODebtToken(IGhoVariableDebtTokenTransferHook(address(GHO_DEBT_TOKEN))); + IGhoToken(ghoToken).addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY); + POOL.setGhoTokens(GHO_DEBT_TOKEN, GHO_ATOKEN); + + GHO_FLASH_MINTER = new GhoFlashMinter( + address(GHO_TOKEN), + TREASURY, + DEFAULT_FLASH_FEE, + address(PROVIDER) + ); + FLASH_BORROWER = new MockFlashBorrower(IERC3156FlashLender(GHO_FLASH_MINTER)); + + IGhoToken(ghoToken).addFacilitator( + address(GHO_FLASH_MINTER), + 'FlashMinter Facilitator', + DEFAULT_CAPACITY + ); + IGhoToken(ghoToken).addFacilitator( + address(FLASH_BORROWER), + 'Gho Flash Borrower', + DEFAULT_CAPACITY + ); + + IGhoToken(ghoToken).addFacilitator(FAUCET, 'Faucet Facilitator', DEFAULT_CAPACITY); + } + + function ghoFaucet(address to, uint256 amount) public { + vm.prank(FAUCET); + GHO_TOKEN.mint(to, amount); + } function borrowAction(address user, uint256 amount) public { borrowActionOnBehalf(user, user, amount); } function borrowActionOnBehalf(address caller, address onBehalfOf, uint256 amount) public { - vm.stopPrank(); - BorrowState memory bs; bs.supplyBeforeAction = GHO_TOKEN.totalSupply(); bs.debtSupplyBeforeAction = GHO_DEBT_TOKEN.totalSupply(); @@ -129,8 +278,6 @@ contract GhoActions is Test, TestEnv { } function repayAction(address user, uint256 amount) public { - vm.stopPrank(); - BorrowState memory bs; bs.supplyBeforeAction = GHO_TOKEN.totalSupply(); bs.debtSupplyBeforeAction = GHO_DEBT_TOKEN.totalSupply(); @@ -225,7 +372,7 @@ contract GhoActions is Test, TestEnv { } function mintAndStakeDiscountToken(address user, uint256 amount) public { - vm.prank(faucet); + vm.prank(FAUCET); AAVE_TOKEN.mint(user, amount); vm.startPrank(user); @@ -235,8 +382,6 @@ contract GhoActions is Test, TestEnv { } function rebalanceDiscountAction(address user) public { - vm.stopPrank(); - BorrowState memory bs; bs.supplyBeforeAction = GHO_TOKEN.totalSupply(); bs.debtSupplyBeforeAction = GHO_DEBT_TOKEN.totalSupply(); diff --git a/src/contracts/test/TestGhoDiscountRateStrategy.t.sol b/src/contracts/test/TestGhoDiscountRateStrategy.t.sol new file mode 100644 index 00000000..066fb340 --- /dev/null +++ b/src/contracts/test/TestGhoDiscountRateStrategy.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +contract TestGhoDiscountRateStrategy is TestGhoBase { + using WadRayMath for uint256; + + uint256 maxDiscountBalance; + + function setUp() public { + // Calculate actual maximum value for discountTokenBalance based on wadMul usage + maxDiscountBalance = + (UINT256_MAX / GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN()) - + WadRayMath.HALF_WAD; + } + + function testDebtBalanceBelowThreshold() public { + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate( + 0, + GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE() + ); + assertEq(result, 0, 'Unexpected discount rate'); + } + + function testDiscountBalanceBelowThreshold() public { + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate( + GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(), + 0 + ); + assertEq(result, 0, 'Unexpected discount rate'); + } + + function testMoreDiscountTokenThanDebtRate() public { + assertGe( + GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(), + 1e18, + 'Unexpected low value for discount token conversion' + ); + assertGe( + GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE(), + GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(), + 'Invalid assumption that discount token balance at least debt token balance' + ); + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate( + GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(), + GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE() + ); + assertEq(result, GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), 'Unexpected discount rate'); + } + + function testLessDiscountTokenThanDebtRate() public { + assertGe( + GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(), + 1e18, + 'Unexpected low value for discount token conversion' + ); + assertGe( + GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE(), + GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(), + 'Invalid assumption that discount token balance at least debt token balance' + ); + + uint256 debtBalance = GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE().wadMul( + GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN() + ) + 1; + + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate( + debtBalance, + GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE() + ); + assertLt(result, GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), 'Unexpected discount rate'); + } + + function testFuzzMinBalance(uint256 debtBalance, uint256 discountTokenBalance) public { + vm.assume( + debtBalance < GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() || + discountTokenBalance < GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE() + ); + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance); + assertEq(result, 0, 'Minimum balance not zero'); + } + + function testFuzzNeverExceedHundredDiscount( + uint256 debtBalance, + uint256 discountTokenBalance + ) public { + vm.assume( + (debtBalance >= GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() || + discountTokenBalance >= GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()) && + discountTokenBalance < maxDiscountBalance + ); + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance); + assertLe(result, 10000, 'Discount rate higher than 100%'); + } + + function testFuzzNeverExceedDiscountRate( + uint256 debtBalance, + uint256 discountTokenBalance + ) public { + vm.assume( + (debtBalance >= GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() || + discountTokenBalance >= GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()) && + discountTokenBalance < maxDiscountBalance + ); + uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance); + assertLe(result, GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), 'Discount rate higher than 100%'); + } +} diff --git a/src/contracts/foundry-test/TestGhoFlashMinter.sol b/src/contracts/test/TestGhoFlashMinter.t.sol similarity index 73% rename from src/contracts/foundry-test/TestGhoFlashMinter.sol rename to src/contracts/test/TestGhoFlashMinter.t.sol index 1d226676..1d8d6836 100644 --- a/src/contracts/foundry-test/TestGhoFlashMinter.sol +++ b/src/contracts/test/TestGhoFlashMinter.t.sol @@ -1,51 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import 'forge-std/Test.sol'; - -import './TestEnv.sol'; -import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; -import {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol'; -import {DebtUtils} from './libraries/DebtUtils.sol'; -import {GhoActions} from './libraries/GhoActions.sol'; - -contract TestGhoFlashMinter is Test, GhoActions { - address public alice; - address public bob; - address public carlos; - uint256 flashMintAmount = 200e18; - - event FlashMint( - address indexed receiver, - address indexed initiator, - address asset, - uint256 indexed amount, - uint256 fee - ); - event FeesDistributedToTreasury( - address indexed ghoTreasury, - address indexed asset, - uint256 amount - ); - event FeeUpdated(uint256 oldFee, uint256 newFee); - event GhoTreasuryUpdated(address indexed oldGhoTreasury, address indexed newGhoTreasury); - - function setUp() public { - alice = users[0]; - bob = users[1]; - carlos = users[2]; - } +import './TestGhoBase.t.sol'; +contract TestGhoFlashMinter is TestGhoBase { function testConstructor() public { GhoFlashMinter flashMinter = new GhoFlashMinter( address(GHO_TOKEN), - treasury, + TREASURY, DEFAULT_FLASH_FEE, address(PROVIDER) ); assertEq(address(flashMinter.GHO_TOKEN()), address(GHO_TOKEN), 'Wrong GHO token address'); assertEq(flashMinter.getFee(), DEFAULT_FLASH_FEE, 'Wrong fee'); - assertEq(flashMinter.getGhoTreasury(), treasury, 'Wrong treasury address'); + assertEq(flashMinter.getGhoTreasury(), TREASURY, 'Wrong TREASURY address'); assertEq( address(flashMinter.ADDRESSES_PROVIDER()), address(PROVIDER), @@ -53,12 +21,17 @@ contract TestGhoFlashMinter is Test, GhoActions { ); } + function testRevertConstructorFeeOutOfRange() public { + vm.expectRevert('FlashMinter: Fee out of range'); + new GhoFlashMinter(address(GHO_TOKEN), TREASURY, 10001, address(PROVIDER)); + } + function testRevertFlashloanNonRecipient() public { vm.expectRevert(); GHO_FLASH_MINTER.flashLoan( IERC3156FlashBorrower(address(this)), address(GHO_TOKEN), - flashMintAmount, + DEFAULT_BORROW_AMOUNT, '' ); } @@ -68,13 +41,13 @@ contract TestGhoFlashMinter is Test, GhoActions { GHO_FLASH_MINTER.flashLoan( IERC3156FlashBorrower(address(FLASH_BORROWER)), address(0), - flashMintAmount, + DEFAULT_BORROW_AMOUNT, '' ); } function testRevertFlashloanMoreThanCapacity() public { - vm.expectRevert(); + vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED'); GHO_FLASH_MINTER.flashLoan( IERC3156FlashBorrower(address(FLASH_BORROWER)), address(GHO_TOKEN), @@ -90,14 +63,14 @@ contract TestGhoFlashMinter is Test, GhoActions { false, 'Flash borrower should not be a whitelisted borrower' ); - vm.expectRevert(); - FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), flashMintAmount); + vm.expectRevert(stdError.arithmeticError); + FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT); } function testRevertFlashloanWrongCallback() public { FLASH_BORROWER.setAllowCallback(false); vm.expectRevert('FlashMinter: Callback failed'); - FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), flashMintAmount); + FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT); } function testRevertUpdateFeeNotPoolAdmin() public { @@ -131,7 +104,7 @@ contract TestGhoFlashMinter is Test, GhoActions { function testRevertFlashfeeNotGho() public { vm.expectRevert('FlashMinter: Unsupported currency'); - GHO_FLASH_MINTER.flashFee(address(0), flashMintAmount); + GHO_FLASH_MINTER.flashFee(address(0), DEFAULT_BORROW_AMOUNT); } // Positives @@ -144,7 +117,7 @@ contract TestGhoFlashMinter is Test, GhoActions { 'Flash borrower should not be a whitelisted borrower' ); - uint256 feeAmount = (DEFAULT_FLASH_FEE * flashMintAmount) / 100e2; + uint256 feeAmount = (DEFAULT_FLASH_FEE * DEFAULT_BORROW_AMOUNT) / 100e2; ghoFaucet(address(FLASH_BORROWER), feeAmount); vm.expectEmit(true, true, true, true, address(GHO_FLASH_MINTER)); @@ -152,14 +125,14 @@ contract TestGhoFlashMinter is Test, GhoActions { address(FLASH_BORROWER), address(FLASH_BORROWER), address(GHO_TOKEN), - flashMintAmount, + DEFAULT_BORROW_AMOUNT, feeAmount ); - FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), flashMintAmount); + FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT); } function testDistributeFeesToTreasury() public { - uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(treasury); + uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(TREASURY); ghoFaucet(address(GHO_FLASH_MINTER), 100e18); assertEq( @@ -169,7 +142,7 @@ contract TestGhoFlashMinter is Test, GhoActions { ); vm.expectEmit(true, true, false, true, address(GHO_FLASH_MINTER)); - emit FeesDistributedToTreasury(treasury, address(GHO_TOKEN), 100e18); + emit FeesDistributedToTreasury(TREASURY, address(GHO_TOKEN), 100e18); GHO_FLASH_MINTER.distributeFeesToTreasury(); assertEq( @@ -178,7 +151,7 @@ contract TestGhoFlashMinter is Test, GhoActions { 'GhoFlashMinter should have no GHO left after fee distribution' ); assertEq( - GHO_TOKEN.balanceOf(treasury), + GHO_TOKEN.balanceOf(TREASURY), treasuryBalanceBefore + 100e18, 'Treasury should have 100 more GHO' ); @@ -193,10 +166,10 @@ contract TestGhoFlashMinter is Test, GhoActions { } function testUpdateGhoTreasury() public { - assertEq(GHO_FLASH_MINTER.getGhoTreasury(), treasury, 'Flashminter non-default treasury'); - assertTrue(treasury != address(this)); + assertEq(GHO_FLASH_MINTER.getGhoTreasury(), TREASURY, 'Flashminter non-default TREASURY'); + assertTrue(TREASURY != address(this)); vm.expectEmit(true, true, false, false, address(GHO_FLASH_MINTER)); - emit GhoTreasuryUpdated(treasury, address(this)); + emit GhoTreasuryUpdated(TREASURY, address(this)); GHO_FLASH_MINTER.updateGhoTreasury(address(this)); } @@ -218,7 +191,7 @@ contract TestGhoFlashMinter is Test, GhoActions { function testWhitelistedFlashFee() public { assertEq( - GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), flashMintAmount), + GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT), 0, 'Flash fee should be 0 for whitelisted borrowers' ); @@ -231,8 +204,8 @@ contract TestGhoFlashMinter is Test, GhoActions { false, 'Flash borrower should not be a whitelisted borrower' ); - uint256 fee = GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), flashMintAmount); - uint256 expectedFee = (DEFAULT_FLASH_FEE * flashMintAmount) / 100e2; + uint256 fee = GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT); + uint256 expectedFee = (DEFAULT_FLASH_FEE * DEFAULT_BORROW_AMOUNT) / 100e2; assertEq(fee, expectedFee, 'Flash fee should be correct'); } diff --git a/src/contracts/test/TestGhoInterestRateStrategy.t.sol b/src/contracts/test/TestGhoInterestRateStrategy.t.sol new file mode 100644 index 00000000..e2680873 --- /dev/null +++ b/src/contracts/test/TestGhoInterestRateStrategy.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +contract TestGhoInterestRateStrategy is TestGhoBase { + function testFuzzVariableRateSetOnly( + uint256 variableBorrowRate, + DataTypes.CalculateInterestRatesParams memory params + ) public { + GhoInterestRateStrategy ghoInterest = new GhoInterestRateStrategy(variableBorrowRate); + (uint256 x, uint256 y, uint256 z) = ghoInterest.calculateInterestRates(params); + assertEq(x, 0, 'Unexpected first return value in interest rate'); + assertEq(y, 0, 'Unexpected second return value in interest rate'); + assertEq(z, variableBorrowRate, 'Unexpected variable borrow rate'); + } +} diff --git a/src/contracts/test/TestGhoManager.t.sol b/src/contracts/test/TestGhoManager.t.sol new file mode 100644 index 00000000..f4116999 --- /dev/null +++ b/src/contracts/test/TestGhoManager.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +contract TestGhoManager is TestGhoBase { + function testUpdateDiscountRateStrategy() public { + vm.expectEmit(true, true, false, true, address(GHO_DEBT_TOKEN)); + emit DiscountRateStrategyUpdated( + address(GHO_DISCOUNT_STRATEGY), + address(GHO_DISCOUNT_STRATEGY) + ); + GHO_MANAGER.updateDiscountRateStrategy(address(GHO_DEBT_TOKEN), address(GHO_DISCOUNT_STRATEGY)); + } + + function testRevertUnauthorizedUpdateDiscountRateStrategy() public { + vm.prank(ALICE); + vm.expectRevert('Ownable: caller is not the owner'); + GHO_MANAGER.updateDiscountRateStrategy(address(GHO_DEBT_TOKEN), address(GHO_DISCOUNT_STRATEGY)); + } + + function testSetReserveInterestRateStrategy() public { + address oldInterestStrategy = POOL.getReserveInterestRateStrategyAddress(address(GHO_TOKEN)); + GhoInterestRateStrategy newInterestStrategy = new GhoInterestRateStrategy(2e25); + vm.expectEmit(true, true, true, true, address(CONFIGURATOR)); + emit ReserveInterestRateStrategyChanged( + address(GHO_TOKEN), + oldInterestStrategy, + address(newInterestStrategy) + ); + GHO_MANAGER.setReserveInterestRateStrategyAddress( + address(CONFIGURATOR), + address(GHO_TOKEN), + address(newInterestStrategy) + ); + } + + function testRevertUnauthorizedSetReserveInterestRateStrategy() public { + GhoInterestRateStrategy newInterestStrategy = new GhoInterestRateStrategy(2e25); + vm.prank(ALICE); + vm.expectRevert('Ownable: caller is not the owner'); + GHO_MANAGER.setReserveInterestRateStrategyAddress( + address(CONFIGURATOR), + address(GHO_TOKEN), + address(newInterestStrategy) + ); + } +} diff --git a/src/contracts/test/TestGhoOracle.t.sol b/src/contracts/test/TestGhoOracle.t.sol new file mode 100644 index 00000000..e0c4bd51 --- /dev/null +++ b/src/contracts/test/TestGhoOracle.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +contract TestGhoOracle is TestGhoBase { + function testLatestAnswer() public { + int256 latest = GHO_ORACLE.latestAnswer(); + assertEq(latest, DEFAULT_GHO_PRICE, 'Wrong GHO price from oracle'); + } + + function testDecimals() public { + uint8 decimals = GHO_ORACLE.decimals(); + assertEq(decimals, DEFAULT_ORACLE_DECIMALS, 'Wrong decimals from oracle'); + } +} diff --git a/src/contracts/test/TestGhoStableDebtToken.t.sol b/src/contracts/test/TestGhoStableDebtToken.t.sol new file mode 100644 index 00000000..9b7d738e --- /dev/null +++ b/src/contracts/test/TestGhoStableDebtToken.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +contract TestGhoStableDebtToken is TestGhoBase { + function testConstructor() public { + GhoStableDebtToken debtToken = new GhoStableDebtToken(IPool(address(POOL))); + assertEq(debtToken.name(), 'GHO_STABLE_DEBT_TOKEN_IMPL', 'Wrong default ERC20 name'); + assertEq(debtToken.symbol(), 'GHO_STABLE_DEBT_TOKEN_IMPL', 'Wrong default ERC20 symbol'); + assertEq(debtToken.decimals(), 0, 'Wrong default ERC20 decimals'); + } + + function testInitialize() public { + GhoStableDebtToken debtToken = new GhoStableDebtToken(IPool(address(POOL))); + string memory tokenName = 'Aave Stable Debt GHO'; + string memory tokenSymbol = 'stableDebtGHO'; + bytes memory empty; + debtToken.initialize( + IPool(address(POOL)), + address(GHO_TOKEN), + IAaveIncentivesController(address(0)), + 18, + tokenName, + tokenSymbol, + empty + ); + + assertEq(debtToken.name(), tokenName, 'Wrong initialized name'); + assertEq(debtToken.symbol(), tokenSymbol, 'Wrong initialized symbol'); + assertEq(debtToken.decimals(), 18, 'Wrong ERC20 decimals'); + } + + function testInitializePoolRevert() public { + string memory tokenName = 'Aave Stable Debt GHO'; + string memory tokenSymbol = 'stableDebtGHO'; + bytes memory empty; + + GhoStableDebtToken debtToken = new GhoStableDebtToken(IPool(address(POOL))); + vm.expectRevert(bytes(Errors.POOL_ADDRESSES_DO_NOT_MATCH)); + debtToken.initialize( + IPool(address(0)), + address(GHO_TOKEN), + IAaveIncentivesController(address(0)), + 18, + tokenName, + tokenSymbol, + empty + ); + } + + function testReInitRevert() public { + string memory tokenName = 'Aave Stable Debt GHO'; + string memory tokenSymbol = 'stableDebtGHO'; + bytes memory empty; + + vm.expectRevert(bytes('Contract instance has already been initialized')); + GHO_STABLE_DEBT_TOKEN.initialize( + IPool(address(POOL)), + address(GHO_TOKEN), + IAaveIncentivesController(address(0)), + 18, + tokenName, + tokenSymbol, + empty + ); + } + + function testUnderlying() public { + assertEq( + GHO_STABLE_DEBT_TOKEN.UNDERLYING_ASSET_ADDRESS(), + address(GHO_TOKEN), + 'Underlying should match token' + ); + } + + function testTransferRevert() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.transfer(CHARLES, 1); + } + + function testTransferFromRevert() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.transferFrom(ALICE, CHARLES, 1); + } + + function testApproveRevert() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.approve(CHARLES, 1); + } + + function testIncreaseAllowanceRevert() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.increaseAllowance(CHARLES, 1); + } + + function testDecreaseAllowanceRevert() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.decreaseAllowance(CHARLES, 1); + } + + function testAllowanceRevert() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.allowance(CHARLES, ALICE); + } + + function testPrincipalBalanceOfZero() public { + assertEq(GHO_STABLE_DEBT_TOKEN.principalBalanceOf(ALICE), 0, 'Unexpected principal balance'); + assertEq(GHO_STABLE_DEBT_TOKEN.principalBalanceOf(BOB), 0, 'Unexpected principal balance'); + assertEq(GHO_STABLE_DEBT_TOKEN.principalBalanceOf(CHARLES), 0, 'Unexpected principal balance'); + } + + function testMintRevert() public { + vm.prank(address(POOL)); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.mint(ALICE, ALICE, 0, 0); + } + + function testUnauthorizedMint() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL)); + GHO_STABLE_DEBT_TOKEN.mint(ALICE, ALICE, 0, 0); + } + + function testBurnRevert() public { + vm.prank(address(POOL)); + vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); + GHO_STABLE_DEBT_TOKEN.burn(ALICE, 0); + } + + function testUnauthorizedBurn() public { + vm.startPrank(ALICE); + vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL)); + GHO_STABLE_DEBT_TOKEN.burn(ALICE, 0); + } + + function testGetAverageStableRateZero() public { + uint256 result = GHO_STABLE_DEBT_TOKEN.getAverageStableRate(); + assertEq(result, 0, 'Unexpected stable rate'); + } + + function testGetUserLastUpdatedZero() public { + assertEq(GHO_STABLE_DEBT_TOKEN.getUserLastUpdated(ALICE), 0, 'Unexpected stable rate'); + assertEq(GHO_STABLE_DEBT_TOKEN.getUserLastUpdated(BOB), 0, 'Unexpected stable rate'); + assertEq(GHO_STABLE_DEBT_TOKEN.getUserLastUpdated(CHARLES), 0, 'Unexpected stable rate'); + } + + function testGetUserStableRateZero() public { + assertEq(GHO_STABLE_DEBT_TOKEN.getUserStableRate(ALICE), 0, 'Unexpected stable rate'); + assertEq(GHO_STABLE_DEBT_TOKEN.getUserStableRate(BOB), 0, 'Unexpected stable rate'); + assertEq(GHO_STABLE_DEBT_TOKEN.getUserStableRate(CHARLES), 0, 'Unexpected stable rate'); + } + + function testGetUserBalanceZero() public { + assertEq(GHO_STABLE_DEBT_TOKEN.balanceOf(ALICE), 0, 'Unexpected stable rate'); + assertEq(GHO_STABLE_DEBT_TOKEN.balanceOf(BOB), 0, 'Unexpected stable rate'); + assertEq(GHO_STABLE_DEBT_TOKEN.balanceOf(CHARLES), 0, 'Unexpected stable rate'); + } + + function testGetSupplyDataZero() public { + ( + uint256 totalSupply, + uint256 calcTotalSupply, + uint256 avgRate, + uint40 timestamp + ) = GHO_STABLE_DEBT_TOKEN.getSupplyData(); + assertEq(totalSupply, 0, 'Unexpected total supply'); + assertEq(calcTotalSupply, 0, 'Unexpected total supply'); + assertEq(avgRate, 0, 'Unexpected average rate'); + assertEq(timestamp, 0, 'Unexpected timestamp'); + } + + function testGetTotalSupplyAvgRateZero() public { + (uint256 calcTotalSupply, uint256 avgRate) = GHO_STABLE_DEBT_TOKEN.getTotalSupplyAndAvgRate(); + assertEq(calcTotalSupply, 0, 'Unexpected total supply'); + assertEq(avgRate, 0, 'Unexpected average rate'); + } + + function testTotalSupplyZero() public { + uint256 result = GHO_STABLE_DEBT_TOKEN.totalSupply(); + assertEq(result, 0, 'Unexpected total supply'); + } + + function testTotalSupplyLastUpdatedZero() public { + uint40 result = GHO_STABLE_DEBT_TOKEN.getTotalSupplyLastUpdated(); + assertEq(result, 0, 'Unexpected timestamp'); + } +} diff --git a/src/contracts/foundry-test/TestGhoToken.sol b/src/contracts/test/TestGhoToken.t.sol similarity index 51% rename from src/contracts/foundry-test/TestGhoToken.sol rename to src/contracts/test/TestGhoToken.t.sol index 236db629..2873a167 100644 --- a/src/contracts/foundry-test/TestGhoToken.sol +++ b/src/contracts/test/TestGhoToken.t.sol @@ -1,46 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import 'forge-std/Test.sol'; - -import './TestEnv.sol'; -import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; -import {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol'; -import {DebtUtils} from './libraries/DebtUtils.sol'; -import {GhoActions} from './libraries/GhoActions.sol'; - -contract TestGhoToken is Test, GhoActions { - address public alice; - address public bob; - address public carlos; - uint256 mintAmount = 200e18; - - event FacilitatorAdded( - address indexed facilitatorAddress, - bytes32 indexed label, - uint256 bucketCapacity - ); - - event FacilitatorRemoved(address indexed facilitatorAddress); - - event FacilitatorBucketCapacityUpdated( - address indexed facilitatorAddress, - uint256 oldCapacity, - uint256 newCapacity - ); - - event FacilitatorBucketLevelUpdated( - address indexed facilitatorAddress, - uint256 oldLevel, - uint256 newLevel - ); - - function setUp() public { - alice = users[0]; - bob = users[1]; - carlos = users[2]; - } +import './TestGhoBase.t.sol'; +contract TestGhoToken is TestGhoBase { function testConstructor() public { GhoToken ghoToken = new GhoToken(); assertEq(ghoToken.name(), 'Gho Token', 'Wrong default ERC20 name'); @@ -51,13 +14,13 @@ contract TestGhoToken is Test, GhoActions { function testGetFacilitatorData() public { IGhoToken.Facilitator memory data = GHO_TOKEN.getFacilitator(address(GHO_ATOKEN)); - assertEq(data.label, 'Gho Atoken Market', 'Unexpected facilitator label'); + assertEq(data.label, 'Aave V3 Pool', 'Unexpected facilitator label'); assertEq(data.bucketCapacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity'); assertEq(data.bucketLevel, 0, 'Unexpected bucket level'); } function testGetNonFacilitatorData() public { - IGhoToken.Facilitator memory data = GHO_TOKEN.getFacilitator(alice); + IGhoToken.Facilitator memory data = GHO_TOKEN.getFacilitator(ALICE); assertEq(data.label, '', 'Unexpected facilitator label'); assertEq(data.bucketCapacity, 0, 'Unexpected bucket capacity'); assertEq(data.bucketLevel, 0, 'Unexpected bucket level'); @@ -70,7 +33,7 @@ contract TestGhoToken is Test, GhoActions { } function testGetNonFacilitatorBucket() public { - (uint256 capacity, uint256 level) = GHO_TOKEN.getFacilitatorBucket(alice); + (uint256 capacity, uint256 level) = GHO_TOKEN.getFacilitatorBucket(ALICE); assertEq(capacity, 0, 'Unexpected bucket capacity'); assertEq(level, 0, 'Unexpected bucket level'); } @@ -89,28 +52,28 @@ contract TestGhoToken is Test, GhoActions { address(FLASH_BORROWER), 'Unexpected address for mock facilitator 3' ); - assertEq(facilitatorList[3], faucet, 'Unexpected address for mock facilitator 4'); + assertEq(facilitatorList[3], FAUCET, 'Unexpected address for mock facilitator 4'); } function testAddFacilitator() public { vm.expectEmit(true, true, false, true, address(GHO_TOKEN)); - emit FacilitatorAdded(alice, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY); - GHO_TOKEN.addFacilitator(alice, 'Alice', DEFAULT_CAPACITY); + emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY); + GHO_TOKEN.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY); } function testRevertAddExistingFacilitator() public { vm.expectRevert('FACILITATOR_ALREADY_EXISTS'); - GHO_TOKEN.addFacilitator(address(GHO_ATOKEN), 'Gho Atoken Market', DEFAULT_CAPACITY); + GHO_TOKEN.addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY); } function testRevertAddFacilitatorNoLabel() public { vm.expectRevert('INVALID_LABEL'); - GHO_TOKEN.addFacilitator(alice, '', DEFAULT_CAPACITY); + GHO_TOKEN.addFacilitator(ALICE, '', DEFAULT_CAPACITY); } function testRevertSetBucketCapacityNonFacilitator() public { vm.expectRevert('FACILITATOR_DOES_NOT_EXIST'); - GHO_TOKEN.setFacilitatorBucketCapacity(alice, DEFAULT_CAPACITY); + GHO_TOKEN.setFacilitatorBucketCapacity(ALICE, DEFAULT_CAPACITY); } function testSetNewBucketCapacity() public { @@ -121,13 +84,13 @@ contract TestGhoToken is Test, GhoActions { function testRevertRemoveNonFacilitator() public { vm.expectRevert('FACILITATOR_DOES_NOT_EXIST'); - GHO_TOKEN.removeFacilitator(alice); + GHO_TOKEN.removeFacilitator(ALICE); } - function testRevertRemoveFacilitatorNonzeroBucket() public { - ghoFaucet(alice, 1); + function testRevertRemoveFacilitatorNonZeroBucket() public { + ghoFaucet(ALICE, 1); vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO'); - GHO_TOKEN.removeFacilitator(faucet); + GHO_TOKEN.removeFacilitator(FAUCET); } function testRemoveFacilitator() public { @@ -137,24 +100,24 @@ contract TestGhoToken is Test, GhoActions { } function testRevertMintBadFacilitator() public { - vm.prank(alice); + vm.prank(ALICE); vm.expectRevert('INVALID_FACILITATOR'); - GHO_TOKEN.mint(alice, mintAmount); + GHO_TOKEN.mint(ALICE, DEFAULT_BORROW_AMOUNT); } function testRevertMintExceedCapacity() public { vm.prank(address(GHO_ATOKEN)); vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED'); - GHO_TOKEN.mint(alice, DEFAULT_CAPACITY + 1); + GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY + 1); } function testMint() public { vm.prank(address(GHO_ATOKEN)); vm.expectEmit(true, true, false, true, address(GHO_TOKEN)); - emit Transfer(address(0), alice, DEFAULT_CAPACITY); + emit Transfer(address(0), ALICE, DEFAULT_CAPACITY); vm.expectEmit(true, false, false, true, address(GHO_TOKEN)); emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY); - GHO_TOKEN.mint(alice, DEFAULT_CAPACITY); + GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY); } function testRevertZeroBurn() public { @@ -170,20 +133,20 @@ contract TestGhoToken is Test, GhoActions { GHO_TOKEN.mint(address(GHO_ATOKEN), DEFAULT_CAPACITY); vm.prank(address(GHO_ATOKEN)); - vm.expectRevert(); + vm.expectRevert(stdError.arithmeticError); GHO_TOKEN.burn(DEFAULT_CAPACITY + 1); } function testRevertBurnOthersTokens() public { vm.prank(address(GHO_ATOKEN)); vm.expectEmit(true, true, false, true, address(GHO_TOKEN)); - emit Transfer(address(0), alice, DEFAULT_CAPACITY); + emit Transfer(address(0), ALICE, DEFAULT_CAPACITY); vm.expectEmit(true, false, false, true, address(GHO_TOKEN)); emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY); - GHO_TOKEN.mint(alice, DEFAULT_CAPACITY); + GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY); vm.prank(address(GHO_ATOKEN)); - vm.expectRevert(); + vm.expectRevert(stdError.arithmeticError); GHO_TOKEN.burn(DEFAULT_CAPACITY); } @@ -200,8 +163,110 @@ contract TestGhoToken is Test, GhoActions { emit FacilitatorBucketLevelUpdated( address(GHO_ATOKEN), DEFAULT_CAPACITY, - DEFAULT_CAPACITY - mintAmount + DEFAULT_CAPACITY - DEFAULT_BORROW_AMOUNT + ); + GHO_TOKEN.burn(DEFAULT_BORROW_AMOUNT); + } + + function testOffboardFacilitator() public { + // Onboard facilitator + vm.expectEmit(true, true, false, true, address(GHO_TOKEN)); + emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY); + GHO_TOKEN.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY); + + // Facilitator mints half of its capacity + vm.prank(ALICE); + GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY / 2); + (uint256 bucketCapacity, uint256 bucketLevel) = GHO_TOKEN.getFacilitatorBucket(ALICE); + assertEq(bucketCapacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity of facilitator'); + assertEq(bucketLevel, DEFAULT_CAPACITY / 2, 'Unexpected bucket level of facilitator'); + + // Facilitator cannot be removed + vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO'); + GHO_TOKEN.removeFacilitator(ALICE); + + // Facilitator Bucket Capacity set to 0 + GHO_TOKEN.setFacilitatorBucketCapacity(ALICE, 0); + + // Facilitator cannot mint more and is expected to burn remaining level + vm.prank(ALICE); + vm.expectRevert('INVALID_FACILITATOR'); + GHO_TOKEN.mint(ALICE, 1); + + vm.prank(ALICE); + GHO_TOKEN.burn(bucketLevel); + + // Facilitator can be removed with 0 bucket level + vm.expectEmit(true, false, false, true, address(GHO_TOKEN)); + emit FacilitatorRemoved(address(ALICE)); + GHO_TOKEN.removeFacilitator(address(ALICE)); + } + + function testDomainSeparator() public { + bytes32 EIP712_DOMAIN = keccak256( + 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' + ); + bytes memory EIP712_REVISION = bytes('1'); + bytes32 expected = keccak256( + abi.encode( + EIP712_DOMAIN, + keccak256(bytes(GHO_TOKEN.name())), + keccak256(EIP712_REVISION), + block.chainid, + address(GHO_TOKEN) + ) + ); + bytes32 result = GHO_TOKEN.DOMAIN_SEPARATOR(); + assertEq(result, expected, 'Unexpected domain separator'); + } + + function testDomainSeparatorNewChain() public { + vm.chainId(31338); + bytes32 EIP712_DOMAIN = keccak256( + 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' + ); + bytes memory EIP712_REVISION = bytes('1'); + bytes32 expected = keccak256( + abi.encode( + EIP712_DOMAIN, + keccak256(bytes(GHO_TOKEN.name())), + keccak256(EIP712_REVISION), + block.chainid, + address(GHO_TOKEN) + ) ); - GHO_TOKEN.burn(mintAmount); + bytes32 result = GHO_TOKEN.DOMAIN_SEPARATOR(); + assertEq(result, expected, 'Unexpected domain separator'); + } + + function testPermitAndVerifyNonce() public { + address david = vm.addr(31338); + ghoFaucet(david, 1e18); + bytes32 PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + bytes32 innerHash = keccak256(abi.encode(PERMIT_TYPEHASH, david, BOB, 1e18, 0, 1 hours)); + bytes32 outerHash = keccak256( + abi.encodePacked('\x19\x01', GHO_TOKEN.DOMAIN_SEPARATOR(), innerHash) + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(31338, outerHash); + GHO_TOKEN.permit(david, BOB, 1e18, 1 hours, v, r, s); + + assertEq(GHO_TOKEN.allowance(david, BOB), 1e18, 'Unexpected allowance'); + assertEq(GHO_TOKEN.nonces(david), 1, 'Unexpected nonce'); + } + + function testRevertPermitInvalidSignature() public { + bytes32 PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + bytes32 innerHash = keccak256(abi.encode(PERMIT_TYPEHASH, ALICE, BOB, 1e18, 0, 1 hours)); + bytes32 outerHash = keccak256( + abi.encodePacked('\x19\x01', GHO_TOKEN.DOMAIN_SEPARATOR(), innerHash) + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(31338, outerHash); + vm.expectRevert(bytes('INVALID_SIGNER')); + GHO_TOKEN.permit(ALICE, BOB, 1e18, 1 hours, v, r, s); + } + + function testRevertPermitInvalidDeadline() public { + vm.expectRevert(bytes('PERMIT_DEADLINE_EXPIRED')); + GHO_TOKEN.permit(ALICE, BOB, 1e18, block.timestamp - 1, 0, 0, 0); } } diff --git a/src/contracts/foundry-test/TestGhoVariableDebtToken.t.sol b/src/contracts/test/TestGhoVariableDebtToken.t.sol similarity index 57% rename from src/contracts/foundry-test/TestGhoVariableDebtToken.t.sol rename to src/contracts/test/TestGhoVariableDebtToken.t.sol index 67b99c50..60a4868e 100644 --- a/src/contracts/foundry-test/TestGhoVariableDebtToken.t.sol +++ b/src/contracts/test/TestGhoVariableDebtToken.t.sol @@ -1,27 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import 'forge-std/Test.sol'; - -import './TestEnv.sol'; -import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; -import {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol'; -import {DebtUtils} from './libraries/DebtUtils.sol'; -import {GhoActions} from './libraries/GhoActions.sol'; - -contract TestGhoVariableDebtToken is Test, GhoActions { - address public alice; - address public bob; - address public carlos; - uint256 borrowAmount = 200e18; - - event ATokenSet(address indexed); +import './TestGhoBase.t.sol'; +contract TestGhoVariableDebtToken is TestGhoBase { function setUp() public { - alice = users[0]; - bob = users[1]; - carlos = users[2]; - mintAndStakeDiscountToken(bob, 10_000e18); + mintAndStakeDiscountToken(BOB, 10_000e18); } function testConstructor() public { @@ -33,8 +17,8 @@ contract TestGhoVariableDebtToken is Test, GhoActions { function testInitialize() public { GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL))); - string memory tokenName = 'GHO Variable Debt'; - string memory tokenSymbol = 'GhoVarDebt'; + string memory tokenName = 'Aave Variable Debt GHO'; + string memory tokenSymbol = 'variableDebtGHO'; bytes memory empty; debtToken.initialize( IPool(address(POOL)), @@ -52,8 +36,8 @@ contract TestGhoVariableDebtToken is Test, GhoActions { } function testInitializePoolRevert() public { - string memory tokenName = 'GHO Variable Debt'; - string memory tokenSymbol = 'GhoVarDebt'; + string memory tokenName = 'Aave Variable Debt GHO'; + string memory tokenSymbol = 'variableDebtGHO'; bytes memory empty; GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL))); @@ -70,8 +54,8 @@ contract TestGhoVariableDebtToken is Test, GhoActions { } function testReInitRevert() public { - string memory tokenName = 'GHO Variable Debt'; - string memory tokenSymbol = 'GhoVarDebt'; + string memory tokenName = 'Aave Variable Debt GHO'; + string memory tokenSymbol = 'variableDebtGHO'; bytes memory empty; vm.expectRevert(bytes('Contract instance has already been initialized')); @@ -87,47 +71,47 @@ contract TestGhoVariableDebtToken is Test, GhoActions { } function testBorrowFixed() public { - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); } function testBorrowOnBehalf() public { - vm.prank(bob); - GHO_DEBT_TOKEN.approveDelegation(alice, borrowAmount); + vm.prank(BOB); + GHO_DEBT_TOKEN.approveDelegation(ALICE, DEFAULT_BORROW_AMOUNT); - borrowActionOnBehalf(alice, bob, borrowAmount); + borrowActionOnBehalf(ALICE, BOB, DEFAULT_BORROW_AMOUNT); } function testBorrowFuzz(uint256 fuzzAmount) public { vm.assume(fuzzAmount < 100000000000000000000000001); vm.assume(fuzzAmount > 0); - borrowAction(alice, fuzzAmount); + borrowAction(ALICE, fuzzAmount); assertEq( - GHO_DEBT_TOKEN.getBalanceFromInterest(alice), + GHO_DEBT_TOKEN.getBalanceFromInterest(ALICE), 0, 'Accumulated interest should be zero' ); } function testBorrowFixedWithDiscount() public { - borrowAction(bob, borrowAmount); + borrowAction(BOB, DEFAULT_BORROW_AMOUNT); } function testMultipleBorrowFixedWithDiscount() public { - borrowAction(bob, borrowAmount); + borrowAction(BOB, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 100000000); - borrowAction(bob, 1e16); + borrowAction(BOB, 1e16); } function testBorrowMultiple() public { for (uint x; x < 100; ++x) { - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); } } function testBorrowMultipleWithDiscount() public { for (uint x; x < 100; ++x) { - borrowAction(bob, borrowAmount); + borrowAction(BOB, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); } } @@ -137,7 +121,7 @@ contract TestGhoVariableDebtToken is Test, GhoActions { vm.assume(fuzzAmount > 0); for (uint x; x < 10; ++x) { - borrowAction(alice, fuzzAmount); + borrowAction(ALICE, fuzzAmount); vm.warp(block.timestamp + 2628000); } } @@ -146,67 +130,67 @@ contract TestGhoVariableDebtToken is Test, GhoActions { uint256 partialRepayAmount = 1e7; // Perform borrow - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); // Perform repayment - repayAction(alice, partialRepayAmount); + repayAction(ALICE, partialRepayAmount); } function testPartialRepay() public { uint256 partialRepayAmount = 50e18; // Perform borrow - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); // Perform repayment - repayAction(alice, partialRepayAmount); + repayAction(ALICE, partialRepayAmount); } function testPartialRepayDiscount() public { uint256 partialRepayAmount = 50e18; // Perform borrow - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); - repayAction(alice, partialRepayAmount); + repayAction(ALICE, partialRepayAmount); - mintAndStakeDiscountToken(alice, 10_000e18); + mintAndStakeDiscountToken(ALICE, 10_000e18); vm.warp(block.timestamp + 2628000); - repayAction(alice, partialRepayAmount); + repayAction(ALICE, partialRepayAmount); } function testFullRepay() public { - vm.prank(alice); + vm.prank(ALICE); // Perform borrow - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); - uint256 allDebt = GHO_DEBT_TOKEN.balanceOf(alice); + uint256 allDebt = GHO_DEBT_TOKEN.balanceOf(ALICE); - ghoFaucet(alice, 1e18); + ghoFaucet(ALICE, 1e18); - repayAction(alice, allDebt); + repayAction(ALICE, allDebt); } function testMultipleMinorRepay() public { uint256 partialRepayAmount = 1e7; // Perform borrow - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); for (uint x; x < 100; ++x) { - repayAction(alice, partialRepayAmount); + repayAction(ALICE, partialRepayAmount); vm.warp(block.timestamp + 2628000); } } @@ -215,22 +199,22 @@ contract TestGhoVariableDebtToken is Test, GhoActions { uint256 partialRepayAmount = 50e18; // Perform borrow - borrowAction(alice, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 2628000); for (uint x; x < 4; ++x) { - repayAction(alice, partialRepayAmount); + repayAction(ALICE, partialRepayAmount); vm.warp(block.timestamp + 2628000); } } function testDiscountRebalance() public { - mintAndStakeDiscountToken(alice, 10e18); - borrowAction(alice, 1000e18); + mintAndStakeDiscountToken(ALICE, 10e18); + borrowAction(ALICE, 1000e18); vm.warp(block.timestamp + 10000000000); - rebalanceDiscountAction(alice); + rebalanceDiscountAction(ALICE); } function testUnderlying() public { @@ -250,90 +234,102 @@ contract TestGhoVariableDebtToken is Test, GhoActions { } function testBalanceOfSameIndex() public { - borrowAction(alice, borrowAmount); - uint256 balanceOne = GHO_DEBT_TOKEN.balanceOf(alice); - uint256 balanceTwo = GHO_DEBT_TOKEN.balanceOf(alice); - assertEq(balanceOne, balanceTwo, 'Balance should be equal if index doesnt increase'); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); + uint256 balanceOne = GHO_DEBT_TOKEN.balanceOf(ALICE); + uint256 balanceTwo = GHO_DEBT_TOKEN.balanceOf(ALICE); + assertEq(balanceOne, balanceTwo, 'Balance should be equal if index does not increase'); } function testTransferRevert() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - GHO_DEBT_TOKEN.transfer(carlos, 1); + GHO_DEBT_TOKEN.transfer(CHARLES, 1); } function testTransferFromRevert() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - GHO_DEBT_TOKEN.transferFrom(alice, carlos, 1); + GHO_DEBT_TOKEN.transferFrom(ALICE, CHARLES, 1); } function testApproveRevert() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - GHO_DEBT_TOKEN.approve(carlos, 1); + GHO_DEBT_TOKEN.approve(CHARLES, 1); } function testIncreaseAllowanceRevert() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - GHO_DEBT_TOKEN.increaseAllowance(carlos, 1); + GHO_DEBT_TOKEN.increaseAllowance(CHARLES, 1); } function testDecreaseAllowanceRevert() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - GHO_DEBT_TOKEN.decreaseAllowance(carlos, 1); + GHO_DEBT_TOKEN.decreaseAllowance(CHARLES, 1); } function testAllowanceRevert() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED)); - GHO_DEBT_TOKEN.allowance(carlos, alice); + GHO_DEBT_TOKEN.allowance(CHARLES, ALICE); } - function testUpdateDiscountByOther() public { - vm.startPrank(alice); + function testUnauthorizedUpdateDiscount() public { + vm.startPrank(ALICE); vm.expectRevert(bytes('CALLER_NOT_DISCOUNT_TOKEN')); - GHO_DEBT_TOKEN.updateDiscountDistribution(alice, alice, 0, 0, 0); + GHO_DEBT_TOKEN.updateDiscountDistribution(ALICE, ALICE, 0, 0, 0); } function testUpdateDiscount() public { - borrowAction(alice, borrowAmount); - borrowAction(bob, borrowAmount); + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); + borrowAction(BOB, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 1000); vm.prank(address(STK_TOKEN)); - GHO_DEBT_TOKEN.updateDiscountDistribution(alice, bob, 0, 0, 0); + GHO_DEBT_TOKEN.updateDiscountDistribution(ALICE, BOB, 0, 0, 0); } - function testDecreaseBalanceByOther() public { - vm.startPrank(alice); + function testUnauthorizedDecreaseBalance() public { + vm.startPrank(ALICE); vm.expectRevert(bytes('CALLER_NOT_A_TOKEN')); - GHO_DEBT_TOKEN.decreaseBalanceFromInterest(alice, 1); + GHO_DEBT_TOKEN.decreaseBalanceFromInterest(ALICE, 1); } - function testMintByOther() public { - vm.startPrank(alice); + function testUnauthorizedMint() public { + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL)); - GHO_DEBT_TOKEN.mint(alice, alice, 0, 0); + GHO_DEBT_TOKEN.mint(ALICE, ALICE, 0, 0); } - function testBurnByOther() public { - vm.startPrank(alice); + function testUnauthorizedBurn() public { + vm.startPrank(ALICE); vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL)); - GHO_DEBT_TOKEN.burn(alice, 0, 0); + GHO_DEBT_TOKEN.burn(ALICE, 0, 0); + } + + function testRevertMintZero() public { + vm.prank(address(POOL)); + vm.expectRevert(bytes(Errors.INVALID_MINT_AMOUNT)); + GHO_DEBT_TOKEN.mint(ALICE, ALICE, 0, 1); + } + + function testRevertBurnZero() public { + vm.prank(address(POOL)); + vm.expectRevert(bytes(Errors.INVALID_BURN_AMOUNT)); + GHO_DEBT_TOKEN.burn(ALICE, 0, 1); } - function testSetATokenByOther() public { + function testUnauthorizedSetAToken() public { GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL))); - vm.startPrank(alice); + vm.startPrank(ALICE); ACL_MANAGER.setState(false); vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN)); - debtToken.setAToken(alice); + debtToken.setAToken(ALICE); } function testSetAToken() public { @@ -346,63 +342,78 @@ contract TestGhoVariableDebtToken is Test, GhoActions { } function testUpdateAToken() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes('ATOKEN_ALREADY_SET')); - GHO_DEBT_TOKEN.setAToken(alice); + GHO_DEBT_TOKEN.setAToken(ALICE); } function testZeroAToken() public { GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL))); - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID')); debtToken.setAToken(address(0)); } - function testUpdateDiscountRateStrategyByOther() public { - vm.startPrank(alice); + function testUnauthorizedUpdateDiscountRateStrategy() public { + vm.startPrank(ALICE); ACL_MANAGER.setState(false); vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN)); - GHO_DEBT_TOKEN.updateDiscountRateStrategy(alice); + GHO_DEBT_TOKEN.updateDiscountRateStrategy(ALICE); } - function testUpdateDiscountTokenByOther() public { - vm.startPrank(alice); + function testUnauthorizedUpdateDiscountToken() public { + vm.startPrank(ALICE); ACL_MANAGER.setState(false); vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN)); - GHO_DEBT_TOKEN.updateDiscountToken(alice); + GHO_DEBT_TOKEN.updateDiscountToken(ALICE); } function testUpdateDiscountTokenToZero() public { - vm.startPrank(alice); + vm.startPrank(ALICE); vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID')); GHO_DEBT_TOKEN.updateDiscountToken(address(0)); } function testUpdateDiscountStrategy() public { - vm.startPrank(alice); - GHO_DEBT_TOKEN.updateDiscountRateStrategy(carlos); + vm.startPrank(ALICE); + GHO_DEBT_TOKEN.updateDiscountRateStrategy(CHARLES); assertEq( GHO_DEBT_TOKEN.getDiscountRateStrategy(), - carlos, + CHARLES, 'Discount Rate Strategy should be updated' ); } + function testRevertUpdateDiscountStrategyZero() public { + vm.startPrank(address(POOL)); + vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID')); + GHO_DEBT_TOKEN.updateDiscountRateStrategy(address(0)); + } + function testUpdateDiscountToken() public { - vm.startPrank(alice); - GHO_DEBT_TOKEN.updateDiscountToken(carlos); - assertEq(GHO_DEBT_TOKEN.getDiscountToken(), carlos, 'Discount token should be updated'); + vm.startPrank(ALICE); + GHO_DEBT_TOKEN.updateDiscountToken(CHARLES); + assertEq(GHO_DEBT_TOKEN.getDiscountToken(), CHARLES, 'Discount token should be updated'); } function testUpdateDiscountTokenWithBorrow() public { - borrowAction(bob, borrowAmount); + borrowAction(BOB, DEFAULT_BORROW_AMOUNT); vm.warp(block.timestamp + 10000); - vm.startPrank(alice); - GHO_DEBT_TOKEN.updateDiscountToken(bob); - assertEq(GHO_DEBT_TOKEN.getDiscountToken(), bob, 'Discount token should be updated'); + vm.startPrank(ALICE); + GHO_DEBT_TOKEN.updateDiscountToken(BOB); + assertEq(GHO_DEBT_TOKEN.getDiscountToken(), BOB, 'Discount token should be updated'); + } + + function testScaledUserBalanceAndSupply() public { + borrowAction(ALICE, DEFAULT_BORROW_AMOUNT); + borrowAction(BOB, DEFAULT_BORROW_AMOUNT); + (uint256 userScaledBalance, uint256 totalScaledSupply) = GHO_DEBT_TOKEN + .getScaledUserBalanceAndSupply(ALICE); + assertEq(userScaledBalance, DEFAULT_BORROW_AMOUNT, 'Unexpected user balance'); + assertEq(totalScaledSupply, DEFAULT_BORROW_AMOUNT * 2, 'Unexpected total supply'); } } diff --git a/src/contracts/test/TestUiGhoDataProvider.t.sol b/src/contracts/test/TestUiGhoDataProvider.t.sol new file mode 100644 index 00000000..97015db9 --- /dev/null +++ b/src/contracts/test/TestUiGhoDataProvider.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +import {UiGhoDataProvider, IUiGhoDataProvider} from '../facilitators/aave/misc/UiGhoDataProvider.sol'; + +contract TestUiGhoDataProvider is TestGhoBase { + UiGhoDataProvider dataProvider; + + function setUp() public { + dataProvider = new UiGhoDataProvider(IPool(POOL), GHO_TOKEN); + } + + function testGhoReserveData() public { + DataTypes.ReserveData memory baseData = POOL.getReserveData(address(GHO_TOKEN)); + (uint256 bucketCapacity, uint256 bucketLevel) = GHO_TOKEN.getFacilitatorBucket( + baseData.aTokenAddress + ); + IUiGhoDataProvider.GhoReserveData memory result = dataProvider.getGhoReserveData(); + assertEq( + result.ghoBaseVariableBorrowRate, + baseData.currentVariableBorrowRate, + 'Unexpected variable borrow rate' + ); + assertEq( + result.ghoDiscountedPerToken, + GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(), + 'Unexpected discount per token' + ); + assertEq( + result.ghoDiscountRate, + GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), + 'Unexpected discount rate' + ); + assertEq( + result.ghoMinDebtTokenBalanceForDiscount, + GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE(), + 'Unexpected minimum discount token balance' + ); + assertEq( + result.ghoMinDiscountTokenBalanceForDiscount, + GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(), + 'Unexpected minimum debt token balance' + ); + assertEq( + result.ghoReserveLastUpdateTimestamp, + baseData.lastUpdateTimestamp, + 'Unexpected last timestamp' + ); + assertEq(result.ghoCurrentBorrowIndex, baseData.variableBorrowIndex, 'Unexpected borrow index'); + assertEq(result.aaveFacilitatorBucketLevel, bucketLevel, 'Unexpected facilitator bucket level'); + assertEq( + result.aaveFacilitatorBucketMaxCapacity, + bucketCapacity, + 'Unexpected facilitator bucket capacity' + ); + } + + function testGhoUserData() public { + IUiGhoDataProvider.GhoUserData memory result = dataProvider.getGhoUserData(ALICE); + assertEq( + result.userGhoDiscountPercent, + GHO_DEBT_TOKEN.getDiscountPercent(ALICE), + 'Unexpected discount percent' + ); + assertEq( + result.userDiscountTokenBalance, + IERC20(GHO_DEBT_TOKEN.getDiscountToken()).balanceOf(ALICE), + 'Unexpected discount token balance' + ); + assertEq( + result.userPreviousGhoBorrowIndex, + GHO_DEBT_TOKEN.getPreviousIndex(ALICE), + 'Unexpected previous index' + ); + assertEq( + result.userGhoScaledBorrowBalance, + GHO_DEBT_TOKEN.scaledBalanceOf(ALICE), + 'Unexpected scaled balance' + ); + } +} diff --git a/src/contracts/test/TestZeroDiscountRateStrategy.t.sol b/src/contracts/test/TestZeroDiscountRateStrategy.t.sol new file mode 100644 index 00000000..3a01cffd --- /dev/null +++ b/src/contracts/test/TestZeroDiscountRateStrategy.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import './TestGhoBase.t.sol'; + +import {ZeroDiscountRateStrategy} from '../facilitators/aave/interestStrategy/ZeroDiscountRateStrategy.sol'; + +contract TestZeroDiscountRateStrategy is TestGhoBase { + ZeroDiscountRateStrategy emptyStrategy; + + function setUp() public { + emptyStrategy = new ZeroDiscountRateStrategy(); + } + + function testFuzzRateAlwaysZero(uint256 debtBalance, uint256 discountTokenBalance) public { + uint256 result = emptyStrategy.calculateDiscountRate(debtBalance, discountTokenBalance); + assertEq(result, 0, 'Unexpected discount rate'); + } +} diff --git a/src/contracts/test/helpers/Constants.sol b/src/contracts/test/helpers/Constants.sol new file mode 100644 index 00000000..4eed9bb9 --- /dev/null +++ b/src/contracts/test/helpers/Constants.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Constants { + // addresses expected for BGD stkAave + address constant SHORT_EXECUTOR = 0xEE56e2B3D491590B5b31738cC34d5232F378a8D5; + address constant STKAAVE_PROXY_ADMIN = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; + + // defaults used in test environment + uint256 constant DEFAULT_FLASH_FEE = 0.0009e4; // 0.09% + uint128 constant DEFAULT_CAPACITY = 100_000_000e18; + uint256 constant DEFAULT_BORROW_AMOUNT = 200e18; + int256 constant DEFAULT_GHO_PRICE = 1e8; + uint8 constant DEFAULT_ORACLE_DECIMALS = 8; + + // sample users used across unit tests + address constant ALICE = address(0x1111); + address constant BOB = address(0x1112); + address constant CHARLES = address(0x1113); + + address constant FAUCET = address(0x10001); + address constant TREASURY = address(0x10002); +} diff --git a/src/contracts/foundry-test/libraries/DebtUtils.sol b/src/contracts/test/helpers/DebtUtils.sol similarity index 86% rename from src/contracts/foundry-test/libraries/DebtUtils.sol rename to src/contracts/test/helpers/DebtUtils.sol index 5b453dc9..3f76af10 100644 --- a/src/contracts/foundry-test/libraries/DebtUtils.sol +++ b/src/contracts/test/helpers/DebtUtils.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + import {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol'; import {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol'; import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; @@ -7,6 +10,11 @@ library DebtUtils { using SafeCast for uint256; using PercentageMath for uint256; + function test_coverage_ignore() public { + // Intentionally left blank. + // Excludes contract from coverage. + } + function computeDebt( uint256 userPreviousIndex, uint256 index, diff --git a/src/contracts/test/helpers/Events.sol b/src/contracts/test/helpers/Events.sol new file mode 100644 index 00000000..591efdb5 --- /dev/null +++ b/src/contracts/test/helpers/Events.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface Events { + // core token events + event Mint( + address indexed caller, + address indexed onBehalfOf, + uint256 value, + uint256 balanceIncrease, + uint256 index + ); + event Burn( + address indexed from, + address indexed target, + uint256 value, + uint256 balanceIncrease, + uint256 index + ); + event Transfer(address indexed from, address indexed to, uint256 value); + + // setter/updater methods + event ATokenSet(address indexed); + event VariableDebtTokenSet(address indexed variableDebtToken); + event GhoTreasuryUpdated(address indexed oldGhoTreasury, address indexed newGhoTreasury); + event DiscountPercentUpdated( + address indexed user, + uint256 oldDiscountPercent, + uint256 indexed newDiscountPercent + ); + event DiscountRateStrategyUpdated( + address indexed oldDiscountRateStrategy, + address indexed newDiscountRateStrategy + ); + event ReserveInterestRateStrategyChanged( + address indexed asset, + address oldStrategy, + address newStrategy + ); + + // flashmint-related events + event FlashMint( + address indexed receiver, + address indexed initiator, + address asset, + uint256 indexed amount, + uint256 fee + ); + event FeeUpdated(uint256 oldFee, uint256 newFee); + + // facilitator-related events + event FacilitatorAdded( + address indexed facilitatorAddress, + bytes32 indexed label, + uint256 bucketCapacity + ); + event FacilitatorRemoved(address indexed facilitatorAddress); + event FacilitatorBucketCapacityUpdated( + address indexed facilitatorAddress, + uint256 oldCapacity, + uint256 newCapacity + ); + event FacilitatorBucketLevelUpdated( + address indexed facilitatorAddress, + uint256 oldLevel, + uint256 newLevel + ); + + // other + event FeesDistributedToTreasury( + address indexed ghoTreasury, + address indexed asset, + uint256 amount + ); +} diff --git a/src/contracts/facilitators/flashMinter/mocks/MockFlashBorrower.sol b/src/contracts/test/mocks/MockFlashBorrower.sol similarity index 93% rename from src/contracts/facilitators/flashMinter/mocks/MockFlashBorrower.sol rename to src/contracts/test/mocks/MockFlashBorrower.sol index 61b792d9..ebec0fe7 100644 --- a/src/contracts/facilitators/flashMinter/mocks/MockFlashBorrower.sol +++ b/src/contracts/test/mocks/MockFlashBorrower.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.10; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol'; import {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol'; -import {IGhoToken} from '../../../gho/interfaces/IGhoToken.sol'; +import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; /** * @title MockFlashBorrower @@ -28,6 +28,11 @@ contract MockFlashBorrower is IERC3156FlashBorrower { _allowCallback = true; } + function test_coverage_ignore() public virtual { + // Intentionally left blank. + // Excludes contract from coverage. + } + /// @dev ERC-3156 Flash loan callback function onFlashLoan( address initiator, diff --git a/src/contracts/foundry-test/mocks/MockedAclManager.sol b/src/contracts/test/mocks/MockedAclManager.sol similarity index 74% rename from src/contracts/foundry-test/mocks/MockedAclManager.sol rename to src/contracts/test/mocks/MockedAclManager.sol index 7b1cf182..3f3f5f73 100644 --- a/src/contracts/foundry-test/mocks/MockedAclManager.sol +++ b/src/contracts/test/mocks/MockedAclManager.sol @@ -8,6 +8,11 @@ contract MockedAclManager { state = true; } + function test_coverage_ignore() public virtual { + // Intentionally left blank. + // Excludes contract from coverage. + } + function setState(bool value) public { state = value; } diff --git a/src/contracts/test/mocks/MockedConfigurator.sol b/src/contracts/test/mocks/MockedConfigurator.sol new file mode 100644 index 00000000..df8933f5 --- /dev/null +++ b/src/contracts/test/mocks/MockedConfigurator.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; +import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol'; + +contract MockedConfigurator { + IPool internal _pool; + + event ReserveInterestRateStrategyChanged( + address indexed asset, + address oldStrategy, + address newStrategy + ); + + constructor(IPool pool) { + _pool = pool; + } + + function test_coverage_ignore() public virtual { + // Intentionally left blank. + // Excludes contract from coverage. + } + + function setReserveInterestRateStrategyAddress( + address asset, + address newRateStrategyAddress + ) external { + DataTypes.ReserveData memory reserve = _pool.getReserveData(asset); + address oldRateStrategyAddress = reserve.interestRateStrategyAddress; + _pool.setReserveInterestRateStrategyAddress(asset, newRateStrategyAddress); + emit ReserveInterestRateStrategyChanged(asset, oldRateStrategyAddress, newRateStrategyAddress); + } +} diff --git a/src/contracts/foundry-test/mocks/MockedPool.sol b/src/contracts/test/mocks/MockedPool.sol similarity index 85% rename from src/contracts/foundry-test/mocks/MockedPool.sol rename to src/contracts/test/mocks/MockedPool.sol index 17cd9312..7d31eb50 100644 --- a/src/contracts/foundry-test/mocks/MockedPool.sol +++ b/src/contracts/test/mocks/MockedPool.sol @@ -17,6 +17,7 @@ import {Helpers} from '@aave/core-v3/contracts/protocol/libraries/helpers/Helper import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol'; import {StableDebtToken} from '@aave/core-v3/contracts/protocol/tokenization/StableDebtToken.sol'; import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/ERC20.sol'; +import {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol'; /** * @dev MockedPool removes assets and users validations from Pool contract. @@ -33,6 +34,11 @@ contract MockedPool is Pool { constructor(IPoolAddressesProvider provider) Pool(provider) {} + function test_coverage_ignore() public virtual { + // Intentionally left blank. + // Excludes contract from coverage. + } + function setGhoTokens(GhoVariableDebtToken ghoDebtToken, GhoAToken ghoAToken) external { DEBT_TOKEN = ghoDebtToken; ATOKEN = ghoAToken; @@ -96,4 +102,16 @@ contract MockedPool is Pool { return paybackAmount; } + + function setReserveInterestRateStrategyAddress( + address asset, + address rateStrategyAddress + ) external override { + require(asset != address(0), Errors.ZERO_ADDRESS_NOT_VALID); + _reserves[asset].interestRateStrategyAddress = rateStrategyAddress; + } + + function getReserveInterestRateStrategyAddress(address asset) external returns (address) { + return _reserves[asset].interestRateStrategyAddress; + } } diff --git a/src/contracts/foundry-test/mocks/MockedProvider.sol b/src/contracts/test/mocks/MockedProvider.sol similarity index 67% rename from src/contracts/foundry-test/mocks/MockedProvider.sol rename to src/contracts/test/mocks/MockedProvider.sol index acf356e2..6b910f54 100644 --- a/src/contracts/foundry-test/mocks/MockedProvider.sol +++ b/src/contracts/test/mocks/MockedProvider.sol @@ -8,6 +8,11 @@ contract MockedProvider { ACL_MANAGER = aclManager; } + function test_coverage_ignore() public virtual { + // Intentionally left blank. + // Excludes contract from coverage. + } + function getACLManager() public returns (address) { return ACL_MANAGER; } diff --git a/src/helpers/contract-getters.ts b/src/helpers/contract-getters.ts index 6b380308..d5b9d961 100644 --- a/src/helpers/contract-getters.ts +++ b/src/helpers/contract-getters.ts @@ -11,6 +11,7 @@ import { GhoOracle, GhoToken, GhoVariableDebtToken, + GhoStableDebtToken, AToken, BaseImmutableAdminUpgradeabilityProxy, Pool, @@ -70,7 +71,7 @@ export const getGhoVariableDebtToken = async ( export const getGhoStableDebtToken = async ( address?: tEthereumAddress -): Promise => +): Promise => getContract( 'GhoStableDebtToken', address || (await hre.deployments.get('GhoStableDebtToken')).address diff --git a/src/test/discount-rebalance.test.ts b/src/test/discount-rebalance.test.ts index 02de668f..7a29e8a2 100644 --- a/src/test/discount-rebalance.test.ts +++ b/src/test/discount-rebalance.test.ts @@ -8,7 +8,7 @@ import { ZERO_ADDRESS, oneRay } from '../helpers/constants'; import { ghoReserveConfig } from '../helpers/config'; import { calcCompoundedInterest, calcDiscountRate } from './helpers/math/calculations'; import { getTxCostAndTimestamp } from './helpers/helpers'; -import { EmptyDiscountRateStrategy__factory } from '../../types'; +import { ZeroDiscountRateStrategy__factory } from '../../types'; makeSuite('Gho Discount Rebalance Flow', (testEnv: TestEnv) => { let ethers; @@ -197,7 +197,7 @@ makeSuite('Gho Discount Rebalance Flow', (testEnv: TestEnv) => { const oldDiscountRateStrategyAddress = await variableDebtToken.getDiscountRateStrategy(); - const emptyStrategy = await new EmptyDiscountRateStrategy__factory(poolAdmin.signer).deploy(); + const emptyStrategy = await new ZeroDiscountRateStrategy__factory(poolAdmin.signer).deploy(); await expect( variableDebtToken.connect(poolAdmin.signer).updateDiscountRateStrategy(emptyStrategy.address) ) diff --git a/src/test/unitTests/gho-token-permit.test.ts b/src/test/gho-token-permit.test.ts similarity index 97% rename from src/test/unitTests/gho-token-permit.test.ts rename to src/test/gho-token-permit.test.ts index 3405c556..4ab70077 100644 --- a/src/test/unitTests/gho-token-permit.test.ts +++ b/src/test/gho-token-permit.test.ts @@ -1,11 +1,11 @@ import hre from 'hardhat'; import { expect } from 'chai'; -import { SignerWithAddress } from '../helpers/make-suite'; -import { GhoToken__factory, IGhoToken } from '../../../types'; +import { SignerWithAddress } from './helpers/make-suite'; +import { GhoToken__factory, IGhoToken } from '../../types'; import { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types'; import { BigNumber } from 'ethers'; -import { HARDHAT_CHAINID, MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../../helpers/constants'; -import { buildPermitParams, getSignatureFromTypedData } from '../helpers/helpers'; +import { HARDHAT_CHAINID, MAX_UINT_AMOUNT, ZERO_ADDRESS } from './../helpers/constants'; +import { buildPermitParams, getSignatureFromTypedData } from './helpers/helpers'; describe('GhoToken Unit Test', () => { let ethers: typeof import('ethers/lib/ethers') & HardhatEthersHelpers; diff --git a/src/test/unitTests/gho-token-unit.test.ts b/src/test/gho-token-unit.test.ts similarity index 98% rename from src/test/unitTests/gho-token-unit.test.ts rename to src/test/gho-token-unit.test.ts index 4bf970de..38841adc 100644 --- a/src/test/unitTests/gho-token-unit.test.ts +++ b/src/test/gho-token-unit.test.ts @@ -1,12 +1,12 @@ import hre from 'hardhat'; import { expect } from 'chai'; import { PANIC_CODES } from '@nomicfoundation/hardhat-chai-matchers/panic'; -import { SignerWithAddress } from '../helpers/make-suite'; -import { ghoTokenConfig } from '../../helpers/config'; -import { GhoToken__factory, IGhoToken } from '../../../types'; +import { SignerWithAddress } from './helpers/make-suite'; +import { ghoTokenConfig } from '../helpers/config'; +import { GhoToken__factory, IGhoToken } from '../../types'; import { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types'; import { BigNumber } from 'ethers'; -import { ZERO_ADDRESS } from '../../helpers/constants'; +import { ZERO_ADDRESS } from '../helpers/constants'; describe('GhoToken Unit Test', () => { let ethers: typeof import('ethers/lib/ethers') & HardhatEthersHelpers; diff --git a/src/test/unitTests/gho.oracle-unit.test.ts b/src/test/unitTests/gho.oracle-unit.test.ts deleted file mode 100644 index 45d89679..00000000 --- a/src/test/unitTests/gho.oracle-unit.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import hardhat, { ethers } from 'hardhat'; -import { expect } from 'chai'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { GhoOracle, GhoOracle__factory } from '../../../types'; -import { evmRevert, evmSnapshot } from '../../helpers/misc-utils'; - -const GHO_ORACLE_DECIMALS = 8; -const TOKEN_TYPE = 1; -const GHO_PRICE = ethers.utils.parseUnits('1', 8); - -describe('Gho Oracle Unit Test', () => { - let ghoOracle: GhoOracle; - let deployer: SignerWithAddress; - let users: SignerWithAddress[]; - - let snapId; - - before(async () => { - [deployer, ...users] = await hardhat.ethers.getSigners(); - ghoOracle = await new GhoOracle__factory(deployer).deploy(); - }); - - beforeEach(async () => { - snapId = await evmSnapshot(); - }); - - afterEach(async () => { - await evmRevert(snapId); - }); - - it('Check initial config params of GHO oracle', async () => { - expect(await ghoOracle.decimals()).to.equal(GHO_ORACLE_DECIMALS); - }); - - it('Check price of GHO', async () => { - expect(await ghoOracle.latestAnswer()).to.equal(GHO_PRICE); - }); -});