Understanding the refund logic in ReentrancyGuard

Topic

I was looking at the ReentrancyGuard implementation, specifically at this part:

// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;

I've read the comments a couple of times but I don't quite understand the purpose. Why would you not want to save as much gas as possible for the user? In other words, why would the user care if the refund comes from ReentrancyGuard or from some other source?

Environment

If we used 0/1 values, setting the _status variable from 0 to 1 would cost 20k gas, and resetting it to 0 at the end of the transaction will cost 100 gas with a 19.9k refund. If the full refund is realized this would amount to only 200 gas. However, refunds are capped at 1/5th of total transaction cost (EIP-3529), so a transaction that uses this version of ReentrancyGuard has to cost around 100k gas in order to actually realize the optimal cost, and this is assuming there are no other refunds in that transaction. Note that if the transaction costs less than 100k gas, the full refund is not realized and the cost of using the reentrancy guard may be closer to 20k gas than to 200.

For a general purpose utility like ReentrancyGuard, we can't assume that a transaction costs more than 100k gas. If instead we use 1/2 values, setting the flag costs 2.9k gas, resetting it costs 100 gas with 2.8k refund. The final amount if the full refund is realized is still 200 gas, but the refund is a lot smaller now so the total transaction cost doesn't need to be as high to realize it.

(This is obviously an EVM inefficiency, but it will be fixed with transient stoage!)

1 Like

Thanks for explaining. All clear now.

It might be worth updating the comments in the Solidity implementations with your indications here (which are illuminatory).

  1. This forum is about OpenZeppelin code, not about the Solidity language
  2. The figures quoted in the answer above, occasionally change as a result of EVM version update, which the Solidity language is agnostic to

With regards to "paying less gas and getting back less refund", I'd like to point out an additional benefit, which seems to have been left out in the answer above.

Suppose the following two cases:

  1. Pay 109K gas, get back 9K gas
  2. Pay 104K gas, get back 4K gas

In both cases, you end up paying the ETH worth of 100K gas.

However, in case 1 you need to provide the ETH worth of 109K gas, while in case 2 you need to provide the ETH worth of 104K gas.

So in order to execute case 1, you need to hold more ETH in your walle tthan what you need in order to execute case 2.

Moreover, since miners opt for "highest gas price / lowest gas limit" first, the expected execution time for case 1 is longer than that of case 2.

The question is about code in OpenZeppelin Contracts!

Yes, for example the refund cap of 1/5th was introduced in the London hard fork in 2021.

This is a good point too.

2 Likes

Yes, I read that as 'Solidity documentation'. My bad.