Skip to content

Commit

Permalink
feat: add calculate_hash method for getting EIP712 hash [APE-1578] (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Dec 6, 2023
1 parent ab8c626 commit 76f172f
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 10 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml

Expand All @@ -10,7 +10,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.11.0
hooks:
- id: black
name: black
Expand All @@ -21,7 +21,7 @@ repos:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [types-setuptools, pydantic]
Expand Down
14 changes: 11 additions & 3 deletions eip712/messages.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""
Message classes for typed structured data hashing and signing in Ethereum.
"""

from typing import Any, Dict, Optional

from dataclassy import dataclass, fields
from eth_abi import is_encodable_type
from eth_account.messages import SignableMessage, hash_domain, hash_eip712_message
from eth_utils import keccak
from eth_utils.curried import ValidationError
from hexbytes import HexBytes

Expand Down Expand Up @@ -119,6 +119,7 @@ def _domain_(self) -> dict:
@property
def _body_(self) -> dict:
"""The EIP-712 structured message to be used for serialization and hashing."""

return {
"domain": self._domain_["domain"],
"types": dict(self._types_, **self._domain_["types"]),
Expand All @@ -138,9 +139,16 @@ def __getitem__(self, key: str) -> Any:

@property
def signable_message(self) -> SignableMessage:
"""The current message as a :class:`SignableMessage` named tuple instance."""
"""
The current message as a :class:`SignableMessage` named tuple instance.
**NOTE**: The 0x19 prefix is NOT included.
"""
return SignableMessage(
HexBytes(b"\x01"),
HexBytes(1),
HexBytes(hash_domain(self._domain_)),
HexBytes(hash_eip712_message(self._body_)),
)


def calculate_hash(msg: SignableMessage) -> HexBytes:
return HexBytes(keccak(b"".join([bytes.fromhex("19"), *msg])))
30 changes: 26 additions & 4 deletions tests/test_common.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import pytest
from eth_account.messages import hash_eip712_message as hash_message
from eth_account._utils.structured_data.hashing import hash_message
from hexbytes import HexBytes

from eip712.common import SAFE_VERSIONS, create_safe_tx_def
from eip712.messages import calculate_hash

MSIG_ADDRESS = "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52"
MAINNET_MSIG_ADDRESS = "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52"
GOERLI_MSIG_ADDRESS = "0x3c59eC3912A6A0c8690ec548D87FB55C3Ba62aBa"


@pytest.mark.parametrize("version", SAFE_VERSIONS)
def test_gnosis_safe_tx(version):
tx_def = create_safe_tx_def(
version=version,
contract_address=MSIG_ADDRESS,
contract_address=MAINNET_MSIG_ADDRESS,
chain_id=1,
)

msg = tx_def(to=MSIG_ADDRESS, nonce=0)
msg = tx_def(to=MAINNET_MSIG_ADDRESS, nonce=0)

assert msg.signable_message.header.hex() == (
"0x88fbc465dedd7fe71b7baef26a1f46cdaadd50b95c77cbe88569195a9fe589ab"
Expand All @@ -27,3 +30,22 @@ def test_gnosis_safe_tx(version):
if version in ("1.3.0",)
else "1b393826bed1f2297ffc01916f8339892f9a51dc7f35f477b9a5cdd651d28603"
)


def test_gnosis_goerli_safe_tx():
tx_def = create_safe_tx_def(
version="1.3.0",
contract_address=GOERLI_MSIG_ADDRESS,
chain_id=5,
)

# Fields matching actual nonce=0 tx from wallet.
receiver = "0x3c59eC3912A6A0c8690ec548D87FB55C3Ba62aBa"
msg = tx_def(
to=receiver,
nonce=0,
value=1_000_000_000_000_000_000,
)
actual = calculate_hash(msg.signable_message)
expected = HexBytes("0xbbb1cbed7c3679b5d5764df26af8fab1b15f3a15c084db9082dffb3624ca74ee")
assert actual == expected

0 comments on commit 76f172f

Please sign in to comment.