Liquid Staking

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

test/linkStaking/community-vault.test.ts

Let's analyze the provided Solidity test code for the CommunityVault contract. This code uses Hardhat for testing smart contracts, utilizing the Chai assertion library. We will identify potential vulnerabilities and propose improvements with detailed solutions.

Code Review and Vulnerabilities

  1. Address Validation:

    • Issue: The accounts[1] used in the constructor of CommunityVault could be invalid if the length of accounts is less than 2.

    • Improvement: Ensure that the accounts array has enough elements before using its indices.

    assert.isAtLeast(accounts.length, 2, "Not enough accounts");
  2. Error Handling for Token Approvals:

    • Issue: If the token approval fails (e.g., if the token contract has restrictions), it may lead to unexpected behavior.

    • Improvement: Ensure that approval transactions revert appropriately if they fail.

    await expect(token.connect(signers[1]).approve(adrs.vault, ethers.MaxUint256)).to.not.be.reverted;
  3. Transfer Checks:

    • Issue: The transfer of tokens to rewardsController and accounts[1] does not check for success.

    • Improvement: Ensure token transfers revert on failure.

    await expect(token.transfer(adrs.rewardsController, toEther(10000))).to.not.be.reverted;
    await expect(token.transfer(accounts[1], toEther(100))).to.not.be.reverted;
  4. Gas Limit and Cost:

    • Issue: There are no gas limits set for transactions, which can lead to out-of-gas errors in some environments.

    • Improvement: Specify gas limits in transactions, especially for more complex interactions.

    await vault.connect(signers[1]).deposit(toEther(100), { gasLimit: 500000 });
  5. Reward Handling Logic:

    • Issue: The test checks claimRewards functionality but does not validate the state after each call or handle cases where rewards may not be available.

    • Improvement: More comprehensive checks can be added to ensure that the contract’s state remains consistent before and after transactions.

    // Validate rewards before claiming
    const initialBalance = await token.balanceOf(accounts[5]);
    await vault.connect(signers[1]).claimRewards(toEther(10), accounts[5]);
    assert.equal(fromEther(await vault.getRewards()), 0);
    assert.equal(fromEther(await token.balanceOf(accounts[5])), initialBalance.add(10));
  6. Check for Overflow/Underflow:

    • Issue: Solidity 0.8+ includes built-in overflow and underflow checks, but it is still a good practice to be aware of any calculations that might result in unexpected behavior.

    • Improvement: Validate any arithmetic operations performed with Ether amounts.

  7. Lack of Revert Messages:

    • Issue: The assertions only check for values but do not provide feedback on why a test might have failed.

    • Improvement: Adding error messages to assertions will provide clarity.

    assert.equal(fromEther(await vault.getRewards()), 10, "Rewards should be 10 after claiming");
  8. Access Control:

    • Issue: Ensure that the claimRewards function has proper access control checks if needed (e.g., only allowing certain users to claim rewards).

    • Improvement: Implement and test access control mechanisms, such as onlyOwner or role-based access.

Detailed Solutions

Here's a revised version of the code with the proposed improvements:

import { ethers } from 'hardhat'
import { assert, expect } from 'chai'
import { toEther, deploy, deployUpgradeable, getAccounts, fromEther } from '../utils/helpers'
import { ERC677, CommunityVault, StakingMock, StakingRewardsMock } from '../../typechain-types'
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'
describe('CommunityVault', () => {
async function deployFixture() {
const { accounts, signers } = await getAccounts()
assert.isAtLeast(accounts.length, 2, "Not enough accounts");
const token = (await deploy('contracts/core/tokens/base/ERC677.sol:ERC677', [
'Chainlink',
'LINK',
1000000000,
])) as ERC677
const tokenAddress = await token.getAddress()
const rewardsController = (await deploy('StakingRewardsMock', [
tokenAddress,
])) as StakingRewardsMock
const rewardsControllerAddress = await rewardsController.getAddress()
const stakingController = (await deploy('StakingMock', [
tokenAddress,
rewardsControllerAddress,
toEther(10),
toEther(100),
toEther(10000),
28 * 86400,
7 * 86400,
])) as StakingMock
const stakingControllerAddress = await stakingController.getAddress()
const vault = (await deployUpgradeable('CommunityVault', [
tokenAddress,
accounts[1],
stakingControllerAddress,
rewardsControllerAddress,
])) as CommunityVault
const vaultAddress = await vault.getAddress()
await expect(token.connect(signers[1]).approve(vaultAddress, ethers.MaxUint256)).to.not.be.reverted;
await expect(token.transfer(rewardsControllerAddress, toEther(10000))).to.not.be.reverted;
await expect(token.transfer(accounts[1], toEther(100))).to.not.be.reverted;
return { signers, accounts, adrs: { token: tokenAddress, rewardsController: rewardsControllerAddress, stakingController: stakingControllerAddress, vault: vaultAddress }, token, rewardsController, stakingController, vault }
}
it('claimRewards should work correctly', async () => {
const { signers, accounts, adrs, vault, rewardsController, token } = await loadFixture(
deployFixture
)
await vault.connect(signers[1]).deposit(toEther(100), { gasLimit: 500000 })
await vault.connect(signers[1]).claimRewards(0, accounts[5])
await rewardsController.setReward(adrs.vault, toEther(10))
const initialBalance = await token.balanceOf(accounts[5]);
await vault.connect(signers[1]).claimRewards(toEther(11), accounts[5])
assert.equal(fromEther(await vault.getRewards()), 10, "Rewards should be 10 after setting rewards");
assert.equal(fromEther(await token.balanceOf(accounts[5])), 0, "Account should have 0 tokens initially");
await vault.connect(signers[1]).claimRewards(toEther(10), accounts[5])
assert.equal(fromEther(await vault.getRewards()), 0, "Rewards should be 0 after claiming all rewards");
assert.equal(fromEther(await token.balanceOf(accounts[5])), initialBalance.add(10), "Account should have received the claimed tokens");
})
})

Summary of Improvements

  • Input Validation: Added checks for the number of accounts to prevent out-of-bounds access.

  • Error Handling: Ensured that all transactions revert gracefully on failure with appropriate checks.

  • Gas Management: Specified gas limits for potentially costly transactions to avoid out-of-gas errors.

  • Improved Assertions: Added messages to assertions to clarify test failures.

  • Pre-claim Checks: Enhanced logic to validate the rewards and balances after claims.

These improvements enhance the robustness of the contract tests and ensure that the implementation is resilient against common pitfalls in Solidity development.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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