Weather Witness

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

If an error in `WeatherNft::fulfillMintRequest` occurs the user can't withdraw the deposited Link.

If an error in WeatherNft::fulfillMintRequest occurs the user can't withdraw the deposited Link.

Description:
If an error occurs in WeatherNft::fulfillMintRequest (e.g., due to invalid response data or other issues), the LINK tokens deposited by the user for Chainlink Keepers remain locked in the contract, with no way for the user to withdraw them.

Impact:
Users may lose their deposited LINK if the minting process fails, leading to financial loss and reduced trust in the platform.

Proof of Concept:
Add the following to the test suite:

unction testOnfulfillMintRequestFailDepositedLinkStucks() public {
string memory pincode = "0000";
string memory isoCode = "XX";
bool registerKeeper = true;
uint256 heartbeat = 12 hours;
uint256 initLinkDeposit = 5e18;
uint256 tokenId = weatherNft.s_tokenCounter();
vm.startPrank(user);
linkToken.approve(address(weatherNft), initLinkDeposit);
bytes32 reqId = weatherNft.requestMintWeatherNFT{value: weatherNft.s_currentMintPrice()}(
pincode, isoCode, registerKeeper, heartbeat, initLinkDeposit
);
vm.stopPrank();
vm.prank(functionsRouter);
bytes memory weatherResponse = abi.encode(WeatherNftStore.Weather.RAINY);
weatherNft.handleOracleFulfillment(reqId, weatherResponse, "");
vm.prank(user);
vm.expectRevert(WeatherNftStore.WeatherNft__Unauthorized.selector);
// forcing a revert
weatherNft.fulfillMintRequest(bytes32("6666"));
assertEq(linkToken.balanceOf(address(weatherNft)), 5e18);
}

Recommended Mitigation:
Add a withdraw functionality to WeatherNft that check the owner of the requestId.

Example:

On WeathersNftStore add:

// variables
uint256 public s_tokenCounter;
mapping(Weather => string) public s_weatherToTokenURI;
FunctionsConfig public s_functionsConfig;
mapping(bytes32 => UserMintRequest) public s_funcReqIdToUserMintReq;
mapping(bytes32 => MintFunctionReqResponse) public s_funcReqIdToMintFunctionReqResponse;
mapping(bytes32 => uint256) public s_funcReqIdToTokenIdUpdate;
+ mapping(address => uint256) addressToLinkDeposit;

On WeatherNft add:

// functions
+ function withdrawLinks(uint256 _tokenId) external {
+ address owner = _ownerOf(_tokenId);
+ if (owner != msg.sender) {
+ revert WeatherNft__Unauthorized();
+ } else {
+ LinkTokenInterface(s_link).approve(owner, addressToLinkDeposit[owner]);
+ IERC20(s_link).safeTransferFrom(address(this), address(this), addressToLinkDeposit[owner]);
+ }
+ }
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;
}
function requestMintWeatherNFT( // check
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;
if (_registerKeeper) {
+ addressToLinkDeposit[msg.sender] = _initLinkDeposit;
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,
// e - using a keeper or not
registerKeeper: _registerKeeper,
heartbeat: _heartbeat,
initLinkDeposit: _initLinkDeposit
});
}
Updates

Appeal created

bube Lead Judge 5 months 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.