Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ChaseTheLight01/02bba40fc1046f1b102ad48d3a541145 to your computer and use it in GitHub Desktop.
Save ChaseTheLight01/02bba40fc1046f1b102ad48d3a541145 to your computer and use it in GitHub Desktop.
LightChaserV3_Cantina_Aave_protocol_v3_1_upgrade

LightChaser-V3

Generated for: Cantina : Aave protocol-v3.1-upgrade

Generated on: 2024-05-10

Total findings: 25

Total Low findings: 6

Total Gas findings: 8

Total NonCritical findings: 11

Summary for Low findings

Number Details Instances
[Low-1] Solidity version 0.8.20 won't work on all chains due to PUSH0 1
[Low-2] Upgradable contracts should have a __gap variable 1
[Low-3] Initializer function can be front run 1
[Low-4] Missing zero address check in constructor 1
[Low-5] Constructors missing validation 1
[Low-6] Consider a uptime feed on L2 deployments to prevent issues caused by downtime 1

Summary for NonCritical findings

Number Details Instances
[NonCritical-1] Superfluous parameter can only be one value 1
[NonCritical-2] Library has public/external functions 1
[NonCritical-3] Floating pragma should be avoided 1
[NonCritical-4] Interfaces should be declared in a separate file 1
[NonCritical-5] Default int values are manually set 2
[NonCritical-6] Old Solidity version 1
[NonCritical-7] Interface imports should be declared first 1
[NonCritical-8] Initialize functions do not emit an event 2
[NonCritical-9] Use 'using' keyword when using specific imports rather than calling the specific import directly 2
[NonCritical-10] Constructors should emit an event 1
[NonCritical-11] Contract and Abstract files should have a fixed compiler version 1

Summary for Gas findings

Number Details Instances Gas
[Gas-1] Use assembly to check for the zero address 1 0.0
[Gas-2] Lack of unchecked in loops 4 960
[Gas-3] Solidity versions 0.8.19 and above are more gas efficient 1 1000
[Gas-4] Variable declared within iteration 4 0.0
[Gas-5] Calling .length in a for loop wastes gas 2 388
[Gas-6] Constructors can be marked as payable to save deployment gas 3 0.0
[Gas-7] Use OZ Array.unsafeAccess() to avoid repeated array length checks 4 33600
[Gas-8] State variable read in a loop 2 23176

[Low-1] Solidity version 0.8.20 won't work on all chains due to PUSH0

Resolution

Solidity version 0.8.20 uses the new Shanghai EVM which introduces the PUSH0 opcode, this may not be implemented on all chains and L2 thus reducing the portability and compatability of the code. Consider using a earlier solidity version.

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity ^0.8.0; // <= FOUND

[Low-2] Upgradable contracts should have a __gap variable

Resolution

This is to ensure the team can add new state variables without compromising compatability.

Num of instances: 1

Findings

Click to show findings

['40']

40: contract UpgradePayload is IProposalGenericExecutor 

[Low-3] Initializer function can be front run

Resolution

In Solidity contract deployment, not making the initialize() function call atomic with the contract creation can leave a vulnerability window. A malicious actor could exploit this time gap and call initialize() before the intended initialization. This action could disrupt the contract's setup, potentially necessitating a full contract re-deployment to ensure proper initialization. To mitigate such risks, it's advised to use a factory contract. This factory contract can be programmed to deploy and initialize a new contract in a single atomic transaction, closing the window of vulnerability and ensuring correct and secure contract initialization.

Num of instances: 1

Findings

Click to show findings

['15']

15:   function initialize(IPoolAddressesProvider provider) external virtual override initializer  // <= FOUND

[Low-4] Missing zero address check in constructor

Resolution

In Solidity, constructors often take address parameters to initialize important components of a contract, such as owner or linked contracts. However, without a check, there's a risk that an address parameter could be mistakenly set to the zero address (0x0). This could occur due to a mistake or oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address, and any funds sent to it are irretrievable. Therefore, it's crucial to include a zero address check in constructors to prevent such potential problems. If a zero address is detected, the constructor should revert the transaction.

Num of instances: 1

Findings

Click to show findings

['49']

