Weather Witness

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

Limited Weather State Mapping

Summary

The contract only maps a subset of OpenWeatherMap condition codes to the contract's weather states, potentially resulting in inaccurate weather representation for certain conditions.

Vulnerability Details

The GetWeather.js file implements a limited mapping of OpenWeatherMap condition codes to the contract's weather states:

// 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;
// cloudy
else if (weather_id_x === 8) weather_enum = 1;
// windy
else weather_enum = 4;

This mapping doesn't account for many weather conditions that OpenWeatherMap can report, such as:

  • Atmospheric conditions (Group 7xx: mist, fog, etc.)

  • Extreme weather conditions (Group 9xx)

  • Specific variations within each major category

Proof of Concept

// SPDX-License-Identifier: MIT
// This is a JavaScript test file that demonstrates the limited weather state mapping issue
// Mock the Chainlink Functions environment
const Functions = {
makeHttpRequest: async (params) => {
// Mock implementation that returns predefined responses
if (params.url.includes("geo/1.0/zip")) {
return {
data: {
lat: 40.7128,
lon: -74.0060
}
};
} else if (params.url.includes("data/2.5/weather")) {
// Return different weather conditions for testing
return {
data: {
weather: [
{
id: mockWeatherId, // This will be set in the test cases
main: mockWeatherMain, // This will be set in the test cases
description: mockWeatherDescription // This will be set in the test cases
}
],
main: {
temp: 20
}
}
};
}
}
};
// Import the GetWeather function (mocked for testing)
const getWeather = (args, secrets) => {
// This is a simplified version of the actual GetWeather.js logic
return new Promise(async (resolve) => {
try {
// Get location data
const geoCodingRequest = await Functions.makeHttpRequest({
url: "http://api.openweathermap.org/geo/1.0/zip",
method: "GET",
params: { zip: `${args[0]},${args[1]}`, appid: secrets.apiKey }
});
// Get weather data
const weatherRequest = await Functions.makeHttpRequest({
url: "https://api.openweathermap.org/data/2.5/weather",
method: "GET",
params: {
lat: geoCodingRequest.data.lat,
lon: geoCodingRequest.data.lon,
appid: secrets.apiKey
}
});
// Extract weather ID and map to enum
const weather_id = weatherRequest.data.weather[0].id;
const weather_id_x = Math.floor(weather_id / 100);
let weather_enum;
// This is the limited mapping logic
// 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;
// cloudy
else if (weather_id_x === 8) weather_enum = 1;
// windy
else weather_enum = 4;
// Return the result
resolve([weather_enum, Math.round(weatherRequest.data.main.temp)]);
} catch (error) {
resolve([0, 0]); // Default to SUNNY and 0 temperature on error
}
});
};
// Test cases to demonstrate the issue
async function runTests() {
const args = ["10001", "US"]; // NYC zip code
const secrets = { apiKey: "mock_api_key" };
console.log("Testing Limited Weather State Mapping Issues:");
// Test case 1: Weather ID 801 (Few clouds)
console.log("\nTest Case 1: Weather ID 801 (Few clouds)");
mockWeatherId = 801;
mockWeatherMain = "Clouds";
mockWeatherDescription = "few clouds";
const result1 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Description: ${mockWeatherDescription}`);
console.log(`Mapped to enum: ${result1[0]} (${getWeatherName(result1[0])})`);
console.log(`Issue: All cloud conditions (few clouds, scattered clouds, broken clouds, overcast) are mapped to the same CLOUDY state, losing detail`);
// Test case 2: Weather ID 511 (Freezing rain)
console.log("\nTest Case 2: Weather ID 511 (Freezing rain)");
mockWeatherId = 511;
mockWeatherMain = "Rain";
mockWeatherDescription = "freezing rain";
const result2 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Description: ${mockWeatherDescription}`);
console.log(`Mapped to enum: ${result2[0]} (${getWeatherName(result2[0])})`);
console.log(`Issue: Freezing rain is mapped to RAINY, but could be better represented as a mix of RAINY and SNOW`);
// Test case 3: Weather ID 771 (Squalls)
console.log("\nTest Case 3: Weather ID 771 (Squalls)");
mockWeatherId = 771;
mockWeatherMain = "Squall";
mockWeatherDescription = "squalls";
const result3 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Description: ${mockWeatherDescription}`);
console.log(`Mapped to enum: ${result3[0]} (${getWeatherName(result3[0])})`);
console.log(`Issue: Squalls are mapped to WINDY by default, but are more severe than regular wind`);
// Test case 4: Weather ID 300 (Light drizzle) vs 502 (Heavy rain)
console.log("\nTest Case 4: Weather ID 300 (Light drizzle) vs 502 (Heavy rain)");
mockWeatherId = 300;
mockWeatherMain = "Drizzle";
mockWeatherDescription = "light intensity drizzle";
const result4a = await getWeather(args, secrets);
mockWeatherId = 502;
mockWeatherMain = "Rain";
mockWeatherDescription = "heavy intensity rain";
const result4b = await getWeather(args, secrets);
console.log(`Light drizzle (ID: 300) mapped to: ${getWeatherName(result4a[0])} (${result4a[0]})`);
console.log(`Heavy rain (ID: 502) mapped to: ${getWeatherName(result4b[0])} (${result4b[0]})`);
console.log(`Issue: Both light drizzle and heavy rain are mapped to the same RAINY state, despite significant intensity difference`);
// Test case 5: Weather ID 602 (Heavy snow) vs 600 (Light snow)
console.log("\nTest Case 5: Weather ID 602 (Heavy snow) vs 600 (Light snow)");
mockWeatherId = 600;
mockWeatherMain = "Snow";
mockWeatherDescription = "light snow";
const result5a = await getWeather(args, secrets);
mockWeatherId = 602;
mockWeatherMain = "Snow";
mockWeatherDescription = "heavy snow";
const result5b = await getWeather(args, secrets);
console.log(`Light snow (ID: 600) mapped to: ${getWeatherName(result5a[0])} (${result5a[0]})`);
console.log(`Heavy snow (ID: 602) mapped to: ${getWeatherName(result5b[0])} (${result5b[0]})`);
console.log(`Issue: Both light and heavy snow are mapped to the same SNOW state, despite significant intensity difference`);
}
// Helper function to convert enum to weather name
function getWeatherName(enumValue) {
const weatherNames = ["SUNNY", "CLOUDY", "RAINY", "THUNDERSTORM", "WINDY", "SNOW"];
return weatherNames[enumValue] || "UNKNOWN";
}
// Run the tests
runTests();

This PoC demonstrates several issues with the limited weather state mapping:

  1. Different cloud conditions (few clouds, scattered clouds, broken clouds) are all mapped to the same CLOUDY state

  2. Different precipitation intensities (light drizzle vs heavy rain) are mapped to the same RAINY state

  3. Special weather conditions like freezing rain lack appropriate representation

  4. Atmospheric conditions like squalls are mapped to generic WINDY state

  5. Different snow intensities are mapped to the same SNOW state

The limited mapping reduces the accuracy and detail of the weather representation in the NFTs, potentially leading to confusion when users see a generic weather state that doesn't match the actual conditions.

Impact

The limited mapping results in:

  • Inaccurate weather representation for many conditions

  • Defaulting to WINDY for many unhandled weather types

  • Reduced user experience due to imprecise weather data

  • Potential confusion when NFT weather doesn't match actual conditions

Tools Used

  • Manual code review

  • OpenWeatherMap API documentation analysis

Recommendations

  1. Expand the weather condition mapping to cover more OpenWeatherMap codes:

// Group 2xx: Thunderstorm
if (weather_id_x === 2) weather_enum = 3;
// Group 3xx: Drizzle and 5xx: Rain
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
// Group 6xx: Snow
else if (weather_id_x === 6) weather_enum = 5;
// Group 7xx: Atmosphere (fog, mist, etc.)
else if (weather_id_x === 7) weather_enum = 1; // Map to cloudy
// Clear sky (800)
else if (weather_id === 800) weather_enum = 0;
// Group 80x: Clouds
else if (weather_id_x === 8) weather_enum = 1;
// Group 9xx: Extreme weather
else if (weather_id_x === 9) {
// Map extreme weather appropriately
if (weather_id >= 950 && weather_id <= 956) {
weather_enum = 4; // Wind-related
} else {
weather_enum = 3; // Thunderstorm for other extreme conditions
}
}
else weather_enum = 4; // Default fallback
  1. Consider expanding the Weather enum in the contract to include more states for better representation.

Updates

Appeal created

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

Support

FAQs

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