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 |
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 |
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 |
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
This is to ensure the team can add new state variables without compromising compatability.
Num of instances: 1
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
Click to show findings
['15']
15: function initialize(IPoolAddressesProvider provider) external virtual override initializer // <= FOUND
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
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: }
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
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: }
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
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
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: }
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
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: }
Num of instances: 1
It is general standard to declare interfaces on files separate from regular contract declarations
Num of instances: 1
In instances where a new variable is defined, there is no need to set it to it's default value.
Num of instances: 2
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
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
Amend the ordering of imports to import interfaces first followed by other imports
Num of instances: 1
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:
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
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
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
Click to show findings
['57']
57: currentReserve.virtualUnderlyingBalance = SafeCast.toUint128(currentVirtualBalance); // <= FOUND 'SafeCast.'
['17']
17: PoolRevisionFourInitialize.initialize(_reservesCount, _reservesList, _reserves); // <= FOUND 'PoolRevisionFourInitialize.'
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
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: }
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
Click to show findings
[]
40: contract UpgradePayload is IProposalGenericExecutor
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
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: }
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
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: }
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
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
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: }
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
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
Num of instances: 3
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: }
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
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: })
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
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: }