Package: eip712-structs
EIP-712 StructsA python interface for simple EIP-712 struct construction.In this module, a "struct" is structured data as defined in the standard.
It is not the same as the Python Standard Library's struct (e.g.,import struct).Read the proposal:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.mdSupported Python Versions3.63.7Installpipinstalleip712-structsUsageSeeAPI.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:struct MyStruct {
    string some_string;
    uint256 some_number;
}With this module, that would look like:# Make a unique domainfromeip712_structsimportmake_domaindomain=make_domain(name='Some name',version='1.0.0')# Make a Domain Separator# Define your struct typefromeip712_structsimportEIP712Struct,String,UintclassMyStruct(EIP712Struct):some_string=String()some_number=Uint(256)# Create an instance with some datamine=MyStruct(some_string='hello world',some_number=1234)# Into a message dict (serializable to JSON) - domain requiredmy_msg=mine.to_message(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())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)# ...etcStruct referencesIn addition to holding basic types, EIP712 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.Install dependencies:pip install -r requirements.txtRun tests:python setup.py testSome tests expect an active local ganache chain onhttp://localhost:8545. Docker will compile the contracts and start the chain for you.Docker is optional, but useful to test the whole suite. If no chain is detected, chain tests are skipped.Usage:docker-compose up -d(Starts containers in the background)Note: Contracts are compiled when you runup, but won't be deployed until the test is run.Cleanup containers when you're done:docker-compose downDeploying a new version:Bump the version number insetup.py, commit it into master.Make a release tag on the master branch in Github. Travis should handle the rest.Shameless PlugWritten byConsenSysfor ourselves and the community! :heart: