Skip to content

Commit

Permalink
error traces wip
Browse files Browse the repository at this point in the history
  • Loading branch information
AlbertoCentonze committed Jan 20, 2025
1 parent 9e95799 commit e52d750
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 21 deletions.
75 changes: 58 additions & 17 deletions boa/contracts/vvm/vvm_contract.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,67 @@
from functools import cached_property
from typing import Optional

from boa.contracts.abi.abi_contract import ABIContractFactory, ABIFunction
from boa.contracts.abi.abi_contract import ABIContract, ABIContractFactory, ABIFunction
from boa.contracts.base_evm_contract import StackTrace
from boa.environment import Env
from boa.util.abi import Address
from boa.util.eip5202 import generate_blueprint_bytecode


class VVMDeployer:
class VVMContract(ABIContract):
def __init__(
self,
name: str,
abi: list[dict],
functions: list[ABIFunction],
events: list[dict],
address: Address,
source_map: dict,
**kwargs,
):
self.source_map = source_map
super().__init__(name, abi, functions, events, address, **kwargs)

def stack_trace(self, computation):
code_stream = computation.code

ast_map = self.source_map["pc_pos_map"]

error = None
for pc in reversed(code_stream._trace):
pc = str(pc)
if pc in ast_map.keys():
error = ast_map[pc]

ret = StackTrace([str(error)])
return ret


class VVMDeployer(ABIContractFactory):
"""
A deployer that uses the Vyper Version Manager (VVM).
This allows deployment of contracts written in older versions of Vyper that
can interact with new versions using the ABI definition.
"""

def __init__(self, abi, bytecode, name, filename):
def __init__(self, abi, bytecode, name, filename, source_code, source_map):
"""
Initialize a VVMDeployer instance.
:param abi: The contract's ABI.
:param bytecode: The contract's bytecode.
:param filename: The filename of the contract.
"""
self.abi: dict = abi
self.bytecode: bytes = bytecode
self.name: Optional[str] = name
self.filename: str = filename
self.source_map: dict = source_map
self.source_code = source_code
super().__init__(name, abi, filename=filename)

@classmethod
def from_compiler_output(cls, compiler_output, name, filename):
def from_compiler_output(cls, compiler_output, name, filename, source_code):
abi = compiler_output["abi"]
bytecode_nibbles = compiler_output["bytecode"]
bytecode = bytes.fromhex(bytecode_nibbles.removeprefix("0x"))
return cls(abi, bytecode, name, filename)

@cached_property
def factory(self):
return ABIContractFactory.from_abi_dict(
self.abi, name=self.name, filename=self.filename
)
source_map = compiler_output["source_map"]
return cls(abi, bytecode, name, filename, source_code, source_map)

@cached_property
def constructor(self):
Expand Down Expand Up @@ -74,6 +99,7 @@ def deploy(self, *args, contract_name=None, env=None, **kwargs):

@cached_property
def _blueprint_deployer(self):
# TODO: this can definitely be removed with some refactoring
# TODO: add filename
return ABIContractFactory.from_abi_dict([])

Expand Down Expand Up @@ -104,5 +130,20 @@ def deploy_as_blueprint(self, env=None, blueprint_preamble=None, **kwargs):
def __call__(self, *args, **kwargs):
return self.deploy(*args, **kwargs)

def at(self, address, nowarn=False):
return self.factory.at(address, nowarn=nowarn)
def at(self, address: Address | str, nowarn=False) -> VVMContract:
"""
Create an VVMContract object for a deployed contract at `address`.
"""
address = Address(address)
contract = VVMContract(
self._name,
self.abi,
self.functions,
self.events,
address,
self.source_map,
nowarn=nowarn,
)

contract.env.register_contract(address, contract)
return contract
9 changes: 5 additions & 4 deletions boa/interpret.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,15 +295,16 @@ def _compile():
# separate _handle_output and _compile so that we don't trample
# name and filename in the VVMDeployer from separate invocations
# (with different values for name+filename).
def _handle_output(compiled_src):
def _handle_output(compiled_src, source_code):
# pprint.pp(compiled_src)
compiler_output = compiled_src["<stdin>"]
return VVMDeployer.from_compiler_output(
compiler_output, name=name, filename=filename
compiler_output, source_code=source_code, name=name, filename=filename
)

# Ensure the cache is initialized
if _disk_cache is None:
return _handle_output(_compile())
return _handle_output(_compile(), source_code)

# Generate a unique cache key
cache_key = f"{source_code}:{version}"
Expand All @@ -317,7 +318,7 @@ def _handle_output(compiled_src):
_disk_cache.invalidate(cache_key)
ret = _disk_cache.caching_lookup(cache_key, _compile)

return _handle_output(ret)
return _handle_output(ret, source_code)


def from_etherscan(
Expand Down
26 changes: 26 additions & 0 deletions tests/unitary/contracts/vvm/test_vvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,29 @@ def baz(x: uint256, _from: address, y: uint256) -> (MyStruct1, MyStruct2):
# assert type(v).__name__ == "MyStruct2"
assert v._0 == addy
assert v.x == 4


def test_vvm_source_maps():
code = """
# pragma version 0.3.10
struct MyStruct1:
x: uint256
@external
def foo(x: uint256) -> MyStruct1:
if x == 0:
return MyStruct1({x: x})
if x == 1:
raise "x is 1"
return MyStruct1({x: x})
@external
def bar() -> uint256:
return 42
"""

c = boa.loads(code)
c.foo(1)

0 comments on commit e52d750

Please sign in to comment.