Vyper Bitwise Operators (Usage and Examples)

whiteboard crypto logo
Published by:
Whiteboard Crypto
on

Bitwise operators in Vyper are specialized tools that directly interact with
data on the binary level, allowing for specific manipulation of information at
the most fundamental unit: bits. Here’s an example:

@public
def toggle_features(current_settings: uint256, feature_mask: uint256) -> uint256:
    return current_settings ^ feature_mask


@public
def has_permission(user_permissions: uint256, permission_mask: uint256) -> bool:
    return (user_permissions & permission_mask) == permission_mask
 

@public
def has_permission(user_permissions: uint256, permission_mask: uint256) -> bool:
    return (user_permissions & permission_mask) == permission_mask

At their core, bitwise operators work on a binary representation of data, viewing data as a series of switches in ‘on’ or ‘off’ states. These operators can perform precise calculations, toggle specific switches on or off, or combine data in minute but controlled ways.

This unique capacity makes them indispensable for intricate mathematical computations, detailed security features, and efficient data management. Before diving into practical examples, it’s crucial to comprehend these operators’ simplicity and clarity despite their deep-rooted computational prowess.

Overview of Bitwise Operators

Bitwise operators allow you to manipulate individual bits within an integer. It’s like having a screwdriver set for computer bytes, every bit is a screw tweaked to perfection.

The Core Bitwise Tools: AND, OR, XOR

In Vyper, you’ll often rely on three core bitwise operators:

  • & (Bitwise AND): It’s the precise operator that turns on a bit only if both inputs have that bit turned on. It’s often used for masking, when you want to filter out specific bits and see if certain flags are set.
  • | (Bitwise OR): Welcoming and inclusive, this operator turns on a bit if it is on in either of the input numbers. This is your go-to when you need to set flags or combine multiple bit-based settings.
  • ^ (Bitwise XOR): The discerning operator, XOR, sets a bit if it is on in one input, but not both. This is ideal for toggling features, inverting flags without affecting others.

Common Use Cases

  • Access Control: Imagine a security system where each bit of a number represents a different permission (like reading, writing, or editing). Using the ‘&‘ operator, you can quickly check if a user has the right combination of permissions. It’s like having a special key that only unlocks certain doors in a building based on the cuts on the key.
  • Feature Flags: Think of a video game where you can turn on various features like sound, high graphics, or multiplayer mode. The ‘|‘ operator allows you to easily switch these features on, combining several on/off options into one number. It’s like having a control panel where flipping one switch can activate multiple lights.
  • Toggle Settings: For a mobile app with settings that users frequently turn on and off, such as dark mode or notifications, the ‘^‘ operator is perfect. It effortlessly flips settings back and forth, making it ideal for quick, responsive user interactions. It’s akin to a light switch that you flick to change from day mode to night mode in your room.

The Purpose Of Bitwise Operators

  • Efficiency and Compactness: Bitwise operations are incredibly efficient. They take up less space and use less processing power than other methods of handling multiple true/false (Boolean) values. This is crucial in programming environments like smart contracts on blockchains, where saving space and computational resources directly translates to cost savings and improved performance.
  • Fine-grained Control: They provide a level of control over your data that higher-level operations can’t match. With bitwise operators, you can manipulate the smallest building blocks of your data, leading to more precise and optimized code.
  • Versatility: These operators are incredibly versatile and find use in various fields, including cryptography, image processing, and custom data compression techniques. They are the unsung heroes behind many features we take for granted in modern software and applications.

Example 1: Toggling Features Using XOR

Say we have a set of feature flags for an application, with each flag represented by a single bit in a uint256. Here’s an example with uint8 for simplicity:

  • What are Feature Flags?
    • Think of feature flags like switches for different options in an app, like turning on/off sound, high graphics, or a night mode.
  • Binary Representation:
    • Each feature is represented by a bit (a binary digit) in a number. A bit can be either 0 (off) or 1 (on).
  • Current Settings:
    • 0b00101101 (in binary, which is 45 in decimal) represents the current state of features. Each position of 1 or 0 is a different feature being on or off.
  • Feature Mask:
    • 0b00000101 (5 in decimal) is our tool to toggle certain features. Wherever there’s a 1 in this mask, it means we want to toggle (change the state of) that particular feature.
  • Using XOR to Toggle:
    • XOR (^) is a bitwise operation that flips the bits where both numbers differ. If the bit is 1 in the mask, it toggles the corresponding bit in the settings.
