Package: eip712-structs-ng
EIP-712 StructsA Python interface forEIP-712struct construction.Note: this is adrop-in replacementfor theeip712-structsmodule, which is dead since 2019.It brings the same objects as its predecessor with some sugar:the code is fully typeddependencies were cleaned upsupport for Python 3.8 and newercode modernization, including Sourcery clean-up99% tests coveragesimplified testing (no more need for docker)Warning: Remove any installation of the oldeip712-structsmodule to prevent import mismatches:python -m pip uninstall -y eip712-structsIn this module, a "struct" is structured data as defined in the standard.
It is not the same as the Python standard library'sstruct.Supported Python VersionsPython 3.8 and newer.Installpython-mpipinstall-Ueip712-structs-ngUsageSeeAPI.mdfor a succinct summary of available methods.Examples & details below.QuickstartSay we want to represent the following struct, convert it to a message and sign it:structMyStruct{stringsome_string;uint256some_number;}With this module, that would look like:fromeip712_structsimportmake_domain,EIP712Struct,String,Uint# Make a domain separatordomain=make_domain(name='Some name',version='1.0.0')# Define your struct typeclassMyStruct(EIP712Struct):some_string=String()some_number=Uint(256)# Create an instance with some datamine=MyStruct(some_string="hello world",some_number=1234)# Values can be get/set dictionary-style:mine["some_number"]=4567assertmine["some_string"]=="hello world"assertmine["some_number"]==4567# Into a message dict - domain requiredmy_msg=mine.to_message(domain)# Into message JSON - domain required.# This method converts bytes types for you, which the default JSON encoder won't handle.my_msg_json=mine.to_message_json(domain)# Into signable bytes - domain requiredmy_bytes=mine.signable_bytes(domain)SeeMember Typesfor more information on supported types.Dynamic ConstructionAttributes may be added dynamically as well.
This may be necessary if you want to use a reserved keyword likefrom:fromeip712_structsimportEIP712Struct,AddressclassMessage(EIP712Struct):passMessage.to=Address()setattr(Message,"from",Address())# At this point, `Message` is equivalent to `struct Message { address to; address from; }`The Domain SeparatorEIP-712 specifies a domain struct, to differentiate between identical structs that may be unrelated.
A helper method exists for this purpose.
All values to themake_domain()function are optional - but at least one must be defined.
If omitted, the resulting domain struct's definition leaves out the parameter entirely.The full signature:make_domain(name:string,version:string,chainId:uint256,verifyingContract:address,salt:bytes32)Setting a Default DomainConstantly providing the same domain can be cumbersome. You can optionally set a default, and then forget it.
It is automatically used by.to_message()and.signable_bytes():importeip712_structsfoo=SomeStruct()my_domain=eip712_structs.make_domain(name="hello world")eip712_structs.default_domain=my_domainassertfoo.to_message()==foo.to_message(my_domain)assertfoo.signable_bytes()==foo.signable_bytes(my_domain)Member TypesBasic TypesEIP712's basic types map directly to solidity types:fromeip712_structsimportAddress,Boolean,Bytes,Int,String,UintAddress()# Solidity's 'address'Boolean()# 'bool'Bytes()# 'bytes'Bytes(N)# 'bytesN' - N must be an int from 1 through 32Int(N)# 'intN' - N must be a multiple of 8, from 8 to 256String()# 'string'Uint(N)# 'uintN' - N must be a multiple of 8, from 8 to 256Use like:fromeip712_structsimportEIP712Struct,Address,BytesclassFoo(EIP712Struct):member_name_0=Address()member_name_1=Bytes(5)# etc.Struct ReferencesIn addition to holding basic types, EIP-712 structs may also hold other structs!
Usage is almost the same - the difference is you don't "instantiate" the class.Example:fromeip712_structsimportEIP712Struct,StringclassDog(EIP712Struct):name=String()breed=String()classPerson(EIP712Struct):name=String()dog=Dog# Take note - no parentheses!# Dog "stands alone"Dog.encode_type()# Dog(string name,string breed)# But Person knows how to include DogPerson.encode_type()# Person(string name,Dog dog)Dog(string name,string breed)Instantiating the structs with nested values may be done a couple different ways:# Method one: set it to a structdog=Dog(name="Mochi",breed="Corgi")person=Person(name="E.M.",dog=dog)# Method two: set it to a dict - the underlying struct is built for youperson=Person(name="E.M.",dog={"name":"Mochi","breed":"Corgi",})ArraysArrays are also supported for the standard:array_member=Array(<item_type>[,<optional_length>])<item_type>- The basic type or struct that will live in the array<optional_length>- If given, the array is set to that length.For example:dynamic_array=Array(String())# String[] dynamic_arraystatic_array=Array(String(),10)# String[10] static_arraystruct_array=Array(MyStruct,10)# MyStruct[10] - again, don't instantiate structs like the basic typesDevelopmentContributions always welcome.Setup a development environment:python-mvenvvenv
.venv/bin/activateInstall dependencies:python-mpipinstall-Upip
python-mpipinstall-e'.[tests]'Run tests:python-mpytestRun linters before submitting a PR:./checks.shSolidity ContractWhen changing the code of the Solidity test contract, you will have to regenerate its Python data code:cdsrc/tests/integration/contract_sources&&./compile.shThat's it! Do not forget to commit those changes.Deploying a New VersionBump the version number in__init__.py, commit it into themainbranch.Make a release tag on themainbranch in GitHub.The CI will handle the PyPi publishing.Shameless PlugOriginally written byConsenSysfor the world! And continued byBoboTiG! :heart: