From ff8b14fa2b55b4d2fc36cb66c442315cf5fa2671 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 5 Dec 2024 00:27:22 +0100 Subject: [PATCH 1/3] feat: add initial gas snapshots While the ci contnously checks gas usage via --gas-report, having isolated gas metering has some merit. --- .prettierignore | 1 + Makefile | 3 + snapshots/AToken.transfer.json | 11 +++ snapshots/Pool.Borrow.json | 6 ++ snapshots/Pool.Getters.json | 10 ++ snapshots/Pool.Setters.json | 6 ++ snapshots/Pool.Supply.json | 5 + snapshots/StataTokenV2.json | 6 ++ tests/gas/AToken.Transfer.gas.t.sol | 145 ++++++++++++++++++++++++++++ tests/gas/Pool.Borrow.gas.t.sol | 49 ++++++++++ tests/gas/Pool.Getters.gas.t.sol | 67 +++++++++++++ tests/gas/Pool.Setters.gas.t.sol | 56 +++++++++++ tests/gas/Pool.Supply.gas.t.sol | 29 ++++++ tests/gas/StataToken.gas.t.sol | 55 +++++++++++ tests/gas/Testhelpers.sol | 42 ++++++++ 15 files changed, 491 insertions(+) create mode 100644 snapshots/AToken.transfer.json create mode 100644 snapshots/Pool.Borrow.json create mode 100644 snapshots/Pool.Getters.json create mode 100644 snapshots/Pool.Setters.json create mode 100644 snapshots/Pool.Supply.json create mode 100644 snapshots/StataTokenV2.json create mode 100644 tests/gas/AToken.Transfer.gas.t.sol create mode 100644 tests/gas/Pool.Borrow.gas.t.sol create mode 100644 tests/gas/Pool.Getters.gas.t.sol create mode 100644 tests/gas/Pool.Setters.gas.t.sol create mode 100644 tests/gas/Pool.Supply.gas.t.sol create mode 100644 tests/gas/StataToken.gas.t.sol create mode 100644 tests/gas/Testhelpers.sol diff --git a/.prettierignore b/.prettierignore index 2339d723..e76b03e3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ lib cache node_modules report +snapshots diff --git a/Makefile b/Makefile index 8db7ae3b..1a8e2b28 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ coverage : make coverage-report make coverage-badge +# Gas reports +forge test --mp 'tests/gas/*.t.sol' --isolate + # Utilities download :; cast etherscan-source --chain ${chain} -d src/etherscan/${chain}_${address} ${address} git-diff : diff --git a/snapshots/AToken.transfer.json b/snapshots/AToken.transfer.json new file mode 100644 index 00000000..76ac4bda --- /dev/null +++ b/snapshots/AToken.transfer.json @@ -0,0 +1,11 @@ +{ + "full amount; receiver: ->enableCollateral": "144837", + "full amount; sender: ->disableCollateral;": "103274", + "full amount; sender: ->disableCollateral; receiver: ->enableCollateral": "145016", + "full amount; sender: ->disableCollateral; receiver: dirty, ->enableCollateral": "133059", + "full amount; sender: collateralDisabled": "103095", + "partial amount; sender: collateralDisabled;": "103095", + "partial amount; sender: collateralDisabled; receiver: ->enableCollateral": "144837", + "partial amount; sender: collateralEnabled;": "103303", + "partial amount; sender: collateralEnabled; receiver: ->enableCollateral": "145045" +} \ No newline at end of file diff --git a/snapshots/Pool.Borrow.json b/snapshots/Pool.Borrow.json new file mode 100644 index 00000000..ecb36dfd --- /dev/null +++ b/snapshots/Pool.Borrow.json @@ -0,0 +1,6 @@ +{ + "first borrow": "256189", + "repay full": "176547", + "repay partial": "189971", + "second borrow": "248474" +} \ No newline at end of file diff --git a/snapshots/Pool.Getters.json b/snapshots/Pool.Getters.json new file mode 100644 index 00000000..954829ee --- /dev/null +++ b/snapshots/Pool.Getters.json @@ -0,0 +1,10 @@ +{ + "getEModeCategoryCollateralConfig": "8000", + "getEModeCategoryData": "13430", + "getLiquidationGracePeriod": "29197", + "getReserveData": "34935", + "getUserAccountData: supplies: 0, borrows: 0": "22641", + "getUserAccountData: supplies: 1, borrows: 0": "34774", + "getUserAccountData: supplies: 2, borrows: 0": "47296", + "getUserAccountData: supplies: 2, borrows: 1": "28968" +} \ No newline at end of file diff --git a/snapshots/Pool.Setters.json b/snapshots/Pool.Setters.json new file mode 100644 index 00000000..a92f7e05 --- /dev/null +++ b/snapshots/Pool.Setters.json @@ -0,0 +1,6 @@ +{ + "setUserEMode: enter eMode, 1 borrow, 1 supply": "140164", + "setUserEMode: leave eMode, 1 borrow, 1 supply": "112199", + "setUserUseReserveAsCollateral: disableCollateral, 1 supply": "93434", + "setUserUseReserveAsCollateral: enableCollateral, 1 supply": "105145" +} \ No newline at end of file diff --git a/snapshots/Pool.Supply.json b/snapshots/Pool.Supply.json new file mode 100644 index 00000000..ff671080 --- /dev/null +++ b/snapshots/Pool.Supply.json @@ -0,0 +1,5 @@ +{ + "supply: collateralDisabled": "146829", + "supply: collateralEnabled": "146829", + "supply: first supply->collateralEnabled": "188344" +} \ No newline at end of file diff --git a/snapshots/StataTokenV2.json b/snapshots/StataTokenV2.json new file mode 100644 index 00000000..5f9315f7 --- /dev/null +++ b/snapshots/StataTokenV2.json @@ -0,0 +1,6 @@ +{ + "deposit": "284677", + "depositATokens": "220136", + "redeem": "205767", + "redeemAToken": "153413" +} \ No newline at end of file diff --git a/tests/gas/AToken.Transfer.gas.t.sol b/tests/gas/AToken.Transfer.gas.t.sol new file mode 100644 index 00000000..cd471e70 --- /dev/null +++ b/tests/gas/AToken.Transfer.gas.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {IAToken} from '../../src/contracts/interfaces/IAToken.sol'; +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; + +/** + * Scenario suite for transfer operations. + */ +contract ATokenTransfer_gas_Tests is Testhelpers { + address token; + IAToken aToken; + address variableDebtToken; + + address sender = makeAddr('sender'); + address receiver = makeAddr('receiver'); + + function setUp() public override { + super.setUp(); + token = tokenList.usdx; + (address aTokenAddress, , address variableDebtTokenAddress) = contracts + .protocolDataProvider + .getReserveTokensAddresses(tokenList.usdx); + aToken = IAToken(aTokenAddress); + variableDebtToken = variableDebtTokenAddress; + } + + function test_transfer_fullAmount() external { + _supplyOnReserve(sender, 1 ether); + vm.prank(sender); + + _skip(100); + + aToken.transfer(receiver, 1 ether); + vm.snapshotGasLastCall( + 'AToken.transfer', + 'full amount; sender: ->disableCollateral; receiver: ->enableCollateral' + ); + } + + function test_transfer_fullAmount_dirtyReceiver() external { + _supplyOnReserve(receiver, 1 ether, tokenList.weth); + _supplyOnReserve(sender, 1 ether); + vm.prank(sender); + + _skip(100); + + aToken.transfer(receiver, 1 ether); + vm.snapshotGasLastCall( + 'AToken.transfer', + 'full amount; sender: ->disableCollateral; receiver: dirty, ->enableCollateral' + ); + } + + function test_transfer_fullAmount_senderCollateralDisabled() external { + _supplyOnReserve(sender, 1 ether); + vm.startPrank(sender); + contracts.poolProxy.setUserUseReserveAsCollateral(token, false); + + _skip(100); + + aToken.transfer(receiver, 1 ether); + vm.snapshotGasLastCall('AToken.transfer', 'full amount; receiver: ->enableCollateral'); + } + + function test_transfer_fullAmount_senderCollateralDisabled_receiverNonZeroFunds2() external { + _supplyOnReserve(sender, 1 ether); + _supplyOnReserve(receiver, 1 ether); + vm.startPrank(sender); + + _skip(100); + + aToken.transfer(receiver, 1 ether); + vm.snapshotGasLastCall('AToken.transfer', 'full amount; sender: ->disableCollateral;'); + } + + function test_transfer_fullAmount_senderCollateralDisabled_receiverNonZeroFunds() external { + _supplyOnReserve(sender, 1 ether); + _supplyOnReserve(receiver, 1 ether); + vm.startPrank(sender); + contracts.poolProxy.setUserUseReserveAsCollateral(token, false); + + _skip(100); + + aToken.transfer(receiver, 1 ether); + vm.snapshotGasLastCall('AToken.transfer', 'full amount; sender: collateralDisabled'); + } + + function test_transfer_partialAmount_senderCollateralEnabled() external { + _supplyOnReserve(sender, 1 ether); + vm.startPrank(sender); + + _skip(100); + + aToken.transfer(receiver, 0.5 ether); + vm.snapshotGasLastCall( + 'AToken.transfer', + 'partial amount; sender: collateralEnabled; receiver: ->enableCollateral' + ); + } + + function test_transfer_partialAmount_senderCollateralEnabled_receiverNonZeroFunds() external { + _supplyOnReserve(sender, 1 ether); + _supplyOnReserve(receiver, 1 ether); + vm.startPrank(sender); + + _skip(100); + + aToken.transfer(receiver, 0.5 ether); + vm.snapshotGasLastCall('AToken.transfer', 'partial amount; sender: collateralEnabled;'); + } + + function test_transfer_partialAmount_receiverNonZeroFunds() external { + _supplyOnReserve(sender, 1 ether); + _supplyOnReserve(receiver, 1 ether); + vm.startPrank(sender); + contracts.poolProxy.setUserUseReserveAsCollateral(token, false); + + _skip(100); + + aToken.transfer(receiver, 0.5 ether); + vm.snapshotGasLastCall('AToken.transfer', 'partial amount; sender: collateralDisabled;'); + } + + function test_transfer_partialAmount() external { + _supplyOnReserve(sender, 1 ether); + vm.startPrank(sender); + contracts.poolProxy.setUserUseReserveAsCollateral(token, false); + + _skip(100); + + aToken.transfer(receiver, 0.5 ether); + vm.snapshotGasLastCall( + 'AToken.transfer', + 'partial amount; sender: collateralDisabled; receiver: ->enableCollateral' + ); + } + + function _supplyOnReserve(address user, uint256 amount) internal { + _supplyOnReserve(user, amount, token); + } +} diff --git a/tests/gas/Pool.Borrow.gas.t.sol b/tests/gas/Pool.Borrow.gas.t.sol new file mode 100644 index 00000000..1bf14437 --- /dev/null +++ b/tests/gas/Pool.Borrow.gas.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; + +/** + * Scenario suite for borrow/repay operations. + */ +contract PoolBorrow_gas_Tests is Testhelpers { + // mock users to supply and borrow liquidity + address borrower = makeAddr('borrower'); + + function setUp() public override { + super.setUp(); + // setup testUser with some collateral + _supplyOnReserve(borrower, 100 ether, tokenList.weth); + } + + function test_borrow() external { + uint256 amountToBorrow = 1000e6; + vm.startPrank(borrower); + contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); + vm.snapshotGasLastCall('Pool.Borrow', 'first borrow'); + + _skip(100); // skip some blocks to allow interest to accrue & the block to be cold + contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); + vm.snapshotGasLastCall('Pool.Borrow', 'second borrow'); + } + + function test_repay() external { + uint256 amountToBorrow = 1000e6; + deal(tokenList.usdx, borrower, amountToBorrow); + vm.startPrank(borrower); + contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); + IERC20(tokenList.usdx).approve(report.poolProxy, type(uint256).max); + + _skip(100); // skip some blocks to allow interest to accrue & the block to be cold + contracts.poolProxy.repay(tokenList.usdx, amountToBorrow / 2, 2, borrower); + vm.snapshotGasLastCall('Pool.Borrow', 'repay partial'); + + _skip(100); // skip some blocks to allow interest to accrue & the block to be cold + contracts.poolProxy.repay(tokenList.usdx, type(uint256).max, 2, borrower); + vm.snapshotGasLastCall('Pool.Borrow', 'repay full'); + } +} diff --git a/tests/gas/Pool.Getters.gas.t.sol b/tests/gas/Pool.Getters.gas.t.sol new file mode 100644 index 00000000..0e6735f7 --- /dev/null +++ b/tests/gas/Pool.Getters.gas.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; + +/** + * Scenario suite for pool getter operations. + */ +contract PoolGetters_gas_Tests is Testhelpers { + // mock users to supply and borrow liquidity + address user = makeAddr('user'); + + function test_getReserveData() external { + contracts.poolProxy.getReserveData(tokenList.usdx); + vm.snapshotGasLastCall('Pool.Getters', 'getReserveData'); + } + + function test_getUserAccountData() external { + contracts.poolProxy.getUserAccountData(user); + vm.snapshotGasLastCall('Pool.Getters', 'getUserAccountData: supplies: 0, borrows: 0'); + } + + function test_getUserAccountData_oneSupplies() external { + _supplyOnReserve(user, 1 ether, tokenList.usdx); + contracts.poolProxy.getUserAccountData(user); + vm.snapshotGasLastCall('Pool.Getters', 'getUserAccountData: supplies: 1, borrows: 0'); + } + + function test_getUserAccountData_twoSupplies() external { + _supplyOnReserve(user, 1 ether, tokenList.usdx); + _supplyOnReserve(user, 1 ether, tokenList.weth); + + contracts.poolProxy.getUserAccountData(user); + vm.snapshotGasLastCall('Pool.Getters', 'getUserAccountData: supplies: 2, borrows: 0'); + } + + function test_getUserAccountData_twoSupplies_oneBorrows() external { + _supplyOnReserve(user, 1 ether, tokenList.usdx); + _supplyOnReserve(user, 1 ether, tokenList.weth); + + _supplyOnReserve(address(1), 0.001e8, tokenList.wbtc); + vm.prank(user); + contracts.poolProxy.borrow(tokenList.wbtc, 0.001e8, 2, 0, user); + + contracts.poolProxy.getUserAccountData(user); + vm.snapshotGasLastCall('Pool.Getters', 'getUserAccountData: supplies: 2, borrows: 1'); + } + + function test_getEModeCategoryData() external { + contracts.poolProxy.getEModeCategoryData(1); + vm.snapshotGasLastCall('Pool.Getters', 'getEModeCategoryData'); + } + + function test_getEModeCategoryCollateralConfig() external { + contracts.poolProxy.getEModeCategoryCollateralConfig(1); + vm.snapshotGasLastCall('Pool.Getters', 'getEModeCategoryCollateralConfig'); + } + + function test_getLiquidationGracePeriod() external { + contracts.poolProxy.getLiquidationGracePeriod(tokenList.usdx); + vm.snapshotGasLastCall('Pool.Getters', 'getLiquidationGracePeriod'); + } +} diff --git a/tests/gas/Pool.Setters.gas.t.sol b/tests/gas/Pool.Setters.gas.t.sol new file mode 100644 index 00000000..7a89eab3 --- /dev/null +++ b/tests/gas/Pool.Setters.gas.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; + +/** + * Scenario suite for pool setter operations. + */ +contract PoolSetters_gas_Tests is Testhelpers { + // mock users to supply and borrow liquidity + address user = makeAddr('user'); + + function test_setUserEMode() external { + vm.startPrank(poolAdmin); + EModeCategoryInput memory ct1 = _genCategoryOne(); + contracts.poolConfiguratorProxy.setEModeCategory(ct1.id, ct1.ltv, ct1.lt, ct1.lb, ct1.label); + contracts.poolConfiguratorProxy.setAssetCollateralInEMode(tokenList.usdx, ct1.id, true); + contracts.poolConfiguratorProxy.setAssetBorrowableInEMode(tokenList.weth, ct1.id, true); + vm.stopPrank(); + _supplyOnReserve(address(this), 0.5 ether, tokenList.weth); + + _supplyOnReserve(user, 5000e6, tokenList.usdx); + vm.startPrank(user); + contracts.poolProxy.borrow(tokenList.weth, 0.5 ether, 2, 0, user); + + _skip(100); + + contracts.poolProxy.setUserEMode(1); + vm.snapshotGasLastCall('Pool.Setters', 'setUserEMode: enter eMode, 1 borrow, 1 supply'); + + _skip(100); + + contracts.poolProxy.setUserEMode(0); + vm.snapshotGasLastCall('Pool.Setters', 'setUserEMode: leave eMode, 1 borrow, 1 supply'); + } + + function test_setUserUseReserveAsCollateral() external { + _supplyOnReserve(address(this), 5000e6, tokenList.usdx); + + contracts.poolProxy.setUserUseReserveAsCollateral(tokenList.usdx, false); + vm.snapshotGasLastCall( + 'Pool.Setters', + 'setUserUseReserveAsCollateral: disableCollateral, 1 supply' + ); + + contracts.poolProxy.setUserUseReserveAsCollateral(tokenList.usdx, true); + vm.snapshotGasLastCall( + 'Pool.Setters', + 'setUserUseReserveAsCollateral: enableCollateral, 1 supply' + ); + } +} diff --git a/tests/gas/Pool.Supply.gas.t.sol b/tests/gas/Pool.Supply.gas.t.sol new file mode 100644 index 00000000..c4557e12 --- /dev/null +++ b/tests/gas/Pool.Supply.gas.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; + +/** + * Scenario suite for supply operations. + */ +contract PoolSupply_gas_Tests is Testhelpers { + function test_supply() external { + _supplyOnReserve(address(this), 100e6, tokenList.usdx); + vm.snapshotGasLastCall('Pool.Supply', 'supply: first supply->collateralEnabled'); + + _skip(100); + + _supplyOnReserve(address(this), 100e6, tokenList.usdx); + vm.snapshotGasLastCall('Pool.Supply', 'supply: collateralEnabled'); + contracts.poolProxy.setUserUseReserveAsCollateral(tokenList.usdx, false); + + _skip(100); + + _supplyOnReserve(address(this), 100e6, tokenList.usdx); + vm.snapshotGasLastCall('Pool.Supply', 'supply: collateralDisabled'); + } +} diff --git a/tests/gas/StataToken.gas.t.sol b/tests/gas/StataToken.gas.t.sol new file mode 100644 index 00000000..eb997bc5 --- /dev/null +++ b/tests/gas/StataToken.gas.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; +import {StataTokenFactory} from '../../src/contracts/extensions/stata-token/StataTokenFactory.sol'; +import {StataTokenV2} from '../../src/contracts/extensions/stata-token/StataTokenV2.sol'; +import {DataTypes} from '../../src/contracts/protocol/libraries/types/DataTypes.sol'; + +/** + * Scenario suite for statatoken operations. + */ +contract StataToken_gas_Tests is Testhelpers { + StataTokenV2 public stataToken; + + function setUp() public override { + super.setUp(); + StataTokenFactory(report.staticATokenFactoryProxy).createStataTokens( + contracts.poolProxy.getReservesList() + ); + stataToken = StataTokenV2( + StataTokenFactory(report.staticATokenFactoryProxy).getStataToken(tokenList.usdx) + ); + } + + function test_deposit() external { + uint256 amountToDeposit = 1000e8; + deal(tokenList.usdx, address(this), amountToDeposit); + IERC20(tokenList.usdx).approve(address(stataToken), amountToDeposit); + + uint256 shares = stataToken.deposit(amountToDeposit, address(this)); + vm.snapshotGasLastCall('StataTokenV2', 'deposit'); + + stataToken.redeem(shares, address(this), address(this)); + vm.snapshotGasLastCall('StataTokenV2', 'redeem'); + } + + function test_depositATokens() external { + uint256 amountToDeposit = 1000e8; + _supplyOnReserve(address(this), amountToDeposit, tokenList.usdx); + DataTypes.ReserveDataLegacy memory reserveData = contracts.poolProxy.getReserveData( + tokenList.usdx + ); + IERC20(reserveData.aTokenAddress).approve(address(stataToken), amountToDeposit); + + uint256 shares = stataToken.depositATokens(amountToDeposit, address(this)); + vm.snapshotGasLastCall('StataTokenV2', 'depositATokens'); + + stataToken.redeemATokens(shares, address(this), address(this)); + vm.snapshotGasLastCall('StataTokenV2', 'redeemAToken'); + } +} diff --git a/tests/gas/Testhelpers.sol b/tests/gas/Testhelpers.sol new file mode 100644 index 00000000..bba34922 --- /dev/null +++ b/tests/gas/Testhelpers.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {IERC20} from 'openzeppelin-contracts/contracts/interfaces/IERC20.sol'; +import {TestnetProcedures} from '../utils/TestnetProcedures.sol'; + +contract Testhelpers is TestnetProcedures { + address rando = makeAddr('randomUser'); + + function setUp() public virtual { + initTestEnvironment(false); + + // supply and borrow some on reserve with a random user as "some" interest accrual + // is the realisitc usecase we want to check in gas snapshots + _supplyOnReserve(rando, 100 ether, tokenList.weth); + _supplyOnReserve(rando, 1_000_000e6, tokenList.usdx); + _supplyOnReserve(rando, 100e8, tokenList.wbtc); + vm.startPrank(rando); + contracts.poolProxy.borrow(tokenList.weth, 1 ether, 2, 0, rando); + contracts.poolProxy.borrow(tokenList.usdx, 1000e6, 2, 0, rando); + contracts.poolProxy.borrow(tokenList.wbtc, 1e8, 2, 0, rando); + vm.stopPrank(); + _skip(100); // skip some blocks to allow interest to accrue & the block to be not cached + } + + function _supplyOnReserve(address user, uint256 amount, address asset) internal { + vm.startPrank(user); + deal(asset, user, amount); + IERC20(asset).approve(report.poolProxy, amount); + contracts.poolProxy.supply(asset, amount, user, 0); + vm.stopPrank(); + } + + /** + * Skips the specified amount of blocks and adjusts the time accordingly. + * Using vm. methods for --via-ir compat. + */ + function _skip(uint256 amount) internal { + vm.warp(vm.getBlockTimestamp() + amount * 12); + vm.roll(vm.getBlockNumber() + amount); + } +} From 794aaf1feaa4ca3dc7e0657bc4d01523df9b8f72 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 13 Dec 2024 09:32:11 +0100 Subject: [PATCH 2/3] fix: update gas tests --- snapshots/Pool.Borrow.json | 6 -- snapshots/Pool.Operations.json | 15 +++ snapshots/Pool.Supply.json | 5 - tests/gas/Pool.Borrow.gas.t.sol | 49 --------- tests/gas/Pool.Operations.gas.t.sol | 155 ++++++++++++++++++++++++++++ tests/gas/Pool.Supply.gas.t.sol | 29 ------ tests/gas/Testhelpers.sol | 19 ++++ 7 files changed, 189 insertions(+), 89 deletions(-) delete mode 100644 snapshots/Pool.Borrow.json create mode 100644 snapshots/Pool.Operations.json delete mode 100644 snapshots/Pool.Supply.json delete mode 100644 tests/gas/Pool.Borrow.gas.t.sol create mode 100644 tests/gas/Pool.Operations.gas.t.sol delete mode 100644 tests/gas/Pool.Supply.gas.t.sol diff --git a/snapshots/Pool.Borrow.json b/snapshots/Pool.Borrow.json deleted file mode 100644 index ecb36dfd..00000000 --- a/snapshots/Pool.Borrow.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "first borrow": "256189", - "repay full": "176547", - "repay partial": "189971", - "second borrow": "248474" -} \ No newline at end of file diff --git a/snapshots/Pool.Operations.json b/snapshots/Pool.Operations.json new file mode 100644 index 00000000..b3d696f9 --- /dev/null +++ b/snapshots/Pool.Operations.json @@ -0,0 +1,15 @@ +{ + "borrow: first borrow->borrowingEnabled": "256189", + "borrow: recurrent borrow": "248474", + "liquidationCall: deficit on liquidated asset": "392266", + "liquidationCall: deficit on liquidated asset + other asset": "422243", + "liquidationCall: full liquidation": "392266", + "liquidationCall: partial liquidation": "381149", + "repay: full repay": "176547", + "repay: partial repay": "189971", + "supply: collateralDisabled": "146829", + "supply: collateralEnabled": "146829", + "supply: first supply->collateralEnabled": "188344", + "withdraw: full withdraw": "165303", + "withdraw: partial withdraw": "181992" +} \ No newline at end of file diff --git a/snapshots/Pool.Supply.json b/snapshots/Pool.Supply.json deleted file mode 100644 index ff671080..00000000 --- a/snapshots/Pool.Supply.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "supply: collateralDisabled": "146829", - "supply: collateralEnabled": "146829", - "supply: first supply->collateralEnabled": "188344" -} \ No newline at end of file diff --git a/tests/gas/Pool.Borrow.gas.t.sol b/tests/gas/Pool.Borrow.gas.t.sol deleted file mode 100644 index 1bf14437..00000000 --- a/tests/gas/Pool.Borrow.gas.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; - -import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; -import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; -import {Testhelpers, IERC20} from './Testhelpers.sol'; - -/** - * Scenario suite for borrow/repay operations. - */ -contract PoolBorrow_gas_Tests is Testhelpers { - // mock users to supply and borrow liquidity - address borrower = makeAddr('borrower'); - - function setUp() public override { - super.setUp(); - // setup testUser with some collateral - _supplyOnReserve(borrower, 100 ether, tokenList.weth); - } - - function test_borrow() external { - uint256 amountToBorrow = 1000e6; - vm.startPrank(borrower); - contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); - vm.snapshotGasLastCall('Pool.Borrow', 'first borrow'); - - _skip(100); // skip some blocks to allow interest to accrue & the block to be cold - contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); - vm.snapshotGasLastCall('Pool.Borrow', 'second borrow'); - } - - function test_repay() external { - uint256 amountToBorrow = 1000e6; - deal(tokenList.usdx, borrower, amountToBorrow); - vm.startPrank(borrower); - contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); - IERC20(tokenList.usdx).approve(report.poolProxy, type(uint256).max); - - _skip(100); // skip some blocks to allow interest to accrue & the block to be cold - contracts.poolProxy.repay(tokenList.usdx, amountToBorrow / 2, 2, borrower); - vm.snapshotGasLastCall('Pool.Borrow', 'repay partial'); - - _skip(100); // skip some blocks to allow interest to accrue & the block to be cold - contracts.poolProxy.repay(tokenList.usdx, type(uint256).max, 2, borrower); - vm.snapshotGasLastCall('Pool.Borrow', 'repay full'); - } -} diff --git a/tests/gas/Pool.Operations.gas.t.sol b/tests/gas/Pool.Operations.gas.t.sol new file mode 100644 index 00000000..b950ab12 --- /dev/null +++ b/tests/gas/Pool.Operations.gas.t.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; +import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {Testhelpers, IERC20} from './Testhelpers.sol'; + +/** + * Scenario suite for common operations supply/borrow/repay/withdraw/liquidationCall. + */ +contract PoolOperations_gas_Tests is Testhelpers { + address supplier = makeAddr('supplier'); + address borrower = makeAddr('borrower'); + address liquidator = makeAddr('liquidator'); + + function test_supply() external { + _supplyOnReserve(supplier, 100e6, tokenList.usdx); + vm.snapshotGasLastCall('Pool.Operations', 'supply: first supply->collateralEnabled'); + + _skip(100); + + _supplyOnReserve(supplier, 100e6, tokenList.usdx); + vm.snapshotGasLastCall('Pool.Operations', 'supply: collateralEnabled'); + vm.prank(supplier); + contracts.poolProxy.setUserUseReserveAsCollateral(tokenList.usdx, false); + + _skip(100); + + _supplyOnReserve(supplier, 100e6, tokenList.usdx); + vm.snapshotGasLastCall('Pool.Operations', 'supply: collateralDisabled'); + } + + function test_withdraw() external { + _supplyOnReserve(supplier, 100e6, tokenList.usdx); + vm.startPrank(supplier); + _skip(100); + + contracts.poolProxy.withdraw(tokenList.usdx, 50e6, supplier); + vm.snapshotGasLastCall('Pool.Operations', 'withdraw: partial withdraw'); + + _skip(100); + + contracts.poolProxy.withdraw(tokenList.usdx, type(uint256).max, supplier); + vm.snapshotGasLastCall('Pool.Operations', 'withdraw: full withdraw'); + } + + function test_borrow() external { + _supplyOnReserve(borrower, 100 ether, tokenList.weth); + uint256 amountToBorrow = 1000e6; + vm.startPrank(borrower); + contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); + vm.snapshotGasLastCall('Pool.Operations', 'borrow: first borrow->borrowingEnabled'); + + _skip(100); + + contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); + vm.snapshotGasLastCall('Pool.Operations', 'borrow: recurrent borrow'); + } + + function test_repay() external { + _supplyOnReserve(borrower, 100 ether, tokenList.weth); + uint256 amountToBorrow = 1000e6; + deal(tokenList.usdx, borrower, amountToBorrow); + vm.startPrank(borrower); + contracts.poolProxy.borrow(tokenList.usdx, amountToBorrow, 2, 0, borrower); + IERC20(tokenList.usdx).approve(report.poolProxy, type(uint256).max); + + _skip(100); + + contracts.poolProxy.repay(tokenList.usdx, amountToBorrow / 2, 2, borrower); + vm.snapshotGasLastCall('Pool.Operations', 'repay: partial repay'); + + _skip(100); + + contracts.poolProxy.repay(tokenList.usdx, type(uint256).max, 2, borrower); + vm.snapshotGasLastCall('Pool.Operations', 'repay: full repay'); + } + + function test_liquidationCall_partial() external { + uint256 price = contracts.aaveOracle.getAssetPrice(tokenList.weth); + _supplyOnReserve(borrower, (((price * 1e6) / 1e8) * 90) / 100, tokenList.usdx); + _borrowArbitraryAmount(borrower, 1 ether, tokenList.weth); + deal(tokenList.weth, liquidator, 0.5 ether); + vm.startPrank(liquidator); + IERC20(tokenList.weth).approve(report.poolProxy, 0.5 ether); + + _skip(100); + + contracts.poolProxy.liquidationCall(tokenList.usdx, tokenList.weth, borrower, 0.5 ether, false); + vm.snapshotGasLastCall('Pool.Operations', 'liquidationCall: partial liquidation'); + } + + function test_liquidationCall_full() external { + uint256 price = contracts.aaveOracle.getAssetPrice(tokenList.weth); + _supplyOnReserve(borrower, (((price * 1e6) / 1e8) * 90) / 100, tokenList.usdx); + _borrowArbitraryAmount(borrower, 1 ether, tokenList.weth); + deal(tokenList.weth, liquidator, 2 ether); + vm.startPrank(liquidator); + IERC20(tokenList.weth).approve(report.poolProxy, type(uint256).max); + + _skip(100); + + contracts.poolProxy.liquidationCall( + tokenList.usdx, + tokenList.weth, + borrower, + type(uint256).max, + false + ); + vm.snapshotGasLastCall('Pool.Operations', 'liquidationCall: full liquidation'); + } + + function test_liquidationCall_deficit() external { + uint256 price = contracts.aaveOracle.getAssetPrice(tokenList.weth); + _supplyOnReserve(borrower, (price * 1e6) / 1e8, tokenList.usdx); + _borrowArbitraryAmount(borrower, 1 ether, tokenList.weth); + deal(tokenList.weth, liquidator, 2 ether); + vm.startPrank(liquidator); + IERC20(tokenList.weth).approve(report.poolProxy, type(uint256).max); + + _skip(100); + + contracts.poolProxy.liquidationCall( + tokenList.usdx, + tokenList.weth, + borrower, + type(uint256).max, + false + ); + vm.snapshotGasLastCall('Pool.Operations', 'liquidationCall: deficit on liquidated asset'); + } + + function test_liquidationCall_deficitInAdditionalReserve() external { + uint256 price = contracts.aaveOracle.getAssetPrice(tokenList.weth); + _supplyOnReserve(borrower, (price * 1e6) / 1e8, tokenList.usdx); + _borrowArbitraryAmount(borrower, 1e5, tokenList.wbtc); // additional deficit + _borrowArbitraryAmount(borrower, 1 ether, tokenList.weth); + deal(tokenList.weth, liquidator, 2 ether); + vm.startPrank(liquidator); + IERC20(tokenList.weth).approve(report.poolProxy, type(uint256).max); + + _skip(100); + + contracts.poolProxy.liquidationCall( + tokenList.usdx, + tokenList.weth, + borrower, + type(uint256).max, + false + ); + vm.snapshotGasLastCall('Pool.Operations', 'liquidationCall: deficit on liquidated asset + other asset'); + } +} diff --git a/tests/gas/Pool.Supply.gas.t.sol b/tests/gas/Pool.Supply.gas.t.sol deleted file mode 100644 index c4557e12..00000000 --- a/tests/gas/Pool.Supply.gas.t.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; - -import {Errors} from '../../src/contracts/protocol/libraries/helpers/Errors.sol'; -import {UserConfiguration} from '../../src/contracts/protocol/libraries/configuration/UserConfiguration.sol'; -import {Testhelpers, IERC20} from './Testhelpers.sol'; - -/** - * Scenario suite for supply operations. - */ -contract PoolSupply_gas_Tests is Testhelpers { - function test_supply() external { - _supplyOnReserve(address(this), 100e6, tokenList.usdx); - vm.snapshotGasLastCall('Pool.Supply', 'supply: first supply->collateralEnabled'); - - _skip(100); - - _supplyOnReserve(address(this), 100e6, tokenList.usdx); - vm.snapshotGasLastCall('Pool.Supply', 'supply: collateralEnabled'); - contracts.poolProxy.setUserUseReserveAsCollateral(tokenList.usdx, false); - - _skip(100); - - _supplyOnReserve(address(this), 100e6, tokenList.usdx); - vm.snapshotGasLastCall('Pool.Supply', 'supply: collateralDisabled'); - } -} diff --git a/tests/gas/Testhelpers.sol b/tests/gas/Testhelpers.sol index bba34922..32ac2d58 100644 --- a/tests/gas/Testhelpers.sol +++ b/tests/gas/Testhelpers.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {IERC20} from 'openzeppelin-contracts/contracts/interfaces/IERC20.sol'; +import {IPriceOracleGetter} from '../../src/contracts/interfaces/IPriceOracleGetter.sol'; import {TestnetProcedures} from '../utils/TestnetProcedures.sol'; contract Testhelpers is TestnetProcedures { @@ -23,6 +24,9 @@ contract Testhelpers is TestnetProcedures { _skip(100); // skip some blocks to allow interest to accrue & the block to be not cached } + /** + * Supplies the specified amount of asset to the reserve. + */ function _supplyOnReserve(address user, uint256 amount, address asset) internal { vm.startPrank(user); deal(asset, user, amount); @@ -31,6 +35,21 @@ contract Testhelpers is TestnetProcedures { vm.stopPrank(); } + // assumes that the caller has at least one unit of collateralAsset that is not the borrowAsset + function _borrowArbitraryAmount(address borrower, uint256 amount, address asset) internal { + // set the oracle price of the borrow asset to 0 + vm.mockCall( + address(contracts.aaveOracle), + abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, address(asset)), + abi.encode(0) + ); + // borrow the full emount of the asset + vm.prank(borrower); + contracts.poolProxy.borrow(asset, amount, 2, 0, borrower); + // revert the oracle price + vm.clearMockedCalls(); + } + /** * Skips the specified amount of blocks and adjusts the time accordingly. * Using vm. methods for --via-ir compat. From b86174a4b6847b6eed0651e93c3066a0181d01a1 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 13 Dec 2024 09:40:15 +0100 Subject: [PATCH 3/3] test: borrow some so gas snapshots are more meaningful --- snapshots/Pool.Operations.json | 2 +- tests/gas/Pool.Operations.gas.t.sol | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/snapshots/Pool.Operations.json b/snapshots/Pool.Operations.json index b3d696f9..834b5f33 100644 --- a/snapshots/Pool.Operations.json +++ b/snapshots/Pool.Operations.json @@ -9,7 +9,7 @@ "repay: partial repay": "189971", "supply: collateralDisabled": "146829", "supply: collateralEnabled": "146829", - "supply: first supply->collateralEnabled": "188344", + "supply: first supply->collateralEnabled": "176387", "withdraw: full withdraw": "165303", "withdraw: partial withdraw": "181992" } \ No newline at end of file diff --git a/tests/gas/Pool.Operations.gas.t.sol b/tests/gas/Pool.Operations.gas.t.sol index b950ab12..8318c0f1 100644 --- a/tests/gas/Pool.Operations.gas.t.sol +++ b/tests/gas/Pool.Operations.gas.t.sol @@ -16,6 +16,10 @@ contract PoolOperations_gas_Tests is Testhelpers { address liquidator = makeAddr('liquidator'); function test_supply() external { + // borrow some, so hf checks are not skipped + _supplyOnReserve(supplier, 1 ether, tokenList.weth); + _borrowArbitraryAmount(supplier, 1e5, tokenList.wbtc); + _supplyOnReserve(supplier, 100e6, tokenList.usdx); vm.snapshotGasLastCall('Pool.Operations', 'supply: first supply->collateralEnabled'); @@ -150,6 +154,9 @@ contract PoolOperations_gas_Tests is Testhelpers { type(uint256).max, false ); - vm.snapshotGasLastCall('Pool.Operations', 'liquidationCall: deficit on liquidated asset + other asset'); + vm.snapshotGasLastCall( + 'Pool.Operations', + 'liquidationCall: deficit on liquidated asset + other asset' + ); } }