ethereumsolidityrevert

Attack function reverting.. Why?


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract GuessTheRandomNumberChallenge {
    bytes32 answer;

    constructor() payable {
        require(msg.value == 1 ether);
        answer = keccak256(abi.encodePacked(block.number - 1));
    }

    function isComplete() public view returns (bool) {
        return address(this).balance == 0;
    }

    function guess(bytes32 n) public payable {
        require(msg.value == 1 ether);

        if (n == answer) {
            payable(msg.sender).transfer(2 ether);
        } else revert("answer is wrong");
    }
}

contract Attack {
    address private addr;

    constructor(address _addr) payable {
        require(msg.value == 2 ether, " 2 ether requires ");
        addr = _addr;
    }

    GuessTheRandomNumberChallenge victim = GuessTheRandomNumberChallenge(addr);

    bytes32 x = keccak256(abi.encodePacked(block.number - 1));

    function attackTo() public payable {
        victim.guess{value: 1 ether}(x);
    }
}

I am trying to get guess() function by using Attack contract. but "victim.guess{value: 1 ether}(x); " line is always reverting.

here is the return :

transact to Attack.attackTo errored: VM error: revert.

revert
    The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance.
Debug the transaction to get more information.

Help please

I was thinking its because of {value: 1 ether }

Here is my Foundry test. test creates contracts but when it reaches to victim.guess{value: 1 ether}(x); line it reverst immediately. I think it's not about sending ethers. There is something else


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import {GuessTheRandomNumberChallenge, Attack} from "../src/GuessTheNumber.sol";

contract TestGuessNumber is Test {
    GuessTheRandomNumberChallenge public guessTheNumber;
    Attack public attack;

    function setUp() public {
        guessTheNumber = new GuessTheRandomNumberChallenge{value: 1 ether}();

        attack = new Attack{value: 2 ether}(address(guessTheNumber));
    }

    function testPwn() public {
        attack.attackTo();
    }
}


Solution

  • The issue is you are creating the victim variable while the address is uninitialized.

    address private addr;
    
    constructor(address _addr) payable {
        require(msg.value == 2 ether, " 2 ether requires ");
        addr = _addr;
    }
    
    GuessTheRandomNumberChallenge victim = GuessTheRandomNumberChallenge(addr);
    

    The victim address is going to be a 0 address, you have to move the initialization into the constructor, like this:

    address private addr;
    GuessTheRandomNumberChallenge victim;
    
    constructor(address _addr) payable {
        require(msg.value == 2 ether, " 2 ether requires ");
        addr = _addr;
        victim = GuessTheRandomNumberChallenge(addr);
    }
    

    Also you also have to include a receive or fallback function in your attack contract so it will be able to receive the ethers, like so:

    receive() external payable {
    
    }