@public
def toggle_features(current_settings: uint256, feature_mask: uint256) -> uint256:
    # This function toggles the feature flags in 
    # 'current_settings' based on 'feature_mask'.
    # - 'current_settings': The original settings (each 
    # bit represents a different feature's state).
    # - 'feature_mask': A binary number where each 
    # '1' indicates the feature at that position should be toggled.
    # - Returns: New settings after toggling the specified features.
    return current_settings ^ feature_mask  # Perform XOR between settings 
    and mask to toggle features.

What Happens in the Function?

  1. Inputs:
    • current_settings: This is the existing configuration of the features (like 45 in our example).
    • feature_mask: This specifies which features to toggle (like 5 in our example).
  2. Operation:
    • The function performs an XOR operation between current_settings and feature_mask.
    • If a bit in feature_mask is 1, the corresponding bit in current_settings is flipped (1 becomes 0, or 0 becomes 1).
    • If a bit in feature_mask is 0, the corresponding bit in current_settings remains unchanged.
  3. Result:
    • The function returns the new configuration where specified features have been toggled according to the mask.

Example Walkthrough:

  • Original Settings: 0b00101101 (45 in decimal)
  • Feature Mask: 0b00000101 (5 in decimal)
  • After XOR Operation: 0b00101000 (40 in decimal)

In the result (0b00101000), you can see that the bits at positions 1 and 3 (from the right) are flipped compared to the original settings.

This approach is efficient for toggling multiple settings simultaneously without needing multiple operations. It’s especially useful in applications like smart contracts, where optimizing for fewer computational steps can lead to significant cost savings.

Example 2: Checking Permissions Using AND

Let’s consider a user permissions example. Each bit corresponds to a particular permission — for example, bit 0 could be “can edit”, bit 1 “can delete”, etc. Here’s how we’d use the has_permission function:

  • Understanding Permissions as Bits:
    • Imagine a security badge that gives different levels of access like “can edit”, “can delete”, etc. Each type of access is represented by a switch (bit) on the badge: if the switch is on (1), the user has that permission; if it’s off (0), they don’t.
  • Binary Representation:
    • Each permission is a bit in a binary number. For instance, “can edit” might be the first bit, “can delete” the second bit, and so on.
  • User Permissions:
    • 0b11001100 (204 in decimal) is like a user’s badge showing which access permissions they have. Here, certain switches are on, and others are off.
  • Permission Mask:
    • 0b00000110 (6 in decimal) is a tool to check specific permissions. Here, it’s set up to check the “can edit” and “can delete” permissions.
  • Using AND to Check Permissions:
    • AND (&) is a bitwise operation that compares two numbers bit by bit. If the same bit is 1 (on) in both numbers, the result is 1; otherwise, it’s 0.
@public
def has_permission(user_permissions: uint256, permission_mask: uint256) -> bool:
    # This function checks if the user has the permissions
    # represented by 'permission_mask'.
    # - 'user_permissions': The current permissions the user
    # has (each bit represents a different permission).
    # - 'permission_mask': A binary number where each '1'  
    # indicates the specific permission we want to check.
    # - Returns: True if the user has all the permissions in the 
    # mask, False otherwise.

    return (user_permissions & permission_mask) == permission_mask  # 
    Compare user permissions with the mask using AND.

What Happens in the Function?

  1. Inputs:
    • user_permissions: The user’s current permissions as a binary number.
    • permission_mask: The specific permissions we’re checking for, also as a binary number.
  2. Operation:
    • The function performs an AND operation between user_permissions and permission_mask.
    • The result of this operation is then compared to permission_mask.
    • If the result matches permission_mask, it means the user has all those specific permissions.
  3. Result:
    • The function returns True if the user has the permissions, False otherwise.

Example Walkthrough:

  • User Permissions: 0b11001100 (204 in decimal)
  • Permission Mask: 0b00000110 (6 in decimal)
  • After AND Operation: 0b00000100 (4 in decimal)

The result, 0b00000100, indicates that the user has the “can delete” permission (the second bit) but not the “can edit” permission (the first bit). The function checks if the user has both permissions as specified in the mask and returns True or False accordingly.