49:   constructor(
50:     address poolAddressesProvider, // <= FOUND
51:     address pool, // <= FOUND
52:     address configurator, // <= FOUND
53:     address poolImpl, // <= FOUND
54:     address poolDataProvider // <= FOUND
55:   ) {
56:     POOL_ADDRESSES_PROVIDER = IPoolAddressesProvider(poolAddressesProvider);
57:     POOL = IPool(pool);
58:     CONFIGURATOR = IPoolConfigurator(configurator);
59:     DEFAULT_IR = new DefaultReserveInterestRateStrategyV2(address(poolAddressesProvider));
60:     POOL_IMPL = poolImpl;
61:     POOL_DATA_PROVIDER = poolDataProvider;
62:   }

[Low-5] Constructors missing validation

Resolution

In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.

Num of instances: 1

Findings

Click to show findings

['49']

49:   constructor(
50:     address poolAddressesProvider,
51:     address pool,
52:     address configurator,
53:     address poolImpl,
54:     address poolDataProvider
55:   ) {
56:     POOL_ADDRESSES_PROVIDER = IPoolAddressesProvider(poolAddressesProvider);
57:     POOL = IPool(pool);
58:     CONFIGURATOR = IPoolConfigurator(configurator);
59:     DEFAULT_IR = new DefaultReserveInterestRateStrategyV2(address(poolAddressesProvider));
60:     POOL_IMPL = poolImpl; // <= FOUND
61:     POOL_DATA_PROVIDER = poolDataProvider; // <= FOUND
62:   }

[Low-6] Consider a uptime feed on L2 deployments to prevent issues caused by downtime

Resolution

In L2 deployments, incorporating an uptime feed is crucial to mitigate issues arising from sequencer downtime. Downtime can disrupt services, leading to transaction failures or incorrect data readings, affecting overall system reliability. By integrating an uptime feed, you gain insight into the operational status of the L2 network, enabling proactive measures like halting sensitive operations or alerting users. This approach ensures that your contract behaves predictably and securely during network outages, enhancing the robustness and reliability of your decentralized application, which is especially important in mission-critical or high-stakes environments.

Num of instances: 1

Findings

Click to show findings

['40']

40: contract UpgradePayload is IProposalGenericExecutor  // <= FOUND

[NonCritical-1] Superfluous parameter can only be one value

Resolution

Using redundant parameters in smart contracts can lead to unnecessary complexity and potential vulnerabilities. When a function parameter is always constrained to a specific value due to an if or require statement, it renders the parameter superfluous. Including such parameters can be misleading to developers or users, suggesting a flexibility that doesn't exist in reality. Additionally, unnecessary parameters increase the gas cost for transactions. Resolution: Analyze the contract to identify parameters that are rendered static by conditional checks. Remove these parameters from the function signature and update the function logic accordingly. This simplifies the code, reduces gas costs, and enhances clarity and security.

Num of instances: 1

Findings

Click to show findings

['15']

15:   function initialize(IPoolAddressesProvider provider) external virtual override initializer { // <= FOUND
16:     require(provider == ADDRESSES_PROVIDER, Errors.INVALID_ADDRESSES_PROVIDER); // <= FOUND
17:     PoolRevisionFourInitialize.initialize(_reservesCount, _reservesList, _reserves);
18:   }

[NonCritical-2] Library has public/external functions

Resolution

Libraries in Solidity are meant to be static collections of functions. They are deployed once and their code is reused by contracts through DELEGATECALL, which executes the library's code in the context of the calling contract. Public or external functions in libraries can be misleading because libraries are not supposed to maintain state or have external interactions in the way contracts do. Designing libraries with these kinds of functions goes against their intended purpose and can lead to confusion or misuse. For state management or external interactions, using contracts instead of libraries is more appropriate. Libraries should focus on utility functions that can operate on the data passed to them without side effects, enhancing code reuse and gas efficiency.

Num of instances: 1

Findings

Click to show findings

['12']

12: library PoolRevisionFourInitialize { // <= FOUND
13:   using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
14: 
15:   function initialize( // <= FOUND
16:     uint256 reservesCount,
17:     mapping(uint256 => address) storage _reservesList,
18:     mapping(address => DataTypes.ReserveData) storage _reserves
19:   ) external {
20:     for (uint256 i = 0; i < reservesCount; i++) {
21:       address currentReserveAddress = _reservesList[i];
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) ||
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress;
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken);
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply();
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply();
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply();
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul(
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul(
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }
63:   }
64: }

