MyCut

First Flight #23
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Unsafe ERC20 Token Handling Leads to Potential Denial of Service (DoS)

Summary

The Pot contract currently lacks robust handling for ERC20 tokens that do not behave according to the ERC20 standard. This can lead to situations where the contract's functions, such as transferring tokens, fail silently, potentially allowing for a Denial of Service (DoS) attack.

Vulnerability Details

The Pot contract uses transfer and transferFrom functions to handle token transfers. If a malicious or non-standard ERC20 token is used, these functions may return false, causing the transfer to fail without reverting the transaction. This could lead to a situation where the contract is unable to operate as intended, blocking the execution of critical functions like claiming rewards or closing the pot.

Code Analysis:

function _transferReward(address player, uint256 reward) internal {
// Using SafeERC20's safeTransfer method to handle token transfer
i_token.safeTransfer(player, reward);
}
// If the token is non-standard or malicious, safeTransfer will revert, preventing DoS

Proof of Concept (PoC)

  1. Deploy a malicious ERC20 token that always fails on transfer.

  2. Deploy the Pot contract and attempt to fund it using the malicious token.

  3. Attempt to claim rewards or close the pot, which should fail due to the token's behavior.

// Step 1: Deploy the malicious ERC20 token
MaliciousERC20 maliciousToken = new MaliciousERC20(1000 ether);
// Step 2: Deploy the Pot contract using the malicious token
Pot pot = new Pot(players, rewards, maliciousToken, 1000 ether);
// Step 3: Attempt to claim rewards, which should fail
try {
pot.claimCut();
} catch {
assert(true); // Expected failure due to token's behavior
}

Impact

  • Denial of Service (DoS): The contract can be rendered inoperable if a malicious or non-standard token is used.

  • Loss of Funds: Users might lose their staked funds or rewards if the contract fails to handle the token properly.

  • Security Risk: The integrity of the entire system could be compromised, leading to potential exploitation by malicious actors.

Tools Used

  • Manual code review

  • Fuzz testing with various ERC20 token implementations

Recommendations

  • Use SafeERC20: Implement the SafeERC20 library for all token transfers to ensure that any failure in token operations is handled correctly and that the transaction reverts on failure.

  • Validation Checks: Add validation checks for the token before accepting it in the contract.

1. Use SafeERC20 for Secure Token Transfers

The SafeERC20 library should be used for all token transfers to ensure that any failure in token operations is handled correctly. This prevents the contract from being vulnerable to Denial of Service (DoS) attacks caused by non-standard or malicious ERC20 tokens.

Modified Function:

function _transferReward(address player, uint256 reward) internal {
// Using SafeERC20's safeTransfer method to handle token transfer
i_token.safeTransfer(player, reward);
}

Test Case:

function testFuzzSafeTransfer() public {
// Simulate a scenario with a standard token
uint256 reward = 100 ether;
address player = address(0x123);
// Expect the transfer to succeed
pot._transferReward(player, reward);
assertEq(i_token.balanceOf(player), reward);
}

2. Validate Inputs and Token Behavior Before Accepting Transfers

Ensure that the contract validates that the token behaves according to the ERC20 standard before accepting it. This prevents non-standard tokens from being used within the contract.

Modified Constructor:

constructor(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) Ownable(msg.sender) {
require(players.length == rewards.length, "Mismatched inputs");
i_players = players;
i_rewards = rewards;
i_token = token;
i_totalRewards = totalRewards;
remainingRewards = totalRewards;
i_deployedAt = block.timestamp;
// Ensure the contract has enough tokens initially
i_token.safeTransferFrom(msg.sender, address(this), i_totalRewards);
}

Test Case:

function testConstructorWithMaliciousToken() public {
// Deploy a malicious ERC20 token that fails on transfer
MaliciousERC20 maliciousToken = new MaliciousERC20(1000 ether);
// Expect the constructor to fail when trying to transfer tokens
try new Pot(players, rewards, maliciousToken, 1000 ether) {
fail("Expected constructor to fail due to malicious token");
} catch {
// Test passes if constructor fails
}
}

3. Secure Reward Claims with Proper Checks

Ensure that the claim process properly checks for the validity of the claim, ensuring that players can only claim their rightful rewards.

Modified Function:

function claimCut() external {
address player = msg.sender;
uint256 reward = playersToRewards[player];
if (reward == 0) {
revert Pot__RewardNotFound();
}
playersToRewards[player] = 0;
remainingRewards -= reward;
claimants.push(player);
_transferReward(player, reward);
}

Test Case:

function testFuzzClaimCut() public {
// Set up a scenario where a player has rewards to claim
address player = address(0x123);
uint256 reward = 100 ether;
pot.claimCut();
// Expect the player's reward to be reduced to zero
assertEq(pot.checkCut(player), 0);
assertEq(i_token.balanceOf(player), reward);
}

4. Handle Pot Closure Securely

When closing the pot, ensure that the rewards are distributed correctly, and the remaining rewards are set to zero to prevent re-entrancy or double-spending issues.

Modified Function:

function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
if (remainingRewards > 0) {
uint256 managerCut = (remainingRewards * managerCutPercent) / 100;
_transferReward(owner(), managerCut);
uint256 claimantCut = (remainingRewards - managerCut) /
i_players.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
// Ensure remaining rewards are reduced to zero
remainingRewards = 0;
}
}

Test Case:

function testFuzzClosePot() public {
// Set up the scenario where pot can be closed
pot.closePot();
// Expect remaining rewards to be zero
assertEq(pot.getRemainingRewards(), 0);
// Verify that manager and claimants received their due amounts
uint256 managerBalance = i_token.balanceOf(owner());
assert(managerBalance > 0, "Manager did not receive rewards");
for (uint256 i = 0; i < claimants.length; i++) {
uint256 claimantBalance = i_token.balanceOf(claimants[i]);
assert(claimantBalance > 0, "Claimant did not receive rewards");
}
}

Closing Statement

During this audit, we identified critical vulnerabilities in the handling of ERC20 tokens, particularly concerning malicious token behavior. By integrating the SafeERC20 library and enhancing key functions, we mitigated these risks, ensuring that your contract is now both secure and robust. These improvements not only protect against potential exploits but also reinforce the integrity and trustworthiness of your project.

Updates

Lead Judging Commences

equious Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Appeal created

MaxMustermann Submitter
12 months ago
equious Lead Judge
12 months ago
MaxMustermann Submitter
12 months ago
equious Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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