Maximum and Minimum Numbers
Introduction
In Solidity, integers and unsigned integers have a maximum and minimum value that can be stored. If the value exceeds this limit, it results in an overflow or underflow, which can lead to unexpected behavior in the smart contract. Solidity 0.8 and later versions have built-in overflow and underflow protection, which means that OpenZeppelin's SafeMath library is not necessary. However, for versions below 0.8, it is still essential to use SafeMath to prevent these issues.
In this article, we will explore the maximum and minimum numbers for integer and unsigned integer types in Solidity 0.8.16 and demonstrate overflow and underflow for each type.
Maximum and Minimum Numbers
In Solidity, the maximum and minimum values for integer and unsigned integer types are defined as follows:
Integer Type
- int8:
- min: -2^7-= -128
- max: 2^7-1 = 127
- int16:
- min: -2^15 = -32,768
- max: 2^15-1 = 32,767
- int32:
- min: -2^31 = -2,147,483,648
- max: 2^31-1 = 2,147,483,647
- int64:
- min: -2^63 = -9,223,372,036,854,775,808
- max: 2^63-1 = 9,223,372,036,854,775,807
- int128
- min: -2^127 = -170,141,183,460,469,231,731,687,303,715,884,105,728
- max: 2^127-1 = 170,141,183,460,469,231,731,687,303,715,884,105,727
- int256:
- min: -2^255 = -57,896,044,618,658,097,711,785,492,504,343,953,926,634,992,332,820,282,019,728,792,003,956,564,819,968
- max: 2^255-1 = 57,896,044,618,658,097,711,785,492,504,343,953,926,634,992,332,820,282,019,728,792,003,956,564,819,967
Unsigned Integer Type
- uint8:
- min: 0
- max: 2^8-1 = 255
- uint16:
- min: 0,
- max: 2^16-1 = 65,535
- uint32:
- min: 0
- max: 2^32-1 = 4,294,967,295
- uint64:
- min: 0
- max: 2^64-1 = 18,446,744,073,709,551,615
- uint128:
- min: 0
- max: 2^128-1 = 340,282,366,920,938,463,463,374,607,431,768,211,455
- uint256:
- min: 0
- max: 2^256-1 = 115,792,089,237,316,195,423,570,985,008,687,907,853,269,984,665,640,564,039,457,584,007,913,129,639,935
.min() and .max()
In Solidity 0.8.0 and later versions, you can use the .min()
and .max()
functions to get the minimum and maximum values for integer and unsigned integer types. You may have seen these methods used in the previous lesson: Working with Ethers.BigNumber.
Here's an example Solidity contract that demonstrates how to use .min()
and .max()
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract NumberLimits {
// integers
int8 public i8;
int16 public i16;
int32 public i32;
int64 public i64;
int128 public i128;
int256 public i256;
// unsigned integers
uint8 public u8;
uint16 public u16;
uint32 public u32;
uint64 public u64;
uint128 public u128;
uint256 public u256;
function setToMax() public {
i8 = type(int8).max;
i16 = type(int16).max;
i32 = type(int32).max;
i64 = type(int64).max;
i128 = type(int128).max;
i256 = type(int256).max;
u8 = type(uint8).max;
u16 = type(uint16).max;
u32 = type(uint32).max;
u64 = type(uint64).max;
u128 = type(uint128).max;
u256 = type(uint256).max;
}
function setToMin() public {
i8 = type(int8).min;
i16 = type(int16).min;
i32 = type(int32).min;
i64 = type(int64).min;
i128 = type(int128).min;
i256 = type(int256).min;
u8 = type(uint8).min;
u16 = type(uint16).min;
u32 = type(uint32).min;
u64 = type(uint64).min;
u128 = type(uint128).min;
u256 = type(uint256).min;
}
function getValues() public view
returns (uint8, uint16, uint32, uint64, uint128, uint256, int8, int16, int32, int64, int128, int256)
{
return (u8, u16, u32, u64, u128, u256, i8, i16, i32, i64, i128, i256);
}
}
The code above is a smart contract written in Solidity version 0.8.16 called NumberLimits
. It contains 12 variables, 6 integers and 6 unsigned integers, which represent the maximum and minimum values for each type in Solidity.
The contract also has three functions: setToMax()
, setToMin()
, and getValues()
.
The setToMax()
function sets all integer and unsigned integer values to their respective maximum values. Conversely, the setToMin()
function sets all integer and unsigned integer values to their respective minimum values.
The getValues()
function returns all the integer and unsigned integer values in the contract. It returns the values as a tuple of 12 elements, where the first 6 elements represent the unsigned integers, and the last 6 elements represent the integers.
This contract is useful for testing smart contracts that involve integer and unsigned integer values. The NumberLimits
contract provides a way to test edge cases involving the maximum and minimum values for these types, which can be crucial for ensuring the security and correctness of smart contracts.
Using .min()
and .max()
is a convenient way to get the minimum and maximum values of different integer and unsigned integer types in Solidity 0.8.0 and later versions.
Testing the NumberLimits Contract
The NumberLimits contract defines a set of variables to test the limits of various integer and unsigned integer data types in Solidity. It also includes functions to set these variables to their minimum and maximum values, and functions to test for overflow and underflow.
To test this contract, we'll write a set of tests using Hardhat and Ethers.js. Our tests will verify that the contract sets the variables to their expected values, and that it throws an error when we try to trigger an overflow or underflow.
Step 1: Set Up Directory
Let's create a new directory called my-project
with the basic Hardhat project structure.
mkdir min-max-solidity
cd min-max-solidity
Step 2: Create a Hardhat Project
Now, we'll create a new Hardhat project by running the following command:
npx hardhat init
npm i ethers
Then choose the "Create New JavaScript Project" option.
Step 3: Add Ethers.js and remove default files
Let's add Ether.js@5.4 with the following command. As of March 2023, the latest version of ethers has compatibility issues with Hardhat testing.
npm i ethers@5.4
Next lets remove the default files with the following commands:
rm contracts/Lock.sol test/Lock.js scripts/deploy.js
Step 5: Configure hardhat.config.js
Replace the following code to hardhat.config.js
:
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.16",
};
Step 6: Add NumberLimits.sol to Contracts Directory
Add the above NumberLimits.sol
to the /contracts
directory. From the root directory in the terminal add the following command.
touch contracts/NumberLimits.sol
Then copy and paste the NumberLimits.sol
code in to the file.
Step 7: Write the Tests
We'll write our tests in a new file called numberLimits.test.js
in the test
directory.
touch test/NumberLimits.js
Now copy and paste the code below into numberLimits.test.js
// test/MaxMinNumbers.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MaxMinNumbers", function () {
let maxMinNumbers;
beforeEach(async function () {
const MaxMinNumbers = await ethers.getContractFactory("MaxMinNumbers");
maxMinNumbers = await MaxMinNumbers.deploy();
await maxMinNumbers.deployed();
});
it("returns the correct MIN values for uint256 and int256", async function () {
const [
minUint256,
maxUint256,
minInt256,
maxInt256
] = await maxMinNumbers.getValues();
expect(minUint256).to.deep.equal(ethers.BigNumber.from("0"));
expect(minInt256).to.deep.equal(ethers.BigNumber.from("-57896044618658097711785492504343953926634992332820282019728792003956564819968"));
});
it("returns the correct MAX values for uint256 and int256", async function () {
const [
minUint256,
maxUint256,
minInt256,
maxInt256
] = await maxMinNumbers.getValues();
expect(maxUint256).to.deep.equal(ethers.BigNumber.from("115792089237316195423570985008687907853269984665640564039457584007913129639935"));
expect(maxInt256).to.deep.equal(ethers.BigNumber.from("57896044618658097711785492504343953926634992332820282019728792003956564819967"));
});
});
Conclusion
In conclusion, Solidity provides different integer types to accommodate different sizes and ranges of integers. The int
type is used for signed integers, while the uint
type is used for unsigned integers. The int
type can store negative and positive numbers, while the uint
type can only store positive numbers.
Solidity version 0.8.16 supports 8, 16, 32, 64, 128, and 256 bit signed and unsigned integers. The type(int256).min
and type(int256).max
constants represent the minimum and maximum values for a signed 256-bit integer, while the type(uint256).min
and type(uint256).max
constants represent the minimum and maximum values for an unsigned 256-bit integer.
It is essential to understand the range of values that can be stored in Solidity variables to prevent overflow or underflow issues in your smart contracts. Additionally, it is important to use the ethers.BigNumber
object when dealing with large numbers to ensure that they are represented accurately and prevent any rounding errors.
In summary, Solidity's integer types provide developers with the flexibility to store and manipulate integers of different sizes and ranges. Understanding the maximum and minimum values of these types is crucial to writing secure and efficient smart contracts. By utilizing the ethers.BigNumber
object, we can accurately represent and manipulate large numbers in Solidity.