Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat publish source #914

Merged
merged 38 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3729297
feat: flatten contracts
iamdefinitelyahuman Dec 17, 2020
e6754aa
feat: add filename and license to flatten
matnad Dec 28, 2020
c5b62c5
feat: flatten returns more info for verification
matnad Dec 28, 2020
e966d44
feat: publish source first version
matnad Dec 28, 2020
de425fe
fix: linting, remove waiting_for_result option
matnad Dec 28, 2020
dc6cb4a
feat: solc version + commit, source top comments
matnad Dec 28, 2020
832ee23
test: fix tests for solc version
matnad Dec 28, 2020
fc2316a
fix: bump py-solc-x version
matnad Dec 28, 2020
24e3f7d
fix: linting
matnad Dec 28, 2020
7350313
fix: linting
matnad Dec 28, 2020
3413b23
fix: fix tests
matnad Dec 29, 2020
ad660c0
fix: fix tests
matnad Dec 29, 2020
c80ab00
fix: requested changes
matnad Dec 30, 2020
ba16c1e
feat: handle experimental abiencoder
matnad Dec 30, 2020
7b483a1
feat: fetch constructor arguments
matnad Dec 30, 2020
0ddd075
feat: dirty (temp) fix for interface build location
matnad Dec 30, 2020
de13d6e
feat: add dependencies for interfaces
matnad Jan 1, 2021
8de0703
feat: add toposort
matnad Jan 1, 2021
2426115
feat: isolate global struct/enum, sort deps
matnad Jan 1, 2021
5fe7d85
fix: linting
matnad Jan 1, 2021
b3250da
fix: test for interface build artifacts
matnad Jan 1, 2021
7000fc8
fix: comments and linting
matnad Jan 1, 2021
6734460
feat: add publish_source kwarg for deploy
matnad Jan 1, 2021
d8ea5fa
feat: various improvements
matnad Jan 1, 2021
e3370c3
feat: bump py-solc-ast to 1.2.8 (required)
matnad Jan 1, 2021
92a21a7
feat: add support for import aliases
matnad Jan 6, 2021
761a6a9
docs: add docs for publish_source
matnad Jan 6, 2021
3e54163
fix: linting
matnad Jan 6, 2021
2cb51e3
test: replaced proxy test with custom kovan contracts
matnad Jan 6, 2021
638431e
docs: changelog
matnad Jan 6, 2021
84f0e03
feat: move publish and verification to ContractContainer
matnad Jan 7, 2021
4e71318
feat: huge solc-ast refactor
matnad Jan 8, 2021
fd38f8d
test: Added a simple test for verification info
matnad Jan 8, 2021
277c6b9
fix: solcx.get_solc_version edited for clarity
matnad Jan 8, 2021
f514b5a
fix: linting...
matnad Jan 8, 2021
e35d6dc
tests: fix keyword argument test
matnad Jan 8, 2021
079b60e
tests: fix verification pragma issue
matnad Jan 8, 2021
51b3fbe
fix: requested changes
matnad Jan 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ 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/eth-brownie/brownie)
### Added
- Automatic source code verification on etherscan ([#919](https://github.com/eth-brownie/brownie/pull/914))

## [1.12.4](https://github.com/eth-brownie/brownie/tree/v1.12.4) - 2021-01-03
### Changed
- Use `ReturnType` instead of `list` for some `_EventItem` return values ([#919](https://github.com/eth-brownie/brownie/pull/919))
- Only decode events in reverting transactions upon request ([#920](https://github.com/eth-brownie/brownie/pull/920))

### Fixed
- Correctly handle malformed calldata in subcalls ([#913](https://github.com/eth-brownie/brownie/pull/913))
- Brownie bake uses the default branch instead of assuming `master` ([#917](https://github.com/eth-brownie/brownie/pull/917))
Expand Down
6 changes: 5 additions & 1 deletion brownie/network/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def deploy(
required_confs: int = 1,
allow_revert: bool = None,
silent: bool = None,
publish_source: bool = False,
) -> Any:
"""Deploys a contract.

Expand Down Expand Up @@ -511,7 +512,10 @@ def deploy(

add_thread.join()
try:
return contract.at(receipt.contract_address)
deployed_contract = contract.at(receipt.contract_address)
if publish_source:
contract.publish_source(deployed_contract, silent=silent)
return deployed_contract
except ContractNotFound:
# if the contract self-destructed during deployment
return receipt
Expand Down
324 changes: 322 additions & 2 deletions brownie/network/contract.py

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions brownie/project/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def _add_contract(self, build_json: Dict, alias: str = None) -> None:
contract_name = alias or build_json["contractName"]
if contract_name in self._contracts and build_json["type"] == "interface":
return
if build_json["sourcePath"].startswith("interface"):
# interfaces should generate artifact in /build/interfaces/ not /build/contracts/
return
self._contracts[contract_name] = build_json
if "pcMap" not in build_json:
# no pcMap means build artifact is for an interface
Expand Down
14 changes: 12 additions & 2 deletions brownie/project/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,23 @@ def get_abi(

output_json = compile_from_input_json(input_json, silent, allow_paths)
source_nodes = solcast.from_standard_output(output_json)
output_json = {k: v for k, v in output_json["contracts"].items() if k in path_list}
abi_json = {k: v for k, v in output_json["contracts"].items() if k in path_list}

for path, name, data in [(k, x, y) for k, v in output_json.items() for x, y in v.items()]:
for path, name, data in [(k, x, y) for k, v in abi_json.items() for x, y in v.items()]:
contract_node = next(i[name] for i in source_nodes if i.absolutePath == path)
dependencies = []
for node in [
i for i in contract_node.dependencies if i.nodeType == "ContractDefinition"
]:
dependency_name = node.name
path_str = node.parent().absolutePath
dependencies.append(_get_alias(dependency_name, path_str))

final_output[name] = {
"abi": data["abi"],
"ast": output_json["sources"][path]["ast"],
"contractName": name,
"dependencies": dependencies,
"type": "interface",
"source": contract_sources[path],
"offset": contract_node.offset,
Expand Down
2 changes: 1 addition & 1 deletion brownie/project/compiler/solidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@


def get_version() -> Version:
return solcx.get_solc_version().truncate()
return solcx.get_solc_version(with_commit_hash=True)


def compile_from_input_json(
Expand Down
5 changes: 3 additions & 2 deletions brownie/project/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def _compile(self, contract_sources: Dict, compiler_config: Dict, silent: bool)
os.chdir(cwd)

for alias, data in build_json.items():
if self._build_path is not None:
if self._build_path is not None and not data["sourcePath"].startswith("interface"):
# interfaces should generate artifact in /build/interfaces/ not /build/contracts/
if alias == data["contractName"]:
# if the alias == contract name, this is a part of the core project
path = self._build_path.joinpath(f"contracts/{alias}.json")
Expand Down Expand Up @@ -651,7 +652,7 @@ def compile_source(
if vyper_version is None:
# if no vyper compiler version is given, try to compile using solidity
compiler_config["solc"] = {
"version": solc_version or str(compiler.solidity.get_version()),
"version": solc_version or str(compiler.solidity.get_version().truncate()),
"optimize": optimize,
"runs": runs,
}
Expand Down
92 changes: 92 additions & 0 deletions brownie/utils/toposort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#######################################################################
# Implements a topological sort algorithm.
#
# Copyright 2014 True Blade Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Notes:
# Based on http://code.activestate.com/recipes/578272-topological-sort
# with these major changes:
# Added unittests.
# Deleted doctests (maybe not the best idea in the world, but it cleans
# up the docstring).
# Moved functools import to the top of the file.
# Changed assert to a ValueError.
# Changed iter[items|keys] to [items|keys], for python 3
# compatibility. I don't think it matters for python 2 these are
# now lists instead of iterables.
# Copy the input so as to leave it unmodified.
# Renamed function from toposort2 to toposort.
# Handle empty input.
# Switch tests to use set literals.
#
########################################################################

from functools import reduce as _reduce

__all__ = ["toposort", "toposort_flatten", "CircularDependencyError"]


class CircularDependencyError(ValueError):
def __init__(self, data):
# Sort the data just to make the output consistent, for use in
# error messages. That's convenient for doctests.
s = "Circular dependencies exist among these items: {{{}}}".format(
", ".join("{!r}:{!r}".format(key, value) for key, value in sorted(data.items()))
)
super(CircularDependencyError, self).__init__(s)
self.data = data


def toposort(data):
"""Dependencies are expressed as a dictionary whose keys are items
and whose values are a set of dependent items. Output is a list of
sets in topological order. The first set consists of items with no
dependences, each subsequent set consists of items that depend upon
items in the preceeding sets.
"""

# Special case empty input.
if len(data) == 0:
return

# Copy the input so as to leave it unmodified.
data = data.copy()

# Ignore self dependencies.
for k, v in data.items():
v.discard(k)
# Find all items that don't depend on anything.
extra_items_in_deps = _reduce(set.union, data.values()) - set(data.keys())
# Add empty dependences where needed.
data.update({item: set() for item in extra_items_in_deps})
while True:
ordered = set(item for item, dep in data.items() if len(dep) == 0)
if not ordered:
break
yield ordered
data = {item: (dep - ordered) for item, dep in data.items() if item not in ordered}
if len(data) != 0:
raise CircularDependencyError(data)


def toposort_flatten(data, sort=True):
"""Returns a single list of dependencies. For any set returned by
toposort(), those items are sorted and appended to the result (just to
make the results deterministic)."""

result = []
for d in toposort(data):
result.extend((sorted if sort else list)(d))
return result
16 changes: 14 additions & 2 deletions docs/api-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ Account Methods
>>> accounts[0].balance() == "100 ether"
True

.. py:classmethod:: Account.deploy(contract, *args, amount=None, gas_limit=None, gas_price=None, nonce=None, required_confs=1)
.. py:classmethod:: Account.deploy(contract, *args, amount=None, gas_limit=None, gas_price=None, nonce=None, required_confs=1, allow_revert=False, silent=False, publish_source=False,)
matnad marked this conversation as resolved.
Show resolved Hide resolved

Deploys a contract.

Expand All @@ -309,6 +309,9 @@ Account Methods
* ``gas_price``: Gas price for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using :attr:`web3.eth.gasPrice <web3.eth.Eth.gasPrice>`.
* ``nonce``: Nonce for the transaction. If none is given, the nonce is set using :meth:`web3.eth.getTransactionCount <web3.eth.Eth.getTransactionCount>` while also considering any pending transactions of the Account.
* ``required_confs``: The required :attr:`confirmations<TransactionReceipt.confirmations>` before the :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` is processed. If none is given, defaults to 1 confirmation. If 0 is given, immediately returns a pending :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` instead of a :func:`Contract <brownie.network.contract.Contract>` instance, while waiting for a confirmation in a separate thread.
* ``allow_revert``: When ``True``, forces the deployment of a contract, even if a revert reason is detected.
* ``silent``: When ``True``, suppresses any console output for the deployment.
* ``publish_source``: When ``True``, attempts to verify the source code on etherscan.io.

Returns a :func:`Contract <brownie.network.contract.Contract>` instance upon success. If the transaction reverts or you do not wait for a confirmation, a :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` is returned instead.

Expand Down Expand Up @@ -706,11 +709,12 @@ ContractContainer Attributes
ContractContainer Methods
*************************

.. py:classmethod:: ContractContainer.deploy(*args)
.. py:classmethod:: ContractContainer.deploy(*args, publish_source=False)

Deploys the contract.

* ``*args``: Contract constructor arguments.
* ``publish_source``: When ``True``, attempts to verify the source code on etherscan.io.

You can optionally include a :py:class:`dict` of :ref:`transaction parameters<transaction-parameters>` as the final argument. If you omit this or do not specify a ``'from'`` value, the transaction will be sent from the same address that deployed the contract.

Expand Down Expand Up @@ -765,6 +769,14 @@ ContractContainer Methods
raise ValueError("No contract deployed at {}".format(address))
ValueError: No contract deployed at 0xefb1336a2E6B5dfD83D4f3a8F3D2f85b7bfb61DC

.. py:classmethod:: ContractContainer.publish_source(contract, silent=False)

Verifies the source code on etherscan.io for a :func:`Project Contract <brownie.network.contract.ProjectContract>` belonging to the container.

* ``contract``: The :func:`Project Contract <brownie.network.contract.ProjectContract>` you intend to verify
* ``silent``: When True, suppresses all console output of the call.


.. py:classmethod:: ContractContainer.decode_input(calldata)

Given the call data of a transaction, returns the function signature as a string and the decoded input arguments.
Expand Down
21 changes: 21 additions & 0 deletions docs/deploy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,27 @@ The following actions WILL remove locally stored deployment data within your pro

To restore a deleted :func:`ProjectContract <brownie.network.contract.ProjectContract>` instance, or generate one for a deployment that was handled outside of Brownie, use the :func:`ContractContainer.at <ContractContainer.at>` method.

Verifying Deployments on Etherscan
==========================================

Brownie features automatic source code verification for solidity contracts on all networks supported by etherscan. To verify a contract while deploying it, add the ``publish_source=True`` argument:

.. code-block:: python

acct = accounts.load('deployment_account')
Token.deploy("My Real Token", "RLT", 18, 1e28, {'from': acct}, publish_source=True)

Verifying already deployed contracts is also possible as long as you set the identical compiler settings:

.. code-block:: python

token = Token.at("0x114A107C1931de1d5023594B14fc19d077FC4dfD")
Token.publish_source(token)


.. warning::

Make sure all your source files use the same compiler version, otherwise the verification will fail.

Saving Deployments on Development Networks
==========================================
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ hypothesis==5.41.3
prompt-toolkit==3.0.8
psutil>=5.7.3,<6.0.0
py>=1.5.0
py-solc-ast>=1.2.7,<2.0.0
py-solc-x>=1.0.2,<2.0.0
py-solc-ast>=1.2.8,<2.0.0
py-solc-x>=1.1.0,<2.0.0
pygments==2.6.1
pygments_lexer_solidity==0.5.1
pytest==6.0.1
Expand Down
Loading