eth-erc712

ERC712 typed data sign material builder
Log | Files | Refs

commit 4fdfc132b11a4656f138e0d5d51f692a3d85ce48
parent 5af30d5955be0ae8be0ddbcc5a1d0043a45f6dc5
Author: lash <dev@holbrook.no>
Date:   Tue, 28 Mar 2023 14:27:44 +0100

Implement EIP712Domain, hashstruct, Mail example

Diffstat:
Apython/eth_erc721/__init__.py | 1+
Apython/eth_erc721/base.py | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apython/eth_erc721/unittest/__init__.py | 1+
Apython/eth_erc721/unittest/base.py | 23+++++++++++++++++++++++
Apython/requirements.txt | 2++
Apython/test_requirements.txt | 5+++++
Apython/tests/test_basic.py | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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()