[NonCritical-3] Floating pragma should be avoided

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity ^0.8.0; // <= FOUND

[NonCritical-4] Interfaces should be declared in a separate file

Resolution

It is general standard to declare interfaces on files separate from regular contract declarations

Num of instances: 1

Findings

Click to show findings

['12']

12: interface ILegacyDefaultInterestRateStrategy  // <= FOUND

[NonCritical-5] Default int values are manually set

Resolution

In instances where a new variable is defined, there is no need to set it to it's default value.

Num of instances: 2

Findings

Click to show findings

['20']

20:     for (uint256 i = 0; i < reservesCount; i++) { // <= FOUND

['70']

70:     for (uint256 i = 0; i < reserves.length; i++) { // <= FOUND

[NonCritical-6] Old Solidity version

Resolution

Using outdated Solidity versions can lead to security risks and inefficiencies. It's recommended to adopt newer versions, ideally the latest, which as of now is 0.8.24. This ensures access to the latest bug fixes, features, and optimizations, particularly crucial for Layer 2 deployments. Regular updates to versions like 0.8.19 or later, up to 0.8.24, enhance contract security and performance.

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity ^0.8.0; // <= FOUND

[NonCritical-7] Interface imports should be declared first

Resolution

Amend the ordering of imports to import interfaces first followed by other imports

Num of instances: 1

Findings

Click to show findings

['2']

2: 
3: pragma solidity ^0.8.0;
4: import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol'; // <= FOUND
5: import {IPoolAddressesProvider} from 'aave-v3-origin/core/contracts/interfaces/IPoolAddressesProvider.sol'; // <= FOUND
6: import {IPool, DataTypes} from 'aave-v3-origin/core/contracts/interfaces/IPool.sol'; // <= FOUND
7: import {IPoolConfigurator} from 'aave-v3-origin/core/contracts/interfaces/IPoolConfigurator.sol'; // <= FOUND
8: import {PoolConfiguratorInstance} from 'aave-v3-origin/core/instances/PoolConfiguratorInstance.sol'; // <= FOUND
9: import {DefaultReserveInterestRateStrategyV2} from 'aave-v3-origin/core/contracts/protocol/pool/DefaultReserveInterestRateStrategyV2.sol'; // <= FOUND
10: import {IDefaultInterestRateStrategyV2} from 'aave-v3-origin/core/contracts/interfaces/IDefaultInterestRateStrategyV2.sol'; // <= FOUND
11: import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; // <= FOUND
12: 
13: interface ILegacyDefaultInterestRateStrategy {
14:   
18:   function OPTIMAL_USAGE_RATIO() external view returns (uint256);
19: 
25:   function getVariableRateSlope1() external view returns (uint256);
26: 
32:   function getVariableRateSlope2() external view returns (uint256);
33: 
38:   function getBaseVariableBorrowRate() external view returns (uint256);
39: }
40: 
41: contract UpgradePayload is IProposalGenericExecutor {
42:   IPoolAddressesProvider public immutable POOL_ADDRESSES_PROVIDER;
43:   IPool public immutable POOL;
44:   IPoolConfigurator public immutable CONFIGURATOR;
45:   DefaultReserveInterestRateStrategyV2 public immutable DEFAULT_IR;
46: 
47:   address public immutable POOL_IMPL;
48:   address public immutable POOL_DATA_PROVIDER;
49: 
50:   constructor(
51:     address poolAddressesProvider,
52:     address pool,
53:     address configurator,
54:     address poolImpl,
55:     address poolDataProvider
56:   ) {
57:     POOL_ADDRESSES_PROVIDER = IPoolAddressesProvider(poolAddressesProvider);
58:     POOL = IPool(pool);
59:     CONFIGURATOR = IPoolConfigurator(configurator);
60:     DEFAULT_IR = new DefaultReserveInterestRateStrategyV2(address(poolAddressesProvider));
61:     POOL_IMPL = poolImpl;
62:     POOL_DATA_PROVIDER = poolDataProvider;
63:   }
64: 
65:   function execute() external {
66:     POOL_ADDRESSES_PROVIDER.setPoolConfiguratorImpl(address(new PoolConfiguratorInstance()));
67:     POOL_ADDRESSES_PROVIDER.setPoolImpl(POOL_IMPL);
68:     POOL_ADDRESSES_PROVIDER.setPoolDataProvider(POOL_DATA_PROVIDER);
69: 
70:     address[] memory reserves = POOL.getReservesList();
71:     for (uint256 i = 0; i < reserves.length; i++) {
72:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]);
73:       uint256 currentUOpt;
74: 
75:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
76:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT();
77:       } else {
78:         currentUOpt =
79:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
80:             .OPTIMAL_USAGE_RATIO() /
81:           1e23;
82:       }
83: 
84:       CONFIGURATOR.setReserveInterestRateStrategyAddress(
85:         reserves[i],
86:         address(DEFAULT_IR),
87:         abi.encode(
88:           IDefaultInterestRateStrategyV2.InterestRateData({
89:             optimalUsageRatio: uint16(currentUOpt),
90:             baseVariableBorrowRate: uint32(
91:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
92:                 .getBaseVariableBorrowRate() / 1e23
93:             ),
94:             variableRateSlope1: uint32(
95:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
96:                 .getVariableRateSlope1() / 1e23
97:             ),
98:             variableRateSlope2: uint32(
99:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
100:                 .getVariableRateSlope2() / 1e23
101:             )
102:           })
103:         )
104:       );
105:     }
106:   }
107: }
108: 

