feat(order_utils.py) generate_order_hash_hex() (#1234)
This commit is contained in:
		@@ -136,5 +136,28 @@ describe('signTypedDataUtils', () => {
 | 
				
			|||||||
            const hashHex = `0x${hash}`;
 | 
					            const hashHex = `0x${hash}`;
 | 
				
			||||||
            expect(hashHex).to.be.eq(orderSignTypedDataHashHex);
 | 
					            expect(hashHex).to.be.eq(orderSignTypedDataHashHex);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        it('creates a hash of an uninitialized order', () => {
 | 
				
			||||||
 | 
					            const uninitializedOrder = {
 | 
				
			||||||
 | 
					                ...orderSignTypedData,
 | 
				
			||||||
 | 
					                message: {
 | 
				
			||||||
 | 
					                    makerAddress: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                    takerAddress: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                    makerAssetAmount: 0,
 | 
				
			||||||
 | 
					                    takerAssetAmount: 0,
 | 
				
			||||||
 | 
					                    expirationTimeSeconds: 0,
 | 
				
			||||||
 | 
					                    makerFee: 0,
 | 
				
			||||||
 | 
					                    takerFee: 0,
 | 
				
			||||||
 | 
					                    feeRecipientAddress: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                    senderAddress: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                    salt: 0,
 | 
				
			||||||
 | 
					                    makerAssetData: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                    takerAssetData: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                    exchangeAddress: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            const hash = signTypedDataUtils.generateTypedDataHash(uninitializedOrder).toString('hex');
 | 
				
			||||||
 | 
					            const hashHex = `0x${hash}`;
 | 
				
			||||||
 | 
					            expect(hashHex).to.be.eq('0xfaa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -160,7 +160,13 @@ setup(
 | 
				
			|||||||
        "publish": PublishCommand,
 | 
					        "publish": PublishCommand,
 | 
				
			||||||
        "ganache": GanacheCommand,
 | 
					        "ganache": GanacheCommand,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    install_requires=["eth-abi", "eth_utils", "mypy_extensions", "web3"],
 | 
					    install_requires=[
 | 
				
			||||||
 | 
					        "eth-abi",
 | 
				
			||||||
 | 
					        "eth_utils",
 | 
				
			||||||
 | 
					        "ethereum",
 | 
				
			||||||
 | 
					        "mypy_extensions",
 | 
				
			||||||
 | 
					        "web3",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    extras_require={
 | 
					    extras_require={
 | 
				
			||||||
        "dev": [
 | 
					        "dev": [
 | 
				
			||||||
            "bandit",
 | 
					            "bandit",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,8 @@ from typing import Any, List
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from mypy_extensions import TypedDict
 | 
					from mypy_extensions import TypedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from eth_abi import encode_abi
 | 
					 | 
				
			||||||
from web3 import Web3
 | 
					from web3 import Web3
 | 
				
			||||||
 | 
					from eth_abi import encode_abi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .type_assertions import assert_is_string, assert_is_list
 | 
					from .type_assertions import assert_is_string, assert_is_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,3 +9,157 @@ just this purpose.  To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli
 | 
				
			|||||||
--networkId 50 -m "concert load couple harbor equip island argue ramp clarify
 | 
					--networkId 50 -m "concert load couple harbor equip island argue ramp clarify
 | 
				
			||||||
fence smart topic"``.
 | 
					fence smart topic"``.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					from typing import Dict
 | 
				
			||||||
 | 
					from pkg_resources import resource_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from mypy_extensions import TypedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from eth_utils import is_address, keccak, to_checksum_address, to_bytes
 | 
				
			||||||
 | 
					from web3 import Web3
 | 
				
			||||||
 | 
					from web3.utils import datatypes
 | 
				
			||||||
 | 
					import web3.exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Constants:  # pylint: disable=too-few-public-methods
 | 
				
			||||||
 | 
					    """Static data used by order utilities."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    contract_name_to_abi = {
 | 
				
			||||||
 | 
					        "Exchange": json.loads(
 | 
				
			||||||
 | 
					            resource_string(
 | 
				
			||||||
 | 
					                "zero_ex.contract_artifacts", "artifacts/Exchange.json"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )["compilerOutput"]["abi"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    network_to_exchange_addr: Dict[str, str] = {
 | 
				
			||||||
 | 
					        "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
 | 
				
			||||||
 | 
					        "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
 | 
				
			||||||
 | 
					        "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
 | 
				
			||||||
 | 
					        "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    null_address = "0x0000000000000000000000000000000000000000"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eip191_header = b"\x19\x01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eip712_domain_separator_schema_hash = keccak(
 | 
				
			||||||
 | 
					        b"EIP712Domain(string name,string version,address verifyingContract)"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eip712_domain_struct_header = (
 | 
				
			||||||
 | 
					        eip712_domain_separator_schema_hash
 | 
				
			||||||
 | 
					        + keccak(b"0x Protocol")
 | 
				
			||||||
 | 
					        + keccak(b"2")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eip712_order_schema_hash = keccak(
 | 
				
			||||||
 | 
					        b"Order("
 | 
				
			||||||
 | 
					        + b"address makerAddress,"
 | 
				
			||||||
 | 
					        + b"address takerAddress,"
 | 
				
			||||||
 | 
					        + b"address feeRecipientAddress,"
 | 
				
			||||||
 | 
					        + b"address senderAddress,"
 | 
				
			||||||
 | 
					        + b"uint256 makerAssetAmount,"
 | 
				
			||||||
 | 
					        + b"uint256 takerAssetAmount,"
 | 
				
			||||||
 | 
					        + b"uint256 makerFee,"
 | 
				
			||||||
 | 
					        + b"uint256 takerFee,"
 | 
				
			||||||
 | 
					        + b"uint256 expirationTimeSeconds,"
 | 
				
			||||||
 | 
					        + b"uint256 salt,"
 | 
				
			||||||
 | 
					        + b"bytes makerAssetData,"
 | 
				
			||||||
 | 
					        + b"bytes takerAssetData"
 | 
				
			||||||
 | 
					        + b")"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Order(TypedDict):  # pylint: disable=too-many-instance-attributes
 | 
				
			||||||
 | 
					    """Object representation of a 0x order."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    maker_address: str
 | 
				
			||||||
 | 
					    taker_address: str
 | 
				
			||||||
 | 
					    fee_recipient_address: str
 | 
				
			||||||
 | 
					    sender_address: str
 | 
				
			||||||
 | 
					    maker_asset_amount: int
 | 
				
			||||||
 | 
					    taker_asset_amount: int
 | 
				
			||||||
 | 
					    maker_fee: int
 | 
				
			||||||
 | 
					    taker_fee: int
 | 
				
			||||||
 | 
					    expiration_time_seconds: int
 | 
				
			||||||
 | 
					    salt: int
 | 
				
			||||||
 | 
					    maker_asset_data: str
 | 
				
			||||||
 | 
					    taker_asset_data: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def make_empty_order() -> Order:
 | 
				
			||||||
 | 
					    """Construct an empty order."""
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        "maker_address": Constants.null_address,
 | 
				
			||||||
 | 
					        "taker_address": Constants.null_address,
 | 
				
			||||||
 | 
					        "sender_address": Constants.null_address,
 | 
				
			||||||
 | 
					        "fee_recipient_address": Constants.null_address,
 | 
				
			||||||
 | 
					        "maker_asset_data": Constants.null_address,
 | 
				
			||||||
 | 
					        "taker_asset_data": Constants.null_address,
 | 
				
			||||||
 | 
					        "salt": 0,
 | 
				
			||||||
 | 
					        "maker_fee": 0,
 | 
				
			||||||
 | 
					        "taker_fee": 0,
 | 
				
			||||||
 | 
					        "maker_asset_amount": 0,
 | 
				
			||||||
 | 
					        "taker_asset_amount": 0,
 | 
				
			||||||
 | 
					        "expiration_time_seconds": 0,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
 | 
				
			||||||
 | 
					    # docstring considered all one line by pylint: disable=line-too-long
 | 
				
			||||||
 | 
					    """Calculate the hash of the given order as a hexadecimal string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> generate_order_hash_hex(
 | 
				
			||||||
 | 
					    ...     {
 | 
				
			||||||
 | 
					    ...         'maker_address': "0x0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ...         'taker_address': "0x0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ...         'fee_recipient_address': "0x0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ...         'sender_address': "0x0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ...         'maker_asset_amount': 1000000000000000000,
 | 
				
			||||||
 | 
					    ...         'taker_asset_amount': 1000000000000000000,
 | 
				
			||||||
 | 
					    ...         'maker_fee': 0,
 | 
				
			||||||
 | 
					    ...         'taker_fee': 0,
 | 
				
			||||||
 | 
					    ...         'expiration_time_seconds': 12345,
 | 
				
			||||||
 | 
					    ...         'salt': 12345,
 | 
				
			||||||
 | 
					    ...         'maker_asset_data': "0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ...         'taker_asset_data': "0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ...     },
 | 
				
			||||||
 | 
					    ...     exchange_address="0x0000000000000000000000000000000000000000",
 | 
				
			||||||
 | 
					    ... )
 | 
				
			||||||
 | 
					    '55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
 | 
				
			||||||
 | 
					    """  # noqa: E501 (line too long)
 | 
				
			||||||
 | 
					    # TODO: use JSON schema validation to validate order. pylint: disable=fixme
 | 
				
			||||||
 | 
					    def pad_20_bytes_to_32(twenty_bytes: bytes):
 | 
				
			||||||
 | 
					        return bytes(12) + twenty_bytes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def int_to_32_big_endian_bytes(i: int):
 | 
				
			||||||
 | 
					        return i.to_bytes(32, byteorder="big")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eip712_domain_struct_hash = keccak(
 | 
				
			||||||
 | 
					        Constants.eip712_domain_struct_header
 | 
				
			||||||
 | 
					        + pad_20_bytes_to_32(to_bytes(hexstr=exchange_address))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eip712_order_struct_hash = keccak(
 | 
				
			||||||
 | 
					        Constants.eip712_order_schema_hash
 | 
				
			||||||
 | 
					        + pad_20_bytes_to_32(to_bytes(hexstr=order["maker_address"]))
 | 
				
			||||||
 | 
					        + pad_20_bytes_to_32(to_bytes(hexstr=order["taker_address"]))
 | 
				
			||||||
 | 
					        + pad_20_bytes_to_32(to_bytes(hexstr=order["fee_recipient_address"]))
 | 
				
			||||||
 | 
					        + pad_20_bytes_to_32(to_bytes(hexstr=order["sender_address"]))
 | 
				
			||||||
 | 
					        + int_to_32_big_endian_bytes(order["maker_asset_amount"])
 | 
				
			||||||
 | 
					        + int_to_32_big_endian_bytes(order["taker_asset_amount"])
 | 
				
			||||||
 | 
					        + int_to_32_big_endian_bytes(order["maker_fee"])
 | 
				
			||||||
 | 
					        + int_to_32_big_endian_bytes(order["taker_fee"])
 | 
				
			||||||
 | 
					        + int_to_32_big_endian_bytes(order["expiration_time_seconds"])
 | 
				
			||||||
 | 
					        + int_to_32_big_endian_bytes(order["salt"])
 | 
				
			||||||
 | 
					        + keccak(to_bytes(hexstr=order["maker_asset_data"]))
 | 
				
			||||||
 | 
					        + keccak(to_bytes(hexstr=order["taker_asset_data"]))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return keccak(
 | 
				
			||||||
 | 
					        Constants.eip191_header
 | 
				
			||||||
 | 
					        + eip712_domain_struct_hash
 | 
				
			||||||
 | 
					        + eip712_order_struct_hash
 | 
				
			||||||
 | 
					    ).hex()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,30 +1,16 @@
 | 
				
			|||||||
"""Signature utilities."""
 | 
					"""Signature utilities."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Dict, Tuple
 | 
					from typing import Tuple
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
from pkg_resources import resource_string
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from eth_utils import is_address, to_checksum_address
 | 
					from eth_utils import is_address, to_checksum_address
 | 
				
			||||||
from web3 import Web3
 | 
					from web3 import Web3
 | 
				
			||||||
import web3.exceptions
 | 
					import web3.exceptions
 | 
				
			||||||
from web3.utils import datatypes
 | 
					from web3.utils import datatypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zero_ex.order_utils import Constants
 | 
				
			||||||
from zero_ex.dev_utils.type_assertions import assert_is_hex_string
 | 
					from zero_ex.dev_utils.type_assertions import assert_is_hex_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# prefer `black` formatting. pylint: disable=C0330
 | 
					 | 
				
			||||||
EXCHANGE_ABI = json.loads(
 | 
					 | 
				
			||||||
    resource_string("zero_ex.contract_artifacts", "artifacts/Exchange.json")
 | 
					 | 
				
			||||||
)["compilerOutput"]["abi"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
network_to_exchange_addr: Dict[str, str] = {
 | 
					 | 
				
			||||||
    "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
 | 
					 | 
				
			||||||
    "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
 | 
					 | 
				
			||||||
    "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
 | 
					 | 
				
			||||||
    "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# prefer `black` formatting. pylint: disable=C0330
 | 
					# prefer `black` formatting. pylint: disable=C0330
 | 
				
			||||||
def is_valid_signature(
 | 
					def is_valid_signature(
 | 
				
			||||||
    provider: Web3.HTTPProvider, data: str, signature: str, signer_address: str
 | 
					    provider: Web3.HTTPProvider, data: str, signature: str, signer_address: str
 | 
				
			||||||
@@ -63,10 +49,11 @@ def is_valid_signature(
 | 
				
			|||||||
    web3_instance = Web3(provider)
 | 
					    web3_instance = Web3(provider)
 | 
				
			||||||
    # false positive from pylint: disable=no-member
 | 
					    # false positive from pylint: disable=no-member
 | 
				
			||||||
    network_id = web3_instance.net.version
 | 
					    network_id = web3_instance.net.version
 | 
				
			||||||
    contract_address = network_to_exchange_addr[network_id]
 | 
					    contract_address = Constants.network_to_exchange_addr[network_id]
 | 
				
			||||||
    # false positive from pylint: disable=no-member
 | 
					    # false positive from pylint: disable=no-member
 | 
				
			||||||
    contract: datatypes.Contract = web3_instance.eth.contract(
 | 
					    contract: datatypes.Contract = web3_instance.eth.contract(
 | 
				
			||||||
        address=to_checksum_address(contract_address), abi=EXCHANGE_ABI
 | 
					        address=to_checksum_address(contract_address),
 | 
				
			||||||
 | 
					        abi=Constants.contract_name_to_abi["Exchange"],
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								python-packages/order_utils/stubs/sha3/__init__.pyi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								python-packages/order_utils/stubs/sha3/__init__.pyi
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					"""Test zero_ex.order_utils.get_order_hash_hex()."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from zero_ex.order_utils import (
 | 
				
			||||||
 | 
					    generate_order_hash_hex,
 | 
				
			||||||
 | 
					    make_empty_order,
 | 
				
			||||||
 | 
					    Constants,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_get_order_hash_hex__empty_order():
 | 
				
			||||||
 | 
					    """Test the hashing of an uninitialized order."""
 | 
				
			||||||
 | 
					    expected_hash_hex = (
 | 
				
			||||||
 | 
					        "faa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    actual_hash_hex = generate_order_hash_hex(
 | 
				
			||||||
 | 
					        make_empty_order(), Constants.null_address
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    assert actual_hash_hex == expected_hash_hex
 | 
				
			||||||
		Reference in New Issue
	
	Block a user