MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Severity: low
Valid

ContestManager emits no events for createContest, fundContest, or closeContest, making contest lifecycle unobservable off-chain

Root + Impact

Description

  • ContestManager is supposed to provide an observable record of contest lifecycle actions — creation, funding, and closure — so frontends, indexers, and monitoring systems can react to state changes without polling on-chain storage.

  • No events are declared or emitted anywhere in ContestManager. Every call to createContest, fundContest, and closeContest changes critical protocol state with no on-chain log. Off-chain systems must resort to polling getContests() every block or parsing ERC20 Transfer events as a proxy, which is brittle and incomplete.

// ContestManager.sol — no events declared or emitted
function createContest(...) public onlyOwner returns (address) {
Pot pot = new Pot(players, rewards, token, totalRewards);
contests.push(address(pot));
contestToTotalRewards[address(pot)] = totalRewards;
// @> no event emitted
return address(pot);
}
function fundContest(uint256 index) public onlyOwner {
...
token.transferFrom(msg.sender, address(pot), totalRewards);
// @> no event emitted
}
function _closeContest(address contest) internal {
Pot pot = Pot(contest);
pot.closePot();
// @> no event emitted
}

Risk

Likelihood:

  • Every call to createContest, fundContest, and closeContest produces this gap — 100% of contest lifecycle actions are unlogged. This affects every deployment and every operator of the protocol.

Impact:

  • Frontends cannot display a real-time feed of new or closed contests without expensive storage polling on every block.

  • There is no immutable on-chain audit trail for owner actions — when a contest was created, funded, or closed cannot be determined from logs alone.

  • Monitoring and alerting systems have no hook to detect funding events or suspicious close activity.

Proof of Concept

Record all logs across a full contest lifecycle and verify that none are emitted by ContestManager — only the underlying ERC20 Transfer events appear.

function testNoEventsEmitted() public mintAndApproveTokens {
vm.startPrank(user);
vm.recordLogs();
address pot = ContestManager(conMan).createContest(
players, rewards, IERC20(weth), totalRewards
);
ContestManager(conMan).fundContest(0);
vm.warp(block.timestamp + 90 days + 1);
ContestManager(conMan).closeContest(pot);
vm.stopPrank();
Vm.Log[] memory logs = vm.getRecordedLogs();
// Every log is an ERC20 Transfer event — zero logs from ContestManager
for (uint256 i = 0; i < logs.length; i++) {
assertNotEq(logs[i].emitter, address(conMan));
}
}

All recorded logs have emitter != conMan, confirming ContestManager emits nothing across its entire lifecycle.

Recommended Mitigation

Declare events for each lifecycle action and emit them at the appropriate points in createContest, fundContest, and _closeContest.

+ event ContestCreated(address indexed pot, IERC20 indexed token, uint256 totalRewards);
+ event ContestFunded(address indexed pot, uint256 amount);
+ event ContestClosed(address indexed pot);
function createContest(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) public onlyOwner returns (address) {
Pot pot = new Pot(players, rewards, token, totalRewards);
contests.push(address(pot));
contestToTotalRewards[address(pot)] = totalRewards;
+ emit ContestCreated(address(pot), token, totalRewards);
return address(pot);
}
function fundContest(uint256 index) public onlyOwner {
Pot pot = Pot(contests[index]);
IERC20 token = pot.getToken();
uint256 totalRewards = contestToTotalRewards[address(pot)];
if (token.balanceOf(msg.sender) < totalRewards) revert ContestManager__InsufficientFunds();
token.transferFrom(msg.sender, address(pot), totalRewards);
+ emit ContestFunded(address(pot), totalRewards);
}
function _closeContest(address contest) internal {
Pot pot = Pot(contest);
pot.closePot();
+ emit ContestClosed(contest);
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 15 days ago
Submission Judgement Published
Validated
Assigned finding tags:

[L-02] Lack of events which reduces transparency and difficulty in monitoring

**Description** The `ContestManager` contract lacks event emissions for critical actions such as creating, funding, and closing contests. Events are crucial in Solidity contracts for logging important actions, as they provide an immutable record on the blockchain that can be indexed and queried by off-chain applications. Without these events, tracking the state and history of the contract becomes difficult, which can hinder monitoring, debugging, and auditing efforts. **Impact** - **Visibility**: Without events, external parties (such as users or monitoring systems) cannot easily track when contests are created, funded, or closed. This lack of visibility can lead to difficulties in verifying the correct operation of the contract. - **Debugging**: Developers and auditors will find it more challenging to diagnose issues or verify contract behavior without event logs. In the event of a bug or issue, the absence of events makes it harder to trace the sequence of actions leading up to the problem. - **Transparency**: Participants in the contests might not have a clear understanding of the state of the contract, which could lead to mistrust or uncertainty. **Proof of Concepts** The following functions in the `ContestManager` contract are identified as lacking event emissions: - `createContest` - `fundContest` - `closeContest` For example, in the `createContest` function: ```solidity function createContest(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) public onlyOwner returns (address) { Pot pot = new Pot(players, rewards, token, totalRewards); contests.push(address(pot)); contestToTotalRewards[address(pot)] = totalRewards; // No event emitted to signal that a new contest has been created. return address(pot); } ``` **Recommended Mitigation:** Add event declarations and emit statements in the contract to log significant actions. ```solidity event ContestCreated(address indexed contestAddress, uint256 totalRewards); event ContestFunded(address indexed contestAddress, uint256 amount); event ContestClosed(address indexed contestAddress); ```

Support

FAQs

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

Give us feedback!