Skip to content

Commit

Permalink
Try to trace failed deploys
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielSchiavini committed Jun 4, 2024
1 parent 5f13b42 commit 79fc445
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 53 deletions.
59 changes: 37 additions & 22 deletions boa_zksync/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from boa_zksync.node import EraTestNode
from boa_zksync.types import DeployTransaction, ZksyncComputation, ZksyncMessage

ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
_CONTRACT_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008006"
ZERO_ADDRESS = Address("0x0000000000000000000000000000000000000000")
_CONTRACT_DEPLOYER_ADDRESS = Address("0x0000000000000000000000000000000000008006")
with open(Path(__file__).parent / "IContractDeployer.json") as f:
CONTRACT_DEPLOYER = ABIContractFactory.from_abi_dict(
json.load(f), "ContractDeployer"
Expand Down Expand Up @@ -55,7 +55,11 @@ def vm(self):

def _reset_fork(self, block_identifier="latest"):
self._vm = None
if block_identifier == "latest" and isinstance(self._rpc, EraTestNode) and (inner_rpc := self._rpc.inner_rpc):
if (
block_identifier == "latest"
and isinstance(self._rpc, EraTestNode)
and (inner_rpc := self._rpc.inner_rpc)
):
del self._rpc # close the old rpc
self._rpc = inner_rpc

Expand Down Expand Up @@ -120,33 +124,22 @@ def execute_code(
sender = self._check_sender(self._get_sender(sender))
args = ZksyncMessage(sender, to_address, gas or 0, value, data)

try:
trace_call = self._rpc.fetch(
"debug_traceCall",
[args.as_json_dict(), "latest", {"tracer": "callTracer"}],
)
traced_computation = ZksyncComputation.from_call_trace(trace_call)
except (RPCError, HTTPError):
output = self._rpc.fetch("eth_call", [args.as_json_dict(), "latest"])
traced_computation = ZksyncComputation(
args, bytes.fromhex(output.removeprefix("0x"))
)

computation = self._compute(args)
if is_modifying:
try:
receipt, trace = self._send_txn(**args.as_tx_params())
self.last_receipt = receipt
if trace:
assert (
traced_computation.is_error == trace.is_error
), f"VMError mismatch: {traced_computation.error} != {trace.error}"
computation.is_error == trace.is_error
), f"VMError mismatch: {computation.error} != {trace.error}"
return ZksyncComputation.from_debug_trace(trace.raw_trace)

except _EstimateGasFailed:
if not traced_computation.is_error: # trace gives more information
if not computation.is_error: # trace gives more information
return ZksyncComputation(args, error=VMError("Estimate gas failed"))

return traced_computation
return computation

def deploy_code(
self,
Expand Down Expand Up @@ -209,9 +202,7 @@ def deploy_code(
paymaster_params=kwargs.pop("paymaster_params", None),
)

estimated_gas = self._rpc.fetch("eth_estimateGas", [tx.get_estimate_tx()])
estimated_gas = int(estimated_gas, 16)

estimated_gas = self._estimate_gas(tx)
signature = tx.sign_typed_data(self._accounts[sender], estimated_gas)
raw_tx = tx.rlp_encode(signature, estimated_gas)

Expand Down Expand Up @@ -254,6 +245,30 @@ def get_balance(self, addr: Address):
def set_balance(self, addr: Address, value: int):
self._rpc.fetch("hardhat_setBalance", [addr, to_hex(value)])

def _estimate_gas(self, tx: DeployTransaction) -> int:
estimate_msg = tx.get_estimate_msg()
try:
estimated_gas = self._rpc.fetch(
"eth_estimateGas", [estimate_msg.as_json_dict()]
)
return int(estimated_gas, 16)
except RPCError as e:
compute = self._compute(tx.get_estimate_msg())
if compute.is_error:
raise compute.error
raise _EstimateGasFailed(e) from e

def _compute(self, args: ZksyncMessage):
try:
trace_call = self._rpc.fetch(
"debug_traceCall",
[args.as_json_dict(), "latest", {"tracer": "callTracer"}],
)
return ZksyncComputation.from_call_trace(trace_call)
except (RPCError, HTTPError):
output = self._rpc.fetch("eth_call", [args.as_json_dict(), "latest"])
return ZksyncComputation(args, bytes.fromhex(output.removeprefix("0x")))


def _hash_code(bytecode: bytes) -> bytes:
"""
Expand Down
81 changes: 52 additions & 29 deletions boa_zksync/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass, field
from functools import cached_property
from typing import Optional

import rlp
from boa.contracts.vyper.vyper_contract import VyperDeployer
Expand Down Expand Up @@ -51,8 +52,8 @@ class DeployTransaction:
Represents a transaction context for a zkSync deployment transaction.
"""

sender: str
to: str
sender: Address
to: Address
gas: int
gas_price: int
max_priority_fee_per_gas: int
Expand All @@ -66,26 +67,21 @@ class DeployTransaction:
chain_id: int
paymaster_params: tuple[int, bytes] | None

def get_estimate_tx(self):
def get_estimate_msg(self) -> "ZksyncMessage":
bytecodes = [self.bytecode] + self.dependency_bytecodes
return {
"transactionType": f"0x{_EIP712_TYPE.hex()}",
"chain_id": self.chain_id,
"from": self.sender,
"to": self.to,
"gas": f"0x{self.gas:0x}",
"gasPrice": f"0x{self.gas_price:0x}",
"maxPriorityFeePerGas": f"0x{self.max_priority_fee_per_gas :0x}",
"nonce": f"0x{self.nonce:0x}",
"value": f"0x{self.value:0x}",
"data": f"0x{self.calldata.hex()}",
"eip712Meta": {
"gasPerPubdata": f"0x{_GAS_PER_PUB_DATA_DEFAULT:0x}",
"factoryDeps": [
[int(byte) for byte in bytecode] for bytecode in bytecodes
],
},
}
return ZksyncMessage(
transaction_type=_EIP712_TYPE,
chain_id=self.chain_id,
sender=self.sender,
to=self.to,
gas=self.gas,
gas_price=self.gas_price,
max_priority_fee_per_gas=self.max_priority_fee_per_gas,
nonce=self.nonce,
value=self.value,
data=self.calldata,
factory_deps=bytecodes,
)

def sign_typed_data(
self, account: Account, estimated_gas: int
Expand Down Expand Up @@ -175,7 +171,9 @@ def global_ctx(self):

@cached_property
def vyper(self) -> CompilerData:
return compiler_data(self.source_code, self.contract_name, VyperDeployer, settings=self.settings)
return compiler_data(
self.source_code, self.contract_name, VyperDeployer, settings=self.settings
)

@cached_property
def settings(self):
Expand All @@ -189,22 +187,47 @@ class ZksyncMessage:
gas: int
value: int
data: bytes
transaction_type: Optional[bytes] = None
chain_id: Optional[int] = None
gas_price: Optional[int] = None
max_priority_fee_per_gas: Optional[int] = None
nonce: Optional[int] = None
factory_deps: Optional[list[bytes]] = None

@property
def code_address(self) -> bytes:
# this is used by boa to find the contract address for stack traces
return to_bytes(self.to)

def as_json_dict(self, sender_field="from"):
return fixup_dict(
{
sender_field: self.sender,
"to": self.to,
"gas": self.gas,
"value": self.value,
"data": to_hex(self.data),
def to_hex_optional(value):
return None if value is None else to_hex(value)

args = {
sender_field: str(self.sender),
"to": str(self.to),
"gas": self.gas,
"value": self.value,
"data": self.data,
"transactionType": self.transaction_type,
"chain_id": self.chain_id,
"gasPrice": self.gas_price,
"maxPriorityFeePerGas": self.max_priority_fee_per_gas,
"nonce": self.nonce,
}
meta = (
{}
if self.factory_deps is None
else {
"eip712Meta": {
"gasPerPubdata": f"0x{_GAS_PER_PUB_DATA_DEFAULT:0x}",
"factoryDeps": [
[int(byte) for byte in dep] for dep in self.factory_deps
],
}
}
)
return {**fixup_dict(args), **meta}

def as_tx_params(self):
return self.as_json_dict(sender_field="from_")
Expand Down
6 changes: 5 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ def zksync_env(account):
def zksync_sepolia_fork(account):
old_env = boa.env
fork_url = os.getenv("FORK_URL", "https://sepolia.era.zksync.dev")
boa_zksync.set_zksync_fork(fork_url, block_identifier=1689570, node_args=("--show-calls", "all", "--show-outputs"))
boa_zksync.set_zksync_fork(
fork_url,
block_identifier=1689570,
node_args=("--show-calls", "all", "--show-outputs"),
)
boa.env.add_account(account, force_eoa=True)
yield boa.env
boa.set_env(old_env)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from boa import BoaError
from boa.contracts.base_evm_contract import StackTrace

from boa_zksync.environment import ZERO_ADDRESS

STARTING_SUPPLY = 100


Expand All @@ -16,6 +18,7 @@ def simple_contract(zksync_env):
def __init__(t: uint256):
self.totalSupply = t
self.balances[self] = t
assert self.totalSupply > 0
@external
def update_total_supply(t: uint16) -> uint256:
Expand All @@ -35,6 +38,20 @@ def test_total_supply(simple_contract):
assert simple_contract.totalSupply() == STARTING_SUPPLY * 3


def test_constructor_reverts(simple_contract):
code = """
interface HasName:
def name() -> String[32]: view
@external
def __init__(impl: HasName):
assert impl.name() == "crvUSD"
"""
with pytest.raises(BoaError) as ctx:
boa.loads(code, ZERO_ADDRESS, name="ConstructorRevert")
assert ctx.value.args == ("Revert(b'assert impl.name() == \"crvUSD\"')",)


def test_blueprint(zksync_env):
blueprint_code = """
val: public(uint256)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def set_implementation(_implementation: address):
"""
c = boa.loads(code)
assert c.implementation() == ZERO_ADDRESS
boa.env.set_balance(boa.env.eoa, 10 ** 20)
boa.env.set_balance(boa.env.eoa, 10**20)
c.set_implementation(c.address)
assert c.implementation() == c.address

Expand Down

0 comments on commit 79fc445

Please sign in to comment.