Skip to content

Commit

Permalink
Merge pull request #56 from HyperLink-Technology/develop
Browse files Browse the repository at this point in the history
Brownie 0.9.5
  • Loading branch information
iamdefinitelyahuman authored Apr 1, 2019
2 parents c0f233e + d70ae46 commit 96ebd14
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 217 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
0.9.5
-----

- check.true and check.false require booleans to pass
- Allow subfolders within tests/
- Only run specific tests within a file
- More efficient transaction stack trace analysis
- Improvements to compiler efficiency and functionality
- account.transfer accepts data
- add ContractTx.encode_abi
- add ContractContainer.get_method
- Bugfixes


0.9.4
-----
-----

- Improved console formatting for lists and dicts
- Run method returns list of scripts when no argument is given
Expand Down
2 changes: 1 addition & 1 deletion __main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from lib.services import color, git


__version__ = "0.9.4" # did you change this in docs/conf.py as well?
__version__ = "0.9.5" # did you change this in docs/conf.py as well?
if git.get_branch() != "master":
__version__+= "-"+git.get_branch()+"-"+git.get_commit()

Expand Down
87 changes: 77 additions & 10 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,31 @@ LocalAccount
Contracts
=========

Contract classes are not meant to be instantiated directly. Each ``ContractContainer`` instance is created automatically during when Brownie starts. New ``Contract`` instances are created via methods in the container.

Contract classes are not meant to be instantiated directly. When launched, Brownie automatically creates ``ContractContainer`` instances from on the files in the ``contracts/`` folder. New ``Contract`` instances are created via methods in the container.

Temporary contracts used for testing can be created with the ``compile_source`` method.

.. py:method:: compile_source(source)
Compiles the given string and returns a list of ContractContainer instances.

