Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use web3 with Kakarot RPC in end-to-end tests when available #1164

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ EVM_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff

# Because cairo-land generated files used protobuf<=3.20 and web3.py uses protobuf ~4
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python

# To override the default web3 provider uri http://localhost:8545, putting as default
# the default Kakarot RPC URL of kakarot-rpc repo
WEB3_HTTP_PROVIDER_URI="http://0.0.0.0:3030"
2 changes: 2 additions & 0 deletions kakarot_scripts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from eth_keys import keys
from starknet_py.net.full_node_client import FullNodeClient
from starknet_py.net.models.chains import StarknetChainId
from web3 import Web3

logging.basicConfig()
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -127,6 +128,7 @@
NETWORK["private_key"] = os.getenv("PRIVATE_KEY")

RPC_CLIENT = FullNodeClient(node_url=NETWORK["rpc_url"])
WEB3 = Web3()

try:
response = requests.post(
Expand Down
94 changes: 64 additions & 30 deletions kakarot_scripts/utils/kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from types import MethodType
from typing import List, Optional, Union, cast

from eth_abi import decode
from eth_abi.exceptions import InsufficientDataBytes
from eth_account import Account as EvmAccount
from eth_account._utils.typed_transactions import TypedTransaction
Expand All @@ -13,7 +14,7 @@
from hexbytes import HexBytes
from starknet_py.net.account.account import Account
from starknet_py.net.client_errors import ClientError
from starknet_py.net.client_models import Call, Event
from starknet_py.net.client_models import Call
from starknet_py.net.models.transaction import InvokeV1
from starknet_py.net.signer.stark_curve_signer import KeyPair
from starkware.starknet.public.abi import starknet_keccak
Expand All @@ -26,7 +27,13 @@
from web3.exceptions import LogTopicError, MismatchedABI, NoABIFunctionsFound
from web3.types import LogReceipt

from kakarot_scripts.constants import EVM_ADDRESS, EVM_PRIVATE_KEY, NETWORK, RPC_CLIENT
from kakarot_scripts.constants import (
EVM_ADDRESS,
EVM_PRIVATE_KEY,
NETWORK,
RPC_CLIENT,
WEB3,
)
from kakarot_scripts.utils.starknet import call as _call_starknet
from kakarot_scripts.utils.starknet import fund_address as _fund_starknet_address
from kakarot_scripts.utils.starknet import get_contract as _get_starknet_contract
Expand Down Expand Up @@ -133,7 +140,7 @@ def get_contract(

contract = cast(
Web3Contract,
Web3().eth.contract(
WEB3.eth.contract(
address=to_checksum_address(address) if address is not None else address,
abi=artifacts["abi"],
bytecode=artifacts["bytecode"],
Expand All @@ -146,7 +153,7 @@ def get_contract(
setattr(contract, fun, MethodType(_wrap_kakarot(fun, caller_eoa), contract))
except NoABIFunctionsFound:
pass
contract.events.parse_starknet_events = MethodType(_parse_events, contract.events)
contract.events.parse_events = MethodType(_parse_events, contract.events)
return contract


Expand All @@ -169,22 +176,31 @@ async def deploy(
if success == 0:
raise EvmTransactionError(bytes(response))

starknet_address, evm_address = response
if WEB3.is_connected():
evm_address = int(receipt.contractAddress or receipt.to, 16)
starknet_address = (
await _call_starknet("kakarot", "compute_starknet_address", evm_address)
).contract_address
else:
starknet_address, evm_address = response
contract.address = Web3.to_checksum_address(f"0x{evm_address:040x}")
contract.starknet_address = starknet_address
logger.info(f"✅ {contract_name} deployed at address {contract.address}")

return contract


def _parse_events(cls: ContractEvents, starknet_events: List[Event]):
def get_log_receipts(tx_receipt):
if WEB3.is_connected():
return tx_receipt.logs

kakarot_address = get_deployments()["kakarot"]["address"]
kakarot_events = [
event
for event in starknet_events
for event in tx_receipt.events
if event.from_address == kakarot_address and event.keys[0] < 2**160
]
log_receipts = [
return [
LogReceipt(
address=to_checksum_address(f"0x{event.keys[0]:040x}"),
blockHash=bytes(),
Expand All @@ -209,6 +225,10 @@ def _parse_events(cls: ContractEvents, starknet_events: List[Event]):
for log_index, event in enumerate(kakarot_events)
]


def _parse_events(cls: ContractEvents, tx_receipt):
log_receipts = get_log_receipts(tx_receipt)

return {
event_abi.get("name"): _get_matching_logs_for_event(event_abi, log_receipts)
for event_abi in cls._events
Expand All @@ -217,10 +237,9 @@ def _parse_events(cls: ContractEvents, starknet_events: List[Event]):

def _get_matching_logs_for_event(event_abi, log_receipts) -> List[dict]:
logs = []
codec = Web3().codec
for log_receipt in log_receipts:
try:
event_data = get_event_data(codec, event_abi, log_receipt)
event_data = get_event_data(WEB3.codec, event_abi, log_receipt)
logs += [event_data["args"]]
except (MismatchedABI, LogTopicError, InsufficientDataBytes):
pass
Expand All @@ -242,30 +261,35 @@ async def _wrapper(self, *args, **kwargs):
)._encode_transaction_data()

if abi["stateMutability"] in ["pure", "view"]:
kakarot_contract = _get_starknet_contract("kakarot")
origin = (
int(caller_eoa_.signer.public_key.to_address(), 16)
if caller_eoa_
else int(EVM_ADDRESS, 16)
)
result = await kakarot_contract.functions["eth_call"].call(
nonce=0,
origin=origin,
to={
"is_some": 1,
"value": int(self.address, 16),
},
gas_limit=gas_limit,
gas_price=gas_price,
value=value,
data=list(HexBytes(calldata)),
access_list=[],
)
if result.success == 0:
raise EvmTransactionError(bytes(result.return_data))
codec = Web3().codec
payload = {
"nonce": 0,
"from": Web3.to_checksum_address(f"{origin:040x}"),
"to": self.address,
"gas_limit": gas_limit,
"gas_price": gas_price,
"value": value,
"data": HexBytes(calldata),
"access_list": [],
}
if WEB3.is_connected():
result = WEB3.eth.call(payload)
else:
kakarot_contract = _get_starknet_contract("kakarot")
payload["to"] = {"is_some": 1, "value": int(payload["to"], 16)}
payload["data"] = list(payload["data"])
payload["origin"] = int(payload["from"], 16)
del payload["from"]
result = await kakarot_contract.functions["eth_call"].call(**payload)
if result.success == 0:
raise EvmTransactionError(bytes(result.return_data))
result = result.return_data
types = [o["type"] for o in abi["outputs"]]
decoded = codec.decode(types, bytes(result.return_data))
decoded = decode(types, bytes(result))
normalized = map_abi_data(BASE_RETURN_NORMALIZERS, types, decoded)
return normalized[0] if len(normalized) == 1 else normalized

Expand Down Expand Up @@ -328,15 +352,20 @@ async def eth_send_transaction(
):
"""Execute the data at the EVM contract to on Kakarot."""
evm_account = caller_eoa or await get_eoa()
nonce = await evm_account.get_nonce()
if WEB3.is_connected():
nonce = WEB3.eth.get_transaction_count(
evm_account.signer.public_key.to_checksum_address()
)
else:
nonce = await evm_account.get_nonce()

payload = {
"type": 0x2,
"chainId": NETWORK["chain_id"],
"nonce": nonce,
"gas": gas,
"maxPriorityFeePerGas": 1,
"maxFeePerGas": 1,
"maxFeePerGas": 100,
"to": to_checksum_address(to) if to else None,
"value": value,
"data": data,
Expand All @@ -349,6 +378,11 @@ async def eth_send_transaction(
hex(evm_account.signer.private_key),
)

if WEB3.is_connected():
tx_hash = WEB3.eth.send_raw_transaction(evm_tx.rawTransaction)
receipt = WEB3.eth.wait_for_transaction_receipt(tx_hash)
return receipt, [], receipt.status, receipt.gasUsed

encoded_unsigned_tx = rlp_encode_signed_data(typed_transaction.as_dict())

prepared_invoke = await evm_account._prepare_invoke(
Expand Down
Loading
Loading