diff --git a/.gitignore b/.gitignore index e5ab662..9a4d2ab 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ logging.conf *.log .coverage *.se +*.sol +*.binary *.yaml MANIFEST upload \ No newline at end of file diff --git a/README.md b/README.md index 64d4f9a..ce33418 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,15 @@ You will need a package definition file in YAML format to get started (see examp deploy: extra: contract: short_namecoin.se +- +# Deploy Solidity contract + deploy: + Config: + contract: config.sol + solidity: + - Config + - mortal + - owned ``` ## Usage diff --git a/pyepm/deploy.py b/pyepm/deploy.py index 6559213..0f6fa82 100644 --- a/pyepm/deploy.py +++ b/pyepm/deploy.py @@ -3,7 +3,7 @@ # @Author: caktux # @Date: 2014-12-21 12:44:20 # @Last Modified by: caktux -# @Last Modified time: 2015-02-17 18:41:40 +# @Last Modified time: 2015-02-17 23:29:14 import logging @@ -11,6 +11,7 @@ import api import json import yaml +import subprocess logger = logging.getLogger(__name__) @@ -45,6 +46,7 @@ def deploy(self, wait=False): if key == 'deploy': for name in definition[key]: # Reset default values at each definition + contract_names = [] from_ = default_from gas = default_gas gas_price = default_gas_price @@ -53,6 +55,8 @@ def deploy(self, wait=False): for option in definition[key][name]: if option == 'contract': contract = definition[key][name][option] + if option == 'solidity': + contract_names = definition[key][name][option] if option == 'from': from_ = definition[key][name][option] if option == 'gas': @@ -64,7 +68,7 @@ def deploy(self, wait=False): if option == 'wait': wait = definition[key][name][option] logger.info(" Deploying %s..." % os.path.join(path, contract)) - contract_address = self.create("%s" % os.path.join(path, contract), from_, gas, gas_price, value, wait) + contract_address = self.create("%s" % os.path.join(path, contract), from_, gas, gas_price, value, wait, contract_names=contract_names) definitions = self.replace(name, definitions, contract_address, True) logger.debug(definitions) @@ -114,14 +118,42 @@ def deploy(self, wait=False): elif key == 'call': self.call(to, from_, fun_name, sig, data, gas, gas_price, value, wait) - def create(self, contract, from_, gas, gas_price, value, wait): + def compile_solidity(self, contract, contract_names=[]): + subprocess.call(["solc", "--input-file", contract, "--binary", "file"]) + contracts = [] + if not isinstance(contract_names, list): + raise Exception("Contract names must be list") + if not contract_names: + contract_names = [contract[:-4]] + for contract_name in contract_names: + filename = "%s.binary" % contract_name + evm = "0x" + open(filename).read() + contracts.append((contract_name, evm)) + return contracts + + def create(self, contract, from_, gas, gas_price, value, wait, contract_names=None): instance = api.Api(self.config) - contract = compile(open(contract).read()).encode('hex') - contract_address = instance.create(contract, from_=from_, gas=gas, gas_price=gas_price, endowment=value) - logger.info(" Contract is available at %s" % contract_address) + contract_addresses = [] + if contract[-3:] == 'sol' or contract_names: + contracts = self.compile_solidity(contract, contract_names) + if contract_names: + for contract_name, contract in contracts: + logger.info("%s: %s" % (contract_name, contract)) + contract_address = instance.create(contract, from_=from_, gas=gas, gas_price=gas_price, endowment=value) + contract_addresses.append(contract_address) + logger.info(" Contract '%s' is available at %s" % (contract_name, contract_address)) + else: + contract_address = instance.create(contract, from_=from_, gas=gas, gas_price=gas_price, endowment=value) + logger.info(" Contract is available at %s" % contract_address) + else: + contract = compile(open(contract).read()).encode('hex') + contract_address = instance.create(contract, from_=from_, gas=gas, gas_price=gas_price, endowment=value) + logger.info(" Contract is available at %s" % contract_address) if wait: instance.wait_for_next_block(verbose=(True if self.config.get('misc', 'verbosity') > 1 else False)) + if contract_addresses: + return contract_addresses return contract_address def transact(self, to, from_, fun_name, sig, data, gas, gas_price, value, wait): diff --git a/test/fixtures/example.yaml.fixture b/test/fixtures/example.yaml.fixture index 5877625..68cf5a2 100644 --- a/test/fixtures/example.yaml.fixture +++ b/test/fixtures/example.yaml.fixture @@ -39,3 +39,12 @@ deploy: extra: contract: short_namecoin.se +- +# Deploy Solidity contract + deploy: + Config: + contract: config.sol + solidity: + - Config + - mortal + - owned \ No newline at end of file diff --git a/test/test_api.py b/test/test_api.py index f6318d4..ae54c85 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -2,7 +2,7 @@ import pytest import requests -from pyepm import config +from pyepm import deploy, config config = config.get_default_config() from pyepm import api # NOQA @@ -111,6 +111,33 @@ def test_is_contract_at_contract_doesnt_exists(mocker): assert not mock_rpc(mocker, 'is_contract_at', [address], json_result=code, rpc_method='eth_codeAt', rpc_params=[address]) +def test_create_solidity(mocker): + contract = 'test/fixtures/config.sol' + deployment = deploy.Deploy(contract, config) + contracts = deployment.compile_solidity(contract, ['Config', 'mortal', 'owned']) + + address = '0x6489ecbe173ac43dadb9f4f098c3e663e8438dd7' + for contract_name, code in contracts: + rpc_params = [{'gas': '10000', + 'code': code, + 'from': 'cd2a3d9f938e13cd947ec05abc7fe734df8dd826', + 'value': '0', + 'gasPrice': '10000000000000'}] + assert mock_rpc(mocker, 'create', [code], json_result=address, + rpc_method='eth_transact', rpc_params=rpc_params) == address + +def test_is_solidity_contract_at_contract_exists(mocker): + address = '0x6489ecbe173ac43dadb9f4f098c3e663e8438dd7' + code = '0xdeadbeef' + assert mock_rpc(mocker, 'is_contract_at', [address], json_result=code, + rpc_method='eth_codeAt', rpc_params=[address]) + +def test_is_solidity_contract_at_contract_doesnt_exists(mocker): + address = '0x6489ecbe173ac43dadb9f4f098c3e663e8438dd7' + code = '0x0000000000000000000000000000000000000000000000000000000000000000' + assert not mock_rpc(mocker, 'is_contract_at', [address], json_result=code, + rpc_method='eth_codeAt', rpc_params=[address]) + def test_is_listening(mocker): assert mock_rpc(mocker, 'is_listening', [], json_result=True, rpc_method='eth_listening', rpc_params=None) diff --git a/test/test_deploy.py b/test/test_deploy.py index 38d67f9..a6bc45d 100644 --- a/test/test_deploy.py +++ b/test/test_deploy.py @@ -21,4 +21,6 @@ def test_load_yaml(): 'funid': 1, 'to': '$Subcurrency', 'value': 0}}}, - {'deploy': {'extra': {'contract': 'short_namecoin.se'}}}] + {'deploy': {'extra': {'contract': 'short_namecoin.se'}}}, + {'deploy': {'Config': {'contract': 'config.sol', + 'solidity': ['Config', 'mortal', 'owned']}}}]