Liquid Staking

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

test/core/strategy.test.ts

The provided code is a set of tests for an Ethereum smart contract using Hardhat and Chai. It tests the upgradeability of a strategy contract (likely a DeFi strategy) while ensuring that state persists across upgrades. Here are some potential vulnerabilities and improvements for the code, along with detailed solutions:

Vulnerabilities and Proposed Improvements

  1. Lack of Reentrancy Protection:

    • Vulnerability: If the deposit function in the StrategyMock contract does not implement reentrancy protection, it could be vulnerable to reentrancy attacks, especially if external calls (like transferring tokens) are made.

    • Improvement: Use the ReentrancyGuard from OpenZeppelin to protect the deposit function.

    Solution:

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    contract StrategyMock is ReentrancyGuard {
    function deposit(uint256 amount, bytes calldata data) external nonReentrant {
    // Deposit logic
    }
    }
  2. Token Approval Risks:

    • Vulnerability: Approving the strategy contract to spend an infinite amount of tokens (ethers.MaxUint256) may expose users to risks if the strategy's implementation is compromised.

    • Improvement: Approve only the required amount of tokens or implement a withdraw mechanism that allows users to reset approvals.

    Solution:

    const tokenAmountToApprove = toEther(1000); // approve only what is needed
    await token.approve(adrs.strategy, tokenAmountToApprove);
  3. Missing Events:

    • Vulnerability: The code does not verify that important state changes (like deposits and upgrades) emit corresponding events, which are crucial for external observers and dApps to track changes.

    • Improvement: Ensure that the deposit, upgradeTo, and other state-changing functions emit appropriate events.

    Solution:

    event Deposited(address indexed user, uint256 amount);
    event Upgraded(address indexed newImplementation);
    function deposit(uint256 amount) external {
    emit Deposited(msg.sender, amount);
    // Deposit logic
    }
    function upgradeTo(address newImplementation) external onlyOwner {
    emit Upgraded(newImplementation);
    // Upgrade logic
    }
  4. Testing Edge Cases:

    • Vulnerability: The tests only cover basic functionality but don't check for edge cases, such as upgrading multiple times or re-depositing after an upgrade.

    • Improvement: Add tests to cover scenarios like multiple upgrades, reentrancy, or deposits after an upgrade.

    Solution:

    it('should handle multiple upgrades correctly', async () => {
    const { adrs, strategy } = await loadFixture(deployFixture);
    let StrategyV2 = await ethers.getContractFactory('StrategyMockV2');
    let upgradedImpAddress = (await upgrades.prepareUpgrade(adrs.strategy, StrategyV2, { kind: 'uups' })) as string;
    await strategy.upgradeTo(upgradedImpAddress);
    // Repeat the upgrade with a new implementation
    let StrategyV3 = await ethers.getContractFactory('StrategyMockV3');
    upgradedImpAddress = (await upgrades.prepareUpgrade(adrs.strategy, StrategyV3, { kind: 'uups' })) as string;
    await strategy.upgradeTo(upgradedImpAddress);
    let upgraded = await ethers.getContractAt('StrategyMockV3', adrs.strategy);
    // Add assertions for state persistence and correctness
    });
  5. Inconsistent Use of assert and expect:

    • Vulnerability: The code uses both assert and expect, which may lead to inconsistency in testing practices.

    • Improvement: Stick to one style (preferably expect) for clarity and consistency.

    Solution:
    Replace assertions like:

    assert.equal(Number(await upgraded.contractVersion()), 2, 'contract not upgraded');

    with:

    expect(await upgraded.contractVersion()).to.equal(2);

Example of an Improved Test Suite

Here’s a snippet of how the improved test suite might look:

describe('Strategy', () => {
async function deployFixture() {
// ...
}
it('should be able to upgrade contract, state should persist', async () => {
const { adrs, token, strategy } = await loadFixture(deployFixture);
await strategy.deposit(toEther(1000), '0x');
let StrategyV2 = await ethers.getContractFactory('StrategyMockV2');
let upgradedImpAddress = (await upgrades.prepareUpgrade(adrs.strategy, StrategyV2, { kind: 'uups' })) as string;
await strategy.upgradeTo(upgradedImpAddress);
let upgraded = await ethers.getContractAt('StrategyMockV2', adrs.strategy);
expect(await upgraded.contractVersion()).to.equal(2);
expect(fromEther(await upgraded.getTotalDeposits())).to.equal(1000);
expect(fromEther(await token.balanceOf(await upgraded.getAddress()))).to.equal(1000);
});
it('contract should only be upgradeable by owner', async () => {
const { adrs, signers, strategy } = await loadFixture(deployFixture);
let StrategyV2 = await ethers.getContractFactory('StrategyMockV2');
let upgradedImpAddress = (await upgrades.prepareUpgrade(adrs.strategy, StrategyV2, { kind: 'uups' })) as string;
await expect(strategy.connect(signers[1]).upgradeTo(upgradedImpAddress)).to.be.revertedWith(
'Ownable: caller is not the owner'
);
});
it('should handle multiple upgrades correctly', async () => {
// Additional test case for multiple upgrades
});
});

Summary

The improvements focus on enhancing security, maintaining consistent testing practices, and ensuring that the code adheres to best practices in smart contract development. By implementing these changes, you can create a more robust and secure smart contract.

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.