Giter Club home page Giter Club logo

titanoboa's People

Contributors

benber86 avatar bout3fiddy avatar charles-cooper avatar controlcpluscontrolv avatar danielschiavini avatar danielvf avatar imachonky avatar j0ker70 avatar mo-anon avatar skellet0r avatar trocher avatar tserg avatar z80dev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

titanoboa's Issues

handle stack traces with unknown contracts properly

    interesting -- i was thinking about this and instead of truncating, do we want to try to recurse and fill in the missing stack frames with something like "unknown location in unknown contract 0x00..ab"? i'm ok either way but the latter seems like better UX. this could be important if the external contract has called back into known contracts.

Originally posted by @charles-cooper in #54 (comment)

bug: cannot access state vars via `boa.eval`

To replicate, need to run the following test:

import pytest
import boa


source_code = """
A: constant(uint256) = 10

@external
def __init__():
    pass
"""


@pytest.fixture(scope="module")
def contract():
    c = boa.loads(source_code)
    return c


def test_get_constant_var(contract):
    A = contract.eval("A")
    assert A == 10

Which gives the following error:

________________________________________________________________ test_get_constant_var ________________________________________________________________

contract = <VyperContract at 0x0000000000000000000000000000000000000066, compiled with vyper-0.3.7+6020b8b>

    def test_get_constant_var(contract):
>       A = contract.eval("A")

tests/unitary/test_get_state_variables.py:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
boa/vyper/contract.py:720: in eval
    bytecode, source_map, typ = generate_bytecode_for_arbitrary_stmt(stmt, self)
boa/vyper/compiler_utils.py:122: in generate_bytecode_for_arbitrary_stmt
    return _compile_vyper_function(wrapper_code, contract)[1:]
boa/vyper/compiler_utils.py:39: in _compile_vyper_function
    ir = generate_ir_for_function(ast, sigs, global_ctx, False)
venv/lib/python3.8/site-packages/vyper/codegen/function_definitions/common.py:62: in generate_ir_for_function
    o = generate_ir_for_external_function(code, sig, context, skip_nonpayable_check)
venv/lib/python3.8/site-packages/vyper/codegen/function_definitions/external_function.py:182: in generate_ir_for_external_function
    body += [parse_body(code.body, context, ensure_terminated=True)]
venv/lib/python3.8/site-packages/vyper/codegen/stmt.py:424: in parse_body
    ir = parse_stmt(stmt, context)
venv/lib/python3.8/site-packages/vyper/codegen/stmt.py:398: in parse_stmt
    return Stmt(stmt, context).ir_node
venv/lib/python3.8/site-packages/vyper/codegen/stmt.py:40: in __init__
    self.ir_node = fn()
venv/lib/python3.8/site-packages/vyper/codegen/stmt.py:374: in parse_Return
    ir_val = Expr(self.stmt.value, self.context).ir_node
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <vyper.codegen.expr.Expr object at 0x105531d60>
node = vyper.ast.nodes.Name:
     4 def __boa_debug__() -> uint256:
---> 5     return A
------------------^
context = <vyper.codegen.context.Context object at 0x1055314c0>

    def __init__(self, node, context):
        self.expr = node
        self.context = context
    
        if isinstance(node, IRnode):
            # TODO this seems bad
            self.ir_node = node
            return
    
        fn = getattr(self, f"parse_{type(node).__name__}", None)
        if fn is None:
            raise TypeCheckFailure(f"Invalid statement node: {type(node).__name__}")
    
        self.ir_node = fn()
        if self.ir_node is None:
>           raise TypeCheckFailure(f"{type(node).__name__} node did not produce IR. {self.expr}")
E           vyper.exceptions.TypeCheckFailure: Name node did not produce IR. vyper.ast.nodes.Name:
E                4 def __boa_debug__() -> uint256:
E           ---> 5     return A
E           ------------------^
E           
E           This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
E           https://github.com/vyperlang/vyper/issues/new?template=bug.md

venv/lib/python3.8/site-packages/vyper/codegen/expr.py:77: TypeCheckFailure
=============================================================== short test summary info ===============================================================
FAILED tests/unitary/test_get_state_variables.py::test_get_constant_var - vyper.exceptions.TypeCheckFailure: Name node did not produce IR. vyper.ast.nodes.Name:

bug: error decoding empty address string

Traceback:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/test/plugin.py:32: in f
    t(*args, **kwargs)