This approach is particularly useful in scenarios like access control systems where you need to efficiently manage multiple permission flags. It allows for a compact representation of permissions and a quick way to check them.

Example 3: Combining Settings Using Bitwise OR

  • Understanding Bitwise OR:
    • Bitwise OR (|) is like a unifier in the binary world. It looks at two binary numbers (imagine them as two sets of switches) and turns on a bit in the result if the bit is on (1) in either one of the original numbers.
  • Practical Use – Combining Features or Flags:
    • Suppose you have different settings or features in an app, like enabling notifications, dark mode, or location tracking. Each feature is a switch (bit) in a binary number.
  • Combining Features with OR:
    • By using the | operator, you can combine multiple settings into one number. If a feature is activated (bit is 1) in any of the numbers you’re combining, it will be activated in the result.
@public
def combine_features(current_settings: uint256, new_features: uint256) -> uint256:
 
    # This function combines the current settings with new features.
    # - 'current_settings': The original settings, with each bit 
    # representing a different feature.
    # - 'new_features': Additional features to be added, represented 
    # in the same way.
    # - Returns: A new settings configuration that includes both   
    # current and new features.

    return current_settings | new_features  # Use OR to combine settings and 
    new features.

What Happens in the Function?

  1. Inputs:
    • current_settings: The existing configuration of features.
    • new_features: Additional features that need to be added to the existing configuration.
  2. Operation:
    • The function performs a Bitwise OR operation between current_settings and new_features.
    • If a bit is 1 in either current_settings or new_features, it will be 1 in the result.
  3. Result:
    • The function returns a new configuration that includes both the original and the new features.

Example Walkthrough:

  • Current Settings: 0b10101010 (170 in decimal)
  • New Features to Add: 0b11000011 (195 in decimal)
  • After OR Operation: 0b11101011 (235 in decimal)

In this example, the result 0b11101011 shows the combination of the original settings and new features. Bits that were 1 in either of the original numbers are 1 in the result, indicating that all these features are now activated.

This approach is extremely useful in scenarios where you need to update or combine configurations efficiently. For instance, in a user settings panel of an application, combining multiple user preferences into a single configuration can be done swiftly using the Bitwise OR operation.

Best Practices With Bitwise Operators

When using bitwise operators, it’s critical to keep in mind the following best practices:

  1. Ensure Correct Data Type: Always double-check that you are operating on the correct data type of the appropriate bit length when using bitwise operators. Applying bitwise operations on unintended data types or different bit lengths can yield unexpected and incorrect results.
  2. Transparent Flag Management: When working with flags, maintain a clear comment or documentation system that signifies what each bit represents. This approach prevents misunderstanding and confusion for both the current developer and any who might work on your code in the future.
  3. Use Bit Masks: Employ bit masks for more complicated bitwise operations. Bit masks act like reusable templates for specific bitwise operations and can significantly simplify your code and reduce chances for errors.

Common Issues to Watch Out For

Although bitwise operators offer substantial advantages, there are a few potential pitfalls you should be aware of:

  1. Operands with Different Bit Sizes: When performing bitwise operations on operands with different bit sizes, Vyper will not automatically resize the operands, often yielding unintended results. Be sure to ensure operand sizes conform to one another before performing bitwise operations.
  2. Unintended Bit Shifts: Be mindful when shifting bits left or right because shifts could inadvertently set a previously zero bit to one, or vice versa, especially when working with large integers.
  3. Overlooking Operator Precedence: Operator precedence rules also apply to bitwise operators. While ‘&’ has higher precedence than ‘^’ and ‘|’, care should be taken to use parentheses when multiple bitwise operators are present in the same expression to ensure correct evaluations.

Understanding when and why to use bitwise operators can empower you to craft intricate solutions in a landscape that prizes optimized data handling. These examples are a mere scratch on the surface of possibilities that bitwise operators unlock.

Vyper Bitwise Operators
whiteboard crypto logo

WhiteboardCrypto is the #1 online resource for crypto education that explains topics of the cryptocurrency world using analogies, stories, and examples so that anyone can easily understand them. Growing to over 870,000 Youtube subscribers, the content has been shared around the world, played in public conferences and universities, and even in Congress.