CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-eth-brownie

A Python framework for Ethereum smart contract deployment, testing and interaction.

Pending
Overview
Eval results
Files

conversion-testing.mddocs/

Data Conversion & Testing

Type conversion utilities for seamless interaction between Python and Ethereum data types, plus integration with Hypothesis for property-based testing of smart contracts.

Capabilities

Data Type Classes

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."""

Conversion Functions

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
    """

Testing Framework Integration

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
    """

Testing Utilities

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]})
    """

Usage Examples

Wei and Unit Conversion

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"))

Fixed-Point Decimals

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})

Address Handling

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")

Type Conversion

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}")

Return Value Handling

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}")

Property-Based Testing

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
    pass

Stateful Testing

from 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.TestCase

Error Testing

from 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 Definitions

# 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

docs

accounts.md

cli.md

contracts.md

conversion-testing.md

index.md

network.md

project.md

tile.json