Advanced Solidity | Gas Golfing | Yul | EVM | Audit | Zero Knowledge Proofs | Puzzles
Advanced Solidity | Gas Golfing | Yul | EVM | Audit | Zero Knowledge Proofs | Puzzles

Subscribe to aṇuha

Subscribe to aṇuha
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
Below is the ReentrancyGuard.sol from OpenZeppelin
Below is the ReentrancyGuard.sol from OpenZeppelin
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* <https://blog.openzeppelin.com/reentrancy-after-istanbul/>[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// 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;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// <https://eips.ethereum.org/EIPS/eip-2200>)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
I am interested in the below section of the code.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
Why is OpenZeppelin using uint256 as against uint8 when the only required values 1 and 2 can fit in the range of uint8?
The simple answer is Gas Optimization.
When writing smart contracts in Solidity, gas optimization is crucial. One way to optimize gas usage is by choosing the appropriate data types. Solidity supported a wide range of unsigned integers ranging between uint8 to uint256 with a gap of 8 bits. In this write-up, we will explore which of these data types is more efficient to store and use.
As the name suggests, uint8 is an unsigned integer that can hold a maximum value of 2⁸-1, which is 255. Therefore, uint8 should use only 1 byte of storage space.
On the other hand, uint256 is an unsigned integer that can hold a maximum value of 2²⁵⁶-1, which is an incredibly large number. Therefore, uint256 requires 32 bytes of storage space.
Let’s take the below code sample
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Example {
uint8 smallNum = 21; // Occupies Slot 0 of Storage
uint256 bigNum = 12134567890; // Occupies Slot 1 of Storage
// 2311 gas (Cost only applies when called by a contract)
function readSmallNum() public view returns(uint8){
return smallNum;
}
// 2248 gas (Cost only applies when called by a contract)
function readBigNum() public view returns(uint256){
return bigNum;
}
// Input 0: 0x0000000000000000000000000000000000000000000000000000000000000015
// Input 1: 0x00000000000000000000000000000000000000000000000000000002d346cfd2
function getValueAtSlot(uint256 slotNum) public view returns (bytes32) {
bytes32 value;
assembly {
value := sload(slotNum)
}
return value;
}
}
In the contract storage, the variables are organized in slots of 32 bytes each irrespective of whether it was uint8 or uint256.

Values at a given slot can be accessed by using the assembly code below:
assembly {
value := sload(slotNum)
}
The above contract was deployed with optimization turned on for 1000 runs.
readBigNum requires 2248 units of gas
readSmallNum requires 2311 units of gas
One of the primary reasons why reading the
uint8value is costlier thanuint256is because when theuint8value is read by EVM it reads the whole slot first and performs extra masking operations, while the uint256 value is returned directly. And any extra opcode operations mean extra gas consumption.
In conclusion, choosing the appropriate data type is essential when designing smart contracts in Solidity. Please consider the learnings from the above section to choose the appropriate data type.
Now that we know the reason for openzeppelin’’s use of uint256 datatypes check the **anuha** GitHub repo and gas-golf-1 folder sample that compare and illustrate the ReentrancyGuard.sol‘s uint8 vs uint256 versions.
Links:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* <https://blog.openzeppelin.com/reentrancy-after-istanbul/>[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// 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;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// <https://eips.ethereum.org/EIPS/eip-2200>)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
I am interested in the below section of the code.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
Why is OpenZeppelin using uint256 as against uint8 when the only required values 1 and 2 can fit in the range of uint8?
The simple answer is Gas Optimization.
When writing smart contracts in Solidity, gas optimization is crucial. One way to optimize gas usage is by choosing the appropriate data types. Solidity supported a wide range of unsigned integers ranging between uint8 to uint256 with a gap of 8 bits. In this write-up, we will explore which of these data types is more efficient to store and use.
As the name suggests, uint8 is an unsigned integer that can hold a maximum value of 2⁸-1, which is 255. Therefore, uint8 should use only 1 byte of storage space.
On the other hand, uint256 is an unsigned integer that can hold a maximum value of 2²⁵⁶-1, which is an incredibly large number. Therefore, uint256 requires 32 bytes of storage space.
Let’s take the below code sample
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Example {
uint8 smallNum = 21; // Occupies Slot 0 of Storage
uint256 bigNum = 12134567890; // Occupies Slot 1 of Storage
// 2311 gas (Cost only applies when called by a contract)
function readSmallNum() public view returns(uint8){
return smallNum;
}
// 2248 gas (Cost only applies when called by a contract)
function readBigNum() public view returns(uint256){
return bigNum;
}
// Input 0: 0x0000000000000000000000000000000000000000000000000000000000000015
// Input 1: 0x00000000000000000000000000000000000000000000000000000002d346cfd2
function getValueAtSlot(uint256 slotNum) public view returns (bytes32) {
bytes32 value;
assembly {
value := sload(slotNum)
}
return value;
}
}
In the contract storage, the variables are organized in slots of 32 bytes each irrespective of whether it was uint8 or uint256.

Values at a given slot can be accessed by using the assembly code below:
assembly {
value := sload(slotNum)
}
The above contract was deployed with optimization turned on for 1000 runs.
readBigNum requires 2248 units of gas
readSmallNum requires 2311 units of gas
One of the primary reasons why reading the
uint8value is costlier thanuint256is because when theuint8value is read by EVM it reads the whole slot first and performs extra masking operations, while the uint256 value is returned directly. And any extra opcode operations mean extra gas consumption.
In conclusion, choosing the appropriate data type is essential when designing smart contracts in Solidity. Please consider the learnings from the above section to choose the appropriate data type.
Now that we know the reason for openzeppelin’’s use of uint256 datatypes check the **anuha** GitHub repo and gas-golf-1 folder sample that compare and illustrate the ReentrancyGuard.sol‘s uint8 vs uint256 versions.
Links:
No activity yet