[NonCritical-8] Initialize functions do not emit an event

Resolution

Emitting an event within initializer functions in Solidity is a best practice for providing transparency and traceability. Initializer functions set the initial state and values of an upgradeable contract. Emitting an event during initialization allows anyone to verify and audit the initial state of the contract via the transaction logs. This can be particularly useful for verifying the parameters set during initialization, tracking the contract's deployment, and troubleshooting or debugging. Therefore, developers should include an event emission in their initializer functions, providing a clear record of the contract's initialization and enhancing the contract's transparency and security.

Num of instances: 2

Findings

Click to show findings

['15']

15:   function initialize(IPoolAddressesProvider provider) external virtual override initializer { // <= FOUND
16:     require(provider == ADDRESSES_PROVIDER, Errors.INVALID_ADDRESSES_PROVIDER);
17:     PoolRevisionFourInitialize.initialize(_reservesCount, _reservesList, _reserves);
18:   }

['15']

15:   function initialize( // <= FOUND
16:     uint256 reservesCount,
17:     mapping(uint256 => address) storage _reservesList,
18:     mapping(address => DataTypes.ReserveData) storage _reserves
19:   ) external {
20:     for (uint256 i = 0; i < reservesCount; i++) {
21:       address currentReserveAddress = _reservesList[i];
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) ||
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress;
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken);
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply();
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply();
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply();
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul(
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul(
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }
63:   }

[NonCritical-9] Use 'using' keyword when using specific imports rather than calling the specific import directly

Resolution

In Solidity, the using keyword can streamline the use of library functions for specific types. Instead of calling library functions directly with their full import paths, you can declare a library once with using for a specific type. This approach makes your code more readable and concise. For example, instead of LibraryName.functionName(variable), you would first declare using LibraryName for TypeName; at the contract level. After this, you can call library functions directly on variables of TypeName like variable.functionName(). This method not only enhances code clarity but also promotes cleaner and more organized code, especially when multiple functions from the same library are used frequently.

Num of instances: 2

Findings

Click to show findings

['57']

57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance); // <= FOUND 'SafeCast.'

['17']

17:     PoolRevisionFourInitialize.initialize(_reservesCount, _reservesList, _reserves); // <= FOUND 'PoolRevisionFourInitialize.'

[NonCritical-10] Constructors should emit an event

Resolution

Emitting an event in a constructor of a smart contract provides transparency and traceability in blockchain applications. This event logs the contract’s creation, aiding in monitoring and verifying contract deployment. Although constructors are executed only once, the emitted event ensures the contract's initialization is recorded on the blockchain.

Num of instances: 1

Findings

Click to show findings

['49']

