Skip to main content

A Python library for EIP712 objects.

Project description

EIP-712 Structs

A Python interface for EIP-712 struct construction.

Note: this is a drop-in replacement for the eip712-structs module, which is dead since 2019.

It brings the same objects as its predecessor with some sugar:

  • the code is fully typed
  • dependencies were cleaned up
  • support for Python 3.8 and newer
  • code modernization, including Sourcery clean-up
  • 99% tests coverage
  • simplified testing (no more need for docker)
  • fixes ConsenSysMesh/py-eip712-structs#19

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.

Supported Python Versions

Python 3.8 and newer.

Install

python -m pip install -U eip712-structs-ng

Usage

See API.md for a succinct summary of available methods.

Examples & details below.

Quickstart

Say 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:

from eip712_structs import make_domain, EIP712Struct, String, Uint


# Make a domain separator
domain = make_domain(name='Some name', version='1.0.0')

# Define your struct type
class MyStruct(EIP712Struct):
    some_string = String()
    some_number = Uint(256)

# Create an instance with some data
mine = MyStruct(some_string="hello world", some_number=1234)

# Values can be get/set dictionary-style:
mine["some_number"] = 4567
assert mine["some_string"] == "hello world"
assert mine["some_number"] == 4567

# Into a message dict - domain required
my_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 required
my_bytes = mine.signable_bytes(domain)

See Member Types for more information on supported types.

Dynamic construction

Attributes may be added dynamically as well. This may be necessary if you want to use a reserved keyword like from:

from eip712_structs import EIP712Struct, Address


class Message(EIP712Struct):
    pass

Message.to = Address()
setattr(Message, "from", Address())

# At this point, `Message` is equivalent to `struct Message { address to; address from; }`

The domain separator

EIP-712 specifies a domain struct, to differentiate between identical structs that may be unrelated. A helper method exists for this purpose. All values to the make_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 domain

Constantly 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():

import eip712_structs


foo = SomeStruct()

my_domain = eip712_structs.make_domain(name="hello world")
eip712_structs.default_domain = my_domain

assert foo.to_message() == foo.to_message(my_domain)
assert foo.signable_bytes() == foo.signable_bytes(my_domain)

Member Types

Basic types

EIP712's basic types map directly to solidity types:

from eip712_structs import Address, Boolean, Bytes, Int, String, Uint


Address()  # Solidity's 'address'
Boolean()  # 'bool'
Bytes()    # 'bytes'
Bytes(N)   # 'bytesN' - N must be an int from 1 through 32
Int(N)     # 'intN' - N must be a multiple of 8, from 8 to 256
String()   # 'string'
Uint(N)    # 'uintN' - N must be a multiple of 8, from 8 to 256

Use like:

from eip712_structs import EIP712Struct, Address, Bytes


class Foo(EIP712Struct):
    member_name_0 = Address()
    member_name_1 = Bytes(5)
    # etc.

Struct references

In 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:

from eip712_structs import EIP712Struct, String


class Dog(EIP712Struct):
    name = String()
    breed = String()

class Person(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 Dog
Person.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 struct
dog = 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 you
person = Person(
    name="E.M.",
    dog={
        "name": "Mochi",
        "breed": "Corgi",
    }
)

Arrays

Arrays 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_array
static_array  = Array(String(), 10)  # String[10] static_array
struct_array  = Array(MyStruct, 10)  # MyStruct[10] - again, don't instantiate structs like the basic types

Development

Contributions always welcome.

Setup a development environment:

python -m venv venv
. venv/bin/activate

Install dependencies:

python -m pip install -U pip
python -m pip install -e '.[tests]'

Run tests:

python -m pytest

Run linters before submitting a PR:

./checks.sh

Deploying a New Version

  • Bump the version number in __init__.py, commit it into the main branch.
  • Make a release tag on the main branch in GitHub.
  • The CI will handle the PyPi publishing.

Shameless Plug

Originally written by ConsenSys for the world! And continued by @BoboTiG! :heart:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eip712_structs_ng-2.0.0.tar.gz (23.2 kB view details)

Uploaded Source

Built Distribution

eip712_structs_ng-2.0.0-py3-none-any.whl (13.4 kB view details)

Uploaded Python 3

File details

Details for the file eip712_structs_ng-2.0.0.tar.gz.

File metadata

  • Download URL: eip712_structs_ng-2.0.0.tar.gz
  • Upload date:
  • Size: 23.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.18

File hashes

Hashes for eip712_structs_ng-2.0.0.tar.gz
Algorithm Hash digest
SHA256 aa2c3e70cd62777bf5f3368bc5230c1b5924a050c370e48faee70c94d341d4b8
MD5 73ea26b2c0e8d60373866955fb168583
BLAKE2b-256 e3782320424a5833b6594bf9ad4dab47a30c548ba9e22f07740ccc750f8172a7

See more details on using hashes here.

File details

Details for the file eip712_structs_ng-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for eip712_structs_ng-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 864766c3d250dd510d0e892662abfec0c04dac97bbdb14e50ec48b8dff029545
MD5 dc1d504d1261f863c5c68b018f060588
BLAKE2b-256 646ae5e900d3df721afe9a22207269613efc92ea600dd50cb87b94b598b2f11e

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page