Skip to content

Commit 5ce0261

Browse files
authored
Merge pull request #8 from ajrgrubbs/perfectionist-cleanup
Improve docs for 1.0.0 release.
2 parents a6aa5be + 6320aca commit 5ce0261

File tree

3 files changed

+83
-19
lines changed

3 files changed

+83
-19
lines changed

README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ Say we want to represent the following struct, convert it to a message and sign
2222
```text
2323
struct MyStruct {
2424
string some_string;
25-
uint some_number;
25+
uint256 some_number;
2626
}
2727
```
2828

29-
With this module:
29+
With this module, that would look like:
3030
```python
3131
# Make a unique domain
3232
from eip712_structs import make_domain
33-
domain = make_domain(name='Some name', version='1.0.0')
33+
domain = make_domain(name='Some name', version='1.0.0') # Make a Domain Separator
3434

3535
# Define your struct type
3636
from eip712_structs import EIP712Struct, String, Uint
@@ -175,10 +175,21 @@ struct_array = Array(MyStruct, 10) # MyStruct[10] - again, don't instantiate s
175175
Contributions always welcome.
176176

177177
Install dependencies:
178-
- `pip install -r requirements.txt && pip install -r test_requirements.txt`
178+
- `pip install -r requirements.txt -r test_requirements.txt`
179179

180180
Run tests:
181181
- `python setup.py test`
182-
- Some tests expect an active local ganache chain. Compile contracts and start the chain using docker:
183-
- `docker-compose up -d`
184-
- If the chain is not detected, then they are skipped.
182+
- Some tests expect an active local ganache chain on http://localhost:8545. Docker will compile the contracts and start the chain for you.
183+
- Docker is optional, but useful to test the whole suite. If no chain is detected, chain tests are skipped.
184+
- Usage:
185+
- `docker-compose up -d` (Starts containers in the background)
186+
- Note: Contracts are compiled when you run `up`, but won't be deployed until the test is run.
187+
- Cleanup containers when you're done: `docker-compose down`
188+
189+
Deploying a new version:
190+
- Set the version number in `eip712_structs/__init__.py`
191+
- Make a release tag on the master branch in Github. Travis should handle the rest.
192+
193+
194+
## Shameless Plug
195+
Written by [ConsenSys](https://consensys.net) for ourselves and the community! :heart:

eip712_structs/types.py

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,28 @@
77

88
class EIP712Type:
99
"""The base type for members of a struct.
10+
11+
Generally you wouldn't use this - instead, see the subclasses below. Or you may want an EIP712Struct instead.
1012
"""
1113
def __init__(self, type_name: str, none_val: Any):
1214
self.type_name = type_name
1315
self.none_val = none_val
1416

1517
def encode_value(self, value) -> bytes:
18+
"""Given a value, verify it and convert into the format required by the spec.
19+
20+
:param value: A correct input value for the implemented type.
21+
:return: A 32-byte object containing encoded data
22+
"""
1623
if value is None:
1724
return self._encode_value(self.none_val)
1825
else:
1926
return self._encode_value(value)
2027

2128
def _encode_value(self, value) -> bytes:
22-
"""Given a value, verify it and convert into the format required by the spec.
29+
"""Must be implemented by subclasses, handles value encoding on a case-by-case basis.
2330
24-
:param value: A correct input value for the implemented type.
25-
:return: A 32-byte object containing encoded data
31+
Don't call this directly - use ``.encode_value(value)`` instead.
2632
"""
2733
pass
2834

@@ -38,6 +44,13 @@ def __hash__(self):
3844

3945
class Array(EIP712Type):
4046
def __init__(self, member_type: Union[EIP712Type, Type[EIP712Type]], fixed_length: int = 0):
47+
"""Represents an array member type.
48+
49+
Example:
50+
a1 = Array(String()) # string[] a1
51+
a2 = Array(String(), 8) # string[8] a2
52+
a3 = Array(MyStruct) # MyStruct[] a3
53+
"""
4154
fixed_length = int(fixed_length)
4255
if fixed_length == 0:
4356
type_name = f'{member_type.type_name}[]'
@@ -48,31 +61,37 @@ def __init__(self, member_type: Union[EIP712Type, Type[EIP712Type]], fixed_lengt
4861
super(Array, self).__init__(type_name, [])
4962

5063
def _encode_value(self, value):
64+
"""Arrays are encoded by concatenating their encoded contents, and taking the keccak256 hash."""
5165
encoder = self.member_type
5266
encoded_values = [encoder.encode_value(v) for v in value]
5367
return keccak(b''.join(encoded_values))
5468

5569

5670
class Address(EIP712Type):
5771
def __init__(self):
72+
"""Represents an ``address`` type."""
5873
super(Address, self).__init__('address', 0)
5974

6075
def _encode_value(self, value):
61-
# Some smart conversions - need to get an address as an int
76+
"""Addresses are encoded like Uint160 numbers."""
77+
78+
# Some smart conversions - need to get the address to a numeric before we encode it
6279
if isinstance(value, bytes):
6380
v = to_int(value)
6481
elif isinstance(value, str):
6582
v = to_int(hexstr=value)
6683
else:
67-
v = value
84+
v = value # Fallback, just use it as-is.
6885
return Uint(160).encode_value(v)
6986

7087

7188
class Boolean(EIP712Type):
7289
def __init__(self):
90+
"""Represents a ``bool`` type."""
7391
super(Boolean, self).__init__('bool', False)
7492

7593
def _encode_value(self, value):
94+
"""Booleans are encoded like the uint256 values of 0 and 1."""
7695
if value is False:
7796
return Uint(256).encode_value(0)
7897
elif value is True:
@@ -83,6 +102,15 @@ def _encode_value(self, value):
83102

84103
class Bytes(EIP712Type):
85104
def __init__(self, length: int = 0):
105+
"""Represents a solidity bytes type.
106+
107+
Length may be used to specify a static ``bytesN`` type. Or 0 for a dynamic ``bytes`` type.
108+
Example:
109+
b1 = Bytes() # bytes b1
110+
b2 = Bytes(10) # bytes10 b2
111+
112+
``length`` MUST be between 0 and 32, or a ValueError is raised.
113+
"""
86114
length = int(length)
87115
if length == 0:
88116
# Special case: Length of 0 means a dynamic bytes type
@@ -95,6 +123,7 @@ def __init__(self, length: int = 0):
95123
super(Bytes, self).__init__(type_name, b'')
96124

97125
def _encode_value(self, value):
126+
"""Static bytesN types are encoded by right-padding to 32 bytes. Dynamic bytes types are keccak256 hashed."""
98127
if self.length == 0:
99128
return keccak(value)
100129
else:
@@ -105,39 +134,58 @@ def _encode_value(self, value):
105134

106135

107136
class Int(EIP712Type):
108-
def __init__(self, length: int):
137+
def __init__(self, length: int = 256):
138+
"""Represents a signed int type. Length may be given to specify the int length in bits. Default length is 256
139+
140+
Example:
141+
i1 = Int(256) # int256 i1
142+
i2 = Int() # int256 i2
143+
i3 = Int(128) # int128 i3
144+
"""
109145
length = int(length)
110146
if length < 8 or length > 256 or length % 8 != 0:
111147
raise ValueError(f'Int length must be a multiple of 8, between 8 and 256. Got: {length}')
112148
self.length = length
113149
super(Int, self).__init__(f'int{length}', 0)
114150

115151
def _encode_value(self, value: int):
152+
"""Ints are encoded by padding them to 256-bit representations."""
116153
value.to_bytes(self.length // 8, byteorder='big', signed=True) # For validation
117154
return value.to_bytes(32, byteorder='big', signed=True)
118155

119156

120157
class String(EIP712Type):
121158
def __init__(self):
159+
"""Represents a string type."""
122160
super(String, self).__init__('string', '')
123161

124162
def _encode_value(self, value):
163+
"""Strings are encoded by taking the keccak256 hash of their contents."""
125164
return keccak(text=value)
126165

127166

128167
class Uint(EIP712Type):
129-
def __init__(self, length: int):
168+
def __init__(self, length: int = 256):
169+
"""Represents an unsigned int type. Length may be given to specify the int length in bits. Default length is 256
170+
171+
Example:
172+
ui1 = Uint(256) # uint256 ui1
173+
ui2 = Uint() # uint256 ui2
174+
ui3 = Uint(128) # uint128 ui3
175+
"""
130176
length = int(length)
131177
if length < 8 or length > 256 or length % 8 != 0:
132178
raise ValueError(f'Uint length must be a multiple of 8, between 8 and 256. Got: {length}')
133179
self.length = length
134180
super(Uint, self).__init__(f'uint{length}', 0)
135181

136182
def _encode_value(self, value: int):
183+
"""Uints are encoded by padding them to 256-bit representations."""
137184
value.to_bytes(self.length // 8, byteorder='big', signed=False) # For validation
138185
return value.to_bytes(32, byteorder='big', signed=False)
139186

140187

188+
# This helper dict maps solidity's type names to our EIP712Type classes
141189
solidity_type_map = {
142190
'address': Address,
143191
'bool': Boolean,
@@ -156,21 +204,24 @@ def from_solidity_type(solidity_type: str):
156204
if match is None:
157205
return None
158206

159-
type_name = match.group(1)
160-
opt_len = match.group(2)
161-
is_array = match.group(3)
162-
array_len = match.group(4)
207+
type_name = match.group(1) # The type name, like the "bytes" in "bytes32"
208+
opt_len = match.group(2) # An optional length spec, like the "32" in "bytes32"
209+
is_array = match.group(3) # Basically just checks for square brackets
210+
array_len = match.group(4) # For fixed length arrays only, this is the length
163211

164212
if type_name not in solidity_type_map:
213+
# Only supporting basic types here - return None if we don't recognize it.
165214
return None
166215

216+
# Construct the basic type
167217
base_type = solidity_type_map[type_name]
168218
if opt_len:
169219
type_instance = base_type(int(opt_len))
170220
else:
171221
type_instance = base_type()
172222

173223
if is_array:
224+
# Nest the aforementioned basic type into an Array.
174225
if array_len:
175226
result = Array(type_instance, int(array_len))
176227
else:

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ def run_tests(self):
5353
setup(
5454
name=name,
5555
version=version,
56+
author='AJ Grubbs',
5657
packages=find_packages(),
5758
install_requires=install_requirements,
5859
tests_require=test_requirements,
5960
cmdclass={
6061
"test": PyTest,
6162
"coveralls": CoverallsCommand,
6263
},
64+
description='A python library for EIP712 objects',
6365
long_description=long_description,
6466
long_description_content_type='text/markdown',
6567
license='MIT',
66-
keywords='ethereum eip712',
68+
keywords='ethereum eip712 solidity',
6769
url='https://github.com/ajrgrubbs/py-eip712-structs',
6870
)

0 commit comments

Comments
 (0)