Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Add support for Solidity constructors. #264

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 16 additions & 11 deletions ethereum/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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']]
Expand All @@ -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)
Expand Down
27 changes: 22 additions & 5 deletions ethereum/tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)


Expand Down
49 changes: 47 additions & 2 deletions ethereum/tests/test_solidity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from rlp.utils import encode_hex
from ethereum import tester
from ethereum import utils


serpent_contract = """
extern solidity: [sub2:[]:i]

Expand Down Expand Up @@ -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
Expand All @@ -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 {

Expand Down