Integer Downcasting Vulnerability: Demonstration and Effective Fix

In Solidity versions 0.8 and above, overflow and underflow checking is enabled by default. However, this checking does not apply to conversions between integer types, including data-type conversions.

As a result, conversions can lead to overflow without triggering a revert.

Allow us to demonstrate this issue:

To assist you in getting started promptly, we have already prepared a repository showcasing this vulnerability. Let's begin by making a copy of the repository.

Step 1: Cloning the Tutorial Repo

1.1 Forking the Tutorial Repository:

  • Visit our Tutorial Repository and click the "Fork" button to duplicate the repository into your own account. Please wait until the forking process is complete before proceeding.

1.2 Cloning the repository locally:

  • Go to your forked repository on GitHub.

  • Click the "Code" button and copy the URL provided (either HTTPS or SSH).

  • Open your terminal or Git Bash.

  • Navigate to the directory where you want to place the cloned repository.

  • Use the following command to clone the repository:

git clone <paste the copied URL here>
  • Wait for the cloning process to finish. Once completed, you should see a new directory with the repository's name in the chosen directory.

  • To access the cloned repository, use the command cd <Name of the cloned Repo>, and then navigate to the DownCastingError folder by executing cd DownCastingError.

Step 2: Let's walk through the contracts.

2.1 unsafeDownCasting.sol** Contract**

carbon (17) (1).png
carbon (17) (1).png

Here's a breakdown of the code:

  1. The SPDX-License-Identifier specifies the license under which the contract is released. In this case, it's the MIT license.

  2. The pragma solidity ^0.8.15; statement defines the version of the Solidity compiler that should be used. This contract requires Solidity version 0.8.15 or higher.

  3. The contract is named unsafeDownCasting. It has a state variable named LuckyNumber of type uint, which will store the lucky number.

  4. The setLuckyNumber function is a public function that takes a uint256 parameter named amount. It is used to set the lucky number. Inside this function, the amount value is downcasted to a uint8 using the uint8(amount) syntax. This downcasting operation can lead to unexpected results if the amount value is greater than the maximum value of uint8, which is 255. If the amount exceeds this value, only the least significant 8 bits will be stored in the number variable, discarding any overflowed bits. This can result in a loss of data and incorrect behavior.

  5. The downcasted number value is then assigned to the LuckyNumber state variable.

  6. The getLuckyNumber function is a public view function that returns the current value of the LuckyNumber variable. It allows other contracts or external entities to retrieve the lucky number without modifying the contract's state.

2.2 safeDownCasting.sol** Contract**

carbon (18) (1).png
carbon (18) (1).png
  1. The contract imports the SafeCast library from the OpenZeppelin contracts. This library provides safe downcasting functionality.

  2. The using SafeCast for uint256; statement allows the contract to use the SafeCast library for type uint256.

  3. The setLuckyNumber function is a public function that takes a uint256 parameter named _amount. It is used to set the lucky number. Inside this function, the _amount value is safely downcasted to uint8 using the toUint8() function from the SafeCast library. If the _amount value is greater than the maximum value of uint8, the downcast will revert, ensuring that only valid downcasts are performed. The resulting uint8 value is then assigned to the LuckyNumber state variable.

Let’s deploy these contracts and see the vulnerability in action.

Step 3: Setting up the Infrastructure for Deploying the Smart Contract

3.1. Visit the BuildBear App. ( Well we can deploy this contract on the local Hardhat node, But we will be Using BuildBear, you can understand the Reason by the end of this Tutorial).

3.2. Create your Private Testnet. You have the option to either fork from the Mainnets or create a new testnet from scratch, using Mainnet as the base. Forking from the Ethereum Mainnet allows us to conveniently utilize existing NFTs and tokens.

Untitled (16).png
Untitled (16).png

3.3. Add your Private Testnet to your MetaMask wallet by using the “Add to Metamask” button:

Untitled (17).png
Untitled (17).png

Step 4: Deploying the Smart Contract

To begin, execute the following command to install the necessary packages:

4.1 Update the hardhat.config.js file:

  • Go to your Dashboard and click on "verify contract".

Untitled (18).png
Untitled (18).png
Untitled (19).png
Untitled (19).png
  • Copy the BuildBear and Etherscan objects, and update the hardhat.config.js file with the new values.

4.2 To deploy the safeDownCasting.sol and unsafeDownCasting.sol  smart contracts run the following command: npx hardhat deploy. This will execute the deployment scripts located in the deploy folder and save the deployment details, including the ABI and Contract address, in the deployments folder.

Untitled (81).png
Untitled (81).png

4.3 Testing the Vulnerability

As we are using BuildBear, we can conveniently interact with the contracts directly from the explorer, eliminating the need to write a script.

Click on "Open Faucet" on the dashboard and connect your wallet to mint Native tokens. With BuildBear, we have our own Faucet, so we don't have to bug tokens as we do when we use public Testnests.

Click on the link provided in the terminal of the unsafeDownCasting contract. You will be taken to the Contract page. Proceed to the "Write Contract" section.

Untitled (82).png
Untitled (82).png

Click on "Connect to Web3". Now the explorer is connected to your wallet. Enter any number larger than 255 and click on "Write" to sign the transaction on MetaMask.

Untitled (83).png
Untitled (83).png

Now move to the "Read Contract" section. You will notice that the lucky number is 44. Since the maximum value that can be represented by a uint8 is 255, the downcast operation causes an overflow. In Solidity, when an overflow occurs during an arithmetic operation, the value "wraps around" and starts from the minimum value of the data type. In this case, the least significant 8 bits of 300 (which is 44 in binary) will be stored in the number variable.

As a result, the LuckyNumber state variable will be assigned the value of 44.

Untitled (84).png
Untitled (84).png

4.4 Testing the Contract with SafeCast Library.

Now move to the explorer page of the safeDownCasting Contract from the link proved in the Terminal, Connect the wallet and enter 300 and click on write, as you can see the Transaction has failed. Click on “View Transaction”, As you can see the Transaction has failed with reason SafeCast: value doesn't fit in 8 bits, this is because the SafeCast function is checking whether the integer is less than uint8 before converting it to uint8.

Untitled (85).png
Untitled (85).png
Untitled (86).png
Untitled (86).png

🎉 We have successfully mitigated the DownCasting Vulnerability using the SafeCast library from the OpenZeppelin contracts.

If you support our efforts, please follow us on Twitter and LinkedIn. If you haven't already, we invite you to join our Telegram group by clicking here.

Github Repo : Buildbear Tutorials

About BuildBear:

BuildBear offers developers a convenient platform to create a customized Private Testnet network for testing their DApps. With the ability to fork EVM chains, developers can create a private network tailored to their needs.

Use BuildBear.io and create Your Private Testnet Now!