.. code-block:: python
>>> container = compile_source('''pragma solidity 0.4.25;
contract SimpleTest {
string public name;
constructor (string _name) public {
name = _name;
}
}'''
[<ContractContainer object 'SimpleTest'>]
>>> container[0]
[]
ContractContainer
-----------------
Expand Down Expand Up @@ -250,6 +274,8 @@ ContractContainer Attributes
A dictionary of bytes4 signatures for each contract method.
If you have a signature and need to find the method name, use ``ContractContainer.get_method``.
.. code-block:: python
>>> Token.signatures
Expand Down Expand Up @@ -357,6 +383,22 @@ ContractContainer Methods
>>> Token
[]
.. py:classmethod:: ContractContainer.get_method(calldata)
Given the call data of a transaction, returns the name of the contract method as a string.
.. code-block:: python
>>> tx = Token[0].transfer(accounts[1], 1000)
Transaction sent: 0xc1fe0c7c8fd08736718aa9106662a635102604ea6db4b63a319e43474de0b420
Token.transfer confirmed - block: 3 gas used: 35985 (26.46%)
<Transaction object '0xc1fe0c7c8fd08736718aa9106662a635102604ea6db4b63a319e43474de0b420'>
>>> tx.input
0xa9059cbb00000000000000000000000066ace0365c25329a407002d22908e25adeacb9bb00000000000000000000000000000000000000000000000000000000000003e8
>>> Token.get_method(tx.input)
transfer
Contract
--------
Expand Down Expand Up @@ -526,6 +568,20 @@ ContractTx Methods
>>> Token[0].transfer.call(accounts[2], 10000, {'from': accounts[0]})
True
.. py:classmethod:: ContractTx.encode_abi(*args)
Returns a hexstring of ABI calldata, to call the method with the given arguments.
.. code-block:: python
>>> calldata = Token[0].transfer.encode_abi(accounts[1], 1000)
0xa9059cbb0000000000000000000000000d36bdba474b5b442310a5bfb989903020249bba00000000000000000000000000000000000000000000000000000000000003e8
>>> accounts[0].transfer(Token[0], 0, data=calldata)
Transaction sent: 0x8dbf15878104571669f9843c18afc40529305ddb842f94522094454dcde22186
Token.transfer confirmed - block: 2 gas used: 50985 (100.00%)
<Transaction object '0x8dbf15878104571669f9843c18afc40529305ddb842f94522094454dcde22186'>
Transactions
============
Expand Down Expand Up @@ -883,38 +939,49 @@ The ``check`` module exposes the following methods that are used in place of ``a
Module Methods
--------------
.. py:method:: check.true(statement, fail_msg = "Expected statement to be true")
.. py:method:: check.true(statement, fail_msg = "Expected statement to be True")
Raises if ``statement`` does not evaluate to True.
Raises if ``statement`` is not ``True``.
.. code-block:: python
>>> check.true(True)
>>> check.true(2 + 2 == 4)
>>>
>>> check.true(0 > 1)
File "brownie/lib/components/check.py", line 18, in true
raise AssertionError(fail_msg)
AssertionError: Expected statement to be true
AssertionError: Expected statement to be True
>>> check.true(False, "What did you expect?")
File "brownie/lib/console.py", line 82, in _run
exec('_result = ' + cmd, self.__dict__, local_)
File "<string>", line 1, in <module>
File "/home/computer/code/python/brownie/lib/components/check.py", line 18, in true
File "brownie/lib/components/check.py", line 18, in true
raise AssertionError(fail_msg)
AssertionError: What did you expect?
>>> check.true(1)
File "brownie/lib/components/check.py", line 16, in true
raise AssertionError(fail_msg+" (evaluated truthfully but not True)")
AssertionError: Expected statement to be True (evaluated truthfully but not True)
.. py:method:: check.false(statement, fail_msg = "Expected statement to be False")
Raises if ``statement`` does not evaluate to False.
Raises if ``statement`` is not ``False``.
.. code-block:: python
>>> check.false(0 > 1)
>>> check.false(2 + 2 == 4)
File "brownie/lib/components/check.py", line 18, in true
File "brownie/lib/components/check.py", line 18, in false
raise AssertionError(fail_msg)
AssertionError: Expected statement to be False
>>> check.false(0)
File "brownie/lib/components/check.py", line 16, in false
raise AssertionError(fail_msg+" (evaluated falsely but not False)")
AssertionError: Expected statement to be False (evaluated falsely but not False)
.. py:method:: check.confirms(fn, args, fail_msg = "Expected transaction to confirm")
Performs the given contract call ``fn`` with arguments ``args``. Raises if the call causes the EVM to revert.
Expand Down
2 changes: 1 addition & 1 deletion 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 = '0.9.4'
release = '0.9.5'


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions docs/tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ You can run a specific test by giving the filename without an extension, for exa

$ brownie test transfer

For larger projects you can also store tests within subfolders, and point Brownie to run only a specific folder. Brownie will skip any file or folder that begins with an underscore.

Running Tests
=============

Expand Down
13 changes: 7 additions & 6 deletions lib/components/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def estimate_gas(self, to, amount, data=""):
return web3.eth.estimateGas({
'from': self.address,
'to': str(to),
'data': data,
'data': HexBytes(data),
'value': wei(amount)
})

Expand All @@ -192,7 +192,7 @@ class Account(_AccountBase):
def _console_repr(self):
return "<Account object '{0[string]}{1}{0}'>".format(color, self.address)

def transfer(self, to, amount, gas_limit=None, gas_price=None):
def transfer(self, to, amount, gas_limit=None, gas_price=None, data=''):
'''Transfers ether from this account.
Args:
Expand All @@ -209,7 +209,8 @@ def transfer(self, to, amount, gas_limit=None, gas_price=None):
'to': str(to),
'value': wei(amount),
'gasPrice': wei(gas_price) or self._gas_price(),
'gas': wei(gas_limit) or self._gas_limit(to, amount)
'gas': wei(gas_limit) or self._gas_limit(to, amount, data),
'data': HexBytes(data)
})
except ValueError as e:
txid = raise_or_return_tx(e)
Expand Down Expand Up @@ -249,7 +250,7 @@ def __init__(self, address, account, priv_key):
def _console_repr(self):
return "<LocalAccount object '{0[string]}{1}{0}'>".format(color, self.address)

def transfer(self, to, amount, gas_limit=None, gas_price=None):
def transfer(self, to, amount, gas_limit=None, gas_price=None, data=''):
'''Transfers ether from this account.
Args:
Expand All @@ -265,10 +266,10 @@ def transfer(self, to, amount, gas_limit=None, gas_price=None):
'from': self.address,
'nonce': self.nonce,
'gasPrice': wei(gas_price) or self._gas_price(),
'gas': wei(gas_limit) or self._gas_limit(to, amount),
'gas': wei(gas_limit) or self._gas_limit(to, amount, data),
'to': str(to),
'value': wei(amount),
'data': ""
'data': HexBytes(data)
}).rawTransaction
txid = web3.eth.sendRawTransaction(signed_tx)
except ValueError as e:
Expand Down
6 changes: 5 additions & 1 deletion lib/components/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
from lib.components.transaction import VirtualMachineError as _VMError


