6 min read

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.