Skip to content

Commit

Permalink
Parse logs
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielSchiavini committed May 14, 2024
1 parent 7a4f7fa commit c85d6ed
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 16 deletions.
8 changes: 5 additions & 3 deletions boa_zksync/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from shutil import which
from tempfile import TemporaryDirectory

from boa.rpc import to_bytes

from boa_zksync.types import ZksyncCompilerData


Expand Down Expand Up @@ -38,10 +40,10 @@ def compile_zksync(
with open(filename) as file:
source_code = file.read()

kwargs = output[filename.removeprefix("./")]
bytecode = bytes.fromhex(kwargs.pop("bytecode").removeprefix("0x"))
compile_output = output[filename.removeprefix("./")]
bytecode = to_bytes(compile_output.pop("bytecode"))
return ZksyncCompilerData(
contract_name, source_code, compiler_args, bytecode, **kwargs
contract_name, source_code, compiler_args, bytecode, **compile_output
)


Expand Down
46 changes: 39 additions & 7 deletions boa_zksync/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
detect_expr_type,
)
from boa.contracts.vyper.vyper_contract import VyperContract
from boa.rpc import to_bytes
from boa.rpc import to_int, to_bytes
from boa.util.abi import Address
from cached_property import cached_property
from vyper.semantics.analysis.base import VarInfo
from vyper.semantics.types.function import ContractFunctionT
Expand All @@ -26,15 +27,18 @@ def eval(self, code):

