Skip to content

Commit

Permalink
feat: support contract address computing utils
Browse files Browse the repository at this point in the history
  • Loading branch information
darwintree committed Sep 23, 2024
1 parent 122c89b commit e45f5a6
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 5 deletions.
File renamed without changes.
66 changes: 66 additions & 0 deletions conflux_web3/utils/address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import (
Union,
overload,
)

import rlp
from hexbytes import HexBytes
from eth_utils import (
keccak,
to_bytes,
to_checksum_address,
)

from cfx_address import Base32Address
from cfx_address.utils import (
normalize_to
)
from cfx_utils.types import (
ChecksumAddress,
)

def get_create_address(sender: Union[str, Base32Address], nonce: int, bytecode_hash: Union[bytes, str]) -> Union[ChecksumAddress, Base32Address]:
"""
Determine the resulting `CREATE` opcode contract address for a sender and a nonce.
Typically, the sender is a wallet address and the nonce is the next nonce of the sender.
NOTE: in Conflux, the contract address is computed differently from that in Ethereum
where the bytecode hash is not accounted for in the address computation.
:param sender: The address of the sender. Can be a hex string or a base32 address.
:param nonce: The nonce of the sender.
:param bytecode_hash: The keccak256 hash of the contract bytecode, whereas the "data" field of the transaction. Can be bytes or hex string.
:return: The computed address as a hex string or base32 address, depending on the type of sender
"""
address_hex = normalize_to(sender, None)
contract_address = "0x8" + keccak(
b"\x00"
+ to_bytes(hexstr=address_hex)
+ nonce.to_bytes(32, "little")
+ HexBytes(bytecode_hash)
).hex()[-39:]
if Base32Address.is_valid_base32(sender):
return Base32Address(contract_address, network_id=Base32Address(sender).network_id)
return to_checksum_address(contract_address)


def get_create2_address(
create2_factory_address: Union[str, Base32Address], salt: bytes, bytecode_hash: Union[bytes, str]
) -> Union[ChecksumAddress, Base32Address]:
"""
Compute the address of a contract created using CREATE2.
:param create2_factory_address: The address of the CREATE2 factory contract. On Conflux, it is deployed via `CIP-31 <https://github.com/Conflux-Chain/CIPs/blob/master/CIPs/cip-31.md>`_
:param salt: A 32-byte value used as salt. Should be bytes.
:param bytecode_hash: The keccak256 hash of the contract bytecode. Can be bytes or hex string.
:return: The computed address as a hex string or base32 address, depending on the type of create2_factory_address
"""
address_hex = normalize_to(create2_factory_address, None)
contract_address = "0x8" + keccak(
b"\xff"
+ to_bytes(hexstr=address_hex)
+ salt
+ HexBytes(bytecode_hash)
).hex()[-39:]
if Base32Address.is_valid_base32(create2_factory_address):
return Base32Address(contract_address, network_id=Base32Address(create2_factory_address).network_id)
return to_checksum_address(contract_address)
21 changes: 16 additions & 5 deletions tests/transaction/test_contract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from typing import TYPE_CHECKING
import os, json, pytest

from hexbytes import HexBytes

from web3.exceptions import ValidationError

from conflux_web3 import Web3
from conflux_web3.contract import (
ConfluxContract,
Expand All @@ -8,10 +13,13 @@
get_contract_metadata
)
from cfx_utils.exceptions import Base32AddressNotMatch
from conflux_web3.middleware.wallet import Wallet

from cfx_account import LocalAccount

from conflux_web3.middleware.wallet import Wallet
from tests._test_helpers.type_check import TypeValidator
from web3.exceptions import ValidationError

from conflux_web3.utils.address import get_create_address

if TYPE_CHECKING:
from conflux_web3 import Web3
Expand All @@ -34,9 +42,12 @@ def test_contract_deploy_and_transfer(self, w3_: Web3):
# test deployment
erc20_metadata = get_contract_metadata("ERC20")
erc20 = w3_.cfx.contract(bytecode=erc20_metadata["bytecode"], abi=erc20_metadata["abi"])
hash = erc20.constructor(name="Coin", symbol="C", initialSupply=10**18).transact()
contract_address = w3_.cfx.wait_for_transaction_receipt(hash)["contractCreated"]
assert contract_address is not None
tx_hash = erc20.constructor(name="Coin", symbol="C", initialSupply=10**18).transact()
contract_address = tx_hash.executed()["contractCreated"]
tx_data = w3_.cfx.get_transaction(tx_hash)
computed_contract_address = get_create_address(w3_.cfx.default_account, tx_data["nonce"], w3_.keccak(tx_data["data"])) # type: ignore
assert contract_address == computed_contract_address, (contract_address, computed_contract_address)

contract = w3_.cfx.contract(contract_address, abi=erc20_metadata["abi"])

# test transfer
Expand Down
21 changes: 21 additions & 0 deletions tests/utils/test_contract_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from conflux_web3 import Web3
from conflux_web3.types import Base32Address
from cfx_address.utils import normalize_to
from conflux_web3.utils.address import get_create_address, get_create2_address

bytecode = "0x608060405234801561001057600080fd5b506101a1806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80637cf5dab014610030575b600080fd5b61004a600480360381019061004591906100b1565b610060565b60405161005791906100ed565b60405180910390f35b600060018261006f9190610137565b9050919050565b600080fd5b6000819050919050565b61008e8161007b565b811461009957600080fd5b50565b6000813590506100ab81610085565b92915050565b6000602082840312156100c7576100c6610076565b5b60006100d58482850161009c565b91505092915050565b6100e78161007b565b82525050565b600060208201905061010260008301846100de565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006101428261007b565b915061014d8361007b565b925082820190508082111561016557610164610108565b5b9291505056fea26469706673582212208eb410cb79fbf08652e19d496e31d076d04be7ed242c64a44aec4c5af0f2533b64736f6c63430008130033"

def test_get_create_address():
address = "cfx:aamz08kfa8wsu69jhhcgrwkjkh69p85wj6222847yp"
nonce = 1
bytecode_hash = Web3.solidity_keccak(["bytes"], [bytecode])
assert get_create_address(address, nonce, bytecode_hash) == Base32Address("0x837f77a1e8da5b860905a07bc1921e43fbfb04ef", 1029)
assert get_create_address(Base32Address(address).hex_address, nonce, bytecode_hash) == normalize_to("0x837f77a1e8da5b860905a07bc1921e43fbfb04ef", None)

def test_get_create2_address():
salt = (1111).to_bytes(32, 'big')
bytecode_hash = Web3.solidity_keccak(["bytes"], [bytecode])
create2_factory_address = "0x8A3A92281Df6497105513B18543fd3B60c778E40"
assert get_create2_address(create2_factory_address, salt, bytecode_hash) == normalize_to("0x80ac53cc16c0b58dc5bde5af47f5ef9e84693fe4", None)
assert get_create2_address(Base32Address(create2_factory_address, 1029), salt, bytecode_hash) == normalize_to("0x80ac53cc16c0b58dc5bde5af47f5ef9e84693fe4", 1029)

0 comments on commit e45f5a6

Please sign in to comment.