Liquid Staking

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

contracts/metisStaking/SequencerVault.sol

Here are some potential vulnerabilities and concerns I identified in the provided SequencerVault smart contract:


1. Unchecked Initialization Front-Running (Initialization Security)

  • Issue: The initialize function is public and could be front-run if deployed incorrectly. If the contract deployer fails to call the initializer quickly, an attacker could initialize the contract first, gaining control.

  • Recommendation:

    • Use initializer modifier carefully.

    • Confirm that _disableInitializers() in the constructor is properly preventing re-initialization during deployment.

    • Consider restricting who can call the initialize function, perhaps using an access control mechanism or constructor arguments in a controlled environment.


2. Missing Zero Address Checks (initialize Function)

  • Issue: No zero address validation for _token, _vaultController, _lockingPool, or _signer. This could lead to unexpected behavior if invalid addresses are passed.

  • Recommendation:

    • Add zero address checks to ensure these addresses are valid:

      if (_token == address(0) || _vaultController == address(0) ||
      _lockingPool == address(0) || _signer == address(0)) revert ZeroAddress();

3. Unchecked Arithmetic Overflow (uint128)

  • Issue: Although you are using SafeCastUpgradeable to cast values, there are arithmetic operations on uint128 (e.g., trackedTotalDeposits += ...) without overflow checks.

  • Impact: An overflow could result in wrong accounting.

  • Recommendation: Either:

    • Use SafeMath-style operations on uint128, or

    • Perform explicit overflow checks:

      uint128 newTotal = trackedTotalDeposits + SafeCastUpgradeable.toUint128(_amount);
      require(newTotal >= trackedTotalDeposits, "Overflow occurred");
      trackedTotalDeposits = newTotal;

4. Unchecked Return Value on ERC20 Approvals

  • Issue: Some ERC20 tokens do not follow the ERC20 standard correctly and may return false on approve calls. This may lead to approvals silently failing.

  • Recommendation:

    • Wrap the approve call with a require statement:

      require(token.approve(_lockingInfo, type(uint256).max), "Approve failed");

5. Potential Reentrancy in withdrawRewards Function

  • Issue: In withdrawRewards(), the function relies on external calls to the vaultController.withdrawOperatorRewards. If the external contract is compromised or reentrancy isn't mitigated, an attacker could drain rewards.

  • Recommendation:

    • Use the Checks-Effects-Interactions pattern:

      uint256 amount = unclaimedRewards;
      unclaimedRewards = 0;
      uint256 amountWithdrawn = vaultController.withdrawOperatorRewards(rewardsReceiver, amount);
    • Consider adding the nonReentrant modifier from OpenZeppelin.


6. Public Getter Functions without SeqId Check

  • Issue: In getPrincipalDeposits() and getRewards(), you call lockingPool.sequencers(seqId). If seqId is 0 or uninitialized, this could lead to invalid reads or unexpected behavior.

  • Recommendation:

    • Add a check to ensure seqId != 0 before accessing lockingPool:

      require(seqId != 0, "Sequencer not initialized");

7. Gas Inefficiencies in Deposit Logic

  • Issue: Every deposit updates trackedTotalDeposits using a 128-bit cast and performs relock or lockWithRewardRecipient. If the sequence owner isn't updated often, you could be incurring unnecessary state updates.

  • Recommendation:

    • Optimize state updates and batching by reducing redundant writes, e.g., call relock only when needed.


8. Unsafe Ether Handling

  • Issue: In updateDeposits(), you allow the contract to transfer any balance back to msg.sender. If malicious ETH is accidentally sent to the contract or if the contract is exploited, this could cause unintended ETH transfers.

  • Recommendation:

    • Validate the balance and restrict ETH transfers to trusted actors. You could add a require check:

      require(msg.sender == owner(), "Unauthorized sender");

9. Upgradeable Contract Risks

  • Issue: The contract uses UUPS proxies, but upgrade authorization relies only on the onlyOwner modifier. If ownership is transferred accidentally or compromised, the upgrade could be exploited.

  • Recommendation:

    • Add multi-sig or time-locked upgrade mechanisms to mitigate upgrade risks.


10. Missing Events for Critical State Changes

  • Issue: Some functions like deposit() and updateDeposits() modify critical state without emitting events.

  • Impact: This reduces the ability to track changes off-chain (important for transparency).

  • Recommendation: Add events to critical state-changing functions.


Summary of Issues and Recommendations

Issue Recommendation
Initialization front-running Ensure initialization is secured.
Missing zero address checks Add zero address checks in initialize.
Arithmetic overflow risks Add safe math checks for uint128 operations.
Unchecked ERC20 approvals Use require for approve return values.
Reentrancy in withdrawRewards Apply nonReentrant and C-E-I pattern.
Getter functions without seqId check Add require(seqId != 0) checks.
Gas inefficiencies in deposit logic Reduce redundant writes and calls.
Unsafe Ether handling Restrict Ether transfers.
UUPS upgrade risks Use multi-sig or time locks for upgrades.
Missing events for state changes Emit events on critical state changes.

This code appears well-structured overall, but implementing the above recommendations would improve its security, gas efficiency, and upgrade safety.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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