Weather Witness

First Flight #40
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

[M-1] Silent Return on Error in fulfillMintRequest Function Causes Loss of User Feedback

[M-1] Silent Return on Error in fulfillMintRequest Function Causes Loss of User Feedback

Description

The fulfillMintRequest function is designed to process oracle responses for weather NFT minting requests, and when successful, mint a new NFT to the user.

However, due to a logic error, the function silently returns without any feedback when an oracle error occurs, leaving users confused and without their NFTs.

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;
@> }
// Rest of function that mints NFT...
}

Risk

Likelihood: High

  • Oracle errors are common in Web3 applications due to network issues, API failures, or data availability problems

  • Any error response from the oracle will trigger this condition

  • The function doesn't revert or emit events in error cases, making it hard to detect

Impact: Medium

  • Users experience transactions that theoretically succeed (don't revert) but don't actually mint any NFTs

  • No feedback mechanism exists to inform users about errors or failed requests

  • User funds (gas costs and potentially LINK deposits) are spent without clear outcome

  • Request data remains in storage indefinitely, potentially causing bloat

  • Users have no way to retry or troubleshoot failed requests

Proof of Concept

Run the test bellow in your testing suite:

function test_silentReturn_whenErrorResponseExists() public {
string memory pincode = "125001";
string memory isoCode = "IN";
bool registerKeeper = true;
uint256 heartbeat = 12 hours;
uint256 initLinkDeposit = 5e18;
uint256 initialTokenCount = weatherNft.s_tokenCounter();
console.log("Initial token count: ", initialTokenCount);
console.log("Initial price: ", weatherNft.s_currentMintPrice());
// Make request from user
vm.startPrank(user);
linkToken.approve(address(weatherNft), initLinkDeposit);
vm.recordLogs();
weatherNft.requestMintWeatherNFT{
value: weatherNft.s_currentMintPrice()
}(pincode, isoCode, registerKeeper, heartbeat, initLinkDeposit);
vm.stopPrank();
// Get request ID from logs
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 reqId;
for (uint256 i; i < logs.length; i++) {
if (
logs[i].topics[0] ==
keccak256(
"WeatherNFTMintRequestSent(address,string,string,bytes32)"
)
) {
(, , , reqId) = abi.decode(
logs[i].data,
(address, string, string, bytes32)
);
break;
}
}
assert(reqId != bytes32(0));
// Oracle fulfills with an error response (empty response with error message)
vm.prank(functionsRouter);
weatherNft.handleOracleFulfillment(reqId, "", "Error fetching data");
// Now try to fulfill the mint request
vm.prank(user);
// This call will return silently without reverting, but also without minting
weatherNft.fulfillMintRequest(reqId);
// Verify that no NFT was minted - token counter should remain unchanged
assertEq(weatherNft.s_tokenCounter(), initialTokenCount);
// Verify user never received an NFT
assertEq(weatherNft.balanceOf(user), 0);
// The request data should still be in storage since it wasn't cleaned up
(uint256 reqHeartbeat, address reqUser, , , , ) = weatherNft
.s_funcReqIdToUserMintReq(reqId);
assertEq(reqUser, user);
assertEq(reqHeartbeat, heartbeat);
}

As shown and explained above, we have no way of telling if the transaction truly failed, instead it will just return nothing and the user will not get any NFT.

Recommended Mitigation

Consider adding a few new custom errors to check for potential issues, and clear storage so that it doesn't bloat and increase gas costs.

function fulfillMintRequest(bytes32 requestId) external {
//...function logic
require(response.length > 0 || err.length > 0, WeatherNft__Unauthorized());
- if (response.length == 0 || err.length > 0) {
- return;
- }
+ // Revert with proper error message when there's a problem with the oracle response
+ if (err.length > 0) {
+ revert WeatherNft__OracleError(requestId, err);
+ }
+
+ // Ensure there's a valid response
+ require(response.length > 0, WeatherNft__EmptyResponse(requestId));
// Rest of function...
+ // Clean up storage
+ delete s_funcReqIdToMintFunctionReqResponse[requestId];
+ delete s_funcReqIdToUserMintReq[requestId];
}
// Add new error types
+ error WeatherNft__OracleError(bytes32 requestId, bytes errorMessage);
+ error WeatherNft__EmptyResponse(bytes32 requestId);
Updates

Appeal created

bube Lead Judge 23 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Lost fee in case of Oracle failure

If Oracle fails, the `fulfillMintRequest` function will not return the payed fee for the token to the user.

Support

FAQs

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