Genuine Garnet Crab
Medium
The lack of restrictions in _computePenalty
enables an attacker to bypass penalties partially or completely. By repeatedly calling _computePenalty
, the attacker can reset monthly penalty percentages without any actual deduction being applied.
AirdropDistribution.sol #L190
In AirdropDistribution.sol
, specifically within the _computePenalty
function, there is no condition to prevent unauthorized calls that reset penaltyPercentageByMonth
to zero for specific months, allowing potential misuse.
1.The attacker has an account ranked in the top 80% of the distribution.
2.The attacker can call _computePenalty
before invoking claim
to reset penalties.
3.The attacker sets monthsPassed
to a non-zero value, resetting penalties across multiple months.
1.The Ethereum block timestamp is past the AIRDROP_INITIAL_START_TIME
.
2.Low Ethereum gas prices allow repeated calls to be financially viable.
1.The attacker repeatedly calls _computePenalty
, manipulating monthsPassed to reset penalty percentages.
2.Each call resets penaltyPercentageByMonth
to zero without enforcing a claim or applying deductions.
3.Finally, the attacker calls claim
without any penalties being deducted.
Stakers suffer a loss equivalent to all unpaid penalties, with funds reduced in line with the accumulated monthly penalty rates. The attacker gains the avoided penalty amount.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.0;
import "forge-std/Test.sol";
import "./AirdropDistribution.sol";
contract PenaltyTest is Test {
AirdropDistribution airdrop;
function setUp() public {
airdrop = new AirdropDistribution();
}
// Wrapper function to access `_computePenalty` for testing purposes
function computePenaltyWrapper(uint256 totalAmount, address account, uint256 monthsPassed) public returns (uint256) {
return airdrop._computePenalty(totalAmount, account, monthsPassed);
}
function testAvoidPenalty() public {
address attacker = address(0x123);
uint256 totalAmount = 1000;
uint256 monthsPassed = 3;
// Initially compute penalty and verify penalty amount is applied
uint256 penalty1 = computePenaltyWrapper(totalAmount, attacker, monthsPassed);
// Call computePenaltyWrapper again and verify penalty is reset
uint256 penalty2 = computePenaltyWrapper(totalAmount, attacker, monthsPassed);
assertEq(penalty1, penalty2, "Penalty should be applied only once");
}
}
1.Convert _computePenalty
to a view
function: This modification will ensure _computePenalty
only calculates the penalty without modifying state variables, preventing any unauthorized resetting of penalties.
2.Reset Penalties Only in the claim
Function: Implementing penalty resets solely in the claim
function ensures that penalties are reset only upon an actual claim, eliminating the possibility of _computePenalty
being used to manipulate penalty values.
3.Implement Security Checks in _computePenalty
: Add security checks within _computePenalty
to restrict unauthorized access and prevent unintended resets, providing additional safeguards against potential misuse.