@contextmanager
def override_vyper_namespace(self):
c = VyperContract(
with self.vyper_contract.override_vyper_namespace():
yield

@cached_property
def vyper_contract(self):
return VyperContract(
self.compiler_data.vyper,
env=self.env,
override_address=self.address,
skip_initcode=True,
filename=self.filename,
)
with c.override_vyper_namespace():
yield

@cached_property
def _storage(self):
Expand All @@ -57,6 +61,31 @@ def internal():
setattr(internal, fn.name, ZksyncInternalFunction(typ, self))
return internal

def get_logs(self):
receipt = self.env.last_receipt
if not receipt:
raise ValueError("No logs available")

receipt_source = Address(receipt["contractAddress"] or receipt["to"])
if receipt_source != self.address:
raise ValueError(
f"Logs are no longer available for {self}, "
f"the last called contract was {receipt_source}"
)

c = self.vyper_contract
ret = []
for log in receipt["logs"]:
address = Address(log["address"])
if address != self.address:
continue
index = to_int(log["logIndex"])
topics = [to_int(topic) for topic in log["topics"]]
data = to_bytes(log["data"])
event = (index, address.canonical_address, topics, data)
ret.append(c.decode_log(event))
return ret


class _ZksyncInternal(ABIFunction):
"""
Expand All @@ -76,10 +105,13 @@ def source_code(self):

def __call__(self, *args, **kwargs):
env = self.contract.env
balance_before = env.get_balance(env.eoa)
env.set_balance(env.eoa, 10**20)
env.set_code(self.contract.address, self._override_bytecode)
try:
return super().__call__(*args, **kwargs)
finally:
env.set_balance(env.eoa, balance_before)
env.set_code(self.contract.address, self.contract.compiler_data.bytecode)


Expand Down Expand Up @@ -155,9 +187,9 @@ def __init__(self, code: str, contract: ZksyncContract):
abi = {
"anonymous": False,
"inputs": [],
"outputs": (
[{"name": "eval", "type": typ.abi_type.selector_name()}] if typ else []
),
"outputs": [
{"name": "eval", "type": typ.abi_type.selector_name()}
] if typ else [],
"name": "__boa_debug__",
"type": "function",
}
Expand Down
3 changes: 1 addition & 2 deletions boa_zksync/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from boa import Env
from boa.contracts.abi.abi_contract import ABIContractFactory, ABIFunction
from boa.rpc import to_bytes
from boa.util.abi import Address

from boa_zksync.compile import compile_zksync, compile_zksync_source
Expand Down Expand Up @@ -73,7 +72,7 @@ def at(self, address: Address | str) -> ZksyncContract:
def deploy_as_blueprint(self, *args, **kwargs) -> ZksyncContract:
"""
In zkSync, any contract can be used as a blueprint.
Note that we do need constructor arguments for this.
Note that we do need constructor arguments for deploying a blueprint.
"""
return self.deploy(*args, **kwargs)

Expand Down
8 changes: 6 additions & 2 deletions boa_zksync/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from boa.environment import _AddressType
from boa.interpret import json
from boa.network import NetworkEnv, _EstimateGasFailed
from boa.rpc import RPC, EthereumRPC, to_hex, RPCError
from boa.rpc import RPC, EthereumRPC, to_hex, RPCError, to_int
from boa.util.abi import Address
from eth.exceptions import VMError
from eth_account import Account
Expand All @@ -36,6 +36,7 @@ def __init__(self, rpc: str | RPC, *args, **kwargs):
super().__init__(rpc, *args, **kwargs)
self.evm = None # not used in zkSync
self.eoa = self.generate_address("eoa")
self.last_receipt: dict | None = None

@property
def vm(self):
Expand Down Expand Up @@ -125,6 +126,7 @@ def execute_code(
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
Expand Down Expand Up @@ -201,6 +203,7 @@ def deploy_code(

tx_hash = self._rpc.fetch("eth_sendRawTransaction", ["0x" + raw_tx.hex()])
receipt = self._rpc.wait_for_tx_receipt(tx_hash, self.tx_settings.poll_timeout)
self.last_receipt = receipt
return Address(receipt["contractAddress"]), bytecode

def get_code(self, address: Address) -> bytes:
Expand Down Expand Up @@ -228,7 +231,8 @@ def generate_address(self, alias: Optional[str] = None) -> _AddressType:
return address

def get_balance(self, addr: Address):
return self._rpc.fetch("eth_getBalance", [addr, "latest"])
balance = self._rpc.fetch("eth_getBalance", [addr, "latest"])
return to_int(balance)

def set_balance(self, addr: Address, value: int):
self._rpc.fetch("hardhat_setBalance", [addr, to_hex(value)])
Expand Down
11 changes: 11 additions & 0 deletions boa_zksync/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ class ZksyncComputation:
output: bytes | None = None
error: VMError | None = None
children: list["ZksyncComputation"] = field(default_factory=list)
gas_used: int = 0
revert_reason: str = None
type: str = "Call"
value: int = 0

@classmethod
def from_call_trace(cls, output: dict) -> "ZksyncComputation":
Expand All @@ -236,6 +240,10 @@ def from_call_trace(cls, output: dict) -> "ZksyncComputation":
output=to_bytes(output["output"]),
error=error,
children=[cls.from_call_trace(call) for call in output.get("calls", [])],
gas_used=int(output["gasUsed"], 16),
revert_reason=output.get("revertReason"),
type=output.get("type", "Call"),
value=int(output.get("value", "0x"), 16),
)

@classmethod
Expand Down Expand Up @@ -277,3 +285,6 @@ def raise_if_error(self) -> None:
"""
if self.error:
raise self.error

def get_gas_used(self):
return self.gas_used
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

@pytest.fixture(scope="module")
def zksync_env(account):
boa.interpret.disable_cache() # todo: remove this when api is stable
old_env = boa.env
boa_zksync.set_zksync_test_env()
boa.env.add_account(account, force_eoa=True)
Expand All @@ -18,7 +19,7 @@ def zksync_env(account):


@pytest.fixture(scope="module")
def zksync_fork_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)
Expand Down
24 changes: 24 additions & 0 deletions tests/test_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,27 @@ def foo(x: uint256) -> uint256:
assert contract._storage.bar.get() == 123
assert contract.eval("self.bar = 456") is None
assert contract.eval("self.bar") == 456


def test_logs(zksync_env):
code = """
event Transfer:
sender: indexed(address)
receiver: indexed(address)
value: uint256
@external
def __init__(supply: uint256):
log Transfer(empty(address), msg.sender, supply)
@external
def transfer(_to : address, _value : uint256) -> bool:
log Transfer(msg.sender, _to, _value)
return True
"""
contract = boa.loads(code, 100)
assert [str(e) for e in contract.get_logs()] == ["Transfer(sender=0x0000000000000000000000000000000000000000, receiver=0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A, value=100)"]

to = boa.env.generate_address()
contract.transfer(to, 10)
assert [str(e) for e in contract.get_logs()] == [f"Transfer(sender={boa.env.eoa}, receiver={to}, value=10)"]
2 changes: 1 addition & 1 deletion tests/test_fork.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import boa


def test_dummy_contract(zksync_fork_env):
def test_dummy_contract(zksync_sepolia_fork):
code = """
@external
@view
Expand Down

0 comments on commit c85d6ed

Please sign in to comment.