Enum Conversion Out of Range
Overview
- Severity: Medium
- Confidence: High
- Affected Versions: < 0.4.5
What is the Enum Conversion Out of Range vulnerability?
In prior versions of the Solidity language, enum types could be cast from values that did not necessarily fit within the range of the enum. Even in newer versions of the language, it is always a good practice to check the potential range of the enum if casting from a variable.
Further reading: Solidity Documentation: Enums
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity 0.4.0;
contract LoanManager {
enum LoanState { Requested, Approved, Funded, Closed }
struct Loan {
uint256 id;
uint256 amount;
LoanState state;
}
mapping(uint256 => Loan) public loans;
uint256 public nextLoanId;
// Function to create a new loan request
function requestLoan(uint256 amount) public {
loans[nextLoanId] = Loan(nextLoanId, amount, LoanState.Requested);
nextLoanId++;
}
// Administrative function to update loan state based on external input
// This function does not validate the new state, risking an out-of-range cast
function updateLoanState(uint256 loanId, uint256 newState) public {
// Risky cast from uint256 to LoanState without range validation
loans[loanId].state = LoanState(newState); // Potential for errors with out-of-range values
}
}
In the example above, contract LoanManager
uses an enum LoanState
to track the lifecycle of loans (for example, as part of a DeFi application). The function updateLoanState()
casts the updated state from a raw uint256
type, without first checking if it could fit within the range, potentially leading to errors or undesired behavior.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.0;
contract LoanManagerUpdated {
enum LoanState { Requested, Approved, Funded, Closed }
struct Loan {
uint256 id;
uint256 amount;
LoanState state;
}
mapping(uint256 => Loan) public loans;
uint256 public nextLoanId;
// Function to create a new loan request
function requestLoan(uint256 amount) public {
loans[nextLoanId] = Loan(nextLoanId, amount, LoanState.Requested);
nextLoanId++;
}
// Administrative function to update loan state based on external input
function updateLoanState(uint256 loanId, uint256 newState) public {
require(newState <= uint256(LoanState.Closed), "Invalid state"); // Validate the new state
loans[loanId].state = LoanState(newState);
}
}
In the revised example above, a newer version of the compiler is specified, and contract LoanManagerUpdated
also explicitly checks the value of newState
inside of updateLoanState()
to make sure it fits within the enum range.