49:   constructor( // <= FOUND
50:     address poolAddressesProvider,
51:     address pool,
52:     address configurator,
53:     address poolImpl,
54:     address poolDataProvider
55:   ) {
56:     POOL_ADDRESSES_PROVIDER = IPoolAddressesProvider(poolAddressesProvider);
57:     POOL = IPool(pool);
58:     CONFIGURATOR = IPoolConfigurator(configurator);
59:     DEFAULT_IR = new DefaultReserveInterestRateStrategyV2(address(poolAddressesProvider));
60:     POOL_IMPL = poolImpl;
61:     POOL_DATA_PROVIDER = poolDataProvider;
62:   }

[NonCritical-11] Contract and Abstract files should have a fixed compiler version

Resolution

Using a fixed compiler version in Solidity contract and abstract files ensures consistency and predictability in smart contract behavior. A fixed version prevents unforeseen issues arising from compiler updates, which might introduce breaking changes or new bugs. It guarantees that the contract's behavior remains stable and consistent with the version used during its development and testing.

Num of instances: 1

Findings

Click to show findings

[]

40: contract UpgradePayload is IProposalGenericExecutor 

[Gas-1] Use assembly to check for the zero address

Resolution

Using assembly for address comparisons in Solidity can save gas because it allows for more direct access to the Ethereum Virtual Machine (EVM), reducing the overhead of higher-level operations. Solidity's high-level abstraction simplifies coding but can introduce additional gas costs. Using assembly for simple operations like address comparisons can be more gas-efficient.

Num of instances: 1

Findings

Click to show findings

['15']

15:   function initialize(
16:     uint256 reservesCount,
17:     mapping(uint256 => address) storage _reservesList,
18:     mapping(address => DataTypes.ReserveData) storage _reserves
19:   ) external {
20:     for (uint256 i = 0; i < reservesCount; i++) {
21:       address currentReserveAddress = _reservesList[i];
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) || // <= FOUND
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress;
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken);
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply();
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply();
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply();
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul(
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul(
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }
63:   }

[Gas-2] Lack of unchecked in loops

Resolution

In Solidity, the unchecked block allows arithmetic operations to not revert on overflow. Without using unchecked in loops, extra gas is consumed due to overflow checks. If it's certain that overflows won't occur within the loop, using unchecked can make the loop more gas-efficient by skipping unnecessary checks.

Num of instances: 4

Findings

Click to show findings

['20']

20:    for (uint256 i = 0; i < reservesCount; i++) {
21:       address currentReserveAddress = _reservesList[i];
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) ||
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress;
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken);
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply();
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply();
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply();
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul(
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul(
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }

['70']

70:    for (uint256 i = 0; i < reserves.length; i++) {
71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]);
72:       uint256 currentUOpt;
73: 
74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
75:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT();
76:       } else {
77:         currentUOpt =
78:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
79:             .OPTIMAL_USAGE_RATIO() /
80:           1e23;
81:       }
82: 
83:       CONFIGURATOR.setReserveInterestRateStrategyAddress(
84:         reserves[i],
85:         address(DEFAULT_IR),
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })
102:         )
103:       );
104:     }

['20']

20:     for (uint256 i = 0; i < reservesCount; i++) {
21:       address currentReserveAddress = _reservesList[i];
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) ||
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress;
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken);
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply();
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply();
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply();
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul(
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul(
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }

['70']

70:     for (uint256 i = 0; i < reserves.length; i++) {
71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]);
72:       uint256 currentUOpt;
73: 
74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
75:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT();
76:       } else {
77:         currentUOpt =
78:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
79:             .OPTIMAL_USAGE_RATIO() /
80:           1e23;
81:       }
82: 
83:       CONFIGURATOR.setReserveInterestRateStrategyAddress(
84:         reserves[i],
85:         address(DEFAULT_IR),
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })
102:         )
103:       );
104:     }

[Gas-3] Solidity versions 0.8.19 and above are more gas efficient

Resolution

Solidity version 0.8.19 introduced a array of gas optimizations which make contracts which use it more efficient. Provided compatability it may be beneficial to upgrade to this version

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity ^0.8.0;

[Gas-4] Variable declared within iteration

Resolution

Please elaborate and generalise the following with detail and feel free to use your own knowledge and lmit ur words to 100 words please:

Num of instances: 4

Findings

Click to show findings

['20']

