Bit manipulation

Using bit manipulation for compact data storage and flags in Clarity smart contracts.

Bit manipulation functions in Clarity provide powerful tools for efficient data storage and flag management in smart contracts. These functions allow developers to pack multiple pieces of information into a single integer, saving storage space and potentially reducing gas costs.

Why these functions matter

Clarity's bit manipulation functions are designed with several important considerations in mind:

  1. Efficiency: They allow for compact data storage, potentially reducing contract storage costs.
  2. Versatility: These functions enable the implementation of bitfields, flags, and other low-level data structures.
  3. Performance: Bit operations are generally faster than higher-level data manipulations.
  4. Interoperability: They facilitate working with data from other systems that use bitwise representations.

Core Bit Manipulation Functions

1. bit-and

What: Performs a bitwise AND operation on two or more integers. Why: Useful for checking if specific bits are set or for clearing certain bits. When: Use when you need to isolate specific bits or apply a bitmask. How:

(bit-and i1 i2...)

Best Practices:

  • Use to check if a specific flag is set in a bitfield.
  • Combine with bit-or for more complex flag manipulations.

Example Use Case: Checking if a user has a specific permission in a compact permission system.

(define-constant PERMISSION_READ u1)  ;; 0001
(define-constant PERMISSION_WRITE u2) ;; 0010
(define-constant PERMISSION_EXEC u4)  ;; 0100

(define-public (has-permission? (user-permissions uint) (required-permission uint))
  (is-eq (bit-and user-permissions required-permission) required-permission))

;; Usage
(has-permission? u3 PERMISSION_READ)  ;; Returns true (3 & 1 == 1)
(has-permission? u3 PERMISSION_EXEC)  ;; Returns false (3 & 4 == 0)

2. bit-or

What: Performs a bitwise OR operation on two or more integers. Why: Useful for setting specific bits or combining flags. When: Use when you need to add flags or set specific bits. How:

(bit-or i1 i2...)

Best Practices:

  • Use to add new permissions or flags to an existing set.
  • Combine with bit-and for more complex flag manipulations.

Example Use Case: Adding a new permission to a user's existing permissions.

(define-public (add-permission (current-permissions uint) (new-permission uint))
  (ok (bit-or current-permissions new-permission)))

;; Usage
(add-permission u3 PERMISSION_EXEC)  ;; Returns (ok u7)

3. bit-not

What: Performs a bitwise NOT operation on an integer. Why: Useful for inverting all bits in a value. When: Use when you need to flip all bits or create a bitmask. How:

(bit-not i1)

Best Practices:

  • Use carefully with signed integers, as it affects the sign bit.
  • Combine with bit-and to clear specific bits.

Example Use Case: Creating a bitmask to clear specific permissions.

(define-public (remove-permission (current-permissions uint) (permission-to-remove uint))
  (ok (bit-and current-permissions (bit-not permission-to-remove))))

;; Usage
(remove-permission u7 PERMISSION_WRITE)  ;; Returns (ok u5)

Practical Example: Compact User Profile Flags

Let's implement a system that stores multiple user profile settings in a single integer using bit flags:

(define-constant SETTING_NEWSLETTER u1)    ;; 0001
(define-constant SETTING_2FA u2)           ;; 0010
(define-constant SETTING_PRIVATE_PROFILE u4) ;; 0100
(define-constant SETTING_DARK_MODE u8)     ;; 1000

(define-map user-settings principal uint)

(define-public (update-setting (setting uint) (enabled bool))
  (let ((current-settings (default-to u0 (map-get? user-settings tx-sender))))
    (if enabled
        (map-set user-settings tx-sender (bit-or current-settings setting))
        (map-set user-settings tx-sender (bit-and current-settings (bit-not setting))))
    (ok true)))

(define-read-only (has-setting? (user principal) (setting uint))
  (let ((user-settings (default-to u0 (map-get? user-settings user))))
    (is-eq (bit-and user-settings setting) setting)))

(define-read-only (get-all-settings (user principal))
  (default-to u0 (map-get? user-settings user)))

This example demonstrates:

  1. Using bit constants to define individual settings.
  2. Updating settings using bit-or to set flags and bit-and with bit-not to clear flags.
  3. Checking individual settings with bit-and.
  4. Storing all user settings compactly in a single uint.

Conclusion

Bit manipulation functions in Clarity provide powerful tools for efficient data storage and flag management in smart contracts. By understanding when and how to use these functions, developers can create more storage-efficient contracts and implement complex flag systems with ease. Always consider the trade-off between storage efficiency and code readability when using these techniques.