1+ import re
12from collections import OrderedDict , defaultdict
23from typing import List , Tuple
34
45from eth_utils .crypto import keccak
56
6- from eip712_structs .types import EIP712Type , from_solidity_type
7+ from eip712_structs .types import Array , EIP712Type , from_solidity_type
78
89
910class OrderedAttributesMeta (type ):
@@ -25,6 +26,16 @@ def __init_subclass__(cls, **kwargs):
2526
2627
2728class EIP712Struct (_EIP712StructTypeHelper ):
29+ """A representation of an EIP712 struct. Subclass it to use it.
30+
31+ Example:
32+ from eip712_structs import EIP712Struct, String
33+
34+ class MyStruct(EIP712Struct):
35+ some_param = String()
36+
37+ struct_instance = MyStruct(some_param='some_value')
38+ """
2839 def __init__ (self , ** kwargs ):
2940 super (EIP712Struct , self ).__init__ (self .type_name )
3041 members = self .get_members ()
@@ -36,20 +47,32 @@ def __init__(self, **kwargs):
3647 self .values [name ] = value
3748
3849 def encode_value (self , value = None ):
50+ """Returns the struct's encoded value.
51+
52+ A struct's encoded value is a concatenation of the bytes32 representation of each member of the struct.
53+ Order is preserved.
54+
55+ :param value: This parameter is not used for structs.
56+ """
3957 encoded_values = [typ .encode_value (self .values [name ]) for name , typ in self .get_members ()]
4058 return b'' .join (encoded_values )
4159
42- def encode_data (self ):
43- return self .encode_value ()
44-
4560 def get_data_value (self , name ):
61+ """Get the value of the given struct parameter.
62+ """
4663 return self .values .get (name )
4764
4865 def set_data_value (self , name , value ):
66+ """Set the value of the given struct parameter.
67+ """
4968 if name in self .values :
5069 self .values [name ] = value
5170
5271 def data_dict (self ):
72+ """Provide the entire data dictionary representing the struct.
73+
74+ Nested structs instances are also converted to dict form.
75+ """
5376 result = dict ()
5477 for k , v in self .values .items ():
5578 if isinstance (v , EIP712Struct ):
@@ -81,23 +104,51 @@ def _gather_reference_structs(cls, struct_set):
81104
82105 @classmethod
83106 def encode_type (cls ):
107+ """Get the encoded type signature of the struct.
108+
109+ Nested structs are also encoded, and appended in alphabetical order.
110+ """
84111 return cls ._encode_type (True )
85112
86113 @classmethod
87114 def type_hash (cls ):
115+ """Get the keccak hash of the struct's encoded type."""
88116 return keccak (text = cls .encode_type ())
89117
90118 def hash_struct (self ):
119+ """The hash of the struct.
120+
121+ hash_struct => keccak(type_hash || encode_data)
122+ """
91123 return keccak (b'' .join ([self .type_hash (), self .encode_data ()]))
92124
93125 @classmethod
94126 def get_members (cls ) -> List [Tuple [str , EIP712Type ]]:
127+ """A list of tuples of supported parameters.
128+
129+ Each tuple is (<parameter_name>, <parameter_type>). The list's order is determined by definition order.
130+ """
95131 members = [m for m in cls .__dict__ .items () if isinstance (m [1 ], EIP712Type )
96132 or (isinstance (m [1 ], type ) and issubclass (m [1 ], EIP712Struct ))]
97133 return members
98134
99135
100- def struct_to_json (domain : EIP712Struct , primary_struct : EIP712Struct ):
136+ def struct_to_dict (primary_struct : EIP712Struct , domain : EIP712Struct ):
137+ """Convert a struct into a dictionary suitable for messaging.
138+
139+ Dictionary is of the form:
140+ {
141+ 'primaryType': Name of the primary type,
142+ 'types': Definition of each included struct type (including the domain type)
143+ 'domain': Values for the domain struct,
144+ 'message': Values for the message struct,
145+ }
146+
147+ The hash is constructed as:
148+ `` b'\\ x19\\ x01' + domain_type_hash + struct_type_hash ``
149+
150+ :returns: A tuple in the form of: (message_dict, encoded_message_hash>)
151+ """
101152 structs = {domain , primary_struct }
102153 primary_struct ._gather_reference_structs (structs )
103154
@@ -122,29 +173,46 @@ def struct_to_json(domain: EIP712Struct, primary_struct: EIP712Struct):
122173 return result , typed_data_hash
123174
124175
125- def struct_from_json (json ):
176+ def struct_from_dict (message_dict ):
177+ """Return the EIP712Struct object of the message and domain structs.
178+
179+ :returns: A tuple in the form of: (<primary struct>, <domain struct>)
180+ """
126181 structs = dict ()
127182 unfulfilled_struct_params = defaultdict (list )
128183
129- for type_name in json ['types' ]:
130- # Dynamically construct struct class from JSON
184+ for type_name in message_dict ['types' ]:
185+ # Dynamically construct struct class from dict representation
131186 StructFromJSON = type (type_name , (EIP712Struct ,), {})
132187
133- for member in json ['types' ][type_name ]:
188+ for member in message_dict ['types' ][type_name ]:
134189 # Either a basic solidity type is set, or None if referring to a reference struct (we'll fill that later)
135190 member_name = member ['name' ]
136191 member_sol_type = from_solidity_type (member ['type' ])
137192 setattr (StructFromJSON , member_name , member_sol_type )
138193 if member_sol_type is None :
194+ # Track the refs we'll need to set later.
139195 unfulfilled_struct_params [type_name ].append ((member_name , member ['type' ]))
140196
141197 structs [type_name ] = StructFromJSON
142198
199+ # Now that custom structs have been parsed, pass through again to set the references
143200 for struct_name , unfulfilled_member_names in unfulfilled_struct_params .items ():
201+ regex_pattern = r'([a-zA-Z0-9_]+)(\[(\d+)?\])?'
202+
144203 struct_class = structs [struct_name ]
145204 for name , type_name in unfulfilled_member_names :
146- ref_struct = structs [type_name ]
147- setattr (struct_class , name , ref_struct )
205+ match = re .match (regex_pattern , type_name )
206+ base_type_name = match .group (1 )
207+ ref_struct = structs [base_type_name ]
208+ if match .group (2 ):
209+ # The type is an array of the struct
210+ arr_len = match .group (3 ) or 0 # length of 0 means the array is dynamically sized
211+ setattr (struct_class , name , Array (ref_struct , arr_len ))
212+ else :
213+ setattr (struct_class , name , ref_struct )
214+
215+ primary_struct = structs [message_dict ['primaryType' ]]
216+ domain_struct = structs ['EIP712Domain' ]
148217
149- primary_struct = structs [json ['primaryType' ]]
150- return primary_struct (** json ['message' ])
218+ return primary_struct (** message_dict ['message' ]), domain_struct (** message_dict ['domain' ])
0 commit comments