Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Multiplier calculation ignores multiple pass ownership

Description:

The getMultiplier() function only checks for pass ownership using balanceOf() > 0 and returns immediately upon finding the highest tier pass, without considering the quantity of passes owned or cumulative benefits from multiple pass types. This design flaw means users who purchase multiple passes of the same type or different types receive the same BEAT token rewards as users with single passes, creating an economic disincentive for additional pass purchases and potentially unfair reward distribution.

Attack path:

  1. User A purchases one BACKSTAGE pass for 0.25 ETH and receives 3x multiplier

  2. User B purchases three BACKSTAGE passes for 0.75 ETH (3x the investment) but still receives only 3x multiplier

  3. Both users attend the same performance and earn identical BEAT rewards despite User B's significantly higher investment

  4. Alternatively, User C purchases 1 BACKSTAGE + 2 VIP passes (total 0.45 ETH) but getMultiplier() returns 3x immediately due to early return logic, ignoring the additional VIP passes entirely

  5. Users realize multiple pass purchases provide no additional benefits and avoid repeat purchases

Impact:

Users have no economic incentive to purchase multiple passes, limiting ETH inflow

Users making larger investments receive disproportionately lower returns per ETH spent

Protocol fails to capture additional value from users willing to pay premium prices

The system doesn't reflect the principle that higher investment should yield proportionally higher rewards

Recommended Mitigation:

Implement one of the following solutions:

  1. Proportional multiplier calculation: Account for quantity of each pass type owned

function getMultiplier(address user) public view returns (uint256) {
uint256 multiplier = 0;
multiplier += balanceOf(user, BACKSTAGE_PASS) * 3;
multiplier += balanceOf(user, VIP_PASS) * 2;
multiplier += balanceOf(user, GENERAL_PASS) * 1;
return multiplier;
}
  1. ** Restrict pass purchases**: Limit each address to owning only one pass of any type to maintain current simple multiplier logic while preventing confusion.

    function buyPass(uint256 collectionId) external payable {
    require(!hasPass(msg.sender), "Already owns a pass");
    // ... existing logic
    }

    Additionaly override ERC1155 transfer functions to prevent pass transfers

function safeTransferFrom(...) public override {
revert("Passes are non-transferable");
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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