diff --git a/.travis.yml b/.travis.yml index fe8752e..94c41ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ matrix: - sudo apt-get update - sudo apt-get install -y solc - pip install -r requirements-dev.txt + script: python -m pytest tests --cov=solcx + after_success: python -m coveralls - name: "Python 3.7.3 on Windows" os: windows # Windows 10.0.17134 N/A Build 17134 language: shell # 'language: python' is an error on Travis CI Windows @@ -32,8 +34,8 @@ matrix: - python -m pip install --upgrade pip - pip3 install -r requirements-dev.txt env: PATH=/c/Python37:/c/Python37/Scripts:$PATH + script: python -m pytest tests --cov=solcx + after_success: python -m coveralls -script: python -m pytest tests --cov=solcx -after_success: python -m coveralls notifications: email: false diff --git a/CHANGELOG b/CHANGELOG index 1311396..e95483c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +0.4.2 +----- + + - Fix link_code to support 0.5.x + - Remove trailing whitespace on solcx.get_version_string - fixes windows 0.5.x bug + 0.4.1 ----- diff --git a/README.md b/README.md index f0f010e..080c8f9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Py-solc-x allows the use of multiple versions of solc and installs them as neede ## Supported Versions -Py-solc-x can install the following `solc` versions: +Py-solc-x can install the following solc versions: * Linux and Windows: `>=0.4.11` * OSX: `>=0.5.0` @@ -52,18 +52,15 @@ Version('0.5.7+commit.6da8b019.Linux.gpp') >>> ``` -To install the highest compatible version based on the pragma version string: - -```python ->>> from solcx import install_solc_pragma ->>> install_solc_pragma('^0.4.20 || >0.5.5 <0.7.0') -``` - -To set the version based on the pragma version string - this will use the highest compatible version installed, if you have a compatible version installed, or it will install the highest compatible version: +You can also set the version based on the pragma version string. The highest compatible version will be used: ```python >>> from solcx import set_solc_version_pragma >>> set_solc_version_pragma('^0.4.20 || >0.5.5 <0.7.0') +Using solc version 0.5.8 +>>> set_solc_version_pragma('^0.4.20 || >0.5.5 <0.7.0', check_new=True) +Using solc version 0.5.8 +Newer compatible solc version exists: 0.5.10 ``` To view available and installed versions: @@ -73,7 +70,14 @@ To view available and installed versions: >>> get_installed_solc_versions() ['v0.4.25', 'v0.5.3'] >>> get_available_solc_versions() -['v0.5.8', 'v0.5.7', 'v0.5.6', 'v0.5.5', 'v0.5.4', 'v0.5.3', 'v0.5.2', 'v0.5.1', 'v0.5.0', 'v0.4.25', 'v0.4.24', 'v0.4.23', 'v0.4.22', 'v0.4.21', 'v0.4.20', 'v0.4.19', 'v0.4.18', 'v0.4.17', 'v0.4.16', 'v0.4.15', 'v0.4.14', 'v0.4.13', 'v0.4.12', 'v0.4.11'] +['v0.5.10', 'v0.5.9', 'v0.5.8', 'v0.5.7', 'v0.5.6', 'v0.5.5', 'v0.5.4', 'v0.5.3', 'v0.5.2', 'v0.5.1', 'v0.5.0', 'v0.4.25', 'v0.4.24', 'v0.4.23', 'v0.4.22', 'v0.4.21', 'v0.4.20', 'v0.4.19', 'v0.4.18', 'v0.4.17', 'v0.4.16', 'v0.4.15', 'v0.4.14', 'v0.4.13', 'v0.4.12', 'v0.4.11'] +``` + +To install the highest compatible version based on the pragma version string: + +```python +>>> from solcx import install_solc_pragma +>>> install_solc_pragma('^0.4.20 || >0.5.5 <0.7.0') ``` ## Standard JSON Compilation @@ -105,7 +109,7 @@ Use the `solcx.compile_standard` function to make use of the [standard-json](htt ## Legacy Combined JSON compilation ```python ->>> from solcx import compile_source, compile_files, link_code +>>> from solcx import compile_source, compile_files >>> compile_source("contract Foo { function Foo() {} }") { 'Foo': { @@ -145,8 +149,14 @@ Use the `solcx.compile_standard` function to make use of the [standard-json](htt }, }, } ->>> unlinked_code = "606060405260768060106000396000f3606060405260e060020a6000350463e7f09e058114601a575b005b60187f0c55699c00000000000000000000000000000000000000000000000000000000606090815273__TestA_________________________________90630c55699c906064906000906004818660325a03f41560025750505056" ->>> link_code(unlinked_code, {'TestA': '0xd3cda913deb6f67967b99d67acdfa1712c293601'}) +``` + +## Unlinked Libraries + +```python +>>> from solcx import link_code +>>> unlinked_bytecode = "606060405260768060106000396000f3606060405260e060020a6000350463e7f09e058114601a575b005b60187f0c55699c00000000000000000000000000000000000000000000000000000000606090815273__TestA_________________________________90630c55699c906064906000906004818660325a03f41560025750505056" +>>> link_code(unlinked_bytecode, {'TestA': '0xd3cda913deb6f67967b99d67acdfa1712c293601'}) ... "606060405260768060106000396000f3606060405260e060020a6000350463e7f09e058114601a575b005b60187f0c55699c00000000000000000000000000000000000000000000000000000000606090815273d3cda913deb6f67967b99d67acdfa1712c29360190630c55699c906064906000906004818660325a03f41560025750505056" ``` @@ -157,7 +167,7 @@ Use the `solcx.compile_standard` function to make use of the [standard-json](htt You can use this like: ```python ->>> from solcx import compile_source, compile_files, link_code +>>> from solcx import compile_files >>> compile_files([source_file_path], import_remappings=["zeppeling=/my-zeppelin-checkout-folder"]) ``` @@ -170,14 +180,14 @@ This project was recently forked from [py-solc](https://github.com/ethereum/py-s ### Tests +Py-solc-x is tested on Linux and Windows with solc versions ``>=0.4.11``. + To run the test suite: ```bash $ pytest tests/ ``` -Tests are still a work in progress. The old (some failing) ``py-solc`` tests are available [here](https://github.com/iamdefinitelyahuman/py-solc-x/tree/master/tests-old). - ## License This project is licensed under the [MIT license](LICENSE). diff --git a/setup.py b/setup.py index c97f56a..7e7ec9d 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='py-solc-x', - version='0.4.1', + version='0.4.2', description="""Python wrapper around the solc binary with 0.5.x support""", long_description_markdown_filename='README.md', author='Ben Hauser (forked from py-solc by Piper Merriam)', diff --git a/solcx/install.py b/solcx/install.py index 1f59a7d..1ada5b2 100644 --- a/solcx/install.py +++ b/solcx/install.py @@ -2,11 +2,11 @@ Install solc """ from io import BytesIO -import operator import os from pathlib import Path import re import requests +from semantic_version import Version, Spec import shutil import stat import subprocess @@ -109,37 +109,36 @@ def set_solc_version(version, silent=False): print("Using solc version {}".format(solc_version)) -def set_solc_version_pragma(version, silent=False): - version = version.strip() - comparator_set_range = [i.strip() for i in version.split('||')] - installed_versions = get_installed_solc_versions() - comparator_regex = re.compile( - r'(?P([<>]?=?|\^))(?P(?P\d+)\.(?P\d+)\.(?P\d+))' +def set_solc_version_pragma(pragma_string, silent=False, check_new=False): + version = _select_pragma_version( + pragma_string, + [Version(i[1:]) for i in get_installed_solc_versions()] ) - range_flag = False - set_version = None - for installed_version in reversed(installed_versions): - for comparator_set in comparator_set_range: - comparators = [m.groupdict() for m in comparator_regex.finditer(comparator_set)] - comparator_set_flag = True - for comparator in comparators: - operator = comparator['operator'] - if not _compare_versions(installed_version, comparator['version'], operator): - comparator_set_flag = False - if comparator_set_flag: - range_flag = True - if range_flag: - set_version = installed_version - newer_version = install_solc_pragma(version, install=False) - if not silent and _compare_versions(set_version, newer_version, '<'): - print("Newer compatible solc version exists: {}".format(newer_version)) - break - if not set_version: - set_version = install_solc_pragma(version) + if not version: + raise SolcNotInstalled( + "No compatible solc version installed. " + + "Use solcx.install_solc_version_pragma('{}') to install.".format(version) + ) global solc_version - solc_version = set_version + solc_version = version if not silent: print("Using solc version {}".format(solc_version)) + if check_new: + latest = install_solc_pragma(pragma_string, False) + if Version(latest) > Version(version): + print("Newer compatible solc version exists: {}".format(latest)) + + +def install_solc_pragma(pragma_string, install=True): + version = _select_pragma_version( + pragma_string, + [Version(i[1:]) for i in get_available_solc_versions()] + ) + if not version: + raise ValueError("Compatible solc version does not exist") + if install: + install_solc(version) + return version def get_available_solc_versions(headers={}): @@ -154,6 +153,20 @@ def get_available_solc_versions(headers={}): return versions +def _select_pragma_version(pragma_string, version_list): + comparator_set_range = pragma_string.replace(" ", "").split('||') + comparator_regex = re.compile(r"(([<>]?=?|\^)\d+\.\d+\.\d+)+") + version = None + + for comparator_set in comparator_set_range: + spec = Spec(*(i[0] for i in comparator_regex.findall(comparator_set))) + selected = spec.select(version_list) + if selected and (not version or version < selected): + version = selected + if version: + return str(version) + + def get_installed_solc_versions(): return sorted(i.name[5:] for i in get_solc_folder().glob('solc-v*')) @@ -177,64 +190,11 @@ def install_solc(version, allow_osx=False): print("solc {} successfully installed at: {}".format(version, binary_path)) -def install_solc_pragma(version, install=True): - version = version.strip() - comparator_set_range = [i.strip() for i in version.split('||')] - comparator_regex = re.compile( - r'(?P([<>]?=?|\^))(?P(?P\d+)\.(?P\d+)\.(?P\d+))' - ) - versions_json = requests.get(ALL_RELEASES).json() - range_flag = False - for version_json in versions_json: - for comparator_set in comparator_set_range: - comparators = [m.groupdict() for m in comparator_regex.finditer(comparator_set)] - comparator_set_flag = True - for comparator in comparators: - operator = comparator['operator'] - if not _compare_versions(version_json['tag_name'], comparator['version'], operator): - comparator_set_flag = False - if comparator_set_flag: - range_flag = True - if range_flag: - _check_version(version_json['tag_name']) - if install: - install_solc(version_json['tag_name']) - return version_json['tag_name'] - raise ValueError("Compatible solc version does not exist") - - -operator_map = { - '<': operator.lt, - '<=': operator.le, - '>=': operator.ge, - '>': operator.gt, - '^': operator.ge -} - - -def _compare_versions(v1, v2, comp='='): - v1 = v1.lstrip('v') - v2 = v2.lstrip('v') - v1_split = [int(i) for i in v1.split('.')] - v2_split = [int(i) for i in v2.split('.')] - if comp in ('=', '==', '', None): - return v1_split == v2_split - if comp not in operator_map: - raise ValueError("operator {} not supported".format(comp)) - idx = next((i for i in range(3) if v1_split[i] != v2_split[i]), 2) - if comp == '^' and idx != 2: - return False - return operator_map[comp](v1_split[idx], v2_split[idx]) - - def _check_version(version): - version = "v0." + version.lstrip("v0.") - if version.count('.') != 2: - raise ValueError("Invalid solc version '{}' - must be in the format v0.x.x".format(version)) - v = [int(i) for i in version[1:].split('.')] - if v[1] < 4 or (v[1] == 4 and v[2] < 11): + version = Version(version.lstrip('v')) + if version not in Spec('>=0.4.11'): raise ValueError("py-solc-x does not support solc versions <0.4.11") - return version + return "v" + str(version) def _check_subprocess_call(command, message=None, verbose=True, **proc_kwargs): diff --git a/solcx/main.py b/solcx/main.py index 125713f..2461a8b 100644 --- a/solcx/main.py +++ b/solcx/main.py @@ -42,7 +42,7 @@ def get_solc_version_string(**kwargs): stderr_data=stderrdata, message="Unable to extract version string from command output", ) - return version_string + return version_string.rstrip() def get_solc_version(**kwargs): @@ -182,15 +182,15 @@ def compile_standard(input_data, allow_empty=False, **kwargs): return compiler_output -def link_code(unlinked_data, libraries): +def link_code(unlinked_bytecode, libraries): libraries_arg = ','.join(( ':'.join((lib_name, lib_address)) for lib_name, lib_address in libraries.items() )) stdoutdata, stderrdata, _, _ = solc_wrapper( - stdin=unlinked_data, + stdin=unlinked_bytecode, link=True, libraries=libraries_arg, ) - return stdoutdata.strip() + return stdoutdata.replace("Linking completed.", "").strip() diff --git a/solcx/wrapper.py b/solcx/wrapper.py index 13abdae..55e430a 100644 --- a/solcx/wrapper.py +++ b/solcx/wrapper.py @@ -153,8 +153,8 @@ def solc_wrapper(solc_binary=None, command.extend(('--evm-version', evm_version)) if ( - standard_json is None and - source_files is None and + not standard_json and + not source_files and "v0.5" in command[0] ): command.append('-') diff --git a/tests-old/conftest.py b/tests-old/conftest.py deleted file mode 100644 index 1cf8b3b..0000000 --- a/tests-old/conftest.py +++ /dev/null @@ -1,101 +0,0 @@ -import pytest - -import textwrap - -from semantic_version import Spec - -from solc import get_solc_version -from solc.main import solc_supports_standard_json_interface - - -@pytest.fixture() -def contracts_dir(tmpdir): - return str(tmpdir.mkdir("contracts")) - - -@pytest.fixture(scope="session") -def solc_version(): - return get_solc_version() - - -@pytest.fixture() -def supported_solc_version(solc_version): - if solc_version not in Spec('>=0.4.1,<=0.4.25,!=0.4.10,!=0.4.3,!=0.4.4,!=0.4.5'): - raise AssertionError("Unsupported compiler version: {0}".format(solc_version)) - - return solc_version - - -@pytest.fixture() -def is_new_key_format(): - return get_solc_version() in Spec('>=0.4.9') - - -@pytest.fixture() -def FOO_SOURCE(supported_solc_version, solc_version): - if solc_version in Spec('<0.4.17'): - return textwrap.dedent('''\ - pragma solidity ^0.4.0; - - contract Foo { - function Foo() {} - - function return13() public returns (uint) { - return 13; - } - } - ''') - else: - return textwrap.dedent('''\ - pragma solidity ^0.4.17; - - contract Foo { - function Foo() public {} - - function return13() public pure returns (uint) { - return 13; - } - } - ''') - - -@pytest.fixture() -def BAR_SOURCE(supported_solc_version): - return textwrap.dedent('''\ - pragma solidity ^0.4.0; - - contract Bar { - function Bar() public {} - } - ''') - - -@pytest.fixture() -def BAZ_SOURCE(supported_solc_version): - return textwrap.dedent('''\ - pragma solidity ^0.4.0; - - import "contracts/Bar.sol"; - - contract Baz is Bar { - function Baz() public {} - - function get_funky() public returns (string) { - return "funky"; - } - } - ''') - - -@pytest.fixture() -def INVALID_SOURCE(supported_solc_version): - return textwrap.dedent('''\ - pragma solidity ^0.4.0; - contract Foo { - ''') - - -def pytest_runtest_setup(item): - if (item.get_marker('requires_standard_json') is not None and - not solc_supports_standard_json_interface()): - pytest.skip('requires `--standard-json` support') diff --git a/tests-old/core/compilation/test_compile_empty.py b/tests-old/core/compilation/test_compile_empty.py deleted file mode 100644 index 554d176..0000000 --- a/tests-old/core/compilation/test_compile_empty.py +++ /dev/null @@ -1,23 +0,0 @@ -import os - -import pytest - -from solc import ( - compile_files, - compile_standard, -) - -from solc.exceptions import ContractsNotFound - - -def test_compile_empty_folder(): - """Execute compile on a folder without contracts.""" - - with pytest.raises(ContractsNotFound): - compile_files([]) - - -@pytest.mark.requires_standard_json -def test_compile_standard_empty_sources(): - with pytest.raises(ContractsNotFound): - compile_standard({'language': 'Solidity', 'sources': {}}) diff --git a/tests-old/core/compilation/test_compile_from_source_code.py b/tests-old/core/compilation/test_compile_from_source_code.py deleted file mode 100644 index 0a50321..0000000 --- a/tests-old/core/compilation/test_compile_from_source_code.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest - -from solc import compile_source - -pytestmark = pytest.mark.usefixtures('supported_solc_version') - - -def test_source_code_compilation(FOO_SOURCE, is_new_key_format): - output = compile_source(FOO_SOURCE, optimize=True) - assert output - - if is_new_key_format: - contact_key = ':Foo' - else: - contact_key = 'Foo' - - assert contact_key in output - - foo_contract_data = output[contact_key] - assert 'bin' in foo_contract_data - assert 'bin-runtime' in foo_contract_data diff --git a/tests-old/core/compilation/test_compile_standard.py b/tests-old/core/compilation/test_compile_standard.py deleted file mode 100644 index 7cf3e4e..0000000 --- a/tests-old/core/compilation/test_compile_standard.py +++ /dev/null @@ -1,97 +0,0 @@ -import os - -import pytest - -from solc import ( - compile_standard, -) -from solc.exceptions import SolcError - -pytestmark = pytest.mark.requires_standard_json - - -def contract_in_output_map(contract_name, contract_map): - try: - return int(contract_map - ['contracts/{}.sol'.format(contract_name)] - [contract_name] - ['evm']['bytecode']['object'], 16) is not None - except: - return False - - -def test_compile_standard(FOO_SOURCE): - result = compile_standard({ - 'language': 'Solidity', - 'sources': { - 'contracts/Foo.sol': { - 'content': FOO_SOURCE, - }, - }, - 'outputSelection': { - "*": {"*": ["evm.bytecode.object"]}, - }, - }) - - assert isinstance(result, dict) - assert 'contracts' in result - assert contract_in_output_map('Foo', result['contracts']) - - -def test_compile_standard_invalid_source(INVALID_SOURCE): - with pytest.raises(SolcError): - compile_standard({ - 'language': 'Solidity', - 'sources': { - 'contracts/Foo.sol': { - 'content': INVALID_SOURCE, - }, - }, - 'outputSelection': { - "*": {"*": ["evm.bytecode.object"]}, - }, - }) - - -def test_compile_standard_with_dependency(BAR_SOURCE, BAZ_SOURCE): - result = compile_standard({ - 'language': 'Solidity', - 'sources': { - 'contracts/Bar.sol': { - 'content': BAR_SOURCE, - }, - 'contracts/Baz.sol': { - 'content': BAZ_SOURCE, - }, - }, - 'outputSelection': { - "*": {"*": ["evm.bytecode.object"]}, - }, - }) - - assert isinstance(result, dict) - assert 'contracts' in result - assert contract_in_output_map('Bar', result['contracts']) - assert contract_in_output_map('Baz', result['contracts']) - - -def test_compile_standard_with_file_paths(FOO_SOURCE, is_new_key_format, contracts_dir): - source_file_path = os.path.join(contracts_dir, 'Foo.sol') - with open(source_file_path, 'w') as source_file: - source_file.write(FOO_SOURCE) - - result = compile_standard({ - 'language': 'Solidity', - 'sources': { - 'contracts/Foo.sol': { - 'urls': [source_file_path], - }, - }, - 'outputSelection': { - "*": {"*": ["evm.bytecode.object"]}, - }, - }, allow_paths=contracts_dir) - - assert isinstance(result, dict) - assert 'contracts' in result - assert contract_in_output_map('Foo', result['contracts']) diff --git a/tests-old/core/compilation/test_compiler_from_source_file.py b/tests-old/core/compilation/test_compiler_from_source_file.py deleted file mode 100644 index ad09c42..0000000 --- a/tests-old/core/compilation/test_compiler_from_source_file.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest - -import os - -from semantic_version import Spec -from solc import ( - get_solc_version, - compile_files, -) - -pytestmark = pytest.mark.usefixtures('supported_solc_version') - - -def test_source_files_compilation(FOO_SOURCE, is_new_key_format, contracts_dir): - source_file_path = os.path.join(contracts_dir, 'Foo.sol') - with open(source_file_path, 'w') as source_file: - source_file.write(FOO_SOURCE) - - output = compile_files([source_file_path], optimize=True) - - assert output - - if is_new_key_format: - contract_key = '{0}:Foo'.format(os.path.abspath(source_file_path)) - else: - contract_key = 'Foo' - - assert contract_key in output - - foo_contract_data = output[contract_key] - assert 'bin' in foo_contract_data - assert 'bin-runtime' in foo_contract_data diff --git a/tests-old/core/compilation/test_compiler_remappings.py b/tests-old/core/compilation/test_compiler_remappings.py deleted file mode 100644 index a0c1c66..0000000 --- a/tests-old/core/compilation/test_compiler_remappings.py +++ /dev/null @@ -1,30 +0,0 @@ -import os - -from semantic_version import Spec -from solc import ( - get_solc_version, - compile_files, -) - - -def test_import_remapping(contracts_dir, is_new_key_format, BAR_SOURCE, BAZ_SOURCE): - solc_version = get_solc_version() - - source_file_path = os.path.join(contracts_dir, 'Bar.sol') - with open(source_file_path, 'w') as source_file: - source_file.write(BAR_SOURCE) - - source_file_path = os.path.join(contracts_dir, 'Baz.sol') - with open(source_file_path, 'w') as source_file: - source_file.write(BAZ_SOURCE) - - output = compile_files([source_file_path], import_remappings=["contracts={}".format(contracts_dir)]) - - assert output - - if is_new_key_format: - contact_key = '{0}:Baz'.format(os.path.abspath(source_file_path)) - else: - contact_key = 'Baz' - - assert contact_key in output diff --git a/tests-old/core/linking/test_library_linking.py b/tests-old/core/linking/test_library_linking.py deleted file mode 100644 index c8cddda..0000000 --- a/tests-old/core/linking/test_library_linking.py +++ /dev/null @@ -1,32 +0,0 @@ -from solc import link_code - -CODE = "6060604052610199806100126000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806344fd4fa01461004f57806358de5f041461005e578063e7f09e051461006d5761004d565b005b61005c600480505061007c565b005b61006b60048050506100db565b005b61007a600480505061013a565b005b73__TestB_________________________________630c55699c604051817c01000000000000000000000000000000000000000000000000000000000281526004018090506000604051808303818660325a03f415610002575050505b565b73__TestC_________________________________630c55699c604051817c01000000000000000000000000000000000000000000000000000000000281526004018090506000604051808303818660325a03f415610002575050505b565b73__TestA_________________________________630c55699c604051817c01000000000000000000000000000000000000000000000000000000000281526004018090506000604051808303818660325a03f415610002575050505b56" - -TEST_A_ADDRESS = "0xd3cda913deb6f67967b99d67acdfa1712c293601" -TEST_B_ADDRESS = "0x304a554a310c7e546dfe434669c62820b7d83490" -TEST_C_ADDRESS = "0xbb9bc244d798123fde783fcc1c72d3bb8c189413" - -LINKED_CODE = "6060604052610199806100126000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806344fd4fa01461004f57806358de5f041461005e578063e7f09e051461006d5761004d565b005b61005c600480505061007c565b005b61006b60048050506100db565b005b61007a600480505061013a565b005b73304a554a310c7e546dfe434669c62820b7d83490630c55699c604051817c01000000000000000000000000000000000000000000000000000000000281526004018090506000604051808303818660325a03f415610002575050505b565b73bb9bc244d798123fde783fcc1c72d3bb8c189413630c55699c604051817c01000000000000000000000000000000000000000000000000000000000281526004018090506000604051808303818660325a03f415610002575050505b565b73d3cda913deb6f67967b99d67acdfa1712c293601630c55699c604051817c01000000000000000000000000000000000000000000000000000000000281526004018090506000604051808303818660325a03f415610002575050505b56" - - -def test_partial_code_linking(): - output = link_code(CODE, {'TestA': TEST_A_ADDRESS}) - assert '__TestA__' not in output - assert '__TestB__' in output - assert '__TestC__' in output - assert TEST_A_ADDRESS[2:] in output - - -def test_full_code_linking(): - output = link_code(CODE, { - 'TestA': TEST_A_ADDRESS, - 'TestB': TEST_B_ADDRESS, - 'TestC': TEST_C_ADDRESS, - }) - assert '__TestA__' not in output - assert '__TestB__' not in output - assert '__TestC__' not in output - assert TEST_A_ADDRESS[2:] in output - assert TEST_B_ADDRESS[2:] in output - assert TEST_C_ADDRESS[2:] in output - assert output == LINKED_CODE diff --git a/tests-old/core/utility/test_is_executable_available.py b/tests-old/core/utility/test_is_executable_available.py deleted file mode 100644 index f4076b4..0000000 --- a/tests-old/core/utility/test_is_executable_available.py +++ /dev/null @@ -1,11 +0,0 @@ -from solc.utils.filesystem import ( - is_executable_available, -) - - -def test_ls_is_available(): - assert is_executable_available('ls') is True - - -def test_for_unavailable_executable(): - assert is_executable_available('there_should_not_be_an_executable_by_this_name') is False diff --git a/tests-old/core/utility/test_solc_supports_standard_json.py b/tests-old/core/utility/test_solc_supports_standard_json.py deleted file mode 100644 index 5bfedde..0000000 --- a/tests-old/core/utility/test_solc_supports_standard_json.py +++ /dev/null @@ -1,13 +0,0 @@ -from solc import get_solc_version -from solc.main import solc_supports_standard_json_interface - -import semantic_version - - -def test_solc_supports_standard_json_interface(): - version = get_solc_version() - - if version in semantic_version.Spec("<0.4.11"): - assert not solc_supports_standard_json_interface() - else: - assert solc_supports_standard_json_interface() diff --git a/tests-old/core/utility/test_solc_version.py b/tests-old/core/utility/test_solc_version.py deleted file mode 100644 index 5fe0b80..0000000 --- a/tests-old/core/utility/test_solc_version.py +++ /dev/null @@ -1,9 +0,0 @@ -from solc import get_solc_version - -import semantic_version - - -def test_get_solc_version(): - version = get_solc_version() - - assert isinstance(version, semantic_version.Version) diff --git a/tests-old/core/wrapper/test_solc_wrapper.py b/tests-old/core/wrapper/test_solc_wrapper.py deleted file mode 100644 index a5cd6ab..0000000 --- a/tests-old/core/wrapper/test_solc_wrapper.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import unicode_literals - -import pytest - -import json -import os - -from solc import get_solc_version -from solc.wrapper import ( - solc_wrapper, -) - - -def is_benign(err): - return not err or err in ( - 'Warning: This is a pre-release compiler version, please do not use it in production.\n', - ) - - -def test_help(): - output, err, _, _ = solc_wrapper(help=True, success_return_code=1) - assert output - assert 'Solidity' in output - assert is_benign(err) - - -def test_version(): - output, err, _, _ = solc_wrapper(version=True) - assert output - assert 'Version' in output - assert is_benign(err) - - -def test_providing_stdin(FOO_SOURCE): - output, err, _, _ = solc_wrapper(stdin=FOO_SOURCE, bin=True) - assert output - assert 'Foo' in output - assert is_benign(err) - - -def test_providing_single_source_file(contracts_dir, FOO_SOURCE): - source_file_path = os.path.join(contracts_dir, 'Foo.sol') - with open(source_file_path, 'w') as source_file: - source_file.write(FOO_SOURCE) - - output, err, _, _ = solc_wrapper(source_files=[source_file_path], bin=True) - assert output - assert 'Foo' in output - assert is_benign(err) - - -def test_providing_multiple_source_files(contracts_dir, FOO_SOURCE, BAR_SOURCE): - source_file_a_path = os.path.join(contracts_dir, 'Foo.sol') - source_file_b_path = os.path.join(contracts_dir, 'Bar.sol') - - with open(source_file_a_path, 'w') as source_file: - source_file.write(FOO_SOURCE) - with open(source_file_b_path, 'w') as source_file: - source_file.write(BAR_SOURCE) - - output, err, _, _ = solc_wrapper(source_files=[source_file_a_path, source_file_b_path], bin=True) - assert output - assert 'Foo' in output - assert 'Bar' in output - assert is_benign(err) - - -@pytest.mark.requires_standard_json -def test_providing_standard_json_input(FOO_SOURCE, BAR_SOURCE): - stdin = json.dumps({ - "language": "Solidity", - "sources": { - "Foo.sol": { - "content": FOO_SOURCE - }, - "Bar.sol": { - "content": BAR_SOURCE - } - }, - "settings": - { - "outputSelection": { - "*": { - "*": [ "abi", "evm.bytecode.link_references", "evm.bytecode.object", "devdoc", "metadata", "userdoc" ] - } - } - } - }) - - output, err, _, _ = solc_wrapper(stdin=stdin, standard_json=True) - output = json.loads(output) - assert output - assert 'Foo.sol' in output['contracts'] - assert 'Bar.sol' in output['contracts'] - assert is_benign(err) diff --git a/tests-old/installation/test_solc_installation.py b/tests-old/installation/test_solc_installation.py deleted file mode 100644 index 57eeaa7..0000000 --- a/tests-old/installation/test_solc_installation.py +++ /dev/null @@ -1,66 +0,0 @@ -import os - -import pytest - -import semantic_version - -from solc import ( - get_solc_version, -) -from solc.install import ( - INSTALL_FUNCTIONS, - get_platform, - install_solc, - get_executable_path, - get_extract_path, -) - - -INSTALLATION_TEST_PARAMS = tuple( - (platform, version) - for platform, platform_install_functions in INSTALL_FUNCTIONS.items() - for version in platform_install_functions.keys() -) - - -@pytest.mark.skipif( - 'SOLC_RUN_INSTALL_TESTS' not in os.environ, - reason=( - "Installation tests will not run unless `SOLC_RUN_INSTALL_TESTS` " - "environment variable is set" - ), -) -@pytest.mark.parametrize( - "platform,version", - INSTALLATION_TEST_PARAMS, -) -def test_solc_installation_as_function_call(monkeypatch, tmpdir, platform, version): - if get_platform() != platform: - pytest.skip("Wront platform for install script") - - base_install_path = str(tmpdir.mkdir("temporary-dir")) - monkeypatch.setenv('SOLC_BASE_INSTALL_PATH', base_install_path) - - # sanity check that it's not already installed. - executable_path = get_executable_path(version) - assert not os.path.exists(executable_path) - - install_solc(identifier=version, platform=platform) - - assert os.path.exists(executable_path) - monkeypatch.setenv('SOLC_BINARY', executable_path) - - extract_path = get_extract_path(version) - if os.path.exists(extract_path): - contains_so_file = any( - os.path.basename(path).partition(os.path.extsep)[2] == 'so' - for path - in os.listdir(extract_path) - ) - if contains_so_file: - monkeypatch.setenv('LD_LIBRARY_PATH', extract_path) - - actual_version = get_solc_version() - expected_version = semantic_version.Spec(version.lstrip('v')) - - assert actual_version in expected_version diff --git a/tests/conftest.py b/tests/conftest.py index 81e95ea..2b5bfad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +#!/usr/bin/python3 + from base64 import b64encode import os import pytest @@ -5,15 +7,6 @@ import solcx -TEST_CONTRACT = '''pragma solidity >=0.4.11 <0.6.0; - -contract Foo { - function return13() public returns (uint) { - return 13; - } -} -''' - if sys.platform == "darwin": VERSIONS = solcx.get_installed_solc_versions() else: @@ -49,13 +42,48 @@ def all_versions(request): @pytest.fixture() -def contract_path(tmp_path): - source = tmp_path.joinpath('test.sol') +def foo_source(): + yield """pragma solidity >=0.4.11; + +contract Foo { + function return13() public returns (uint) { + return 13; + } +} +""" + + +@pytest.fixture() +def bar_source(): + yield """ +pragma solidity >=0.4.11; + +import "contracts/Foo.sol"; + +contract Bar is Foo { + function getFunky() public returns (bytes4) { + return 0x420Faded; + } +}""" + + +@pytest.fixture() +def invalid_source(): + yield """pragma solidity >=0.4.11; +contract Foo {""" + + +@pytest.fixture() +def foo_path(tmp_path, foo_source): + source = tmp_path.joinpath('Foo.sol') with source.open('w') as fp: - fp.write(TEST_CONTRACT) - return str(source) + fp.write(foo_source) + return source.as_posix() @pytest.fixture() -def contract_source(): - yield TEST_CONTRACT +def bar_path(tmp_path, bar_source): + source = tmp_path.joinpath('Bar.sol') + with source.open('w') as fp: + fp.write(bar_source) + return source.as_posix() diff --git a/tests/test_compile.py b/tests/test_compile.py index 2e8e926..6affcb6 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -1,6 +1,10 @@ +#!/usr/bin/python3 + +from pathlib import Path import pytest import solcx +from solcx.exceptions import ContractsNotFound @pytest.fixture(autouse=True) @@ -8,9 +12,35 @@ def setup(all_versions): pass -def test_compile_files(contract_path): - solcx.compile_files([contract_path]) +def test_compile_source(foo_source): + output = solcx.compile_source(foo_source, optimize=True) + _compile_assertions(output, ":Foo") + + +def test_compile_source_empty(): + with pytest.raises(ContractsNotFound): + solcx.compile_source(" ") + + +def test_compile_files(foo_path): + output = solcx.compile_files([foo_path]) + _compile_assertions(output, f"{foo_path}:Foo") + + +def test_import_remapping(foo_path, bar_path): + path = Path(bar_path).parent.as_posix() + output = solcx.compile_files([bar_path], import_remappings=[f"contracts={path}"]) + assert output + assert f'{bar_path}:Bar' in output + + +def test_compile_files_empty(): + with pytest.raises(ContractsNotFound): + solcx.compile_files([]) -def test_compile_source(contract_source): - solcx.compile_source(contract_source) +def _compile_assertions(output, key): + assert output + assert key in output + assert 'bin' in output[key] + assert 'bin-runtime' in output[key] diff --git a/tests/test_compile_standard.py b/tests/test_compile_standard.py new file mode 100644 index 0000000..7bd17b4 --- /dev/null +++ b/tests/test_compile_standard.py @@ -0,0 +1,67 @@ +#!/usr/bin/python3 + +from pathlib import Path +import pytest + +import solcx +from solcx.exceptions import SolcError, ContractsNotFound + + +@pytest.fixture(autouse=True) +def setup(all_versions): + pass + + +@pytest.fixture +def input_json(): + json = { + 'language': 'Solidity', + 'sources': {}, + 'settings': { + 'outputSelection': {"*": {"*": ["evm.bytecode.object"]}} + } + } + yield json + + +def test_compile_standard(input_json, foo_source): + input_json['sources'] = {'contracts/Foo.sol': {'content': foo_source}} + result = solcx.compile_standard(input_json) + + _compile_assertions(result, "Foo") + + +def test_compile_standard_invalid_source(input_json, invalid_source): + input_json['sources'] = {'contracts/Foo.sol': {'content': invalid_source}} + with pytest.raises(SolcError): + solcx.compile_standard(input_json) + + +def test_compile_standard_with_dependency(input_json, foo_source, bar_source): + input_json['sources'] = { + 'contracts/Foo.sol': {'content': foo_source}, + 'contracts/Bar.sol': {'content': bar_source}, + } + result = solcx.compile_standard(input_json) + + _compile_assertions(result, "Foo", "Bar") + + +def test_compile_standard_with_file_paths(input_json, foo_path): + input_json['sources'] = {'contracts/Foo.sol': {'urls': [foo_path]}} + result = solcx.compile_standard(input_json, allow_paths=Path(foo_path).parent.as_posix()) + + _compile_assertions(result, "Foo") + + +def test_compile_standard_empty(): + with pytest.raises(ContractsNotFound): + solcx.compile_standard({'language': 'Solidity', 'sources': {}}) + + +def _compile_assertions(output_json, *contract_names): + assert isinstance(output_json, dict) + assert 'contracts' in output_json + contracts = output_json['contracts'] + for key in contract_names: + assert int(contracts[f'contracts/{key}.sol'][key]['evm']['bytecode']['object'], 16) diff --git a/tests/test_library_linking.py b/tests/test_library_linking.py new file mode 100644 index 0000000..2dd9087 --- /dev/null +++ b/tests/test_library_linking.py @@ -0,0 +1,56 @@ +#!/usr/bin/python3 + +import pytest + +import solcx + +source = """pragma solidity >=0.4.11; +library UnlinkedLib { + function linkMethod(uint _value, uint _multiplier) public returns (uint) { + return _value * _multiplier; + } +} + +library OtherUnlinkedLib { + function otherLinkMethod(uint _value, uint _multiplier) public returns (uint) { + return _value * _multiplier; + } +} + +contract LinkTester { + function testLibraryLinks(uint amount, uint multiple) external returns (uint) { + uint a = UnlinkedLib.linkMethod(amount, multiple); + return OtherUnlinkedLib.otherLinkMethod(a, multiple); + } +}""" + +addr1 = "0x1234567890123456789012345678901234567890" +addr2 = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + + +@pytest.fixture +def bytecode(): + yield solcx.compile_source(source)[':LinkTester']['bin'] + + +def test_partial_link(all_versions, bytecode): + assert '_' in bytecode + assert addr1[2:] not in bytecode + output = solcx.link_code(bytecode, {':UnlinkedLib': addr1}) + assert output != bytecode + assert '_' in output + assert addr1[2:] in output + + +def test_full_link(all_versions, bytecode): + assert '_' in bytecode + assert addr1[2:] not in bytecode + assert addr2[2:] not in bytecode + output = solcx.link_code( + bytecode, + {':UnlinkedLib': addr1, ':OtherUnlinkedLib': addr2} + ) + assert output != bytecode + assert '_' not in output + assert addr1[2:] in output + assert addr2[2:] in output diff --git a/tests/test_solc_version.py b/tests/test_solc_version.py new file mode 100644 index 0000000..a515fc1 --- /dev/null +++ b/tests/test_solc_version.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 + +import functools +import pytest +from semantic_version import Version + +import solcx +from solcx.exceptions import SolcNotInstalled + + +@pytest.fixture +def pragmapatch(monkeypatch): + monkeypatch.setattr( + 'solcx.install.get_installed_solc_versions', + lambda: ['v0.4.2', 'v0.4.11', 'v0.4.25', 'v0.5.0', 'v0.5.4', 'v0.5.7', 'v1.2.3'] + ) + monkeypatch.setattr( + 'solcx.install.get_available_solc_versions', + lambda: ['v0.4.11', 'v0.4.24', 'v0.4.26', 'v0.5.3', 'v0.6.0'] + ) + + +def test_solc_supports_standard_json_interface(monkeypatch): + monkeypatch.setattr('solcx.main.get_solc_version', lambda: Version("0.5.0")) + assert solcx.main.solc_supports_standard_json_interface() + monkeypatch.setattr('solcx.main.get_solc_version', lambda: Version("0.4.11")) + assert solcx.main.solc_supports_standard_json_interface() + monkeypatch.setattr('solcx.main.get_solc_version', lambda: Version("0.4.10")) + assert not solcx.main.solc_supports_standard_json_interface() + + +def test_get_solc_version(all_versions): + v = solcx.get_solc_version() + assert isinstance(v, Version) + + +def test_get_solc_version_string(all_versions): + v = solcx.get_solc_version_string() + assert isinstance(v, str) + + +def test_set_solc_version_pragma(pragmapatch): + set_pragma = functools.partial(solcx.set_solc_version_pragma, check_new=True) + set_pragma('pragma solidity 0.4.11;') + assert solcx.install.solc_version == '0.4.11' + set_pragma('pragma solidity ^0.4.11;') + assert solcx.install.solc_version == '0.4.25' + set_pragma('pragma solidity >=0.4.0<0.4.25;') + assert solcx.install.solc_version == '0.4.11' + set_pragma('pragma solidity >=0.4.2;') + assert solcx.install.solc_version == '1.2.3' + set_pragma('pragma solidity >=0.4.2<0.5.5;') + assert solcx.install.solc_version == '0.5.4' + set_pragma('pragma solidity ^0.4.2 || 0.5.5;') + assert solcx.install.solc_version == '0.4.25' + set_pragma('pragma solidity ^0.4.2 || >=0.5.4<0.7.0;') + assert solcx.install.solc_version == '0.5.7' + with pytest.raises(SolcNotInstalled): + set_pragma('pragma solidity ^0.7.1;') + + +def test_install_solc_version_pragma(pragmapatch): + install_pragma = functools.partial(solcx.install_solc_pragma, install=False) + assert install_pragma('pragma solidity ^0.4.11;') == "0.4.26" + assert install_pragma('pragma solidity >=0.4.0<0.4.25;') == "0.4.24" + assert install_pragma('pragma solidity >=0.4.2;') == "0.6.0" + assert install_pragma('pragma solidity >=0.4.2<0.5.5;') == "0.5.3" + assert install_pragma('pragma solidity ^0.4.2 || 0.5.5;') == "0.4.26" + assert install_pragma('pragma solidity ^0.4.2 || >=0.5.4<0.7.0;') == "0.6.0" + with pytest.raises(ValueError): + install_pragma('pragma solidity ^0.7.1;')