# EIP-712 Structs [![Build Status](https://travis-ci.org/ajrgrubbs/py-eip712-structs.svg?branch=master)](https://travis-ci.org/ajrgrubbs/py-eip712-structs) [![Coverage Status](https://coveralls.io/repos/github/ajrgrubbs/py-eip712-structs/badge.svg?branch=master)](https://coveralls.io/github/ajrgrubbs/py-eip712-structs?branch=master)
A 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:<br/>
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md
#### Supported Python Versions
- `3.6`
- `3.7`
## Install
```bash
pip install eip712-structs
```
## Usage
See [API.md](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:
```text
struct MyStruct {
string some_string;
uint256 some_number;
}
```
With this module, that would look like:
```python
# Make a unique domain
from eip712_structs import make_domain
domain = make_domain(name='Some name', version='1.0.0') # Make a Domain Separator
# Define your struct type
from eip712_structs import EIP712Struct, String, Uint
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)
# Into a message dict (serializable to JSON) - domain required
my_msg = mine.to_message(domain)
# Into signable bytes - domain required
my_bytes = mine.signable_bytes(domain)
```
See [Member Types](#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`.
```python
from eip712_structs import EIP712Struct, Address
class Message(EIP712Struct):
pass
Message.to = Address()
setattr(Message, 'from', Address())
```
#### 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: <br/>
`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()`
```python
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.
```python
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:
```python
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, EIP712 structs may also hold other structs!
Usage is almost the same - the difference is you don't "instantiate" the class.
Example:
```python
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:
```python
# 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.
```python
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:
```python
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.
Install dependencies:
- `pip install -r requirements.txt`
Run tests:
- `python setup.py test`
- Some tests expect an active local ganache chain on http://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 run `up`, but won't be deployed until the test is run.
- Cleanup containers when you're done: `docker-compose down`
Deploying a new version:
- Bump the version number in `setup.py`, commit it into master.
- Make a release tag on the master branch in Github. Travis should handle the rest.
## Shameless Plug
Written by [ConsenSys](https://consensys.net) for ourselves and the community! :heart:
Raw data
{
"_id": null,
"home_page": "https://github.com/ajrgrubbs/py-eip712-structs",
"name": "eip712-structs",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "ethereum eip712 solidity",
"author": "AJ Grubbs",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/36/e0/0c79d27da8918f7642cba8ad4b0e6176ff7a8b4774f363c6ceb2513474be/eip712-structs-1.1.0.tar.gz",
"platform": "",
"description": "# EIP-712 Structs [![Build Status](https://travis-ci.org/ajrgrubbs/py-eip712-structs.svg?branch=master)](https://travis-ci.org/ajrgrubbs/py-eip712-structs) [![Coverage Status](https://coveralls.io/repos/github/ajrgrubbs/py-eip712-structs/badge.svg?branch=master)](https://coveralls.io/github/ajrgrubbs/py-eip712-structs?branch=master)\n\nA python interface for simple EIP-712 struct construction.\n\nIn this module, a \"struct\" is structured data as defined in the standard.\nIt is not the same as the Python Standard Library's struct (e.g., `import struct`).\n\nRead the proposal:<br/>\nhttps://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md\n\n#### Supported Python Versions\n- `3.6`\n- `3.7`\n\n## Install\n```bash\npip install eip712-structs\n```\n\n## Usage\nSee [API.md](API.md) for a succinct summary of available methods.\n\nExamples/Details below.\n\n#### Quickstart\nSay we want to represent the following struct, convert it to a message and sign it:\n```text\nstruct MyStruct {\n string some_string;\n uint256 some_number;\n}\n```\n\nWith this module, that would look like:\n```python\n# Make a unique domain\nfrom eip712_structs import make_domain\ndomain = make_domain(name='Some name', version='1.0.0') # Make a Domain Separator\n\n# Define your struct type\nfrom eip712_structs import EIP712Struct, String, Uint\nclass MyStruct(EIP712Struct):\n some_string = String()\n some_number = Uint(256)\n\n# Create an instance with some data\nmine = MyStruct(some_string='hello world', some_number=1234)\n\n# Into a message dict (serializable to JSON) - domain required\nmy_msg = mine.to_message(domain)\n\n# Into signable bytes - domain required\nmy_bytes = mine.signable_bytes(domain)\n```\n\nSee [Member Types](#member-types) for more information on supported types.\n\n#### Dynamic construction\nAttributes may be added dynamically as well. This may be necessary if you\nwant to use a reserved keyword like `from`.\n\n```python\nfrom eip712_structs import EIP712Struct, Address\nclass Message(EIP712Struct):\n pass\n\nMessage.to = Address()\nsetattr(Message, 'from', Address())\n```\n\n#### The domain separator\nEIP-712 specifies a domain struct, to differentiate between identical structs that may be unrelated.\nA helper method exists for this purpose.\nAll values to the `make_domain()`\nfunction are optional - but at least one must be defined. If omitted, the resulting\ndomain struct's definition leaves out the parameter entirely.\n\nThe full signature: <br/>\n`make_domain(name: string, version: string, chainId: uint256, verifyingContract: address, salt: bytes32)`\n\n##### Setting a default domain\nConstantly providing the same domain can be cumbersome. You can optionally set a default, and then forget it.\nIt is automatically used by `.to_message()` and `.signable_bytes()`\n\n```python\nimport eip712_structs\n\nfoo = SomeStruct()\n\nmy_domain = eip712_structs.make_domain(name='hello world')\neip712_structs.default_domain = my_domain\n\nassert foo.to_message() == foo.to_message(my_domain)\nassert foo.signable_bytes() == foo.signable_bytes(my_domain)\n```\n\n## Member Types\n\n### Basic types\nEIP712's basic types map directly to solidity types.\n\n```python\nfrom eip712_structs import Address, Boolean, Bytes, Int, String, Uint\n\nAddress() # Solidity's 'address'\nBoolean() # 'bool'\nBytes() # 'bytes'\nBytes(N) # 'bytesN' - N must be an int from 1 through 32\nInt(N) # 'intN' - N must be a multiple of 8, from 8 to 256\nString() # 'string'\nUint(N) # 'uintN' - N must be a multiple of 8, from 8 to 256\n```\n\nUse like:\n```python\nfrom eip712_structs import EIP712Struct, Address, Bytes\n\nclass Foo(EIP712Struct):\n member_name_0 = Address()\n member_name_1 = Bytes(5)\n # ...etc\n```\n\n### Struct references\nIn addition to holding basic types, EIP712 structs may also hold other structs!\nUsage is almost the same - the difference is you don't \"instantiate\" the class.\n\nExample:\n```python\nfrom eip712_structs import EIP712Struct, String\n\nclass Dog(EIP712Struct):\n name = String()\n breed = String()\n\nclass Person(EIP712Struct):\n name = String()\n dog = Dog # Take note - no parentheses!\n\n# Dog \"stands alone\"\nDog.encode_type() # Dog(string name,string breed)\n\n# But Person knows how to include Dog\nPerson.encode_type() # Person(string name,Dog dog)Dog(string name,string breed)\n```\n\nInstantiating the structs with nested values may be done a couple different ways:\n\n```python\n# Method one: set it to a struct\ndog = Dog(name='Mochi', breed='Corgi')\nperson = Person(name='E.M.', dog=dog)\n\n# Method two: set it to a dict - the underlying struct is built for you\nperson = Person(\n name='E.M.',\n dog={\n 'name': 'Mochi',\n 'breed': 'Corgi',\n }\n)\n```\n\n### Arrays\nArrays are also supported for the standard.\n\n```python\narray_member = Array(<item_type>[, <optional_length>])\n```\n\n- `<item_type>` - The basic type or struct that will live in the array\n- `<optional_length>` - If given, the array is set to that length.\n\nFor example:\n```python\ndynamic_array = Array(String()) # String[] dynamic_array\nstatic_array = Array(String(), 10) # String[10] static_array\nstruct_array = Array(MyStruct, 10) # MyStruct[10] - again, don't instantiate structs like the basic types\n```\n\n## Development\nContributions always welcome.\n\nInstall dependencies:\n- `pip install -r requirements.txt`\n\nRun tests:\n- `python setup.py test`\n- Some tests expect an active local ganache chain on http://localhost:8545. Docker will compile the contracts and start the chain for you.\n- Docker is optional, but useful to test the whole suite. If no chain is detected, chain tests are skipped.\n- Usage:\n - `docker-compose up -d` (Starts containers in the background)\n - Note: Contracts are compiled when you run `up`, but won't be deployed until the test is run.\n - Cleanup containers when you're done: `docker-compose down`\n\nDeploying a new version:\n- Bump the version number in `setup.py`, commit it into master.\n- Make a release tag on the master branch in Github. Travis should handle the rest.\n\n\n## Shameless Plug\nWritten by [ConsenSys](https://consensys.net) for ourselves and the community! :heart:",
"bugtrack_url": null,
"license": "MIT",
"summary": "A python library for EIP712 objects",
"version": "1.1.0",
"project_urls": {
"Homepage": "https://github.com/ajrgrubbs/py-eip712-structs"
},
"split_keywords": [
"ethereum",
"eip712",
"solidity"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "36e00c79d27da8918f7642cba8ad4b0e6176ff7a8b4774f363c6ceb2513474be",
"md5": "91b66a28a1e8343dd6fe94312d12aa5d",
"sha256": "b24400aef07b4d0287fb9bf8ce02b0abbe80c476d1b67222a7c5158df3a3e38d"
},
"downloads": -1,
"filename": "eip712-structs-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "91b66a28a1e8343dd6fe94312d12aa5d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 12853,
"upload_time": "2019-06-15T04:54:49",
"upload_time_iso_8601": "2019-06-15T04:54:49.299015Z",
"url": "https://files.pythonhosted.org/packages/36/e0/0c79d27da8918f7642cba8ad4b0e6176ff7a8b4774f363c6ceb2513474be/eip712-structs-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2019-06-15 04:54:49",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ajrgrubbs",
"github_project": "py-eip712-structs",
"travis_ci": true,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "atomicwrites",
"specs": [
[
"==",
"1.3.0"
]
]
},
{
"name": "attrdict",
"specs": [
[
"==",
"2.0.1"
]
]
},
{
"name": "attrs",
"specs": [
[
"==",
"19.1.0"
]
]
},
{
"name": "certifi",
"specs": [
[
"==",
"2019.3.9"
]
]
},
{
"name": "chardet",
"specs": [
[
"==",
"3.0.4"
]
]
},
{
"name": "coverage",
"specs": [
[
"==",
"4.5.3"
]
]
},
{
"name": "coveralls",
"specs": [
[
"==",
"1.8.0"
]
]
},
{
"name": "cytoolz",
"specs": [
[
"==",
"0.9.0.1"
]
]
},
{
"name": "docopt",
"specs": [
[
"==",
"0.6.2"
]
]
},
{
"name": "eth-abi",
"specs": [
[
"==",
"1.3.0"
]
]
},
{
"name": "eth-account",
"specs": [
[
"==",
"0.3.0"
]
]
},
{
"name": "eth-hash",
"specs": [
[
"==",
"0.2.0"
]
]
},
{
"name": "eth-keyfile",
"specs": [
[
"==",
"0.5.1"
]
]
},
{
"name": "eth-keys",
"specs": [
[
"==",
"0.2.3"
]
]
},
{
"name": "eth-rlp",
"specs": [
[
"==",
"0.1.2"
]
]
},
{
"name": "eth-typing",
"specs": [
[
"==",
"2.1.0"
]
]
},
{
"name": "eth-utils",
"specs": [
[
"==",
"1.6.0"
]
]
},
{
"name": "hexbytes",
"specs": [
[
"==",
"0.2.0"
]
]
},
{
"name": "idna",
"specs": [
[
"==",
"2.8"
]
]
},
{
"name": "importlib-metadata",
"specs": [
[
"==",
"0.17"
]
]
},
{
"name": "lru-dict",
"specs": [
[
"==",
"1.1.6"
]
]
},
{
"name": "more-itertools",
"specs": [
[
"==",
"7.0.0"
]
]
},
{
"name": "packaging",
"specs": [
[
"==",
"19.0"
]
]
},
{
"name": "parsimonious",
"specs": [
[
"==",
"0.8.1"
]
]
},
{
"name": "pluggy",
"specs": [
[
"==",
"0.12.0"
]
]
},
{
"name": "py",
"specs": [
[
"==",
"1.8.0"
]
]
},
{
"name": "pycryptodome",
"specs": [
[
"==",
"3.8.2"
]
]
},
{
"name": "pyparsing",
"specs": [
[
"==",
"2.4.0"
]
]
},
{
"name": "pysha3",
"specs": [
[
"==",
"1.0.2"
]
]
},
{
"name": "pytest",
"specs": [
[
"==",
"4.6.2"
]
]
},
{
"name": "pytest-cov",
"specs": [
[
"==",
"2.7.1"
]
]
},
{
"name": "requests",
"specs": [
[
"==",
"2.22.0"
]
]
},
{
"name": "rlp",
"specs": [
[
"==",
"1.1.0"
]
]
},
{
"name": "six",
"specs": [
[
"==",
"1.12.0"
]
]
},
{
"name": "toolz",
"specs": [
[
"==",
"0.9.0"
]
]
},
{
"name": "urllib3",
"specs": [
[
"==",
"1.25.3"
]
]
},
{
"name": "wcwidth",
"specs": [
[
"==",
"0.1.7"
]
]
},
{
"name": "web3",
"specs": [
[
"==",
"4.9.2"
]
]
},
{
"name": "websockets",
"specs": [
[
"==",
"6.0"
]
]
},
{
"name": "zipp",
"specs": [
[
"==",
"0.5.1"
]
]
}
],
"lcname": "eip712-structs"
}