20:    for (uint256 i = 0; i < reservesCount; i++) { // <= FOUND
21:       address currentReserveAddress = _reservesList[i]; // <= FOUND
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) ||
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress; // <= FOUND
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken); // <= FOUND
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply(); // <= FOUND
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply(); // <= FOUND
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply(); // <= FOUND
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul( // <= FOUND
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul( // <= FOUND
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }

['20']

20:     for (uint256 i = 0; i < reservesCount; i++) { // <= FOUND
21:       address currentReserveAddress = _reservesList[i]; // <= FOUND
22:       
23:       
24:       if (
25:         currentReserveAddress == address(0) ||
26:         currentReserveAddress == AaveV3EthereumAssets.GHO_UNDERLYING
27:       ) {
28:         continue;
29:       }
30: 
31:       DataTypes.ReserveData storage currentReserve = _reserves[currentReserveAddress];
32:       address currentAToken = currentReserve.aTokenAddress; // <= FOUND
33:       uint256 balanceOfUnderlying = IERC20(currentReserveAddress).balanceOf(currentAToken); // <= FOUND
34:       uint256 aTokenTotalSupply = IERC20(currentAToken).totalSupply(); // <= FOUND
35:       uint256 vTokenTotalSupply = IERC20(currentReserve.variableDebtTokenAddress).totalSupply(); // <= FOUND
36:       uint256 sTokenTotalSupply = IERC20(currentReserve.stableDebtTokenAddress).totalSupply(); // <= FOUND
37: 
38:       
39:       uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
40:         currentReserve.currentLiquidityRate,
41:         currentReserve.lastUpdateTimestamp
42:       );
43:       uint256 nextLiquidityIndex = WadRayMath.rayMul( // <= FOUND
44:         cumulatedLiquidityInterest,
45:         currentReserve.liquidityIndex
46:       );
47:       uint256 accruedToTreasury = WadRayMath.rayMul( // <= FOUND
48:         currentReserve.accruedToTreasury,
49:         nextLiquidityIndex
50:       );
51: 
52:       uint256 currentVirtualBalance = (aTokenTotalSupply + accruedToTreasury) -
53:         (sTokenTotalSupply + vTokenTotalSupply);
54:       if (balanceOfUnderlying < currentVirtualBalance) {
55:         currentVirtualBalance = balanceOfUnderlying;
56:       }
57:       currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance);
58: 
59:       DataTypes.ReserveConfigurationMap memory currentConfiguration = currentReserve.configuration;
60:       currentConfiguration.setVirtualAccActive(true);
61:       currentReserve.configuration = currentConfiguration;
62:     }

['70']

70:    for (uint256 i = 0; i < reserves.length; i++) { // <= FOUND
71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]);
72:       uint256 currentUOpt; // <= FOUND
73: 
74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
75:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT();
76:       } else {
77:         currentUOpt =
78:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
79:             .OPTIMAL_USAGE_RATIO() /
80:           1e23;
81:       }
82: 
83:       CONFIGURATOR.setReserveInterestRateStrategyAddress(
84:         reserves[i],
85:         address(DEFAULT_IR),
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })
102:         )
103:       );
104:     }

['70']

70:     for (uint256 i = 0; i < reserves.length; i++) { // <= FOUND
71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]);
72:       uint256 currentUOpt; // <= FOUND
73: 
74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
75:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT();
76:       } else {
77:         currentUOpt =
78:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
79:             .OPTIMAL_USAGE_RATIO() /
80:           1e23;
81:       }
82: 
83:       CONFIGURATOR.setReserveInterestRateStrategyAddress(
84:         reserves[i],
85:         address(DEFAULT_IR),
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })
102:         )
103:       );
104:     }

[Gas-5] Calling .length in a for loop wastes gas

Resolution

Rather than calling .length for an array in a for loop declaration, it is far more gas efficient to cache this length before and use that instead. This will prevent the array length from being called every loop iteration

Num of instances: 2

Findings

Click to show findings

['70']

70: for (uint256 i = 0; i < reserves.length; i++)  // <= FOUND

['70']

70: for (uint256 i = 0; i < reserves.length; i++)  // <= FOUND

[Gas-6] Constructors can be marked as payable to save deployment gas

Num of instances: 3

Findings

Click to show findings

['10']

10:   constructor(IPoolAddressesProvider provider) PoolInstanceWithCustomInitialize(provider) {}

['13']

