Weather Witness

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

Unrestricted _heartbeat Parameter

No restrictions on the _heartbeat parameter in the requestMintWeatherNFT()

Description

  • The requestMintWeatherNFT() function allows users to specify a _heartbeat parameter, which dictates the minimum time interval (in seconds) between automated weather updates for the minted NFT via Chainlink Keepers (Automation). This parameter is intended to control the frequency and associated costs of keeping the NFT's weather data fresh. However, there are no validation checks on the _heartbeat value provided by the user. This absence of restrictions allows users to set arbitrarily low or high values, leading to potential issues for both the protocol's operational costs and the utility of the minted NFTs.

function requestMintWeatherNFT(
string memory _pincode,
string memory _isoCode,
bool _registerKeeper,
uint256 _heartbeat, // @> No validation or bounds checking on this parameter
uint256 _initLinkDeposit
) external payable returns (bytes32 _reqId) {
// ...
s_funcReqIdToUserMintReq[_reqId] = UserMintRequest({
user: msg.sender,
pincode: _pincode,
isoCode: _isoCode,
registerKeeper: _registerKeeper,
heartbeat: _heartbeat, // @> Unrestricted value directly stored
initLinkDeposit: _initLinkDeposit
});
}
function checkUpkeep(bytes calldata checkData)
external
view
override
returns (bool upkeepNeeded, bytes memory performData)
{
uint256 _tokenId = abi.decode(checkData, (uint256));
// ...
upkeepNeeded =
(block.timestamp >= s_weatherNftInfo[_tokenId].lastFulfilledAt + s_weatherNftInfo[_tokenId].heartbeat); // @> Unrestricted heartbeat used in logic
// ...
}

Risk

Likelihood: High

  • The _heartbeat parameter is a direct input from the user in the requestMintWeatherNFT function, which can be set to any arbitrary uint256 value.

  • The contract's logic lacks any require statements or other validation mechanisms to enforce a reasonable or safe range for the _heartbeat parameter.

Impact: High

  • Excessive LINK Drain / Denial of Service: Setting _heartbeat to a very low value (e.g., 1 second) or zero would cause the NFT to be almost continuously eligible for updates. Each performUpkeep call triggers a Chainlink Functions request, consuming LINK from the contract's subscription. This can quickly deplete the contract owner's LINK funds, leading to a denial of service for all NFTs that rely on automated updates.

  • Stale Data / Reduced Utility: Setting _heartbeat to an impractically high value (e.g., type(uint256).max or many years) would result in the NFT's weather data rarely, if ever, being updated automatically. This significantly reduces the utility and accuracy of the "live weather" feature of the NFT, diminishing its value proposition for users.

Recommended Mitigation

Implement explicit require statements in the requestMintWeatherNFT() function to enforce a sensible minimum and maximum value for the _heartbeat parameter. This prevents users from setting values that could either exhaust contract funds or render the NFT's dynamic feature useless.

function requestMintWeatherNFT(
string memory _pincode,
string memory _isoCode,
bool _registerKeeper,
uint256 _heartbeat,
uint256 _initLinkDeposit
) external payable returns (bytes32 _reqId) {
require(msg.value == s_currentMintPrice, WeatherNft__InvalidAmountSent());
s_currentMintPrice += s_stepIncreasePerMint;
+ // MITIGATION: Add validation for _heartbeat parameter
+ // Enforce a minimum heartbeat (e.g., 1 hour to prevent excessive updates)
+ require(_heartbeat >= 1 hours, WeatherNft__HeartbeatTooLow());
+ // Enforce a maximum heartbeat (e.g., 30 days to ensure data freshness)
+ require(_heartbeat <= 30 days, WeatherNft__HeartbeatTooHigh());
+
if (_registerKeeper) {
IERC20(s_link).safeTransferFrom(msg.sender, address(this), _initLinkDeposit);
}
_reqId = _sendFunctionsWeatherFetchRequest(_pincode, _isoCode);
emit WeatherNFTMintRequestSent(msg.sender, _pincode, _isoCode, _reqId);
s_funcReqIdToUserMintReq[_reqId] = UserMintRequest({
user: msg.sender,
pincode: _pincode,
isoCode: _isoCode,
registerKeeper: _registerKeeper,
heartbeat: _heartbeat,
initLinkDeposit: _initLinkDeposit
});
}
+
+ // New custom errors for heartbeat validation
+ error WeatherNft__HeartbeatTooLow();
+ error WeatherNft__HeartbeatTooHigh();
Updates

Appeal created

bube Lead Judge 5 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[Invalid] Lack of input validation in `requestMintWeatherNFT`

This is informational. It is user's responsibility to provide correct input arguments. If the user provides incorrect arguments, it will lead to incorrect results, lost funds or failed transaction.

Support

FAQs

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