Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Some users might not be able to withdraw their funds from queue for whole distribution period

Summary

The vulnerability lies in the interaction between two ChainLink functions: getAccountData() and updateDistribution(). The trusted party gathers on-chain account data using getAccountData() and constructs a Merkle tree. It then calls updateDistribution() to update the distribution process. During this call, the merkleTreeSize is set to the current number of accounts (merkleTreeSize = accounts.length). However, there is a potential for the number of accounts to change between the getAccountData() and updateDistribution() steps. A user could deposit or queue funds with a new account after the getAccountData() step but before updateDistribution(). This results in their account being incorrectly considered part of the Merkle tree for the distribution.

Vulnerability Details

Trusted party in ChainLink takes on-chain data using getAccountData() function and then builds merkle tree and calls updateDistribution(). Importantly, it sets merkleTreeSize = accounts.length:

function updateDistribution(
bytes32 _merkleRoot,
bytes32 _ipfsHash,
uint256 _amountDistributed,
uint256 _sharesAmountDistributed
) external onlyDistributionOracle {
// [...]
@> merkleTreeSize = accounts.length;

There is possibility that amount of accounts will differ between getAccountData() and updateDistribution().

This has consequences, that the user can't unqueue the funds until next distibution due to the accountIndexes[account] < merkleTreeSize check:

if (_merkleProof.length != 0) {
bytes32 node = keccak256(
bytes.concat(keccak256(abi.encode(account, _amount, _sharesAmount)))
);
if (!MerkleProofUpgradeable.verify(_merkleProof, merkleRoot, node))
revert InvalidProof();
// @audit user should be able to unqueue immediately if they queued tokens with new account after last distribution
} else if (accountIndexes[account] < merkleTreeSize) {
revert InvalidProof();
}

Technically this check will verify that the account is newer than the one saved during last distribution. However, as mentioned earlier, the user can deposit/queue the funds from account that never did it before between getAccountData() and updateDistribution(), making it count as if the user was already a part of the distribution.

Impact

Due to the accountIndexes[account] < merkleTreeSize check, the user might be unable to unqueue their funds until the next distribution cycle, as their account appears "newer" than those involved in the last distribution.

Tools Used

Manual review

Recommendations

We recommend adding number of accounts in the merkle tree to the tree and using it during withdrawal. This way, merkle tree size will always reflect the amount of accounts in the merkle tree.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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