Weather Witness

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

No Mechanism to Withdraw ETH/LINK

[L-1]No Mechanism to Withdraw ETH/LINK

Description:
The contract lacks a mechanism to withdraw ETH or LINK tokens that may become trapped in the contract. This includes mint fees collected, leftover LINK from Chainlink function calls, or direct transfers to the contract.

Impact:
If ETH or LINK tokens get trapped in the contract, they become permanently inaccessible, resulting in lost funds for the project owners or users.

Proof of Concept:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;
import {Test, console} from "forge-std/Test.sol";
import {WeatherNft} from "src/WeatherNft.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
contract WeatherNftWithdrawalTest is Test {
WeatherNft weatherNft;
LinkTokenInterface linkToken;
address owner = makeAddr("owner");
address user = makeAddr("user");
function setUp() external {
// Use the deployed contract
weatherNft = WeatherNft(0x4fF356bB2125886d048038386845eCbde022E15e);
linkToken = LinkTokenInterface(0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846);
// Fund user
vm.deal(user, 10 ether);
deal(address(linkToken), user, 100e18);
}
function test_stuckFunds() public {
// Record initial balances
uint256 initialContractETH = address(weatherNft).balance;
uint256 initialContractLINK = linkToken.balanceOf(address(weatherNft));
// Simulate a mint to add ETH to contract
vm.startPrank(user);
uint256 mintPrice = weatherNft.s_currentMintPrice();
weatherNft.requestMintWeatherNFT{value: mintPrice}(
"125001", // pincode
"IN", // isoCode
false, // registerKeeper
12 hours, // heartbeat
0 // initLinkDeposit
);
vm.stopPrank();
// Directly send LINK to contract (could happen accidentally)
vm.prank(user);
linkToken.transfer(address(weatherNft), 5e18);
// Check updated balances
uint256 finalContractETH = address(weatherNft).balance;
uint256 finalContractLINK = linkToken.balanceOf(address(weatherNft));
console.log("ETH in contract:", finalContractETH);
console.log("LINK in contract:", finalContractLINK);
// Try to find withdraw functions
bytes4[] memory selectors = new bytes4[](0);
bool hasWithdrawFunction = false;
// Look for common withdrawal function signatures
bytes4[] memory commonWithdrawFunctions = new bytes4[](5);
commonWithdrawFunctions[0] = bytes4(keccak256("withdraw()"));
commonWithdrawFunctions[1] = bytes4(keccak256("withdrawETH()"));
commonWithdrawFunctions[2] = bytes4(keccak256("withdrawLink()"));
commonWithdrawFunctions[3] = bytes4(keccak256("withdrawTokens(address)"));
commonWithdrawFunctions[4] = bytes4(keccak256("emergencyWithdraw()"));
for (uint i = 0; i < commonWithdrawFunctions.length; i++) {
(bool success, ) = address(weatherNft).staticcall(abi.encodeWithSelector(commonWithdrawFunctions[i]));
if (success) {
hasWithdrawFunction = true;
break;
}
}
console.log("Contract has withdrawal function:", hasWithdrawFunction);
assertFalse(hasWithdrawFunction, "Withdraw function exists, vulnerability not confirmed");
// If balance increased and no withdraw function, funds are stuck
assertGt(finalContractETH, initialContractETH, "No ETH added to contract");
assertGt(finalContractLINK, initialContractLINK, "No LINK added to contract");
console.log("VULNERABILITY CONFIRMED: Funds are stuck in the contract");
}
}
Updates

Appeal created

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

Locked LINK tokens

Support

FAQs

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