Weather Witness

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

Lack of Fallback Mechanisms

Author Revealed upon completion

Summary

The contract lacks fallback mechanisms if external dependencies like Chainlink fail, potentially leaving NFTs stuck in an outdated state.

Vulnerability Details

The WeatherNft contract relies heavily on Chainlink Functions and Automation for its core functionality:

function performUpkeep(bytes calldata performData) external {
uint256 tokenId = abi.decode(performData, (uint256));
_sendFunctionsWeatherFetchRequest(
s_weatherNftInfo[tokenId].pincode,
s_weatherNftInfo[tokenId].isoCode
);
}

However, there are no fallback mechanisms implemented if:

  • Chainlink Functions service is disrupted

  • The OpenWeather API becomes unavailable

  • The Chainlink Automation network experiences issues

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;
import "forge-std/Test.sol";
import "../src/WeatherNft.sol";
contract FallbackMechanismsTest is Test {
WeatherNft public weatherNft;
address public owner;
address public user;
function setUp() public {
// Setup the WeatherNft contract and necessary dependencies
owner = address(0x1);
user = address(0x3);
// Deploy the contract with owner as the owner
vm.startPrank(owner);
weatherNft = new WeatherNft(
/* constructor parameters */
);
vm.stopPrank();
// Fund accounts
vm.deal(owner, 10 ether);
vm.deal(user, 10 ether);
}
function testChainlinkFunctionsFailure() public {
// This test simulates a scenario where Chainlink Functions service is disrupted
// 1. First, let's simulate a user minting an NFT
vm.startPrank(user);
// Assume user has minted NFT with tokenId 1
// and has set up automation
vm.stopPrank();
// 2. Record the initial weather state
// Assume the NFT has a current weather state
Weather initialWeather = Weather.SUNNY; // Mock initial state
// 3. Set up a mock Chainlink Functions Router that simulates service disruption
MockFailingFunctionsRouter mockRouter = new MockFailingFunctionsRouter();
// 4. Update the contract to use our mock router (this would require a function to update the router)
// weatherNft.updateFunctionsRouter(address(mockRouter));
// 5. Simulate an upkeep that triggers a weather update
vm.startPrank(address(0x999)); // Simulate Chainlink Automation
bytes memory performData = abi.encode(uint256(1)); // TokenId 1
weatherNft.performUpkeep(performData);
vm.stopPrank();
// 6. Verify that the weather state remains unchanged due to the failure
// In a real test, we would check the actual state
// assertEq(uint8(weatherNft.getWeatherState(1)), uint8(initialWeather));
// 7. Simulate passage of time (e.g., 30 days)
vm.warp(block.timestamp + 30 days);
// 8. Verify that there's no way for the user to manually update the weather
vm.startPrank(user);
// There's no function available for the user to manually update the weather
// This would require something like:
// weatherNft.manualWeatherUpdate(1, uint8(Weather.RAINY));
vm.stopPrank();
// This demonstrates that when Chainlink Functions fails, the NFT remains
// stuck with outdated weather data with no way for users to update it
}
function testOpenWeatherAPIFailure() public {
// This test simulates a scenario where the OpenWeather API is unavailable
// 1. Set up a mock JavaScript environment that simulates API failure
// (This would be part of the Chainlink Functions source code)
// 2. Simulate an upkeep that triggers a weather update
vm.startPrank(address(0x999)); // Simulate Chainlink Automation
bytes memory performData = abi.encode(uint256(1)); // TokenId 1
weatherNft.performUpkeep(performData);
vm.stopPrank();
// 3. Verify that the weather state remains unchanged due to the API failure
// In a real test, we would check the actual state
// 4. Simulate multiple failed attempts over time
for (uint i = 0; i < 5; i++) {
vm.warp(block.timestamp + 1 days);
vm.startPrank(address(0x999)); // Simulate Chainlink Automation
weatherNft.performUpkeep(performData);
vm.stopPrank();
// Weather state should still remain unchanged
}
// This demonstrates that when the OpenWeather API fails, the NFT remains
// stuck with outdated weather data with no fallback data source
}
function testChainlinkAutomationFailure() public {
// This test simulates a scenario where Chainlink Automation network is down
// 1. Record the initial weather state
// Assume the NFT has a current weather state
Weather initialWeather = Weather.SUNNY; // Mock initial state
// 2. Simulate passage of time without any upkeep calls (automation network down)
vm.warp(block.timestamp + 30 days);
// 3. Verify that the weather state remains unchanged due to no upkeep calls
// In a real test, we would check the actual state
// assertEq(uint8(weatherNft.getWeatherState(1)), uint8(initialWeather));
// 4. Verify that there's no way for the user to manually trigger an update
vm.startPrank(user);
// There's no function available for the user to manually trigger an update
// This would require something like:
// weatherNft.triggerWeatherUpdate(1);
vm.stopPrank();
// This demonstrates that when Chainlink Automation fails, the NFT remains
// stuck with outdated weather data with no way for users to trigger updates
}
}
// Mock Chainlink Functions Router that simulates service disruption
contract MockFailingFunctionsRouter {
function sendRequest(
uint64,
bytes memory,
uint32,
uint256
) external returns (bytes32) {
// Simulate a failure by reverting
revert("Chainlink Functions service disruption");
}
}

This PoC demonstrates three scenarios related to the lack of fallback mechanisms:

  1. Chainlink Functions failure: When the Chainlink Functions service is disrupted, weather updates fail with no alternative mechanism

  2. OpenWeather API failure: When the external API is unavailable, there's no fallback data source

  3. Chainlink Automation failure: When the automation network is down, there's no way for users to manually trigger updates

These scenarios show how the system's complete dependence on external services without fallback mechanisms can lead to NFTs being stuck with outdated weather data for extended periods.

Impact

Without fallback mechanisms:

  • NFTs could be stuck with outdated weather data

  • Users would have no way to manually update their NFTs

  • The system's reliability depends entirely on external services

  • Extended service disruptions could render the entire system non-functional

Recommendations

  1. Implement manual update functionality for emergencies:

function emergencyWeatherUpdate(
uint256 tokenId,
uint8 weatherState
) external {
require(ownerOf(tokenId) == msg.sender || msg.sender == owner(), "WeatherNft__Unauthorized");
require(
block.timestamp > s_lastEmergencyUpdate[tokenId] + EMERGENCY_UPDATE_COOLDOWN,
"WeatherNft__TooFrequent"
);
// Update the weather state
s_weatherNftInfo[tokenId].currentWeather = Weather(weatherState);
s_lastEmergencyUpdate[tokenId] = block.timestamp;
emit EmergencyWeatherUpdate(tokenId, weatherState, msg.sender);
}
  1. Add a circuit breaker pattern that can switch to alternative data sources if the primary source fails.

  2. Implement a monitoring system that can detect when external services are unavailable and notify users or administrators.

Updates

Appeal created

bube Lead Judge about 1 hour 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.