Unsafe Downcast
Overview
- Severity: Low
- Confidence: Medium
- Affected Versions: All
What is the Unsafe Downcast vulnerability?
In Solidity, as in many other typed programming languages, a developer may (attempt to) cast between types in their code. Solidity offers many variants of certain builtin types at different bit widths (for example, uint8
vs. uint256
), and it is possible to cast between these. However, a narrowing type cast (from a higher to a lower bit width) may inadvertently truncate bits and cause the value after the cast to not be equivalent to that before the cast. This can lead to inadvertent logical errors in smart contract execution.
Further reading: Solidity Documentation: Explicit Type Conversions
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract TokenSale {
uint public basePrice = 1 ether; // Base price of 1 token in wei
uint constant public PRICE_MULTIPLIER = 25;
address private owner;
constructor() {
owner = msg.sender;
}
// Event to emit the price at which tokens are sold
event TokenSold(address buyer, uint256 amount, uint256 price);
// Function to allow the owner to update the base price
function updatePrice(uint _newPrice) private {
require(msg.sender == owner, "Only the owner may update the base price.");
basePrice = _newPrice;
}
// Function to sell tokens to a buyer
function sellTokens(address buyer, uint256 tokenAmount) public {
// Incorrectly assuming that the price will fit in a uint16
uint16 priceToCharge = uint16(basePrice * PRICE_MULTIPLIER);
// Logic to proceed with the token sale at the incorrectly calculated price
emit TokenSold(buyer, tokenAmount, priceToCharge);
// Further logic for transferring tokens and accepting payment would go here
}
}
In the example above, contract TokenSale
(partially) implements a function sellTokens()
designed to sell tokens to a user at a calculated price based on the current value of basePrice
(set by the contract owner) and the fixed PRICE_MULTIPLIER
value. However, this value (possibly for efficiency reasons) is stored in a uint16
variable, which the developer perhaps inadvertently failed to realize would not contain the value of an ether
unit specified in the original basePrice
variable (which is 1e18
as an integer). This will cause the price to be calculated incorrectly, subverting the intended logic of the contract.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract TokenSaleUpdated {
uint public basePrice = 1 ether; // Base price of 1 token in wei
uint constant public PRICE_MULTIPLIER = 25;
address private owner;
constructor() {
owner = msg.sender;
}
// Event to emit the price at which tokens are sold
event TokenSold(address buyer, uint256 amount, uint256 price);
// Function to allow the owner to update the base price
function updatePrice(uint _newPrice) private {
require(msg.sender == owner, "Only the owner may update the base price.");
basePrice = _newPrice;
}
// Function to sell tokens to a buyer
function sellTokens(address buyer, uint256 tokenAmount) public {
uint256 priceToCharge = basePrice * PRICE_MULTIPLIER;
emit TokenSold(buyer, tokenAmount, priceToCharge);
// Further logic for transferring tokens and accepting payment would go here
}
}
In the revised example above, contract TokenSaleUpdated
uses a uint256
variable to capture the price to charge, rather than downcasting the product of wider unsigned integers.