Weather Witness

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

No monitor on Link balance for weather update automation

Root + Impact

Description

  • The contract offers the option for weather update automation of the Weather NFT

  • In fulfillMintRequest() if the user opted for, a Chainlink Keeper will be registered to auto-update this NFT's weather.

  • So the Chainlink Upkeep, which triggers the function automation, needs Link to function

  • When there is no Link balance, the automation stops working

function fulfillMintRequest(bytes32 requestId) external {
bytes memory response = s_funcReqIdToMintFunctionReqResponse[requestId]
.response;
bytes memory err = s_funcReqIdToMintFunctionReqResponse[requestId].err;
require(
response.length > 0 || err.length > 0,
WeatherNft__Unauthorized()
);
if (response.length == 0 || err.length > 0) {
return;
}
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[
requestId
];
uint8 weather = abi.decode(response, (uint8));
uint256 tokenId = s_tokenCounter;
s_tokenCounter++;
emit WeatherNFTMinted(requestId, msg.sender, Weather(weather));
_mint(msg.sender, tokenId);
s_tokenIdToWeather[tokenId] = Weather(weather);
uint256 upkeepId;
if (_userMintRequest.registerKeeper) {
LinkTokenInterface(s_link).approve(
s_keeperRegistrar,
_userMintRequest.initLinkDeposit
);
IAutomationRegistrarInterface.RegistrationParams
memory _keeperParams = IAutomationRegistrarInterface
.RegistrationParams({
name: string.concat(
"Weather NFT Keeper: ",
Strings.toString(tokenId)
),
encryptedEmail: "",
upkeepContract: address(this),
gasLimit: s_upkeepGaslimit,
adminAddress: address(this),
triggerType: 0,
checkData: abi.encode(tokenId),
triggerConfig: "",
offchainConfig: "",
amount: uint96(_userMintRequest.initLinkDeposit)
});
upkeepId = IAutomationRegistrarInterface(s_keeperRegistrar)
.registerUpkeep(_keeperParams);
}
// @audit-issue Monitor Link balance in Upkeep to avoid running low
s_weatherNftInfo[tokenId] = WeatherNftInfo({
heartbeat: _userMintRequest.heartbeat,
lastFulfilledAt: block.timestamp,
upkeepId: upkeepId,
pincode: _userMintRequest.pincode,
isoCode: _userMintRequest.isoCode
});
}

Risk

Likelihood:

  • Reason 1 Weather NFT is automated to update weather, which is done periodically

  • Reason 2 Link balance of the Upkeep decreases, reaching a value which is below it's operating value

Impact:

  • Impact 1 Automation of wehater update for the Weather NFT stops

  • Impact 2 Weather NFT will show outdated weather status, unless updated manually

  • Impact 3 Important functionality of the procol stops working

Proof of Concept

Recommended Mitigation

Add a function that retrieves Upkeep's Link Balance.

Add a minimum Link balance treshold.

Check if Link balance is below Link treshold and revert with message if so.

function fulfillMintRequest(bytes32 requestId) external {
bytes memory response = s_funcReqIdToMintFunctionReqResponse[requestId]
.response;
bytes memory err = s_funcReqIdToMintFunctionReqResponse[requestId].err;
require(
response.length > 0 || err.length > 0,
WeatherNft__Unauthorized()
);
if (response.length == 0 || err.length > 0) {
return;
}
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[
requestId
];
uint8 weather = abi.decode(response, (uint8));
uint256 tokenId = s_tokenCounter;
s_tokenCounter++;
emit WeatherNFTMinted(requestId, msg.sender, Weather(weather));
_mint(msg.sender, tokenId);
s_tokenIdToWeather[tokenId] = Weather(weather);
uint256 upkeepId;
if (_userMintRequest.registerKeeper) {
bool success = LinkTokenInterface(s_link).approve(
s_keeperRegistrar,
_userMintRequest.initLinkDeposit
);
if (!success) {
return "Aprove failed";
}
IAutomationRegistrarInterface.RegistrationParams
memory _keeperParams = IAutomationRegistrarInterface
.RegistrationParams({
name: string.concat(
"Weather NFT Keeper: ",
Strings.toString(tokenId)
),
encryptedEmail: "",
upkeepContract: address(this),
gasLimit: s_upkeepGaslimit,
adminAddress: address(this),
triggerType: 0,
checkData: abi.encode(tokenId),
triggerConfig: "",
offchainConfig: "",
amount: uint96(_userMintRequest.initLinkDeposit)
});
upkeepId = IAutomationRegistrarInterface(s_keeperRegistrar)
.registerUpkeep(_keeperParams);
}
// @audit-issue Monitor Link balance to avoid running low
// Function to retrieve Upkeep Link balance
// + function getUpkeepLinkBalance(uint256 upkeepId) external view returns (uint96) {
// + (, , , uint96 balance, , , , , ) = IAutomationRegistry(s_keeperRegistry).getUpkeep(upkeepId);
// + return balance;
// + }
// ✅ Get LINK balance of the newly registered upkeep
// + uint96 upkeepLinkBalance = getUpkeepLinkBalance(upkeepId);
// ✅ Choose a minimum threshold in LINK (scaled by 10**18)
// + uint96 minRequiredLink = 1 ether; // Example: 1 LINK
// ✅ Check if Link balance is under treshold's value and revert if so
// + if (upkeepLinkBalance >= minRequiredLink) {
// + revert "InsufficientUpkeepLINKBalance";
// + }
s_weatherNftInfo[tokenId] = WeatherNftInfo({
heartbeat: _userMintRequest.heartbeat,
lastFulfilledAt: block.timestamp,
upkeepId: upkeepId,
pincode: _userMintRequest.pincode,
isoCode: _userMintRequest.isoCode
});
}
function _fulfillWeatherUpdate(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal {
if (response.length == 0 || err.length > 0) {
return;
}
uint256 tokenId = s_funcReqIdToTokenIdUpdate[requestId];
uint8 weather = abi.decode(response, (uint8));
s_weatherNftInfo[tokenId].lastFulfilledAt = block.timestamp;
s_tokenIdToWeather[tokenId] = Weather(weather);
emit NftWeatherUpdated(tokenId, Weather(weather));
}
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal override {
if (s_funcReqIdToUserMintReq[requestId].user != address(0)) {
s_funcReqIdToMintFunctionReqResponse[
requestId
] = MintFunctionReqResponse({response: response, err: err});
} else if (s_funcReqIdToTokenIdUpdate[requestId] > 0) {
_fulfillWeatherUpdate(requestId, response, err);
}
}
Updates

Appeal created

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

[Invalid] The LINK deposit is not checked

This is informational/invalid. If the LINK deposit is not enough, the function `registerUpkeep` will revert and it is responsibility of the user to provide the correct amount of `_initLinkDeposit`, if the user wants automated weather updates.

Support

FAQs

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