How to Handle Integer Types in GenLayer Contracts: A Complete Guide to u256 and i256
GenLayer contracts require the u256 and i256 wrapper classes for all integer state storage, forcing developers to convert to native Python int for arithmetic operations and re-wrap results before assignment to maintain deterministic VM compliance.
The genlayerlabs/genlayer-project-boilerplate repository demonstrates how GenLayer's deterministic virtual machine enforces strict storage typing for numeric values. Unlike standard Python, you cannot store raw integers directly in contract state—instead, you must handle integer types in GenLayer contracts using specialized 256-bit wrapper classes that ensure consistent, deterministic behavior across all execution nodes.
Understanding u256 and i256 Storage Types
GenLayer provides two fixed-size, 256-bit numeric classes for integer arithmetic. Both types are immutable wrappers around Python’s native int and are the only permitted primitive storage types for numeric values in contract state.
u256 (unsigned 256-bit integer)
- Values are always non-negative and wrap on overflow modulo 2²⁵⁶
- Use for counters, token balances, points, or any value that cannot be negative
- Instantiation:
u256(0)oru256(100)
i256 (signed 256-bit integer)
- Supports negative values and wraps on overflow modulo 2²⁵⁶
- Use for scores that may be negative, offsets, or calculations where subtraction can produce negative results
- Instantiation:
i256(-5)ori256(0)
According to the CLAUDE.md file in the official boilerplate, these types are listed under Storage types and represent the complete set of integer primitives available to contract developers.
Instantiating Integer Wrappers
You must explicitly construct wrapper instances using class constructors. Direct assignment of raw Python int values to state variables will be rejected by the GenLayer linter.
from genlayer import gl
class Counter(gl.Contract):
count: u256
def __init__(self):
# Correct: explicit wrapper construction
self.count = u256(0)
# Incorrect: raw int assignment fails linting
# self.count = 0
The wrappers validate constraints at construction time—u256 rejects negative inputs while i256 accepts the full signed range.
Performing Arithmetic Operations
Because u256 and i256 are immutable, you cannot perform in-place arithmetic. The required pattern is unwrap-calculate-wrap: convert the wrapper to a native int, perform the calculation, then construct a new wrapper with the result.
In contracts/PatternTest.py, the canonical increment pattern appears in the increment method:
@gl.public.write
def increment(self) -> u256:
"""Increase count by one using the unwrap-calculate-wrap pattern."""
self.count = u256(int(self.count) + 1)
return self.count
The same pattern applies to signed arithmetic:
# Decrementing an i256 value
self.balance = i256(int(self.balance) - amount)
When returning values to callers, the VM automatically converts wrappers back to Python int. View methods should annotate return types as int (or keep the wrapper if preserving type information), as shown in the get_count method:
@gl.public.view
def get_count(self) -> int:
"""Expose the counter as a plain Python int."""
return int(self.count)
Storing Integers in Contract State
The linter only accepts u256 and i256 inside built-in storage containers (TreeMap, DynArray, Array). You cannot store raw Python integers in contract state—attempting to do so produces validation errors during compilation.
The contracts/football_bets.py file demonstrates a production pattern for user-scored points:
class FootballBets(gl.Contract):
points: TreeMap[Address, u256]
@gl.public.write
def award_point(self, player: Address) -> None:
"""Give a player one point."""
# Initialize to zero if the address is unseen
if player not in self.points:
self.points[player] = u256(0)
# Increment safely using conversion pattern
current = int(self.points[player])
self.points[player] = u256(current + 1)
This pattern ensures that all state storage remains deterministic and compatible with GenLayer's consensus mechanism.
Handling Overflow and Determinism
Both u256 and i256 implement wrapping arithmetic modulo 2²⁵⁶. This behavior mirrors Solidity’s uint256 and int256 semantics, ensuring deterministic execution across all validator nodes regardless of underlying hardware.
When an operation exceeds the maximum representable value, it wraps around to the minimum:
# u256 overflow example
max_val = u256(2**256 - 1)
result = u256(int(max_val) + 1) # Wraps to u256(0)
# i256 overflow example
min_val = i256(-2**255)
result = i256(int(min_val) - 1) # Wraps to i256(2**255 - 1)
The tests/direct/test_patterns.py file contains unit tests verifying correct round-trip conversion and default initialization behavior for these types, ensuring your contracts behave identically in direct-mode testing and on-chain execution.
Summary
- Use wrappers exclusively: Store only
u256andi256instances in contract state, never raw Pythonintvalues. - Follow the conversion pattern: Unwrap with
int(wrapper), calculate, then re-wrap withu256(result)ori256(result)before assignment. - Respect container constraints: Place integer wrappers only inside approved storage containers like
TreeMaporDynArray. - Expect wrapping overflow: Arithmetic operations wrap modulo 2²⁵⁶, matching Ethereum Virtual Machine behavior for consistent cross-node execution.
- Annotate view returns: Return
intfrom view methods for automatic conversion, or maintain wrappers if type preservation is required.
Frequently Asked Questions
Can I store raw Python int values in GenLayer contract state?
No. The GenLayer linter explicitly rejects raw Python integers in state declarations. You must declare state variables using u256 for unsigned values or i256 for signed values, then wrap all assignments using the class constructors (e.g., self.value = u256(100)). Attempting to store int types directly results in compilation failures.
How do I handle negative numbers in GenLayer contracts?
Use the i256 type for any value that may become negative. Unlike u256, which only accepts non-negative inputs, i256 supports the full signed 256-bit integer range from -2²⁵⁵ to 2²⁵⁵-1. Construct negative values with i256(-5), and always apply the unwrap-calculate-wrap pattern when performing arithmetic that might produce negative intermediate results.
What happens when a u256 or i256 value overflows?
Both types implement wrapping overflow semantics identical to Solidity. When a value exceeds 2²⁵⁶-1 (for unsigned) or falls below -2²⁵⁵ (for signed), it wraps around modulo 2²⁵⁶. This deterministic behavior ensures that all nodes in the GenLayer network compute identical results regardless of hardware architecture, preventing consensus failures.
Should view methods return u256 or int types?
View methods should typically annotate return types as int. The GenLayer VM automatically converts u256 and i256 wrappers to native Python integers when returning values to callers, making int the natural choice for external interfaces. However, you may return the wrapper type directly if you specifically want to preserve the type information in the contract's public API.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →