commit 4fdfc132b11a4656f138e0d5d51f692a3d85ce48
parent 5af30d5955be0ae8be0ddbcc5a1d0043a45f6dc5
Author: lash <dev@holbrook.no>
Date: Tue, 28 Mar 2023 14:27:44 +0100
Implement EIP712Domain, hashstruct, Mail example
Diffstat:
7 files changed, 181 insertions(+), 0 deletions(-)
diff --git a/python/eth_erc721/__init__.py b/python/eth_erc721/__init__.py
@@ -0,0 +1 @@
+from .base import ERC712Encoder, EIP712Domain
diff --git a/python/eth_erc721/base.py b/python/eth_erc721/base.py
@@ -0,0 +1,77 @@
+# standard imports
+import logging
+import sha3
+
+# external imports
+from chainlib.eth.contract import ABIContractType
+from chainlib.eth.contract import ABIContractEncoder
+from chainlib.hash import keccak256
+from chainlib.hash import keccak256_string_to_hex
+
+logg = logging.getLogger(__name__)
+
+
+class ERC712Encoder(ABIContractEncoder):
+
+ def __init__(self, struct_name):
+ super(ERC712Encoder, self).__init__()
+ self.method(struct_name)
+ self.ks = []
+ self.encode = self.get_contents
+
+
+ def add(self, k, t, v):
+ typ_checked = ABIContractType(t.value)
+ self.typ_literal(t.value + ' ' + k)
+ m = getattr(self, t.value)
+ m(v)
+
+
+ def string(self, s):
+ self.types.append(ABIContractType.STRING)
+ v = keccak256_string_to_hex(s)
+ self.contents.append(v)
+ self.__log_latest_erc712(s)
+
+
+ def bytes(self, s):
+ self.types.append(ABIContractType.BYTES)
+ v = keccak256_string_to_hex(s)
+ self.contents.append(v)
+ self.__log_latest_erc712(s)
+
+
+ def __log_latest_erc712(self, v):
+ l = len(self.types) - 1
+ logg.debug('Encoder added {} -> {} ({})'.format(v, self.contents[l], self.types[l].value))
+
+
+ def encode_type(self):
+ v = self.get_method()
+ r = keccak256(v)
+ logg.debug('typehash material {} -> {}'.format(v, r.hex()))
+ return r
+
+
+ def encode_data(self):
+ return b''.join(list(map(lambda x: bytes.fromhex(x), self.contents)))
+
+
+ def get_contents(self):
+ return b'\x19\x01' + self.encode_type() + self.encode_data()
+
+
+class EIP712Domain(ERC712Encoder):
+
+ def __init__(self, name=None, version=None, chain_id=None, verifying_contract=None, salt=None):
+ super(EIP712Domain, self).__init__('EIP712Domain')
+ if name != None:
+ self.add('name', ABIContractType.STRING, name)
+ if version != None:
+ self.add('version', ABIContractType.STRING, version)
+ if chain_id != None:
+ self.add('chainId', ABIContractType.UINT256, chain_id)
+ if verifying_contract != None:
+ self.add('verifyingContract', ABIContractType.ADDRESS, verifying_contract)
+ if salt != None:
+ self.add('salt', ABIContractType.BYTES32, salt)
diff --git a/python/eth_erc721/unittest/__init__.py b/python/eth_erc721/unittest/__init__.py
@@ -0,0 +1 @@
+from .base import TestERC712
diff --git a/python/eth_erc721/unittest/base.py b/python/eth_erc721/unittest/base.py
@@ -0,0 +1,23 @@
+# standard imports
+import os
+
+# external imports
+from chainlib.eth.unittest.ethtester import EthTesterCase
+from chainlib.eth.address import to_checksum_address
+
+# local imports
+from eth_erc721 import EIP712Domain
+
+
+class TestERC712(EthTesterCase):
+
+ def setUp(self):
+ address = os.urandom(20).hex()
+ salt = os.urandom(32).hex()
+ self.struct = EIP712Domain(
+ name='Ether Mail',
+ version='1',
+ chain_id=42,
+ verifying_contract=to_checksum_address('0xcccccccccccccccccccccccccccccccccccccccc'),
+ salt=salt,
+ )
diff --git a/python/requirements.txt b/python/requirements.txt
@@ -0,0 +1,2 @@
+chainlib-eth==0.4.17
+pysha3==1.0.2
diff --git a/python/test_requirements.txt b/python/test_requirements.txt
@@ -0,0 +1,5 @@
+eth_tester==0.5.0b3
+py-evm==0.3.0a20
+rlp==2.0.1
+pytest==6.0.1
+coverage==5.5
diff --git a/python/tests/test_basic.py b/python/tests/test_basic.py
@@ -0,0 +1,72 @@
+# standard imports
+import unittest
+import logging
+import os
+
+# external imports
+from chainlib.eth.contract import ABIContractType
+
+# local imports
+from eth_erc721.unittest import TestERC712 as TestERC712Base
+from eth_erc721 import ERC712Encoder
+
+logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
+
+class ExamplePerson(ERC712Encoder):
+
+ def __init__(self, name, wallet):
+ super(ExamplePerson, self).__init__('Person')
+ self.add('name', ABIContractType.STRING, name)
+ self.add('wallet', ABIContractType.ADDRESS, wallet)
+
+
+class ExampleMail(ERC712Encoder):
+
+ def __init__(self, from_name, from_wallet, to_name, to_wallet, contents):
+ self.pfrom = ExamplePerson(from_name, from_wallet)
+ self.pto = ExamplePerson(to_name, to_wallet)
+ super(ExampleMail, self).__init__('Mail')
+ self.typ_literal('Person from')
+ self.typ_literal('Person to')
+ self.add('contents', ABIContractType.STRING, contents)
+
+
+ # In general implementation, remember to sort structs alphabetically
+ def get_method(self):
+ typ = super(ExampleMail, self).get_method()
+ typ += self.pto.get_method()
+ logg.debug('Method is composite type: ' + typ)
+ return typ
+
+
+ def encode_data(self):
+ content = super(ExampleMail, self).encode_data()
+ from_content = self.pfrom.encode_data()
+ to_content = self.pto.encode_data()
+ return from_content + to_content + content
+
+
+class TestERC712(TestERC712Base):
+
+ def test_domain_separator(self):
+ r = self.struct.encode_type()
+ self.assertEqual(r, bytes.fromhex('d87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472'))
+
+
+ def test_domain_data(self):
+ r = self.struct.get()
+ print(r.hex())
+
+
+ def test_mail(self):
+ a = os.urandom(20).hex()
+ b = os.urandom(20).hex()
+ o = ExampleMail('Pinky Inky', a, 'Clyde Blinky', b, 'barbarbar')
+ r = o.get()
+ print(r.hex())
+
+
+if __name__ == '__main__':
+ unittest.main()