When calculating TVL it iterates over all the operator delegators and inside it iterates over all the collateral tokens.
for (uint256 i = 0; i < odLength; ) {
...
// Iterate through the tokens and get the value of each
uint256 tokenLength = collateralTokens.length;
for (uint256 j = 0; j < tokenLength; ) {
...
// record token value of withdraw queue
if (!withdrawQueueTokenBalanceRecorded) {
totalWithdrawalQueueValue += renzoOracle.lookupTokenValue(
collateralTokens[i],
collateralTokens[j].balanceOf(withdrawQueue)
);
}
unchecked {
++j;
}
}
...
unchecked {
++i;
}
}However, the balance of withdrawQueue is incorrectly fetched, specifically this line:
totalWithdrawalQueueValue += renzoOracle.lookupTokenValue(
collateralTokens[i],
collateralTokens[j].balanceOf(withdrawQueue)
);It uses an incorrect index of the outer loop i to access the collateralTokens. i belongs to the operator delegator index, thus the returned value will not represent the real value of the token. For instance, if there is 1 OD and 3 collateral tokens, it will add the balance of the first token 3 times and neglect the other 2 tokens. If there are more ODs than collateral tokens, the the execution will revert (index out of bounds).
This calculation impacts the TVL which is the essential data when calculating mint/redeem and other critical values. A miscalculation in TVL could have devastating results.
Proof of Concept
A simplified version of the function to showcase that the same token (in this case address(1)) is emitted multiple times and other tokens are untouched:
contract RestakeManager {
address[] public operatorDelegators;
address[] public collateralTokens;
event CollateralTokenLookup(address token);
constructor() {
operatorDelegators.push(msg.sender);
collateralTokens.push(address(1));
collateralTokens.push(address(2));
collateralTokens.push(address(3));
}
function calculateTVLs() public {
// Iterate through the ODs
uint256 odLength = operatorDelegators.length;
for (uint256 i = 0; i < odLength; ) {
// Iterate through the tokens and get the value of each
uint256 tokenLength = collateralTokens.length;
for (uint256 j = 0; j < tokenLength; ) {
emit CollateralTokenLookup(collateralTokens[i]);
unchecked {
++j;
}
}
unchecked {
++i;
}
}
}
}Recommended Mitigation Steps
Change to collateralTokens[j].
Assessed type
Math