tests/unitary/tricrypto/test_exchange.py:[39](https://github.com/curvefi/tricrypto-ng/actions/runs/3781658949/jobs/6428725199#step:6:40): in test_exchange_all
    tricrypto_swap_with_deposit.exchange(i, j, 10**6, 0)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/contract.py:876: in __call__
    return self.contract.marshal_to_python(computation, typ)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/contract.py:590: in marshal_to_python
    self.handle_error(computation)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/contract.py:617: in handle_error
    raise BoaError(self.vyper_stack_trace(computation))
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/contract.py:624: in vyper_stack_trace
    ret = StackTrace([ErrorDetail.from_computation(self, computation)])
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/contract.py:176: in from_computation
    frame_detail = contract.debug_frame(computation)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/contract.py:[49](https://github.com/curvefi/tricrypto-ng/actions/runs/3781658949/jobs/6428725199#step:6:50)1: in debug_frame
    frame_detail[k] = decode_vyper_object(mem.read(ofst, size), v.typ)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/decoder_utils.py:68: in decode_vyper_object
    return [
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/decoder_utils.py:69: in <listcomp>
    decode_vyper_object(mem[i * n : i * n + n], typ.subtype)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/boa/vyper/decoder_utils.py:[51](https://github.com/curvefi/tricrypto-ng/actions/runs/3781658949/jobs/6428725199#step:6:52): in decode_vyper_object
    return to_checksum_address(mem[12:32].tobytes())
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/site-packages/eth_utils/address.py:112: in to_checksum_address
    norm_address = to_normalized_address(value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

image

Fix: boa > vyper > decoder_utils

def decode_vyper_object(mem, typ):
    if is_bytes_m_type(typ):
        # TODO tag return value like `vyper_object` does
        return mem[: typ._bytes_info.m].tobytes()
    if is_base_type(typ, "address"):
        addr = ''
        if mem[12:32].nbytes:
            addr = to_checksum_address(mem[12:32].tobytes())
        return addr

trouble installing boa alongside brownie

Overview

Dependency conflict between the two projects does not allow both projects to work in the same project

Repro steps

python3 -m venv .venv
source .venv/bin/activate

pip install eth-brownie
pip install ../titanoboa

brownie compile

Brownie v1.19.0 - Python development framework for Ethereum

Compiling contracts...
  Vyper version: 0.3.4
Generating build data...
 - test
  File "brownie/_cli/__main__.py", line 64, in main
    importlib.import_module(f"brownie._cli.{cmd}").main()
  File "brownie/_cli/compile.py", line 50, in main
    proj = project.load()
  File "brownie/project/main.py", line 768, in load
    return Project(name, project_path)
  File "brownie/project/main.py", line 188, in __init__
    self.load()
  File "brownie/project/main.py", line 245, in load
    self._compile(changed, self._compiler_config, False)
  File "brownie/project/main.py", line 100, in _compile
    build_json = compiler.compile_and_format(
  File "brownie/project/compiler/__init__.py", line 142, in compile_and_format
    build_json.update(generate_build_json(input_json, output_json, compiler_data, silent))
  File "brownie/project/compiler/__init__.py", line 328, in generate_build_json
    build_json[contract_alias] = vyper._get_unique_build_json(
  File "brownie/project/compiler/vyper.py", line 255, in _get_unique_build_json
    pc_map, statement_map, branch_map = _generate_coverage_data(
  File "brownie/project/compiler/vyper.py", line 314, in _generate_coverage_data
    source = source_map.popleft()
IndexError: pop from an empty deque

More info

I tried installing eth-brownie and then overwriting the vyper version with pip install git+https://github.com/vyperlang/vyper@573d77f7af177fb3bf2be2a14d16e3b6c477a0fc, and the same error occured. so this isnt technically an issue with boa, but something to do with how brownie interacts with vyper==0.3.4

But anyways, it means that the workaround to install brownie and boa together doesnt really work

`get_gas_used` returns too high numbers with multiple functions in the same contract

In order to reproduce this bug you can use my Math.vy here.

If you do the following (calling the function is_negative) you will get 338 gas usage.

>> import boa
>> c = boa.load("src/utils/Math.vy")
>> c.is_negative(-13)
True
>> c._computation.get_gas_used()
338

However that's actually the wrong figure since if you put only the function into an empty contract Math2.vy, you will get the correct figure of 131. And this around +200 gas usage is consistent across other functions. So the contract size somehow messes with the gas computation which should not be the case.

image

Coverage Broken - Cannot rewrite plugin for hook

I made a minimal repro , which sets up a simple vyper project.
When run with

poetry shell
pytest .

All tests should pass, but upon running

coverage run -m pytest . 

The tests will pass, but coverage will not run, exiting with the following error

../../../Library/Caches/pypoetry/virtualenvs/minimal-repro-NwtGl1oq-py3.10/lib/python3.10/site-packages/_pytest/config/__init__.py:1191
  /Users/controlc/Library/Caches/pypoetry/virtualenvs/minimal-repro-NwtGl1oq-py3.10/lib/python3.10/site-packages/_pytest/config/__init__.py:1191: PytestAssertRewriteWarning: Module already imported so cannot be rewritten: boa
    self._mark_plugins_for_rewrite(hook)

../../../Library/Caches/pypoetry/virtualenvs/minimal-repro-NwtGl1oq-py3.10/lib/python3.10/site-packages/_pytest/config/__init__.py:1191
  /Users/controlc/Library/Caches/pypoetry/virtualenvs/minimal-repro-NwtGl1oq-py3.10/lib/python3.10/site-packages/_pytest/config/__init__.py:1191: PytestAssertRewriteWarning: Module already imported so cannot be rewritten: hypothesis
    self._mark_plugins_for_rewrite(hook)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

log events without eval

Currently one needs to do:

token_contract.eval(f"log Transfer(empty(address), {addr}, {amount})")

make it so that:

token_contract.log.Transfer(0x0, addr, amount)

installing from github "No module named boa.vyper"

Overview

for some reason, installing from github as described in the readme fails during import

Repro steps

python3 -m venv test
source venv/bin/activate
pip install git+https://github.com/vyperlang/titanoboa
python
import boa
Traceback (most recent call last):
File "", line 1, in
File "/home/tonz/projects/titanoboa/test/lib/python3.8/site-packages/boa/init.py", line 4, in
from boa.interpret import contract, load, loads
File "/home/tonz/projects/titanoboa/test/lib/python3.8/site-packages/boa/interpret.py", line 5, in
from boa.contract import VyperContract, VyperFactory
File "/home/tonz/projects/titanoboa/test/lib/python3.8/site-packages/boa/contract.py", line 25, in
from boa.vyper.decoder_utils import decode_vyper_object
ModuleNotFoundError: No module named 'boa.vyper'

Notes

one way to get around this issue is to clone https://github.com/vyperlang/titanoboa. then when you run import boa from within the titanoboa directory, it works

another way I got it to work was from within a poetry shell. again, simply running poetry add git+https://github.com/vyperlang/titanoboa gives the same error. but if you run pip install -e ../titanoboa from a poetry shell, it works. interestingly, running pip install -e ../titanoboa on a regular venv gives an error "File setup.py" not found. Also, running poetry add ../titanoboa also gives an error (Directory does not seem to be a python package). Not sure what the difference is.

add EVM forking

probably expose it as a (monkey-patched) method on Env.vm. the interface should be something like

boa.env.vm.fork_from_rpc(<rpc args>)

this would override the internal account and state dbs to look up from a state cache, hitting the RPC on a cache miss.

rework environment variables API

right now chain environment variables are set using boa.env.vm.patch, ex. boa.env.vm.patch.chain_id = 5.

but it might be friendlier for it to be like boa.env.chain.id = 5 (which would match the vyper naming as well).

bonus points if we can figure out how to make it a context manager at the same time, ex.

with boa.env.chain.id(5):
  ...
# equivalent to
tmp = boa.env.chain.id
boa.env.chain.id = 5
try:
  ...
finally:
  boa.env.chain.id = tmp

docs: pytest and hypothesis recipes

include:

  • basic fuzzing
  • document boa.test module, including hypothesis strategies
  • link to eth-stdlib strategies?
  • contracts should generally be module-level fixtures (for performance)
  • state machine examples

feature: jupyterlab extension

it would be cool to have https://github.com/vyperlang/titanoboa/blob/2c2a2664ad3fa566ddda8a43851cd6f14ab9276a/boa/integrations/jupyter.py work with jupyterlab (on account of jupyterlab being the new thing, it has an IDE and workspace etc). the current method for loading ethers does not seem to work though, it fails with require not defined, seems to be related to Calysto/calysto_processing#11 (comment). it looks like the thing to do is write a proper jupyterlab extension, which would be cool too since we could support vyper files natively.

smol bug: incompatible versions in the resolved dependencies

% pip-compile
Could not find a version that matches eth-typing<3.0.0,<4.0.0,>=2.3.0,>=3.0.0 (from py-evm==0.5.0a3->titanoboa==0.1.2->-r requirements.in (line 3))
Tried: 0.2.0, 0.2.0, 0.3.0, 0.3.0, 0.3.1, 0.3.1, 1.0.0, 1.0.0, 1.1.0, 1.1.0, 1.3.0, 1.3.0, 2.0.0, 2.0.0, 2.1.0, 2.1.0, 2.2.0, 2.2.0, 2.2.1, 2.2.1, 2.2.2, 2.2.2, 2.3.0, 2.3.0, 3.0.0, 3.0.0, 3.1.0, 3.1.0
There are incompatible versions in the resolved dependencies:
  eth-typing (from titanoboa==0.1.2->-r requirements.in (line 3))
  eth-typing<4.0.0,>=3.0.0 (from eth-abi==3.0.1->titanoboa==0.1.2->-r requirements.in (line 3))
  eth-typing<3.0.0,>=2.3.0 (from py-evm==0.5.0a3->titanoboa==0.1.2->-r requirements.in (line 3))

this happens when I try to pip-compile:

pip-tools
jupyter-book
git+https://github.com/vyperlang/titanoboa
ipython
black

Add __init__ example

Issue

Current README example only shows basic usage, running with init for vyper contracts is currently unclear.
Propose adding an examples folder to store some examples that users can work with and also updating README with init example.

bug: arrays are returned as tuples

Calling foo() returns (1, 2, 3) instead of [1, 2, 3].

@external
def foo() -> uint256[3]:
    return [1, 2, 3]
>>> boa.loads("@external\ndef foo() -> uint256[3]:\n\treturn [1,2,3]\n")
<VyperContract at 0x0000000000000000000000000000000000000067, compiled with vyper-0.3.8+847849c5>
>>> a = boa.loads("@external\ndef foo() -> uint256[3]:\n\treturn [1,2,3]\n")
>>> a.foo()
(1, 2, 3)

local var frame incorrect when revert with reason string is used

ex.

@external
def foo():
    x: uint256 = 5
    raise "reason"

in the traceback, 32 will be displayed as the value for x since the encoding of the reason string clobbers x.

there are two ways to fix this -- one is a short term fix, which is to optimize the handling of string literals so that the clobbering is not required here: https://github.com/vyperlang/vyper/blob/c71b0238bb8e804072a92f7ebc39cbafdb5da3e7/vyper/codegen/stmt.py#L195-L207

but the longer term issue is that local variables may be aliased in future - they may be deallocated after last use to reduce memory expansion. so we will need a plan to trace variables at the time that they are deallocated.

improvement: fixtures powered profiling

Since @charles-cooper introduced boa's shiny new gas profiler, it brings us closer to a framework that gives vyper devs very granular insights into their contracts. Just as a refresher, one can generate a line-by-line gas profile of their vyper code using the following steps (example):

In [1]: import boa
In [2]: boa.env.enable_gas_profiling()
In [3]: t = boa.load("examples/ERC20.vy", "Token", "TKN", 18, 0)
In [4]: alice = boa.env.generate_address()
In [5]: t.mint(alice, 100)
In [6]: t.line_profile().summary()
Out[6]: 
0x0000000000000000000000000000000000000066/examples/ERC20.vy net_tot_gas: 22215  self.balanceOf[_to] += _value
0x0000000000000000000000000000000000000066/examples/ERC20.vy net_tot_gas: 20144  self.totalSupply += _value
0x0000000000000000000000000000000000000066/examples/ERC20.vy net_tot_gas: 1807   log Transfer(empty(address), _to, _value)
0x0000000000000000000000000000000000000066/examples/ERC20.vy net_tot_gas: 121    assert msg.sender == self.minter  # rekt: non-minter tried to mint
0x0000000000000000000000000000000000000066/examples/ERC20.vy net_tot_gas: 22     assert _to != empty(address)
0x0000000000000000000000000000000000000066/examples/ERC20.vy net_tot_gas: 15     def mint(_to: address, _value: uint256):

One can imagine this can be combined with hypothesis and pytest fixtures to generate line-by-line gas reports. The user would set up a profile_* python method in the following manner (similar to how test_* works for pytest):

from hypothesis import given, strategies, settings

@given(strategies.integers(10**15, (10**10)**3 * 10**18))
@settings(max_examples=10000)
def profile_mint(erc20mock, alice, value):
    with boa.env.anchor():
        erc20mock.mint(alice, val)

To run profiling, it would be similar to pytest: python -m boa-profile -k "profile_mint"
And the output would be similar to a line profile, but perhaps with more information (some idea I thought about just now, so maybe not the prettiest way a gas report would look, but I could add more details here, I guess):

line:  self.balanceOf[_to] += _value ; net_tot_gas: 22215 ; std_tot_gas: 2000
line:  self.totalSupply += _value ; net_tot_gas: 20144  ; std_tot_gas: 100
line:  log Transfer(empty(address), _to, _value) ; net_tot_gas: 1807 ; std_tot_gas: 1000
line:  assert msg.sender == self.minter  # rekt: non-minter tried to mint ; net_tot_gas: 121 ; std_tot_gas: 10
line:  assert _to != empty(address) ; net_tot_gas: 22 ; std_tot_gas: 1
line:  def mint(_to: address, _value: uint256): ; net_tot_gas: 15 ; std_tot_gas: 15

The goal would be to save gas reports from this fuzz-style profiling. Devs could commit these reports into their git repositories as well. Gas reports like this could be pretty cool and advantageous for vyper devs.

What's unclear is how this gas report would look / what more detail it would have. Ideas here are welcome!

docs: storage tracing

we probably want to document the high level API -- VyperContract._storage: StorageModel

Feature Request -- Export / Save Snapshot to reload boa.env in prior state

Want to be able to export state of boa.env in a file that can be loaded by a different instantiation of boa.env. Currently working on this as follows:

under class VMPatcher:
~87

   def export_state(self):
       snap = {}
       for s, _ in self._patchables:
           for attr in s:
               snap[attr] = getattr(self, attr)
       snap['prev_hashes'] = list(snap['prev_hashes']) # can't pickle a generator
       return snap

   def load_state(self, snap: dict):
       snap['prev_hashes'] = (x for x in snap['prev_hashes']) # makes it a generator but not the same class as before -- issue?
       for s, _ in self._patchables:
           for attr in s:
               setattr(self, attr, snap[attr])

Under class ENV:
~399

    def export_state(self, file_name: str = "boa_env_state.pickle"):
        snap = self.vm.patch.export_state()
        out_file = "{}/{}".format(os.getcwd(), file_name)
        with open(out_file, "wb") as file:
            pickle.dump(snap, file)

    def load_state(self, file_name: str):
        with open(file_name, "rb") as file:
            snap = pickle.load(file)
        self.vm.patch.load_state(snap)

It all seems to work but the new env doesn't have the same compiled bytecode when load_partial(contract_source_code.vy).at(contract_address_in_first_env) is invoked:
warnings.warn(f"casted bytecode does not match compiled bytecode at {self}").

Obv. the second env isn't loading the bytecode from the first. I'm not familiar enough with py-evm to understand why or where the issue is -- welcome to suggestions.

ENV 1 Saves state

image

ENV 2 Loads state, but fails

Screenshot 2023-01-18 at 2 28 58 PM

``` --------------------------------------------------------------------------- InsufficientDataBytes Traceback (most recent call last) Cell In[4], line 1 ----> 1 MBT_connect.symbol()

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/boa/vyper/contract.py:895, in VyperFunction.call(self, value, gas, sender, *args, **kwargs)
885 computation = self.env.execute_code(
886 to_address=self.contract.address,
887 bytecode=self._bytecode,
(...)
891 gas=gas,
892 )
894 typ = self.fn_signature.return_type
--> 895 return self.contract.marshal_to_python(computation, typ)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/boa/vyper/contract.py:627, in VyperContract.marshal_to_python(self, computation, vyper_typ)
624 return None
626 return_typ = calculate_type_for_external_return(vyper_typ)
--> 627 ret = abi_decode(return_typ.abi_type.selector_name(), computation.output)
629 # unwrap the tuple if needed
630 if not isinstance(vyper_typ, TupleType):

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/codec.py:180, in ABIDecoder.decode_single(self, typ, data)
177 decoder = self._registry.get_decoder(typ)
178 stream = self.stream_class(data)
--> 180 return decoder(stream)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:129, in BaseDecoder.call(self, stream)
128 def call(self, stream: ContextFramesBytesIO) -> Any:
--> 129 return self.decode(stream)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_utils/functional.py:45, in apply_to_return_value..outer..inner(*args, **kwargs)
43 @functools.wraps(fn)
44 def inner(*args, **kwargs) -> T: # type: ignore
---> 45 return callback(fn(*args, **kwargs))

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:175, in TupleDecoder.decode(self, stream)
172 @to_tuple
173 def decode(self, stream):
174 for decoder in self.decoders:
--> 175 yield decoder(stream)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:129, in BaseDecoder.call(self, stream)
128 def call(self, stream: ContextFramesBytesIO) -> Any:
--> 129 return self.decode(stream)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:144, in HeadTailDecoder.decode(self, stream)
143 def decode(self, stream):
--> 144 start_pos = decode_uint_256(stream)
146 stream.push_frame(start_pos)
147 value = self.tail_decoder(stream)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:129, in BaseDecoder.call(self, stream)
128 def call(self, stream: ContextFramesBytesIO) -> Any:
--> 129 return self.decode(stream)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:199, in SingleDecoder.decode(self, stream)
198 def decode(self, stream):
--> 199 raw_data = self.read_data_from_stream(stream)
200 data, padding_bytes = self.split_data_and_padding(raw_data)
201 value = self.decoder_fn(data)

File ~/Library/Caches/pypoetry/virtualenvs/peg-boa-livPFXpy-py3.10/lib/python3.10/site-packages/eth_abi/decoding.py:306, in FixedByteSizeDecoder.read_data_from_stream(self, stream)
303 data = stream.read(self.data_byte_size)
305 if len(data) != self.data_byte_size:
--> 306 raise InsufficientDataBytes(
307 "Tried to read {0} bytes. Only got {1} bytes".format(
308 self.data_byte_size,
309 len(data),
310 )
311 )
313 return data

InsufficientDataBytes: Tried to read 32 bytes. Only got 0 bytes

Error when running boa in vyper project folder

Generally when running some boa commands that imports vyper.version like boa.env throws an error ModuleNotFoundError: No module named 'vyper.version' when running from locally cloned vyper project folder.

This is due to sys.path searching for vyper locally before installed packages.

Thinking to add a line on README.md reflecting this and how to fix.

Bug: Successful vyper compile doesn't mean a successful boa load

My contract compiles fine, here is the contract:

# SPDX-License-Identifier: MIT
# @version ^0.3.7

# import contract.SimpleStorage as SimpleStorage # This imports it as an interface!
from . import SimpleStorage as SimpleStorage

# interface SimpleStorage:
#     def store(): nonpayable
#     def retrieve() -> uint256: view
#     def add_person(): nonpayable

contract_to_copy: address
list_of_simple_storage_contracts: public(DynArray[SimpleStorage, 10])

@external
def __init__(_contract_to_copy: address):
    self.contract_to_copy = _contract_to_copy


@external
def create_simple_storage_contract():
    deployed_simple_storage: address = create_copy_of(self.contract_to_copy)
    self.list_of_simple_storage_contracts.append(SimpleStorage(deployed_simple_storage))

@external
def storage_factory_store(_simple_storage_index: uint256, _simple_storage_number: uint256):
    # Address 
    # ABI
    self.list_of_simple_storage_contracts[_simple_storage_index].store(_simple_storage_number)

@external
@view
def storage_factory_retrieve(_simple_storage_index: uint256) -> uint256:
    return self.list_of_simple_storage_contracts[_simple_storage_index].retrieve()
 vyper StorageFactory.vy 
0x60206101b66000396000518060a01c6101b157604052346101b15760405160005561017e6100326100003961017e610000f36003361161000c57610166565b60003560e01c3461016c57634ed7246e8118610089576004361061016c57600054803b59811561016c578160381b6a620000003d81600b3d39f317815281600060208301853c600b8201601582016000f0801561016c579050905090506040526001546009811161016c5760018101600155604051816002015550005b636de8b70b81186100e1576044361061016c5760043560015481101561016c5760020154636057361d604052602435606052803b1561016c57600060406024605c6000855af16100de573d600060003e3d6000fd5b50005b63b24a471d8118610137576024361061016c57602060043560015481101561016c5760020154632e64cec1604052602060406004605c845afa610129573d600060003e3d6000fd5b60203d1061016c5760409050f35b63d479dfac8118610164576024361061016c5760043560015481101561016c576002015460405260206040f35b505b60006000fd5b600080fda165767970657283000307000b005b600080fd

But, boa doesn't load it fine:

$ python
Python 3.10.6 (v3.10.6:9c7b4bd164, Aug  1 2022, 17:13:48) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import boa
>>> storage_factory = boa.load("StorageFactory.vy")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/xxxxx/site-packages/boa/interpret.py", line 40, in load
    return loads(f.read(), *args, name=filename, **kwargs)
  File "/xxxxx/site-packages/boa/interpret.py", line 48, in loads
    return d.deploy(*args, **kwargs)
  File "/xxxxxsite-packages/boa/contract.py", line 70, in deploy
    return VyperContract(self.compiler_data, *args, **kwargs)
  File "/xxxxx/site-packages/boa/contract.py", line 368, in __init__
    fns = {fn.name: fn for fn in self.global_ctx._function_defs}
  File "/xxxxx/site-packages/boa/contract.py", line 449, in global_ctx
    return self.compiler_data.global_ctx
  File "/xxxxx/site-packages/vyper/compiler/phases.py", line 117, in global_ctx
    return generate_global_context(self.vyper_module_folded, self.interface_codes)
  File "/xxxxx/site-packages/vyper/compiler/phases.py", line 107, in vyper_module_folded
    module, storage_layout = self._folded_module
  File "/xxxxx/lib/python3.10/functools.py", line 981, in __get__
    val = self.func(instance)
  File "/xxxxx/site-packages/vyper/compiler/phases.py", line 101, in _folded_module
    return generate_folded_ast(
  File "/xxxxx/site-packages/vyper/compiler/phases.py", line 229, in generate_folded_ast
    validate_semantics(vyper_module_folded, interface_codes)
  File "/xxxxx/site-packages/vyper/semantics/validation/__init__.py", line 10, in validate_semantics
    add_module_namespace(vyper_ast, interface_codes)
  File "/xxxxx/site-packages/vyper/semantics/validation/module.py", line 38, in add_module_namespace
    ModuleNodeVisitor(vy_module, interface_codes, namespace)
  File "/xxxxx/site-packages/vyper/semantics/validation/module.py", line 83, in __init__
    err_list.raise_if_not_empty()
  File "/xxxxx/site-packages/vyper/exceptions.py", line 22, in raise_if_not_empty
    raise VyperException("\n\n".join(err_msg))
vyper.exceptions.VyperException: Compilation failed with the following errors:

UnknownType: No builtin or user-defined type named 'SimpleStorage'. 
  contract "StorageFactory.vy:13", line 13:50 
       12 contract_to_copy: address
  ---> 13 list_of_simple_storage_contracts: public(DynArray[SimpleStorage, 10])
  ----------------------------------------------------------^
       14


UndeclaredDefinition: Unknown interface: SimpleStorage. Did you mean 'ERC20Detailed', or maybe 'ERC165'?
  contract "StorageFactory.vy:5", line 5:0 
       4 # import contract.SimpleStorage as SimpleStorage # This imports it as an interface!
  ---> 5 from . import SimpleStorage as SimpleStorage

perf: improve performance of revert handling

revert handling is probably a performance hot spot, because it does a lot of work to make good error messages / tracebacks. however, in cases where the user only cares that reverts happen (e.g. with boa.reverts(...)), we don't actually need to construct the error message. investigate further

Windows installation fails due to `pyethash`

Installing titanoboa on Windows currently fails due to a build failure of pyethash:
image

  • Python version 3.9.2
  • Vyper version 0.3.4
  • OS: Windows 11 Pro (version 21H2)

The installation works smoothly using Ubuntu via WSL.

vyper imports do not work

when CompilerData is constructed, interfaces are not supplied:

def compiler_data(source_code: str, contract_name: str) -> CompilerData:
global _disk_cache
if _disk_cache is None:
return CompilerData(source_code, contract_name)
def func():
ret = CompilerData(source_code, contract_name)
ret.bytecode_runtime # force compilation to happen
return ret
return _disk_cache.caching_lookup(source_code, func)

probably, passing the result of get_interface_codes will work here (cf.
https://github.com/vyperlang/vyper/blob/55f31f8bc1444aaf2ad65ade45be6900b6212506/vyper/cli/vyper_compile.py#L292)

generate example cases when using hypothesis

we should have a special strategy which uses strategies taken from the state or contract storage. e.g. in the following example,

def __init__():
    self.foo = msg.sender
@given(addr=st.addresses)  # (?)
def test_bar(addr):
    ...

st.addresses should include whatever self.foo is as an example.

bug: error generating stack trace when external call fails on local fork

In contract.py's handle_error() boa will attempt to generate a stack trace when an exception is encountered.
In the case of an external call, it will look for the contract among the environment's contracts assuming that the contract has been previously deployed.
However, when calling an already deployed contract on a fork, the look up will come back empty handed.
boa currently does handle this scenario, so in the following lines:

        child_obj = self.env.lookup_contract(child.msg.code_address)
        child_trace = child_obj.vyper_stack_trace(child)

The attempt to call vyper_stack_trace will fail when the contract called is a "fork contract" and child_obj is None

To reproduce the bug:

import boa

if __name__ == '__main__':
    boa.env.fork(url='http://localhost:8545')
    test = boa.load("test.vy")
    print(test.test2())

test.vy:

@external
def test2() -> Bytes[32]:
    x: uint256 = 0
    return  raw_call(0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF,b'\xc6a\x06W\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',max_outsize=32)

perf: replacements for py-evm classes

we can probably replace AccountDB with something that doesn't deal with RLP or the trie at all. i am guessing this is a big performance bottleneck.

bug: error parsing reason comment on multi-line statements

The DevReason class is built with the assumption that an error can be pinpointed to a single line of code.
That assumption in itself is harmless, it just means that only the first line of a single line statement will be search for comments.
However, when parsing the line for comments, boa uses the Python tokenizer.
So if the multiline statement is not "closed" at the end of the first line, the Python tokenizer will throw an EOF in multi-line statement error.

image

For instance in the following contract:

@external
def test2() -> Bytes[32]:
    return raw_call(
    0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF,
    b'\xc6a\x06W\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
    max_outsize=32,
    )

The first line return raw_call( only will be sent to the tokenizer in ast_utils.py. Because the opening parenthesis is not closed, the tokenizer will throw an exception.

storage dump is wrong after reversion

storage dump will pick up the state after the state inside the computation already got rolled back. to properly fix this, we need to patch py-evm to save the state inside of apply_computation / apply_message before the state gets reverted (although, that would be a performance hit).

Bug: AttributeError: 'VyperDeployer' object has no attribute 'wrap'

I have a factory contract that has a function create_simple_storage_contract which returns the address of a contract it just deployed:

import boa

simple_storage = boa.load("SimpleStorage.vy")
storage_factory = boa.load("StorageFactory.vy", simple_storage.address)
new_simple_storage_address = storage_factory.create_simple_storage_contract()

# Breaks
new_simple_storage_contract_deployer = simple_storage.at(new_simple_storage_address)

However, when I try to get a VyperContract object using at I get an error:

Traceback (most recent call last):
  File "/xxx/deploy_from_storage_factory.py", line 10, in <module>
    new_simple_storage_contract_deployer = simple_storage.at(new_simple_storage_address)
  File "/xxx/site-packages/boa/contract.py", line 418, in at
    return self.deployer.wrap(*args, **kwargs)
AttributeError: 'VyperDeployer' object has no attribute 'wrap'

feature: deployment logs

log things about deployments (e.g., what is currently being printed in boa/network.py, but maybe also contract / compiler_data) so that interrupted deployments can continue. probably keep it in a leveldb database.

feature: branch coverage

30f85b4 implemented statement coverage, it shouldn't be too hard to add branch coverage but extra fiddling with the plugin and checking JUMPI and stuff

set default hypothesis settings for `deadline` to `None`

Currently, one writes fuzzing tests in the following manner:

from hypothesis import settings, given
from boa.test import strategy


@given(val=strategy("uint256[3]"))
@settings(max_examples=10, deadline=None)
def test_something(some_fixture, val):
    assert val is not None

If one does not provide None as deadline, then tests for large number of examples will bork because of flakiness. This flakiness can arise from inconsistent test times when hypothesis does runs tests with the same inputs:

E Unreliable test timings! On an initial run, this test took 295.97ms, which exceeded the deadline of 200.00ms, but on a subsequent run it took 138.73 ms, which did not. If you expect this sort of variability in your test timings, consider turning deadlines off for this test by setting deadline=None.

venv/lib/python3.10/site-packages/hypothesis/core.py:716: Flaky

To prevent this, perhaps boa should have a default deadline time set to None.

high-level precompile API

right now, when you register a precompile, you need to implement it at a fairly low level.

on the python side, you need to decode the calldata params, and encode the result, like

def my_precompile(computation):
    arg1, arg2, arg3 = abi.decode("(uint256,uint256,uint256)", computation.msg.data[4:])
    ...
    res = ...
    computation.output = abi.encode("(uint256", res)

and on the vyper side, you need to encode the params and use raw_call on a possibly gnarly looking address

def foo():
    raw_call(0x0000000000000000000000000000000000000065, _abi_encode(arg1, arg2, arg3, method_id="something")

it would be nice to define the precompile like

@precompile_magic
def my_precompile(arg1, arg2, arg3):
    ...
    return res

and use it almost like a builtin:

def foo():
    my_precompile(arg1, arg2, arg3)

here, precompile_magic is some magic decorator which does all of the marshaling/unmarshaling above and inserts the precompile into the vyper namespace.

make timetravel api cleaner

right now, if one wants to time travel:

import boa

def boa_sleep(sleep_time):
    boa.env.vm.patch.timestamp += sleep_time
    boa.env.vm.patch.block_number += sleep_time // 12

this can be under the boa.env umbrella instead, such that:

boa.env.time_travel(sleep_time)

adding this feature to boa > environements.py > Env closes this issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.