2

Is it possible to create a mutex that prevents reentrancy, without using the SSTORE operation?

I would like to create a flag to prevent reentrancy of my contract which will call out to other contracts. The SSTORE operation, of writing to storage is expensive. Is there a way to maintain this mutex in memory, or do it with less expensive operations?

I'm writing with Solidity, but curious about any version. Seems like this problem will exist for use cases beyond reentrancy protection. Limiting fees by keeping them to just Gsreset fees after initialization is the cheapest I can think of right now, but maybe there is something I'm missing.

2 Answers 2

2

Is there a way to maintain this mutex in memory?

Memory and stack in EVM are isolated for nested message calls which means that there is no way to access a memory mutex set in the outer call/transaction.

do it with less expensive operations?

The only way I can think of is to use the storage as, unlike memory and stack, the storage is shared for the same contract across different message calls. Note that when resetting the mutex flag, the storage is reset which means some gas will be refunded. In the yellow paper Gsclear = 15000 is defined:

Refund given (added into refund counter) when the storage value is set to zero from non-zero.

So in the best case you would only need to pay Gsset - Gsclear = 5000 gas. I said "in the best case" because the refund is capped up to a maximum of half (rounded down) of the total amount used, as specified in the section 6.2. The cap will be at least 10000 since you always need to set the mutex first, which costs 20000. So in the worst case you'd pay 10000 gas.

2

IMHO it is inevitable to use storage, therefore the SSTORE opcode :( Let's take this simple contract as an example. It uses mutexes to prevent reentrancy in the withdrawBalance() function.

pragma solidity ^0.4.16;

contract EtherBank{
    mapping(address => uint) public userBalances;
    mapping(address => bool) public withdrawMutex;

    function getBalance(address user) constant returns(uint) {  
        return userBalances[user];
    }

    function addToBalance() {  
        userBalances[msg.sender] += msg.value;
    }

    function withdrawBalance() {  
        if ( withdrawMutex[msg.sender] == true) { throw; }
        withdrawMutex[msg.sender] = true;
        uint amountToWithdraw = userBalances[msg.sender];
        if (amountToWithdraw > 0) {
            if (!(msg.sender.send(amountToWithdraw))) { throw; }
         }
        userBalances[msg.sender] = 0;
        withdrawMutex[msg.sender] = false;
    }
}

This is a simple banking contract taken from this blog, which is highly recommended to read if you're interested in reentrancy bugs in smart contracts. As this example shows withdrawMutex is stored in the permanent storage of the contract.

You simply can not have your mutex stored in memory, because it would be flushed out after each invocation of the contract. The mutex should hold its state possibly for many invocations of the contract, implying it should be stored in the permanent storage. Bad news for gas usage :(

However you could prevent reentrancy bugs using different strategies, like the one mentioned in the blog post. This simple tactic is much more gas-cost friendly:

function withdrawBalance() {  
        uint amountToWithdraw = userBalances[msg.sender];
        userBalances[msg.sender] = 0;
        if (amountToWithdraw > 0) {
        if (!(msg.sender.send(amountToWithdraw))) { throw; }
    }

So just get your ordering correct! ;)

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.