Liquid Staking

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

test/ethStaking/deposit-controller.test.ts

This Solidity test code is for a DepositController smart contract using Hardhat and Chai for testing. While it's primarily a testing suite, there are areas to improve both in terms of code quality and potential security vulnerabilities. Below, I’ll outline some identified vulnerabilities and suggest improvements along with detailed solutions.

Identified Vulnerabilities

  1. Reentrancy Vulnerability:

    • The commented-out depositEther test indicates that the function might modify state and then call external contracts, which could be susceptible to reentrancy attacks.

  2. Lack of Input Validation:

    • The functions do not perform checks on inputs before processing, which can lead to unexpected behavior or state changes.

  3. Use of any Type:

    • Using the any type for adrs is not type-safe and can lead to runtime errors.

  4. Hardcoded Values:

    • There are multiple instances of hardcoded values (like 48 and 96 for byte lengths). These should be defined as constants for better maintainability.

  5. Potential Gas Limit Issues:

    • The loop in the fixture can potentially consume a lot of gas if there are too many iterations. This could lead to transaction failures.

  6. Poor Error Handling:

    • The assertions in the test do not handle all potential error states. More informative messages can be added to understand what went wrong during the assertions.

Proposed Improvements

  1. Reentrancy Guard:

    • Implement a reentrancy guard for any functions that alter the state or transfer Ether. Use the nonReentrant modifier to prevent reentrancy.

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    contract DepositController is ReentrancyGuard {
    // ...
    function depositEther(/* parameters */) external nonReentrant {
    // function logic
    }
    }
  2. Input Validation:

    • Validate the inputs in functions to ensure they meet the required conditions before proceeding with the logic.

    require(depositData.length > 0, "Deposit data must not be empty");
  3. Type Definitions:

    • Instead of using any for the adrs variable, define a specific type or an interface that describes its shape.

    interface AddressDictionary {
    wETH: string;
    stakingPool: string;
    wsdToken: string;
    depositContract: string;
    strategy: string;
    nwlOperatorController: string;
    wlOperatorController: string;
    nwlRewardsPool: string;
    wlRewardsPool: string;
    depositController: string;
    }
    const adrs: AddressDictionary = {} as AddressDictionary;
  4. Define Constants:

    • Define constants for the byte lengths to avoid magic numbers and make the code clearer.

    const PUBKEY_LENGTH = 48;
    const SIGNATURE_LENGTH = 96;
  5. Optimize Gas Usage:

    • Consider using batch operations or reducing the number of external calls in the loop for adding operators to prevent potential gas limit issues.

    for (let i = 0; i < operatorCount; i++) {
    // Instead of calling addOperator in each iteration, consider aggregating operations if possible
    }
  6. Enhanced Error Messaging:

    • Modify the error messages in assertions to provide more context on failures.

    assert.equal(depositRoot, await depositContract.get_deposit_root(), `Expected depositRoot ${depositRoot} to match contract value`);

Detailed Solution Implementation

Here's an example of how you could start implementing some of the suggested improvements in the code:

import { ethers } from 'hardhat'
import { assert, expect } from 'chai'
import { toEther, deploy, deployUpgradeable, getAccounts, padBytes, concatBytes } from '../utils/helpers'
import { DepositController } from '../../typechain-types'
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'
// Define constants for byte lengths
const PUBKEY_LENGTH = 48;
const SIGNATURE_LENGTH = 96;
// Define Address Dictionary interface
interface AddressDictionary {
wETH: string;
stakingPool: string;
wsdToken: string;
depositContract: string;
strategy: string;
nwlOperatorController: string;
wlOperatorController: string;
nwlRewardsPool: string;
wlRewardsPool: string;
depositController: string;
}
const nwlOps = {
keys: concatBytes([padBytes('0xa1', PUBKEY_LENGTH), padBytes('0xa2', PUBKEY_LENGTH)]),
signatures: concatBytes([padBytes('0xb1', SIGNATURE_LENGTH), padBytes('0xb2', SIGNATURE_LENGTH)]),
};
describe('DepositController', () => {
async function deployFixture() {
const { accounts, signers } = await getAccounts();
const adrs: AddressDictionary = {} as AddressDictionary;
const wETH = (await deploy('WrappedETH')) as WrappedETH;
adrs.wETH = await wETH.getAddress();
// ... deployment logic remains the same ...
return {
signers,
accounts,
adrs,
// ... other returned objects
}
}
it('getNextValidators should work correctly', async () => {
const { depositController, depositContract, nwlOperatorController, wlOperatorController } = await loadFixture(deployFixture);
let [depositRoot, nwlStateHash, wlStateHash, nwlTotalValidatorCount, wlTotalValidatorCount] = await depositController.getNextValidators(7);
assert.equal(depositRoot, await depositContract.get_deposit_root(), `Expected depositRoot ${depositRoot} to match contract value`);
// ... other assertions remain the same ...
});
// Other test cases remain the same
});

Conclusion

The above improvements and proposals not only enhance the security of the smart contract tests but also improve code readability and maintainability. Ensuring proper input validation and avoiding reentrancy will help in making the contract more robust. By defining types and constants, the code becomes easier to read and understand, which is crucial when working in complex environments like blockchain development.

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.