vyperlang / titanoboa Goto Github PK
View Code? Open in Web Editor NEWa vyper interpreter
Home Page: https://titanoboa.readthedocs.io
License: Other
a vyper interpreter
Home Page: https://titanoboa.readthedocs.io
License: Other
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)
pytest plugin, and also hypothesis. state isolation, strategies.
Line 13 in 9f6d71b
should be from boa.interpret.stmt import interpret_module
I think
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:
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)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
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
Dependency conflict between the two projects does not allow both projects to work in the same project
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
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
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.
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
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)
for some reason, installing from github as described in the readme fails during import
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'
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.
coverage was done in 30f85b4
usage is like coverage run -m pytest ...
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.
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
include:
new backend which targets hevm. cf. https://hevm.dev/symbolic.html
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.
The documentation does not reflect the latest
version. I am not sure how this can be fixed.
% 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
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.
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)
also show how to disable gas metering (for performance)
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.
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!
we probably want to document the high level API -- VyperContract._storage: StorageModel
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.
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
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.
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
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
when CompilerData is constructed, interfaces are not supplied:
Lines 24 to 35 in d61f686
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)
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.
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)
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.
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.
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.
compiler vs dev vs vm errors. see the tests in test_reverts.py.
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).
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'
for interop with on-chain stuff
per @bout3fiddy
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.
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
we should probably rename the profile
mark to like gas_profile
or profile_gas
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
.
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.
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.