Summary
Several state-changing functions in the WeatherNft contract don't emit events, making it difficult to track changes off-chain and for frontend applications to stay synchronized with contract state.
Vulnerability Details
Multiple functions that modify critical contract state do not emit events:
function updateFunctionsGasLimit(uint32 newGasLimit) external onlyOwner {
s_functionsConfig.gasLimit = newGasLimit;
}
function updateSubId(uint64 newSubId) external onlyOwner {
s_functionsConfig.subId = newSubId;
}
function updateSource(string memory newSource) external onlyOwner {
s_functionsConfig.source = newSource;
}
Additionally, the price increase in requestMintWeatherNFT doesn't emit an event:
s_currentMintPrice += s_stepIncreasePerMint;
Proof of Concept
pragma solidity 0.8.29;
import "forge-std/Test.sol";
import "../src/WeatherNft.sol";
contract EventEmissionTest is Test {
WeatherNft public weatherNft;
address public owner;
address public user;
function setUp() public {
owner = address(0x1);
user = address(0x3);
vm.startPrank(owner);
weatherNft = new WeatherNft(
);
vm.stopPrank();
vm.deal(owner, 10 ether);
vm.deal(user, 10 ether);
}
function testNoEventForGasLimitUpdate() public {
vm.recordLogs();
vm.startPrank(owner);
weatherNft.updateFunctionsGasLimit(100000);
vm.stopPrank();
Vm.Log[] memory entries = vm.getRecordedLogs();
assertEq(entries.length, 0, "No events should be emitted");
}
function testNoEventForSubIdUpdate() public {
vm.recordLogs();
vm.startPrank(owner);
weatherNft.updateSubId(12345);
vm.stopPrank();
Vm.Log[] memory entries = vm.getRecordedLogs();
assertEq(entries.length, 0, "No events should be emitted");
}
function testNoEventForSourceUpdate() public {
vm.recordLogs();
vm.startPrank(owner);
weatherNft.updateSource("new source code");
vm.stopPrank();
Vm.Log[] memory entries = vm.getRecordedLogs();
assertEq(entries.length, 0, "No events should be emitted");
}
function testNoEventForPriceIncrease() public {
vm.recordLogs();
vm.startPrank(user);
vm.stopPrank();
Vm.Log[] memory entries = vm.getRecordedLogs();
bool priceIncreaseEventFound = false;
for (uint i = 0; i < entries.length; i++) {
}
assertEq(priceIncreaseEventFound, false, "No price increase event should be found");
}
function testOffChainSynchronizationIssue() public {
uint256 frontendMintPrice = weatherNft.s_currentMintPrice();
vm.startPrank(user);
vm.stopPrank();
uint256 actualMintPrice = weatherNft.s_currentMintPrice();
assertNotEq(frontendMintPrice, actualMintPrice, "Prices should be different");
vm.startPrank(user);
vm.expectRevert("WeatherNft__InvalidAmountSent");
vm.stopPrank();
}
}
This PoC demonstrates how the lack of event emissions for state changes can lead to synchronization issues between the contract and off-chain applications. It shows that:
No events are emitted when updating critical parameters
No event is emitted when the mint price increases
This can lead to off-chain applications having outdated information
Users relying on this outdated information may have their transactions fail
Impact
The lack of event emissions:
Makes it difficult for off-chain services to track contract state changes
Complicates frontend application development and synchronization
Reduces transparency for users and stakeholders
Makes debugging and auditing contract behavior more challenging
Recommendations
Add appropriate events for all state-changing functions:
event FunctionsGasLimitUpdated(uint32 newGasLimit);
event SubIdUpdated(uint64 newSubId);
event SourceUpdated(string newSource);
event MintPriceIncreased(uint256 newPrice);
function updateFunctionsGasLimit(uint32 newGasLimit) external onlyOwner {
s_functionsConfig.gasLimit = newGasLimit;
emit FunctionsGasLimitUpdated(newGasLimit);
}
function updateSubId(uint64 newSubId) external onlyOwner {
s_functionsConfig.subId = newSubId;
emit SubIdUpdated(newSubId);
}
function updateSource(string memory newSource) external onlyOwner {
s_functionsConfig.source = newSource;
emit SourceUpdated(newSource);
}
In the requestMintWeatherNFT function:
s_currentMintPrice += s_stepIncreasePerMint;
emit MintPriceIncreased(s_currentMintPrice);