Beatland Festival

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

reentrancy-events

Root + Impact

Description

  • Best practice for smart contracts dictates that events should be emitted only after all state changes have been finalized and any external interactions related to the current operation have successfully completed. This ensures that off-chain observers receive event logs that accurately reflect the contract's final, consistent state.

  • In several functions within FestivalPass.sol (e.g., attendPerformance, buyPass, redeemMemorabilia), external calls are made before their corresponding events are emitted. This means off-chain systems listening to these events might log an event for an action that could subsequently revert (due to the external call failing), leading to a misrepresentation of the contract's true state in off-chain records.

SLITHER OUTPUT:

## reentrancy-events Impact: Low
Confidence: Medium - [ ] ID-14
Reentrancy in [FestivalPass.attendPerformance(uint256)](src/FestivalPass.sol#L106-L117):
External calls:
- [BeatToken(beatToken).mint(msg.sender,performances[performanceId].baseReward * multiplier)](src/FestivalPass.sol#L115)
Event emitted after the call(s):
- [Attended(msg.sender,performanceId,performances[performanceId].baseReward * multiplier)](src/FestivalPass.sol#L116)
src/FestivalPass.sol#L106-L117
- [ ] ID-15 Reentrancy in [FestivalPass.buyPass(uint256)](src/FestivalPass.sol#L69-L85):
External calls: - [_mint(msg.sender,collectionId,1,)](src/FestivalPass.sol#L76)
- [response = IERC1155Receiver(to).onERC1155BatchReceived(operator,from,ids,values,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Utils.sol#L69-L85)
- [response = IERC1155Receiver(to).onERC1155Received(operator,from,id,value,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Utils.sol#L34-L48)
- [ERC1155Utils.checkOnERC1155Received(operator,from,to,id,value,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol#L194)
- [ERC1155Utils.checkOnERC1155BatchReceived(operator,from,to,ids,values,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol#L196) - [BeatToken(beatToken).mint(msg.sender,bonus)](src/FestivalPass.sol#L82) Event emitted after the call(s):
- [PassPurchased(msg.sender,collectionId)](src/FestivalPass.sol#L84)
src/FestivalPass.sol#L69-L85
- [ ] ID-16 Reentrancy in [FestivalPass.redeemMemorabilia(uint256)](src/FestivalPass.sol#L190-L210):
External calls: - [BeatToken(beatToken).burnFrom(msg.sender,collection.priceInBeat)](src/FestivalPass.sol#L197) - [_mint(msg.sender,tokenId,1,)](src/FestivalPass.sol#L207)
- [response = IERC1155Receiver(to).onERC1155Received(operator,from,id,value,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Utils.sol#L34-L48)
- [response = IERC1155Receiver(to).onERC1155BatchReceived(operator,from,ids,values,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Utils.sol#L69-L85)
- [ERC1155Utils.checkOnERC1155Received(operator,from,to,id,value,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol#L194)
- [ERC1155Utils.checkOnERC1155BatchReceived(operator,from,to,ids,values,data)](lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol#L196)
Event emitted after the call(s):
- [MemorabiliaRedeemed(msg.sender,tokenId,collectionId,itemId)](src/FestivalPass.sol#L209)
- [TransferBatch(operator,from,to,ids,values)](lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol#L168)
- [_mint(msg.sender,tokenId,1,)](src/FestivalPass.sol#L207)
- [TransferSingle(operator,from,to,id_scope_0,value_scope_1)](lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol#L166)
- [_mint(msg.sender,tokenId,1,)](src/FestivalPass.sol#L207)
src/FestivalPass.sol#L190-L210
// Root cause in the codebase with @> marks to highlight the relevant section
// ID-14: FestivalPass.attendPerformance(uint256)
src/FestivalPass.sol#L115-L116
BeatToken(beatToken).mint(msg.sender, REWARD_AMOUNT); // @> External call
emit Attended(msg.sender, performanceId); // @> Event emitted after external call
// ID-15: FestivalPass.buyPass(uint256)
src/FestivalPass.sol#L76-L84
_mint(msg.sender, collectionId, 1, ""); // @> External call
// ...
// BeatToken(beatToken).mint(msg.sender,bonus); // @> Another external call
emit PassPurchased(msg.sender, collectionId); // @> Event emitted after external calls
// ID-16: FestivalPass.redeemMemorabilia(uint256)
src/FestivalPass.sol#L197-L209
BeatToken(beatToken).burnFrom(msg.sender, price); // @> External call
_mint(msg.sender, tokenId, 1, ""); // @> Another external call
emit MemorabiliaRedeemed(msg.sender, tokenId, collectionId, itemId); // @> Event emitted after external calls

Risk

Likelihood:

  • This will occur every time the affected functions are successfully executed, and an external call is made.

  • This will occur consistently, leading to off-chain systems processing events that might not reflect the final on-chain state if the external call (or a re-entry from it) causes a revert.

Impact:

  • Off-chain services (e.g., analytics dashboards, indexers, block explorers, DApp frontends) may display temporarily or permanently inconsistent information, showing an action as completed when it ultimately failed.

  • Auditing the history of contract interactions becomes more complex, as event logs might not perfectly align with the final on-chain state if external calls revert.

  • Can lead to increased complexity in off-chain data reconciliation.

Proof of Concept

// Example PoC demonstrating reentrancy-events in attendPerformance
// Scenario:
// 1. `attendPerformance` is called.
// 2. `BeatToken(beatToken).mint` is executed.
// 3. Before the `mint` transaction is fully confirmed (or if it reverts after the event is queued),
// the `Attended` event is emitted.
// 4. An off-chain listener receives and logs the `Attended` event.
// 5. If, for some reason, the `mint` call ultimately fails or is reverted (e.g., due to low balance in BeatToken),
// the `Attended` event would have been logged, but the underlying state change (token minting) did not occur.
// Result: Off-chain data becomes inconsistent with on-chain reality.

Recommended Mitigation

(Similar adjustments should be made for buyPass and redeemMemorabilia, ensuring events are emitted at the very end of the function after all effects and external interactions have concluded.)

- remove this code
+ add this code
--- a/src/FestivalPass.sol
+++ b/src/FestivalPass.sol
@@ -114,6 +114,6 @@
}
BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);
- emit Attended(msg.sender, performanceId, performances[performanceId].baseReward * multiplier); // @> Move event emission after all state changes and external calls are fully committed
+ emit Attended(msg.sender, performanceId, performances[performanceId].baseReward * multiplier); // @> Ensure this is the very last step if no other state changes or external calls are pending
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 25 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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