Uninitialized State Variable
Overview
- Severity: Medium
- Confidence: Medium
- Affected Versions: All
What is the Uninitialized State Variable vulnerability?
In Solidity, smart contracts may define various state variables at the level of the contract itself, analogous to class members in many object-oriented programming languages. State variables will have default values dictated by the EVM if the variables are not initialized; these may be desired values in some cases, but they may be unexpected in other cases, and failure to initialize them will not prevent a smart contract from compiling even if lack of initialization leads to a logical bug.
Further reading: Solidity Documentation: Scoping and Declarations
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleAuction {
address public highestBidder;
uint public highestBid;
bool public auctionEnded;
uint public endBlock;
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
constructor() {
highestBidder = address(0);
highestBid = 0;
auctionEnded = false;
}
function bid() public payable {
require(!auctionEnded, "Auction already ended.");
require(block.number <= endBlock, "Auction time expired.");
require(msg.value > highestBid, "There already is a higher bid.");
// refund the current high bidder, if one exists
if (highestBidder != address(0)) {
payable(highestBidder).transfer(highestBid);
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
function endAuction() public {
require(!auctionEnded, "Auction already ended.");
require(block.number >= endBlock, "Auction not yet ended.");
auctionEnded = true;
emit AuctionEnded(highestBidder, highestBid);
}
// other code to handle the item up for auction (e.g. an NFT), including transfer to the winner
}
The example above implements a simple auction (for example, for an NFT or some other token), which is intended to run for some amount of time taking bids in ether. However, the endBlock
state variable is never properly initialized, which will cause the auction to be unable to accept bids via the bid()
function. Note that in this case, the default values of zero-like for other state variables are appropriate, but explicit initialization makes this more clear.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleAuction {
address public highestBidder;
uint public highestBid;
bool public auctionEnded;
uint public endBlock;
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
constructor() {
highestBidder = address(0);
highestBid = 0;
auctionEnded = false;
endBlock = block.number + 100;
}
function bid() public payable {
require(!auctionEnded, "Auction already ended.");
require(block.number <= endBlock, "Auction time expired.");
require(msg.value > highestBid, "There already is a higher bid.");
// refund the current high bidder, if one exists
if (highestBidder != address(0)) {
payable(highestBidder).transfer(highestBid);
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
function endAuction() public {
require(!auctionEnded, "Auction already ended.");
require(block.number >= endBlock, "Auction not yet ended.");
auctionEnded = true;
emit AuctionEnded(highestBidder, highestBid);
}
// other code to handle the item up for auction (e.g. an NFT), including transfer to the winner
}
In the updated example, the constructor initializes the endBlock
variable to 100 blocks ahead of the block in which the auction contract was deployed. This will ensure that the auction is able to accept bids before closing.