Skip to content

Commit 4db667d

Browse files
committed
Add default domain option - constantly providing a domain is annoying
1 parent 7bfee7e commit 4db667d

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed

eip712_structs/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44

55
name = 'eip712-structs'
66
version = '1.0.0'
7+
8+
9+
default_domain = None

eip712_structs/struct.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from eth_utils.crypto import keccak
66

7+
import eip712_structs
78
from eip712_structs.types import Array, EIP712Type, from_solidity_type
89

910

@@ -137,7 +138,14 @@ def get_members(cls) -> List[Tuple[str, EIP712Type]]:
137138
or (isinstance(m[1], type) and issubclass(m[1], EIP712Struct))]
138139
return members
139140

140-
def to_message(self, domain: 'EIP712Struct') -> dict:
141+
@staticmethod
142+
def _assert_domain(domain):
143+
result = domain or eip712_structs.default_domain
144+
if not result:
145+
raise ValueError('Domain must be provided, or eip712_structs.default_domain must be set.')
146+
return result
147+
148+
def to_message(self, domain: 'EIP712Struct' = None) -> dict:
141149
"""Convert a struct into a dictionary suitable for messaging.
142150
143151
Dictionary is of the form:
@@ -150,6 +158,7 @@ def to_message(self, domain: 'EIP712Struct') -> dict:
150158
151159
:returns: This struct + the domain in dict form, structured as specified for EIP712 messages.
152160
"""
161+
domain = self._assert_domain(domain)
153162
structs = {domain, self}
154163
self._gather_reference_structs(structs)
155164

@@ -171,15 +180,16 @@ def to_message(self, domain: 'EIP712Struct') -> dict:
171180

172181
return result
173182

174-
def signable_bytes(self, domain: 'EIP712Struct') -> bytes:
183+
def signable_bytes(self, domain: 'EIP712Struct' = None) -> bytes:
175184
"""Return a ``bytes`` object suitable for signing, as specified for EIP712.
176185
177186
As per the spec, bytes are constructed as follows:
178187
``b'\x19\x01' + domain_hash_bytes + struct_hash_bytes``
179188
180-
:param domain: The domain to include in the hash bytes
189+
:param domain: The domain to include in the hash bytes. If None, uses ``eip712_structs.default_domain``
181190
:return: The bytes object
182191
"""
192+
domain = self._assert_domain(domain)
183193
result = b'\x19\x01' + domain.hash_struct() + self.hash_struct()
184194
return result
185195

tests/test_domain_separator.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@
33
import pytest
44
from eth_utils.crypto import keccak
55

6-
from eip712_structs import make_domain
6+
import eip712_structs
7+
from eip712_structs import make_domain, EIP712Struct, String
8+
9+
10+
@pytest.fixture
11+
def default_domain_manager():
12+
# This fixture can be called to ensure we cleanup our default domain var before/after tests
13+
current_value = eip712_structs.default_domain
14+
eip712_structs.default_domain = None
15+
yield
16+
eip712_structs.default_domain = current_value
717

818

919
def test_domain_sep_create():
@@ -35,3 +45,37 @@ def test_domain_sep_types():
3545

3646
expected_data = b''.join(encoded_data)
3747
assert domain_struct.encode_value() == expected_data
48+
49+
50+
def test_default_domain(default_domain_manager):
51+
assert eip712_structs.default_domain is None
52+
53+
class Foo(EIP712Struct):
54+
s = String()
55+
foo = Foo(s='hello world')
56+
57+
domain = make_domain(name='domain')
58+
other_domain = make_domain(name='other domain')
59+
60+
# When neither methods provide a domain, expect a ValueError
61+
with pytest.raises(ValueError, match='Domain must be provided'):
62+
foo.to_message()
63+
with pytest.raises(ValueError, match='Domain must be provided'):
64+
foo.signable_bytes()
65+
66+
# But we can still provide a domain explicitly
67+
explicit_msg = foo.to_message(domain)
68+
explicit_bytes = foo.signable_bytes(domain)
69+
70+
# Setting it lets us forgo providing it
71+
eip712_structs.default_domain = domain
72+
implicit_msg = foo.to_message()
73+
implicit_bytes = foo.signable_bytes()
74+
75+
# Either method should produce the same result
76+
assert implicit_msg == explicit_msg
77+
assert implicit_bytes == explicit_bytes
78+
79+
# Using a different domain should not use any current default domain
80+
assert implicit_msg != foo.to_message(other_domain)
81+
assert implicit_bytes != foo.signable_bytes(other_domain)

0 commit comments

Comments
 (0)