0

I'm trying to validate a condition using require(). If the condition is false for the first time, the contract is stopped and reverted to its initial state. On the other hand, if the condition is true for the first time and false second time onwards the contract continues to execute in spite of the false condition in require.

Similar behavior is observed in case of assert and revert. I'm testing everything in remix with JavaScript VM.

Can somebody help me to understand why this is happening?

In below example of SimpleDAO, we are changing the flag value when withdraw function is called and using this flag value we are checking if withdraw is called for more than one time. But the require is not working as expected and also the if condition which is placed after require is not evaluated except for the first call of withdraw.

pragma solidity >=0.4.0 <0.7.0;

contract SimpleDAO {
    mapping (address => uint256) public credit;
    bool flag = true;

    constructor() payable public {
        donate();
    }

    function donate() payable public{
        credit[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public{
        require(flag, "Only one time call is allowed.");
        flag = false;
        if (credit[msg.sender]>= amount) {
            msg.sender.call.value(amount)("");
            credit[msg.sender]-=amount;
        }
    }

    function queryCredit(address to) public view returns (uint256) {
        return credit[to];
    }
}

contract Mallory2 {
  SimpleDAO public dao;
  address payable owner; 

  constructor(SimpleDAO addr) public payable{
    owner = msg.sender;
    dao = addr;
  }

  function attack() public payable{
    dao.donate.value(1)();
    dao.withdraw(1);
  }

  function getJackpot() public{
    dao.withdraw(address(dao).balance);
    owner.transfer(address(this).balance);
  }

  function() external payable{
       dao.withdraw(1);
  }
}
8
  • We'd need to see your code to help debug, and perhaps you'll need to explain what you mean by "the first time" and "the second time". If your code says require(x) and x is false, then the transaction will be reverted.
    – user19510
    Commented May 12, 2019 at 8:58
  • "this is happening" because of how you implemented the contract (most likely, you have placed the require statement to be executed only under certain conditions, which means that your description above is not even correct). Obviously, no one here will be able to guess any further than that, since you haven't shared this contract! Commented May 12, 2019 at 9:04
  • Thanks. I have edited the post and added the example. I hope now you can understand my question better and provide any comments on the behavior of require.
    – snehal
    Commented May 12, 2019 at 9:36
  • 1
    When the condition (flag) is true in the first time, it will not necessarily be false in the second time. If the function reverts at msg.sender.call.value(amount)(""), then the operation flag = false will be reverted along with the rest of the operations in the function, thus leaving flag == true. Commented May 12, 2019 at 9:38
  • Also, correct me if I'm wrong - you are trying to prevent reentrant attacks using an additional (i.e., unrelated) state variable. First, please note that you are essentially blocking all but one credit holder from withdrawing. Are you sure that this feature complies with the rest of your goals? Second, in order to prevent reentrant attack, I believe that you can simply perform the subtraction before the funds-transfer (i.e., do credit[msg.sender]-=amount first, and msg.sender.call.value(amount)("") last). Commented May 12, 2019 at 9:43

1 Answer 1

0
pragma solidity >=0.4.0 <0.7.0;

contract SimpleDAO {
    mapping (address => uint256) public credit;
    bool flag = true;

    constructor() payable public {
        donate();
    }

    function donate() payable public{
        credit[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public{
        require(flag, "Only one time call is allowed.");
        flag = false;
        if (credit[msg.sender]>= amount) {
            credit[msg.sender]-=amount;
        }
    }

    function queryCredit(address to) public view returns (uint256) {
        return credit[to];
    }
}

Tried the above code in remix and it worked as expected i.e withdraw function worked for the first time and gave an error for the second time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.