7
7
8
8
class EIP712Type :
9
9
"""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.
10
12
"""
11
13
def __init__ (self , type_name : str , none_val : Any ):
12
14
self .type_name = type_name
13
15
self .none_val = none_val
14
16
15
17
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
+ """
16
23
if value is None :
17
24
return self ._encode_value (self .none_val )
18
25
else :
19
26
return self ._encode_value (value )
20
27
21
28
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 .
23
30
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.
26
32
"""
27
33
pass
28
34
@@ -38,6 +44,13 @@ def __hash__(self):
38
44
39
45
class Array (EIP712Type ):
40
46
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
+ """
41
54
fixed_length = int (fixed_length )
42
55
if fixed_length == 0 :
43
56
type_name = f'{ member_type .type_name } []'
@@ -48,31 +61,37 @@ def __init__(self, member_type: Union[EIP712Type, Type[EIP712Type]], fixed_lengt
48
61
super (Array , self ).__init__ (type_name , [])
49
62
50
63
def _encode_value (self , value ):
64
+ """Arrays are encoded by concatenating their encoded contents, and taking the keccak256 hash."""
51
65
encoder = self .member_type
52
66
encoded_values = [encoder .encode_value (v ) for v in value ]
53
67
return keccak (b'' .join (encoded_values ))
54
68
55
69
56
70
class Address (EIP712Type ):
57
71
def __init__ (self ):
72
+ """Represents an ``address`` type."""
58
73
super (Address , self ).__init__ ('address' , 0 )
59
74
60
75
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
62
79
if isinstance (value , bytes ):
63
80
v = to_int (value )
64
81
elif isinstance (value , str ):
65
82
v = to_int (hexstr = value )
66
83
else :
67
- v = value
84
+ v = value # Fallback, just use it as-is.
68
85
return Uint (160 ).encode_value (v )
69
86
70
87
71
88
class Boolean (EIP712Type ):
72
89
def __init__ (self ):
90
+ """Represents a ``bool`` type."""
73
91
super (Boolean , self ).__init__ ('bool' , False )
74
92
75
93
def _encode_value (self , value ):
94
+ """Booleans are encoded like the uint256 values of 0 and 1."""
76
95
if value is False :
77
96
return Uint (256 ).encode_value (0 )
78
97
elif value is True :
@@ -83,6 +102,15 @@ def _encode_value(self, value):
83
102
84
103
class Bytes (EIP712Type ):
85
104
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
+ """
86
114
length = int (length )
87
115
if length == 0 :
88
116
# Special case: Length of 0 means a dynamic bytes type
@@ -95,6 +123,7 @@ def __init__(self, length: int = 0):
95
123
super (Bytes , self ).__init__ (type_name , b'' )
96
124
97
125
def _encode_value (self , value ):
126
+ """Static bytesN types are encoded by right-padding to 32 bytes. Dynamic bytes types are keccak256 hashed."""
98
127
if self .length == 0 :
99
128
return keccak (value )
100
129
else :
@@ -105,39 +134,58 @@ def _encode_value(self, value):
105
134
106
135
107
136
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
+ """
109
145
length = int (length )
110
146
if length < 8 or length > 256 or length % 8 != 0 :
111
147
raise ValueError (f'Int length must be a multiple of 8, between 8 and 256. Got: { length } ' )
112
148
self .length = length
113
149
super (Int , self ).__init__ (f'int{ length } ' , 0 )
114
150
115
151
def _encode_value (self , value : int ):
152
+ """Ints are encoded by padding them to 256-bit representations."""
116
153
value .to_bytes (self .length // 8 , byteorder = 'big' , signed = True ) # For validation
117
154
return value .to_bytes (32 , byteorder = 'big' , signed = True )
118
155
119
156
120
157
class String (EIP712Type ):
121
158
def __init__ (self ):
159
+ """Represents a string type."""
122
160
super (String , self ).__init__ ('string' , '' )
123
161
124
162
def _encode_value (self , value ):
163
+ """Strings are encoded by taking the keccak256 hash of their contents."""
125
164
return keccak (text = value )
126
165
127
166
128
167
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
+ """
130
176
length = int (length )
131
177
if length < 8 or length > 256 or length % 8 != 0 :
132
178
raise ValueError (f'Uint length must be a multiple of 8, between 8 and 256. Got: { length } ' )
133
179
self .length = length
134
180
super (Uint , self ).__init__ (f'uint{ length } ' , 0 )
135
181
136
182
def _encode_value (self , value : int ):
183
+ """Uints are encoded by padding them to 256-bit representations."""
137
184
value .to_bytes (self .length // 8 , byteorder = 'big' , signed = False ) # For validation
138
185
return value .to_bytes (32 , byteorder = 'big' , signed = False )
139
186
140
187
188
+ # This helper dict maps solidity's type names to our EIP712Type classes
141
189
solidity_type_map = {
142
190
'address' : Address ,
143
191
'bool' : Boolean ,
@@ -156,21 +204,24 @@ def from_solidity_type(solidity_type: str):
156
204
if match is None :
157
205
return None
158
206
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
163
211
164
212
if type_name not in solidity_type_map :
213
+ # Only supporting basic types here - return None if we don't recognize it.
165
214
return None
166
215
216
+ # Construct the basic type
167
217
base_type = solidity_type_map [type_name ]
168
218
if opt_len :
169
219
type_instance = base_type (int (opt_len ))
170
220
else :
171
221
type_instance = base_type ()
172
222
173
223
if is_array :
224
+ # Nest the aforementioned basic type into an Array.
174
225
if array_len :
175
226
result = Array (type_instance , int (array_len ))
176
227
else :
0 commit comments