An enumeration, or Enum, in Vyper, is an ordered set of predefined values. Check out these examples:
enum Stages:
NEGOTIATION
AGREEMENT
DELIVERY
stage: Stages = Stages.NEGOTIATION
@external
def move_to_agreement():
self.stage = Stages.AGREEMENT
enum Roles:
USER
ADMIN
SUPER_ADMIN
role: Roles
@external
def assign_admin():
self.role = Roles.ADMIN
@external
def upgrade_to_superadmin():
self.role |= Roles.SUPER_ADMIN
enum Roles:
USER
ADMIN
role: Roles
@external
def assign_role(r: Roles):
self.role = r
@external
def perform_admin_task():
assert self.role == Roles.ADMIN, "Only admins can perform this task"
Enums are incredibly useful in defining a type that can have one of a few predefined values, often useful for specifying a state or a mode that a contract can be in.
Overview of Enums
Vyper enums are full-fledged custom data types, defined using the enum
keyword. that can represent a set of constant values. They can contain anywhere from one to a maximum of 256 members, providing versatility for developers. Enums serve the purpose of making Vyper contracts more readable and safe, enabling developers to work with variables that only accept valid inputs.
The values of Enum members are represented as uint256
– they form a series of powers of two (2^n) where n is the index of the member in the range 0 <= n <= 255. This characteristic makes them excel for use cases where binary or bitwise operations are to be performed.
Use Cases
- Role-based Access Control: With Enums, you can define a set of roles in your contract, such as USER, ADMIN, and SUPER_ADMIN. Then, with role-based checks, you ensure only users with certain roles can invoke specified functions in the contract, thereby enforcing a robust access control mechanism.
- Managing Contract States: Enums represent an excellent choice to manage various states of your contract. For instance, a crowdfunding contract might have states like “Fundraising”, “Expired”, and “Successful”. These states will govern the logic of your smart contract’s functions.
- Event Logging: Enums can help you log different types of events occurring in your contract. By logging different enum values, you can make your events more readable and easier to interpret, rather than logging arbitrary numbers or strings.
- Decision Control Flow: You can use Enum types to control separate branches of your contract’s logic in a more readable way. It’s often a clearer choice compared to using multiple boolean flags, for instance.
Example 1: Enums for Stage Management
Enums are commonly used for representing stages or steps in a process.
# Define the stages of a delivery process using an Enum
enum Stages:
NEGOTIATION
AGREEMENT
DELIVERY
# Set the initial stage to "NEGOTIATION"
stage: Stages = Stages.NEGOTIATION
@external
def move_to_agreement():
# Change the stage from "NEGOTIATION" to "AGREEMENT"
self.stage = Stages.AGREEMENT
Here, we are defining stages of a delivery process. Then we utilize our enum to easily track the process from one defined scenario to the next using the move_to_agreement
method.
Example 2: Performing Bitwise Operations
Enums in Vyper support bitwise operations, providing a powerful tool for managing roles or permissions within a contract. You can use bitwise operators such as AND &
, OR |
, XOR ^
and NOT ~
to perform binary operations on enum members.
# Define the Roles using an Enum
enum Roles:
USER
ADMIN
SUPER_ADMIN
# Declare a variable of type Roles
role: Roles
@external
def assign_admin():
# Assign the role "ADMIN"
self.role = Roles.ADMIN
@external
def upgrade_to_superadmin():
# Add the "SUPER_ADMIN" role without removing the "ADMIN" role
self.role |= Roles.SUPER_ADMIN
Notice how we used the bitwise OR |
operator to upgrade an ADMIN to also be a SUPER_ADMIN. Now, self.role
includes both the ADMIN and SUPER_ADMIN role.
Example 3: Leveraging Enum for Access Control
Enums can be elegantly used for access control in the context of smart contracts – controlling who gets to invoke which function of your contract based on their set role.
# Define the Enums for the Roles
enum Roles:
USER
ADMIN
# Declare the variable role of type Roles
role: Roles
@external
def assign_role(r: Roles):
# Assign a role to a user
self.role = r
@external
def perform_admin_task():
# Check if the user is an ADMIN before executing the task
assert self.role == Roles.ADMIN, "Only admins can perform this task"
The above contract only allows admins to perform the exclusive task by wrapping the perform_admin_task
function logic within an assert statement. If the role is not ADMIN
, the task won’t be performed, and the transaction will revert.
Best Practices & Issues to Watch for
A couple of pointers to note when using Enums:
- Understand Enum Defaults: In Vyper, Enum values start at 0 and increase by 1 for each subsequent member. This default numbering can lead to problems if developers are not aware of it. Always be aware of the default values to ensure correct logic in your smart contract.
- Use Enums for Readability: Enums in Vyper are great for improving the readability of a contract. They can be used to replace arbitrary numerical or boolean values with semantic names, making it easier for developers to understand the intent of the code.
- Initialize for Safety: In Vyper, uninitialized Enum values have a default value of the first member. While this can be convenient, it can also lead to unexpected behavior if a developer assumes an uninitialized Enum has no value or a null value. Therefore, it’s a good practice to always initialize Enums explicitly for clarity and to avoid accidental reliance on the default value.
- Plan for Extra Members: If there is a chance that more members may need to be added to an Enum in the future, plan accordingly from the start. Adding new members to an Enum in the middle may require changes throughout the contract to accommodate the new members, which could introduce bugs. A better practice can be leaving some placeholder members or leaving room in your code for additions.
In summary, Enums in Vyper provide a handy tool for representing distinct states, modes, or roles within your smart contracts. As you continue your Vyper journey, it’s a good practice to leverage Enums to write contracts that are clean, safe, and maintainable. Happy coding!