A Python framework for Ethereum smart contract deployment, testing and interaction.
—
Type conversion utilities for seamless interaction between Python and Ethereum data types, plus integration with Hypothesis for property-based testing of smart contracts.
Specialized classes for handling Ethereum-specific data types with automatic conversion and validation.
class Wei:
"""
Ethereum wei value with automatic unit conversion and arithmetic operations.
Supports all standard Ethereum units: wei, kwei, mwei, gwei, szabo, finney, ether.
"""
def __init__(self, value: Union[int, str, float]):
"""
Initialize Wei value.
Args:
value: Value in wei or string with units (e.g., "1 ether", "20 gwei")
"""
def __str__(self) -> str:
"""String representation in wei."""
def __int__(self) -> int:
"""Convert to integer wei value."""
def __float__(self) -> float:
"""Convert to float wei value."""
def __add__(self, other: Union['Wei', int, str]) -> 'Wei':
"""Add Wei values."""
def __sub__(self, other: Union['Wei', int, str]) -> 'Wei':
"""Subtract Wei values."""
def __mul__(self, other: Union[int, float]) -> 'Wei':
"""Multiply Wei value."""
def __truediv__(self, other: Union[int, float]) -> 'Wei':
"""Divide Wei value."""
def __eq__(self, other: Union['Wei', int, str]) -> bool:
"""Check equality with other Wei values."""
def __lt__(self, other: Union['Wei', int, str]) -> bool:
"""Compare Wei values."""
def to(self, unit: str) -> Union[int, float]:
"""
Convert to specific unit.
Args:
unit: Target unit (wei, kwei, mwei, gwei, szabo, finney, ether)
Returns:
Union[int, float]: Value in specified unit
"""
class Fixed:
"""
Fixed-point decimal number with configurable precision for financial calculations.
"""
def __init__(self, value: Union[int, str, float], digits: int = None):
"""
Initialize fixed-point number.
Args:
value: Numeric value
digits: Decimal places (auto-detect if None)
"""
def __str__(self) -> str:
"""String representation with full precision."""
def __int__(self) -> int:
"""Convert to integer (truncated)."""
def __float__(self) -> float:
"""Convert to float."""
def __add__(self, other: Union['Fixed', int, float]) -> 'Fixed':
"""Add Fixed values."""
def __sub__(self, other: Union['Fixed', int, float]) -> 'Fixed':
"""Subtract Fixed values."""
def __mul__(self, other: Union['Fixed', int, float]) -> 'Fixed':
"""Multiply Fixed values."""
def __truediv__(self, other: Union['Fixed', int, float]) -> 'Fixed':
"""Divide Fixed values."""
class EthAddress:
"""
Ethereum address with validation and checksum formatting.
"""
def __init__(self, address: str):
"""
Initialize Ethereum address.
Args:
address: Ethereum address string
Raises:
ValueError: If address format is invalid
"""
def __str__(self) -> str:
"""Checksum formatted address."""
def __eq__(self, other: Union['EthAddress', str]) -> bool:
"""Compare addresses (case-insensitive)."""
def is_checksum(self) -> bool:
"""Check if address uses valid checksum formatting."""
class HexString:
"""
Hexadecimal string with validation and conversion utilities.
"""
def __init__(self, data: Union[str, bytes, int]):
"""
Initialize hex string.
Args:
data: Data to convert to hexadecimal
"""
def __str__(self) -> str:
"""Hex string representation."""
def __bytes__(self) -> bytes:
"""Convert to bytes."""
def __int__(self) -> int:
"""Convert to integer."""
class ReturnValue:
"""
Smart contract return value wrapper with named field access.
"""
def __init__(self, values: tuple, abi: dict = None):
"""
Initialize return value wrapper.
Args:
values: Tuple of return values
abi: Function ABI for named access
"""
def __getitem__(self, index: Union[int, str]):
"""Access return value by index or name."""
def __iter__(self):
"""Iterate over return values."""
def __len__(self) -> int:
"""Number of return values."""
def dict(self) -> dict:
"""Convert to dictionary with named fields."""Utility functions for converting between Python and Ethereum data types with validation and error handling.
def to_uint(value: Any, type_str: str = "uint256") -> int:
"""
Convert value to unsigned integer.
Args:
value: Value to convert
type_str: Solidity uint type (uint8, uint16, ..., uint256)
Returns:
int: Converted unsigned integer value
Raises:
ValueError: If value cannot be converted or is out of range
"""
def to_int(value: Any, type_str: str = "int256") -> int:
"""
Convert value to signed integer.
Args:
value: Value to convert
type_str: Solidity int type (int8, int16, ..., int256)
Returns:
int: Converted signed integer value
Raises:
ValueError: If value cannot be converted or is out of range
"""
def to_decimal(value: Any) -> Fixed:
"""
Convert value to fixed-point decimal.
Args:
value: Value to convert
Returns:
Fixed: Fixed-point decimal representation
"""
def to_address(value: Any) -> str:
"""
Convert value to Ethereum address.
Args:
value: Value to convert (string, bytes, int)
Returns:
str: Checksum formatted Ethereum address
Raises:
ValueError: If value cannot be converted to valid address
"""
def to_bytes(value: Any, type_str: str = "bytes32") -> bytes:
"""
Convert value to bytes.
Args:
value: Value to convert
type_str: Solidity bytes type (bytes1, bytes2, ..., bytes32, bytes)
Returns:
bytes: Converted bytes value
Raises:
ValueError: If value cannot be converted or is wrong length
"""
def to_bool(value: Any) -> bool:
"""
Convert value to boolean following Solidity rules.
Args:
value: Value to convert
Returns:
bool: Boolean value (0 is False, everything else is True)
"""
def to_string(value: Any) -> str:
"""
Convert value to string.
Args:
value: Value to convert
Returns:
str: String representation
"""Decorators and utilities for property-based testing with Hypothesis integration.
def given(**strategies) -> Callable:
"""
Hypothesis property-based testing decorator.
Args:
**strategies: Mapping of parameter names to test strategies
Returns:
Callable: Decorated test function
Example:
@given(value=strategy('uint256'), sender=strategy('address'))
def test_transfer(token, value, sender):
# Test with generated values
"""
def strategy(name: str, **kwargs) -> Callable:
"""
Create custom test data generation strategy.
Args:
name: Strategy name (uint256, address, bytes32, etc.)
**kwargs: Strategy configuration options
Returns:
Callable: Strategy function for generating test data
Available strategies:
- address: Random Ethereum addresses
- uint256: Unsigned integers with configurable range
- int256: Signed integers with configurable range
- bytes32: Random 32-byte values
- string: Random strings with configurable length
- bool: Boolean values
- decimal: Fixed-point decimals
"""
def contract_strategy(name: str) -> Callable:
"""
Generate contract deployment strategies for testing.
Args:
name: Contract name to generate strategy for
Returns:
Callable: Strategy for contract deployment with random parameters
"""Additional utilities for smart contract testing including state management and assertion helpers.
def reverts(reason: str = None) -> ContextManager:
"""
Context manager for testing transaction reverts.
Args:
reason: Expected revert reason string
Returns:
ContextManager: Context manager for revert testing
Example:
with reverts("Insufficient balance"):
token.transfer(recipient, amount, {'from': sender})
"""
def state_machine() -> Callable:
"""
Hypothesis stateful testing decorator for complex contract interactions.
Returns:
Callable: Decorator for stateful test classes
Example:
@state_machine()
class TokenStateMachine:
def __init__(self):
self.token = Token.deploy({'from': accounts[0]})
"""from brownie.convert import Wei
# Create Wei values
amount1 = Wei(1000000000000000000) # 1 ether in wei
amount2 = Wei("1 ether") # Same as above
amount3 = Wei("20 gwei") # Gas price
amount4 = Wei(0.5) # 0.5 wei (float)
# Arithmetic operations
total = amount1 + amount2 # 2 ether
difference = amount1 - Wei("0.1 ether") # 0.9 ether
doubled = amount1 * 2 # 2 ether
half = amount1 / 2 # 0.5 ether
# Unit conversion
print(f"In ether: {amount1.to('ether')}") # 1.0
print(f"In gwei: {amount1.to('gwei')}") # 1000000000.0
print(f"In wei: {amount1.to('wei')}") # 1000000000000000000
# Comparisons
if amount1 > Wei("0.5 ether"):
print("Amount is greater than 0.5 ether")
# Use in transactions
account.transfer(recipient, Wei("1 ether"))from brownie.convert import Fixed
# Create fixed-point numbers
price1 = Fixed("123.456789") # Full precision
price2 = Fixed(123.456789, 6) # 6 decimal places
price3 = Fixed(123456789, -6) # Scale factor
# Arithmetic with precision
total_price = price1 + price2
discount = total_price * Fixed("0.9") # 10% discount
final_price = total_price - discount
print(f"Final price: {final_price}")
# Use in contract calculations
token_amount = Fixed(user_input) * Fixed(exchange_rate)
contract.swap(int(token_amount), {'from': account})from brownie.convert import to_address, EthAddress
# Convert various formats to address
addr1 = to_address("0x742d35Cc6634C0532925a3b8D8D944d0Cdbc1234")
addr2 = to_address(0x742d35Cc6634C0532925a3b8D8D944d0Cdbc1234)
addr3 = to_address(b'\x74\x2d\x35\xCc\x66\x34\xC0\x53\x29\x25\xa3\xb8\xD8\xD9\x44\xd0\xCd\xbc\x12\x34')
# Use EthAddress class
eth_addr = EthAddress("0x742d35cc6634c0532925a3b8d8d944d0cdbc1234")
print(f"Checksum address: {eth_addr}") # Proper checksum
print(f"Is checksum: {eth_addr.is_checksum()}")
# Address validation in functions
def validate_recipient(address):
try:
return to_address(address)
except ValueError:
raise ValueError("Invalid recipient address")from brownie.convert import to_uint, to_int, to_bytes, to_bool
# Convert to Solidity types
uint_value = to_uint(123, "uint8") # Fits in uint8
large_uint = to_uint("123456789", "uint256")
int_value = to_int(-123, "int16") # Signed integer
bytes_value = to_bytes("hello", "bytes32") # Padded to 32 bytes
bool_value = to_bool(1) # True
# Use in contract interactions
contract.setValues(
to_uint(user_amount, "uint256"),
to_bytes(user_data, "bytes32"),
to_bool(user_flag),
{'from': account}
)
# Handle conversion errors
try:
value = to_uint(300, "uint8") # Too large for uint8
except ValueError as e:
print(f"Conversion error: {e}")from brownie.convert import ReturnValue
# Contract method with multiple returns
result = contract.getTokenInfo() # Returns (name, symbol, decimals, totalSupply)
# Access by index
token_name = result[0]
token_symbol = result[1]
# Access by name (if ABI available)
token_name = result['name']
decimals = result['decimals']
# Convert to dictionary
token_info = result.dict()
print(f"Token info: {token_info}")
# Iterate over values
for i, value in enumerate(result):
print(f"Return value {i}: {value}")from brownie.test import given, strategy
from brownie import accounts, reverts
@given(
amount=strategy('uint256', max_value=10**18),
recipient=strategy('address')
)
def test_transfer(token, amount, recipient):
"""Test token transfer with random values."""
sender = accounts[0]
initial_balance = token.balanceOf(sender)
if amount <= initial_balance:
# Should succeed
tx = token.transfer(recipient, amount, {'from': sender})
assert token.balanceOf(sender) == initial_balance - amount
assert token.balanceOf(recipient) >= amount
else:
# Should revert
with reverts():
token.transfer(recipient, amount, {'from': sender})
@given(
value=strategy('uint256', min_value=1, max_value=1000),
data=strategy('bytes32')
)
def test_contract_call(contract, value, data):
"""Test contract method with random parameters."""
initial_state = contract.getState()
tx = contract.processData(value, data, {'from': accounts[0]})
# Verify state changes
new_state = contract.getState()
assert new_state != initial_state
assert contract.lastValue() == value
assert contract.lastData() == data
# Custom strategies
from hypothesis import strategies as st
address_strategy = strategy(
'address',
exclude=['0x0000000000000000000000000000000000000000'] # Exclude zero address
)
amount_strategy = strategy(
'uint256',
min_value=1,
max_value=Wei("1000 ether").to('wei')
)
@given(
sender=address_strategy,
amount=amount_strategy
)
def test_with_custom_strategies(token, sender, amount):
# Test implementation
passfrom brownie.test import state_machine, given, strategy
from brownie import accounts
import hypothesis.strategies as st
@state_machine()
class TokenStateMachine:
"""Stateful testing for complex token interactions."""
def __init__(self):
self.token = Token.deploy("Test", "TST", 18, 1000000, {'from': accounts[0]})
self.balances = {accounts[0].address: 1000000}
@given(
recipient=strategy('address'),
amount=st.integers(min_value=1, max_value=1000)
)
def transfer(self, recipient, amount):
"""State transition: transfer tokens."""
sender = accounts[0]
if self.balances[sender.address] >= amount:
# Execute transfer
self.token.transfer(recipient, amount, {'from': sender})
# Update internal state
self.balances[sender.address] -= amount
self.balances[recipient] = self.balances.get(recipient, 0) + amount
# Verify contract state matches internal state
assert self.token.balanceOf(sender) == self.balances[sender.address]
assert self.token.balanceOf(recipient) == self.balances[recipient]
@given(amount=st.integers(min_value=1, max_value=1000))
def mint(self, amount):
"""State transition: mint new tokens."""
recipient = accounts[1]
self.token.mint(recipient, amount, {'from': accounts[0]})
# Update internal state
self.balances[recipient] = self.balances.get(recipient, 0) + amount
# Verify state
assert self.token.balanceOf(recipient) == self.balances[recipient]
# Run stateful tests
TestToken = TokenStateMachine.TestCasefrom brownie import reverts, VirtualMachineError
def test_revert_conditions(token):
"""Test various revert conditions."""
sender = accounts[0]
recipient = accounts[1]
# Test with specific revert message
with reverts("ERC20: transfer amount exceeds balance"):
token.transfer(recipient, token.totalSupply() + 1, {'from': sender})
# Test any revert
with reverts():
token.transfer("0x0000000000000000000000000000000000000000", 1, {'from': sender})
# Test custom error types
with reverts(VirtualMachineError):
token.riskyFunction({'from': sender})
# Allow reverted transactions for analysis
tx = token.transfer(recipient, token.totalSupply() + 1,
{'from': sender, 'allow_revert': True})
assert tx.status == 0 # Transaction reverted
print(f"Revert reason: {tx.revert_msg}")# Type aliases for conversion and testing
ConvertibleValue = Union[int, float, str, bytes]
EthereumAddress = Union[str, EthAddress]
SolidityType = str # e.g., "uint256", "bytes32", "address"
TestStrategy = Callable[..., Any]
StateMachine = Callable[..., Any]
RevertContext = ContextManager[None]Install with Tessl CLI
npx tessl i tessl/pypi-eth-brownie@1.21.1