Weather Witness

First Flight #40
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

State Variable Naming Inconsistency Breaking Upkeep Tracking

Description

  • The WeatherNft contract tracks NFT state through a mapping called s_weatherNftInfo that includes a lastFulfilledAt timestamp to determine when the next update should occur.

  • In the performUpkeep function, it incorrectly updates a non-existent mapping called s_NftInfo instead of the correct s_weatherNftInfo, leading to a failure in tracking update timestamps.

function performUpkeep(bytes calldata performData) external override {
uint256 _tokenId = abi.decode(performData, (uint256));
uint256 upkeepId = s_weatherNftInfo[_tokenId].upkeepId;
@> s_NftInfo[_tokenId].lastFulfilledAt = block.timestamp;
// make functions request
string memory pincode = s_weatherNftInfo[_tokenId].pincode;
string memory isoCode = s_weatherNftInfo[_tokenId].isoCode;
// ...
}

Risk

Likelihood: High

  • The error exists in the core upkeep execution path and will trigger every time performUpkeep is called

  • Chainlink Keeper automation will continue calling this function based on the original checkUpkeep conditions

Impact: High

  • NFT weather information will update at a much faster rate than intended because checkUpkeep compares the current timestamp with s_weatherNftInfo[_tokenId].lastFulfilledAt, which is never updated

  • This causes excessive Chainlink Function calls, rapidly depleting the user's LINK balance

  • Automatic weather updates will occur at every block rather than following the user's configured heartbeat parameter

Proof of Concept

// 1. User calls requestMintWeatherNFT with a heartbeat of 86400 (1 day)
// 2. s_weatherNftInfo[tokenId].lastFulfilledAt is set to block.timestamp (e.g. 1000)
// 3. checkUpkeep correctly evaluates if current time > lastFulfilledAt + heartbeat
// 4. performUpkeep is called but updates s_NftInfo (which doesn't exist) instead of s_weatherNftInfo
// 5. s_weatherNftInfo[tokenId].lastFulfilledAt remains at 1000
// 6. Next block, checkUpkeep will return true again since s_weatherNftInfo[tokenId].lastFulfilledAt wasn't updated
// 7. This creates a continuous loop of upkeep calls instead of waiting for the heartbeat interval

Recommended Mitigation

function performUpkeep(bytes calldata performData) external override {
uint256 _tokenId = abi.decode(performData, (uint256));
uint256 upkeepId = s_weatherNftInfo[_tokenId].upkeepId;
- s_NftInfo[_tokenId].lastFulfilledAt = block.timestamp;
+ s_weatherNftInfo[_tokenId].lastFulfilledAt = block.timestamp;
// make functions request
string memory pincode = s_weatherNftInfo[_tokenId].pincode;
string memory isoCode = s_weatherNftInfo[_tokenId].isoCode;
bytes32 _reqId = _sendFunctionsWeatherFetchRequest(pincode, isoCode);
s_funcReqIdToTokenIdUpdate[_reqId] = _tokenId;
emit NftWeatherUpdateRequestSend(_tokenId, _reqId, upkeepId);
}
Updates

Appeal created

bube Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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