diff --git a/ethereum/abi.py b/ethereum/abi.py index ff5727042..1c86bce01 100644 --- a/ethereum/abi.py +++ b/ethereum/abi.py @@ -13,7 +13,7 @@ def json_decode(x): class ContractTranslator(): - def __init__(self, full_signature): + def __init__(self, full_signature, contract_name='__contract__'): self.function_data = {} self.event_data = {} v = vars(self) @@ -24,7 +24,7 @@ def __init__(self, full_signature): continue encode_types = [f['type'] for f in sig_item['inputs']] signature = [(f['type'], f['name']) for f in sig_item['inputs']] - name = sig_item['name'] + name = sig_item.get('name', contract_name) if '(' in name: name = name[:name.find('(')] if name in v: @@ -36,19 +36,23 @@ def __init__(self, full_signature): " name. Use %s to call %s with types %r" % (name, sig_item['name'], encode_types)) sig = name + '(' + ','.join(encode_types) + ')' - if sig_item['type'] == 'function': - prefix = big_endian_to_int(utils.sha3(sig)[:4]) - decode_types = [f['type'] for f in sig_item['outputs']] - is_unknown_type = len(sig_item['outputs']) and \ + if sig_item['type'] in ('function', 'constructor'): + decode_types = [f['type'] for f in sig_item.get('outputs', [])] + is_unknown_type = len(decode_types) > 0 and \ sig_item['outputs'][0]['name'] == 'unknown_out' - self.function_data[name] = { - "prefix": prefix, + func = { "encode_types": encode_types, "decode_types": decode_types, "is_unknown_type": is_unknown_type, "is_constant": sig_item.get('constant', False), "signature": signature } + + if sig_item['type'] == 'function': + func['prefix'] = big_endian_to_int(utils.sha3(sig)[:4]) + + self.function_data[name] = func + elif sig_item['type'] == 'event': prefix = big_endian_to_int(utils.sha3(sig)) indexed = [f['indexed'] for f in sig_item['inputs']] @@ -62,9 +66,10 @@ def __init__(self, full_signature): def encode(self, name, args): fdata = self.function_data[name] - o = zpad(encode_int(fdata['prefix']), 4) + \ - encode_abi(fdata['encode_types'], args) - return o + prefix = '' + if 'prefix' in fdata: + prefix = zpad(encode_int(fdata['prefix']), 4) + return prefix + encode_abi(fdata['encode_types'], args) def decode(self, name, data): # print 'out', utils.encode_hex(data) diff --git a/ethereum/tester.py b/ethereum/tester.py index 0e9c3f07c..219657298 100644 --- a/ethereum/tester.py +++ b/ethereum/tester.py @@ -7,7 +7,7 @@ import ethereum.opcodes as opcodes import ethereum.abi as abi from ethereum.slogging import LogRecorder, configure_logging, set_level -from ethereum.utils import to_string +from ethereum.utils import to_string, is_string from ethereum._solidity import get_solidity import rlp from rlp.utils import decode_hex, encode_hex, ascii_chr @@ -132,17 +132,24 @@ def __init__(self, num_accounts=len(keys)): def __del__(self): shutil.rmtree(self.temp_data_dir) - def contract(self, code, sender=k0, endowment=0, language='serpent', gas=None): + def contract(self, code, sender=k0, endowment=0, language='serpent', + gas=None, constructor_args=[]): if language not in languages: languages[language] = __import__(language) language = languages[language] evm = language.compile(code) + if len(constructor_args) > 0: + evm += abi.encode_abi( + [a['type'] for a in constructor_args], + [a['val'] for a in constructor_args]) + o = self.evm(evm, sender, endowment) assert len(self.block.get_code(o)), "Contract code empty" return o - def abi_contract(self, code, sender=k0, endowment=0, language='serpent', contract_name='', - gas=None, log_listener=None, listen=True): + def abi_contract(self, code, sender=k0, endowment=0, language='serpent', + contract_name='', gas=None, log_listener=None, listen=True, + constructor_args=[]): if contract_name: assert language == 'solidity' cn_args = dict(contract_name=contract_name) @@ -151,10 +158,20 @@ def abi_contract(self, code, sender=k0, endowment=0, language='serpent', contrac if language not in languages: languages[language] = __import__(language) language = languages[language] + _abi = language.mk_full_signature(code, **cn_args) + if is_string(_abi): + _abi = abi.json_decode(_abi) + evm = language.compile(code, **cn_args) + + if len([i for i in _abi if i['type'] == 'constructor']) > 0 \ + and len(constructor_args) > 0: + cname = contract_name or '__contract__' + translator = abi.ContractTranslator(_abi, contract_name=cname) + evm += translator.encode(cname, constructor_args) + address = self.evm(evm, sender, endowment, gas) assert len(self.block.get_code(address)), "Contract code empty" - _abi = language.mk_full_signature(code, **cn_args) return ABIContract(self, _abi, address, listen=listen, log_listener=log_listener) diff --git a/ethereum/tests/test_solidity.py b/ethereum/tests/test_solidity.py index a6b0c18e9..efddb81a6 100644 --- a/ethereum/tests/test_solidity.py +++ b/ethereum/tests/test_solidity.py @@ -1,5 +1,8 @@ +from rlp.utils import encode_hex from ethereum import tester from ethereum import utils + + serpent_contract = """ extern solidity: [sub2:[]:i] @@ -29,8 +32,6 @@ def sub1(): def test_interop(): - if 'solidity' not in tester.languages: - return s = tester.state() c1 = s.abi_contract(serpent_contract) c2 = s.abi_contract(solidity_contract, language='solidity') # should be zoo @@ -41,6 +42,50 @@ def test_interop(): assert c2.main(c1.address) == 10 +constructor_contract = """ +contract gondor { + address public ruler; + + function gondor(address steward) { + if (steward == 0x0) { + ruler = msg.sender; + } else { + ruler = steward; + } + } +} +""" + + +def test_abi_constructor(): + s = tester.state() + c1 = s.abi_contract( + constructor_contract, language='solidity', + contract_name='gondor' + ) + c2 = s.abi_contract( + constructor_contract, constructor_args=[tester.a1], + language='solidity', contract_name='gondor' + ) + assert c1.ruler() != c2.ruler() + assert c2.ruler() == utils.encode_hex(tester.a1) + + +def test_constructor(): + s = tester.state() + a1 = s.contract(constructor_contract, language='solidity') + a2 = s.contract( + constructor_contract, constructor_args=[ + {'type': 'address', 'val': tester.a1} + ], language='solidity' + ) + _abi = tester.languages['solidity'].mk_full_signature(constructor_contract) + c1 = tester.ABIContract(s, _abi, a1) + c2 = tester.ABIContract(s, _abi, a2) + assert c1.ruler() != c2.ruler() + assert c2.ruler() == utils.encode_hex(tester.a1) + + solidity_currency = """ contract currency {