0
# Data Conversion & Testing
1
2
Type conversion utilities for seamless interaction between Python and Ethereum data types, plus integration with Hypothesis for property-based testing of smart contracts.
3
4
## Capabilities
5
6
### Data Type Classes
7
8
Specialized classes for handling Ethereum-specific data types with automatic conversion and validation.
9
10
```python { .api }
11
class Wei:
12
"""
13
Ethereum wei value with automatic unit conversion and arithmetic operations.
14
15
Supports all standard Ethereum units: wei, kwei, mwei, gwei, szabo, finney, ether.
16
"""
17
18
def __init__(self, value: Union[int, str, float]):
19
"""
20
Initialize Wei value.
21
22
Args:
23
value: Value in wei or string with units (e.g., "1 ether", "20 gwei")
24
"""
25
26
def __str__(self) -> str:
27
"""String representation in wei."""
28
29
def __int__(self) -> int:
30
"""Convert to integer wei value."""
31
32
def __float__(self) -> float:
33
"""Convert to float wei value."""
34
35
def __add__(self, other: Union['Wei', int, str]) -> 'Wei':
36
"""Add Wei values."""
37
38
def __sub__(self, other: Union['Wei', int, str]) -> 'Wei':
39
"""Subtract Wei values."""
40
41
def __mul__(self, other: Union[int, float]) -> 'Wei':
42
"""Multiply Wei value."""
43
44
def __truediv__(self, other: Union[int, float]) -> 'Wei':
45
"""Divide Wei value."""
46
47
def __eq__(self, other: Union['Wei', int, str]) -> bool:
48
"""Check equality with other Wei values."""
49
50
def __lt__(self, other: Union['Wei', int, str]) -> bool:
51
"""Compare Wei values."""
52
53
def to(self, unit: str) -> Union[int, float]:
54
"""
55
Convert to specific unit.
56
57
Args:
58
unit: Target unit (wei, kwei, mwei, gwei, szabo, finney, ether)
59
60
Returns:
61
Union[int, float]: Value in specified unit
62
"""
63
64
class Fixed:
65
"""
66
Fixed-point decimal number with configurable precision for financial calculations.
67
"""
68
69
def __init__(self, value: Union[int, str, float], digits: int = None):
70
"""
71
Initialize fixed-point number.
72
73
Args:
74
value: Numeric value
75
digits: Decimal places (auto-detect if None)
76
"""
77
78
def __str__(self) -> str:
79
"""String representation with full precision."""
80
81
def __int__(self) -> int:
82
"""Convert to integer (truncated)."""
83
84
def __float__(self) -> float:
85
"""Convert to float."""
86
87
def __add__(self, other: Union['Fixed', int, float]) -> 'Fixed':
88
"""Add Fixed values."""
89
90
def __sub__(self, other: Union['Fixed', int, float]) -> 'Fixed':
91
"""Subtract Fixed values."""
92
93
def __mul__(self, other: Union['Fixed', int, float]) -> 'Fixed':
94
"""Multiply Fixed values."""
95
96
def __truediv__(self, other: Union['Fixed', int, float]) -> 'Fixed':
97
"""Divide Fixed values."""
98
99
class EthAddress:
100
"""
101
Ethereum address with validation and checksum formatting.
102
"""
103
104
def __init__(self, address: str):
105
"""
106
Initialize Ethereum address.
107
108
Args:
109
address: Ethereum address string
110
111
Raises:
112
ValueError: If address format is invalid
113
"""
114
115
def __str__(self) -> str:
116
"""Checksum formatted address."""
117
118
def __eq__(self, other: Union['EthAddress', str]) -> bool:
119
"""Compare addresses (case-insensitive)."""
120
121
def is_checksum(self) -> bool:
122
"""Check if address uses valid checksum formatting."""
123
124
class HexString:
125
"""
126
Hexadecimal string with validation and conversion utilities.
127
"""
128
129
def __init__(self, data: Union[str, bytes, int]):
130
"""
131
Initialize hex string.
132
133
Args:
134
data: Data to convert to hexadecimal
135
"""
136
137
def __str__(self) -> str:
138
"""Hex string representation."""
139
140
def __bytes__(self) -> bytes:
141
"""Convert to bytes."""
142
143
def __int__(self) -> int:
144
"""Convert to integer."""
145
146
class ReturnValue:
147
"""
148
Smart contract return value wrapper with named field access.
149
"""
150
151
def __init__(self, values: tuple, abi: dict = None):
152
"""
153
Initialize return value wrapper.
154
155
Args:
156
values: Tuple of return values
157
abi: Function ABI for named access
158
"""
159
160
def __getitem__(self, index: Union[int, str]):
161
"""Access return value by index or name."""
162
163
def __iter__(self):
164
"""Iterate over return values."""
165
166
def __len__(self) -> int:
167
"""Number of return values."""
168
169
def dict(self) -> dict:
170
"""Convert to dictionary with named fields."""
171
```
172
173
### Conversion Functions
174
175
Utility functions for converting between Python and Ethereum data types with validation and error handling.
176
177
```python { .api }
178
def to_uint(value: Any, type_str: str = "uint256") -> int:
179
"""
180
Convert value to unsigned integer.
181
182
Args:
183
value: Value to convert
184
type_str: Solidity uint type (uint8, uint16, ..., uint256)
185
186
Returns:
187
int: Converted unsigned integer value
188
189
Raises:
190
ValueError: If value cannot be converted or is out of range
191
"""
192
193
def to_int(value: Any, type_str: str = "int256") -> int:
194
"""
195
Convert value to signed integer.
196
197
Args:
198
value: Value to convert
199
type_str: Solidity int type (int8, int16, ..., int256)
200
201
Returns:
202
int: Converted signed integer value
203
204
Raises:
205
ValueError: If value cannot be converted or is out of range
206
"""
207
208
def to_decimal(value: Any) -> Fixed:
209
"""
210
Convert value to fixed-point decimal.
211
212
Args:
213
value: Value to convert
214
215
Returns:
216
Fixed: Fixed-point decimal representation
217
"""
218
219
def to_address(value: Any) -> str:
220
"""
221
Convert value to Ethereum address.
222
223
Args:
224
value: Value to convert (string, bytes, int)
225
226
Returns:
227
str: Checksum formatted Ethereum address
228
229
Raises:
230
ValueError: If value cannot be converted to valid address
231
"""
232
233
def to_bytes(value: Any, type_str: str = "bytes32") -> bytes:
234
"""
235
Convert value to bytes.
236
237
Args:
238
value: Value to convert
239
type_str: Solidity bytes type (bytes1, bytes2, ..., bytes32, bytes)
240
241
Returns:
242
bytes: Converted bytes value
243
244
Raises:
245
ValueError: If value cannot be converted or is wrong length
246
"""
247
248
def to_bool(value: Any) -> bool:
249
"""
250
Convert value to boolean following Solidity rules.
251
252
Args:
253
value: Value to convert
254
255
Returns:
256
bool: Boolean value (0 is False, everything else is True)
257
"""
258
259
def to_string(value: Any) -> str:
260
"""
261
Convert value to string.
262
263
Args:
264
value: Value to convert
265
266
Returns:
267
str: String representation
268
"""
269
```
270
271
### Testing Framework Integration
272
273
Decorators and utilities for property-based testing with Hypothesis integration.
274
275
```python { .api }
276
def given(**strategies) -> Callable:
277
"""
278
Hypothesis property-based testing decorator.
279
280
Args:
281
**strategies: Mapping of parameter names to test strategies
282
283
Returns:
284
Callable: Decorated test function
285
286
Example:
287
@given(value=strategy('uint256'), sender=strategy('address'))
288
def test_transfer(token, value, sender):
289
# Test with generated values
290
"""
291
292
def strategy(name: str, **kwargs) -> Callable:
293
"""
294
Create custom test data generation strategy.
295
296
Args:
297
name: Strategy name (uint256, address, bytes32, etc.)
298
**kwargs: Strategy configuration options
299
300
Returns:
301
Callable: Strategy function for generating test data
302
303
Available strategies:
304
- address: Random Ethereum addresses
305
- uint256: Unsigned integers with configurable range
306
- int256: Signed integers with configurable range
307
- bytes32: Random 32-byte values
308
- string: Random strings with configurable length
309
- bool: Boolean values
310
- decimal: Fixed-point decimals
311
"""
312
313
def contract_strategy(name: str) -> Callable:
314
"""
315
Generate contract deployment strategies for testing.
316
317
Args:
318
name: Contract name to generate strategy for
319
320
Returns:
321
Callable: Strategy for contract deployment with random parameters
322
"""
323
```
324
325
### Testing Utilities
326
327
Additional utilities for smart contract testing including state management and assertion helpers.
328
329
```python { .api }
330
def reverts(reason: str = None) -> ContextManager:
331
"""
332
Context manager for testing transaction reverts.
333
334
Args:
335
reason: Expected revert reason string
336
337
Returns:
338
ContextManager: Context manager for revert testing
339
340
Example:
341
with reverts("Insufficient balance"):
342
token.transfer(recipient, amount, {'from': sender})
343
"""
344
345
def state_machine() -> Callable:
346
"""
347
Hypothesis stateful testing decorator for complex contract interactions.
348
349
Returns:
350
Callable: Decorator for stateful test classes
351
352
Example:
353
@state_machine()
354
class TokenStateMachine:
355
def __init__(self):
356
self.token = Token.deploy({'from': accounts[0]})
357
"""
358
```
359
360
## Usage Examples
361
362
### Wei and Unit Conversion
363
364
```python
365
from brownie.convert import Wei
366
367
# Create Wei values
368
amount1 = Wei(1000000000000000000) # 1 ether in wei
369
amount2 = Wei("1 ether") # Same as above
370
amount3 = Wei("20 gwei") # Gas price
371
amount4 = Wei(0.5) # 0.5 wei (float)
372
373
# Arithmetic operations
374
total = amount1 + amount2 # 2 ether
375
difference = amount1 - Wei("0.1 ether") # 0.9 ether
376
doubled = amount1 * 2 # 2 ether
377
half = amount1 / 2 # 0.5 ether
378
379
# Unit conversion
380
print(f"In ether: {amount1.to('ether')}") # 1.0
381
print(f"In gwei: {amount1.to('gwei')}") # 1000000000.0
382
print(f"In wei: {amount1.to('wei')}") # 1000000000000000000
383
384
# Comparisons
385
if amount1 > Wei("0.5 ether"):
386
print("Amount is greater than 0.5 ether")
387
388
# Use in transactions
389
account.transfer(recipient, Wei("1 ether"))
390
```
391
392
### Fixed-Point Decimals
393
394
```python
395
from brownie.convert import Fixed
396
397
# Create fixed-point numbers
398
price1 = Fixed("123.456789") # Full precision
399
price2 = Fixed(123.456789, 6) # 6 decimal places
400
price3 = Fixed(123456789, -6) # Scale factor
401
402
# Arithmetic with precision
403
total_price = price1 + price2
404
discount = total_price * Fixed("0.9") # 10% discount
405
final_price = total_price - discount
406
407
print(f"Final price: {final_price}")
408
409
# Use in contract calculations
410
token_amount = Fixed(user_input) * Fixed(exchange_rate)
411
contract.swap(int(token_amount), {'from': account})
412
```
413
414
### Address Handling
415
416
```python
417
from brownie.convert import to_address, EthAddress
418
419
# Convert various formats to address
420
addr1 = to_address("0x742d35Cc6634C0532925a3b8D8D944d0Cdbc1234")
421
addr2 = to_address(0x742d35Cc6634C0532925a3b8D8D944d0Cdbc1234)
422
addr3 = to_address(b'\x74\x2d\x35\xCc\x66\x34\xC0\x53\x29\x25\xa3\xb8\xD8\xD9\x44\xd0\xCd\xbc\x12\x34')
423
424
# Use EthAddress class
425
eth_addr = EthAddress("0x742d35cc6634c0532925a3b8d8d944d0cdbc1234")
426
print(f"Checksum address: {eth_addr}") # Proper checksum
427
print(f"Is checksum: {eth_addr.is_checksum()}")
428
429
# Address validation in functions
430
def validate_recipient(address):
431
try:
432
return to_address(address)
433
except ValueError:
434
raise ValueError("Invalid recipient address")
435
```
436
437
### Type Conversion
438
439
```python
440
from brownie.convert import to_uint, to_int, to_bytes, to_bool
441
442
# Convert to Solidity types
443
uint_value = to_uint(123, "uint8") # Fits in uint8
444
large_uint = to_uint("123456789", "uint256")
445
446
int_value = to_int(-123, "int16") # Signed integer
447
bytes_value = to_bytes("hello", "bytes32") # Padded to 32 bytes
448
bool_value = to_bool(1) # True
449
450
# Use in contract interactions
451
contract.setValues(
452
to_uint(user_amount, "uint256"),
453
to_bytes(user_data, "bytes32"),
454
to_bool(user_flag),
455
{'from': account}
456
)
457
458
# Handle conversion errors
459
try:
460
value = to_uint(300, "uint8") # Too large for uint8
461
except ValueError as e:
462
print(f"Conversion error: {e}")
463
```
464
465
### Return Value Handling
466
467
```python
468
from brownie.convert import ReturnValue
469
470
# Contract method with multiple returns
471
result = contract.getTokenInfo() # Returns (name, symbol, decimals, totalSupply)
472
473
# Access by index
474
token_name = result[0]
475
token_symbol = result[1]
476
477
# Access by name (if ABI available)
478
token_name = result['name']
479
decimals = result['decimals']
480
481
# Convert to dictionary
482
token_info = result.dict()
483
print(f"Token info: {token_info}")
484
485
# Iterate over values
486
for i, value in enumerate(result):
487
print(f"Return value {i}: {value}")
488
```
489
490
### Property-Based Testing
491
492
```python
493
from brownie.test import given, strategy
494
from brownie import accounts, reverts
495
496
@given(
497
amount=strategy('uint256', max_value=10**18),
498
recipient=strategy('address')
499
)
500
def test_transfer(token, amount, recipient):
501
"""Test token transfer with random values."""
502
sender = accounts[0]
503
initial_balance = token.balanceOf(sender)
504
505
if amount <= initial_balance:
506
# Should succeed
507
tx = token.transfer(recipient, amount, {'from': sender})
508
assert token.balanceOf(sender) == initial_balance - amount
509
assert token.balanceOf(recipient) >= amount
510
else:
511
# Should revert
512
with reverts():
513
token.transfer(recipient, amount, {'from': sender})
514
515
@given(
516
value=strategy('uint256', min_value=1, max_value=1000),
517
data=strategy('bytes32')
518
)
519
def test_contract_call(contract, value, data):
520
"""Test contract method with random parameters."""
521
initial_state = contract.getState()
522
523
tx = contract.processData(value, data, {'from': accounts[0]})
524
525
# Verify state changes
526
new_state = contract.getState()
527
assert new_state != initial_state
528
assert contract.lastValue() == value
529
assert contract.lastData() == data
530
531
# Custom strategies
532
from hypothesis import strategies as st
533
534
address_strategy = strategy(
535
'address',
536
exclude=['0x0000000000000000000000000000000000000000'] # Exclude zero address
537
)
538
539
amount_strategy = strategy(
540
'uint256',
541
min_value=1,
542
max_value=Wei("1000 ether").to('wei')
543
)
544
545
@given(
546
sender=address_strategy,
547
amount=amount_strategy
548
)
549
def test_with_custom_strategies(token, sender, amount):
550
# Test implementation
551
pass
552
```
553
554
### Stateful Testing
555
556
```python
557
from brownie.test import state_machine, given, strategy
558
from brownie import accounts
559
import hypothesis.strategies as st
560
561
@state_machine()
562
class TokenStateMachine:
563
"""Stateful testing for complex token interactions."""
564
565
def __init__(self):
566
self.token = Token.deploy("Test", "TST", 18, 1000000, {'from': accounts[0]})
567
self.balances = {accounts[0].address: 1000000}
568
569
@given(
570
recipient=strategy('address'),
571
amount=st.integers(min_value=1, max_value=1000)
572
)
573
def transfer(self, recipient, amount):
574
"""State transition: transfer tokens."""
575
sender = accounts[0]
576
577
if self.balances[sender.address] >= amount:
578
# Execute transfer
579
self.token.transfer(recipient, amount, {'from': sender})
580
581
# Update internal state
582
self.balances[sender.address] -= amount
583
self.balances[recipient] = self.balances.get(recipient, 0) + amount
584
585
# Verify contract state matches internal state
586
assert self.token.balanceOf(sender) == self.balances[sender.address]
587
assert self.token.balanceOf(recipient) == self.balances[recipient]
588
589
@given(amount=st.integers(min_value=1, max_value=1000))
590
def mint(self, amount):
591
"""State transition: mint new tokens."""
592
recipient = accounts[1]
593
594
self.token.mint(recipient, amount, {'from': accounts[0]})
595
596
# Update internal state
597
self.balances[recipient] = self.balances.get(recipient, 0) + amount
598
599
# Verify state
600
assert self.token.balanceOf(recipient) == self.balances[recipient]
601
602
# Run stateful tests
603
TestToken = TokenStateMachine.TestCase
604
```
605
606
### Error Testing
607
608
```python
609
from brownie import reverts, VirtualMachineError
610
611
def test_revert_conditions(token):
612
"""Test various revert conditions."""
613
sender = accounts[0]
614
recipient = accounts[1]
615
616
# Test with specific revert message
617
with reverts("ERC20: transfer amount exceeds balance"):
618
token.transfer(recipient, token.totalSupply() + 1, {'from': sender})
619
620
# Test any revert
621
with reverts():
622
token.transfer("0x0000000000000000000000000000000000000000", 1, {'from': sender})
623
624
# Test custom error types
625
with reverts(VirtualMachineError):
626
token.riskyFunction({'from': sender})
627
628
# Allow reverted transactions for analysis
629
tx = token.transfer(recipient, token.totalSupply() + 1,
630
{'from': sender, 'allow_revert': True})
631
632
assert tx.status == 0 # Transaction reverted
633
print(f"Revert reason: {tx.revert_msg}")
634
```
635
636
## Type Definitions
637
638
```python { .api }
639
# Type aliases for conversion and testing
640
ConvertibleValue = Union[int, float, str, bytes]
641
EthereumAddress = Union[str, EthAddress]
642
SolidityType = str # e.g., "uint256", "bytes32", "address"
643
TestStrategy = Callable[..., Any]
644
StateMachine = Callable[..., Any]
645
RevertContext = ContextManager[None]
646
```