Skip to content

Commit

Permalink
Merge pull request #357 from iamdefinitelyahuman/eip170
Browse files Browse the repository at this point in the history
Eip170 checks, v1.6.3
  • Loading branch information
iamdefinitelyahuman committed Feb 9, 2020
2 parents 1fd4844 + 0257c11 commit 50823b9
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 28 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ This changelog format is based on [Keep a Changelog](https://keepachangelog.com/
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased](https://github.com/iamdefinitelyahuman/brownie)

## [1.6.3](https://github.com/iamdefinitelyahuman/brownie/tree/v1.6.3) - 2020-02-09
### Added
- `--stateful` flag to only run or skip stateful test cases
- [EIP-170](https://github.com/ethereum/EIPs/issues/170) size limits: warn on compile, give useful error message on failed deployment

### Changed
- unexpanded transaction trace is available for deployment transactions

### Fixed
- Warn instead of raising when an import spec cannot be found
- Handle `REVERT` outside of function when generating revert map

## [1.6.2](https://github.com/iamdefinitelyahuman/brownie/tree/v1.6.2) - 2020-02-05
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion brownie/_cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from brownie.utils import color, notify
from brownie.utils.docopt import docopt, levenshtein_norm

__version__ = "1.6.2"
__version__ = "1.6.3"

__doc__ = """Usage: brownie <command> [<args>...] [options <args>]
Expand Down
63 changes: 43 additions & 20 deletions brownie/network/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ def wrapper(self: "TransactionReceipt") -> Any:
return wrapper


def trace_inspection(fn: Callable) -> Any:
def wrapper(self: "TransactionReceipt", *args: Any, **kwargs: Any) -> Any:
if self.contract_address:
raise NotImplementedError(
"Trace inspection methods are not available for deployment transactions."
)
if self.input == "0x" and self.gas_used == 21000:
return None
return fn(self, *args, **kwargs)

return wrapper


class TransactionReceipt:

"""Attributes and methods relating to a broadcasted transaction.
Expand Down Expand Up @@ -175,14 +188,17 @@ def __init__(
if self._revert_msg is None:
# no revert message and unable to check dev string - have to get trace
self._expand_trace()
source = self._traceback_string() if ARGV["revert"] else self._error_string(1)
if self.contract_address:
source = ""
else:
source = self._traceback_string() if ARGV["revert"] else self._error_string(1)
raise VirtualMachineError({"message": self._revert_msg or "", "source": source})
except KeyboardInterrupt:
if ARGV["cli"] != "console":
raise

def __repr__(self) -> str:
c = {-1: "pending", 0: "error", 1: None}
c = {-1: "bright yellow", 0: "bright red", 1: None}
return f"<Transaction '{color(c[self.status])}{self.txid}{color}'>"

def __hash__(self) -> int:
Expand Down Expand Up @@ -234,7 +250,11 @@ def return_value(self) -> Optional[str]:

@trace_property
def revert_msg(self) -> Optional[str]:
if not self.status and self._revert_msg is None:
if self.status:
return None
if self._revert_msg is None:
self._get_trace()
elif self.contract_address and self._revert_msg == "out of gas":
self._get_trace()
return self._revert_msg

Expand Down Expand Up @@ -331,8 +351,8 @@ def _get_trace(self) -> None:
if self._raw_trace is not None:
return
self._raw_trace = []
if (self.input == "0x" and self.gas_used == 21000) or self.contract_address:
self._modified_state = bool(self.contract_address)
if self.input == "0x" and self.gas_used == 21000:
self._modified_state = False
self._trace = []
return

Expand Down Expand Up @@ -361,7 +381,7 @@ def _get_trace(self) -> None:
def _confirmed_trace(self, trace: Sequence) -> None:
self._modified_state = next((True for i in trace if i["op"] == "SSTORE"), False)

if trace[-1]["op"] != "RETURN":
if trace[-1]["op"] != "RETURN" or self.contract_address:
return
contract = _find_contract(self.receiver)
if contract:
Expand All @@ -373,6 +393,10 @@ def _reverted_trace(self, trace: Sequence) -> None:
self._modified_state = False
# get events from trace
self._events = _decode_trace(trace)
if self.contract_address:
step = next((i for i in trace if i["op"] == "CODECOPY"), None)
if step is not None and int(step["stack"][-3], 16) > 24577:
self._revert_msg = "exceeds EIP-170 size limit"
if self._revert_msg is not None:
return
# get revert message
Expand All @@ -382,6 +406,9 @@ def _reverted_trace(self, trace: Sequence) -> None:
data = _get_memory(step, -1)[4:]
self._revert_msg = decode_abi(["string"], data)[0]
return
if self.contract_address:
self._revert_msg = "invalid opcode" if step["op"] == "INVALID" else ""
return
# check for dev revert string using program counter
self._revert_msg = build._get_dev_revert(step["pc"])
if self._revert_msg is not None:
Expand Down Expand Up @@ -419,7 +446,7 @@ def _expand_trace(self) -> None:
self._trace = trace = self._raw_trace
self._new_contracts = []
self._internal_transfers = []
if not trace:
if self.contract_address or not trace:
coverage._add_transaction(self.coverage_hash, {})
return
if "fn" in trace[0]:
Expand Down Expand Up @@ -562,18 +589,15 @@ def info(self) -> None:
result += f"\n {key}: {color('bright blue')}{value}{color}"
print(result)

@trace_inspection
def call_trace(self) -> None:
"""Displays the complete sequence of contracts and methods called during
the transaction, and the range of trace step indexes for each method.
Lines highlighed in red ended with a revert.
"""
trace = self.trace
if not trace:
if not self.contract_address:
return
raise NotImplementedError("Call trace is not available for deployment transactions.")

trace = self.trace
result = f"Call trace for '{color('bright blue')}{self.txid}{color}':"
result += _step_print(trace[0], trace[-1], None, 0, len(trace))
indent = {0: 0}
Expand Down Expand Up @@ -613,17 +637,14 @@ def call_trace(self) -> None:
print(result)

def traceback(self) -> None:
print(self._traceback_string())
print(self._traceback_string() or "")

@trace_inspection
def _traceback_string(self) -> str:
"""Returns an error traceback for the transaction."""
if self.status == 1:
return ""
trace = self.trace
if not trace:
if not self.contract_address:
return ""
raise NotImplementedError("Traceback is not available for deployment transactions.")

try:
idx = next(i for i in range(len(trace)) if trace[i]["op"] in ("REVERT", "INVALID"))
Expand Down Expand Up @@ -651,8 +672,9 @@ def _traceback_string(self) -> str:
)

def error(self, pad: int = 3) -> None:
print(self._error_string(pad))
print(self._error_string(pad) or "")

@trace_inspection
def _error_string(self, pad: int = 3) -> str:
"""Returns the source code that caused the transaction to revert.
Expand Down Expand Up @@ -682,8 +704,9 @@ def _error_string(self, pad: int = 3) -> str:
return ""

def source(self, idx: int, pad: int = 3) -> None:
print(self._source_string(idx, pad))
print(self._source_string(idx, pad) or "")

@trace_inspection
def _source_string(self, idx: int, pad: int) -> str:
"""Displays the associated source code for a given stack trace step.
Expand All @@ -694,7 +717,7 @@ def _source_string(self, idx: int, pad: int) -> str:
Returns: source code string
"""
trace = self.trace[idx]
if not trace["source"]:
if not trace.get("source", None):
return ""
contract = _find_contract(self.trace[idx]["address"])
source, linenos = highlight_source(
Expand Down
8 changes: 8 additions & 0 deletions brownie/project/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
from typing import Dict, Optional, Union

from eth_utils import remove_0x_prefix
from semantic_version import Version

from brownie.exceptions import UnsupportedLanguage
Expand All @@ -15,6 +16,7 @@
install_solc,
set_solc_version,
)
from brownie.utils import notify

from . import solidity, vyper

Expand Down Expand Up @@ -279,6 +281,12 @@ def generate_build_json(
"sourcePath": path_str,
}
)
size = len(remove_0x_prefix(output_evm["deployedBytecode"]["object"])) / 2 # type: ignore
if size > 24577:
notify(
"WARNING",
f"deployed size of {contract_name} is {size} bytes, exceeds EIP-170 limit of 24577",
)

if not silent:
print("")
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def setup(sphinx):
# The short X.Y version
version = ""
# The full version, including alpha/beta/rc tags
release = "v1.6.2"
release = "v1.6.3"


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.6.2
current_version = 1.6.3

[bumpversion:file:setup.py]

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
setup(
name="eth-brownie",
packages=find_packages(),
version="1.6.2", # don't change this manually, use bumpversion instead
version="1.6.3", # don't change this manually, use bumpversion instead
license="MIT",
description="A Python framework for Ethereum smart contract deployment, testing and interaction.", # noqa: E501
long_description=long_description,
Expand Down
11 changes: 10 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,16 @@ def pytest_sessionstart():
monkeypatch_session = MonkeyPatch()
monkeypatch_session.setattr(
"solcx.get_available_solc_versions",
lambda: ["v0.6.2", "v0.5.15", "v0.5.8", "v0.5.7", "v0.4.25", "v0.4.24", "v0.4.22"],
lambda: [
"v0.6.2",
"v0.5.15",
"v0.5.8",
"v0.5.7",
"v0.5.0",
"v0.4.25",
"v0.4.24",
"v0.4.22",
],
)


Expand Down
7 changes: 7 additions & 0 deletions tests/network/transaction/test_revert_msg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from brownie.exceptions import VirtualMachineError
from brownie.project import compile_source


def test_revert_msg_via_jump(ext_tester, console_mode):
Expand Down Expand Up @@ -76,3 +77,9 @@ def test_vyper_revert_reasons(vypertester, console_mode):
assert tx.revert_msg == "Modulo by zero"
tx = vypertester.overflow(0, 0, {"value": 31337})
assert tx.revert_msg == "Cannot send ether to nonpayable function"


def test_deployment_size_limit(accounts, console_mode):
code = f"@public\ndef baz():\n assert msg.sender != ZERO_ADDRESS, '{'blah'*10000}'"
tx = compile_source(code).Vyper.deploy({"from": accounts[0]})
assert tx.revert_msg == "exceeds EIP-170 size limit"
5 changes: 3 additions & 2 deletions tests/network/transaction/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,9 @@ def test_call_trace(console_mode, tester):


def test_trace_deploy(tester):
"""trace is not calculated for deploying contracts"""
assert not tester.tx.trace
"""trace is calculated for deploying contracts but not expanded"""
assert tester.tx.trace
assert "fn" not in tester.tx.trace[0]


def test_trace_transfer(accounts):
Expand Down
4 changes: 3 additions & 1 deletion tests/network/transaction/test_verbosity.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ def test_error(tx, reverted_tx, capfd):

def test_deploy_reverts(BrownieTester, accounts, console_mode):
tx = BrownieTester.deploy(True, {"from": accounts[0]}).tx
tx.traceback()
with pytest.raises(NotImplementedError):
tx.traceback()
with pytest.raises(NotImplementedError):
tx.call_trace()

revertingtx = BrownieTester.deploy(False, {"from": accounts[0]})
with pytest.raises(NotImplementedError):
revertingtx.call_trace()
Expand Down
10 changes: 10 additions & 0 deletions tests/project/compiler/test_solidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,13 @@ def test_get_abi():
"type": "function",
}
]


def test_size_limit(capfd):
code = f"""
pragma solidity 0.6.2;
contract Foo {{ function foo() external returns (bool) {{
require(msg.sender != address(0), "{"blah"*10000}"); }}
}}"""
compiler.compile_and_format({"foo.sol": code})
assert "exceeds EIP-170 limit of 24577" in capfd.readouterr()[0]
6 changes: 6 additions & 0 deletions tests/project/compiler/test_vyper.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,9 @@ def test_get_abi():
"gas": 351,
}
]


def test_size_limit(capfd):
code = f"@public\ndef baz():\n assert msg.sender != ZERO_ADDRESS, '{'blah'*10000}'"
compiler.compile_and_format({"foo.vy": code})
assert "exceeds EIP-170 limit of 24577" in capfd.readouterr()[0]

0 comments on commit 50823b9

Please sign in to comment.