Skip to content

Commit

Permalink
Merge pull request #126 from HyperLink-Technology/v1.0.0b6
Browse files Browse the repository at this point in the history
V1.0.0b6
  • Loading branch information
iamdefinitelyahuman authored May 13, 2019
2 parents 55f466d + b0abec8 commit 5d5f89f
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 48 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.0.0b6
-------

- Changes to ContractConstructor call arguments
- Bugfixes and minor changes

1.0.0b5
-------

Expand Down
2 changes: 1 addition & 1 deletion brownie/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from brownie.cli.utils import color
import brownie.project as project

__version__ = "1.0.0b5" # did you change this in docs/conf.py as well?
__version__ = "1.0.0b6" # did you change this in docs/conf.py as well?

__doc__ = """Usage: brownie <command> [<args>...] [options <args>]
Expand Down
5 changes: 3 additions & 2 deletions brownie/cli/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ def run_test_modules(test_data, save):
count = sum([len([x for x in i[3] if x != "setup"]) for i in test_data])
print("Running {} tests across {} modules.".format(count, len(test_data)))
network.connect(ARGV['network'])
for key in ('broadcast_reverting_tx', 'gas_limit'):
CONFIG['active_network'][key] = CONFIG['test'][key]
CONFIG._unlock()
CONFIG['active_network'].update(CONFIG['test'])
CONFIG._lock()
if not CONFIG['active_network']['broadcast_reverting_tx']:
print("{0[error]}WARNING{0}: Reverting transactions will NOT be broadcasted.".format(color))
traceback_info = []
Expand Down
6 changes: 4 additions & 2 deletions brownie/network/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def deploy(self, contract, *args, amount=None, gas_limit=None, gas_price=None, c
'from': self.address,
'value': wei(amount),
'gasPrice': wei(gas_price) or self._gas_price(),
'gas': wei(gas_limit) or self._gas_limit(amount, data),
'gas': wei(gas_limit) or self._gas_limit("", amount, data),
'data': HexBytes(data)
})
except ValueError as e:
Expand Down Expand Up @@ -323,8 +323,10 @@ def _transact(self, tx):


def _raise_or_return_tx(exc):
data = eval(str(exc))
try:
data = eval(str(exc))
return next(i for i in data['data'].keys() if i[:2] == "0x")
except SyntaxError:
raise exc
except Exception:
raise VirtualMachineError(exc)
24 changes: 13 additions & 11 deletions brownie/network/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,24 @@ def __repr__(self):
) for i in self.abi['inputs'])
)

def __call__(self, account, *args):
def __call__(self, *args):
'''Deploys a contract.
Args:
account: Account instance to deploy the contract from.
*args: Constructor arguments. The last argument may optionally be
a dictionary of transaction values.
*args: Constructor arguments. The last argument MUST be a dictionary
of transaction values containing at minimum a 'from' key to
specify which account to deploy this contract from.
Returns:
* Contract instance if the transaction confirms
* TransactionReceipt if the transaction is pending or reverts'''
args, tx = _get_tx(account, args)
return account.deploy(
args, tx = _get_tx(None, args)
if not tx['from']:
raise AttributeError(
"Contract has no owner, you must supply a tx dict"
" with a 'from' field as the last argument."
)
return tx['from'].deploy(
self._parent,
*args,
amount=tx['value'],
Expand Down Expand Up @@ -296,7 +301,7 @@ def transact(self, *args, _rpc_clear=True):
args, tx = _get_tx(self._owner, args)
if not tx['from']:
raise AttributeError(
"Contract has no owner, you must supply a tx dict"
"No deployer address given. You must supply a tx dict"
" with a 'from' field as the last argument."
)
if _rpc_clear:
Expand Down Expand Up @@ -331,10 +336,7 @@ class ContractTx(_ContractMethod):
signature: Bytes4 method signature.'''

def __init__(self, fn, abi, name, owner):
if (
ARGV['cli'] == "test" and not
CONFIG['test']['default_contract_owner']
):
if ARGV['cli'] == "test" and not CONFIG['test']['default_contract_owner']:
owner = None
super().__init__(fn, abi, name, owner)

Expand Down
60 changes: 41 additions & 19 deletions brownie/network/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,14 @@ def _evaluate_trace(self):
if not trace:
return
contract = self.contract_address or self.receiver
pc = contract._build['pcMap'][0]
fn = sources.get_contract_name(pc['contract'], pc['start'], pc['stop'])
fn += "."+self.fn_name.split('.')[-1]
last_map = {0: {
'address': contract.address,
'contract': contract,
'fn': [self.fn_name.split('.')[-1]],
'fn': [fn],
}}
pc = contract._build['pcMap'][0]
trace[0].update({
'address': last_map[0]['address'],
'contractName': last_map[0]['contract']._name,
Expand All @@ -327,10 +329,13 @@ def _evaluate_trace(self):
stack_idx = -4 if trace[i-1]['op'] in ('CALL', 'CALLCODE') else -3
memory_idx = int(trace[i-1]['stack'][stack_idx], 16) * 2
sig = "0x" + "".join(trace[i-1]['memory'])[memory_idx:memory_idx+8]
pc = contract._build['pcMap'][trace[i]['pc']]
fn = sources.get_contract_name(pc['contract'], pc['start'], pc['stop'])
fn += "."+(contract.get_method(sig) or "")
last_map[trace[i]['depth']] = {
'address': address,
'contract': contract,
'fn': [contract.get_method(sig) or ""],
'fn': [fn],
}
last = last_map[trace[i]['depth']]
contract = last['contract']
Expand Down Expand Up @@ -372,36 +377,53 @@ def call_trace(self):
_print_path(trace[-1], idx, sep)

def error(self, pad=3):
'''Displays the source code that caused the transaction to revert.'''
'''Displays the source code that caused the transaction to revert.
Args:
pad: Number of unrelated lines of code to include before and after
'''
try:
idx = self.trace.index(next(i for i in self.trace if i['op'] in ("REVERT", "INVALID")))
except StopIteration:
return ""
while True:
if idx == -1:
return ""
if not self.trace[idx]['source']['filename']:
trace = self.trace[idx]
if not trace['source']['filename']:
idx -= 1
continue
span = (self.trace[idx]['source']['start'], self.trace[idx]['source']['stop'])
if sources.get_type(self.trace[idx]['contractName']) in ("contract", "library"):
span = (trace['source']['start'], trace['source']['stop'])
if sources.get_fn(trace['source']['filename'], span[0], span[1]) != trace['fn']:
idx -= 1
continue
source = sources[self.trace[idx]['source']['filename']]
newlines = [i for i in range(len(source)) if source[i] == "\n"]
try:
start = newlines.index(next(i for i in newlines if i >= span[0]))
stop = newlines.index(next(i for i in newlines if i >= span[1]))
break
except StopIteration:
idx -= 1
return self.source(idx)

def source(self, idx, pad=3):
'''Displays the associated source code for a given stack trace step.
Args:
idx: Stack trace step index
pad: Number of unrelated lines of code to include before and after
'''
trace = self.trace[idx]
if not trace['source']['filename']:
return ""
source = sources[trace['source']['filename']]
span = (trace['source']['start'], trace['source']['stop'])
newlines = [i for i in range(len(source)) if source[i] == "\n"]
try:
start = newlines.index(next(i for i in newlines if i >= span[0]))
stop = newlines.index(next(i for i in newlines if i >= span[1]))
except StopIteration:
return ""
ln = start + 1
start = newlines[max(start-(pad+1), 0)]
stop = newlines[min(stop+pad, len(newlines)-1)]
result = ((
'{0[dull]}File {0[string]}"{1}"{0[dull]}, ' +
'line {0[value]}{2}{0[dull]}, in {0[callable]}{3}'
).format(color, self.trace[idx]['source']['filename'], ln, self.trace[idx]['fn']))
'Source code for trace step {0[value]}{4}{0}:\n File {0[string]}' +
'"{1}"{0}, line {0[value]}{2}{0}, in {0[callable]}{3}{0}:'
).format(color, trace['source']['filename'], ln, trace['fn'], idx))
result += ("{0[dull]}{1}{0}{2}{0[dull]}{3}{0}".format(
color,
source[start:span[0]],
Expand All @@ -413,7 +435,7 @@ def error(self, pad=3):

def _print_path(trace, idx, sep):
col = "error" if trace['op'] in ("REVERT", "INVALID") else "pending"
name = "{0[contractName]}.{1}{0[fn]}".format(trace, color(col))
name = "{}{}".format(trace['fn'], color(col))
print(
(" "*sep*trace['depth']) + (" "*(trace['jumpDepth']-1)) +
"{}{} {}{} ({})".format(color(col), name, color('dull'), idx, trace['address']) +
Expand Down
28 changes: 23 additions & 5 deletions brownie/project/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _get_contract_data(self, path):
)
for source in contracts:
type_, name, inherited = re.findall(
r"\s*(contract|library|interface) (\S*) (?:is (.*?)|)(?: *{)",
r"\s*(contract|library|interface)\s{1,}(\S*)\s*(?:is\s{1,}(.*?)|)(?:{)",
source
)[0]
inherited = set(i.strip() for i in inherited.split(', ') if i)
Expand Down Expand Up @@ -98,20 +98,22 @@ def _recursive_inheritance(self, inherited):
return final

def get_hash(self, contract_name):
'''Returns a hash of the contract source code after comments have been removed.'''
return self._data[contract_name]['sha1']

def get_path(self, contract_name):
'''Returns the path to the source file where a contract is located.'''
return self._data[contract_name]['sourcePath']

def get_type(self, contract_name):
'''Returns the type of contract (contract, interface, library).'''
return self._data[contract_name]['type']

def get_fn(self, name, start, stop):
'''Given a contract name, start and stop offset, returns the name of the
associated function. Returns False if the offset spans multiple functions.'''
if name not in self._data:
name = next((
k for k, v in self._data.items() if v['sourcePath'] == str(name) and
v['offset'][0] <= start <= stop <= v['offset'][1]
), False)
name = self.get_contract_name(name, start, stop)
if not name:
return False
offsets = self._data[name]['fn_offsets']
Expand All @@ -121,6 +123,7 @@ def get_fn(self, name, start, stop):
return False if stop > offset[2] else offset[0]

def get_fn_offset(self, name, fn_name):
'''Given a contract and function name, returns the source offsets of the function.'''
try:
if name not in self._data:
name = next(
Expand All @@ -131,10 +134,25 @@ def get_fn_offset(self, name, fn_name):
except StopIteration:
raise ValueError("Unknown function '{}' in contract {}".format(fn_name, name))

def get_contract_name(self, path, start, stop):
'''Given a path and source offsets, returns the name of the contract.
Returns False if the offset spans multiple contracts.'''
return next((
k for k, v in self._data.items() if v['sourcePath'] == str(path) and
v['offset'][0] <= start <= stop <= v['offset'][1]
), False)

def inheritance_map(self):
'''Returns a dict of sets in the format:
{'contract name': {'inheritedcontract', 'inherited contract'..} }
'''
return dict((k, v['inherited'].copy()) for k, v in self._data.items())

def add_source(self, source):
'''Given source code as a string, adds it to the object and returns a path
string formatted as "<string-X>" where X is a number that is incremented.
'''
path = "<string-{}>".format(self._string_iter)
self._source[path] = source
self._remove_comments(path)
Expand Down
32 changes: 29 additions & 3 deletions docs/api-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1364,20 +1364,22 @@ TransactionReceipt Methods
>>> tx.call_trace()
Token.transferFrom 0 (0x4C2588c6BFD533E0a27bF7572538ca509f31882F)
Token.sub 86 (0x4C2588c6BFD533E0a27bF7572538ca509f31882F)
SafeMath.sub 86 (0x4C2588c6BFD533E0a27bF7572538ca509f31882F)
.. py:classmethod:: TransactionReceipt.error(pad=3)
Displays the source code that caused the first revert in the transaction, if any.

* ``pad``: Number of unrelated lines to show around the relevent source code.
* ``pad``: Number of unrelated liness of code to include before and after the relevant source


.. code-block:: python
>>> tx
<Transaction object '0xac54b49987a77805bf6bdd78fb4211b3dc3d283ff0144c231a905afa75a06db0'>
>>> tx.error()
File "contracts/SafeMath.sol", line 9:
Source code for trace step 86:
File "contracts/SafeMath.sol", line 9, in SafeMath.sub:
c = a + b;
require(c >= a);
Expand All @@ -1389,6 +1391,30 @@ TransactionReceipt Methods
function mul(uint a, uint b) internal pure returns (uint c) {
c = a * b;
.. py:classmethod:: TransactionReceipt.source(idx, pad=3)
Displays the associated source code for a given stack trace step.
* ``idx``: Stack trace step index
* ``pad``: Number of unrelated liness of code to include before and after the relevant source
.. code-block:: python
>>> tx
<Transaction object '0xac54b49987a77805bf6bdd78fb4211b3dc3d283ff0144c231a905afa75a06db0'>
>>> tx.source(86)
Source code for trace step 86:
File "contracts/SafeMath.sol", line 9, in SafeMath.sub:
c = a + b;
require(c >= a);
}
function sub(uint a, uint b) internal pure returns (uint c) {
require(b <= a);
c = a - b;
}
function mul(uint a, uint b) internal pure returns (uint c) {
c = a * b;
``brownie.network.web3``
========================
Expand Down
16 changes: 14 additions & 2 deletions docs/api-project.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Sources
.. py:classmethod:: Sources.get_path(contract_name)
Returns the absolute path to the file where a contract is located.
Returns the path to the file where a contract is located.
.. py:classmethod:: Sources.get_type(contract_name)
Expand All @@ -170,9 +170,21 @@ Sources
Given a contract and function name, returns the source offsets of the function.
.. py:classmethod:: Sources.get_contract_name(path, start, stop)
Given a path and source offsets, returns the name of the contract. Returns ``False`` if the offset spans multiple contracts.
.. py:classmethod:: Sources.inheritance_map()
Returns a set of all contracts that the given contract inherits from.
Returns a dictionary of sets, where each key is a contract name and each value is the name of each contract that the main contact inherits from.
.. code-block:: python
>>> sources.inheritance_map()
{
'SafeMath': set(),
'Token': {'SafeMath'}
}
.. py:classmethod: Sources.add_source(source)
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = 'v1.0.0b5'
release = 'v1.0.0b6'


# -- General configuration ---------------------------------------------------
Expand Down Expand Up @@ -172,4 +172,4 @@
# epub_uid = ''

# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
epub_exclude_files = ['search.html']
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.0.0b5",
version="1.0.0b6",
license="MIT",
description="A Python framework for Ethereum smart contract deployment, testing and interaction.", # noqa: E501
long_description=long_description,
Expand Down

0 comments on commit 5d5f89f

Please sign in to comment.