Weather Witness

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

[H-1] Contract locks Ether without a withDraw function.

Root + Impact

Loss of ETH/Trapped ETH in WeatherNft::requestmintWeatherNFT function.

Impact: Medium severity—trapped ETH could lead to loss of user funds, though not exploitable for theft.

Description

The WeatherNft.sol::requestMintWeatherNFT function allows users to mint NFTs tied to geographic locations by paying a mint price in ETH. The function accepts ETH via msg.value to cover the minting cost, which increases with each mint. However, there is no mechanism in the contract to withdraw this ETH, meaning any ETH sent—whether overpaid, unused, or accumulated from failed mints—becomes trapped, inaccessible to users or the contract owner.

function requestMintWeatherNFT(
string memory _pincode,
string memory _isoCode,
bool _registerKeeper,
uint256 _heartbeat,
uint256 _initLinkDeposit
) external payable returns (bytes32 _reqId) {
//@> Start of root cause
require(
msg.value == s_currentMintPrice,
WeatherNft__InvalidAmountSent()
);
s_currentMintPrice += s_stepIncreasePerMint;
//@> End of root cause
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
});
}
## Risk
**Likelihood**:
Users send excess ETH when minting an NFT, as the require only checks for equality but doesn’t refund overpayments.
The minting process fails after ETH is sent, such as if _sendFunctionsWeatherFetchRequest reverts, leaving ETH in the contract.
**Impact**:
Users lose access to ETH sent in excess or during failed mints, as there’s no way to retrieve it.
The contract owner cannot manage or refund trapped ETH, potentially leading to user disputes or loss of trust.
## Proof of Concept
```
// Attacker scenario: Overpay ETH during minting
// 1. Call `requestMintWeatherNFT` with excess ETH
function attack() external payable {
// Assume s_currentMintPrice is 0.1 ETH
// Attacker sends 0.2 ETH (overpaying)
WeatherNft(weatherNftAddress).requestMintWeatherNFT{value: 0.2 ether}(
"12345", // pincode
"US", // isoCode
false, // _registerKeeper
0, // _heartbeat
0 // _initLinkDeposit
);
}
```
As we can see, Result: 0.1 ETH is trapped since `require` passes but excess isn’t refunded
## Recommended Mitigation
Add a `withdrawEther` function to allow the contract owner to retrieve trapped ETH, ensuring funds arent permanently locked.
```
+ // Add this function to WeatherNft.sol
+ function withdrawEther() external onlyOwner {
+ payable(msg.sender).transfer(address(this).balance);
+ }
```
Alternatively, refund excess ETH directly in requestMintWeatherNFT:
```diff
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()
- );
+ require(
+ msg.value >= s_currentMintPrice,
+ WeatherNft__InvalidAmountSent()
+ );
+ if (msg.value > s_currentMintPrice) {
+ payable(msg.sender).transfer(msg.value - s_currentMintPrice);
+ }
s_currentMintPrice += s_stepIncreasePerMint;
// ... rest of the function
}
```
Updates

Appeal created

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

Lack of `withdraw` function

The contract collects funds for minting a WeatherNFT, but there is no function that allows the owner to withdraw these funds.

Support

FAQs

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