DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Valid

The function to initialize the whitelisted tokens on L2 will revert due to an out of bound error

Summary

The function to initialize the whitelisted tokens on L2 will revert due to an out of bound error because when the whitelistedStatuses array have 0 elements the loop will try to access the 0 position and since it will not exist, it will revert

Relevant GitHub Links:

https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/main/protocol/contracts/libraries/Silo/LibWhitelistedTokens.sol#L296-L318

Vulnerability Details

When the protocol will be deployed on L2 will follow the next sequence:

reseeds = [
reseed1, // remove all L1 diamond facets, pause beanstalk and transfer bean LPs to BCM
reseedDeployL2Beanstalk, // deploy a new beanstalk on L2
reseed3, // reseed the field component
reseed4, // reseed the barn component
reseed5, // reseed the silo component
reseed6, // reseed internal balances
reseed7, // reseed whitelisted tokens
reseed8, // reseed bean
reseed9 // add all facets to the diamond and unpause it
];

As we can see, the diamond on L2 will not execute any init function, all the reseeds should migrate succesfully all the state from the L1.
However, let's see how it reseeds the whitelisted tokens.

function init(address[] calldata tokens, AssetSettings[] calldata asset) external {
for (uint i; i < tokens.length; i++) {
LibWhitelist.whitelistToken(
tokens[i],
asset[i].selector,
asset[i].stalkIssuedPerBdv,
asset[i].stalkEarnedPerSeason,
asset[i].encodeType,
asset[i].gaugePointImplementation.selector,
asset[i].liquidityWeightImplementation.selector,
asset[i].gaugePoints,
asset[i].optimalPercentDepositedBdv,
asset[i].oracleImplementation
);
}
}

To whitelist the tokens, the function whitelistToken will be be called. Inside there, the main logic is executed from the LibWhitelist::whitelistToken. This function does the following:

function whitelistToken(
address token,
bytes4 selector,
uint32 stalkIssuedPerBdv,
uint32 stalkEarnedPerSeason,
bytes1 encodeType,
bytes4 gaugePointSelector,
bytes4 liquidityWeightSelector,
uint128 gaugePoints,
uint64 optimalPercentDepositedBdv,
Implementation memory oracleImplementation
) internal {
...
verifyWhitelistStatus(token, selector, stalkIssuedPerBdv);
...
}
function verifyWhitelistStatus(
address token,
bytes4 selector,
uint32 stalkIssuedPerBdv
) internal {
(bool isWhitelisted, bool previouslyWhitelisted) = LibWhitelistedTokens.checkWhitelisted(
token
);
...
}

It checks if the token that is wanted to be whitelisted is already in the list to prevent a copy of it. This check is coded in the LibWhitelistedTokens.checkWhitelisted:

function checkWhitelisted(
address token
) internal view returns (bool isWhitelisted, bool previouslyWhitelisted) {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 whitelistedStatusLength = s.sys.silo.whitelistStatuses.length;
uint256 i;
// @audit-issue this function reverts when there are no elements into the dynamic array
while (s.sys.silo.whitelistStatuses[i].token != token) {
i++;
if (i >= whitelistedStatusLength) {
return (false, false);
}
}
if (s.sys.silo.whitelistStatuses[i].isWhitelisted) {
return (true, false);
} else {
return (false, true);
}
}

This function is intended to loop through all the whitelisted tokens in order to find or not the token in the array. However, if the list is empty, it will try to access the first element and it will revert due to out of bound.
Since there will be no initialization of the diamond before the reseeds on L2, the init function to whitelist the tokens will revert due to this error because the array of whitelisted tokens will have 0 elements and the first condition in the while loop will try to access the array s.sys.silo.whitelistStatuses[i].token i being 0 and since it will be empty it will revert.

Impact

Medium, the reseed function will revert but no funds are at risk

Tools Used

Manual review

Recommendations

Add the following changes:

function checkWhitelisted(
address token
) internal view returns (bool isWhitelisted, bool previouslyWhitelisted) {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 whitelistedStatusLength = s.sys.silo.whitelistStatuses.length;
+ if(whitelistedStatusLength == 0){
+ return (false, false);
+ }
uint256 i;
while (s.sys.silo.whitelistStatuses[i].token != token) {
i++;
if (i >= whitelistedStatusLength) {
return (false, false);
}
}
if (s.sys.silo.whitelistStatuses[i].isWhitelisted) {
return (true, false);
} else {
return (false, true);
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

The function to initialize the whitelisted tokens on L2 will revert due to an out of bound error

Support

FAQs

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