def true(statement, fail_msg="Expected statement to be true"):
def true(statement, fail_msg="Expected statement to be True"):
'''Expects an object or statement to evaluate True.
Args:
statement: The object or statement to check.
fail_msg: Message to show if the check fails.'''
if statement and statement is not True:
raise AssertionError(fail_msg+" (evaluated truthfully but not True)")
if not statement:
raise AssertionError(fail_msg)

Expand All @@ -22,6 +24,8 @@ def false(statement, fail_msg="Expected statement to be False"):
Args:
statement: The object or statement to check.
fail_msg: Message to show if the check fails.'''
if not statement and statement is not False:
raise AssertionError(fail_msg+" (evaluated falsely but not False)")
if statement:
raise AssertionError(fail_msg)

Expand Down
39 changes: 31 additions & 8 deletions lib/components/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections import OrderedDict
import eth_event
import eth_abi
import re

from lib.services.datatypes import KwargTuple, format_output
Expand All @@ -17,9 +18,9 @@


def find_contract(address):
address = web3.toChecksumAddress(address)
address = web3.toChecksumAddress(str(address))
contracts = [x for v in deployed_contracts.values() for x in v.values()]
return next((i for i in contracts if i == address), False)
return next((i for i in contracts if i == address), None)


class _ContractBase:
Expand All @@ -41,6 +42,12 @@ def __init__(self, build):
)).hex()[:10]
) for i in self.abi if i['type'] == "function")

def get_method(self, calldata):
return next(
(k for k, v in self.signatures.items() if v == calldata[:10].lower()),
None
)


class ContractContainer(_ContractBase):

Expand Down Expand Up @@ -91,6 +98,9 @@ def __delitem__(self, key):
def __len__(self):
return len(deployed_contracts[self._name])

def __repr__(self):
return "<ContractContainer object '{1[string]}{0._name}{1}'>".format(self, color)

def _console_repr(self):
return str(list(deployed_contracts[self._name].values()))

Expand All @@ -110,7 +120,7 @@ def at(self, address, owner=None, tx=None):
address: Address string of the contract.
owner: Default Account instance to send contract transactions from.
tx: Transaction ID of the contract creation.'''
address = web3.toChecksumAddress(address)
address = web3.toChecksumAddress(str(address))
if address in deployed_contracts[self._name]:
return deployed_contracts[self._name][address]
contract = find_contract(address)
Expand Down Expand Up @@ -186,18 +196,19 @@ def __call__(self, account, *args):
[i['type'] for i in self.abi[0]['inputs']] if self.abi else []
),
tx,
self._name,
self._name+".constructor",
self._callback
)
if tx.status == 1:
return self._parent.at(tx.contract_address)
tx.contract_address = self._parent.at(tx.contract_address)
return tx.contract_address
return tx

def _callback(self, tx):
# ensures the Contract instance is added to the container if the user
# presses CTRL-C while deployment is still pending
if tx.status == 1:
self._parent.at(tx.contract_address, tx.sender, tx)
tx.contract_address = self._parent.at(tx.contract_address, tx.sender, tx)


class Contract(_ContractBase):
Expand Down Expand Up @@ -293,7 +304,7 @@ def call(self, *args):
except ValueError as e:
raise VirtualMachineError(e)

if type(result) is not list or len(result)==1:
if type(result) is not list or len(result) == 1:
return format_output(result)
return KwargTuple(result, self.abi)

Expand All @@ -319,6 +330,18 @@ def transact(self, *args):
self._name
)

def encode_abi(self, *args):
'''Returns encoded ABI data to call the method with the given arguments.
Args:
*args: Contract method inputs
Returns:
Hexstring of encoded ABI data.'''
data = self._format_inputs(args)
types = [i['type'] for i in self.abi['inputs']]
return self.signature + eth_abi.encode_abi(types, data).hex()


class ContractTx(_ContractMethod):

Expand Down Expand Up @@ -374,7 +397,7 @@ def __call__(self, *args):
def _get_tx(owner, args):
# seperate contract inputs from tx dict
if args and type(args[-1]) is dict:
args, tx = (args[:-1], args[-1])
args, tx = (args[:-1], args[-1].copy())
if 'from' not in tx:
tx['from'] = owner
for key in [i for i in ('value', 'gas', 'gasPrice') if i in tx]:
Expand Down
Loading

0 comments on commit 96ebd14

Please sign in to comment.