20,000 USDC
View results
Submission Details
Severity: gas
Valid

Using memory location for strcuts/arrays will result in gas savings

Summary

Using memory location for strcuts/arrays will result in gas savings

Vulnerability Details

The addToPool function uses pools[poolId] multiple times within its execution, when a certain pool needs to be retrieved from the pools mapping. It accesses the pools mapping each time, which is leading to redundant storage reads. This is leading to less gas-efficient code.

There are 4 instances of this problem and all of them are located in the Lender.sol contract:

https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L178C1-L192C6

/// @notice add to the pool balance
/// can only be called by the pool lender
/// @param poolId the id of the pool to add to
/// @param amount the amount to add
function addToPool(bytes32 poolId, uint256 amount) external {
if (pools[poolId].lender != msg.sender) revert Unauthorized();
if (amount == 0) revert PoolConfig();
_updatePoolBalance(poolId, pools[poolId].poolBalance + amount);
// transfer the loan tokens from the lender to the contract
IERC20(pools[poolId].loanToken).transferFrom(
msg.sender,
address(this),
amount
);
}

https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L194C1-L205C1

/// @notice remove from the pool balance
/// can only be called by the pool lender
/// @param poolId the id of the pool to remove from
/// @param amount the amount to remove
function removeFromPool(bytes32 poolId, uint256 amount) external {
if (pools[poolId].lender != msg.sender) revert Unauthorized();
if (amount == 0) revert PoolConfig();
_updatePoolBalance(poolId, pools[poolId].poolBalance - amount);
// transfer the loan tokens from the contract to the lender
IERC20(pools[poolId].loanToken).transfer(msg.sender, amount);
}

https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L206C1-L215C6

/// @notice update the max loan ratio for a pool
/// can only be called by the pool lender
/// @param poolId the id of the pool to update
/// @param maxLoanRatio the new max loan ratio
function updateMaxLoanRatio(bytes32 poolId, uint256 maxLoanRatio) external {
if (pools[poolId].lender != msg.sender) revert Unauthorized();
if (maxLoanRatio == 0) revert PoolConfig();
pools[poolId].maxLoanRatio = maxLoanRatio;
emit PoolMaxLoanRatioUpdated(poolId, maxLoanRatio);
}

https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L217C1-L226C6

/// @notice update the interest rate for a pool
/// can only be called by the pool lender
/// @param poolId the id of the pool to update
/// @param interestRate the new interest rate
function updateInterestRate(bytes32 poolId, uint256 interestRate) external {
if (pools[poolId].lender != msg.sender) revert Unauthorized();
if (interestRate > MAX_INTEREST_RATE) revert PoolConfig();
pools[poolId].interestRate = interestRate;
emit PoolInterestRateUpdated(poolId, interestRate);
}

Impact

There is no caching for the pools[poolId struct in memory, which can result in increased gas consumption. This problem can lead to higher transaction and execution costs for each user who interacts with the given contract and its functionality.

Tools Used

Manual Review

Recommendations

In the current state of the contract, the storage variable is retrieved multiple times, which will result in higher gas consumption. We can solve this by declaring a struct variable with a memory location to save gas when invoking variables from the struct and storing a temporary copy. This way the contract can avoid repeated storage reads, leading to significant gas savings.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.