From d3de282409d953153868ca72198540b54666696b Mon Sep 17 00:00:00 2001 From: Ryan Casey Date: Sun, 14 Jun 2015 16:11:44 -0700 Subject: [PATCH 1/3] Add support for Solidity constructors. --- ethereum/abi.py | 27 +++++++++++++--------- ethereum/tester.py | 22 ++++++++++++++---- ethereum/tests/test_solidity.py | 41 ++++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/ethereum/abi.py b/ethereum/abi.py index 2662a7071..94d8499f9 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) @@ -22,7 +22,7 @@ def __init__(self, full_signature): for sig_item in full_signature: 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: @@ -34,19 +34,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']] @@ -60,9 +64,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', data.encode('hex') diff --git a/ethereum/tester.py b/ethereum/tester.py index 0e9c3f07c..85bb495d3 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,7 +132,8 @@ 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] @@ -141,8 +142,9 @@ def contract(self, code, sender=k0, endowment=0, language='serpent', gas=None): 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 +153,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 00dd858b6..c85537622 100644 --- a/ethereum/tests/test_solidity.py +++ b/ethereum/tests/test_solidity.py @@ -1,4 +1,13 @@ +from rlp.utils import encode_hex from ethereum import tester + +def needs_solidity(function): + def decorated(*args, **kwargs): + if 'solidity' in tester.languages: + return function(*args, **kwargs) + + return decorated + serpent_contract = """ extern solidity: [sub2:_:i] @@ -23,10 +32,8 @@ def sub1(): } """ - +@needs_solidity 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 @@ -34,3 +41,31 @@ def test_interop(): assert c2.sub2() == 7 assert c1.main(c2.address) == 14 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; + } + } +} +""" + +@needs_solidity +def test_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() == encode_hex(tester.a1) From 17031f51aaa0ced5e5ddf149dfb6b0d182826e80 Mon Sep 17 00:00:00 2001 From: Ryan Casey Date: Sun, 14 Jun 2015 16:31:01 -0700 Subject: [PATCH 2/3] Enable passing constructor arguments to tester.contract. --- ethereum/tester.py | 5 +++++ ethereum/tests/test_solidity.py | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ethereum/tester.py b/ethereum/tester.py index 85bb495d3..219657298 100644 --- a/ethereum/tester.py +++ b/ethereum/tester.py @@ -138,6 +138,11 @@ def contract(self, code, sender=k0, endowment=0, language='serpent', 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 diff --git a/ethereum/tests/test_solidity.py b/ethereum/tests/test_solidity.py index c85537622..7ab849032 100644 --- a/ethereum/tests/test_solidity.py +++ b/ethereum/tests/test_solidity.py @@ -57,7 +57,7 @@ def test_interop(): """ @needs_solidity -def test_constructor(): +def test_abi_constructor(): s = tester.state() c1 = s.abi_contract( constructor_contract, language='solidity', @@ -69,3 +69,18 @@ def test_constructor(): ) assert c1.ruler() != c2.ruler() assert c2.ruler() == encode_hex(tester.a1) + +@needs_solidity +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() == encode_hex(tester.a1) From 24581bbc698855acca16ea85c26212cb02d4cecd Mon Sep 17 00:00:00 2001 From: Ryan Casey Date: Sun, 14 Jun 2015 16:48:29 -0700 Subject: [PATCH 3/3] Moved a brace. --- ethereum/tests/test_solidity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/tests/test_solidity.py b/ethereum/tests/test_solidity.py index 7ab849032..bcef79440 100644 --- a/ethereum/tests/test_solidity.py +++ b/ethereum/tests/test_solidity.py @@ -76,8 +76,8 @@ def test_constructor(): a1 = s.contract(constructor_contract, language='solidity') a2 = s.contract( constructor_contract, constructor_args=[ - {'type': 'address', 'val': tester.a1 - }], language='solidity' + {'type': 'address', 'val': tester.a1} + ], language='solidity' ) _abi = tester.languages['solidity'].mk_full_signature(constructor_contract) c1 = tester.ABIContract(s, _abi, a1)