13:   constructor(IPoolAddressesProvider provider) PoolInstance(provider) {}

['49']

49:   constructor(
50:     address poolAddressesProvider,
51:     address pool,
52:     address configurator,
53:     address poolImpl,
54:     address poolDataProvider
55:   ) {
56:     POOL_ADDRESSES_PROVIDER = IPoolAddressesProvider(poolAddressesProvider);
57:     POOL = IPool(pool);
58:     CONFIGURATOR = IPoolConfigurator(configurator);
59:     DEFAULT_IR = new DefaultReserveInterestRateStrategyV2(address(poolAddressesProvider));
60:     POOL_IMPL = poolImpl;
61:     POOL_DATA_PROVIDER = poolDataProvider;
62:   }

[Gas-7] Use OZ Array.unsafeAccess() to avoid repeated array length checks

Resolution

The OpenZeppelin Array.unsafeAccess() method is a optimization strategy for Solidity, aimed at reducing gas costs by bypassing automatic length checks on storage array accesses. In Solidity, every access to an array element involves a hidden gas cost due to a length check, ensuring that accesses do not exceed the array bounds. However, if a developer has already verified the array's bounds earlier in the function or knows through logic that the access is safe, directly accessing the array elements without redundant length checks can save gas. This approach requires careful consideration to avoid out-of-bounds errors, as it trades off safety checks for efficiency.

Num of instances: 4

Findings

Click to show findings

['21']

21:       address currentReserveAddress = _reservesList[i]; // <= FOUND

['71']

71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]); // <= FOUND

['74']

74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) { // <= FOUND

['83']

83:       CONFIGURATOR.setReserveInterestRateStrategyAddress(
84:         reserves[i], // <= FOUND
85:         address(DEFAULT_IR),
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })

[Gas-8] State variable read in a loop

Resolution

Reading a state variable inside a loop in a smart contract can unnecessarily increase gas consumption, as each read operation from the blockchain state is costly. This inefficiency becomes pronounced in loops with many iterations. To optimize gas usage, it's advisable to read the state variable once before the loop starts, store its value in a local (memory) variable, and then use this local variable within the loop. This approach minimizes the number of state read operations, thereby reducing the gas cost associated with executing the contract function, making the smart contract more efficient and cost-effective to run.

Num of instances: 2

Findings

Click to show findings

['70']

70:    for (uint256 i = 0; i < reserves.length; i++) {
71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]); // <= FOUND
72:       uint256 currentUOpt;
73: 
74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
75:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT(); // <= FOUND
76:       } else {
77:         currentUOpt =
78:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
79:             .OPTIMAL_USAGE_RATIO() /
80:           1e23;
81:       }
82: 
83:       CONFIGURATOR.setReserveInterestRateStrategyAddress( // <= FOUND
84:         reserves[i],
85:         address(DEFAULT_IR), // <= FOUND
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })
102:         )
103:       );
104:     }

['70']

70:     for (uint256 i = 0; i < reserves.length; i++) {
71:       DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(reserves[i]); // <= FOUND
72:       uint256 currentUOpt;
73: 
74:       if (reserves[i] == AaveV3EthereumAssets.GHO_UNDERLYING) {
75:         currentUOpt = DEFAULT_IR.MAX_OPTIMAL_POINT(); // <= FOUND
76:       } else {
77:         currentUOpt =
78:           ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
79:             .OPTIMAL_USAGE_RATIO() /
80:           1e23;
81:       }
82: 
83:       CONFIGURATOR.setReserveInterestRateStrategyAddress( // <= FOUND
84:         reserves[i],
85:         address(DEFAULT_IR), // <= FOUND
86:         abi.encode(
87:           IDefaultInterestRateStrategyV2.InterestRateData({
88:             optimalUsageRatio: uint16(currentUOpt),
89:             baseVariableBorrowRate: uint32(
90:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
91:                 .getBaseVariableBorrowRate() / 1e23
92:             ),
93:             variableRateSlope1: uint32(
94:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
95:                 .getVariableRateSlope1() / 1e23
96:             ),
97:             variableRateSlope2: uint32(
98:               ILegacyDefaultInterestRateStrategy(reserveData.interestRateStrategyAddress)
99:                 .getVariableRateSlope2() / 1e23
100:             )
101:           })
102:         )
103:       );
104:     }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment