Liquid Staking

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

test/metisStaking/sequencer-rewards-ccip-sender.test.ts

The provided Solidity code uses Hardhat and the Chai assertion library to test the SequencerRewardsCCIPSender smart contract. Below, I've identified potential vulnerabilities and proposed improvements, including detailed solutions:

Vulnerabilities and Improvements

  1. Lack of Access Control

    • Issue: Functions that change state or sensitive data may not have access control, allowing unauthorized users to call them.

    • Solution: Implement role-based access control using OpenZeppelin's Ownable or AccessControl contracts. For example, restrict the setDestinationReceiver function so only the owner or an authorized role can modify it.

    import "@openzeppelin/contracts/access/Ownable.sol";
    contract SequencerRewardsCCIPSender is Ownable {
    function setDestinationReceiver(address _receiver) external onlyOwner {
    // ...
    }
    }
  2. Insufficient Input Validation

    • Issue: Functions may not validate input parameters, leading to potential failures or state inconsistencies.

    • Solution: Add require statements to check that amounts are non-zero and that addresses are valid.

    function transferRewards(uint256 amount) external {
    require(amount > 0, "Amount must be greater than zero");
    require(msg.sender != address(0), "Invalid address");
    // ...
    }
  3. Potential for Reentrancy Attacks

    • Issue: If external calls (e.g., transferring tokens) are made before state changes, it can lead to reentrancy vulnerabilities.

    • Solution: Use the Checks-Effects-Interactions pattern by updating the state before calling external contracts or use a reentrancy guard.

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    contract SequencerRewardsCCIPSender is ReentrancyGuard {
    function transferRewards(uint256 amount) external nonReentrant {
    // State change first
    // Then external call
    }
    }
  4. Handling of Fees

    • Issue: The fee handling mechanism may not correctly account for edge cases, potentially allowing overcharging or incorrect fee distributions.

    • Solution: Define clear rules for fee calculations and include assertions or checks to ensure they are always valid.

    require(fee <= maxFee, "Fee exceeds limit");
  5. Magic Numbers

    • Issue: The use of raw numbers (e.g., 77 in the router configuration) can lead to confusion and errors.

    • Solution: Define constants with descriptive names for better readability and maintainability.

    uint256 constant RAMP_TYPE = 77;
  6. Improper Error Handling in Tests

    • Issue: Tests may not adequately check the error handling and state changes after errors are thrown.

    • Solution: After expecting a revert, check that the state remains unchanged or as expected.

    const initialBalance = await linkToken.balanceOf(adrs.ccipSender);
    await expect(ccipSender.transferRewards(toEther(1))).to.be.revertedWithCustomError(ccipSender, 'FeeExceedsLimit()');
    const finalBalance = await linkToken.balanceOf(adrs.ccipSender);
    expect(finalBalance).to.equal(initialBalance); // Ensure balance is unchanged

Complete Example of Proposed Improvements

Here's an updated version of your contract and test case, incorporating the above suggestions:

Updated Contract Snippet

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SequencerRewardsCCIPSender is Ownable, ReentrancyGuard {
uint256 constant RAMP_TYPE = 77;
function transferRewards(uint256 amount) external nonReentrant {
require(amount > 0, "Amount must be greater than zero");
require(msg.sender != address(0), "Invalid address");
// ... existing logic ...
require(fee <= maxFee, "Fee exceeds limit");
// Perform external transfer after updating state
}
function setDestinationReceiver(address _receiver) external onlyOwner {
require(_receiver != address(0), "Invalid receiver address");
// ... existing logic ...
}
}

Updated Test Snippet

it('transferRewards should work correctly', async () => {
const { accounts, adrs, linkToken, token, ccipSender, onRamp } = await loadFixture(
deployFixture
);
await linkToken.transfer(adrs.ccipSender, toEther(10));
await token.transfer(adrs.ccipSender, toEther(100));
// Initial balance check
const initialBalance = await linkToken.balanceOf(adrs.ccipSender);
await ccipSender.transferRewards(toEther(10));
let lastRequestData = await onRamp.getLastRequestData();
let lastRequestMsg = await onRamp.getLastRequestMessage();
// Assert final states
assert.equal(fromEther(await token.balanceOf(adrs.tokenPool)), 100);
assert.equal(fromEther(await linkToken.balanceOf(adrs.ccipSender)), 8);
assert.equal(fromEther(lastRequestData[0]), 2);
assert.equal(lastRequestData[1], adrs.ccipSender);
assert.equal(
ethers.AbiCoder.defaultAbiCoder().decode(['address'], lastRequestMsg[0])[0],
accounts[5]
);
assert.equal(lastRequestMsg[1], '0x');
assert.deepEqual(
lastRequestMsg[2].map((d) => [d.token, fromEther(d.amount)]),
[[adrs.token, 100]]
);
assert.equal(lastRequestMsg[3], adrs.linkToken);
assert.equal(lastRequestMsg[4], '0x1111');
// Check for expected revert with unchanged state
await expect(ccipSender.transferRewards(toEther(10))).to.be.revertedWithCustomError(
ccipSender,
'NoRewards()'
);
await token.transfer(adrs.ccipSender, toEther(100));
await expect(ccipSender.transferRewards(toEther(1))).to.be.revertedWithCustomError(
ccipSender,
'FeeExceedsLimit()'
);
const finalBalance = await linkToken.balanceOf(adrs.ccipSender);
expect(finalBalance).to.equal(initialBalance); // Ensure balance is unchanged
});

Summary

These changes enhance the security and maintainability of the contract. By adding access control, validating inputs, preventing reentrancy, improving error handling in tests, and avoiding magic numbers, you help ensure the integrity and robustness of your smart contract.

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.