Weather Witness

First Flight #40
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Incomplete Handling of `weather_id_x` in `GetWeather.js` Defaults to "WINDY" State, Potentially Inaccurate

Root + Impact

Description

The GetWeather.js script fetches weather data from the OpenWeather API and maps the API's weather condition ID (weather_id) to the Weather enum defined in the smart contract. The script first categorizes weather conditions by calculating weather_id_x (dividing weather_id by 100, e.g., 2xx for Thunderstorm, 5xx for Rain).
However, the if-else if structure only explicitly handles weather_id_x values for 2 (Thunderstorm), 3 (Drizzle), 5 (Rain), 6 (Snow), and 8 (Clouds/Clear). The OpenWeather API defines other weather ID categories, notably:

  • 7xx (Atmosphere): Includes conditions like Fog, Sand, Dust, Volcanic Ash, Tornado, etc.

According to the current script logic, any weather_id_x not equal to 2, 3, 5, 6, or 8 (e.g., when weather_id_x is 7) falls into the final else clause, causing weather_enum to be set to 4 (WINDY). This means various non-windy atmospheric conditions (like fog, haze, dust storms) will be incorrectly represented as "WINDY".
The README file acknowledges this as a known issue: "The calculation of weather_enum in GetWeather.js is limited and set to windy in else statement even though multiple weather exists (this is done just to keep limited images)". While this was an intentional choice for image simplicity, it still constitutes an issue from an accuracy and user expectation standpoint, as it leads to the NFT displaying a state incongruent with the actual weather.

// Root cause in the codebase with @> marks to highlight the relevant section
// File: functionsSource/GetWeather.js
// ... (API request parts) ...
const weather_id = weatherResponse.data.weather[0].id;
const weather_id_x = parseInt(weather_id / 100); // Gets the hundreds digit of weather ID for categorization
let weather_enum = 0;
// ref: https://openweathermap.org/weather-conditions
// thunderstorm
if (weather_id_x === 2) weather_enum = 3;
// rain
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2; // 3xx (Drizzle) and 5xx (Rain) map to RAINY
// snow
else if (weather_id_x === 6) weather_enum = 5;
// clear
else if (weather_id === 800) weather_enum = 0; // Special handling for ID 800 (Clear)
// cloudy
else if (weather_id_x === 8) weather_enum = 1; // 8xx (Clouds) map to CLOUDY (except 800)
// windy
// @> else weather_enum = 4; // Any other weather_id_x (e.g., 7xx - Atmosphere) falls here, incorrectly categorized as WINDY
return Functions.encodeUint256(weather_enum);

Risk

Likelihood: High

  • The OpenWeather API defines various weather condition IDs under the 7xx (Atmosphere) category, which can occur globally.

  • Whenever these 7xx conditions occur, the script will inaccurately map them to "WINDY".

Impact: Medium

  • Inaccurate NFT State: The NFT will display a "WINDY" state when the actual weather might be fog, haze, dust storms, tornadoes, etc., which contradicts the NFT's core value proposition of reflecting real weather.

  • User Confusion and Reduced Trust: Users may notice discrepancies between the NFT's displayed weather and the actual observed weather, eroding trust in the accuracy of the NFT system.

  • Potential Misjudgment of Value: If the NFT's value or utility is in any way tied to its displayed weather state, inaccurate representation could lead to misjudgments.

Proof of Concept

  1. A user mints a Weather NFT for a location (e.g., San Francisco).

  2. The actual weather at the location is dense fog, and the OpenWeather API returns a weather ID like 741 (Fog).

  3. The GetWeather.js script executes:

    • weather_id = 741

    • weather_id_x = parseInt(741 / 100) = 7

  4. In the if-else if conditional logic:

    • weather_id_x === 2 (false)

    • weather_id_x === 3 || weather_id_x === 5 (false)

    • weather_id_x === 6 (false)

    • weather_id === 800 (false)

    • weather_id_x === 8 (false)

    • The else branch is executed, and weather_enum is set to 4 (WINDY).

  5. Functions.encodeUint256(4) is returned to the smart contract.

  6. The smart contract updates the NFT's weather state to WINDY.

  7. The user views the NFT and sees "WINDY", while it's actually foggy outside, leading to confusion.

Recommended Mitigation

Although the README mentions this is for image simplicity, for functionality and accuracy, a more comprehensive handling of weather conditions should be considered, or at least a more neutral or "unknown" default for unhandled categories, rather than misclassifying as "WINDY".

  1. Expand Mapping: Add handling for the 7xx (Atmosphere) category. If the Weather enum in the contract and image resources allow, map it to a new enum value (e.g., FOGGY_MISTY) or the closest existing one (e.g., CLOUDY might be a better approximation for some 7xx conditions than WINDY if the contract supports it).

  2. More Appropriate Default: If not expanding the enum, change the default value in the else clause to a more generic state like CLOUDY (1) or SUNNY (0), or, if the contract could support it, an UNKNOWN or OTHER state. Defaulting unknown or unhandled weather to "WINDY" is misleading.

  3. Update Contract Enum and Images: The most complete solution would be to extend the Weather enum in the contract to include more weather types (like FOGGY, HAZY, etc.) and provide corresponding image URIs.

Given the current fixed Weather enum in the contract, a pragmatic short-term fix could be to map unhandled cases to CLOUDY, as this is generally more neutral than WINDY, or to specifically add a mapping for 7xx (perhaps also to CLOUDY or another existing state if deemed appropriate by the project).

An example of improved mapping logic in GetWeather.js, mapping 7xx to CLOUDY and making the final else also CLOUDY as a safer default:

// File: functionsSource/GetWeather.js
// ... (API request parts) ...
const weather_id = weatherResponse.data.weather[0].id;
const weather_id_x = parseInt(weather_id / 100);
let weather_enum = 0;
// ref: https://openweathermap.org/weather-conditions
// thunderstorm
if (weather_id_x === 2) weather_enum = 3;
// rain
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
// snow
else if (weather_id_x === 6) weather_enum = 5;
// clear
else if (weather_id === 800) weather_enum = 0;
+// atmosphere conditions (e.g., fog, mist, haze) - map to CLOUDY as a general representation
+else if (weather_id_x === 7) weather_enum = 1; // 7xx (Atmosphere) maps to CLOUDY
// cloudy
else if (weather_id_x === 8) weather_enum = 1;
// windy - This case should ideally be more specific if possible,
// based on OpenWeather's wind data, not just a catch-all.
// For now, if truly windy conditions are desired, they might need a different trigger.
// The original 'else' for windy is problematic as a catch-all.
// Acknowledge the README: "this is done just to keep limited images"
// If a specific 'WINDY' state is truly desired for specific conditions, it should be explicit.
// The current broad 'else' is misleading. Let's make 'CLOUDY' the fallback for unhandled.
-else weather_enum = 4;
+// Default to CLOUDY for any other unhandled cases to be safer than WINDY
+else weather_enum = 1; // Fallback to CLOUDY
return Functions.encodeUint256(weather_enum);

Important Note: The diff above is an example aimed at reducing misrepresentation as "WINDY". Ideally, a "WINDY" state should be determined based on actual wind speed data from the OpenWeather API, not as a default for unclassified weather conditions. Given the current contract enum limitations, choosing the least misleading existing enum as a default is key. If the project aims for maximum accuracy, extending the contract's enum and associated images would be necessary.

Updates

Appeal created

bube Lead Judge 5 days ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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