FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: medium
Likelihood: medium

Unrestricted contributeBonus() allows donation griefing by arbitrarily inflating totalBonus

Author Revealed upon completion

Root + Impact

Description

  • Description
    The contributeBonus() function allows any address to contribute an arbitrary amount of stakeToken to the pool.


  • totalBonus += received;


  • There are no restrictions on who may contribute or how much may be donated. As a result, an attacker can intentionally donate an excessively large amount of tokens, causing totalBonus to become arbitrarily large.
    While the attacker sacrifices their own funds, this can be used to grief the protocol by manipulating reward-related metrics, distorting economic assumptions, or interfering with downstream logic that relies on totalBonus remaining within expected bounds.
    This issue does not directly enable theft of funds but can negatively affect protocol behavior depending on how totalBonus is consumed elsewhere.

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

Low (or Medium if you can prove it materially disrupts reward distribution, protocol accounting, or causes denial of service).

Proof of Concept (Illustrative)
Initial State
Alice stakes: 100 tokens
Bob stakes: 100 tokens
totalEligibleStake = 200
totalBonus = 20
Step 1: Attacker makes a large donation
The attacker calls:
contributeBonus(1_000_000e18);
State becomes:
totalBonus = 1,000,020
Step 2: Downstream impact
Suppose rewards are later calculated as:
reward = userStake * totalBonus / totalEligibleStake;
The unexpectedly large totalBonus now dramatically changes reward calculations.
Or suppose the frontend displays:
Estimated APY = totalBonus / totalEligibleStake
The displayed APY becomes unrealistically high, potentially misleading users into staking based on manipulated information.
Result
The attacker loses their donated tokens but successfully manipulates protocol economics or user perception. This is a griefing attack because the attacker accepts a financial loss to negatively affect the protocol or its users.

Recommended Mitigation

Restrict bonus contributions to a designated sponsor or governance-controlled address.

Introduce a maximum cap on totalBonus.

Limit the maximum amount per contribution.

Explicitly document that unrestricted third-party donations are an intended feature if this behavior is by design


- remove this code
function contributeBonus(uint256 amount) external nonReentrant whenPoolNotPaused {
if (amount == 0) revert InvalidAmount();
if (outcome != PoolStates.Outcome.UNRESOLVED) revert OutcomeAlreadySet();
if (block.timestamp >= expiry) revert StakingClosed();
_assertDepositsAllowed(_observePoolState());
// Balance-diff defense-in-depth — see `stake`.
// aderyn-fp-next-line(reentrancy-state-change)
uint256 balanceBefore = stakeToken.balanceOf(address(this));
stakeToken.safeTransferFrom(msg.sender, address(this), amount);
// aderyn-fp-next-line(reentrancy-state-change)
uint256 balanceAfter = stakeToken.balanceOf(address(this));
uint256 received = balanceAfter > balanceBefore ? balanceAfter - balanceBefore : 0;
if (received == 0) revert NoTokensReceived();
totalBonus += received;
emit BonusContributed(msg.sender, received);
In this functions there is grief donation bugs and explain how
}
+ add this code
address public sponsor;
modifier onlySponsor() {
require(msg.sender == sponsor, "Not sponsor");
_;
}
function contributeBonus(uint256 amount)
external
onlySponsor
nonReentrant
whenPoolNotPaused
{
if (amount == 0) revert InvalidAmount();
if (outcome != PoolStates.Outcome.UNRESOLVED) revert OutcomeAlreadySet();
if (block.timestamp >= expiry) revert StakingClosed();
_assertDepositsAllowed(_observePoolState());
uint256 balanceBefore = stakeToken.balanceOf(address(this));
stakeToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = stakeToken.balanceOf(address(this));
uint256 received = balanceAfter - balanceBefore;
if (received == 0) revert NoTokensReceived();
totalBonus += received;
emit BonusContributed(msg.sender, received);

Support

FAQs

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

Give us feedback!