|
1 |
| -import sys |
| 1 | +# -*- coding: utf8 -*- |
| 2 | +import ast |
2 | 3 | import re
|
| 4 | +import warnings |
| 5 | + |
3 | 6 | import yaml # use yaml instead of json to get non unicode (works with ascii only data)
|
4 |
| -from ethereum import utils |
5 | 7 | from rlp.utils import decode_hex, encode_hex
|
| 8 | + |
| 9 | +from ethereum import utils |
6 | 10 | from ethereum.utils import encode_int, zpad, big_endian_to_int, is_numeric, is_string, ceil32
|
7 | 11 | from ethereum.utils import isnumeric, TT256, TT255
|
8 |
| -import ast |
9 | 12 |
|
10 | 13 |
|
11 |
| -def json_decode(x): |
12 |
| - return yaml.safe_load(x) |
| 14 | +def json_decode(data): |
| 15 | + return yaml.safe_load(data) |
| 16 | + |
| 17 | + |
| 18 | +def split32(data): |
| 19 | + """ Split data into pieces of 32 bytes. """ |
| 20 | + all_pieces = [] |
| 21 | + |
| 22 | + for position in range(0, len(data), 32): |
| 23 | + piece = data[position:position + 32] |
| 24 | + all_pieces.append(piece) |
| 25 | + |
| 26 | + return all_pieces |
| 27 | + |
| 28 | + |
| 29 | +def _canonical_name(name): |
| 30 | + """ Replace aliases to the corresponding type. """ |
13 | 31 |
|
| 32 | + if name.startswith('int['): |
| 33 | + return 'uint256' + name[3:] |
14 | 34 |
|
15 |
| -def _canonical_name(x): |
16 |
| - if x.startswith('int['): |
17 |
| - return 'uint256' + x[3:] |
18 |
| - elif x == 'int': |
| 35 | + if name == 'int': |
19 | 36 | return 'uint256'
|
20 |
| - elif x.startswith('real['): |
21 |
| - return 'real128x128' + x[4:] |
22 |
| - elif x == 'real': |
| 37 | + |
| 38 | + if name.startswith('real['): |
| 39 | + return 'real128x128' + name[4:] |
| 40 | + |
| 41 | + if name == 'real': |
23 | 42 | return 'real128x128'
|
24 |
| - return x |
| 43 | + |
| 44 | + return name |
25 | 45 |
|
26 | 46 |
|
27 | 47 | def method_id(name, encode_types):
|
28 |
| - sig = name + '(' + ','.join(_canonical_name(x) for x in encode_types) + ')' |
29 |
| - return big_endian_to_int(utils.sha3(sig)[:4]) |
| 48 | + """ Return the unique method id. |
| 49 | +
|
| 50 | + The signature is defined as the canonical expression of the basic |
| 51 | + prototype, i.e. the function name with the parenthesised list of parameter |
| 52 | + types. Parameter types are split by a single comma - no spaces are used. |
| 53 | +
|
| 54 | + The method id is defined as the first four bytes (left, high-order in |
| 55 | + big-endian) of the Keccak (SHA-3) hash of the signature of the function. |
| 56 | + """ |
| 57 | + function_types = [ |
| 58 | + _canonical_name(type_) |
| 59 | + for type_ in encode_types |
| 60 | + ] |
| 61 | + |
| 62 | + function_signature = '{function_name}({canonical_types})'.format( |
| 63 | + function_name=name, |
| 64 | + canonical_types=','.join(function_types), |
| 65 | + ) |
| 66 | + |
| 67 | + function_keccak = utils.sha3(function_signature) |
| 68 | + first_bytes = function_keccak[:4] |
| 69 | + |
| 70 | + return big_endian_to_int(first_bytes) |
30 | 71 |
|
31 | 72 |
|
32 | 73 | def event_id(name, encode_types):
|
33 |
| - sig = name + '(' + ','.join(_canonical_name(x) for x in encode_types) + ')' |
34 |
| - return big_endian_to_int(utils.sha3(sig)) |
| 74 | + """ Return the event id. |
| 75 | +
|
| 76 | + Defined as: |
| 77 | +
|
| 78 | + `keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")` |
| 79 | +
|
| 80 | + Where `canonical_type_of` is a function that simply returns the canonical |
| 81 | + type of a given argument, e.g. for uint indexed foo, it would return |
| 82 | + uint256). Note the lack of spaces. |
| 83 | + """ |
| 84 | + |
| 85 | + event_types = [ |
| 86 | + _canonical_name(type_) |
| 87 | + for type_ in encode_types |
| 88 | + ] |
35 | 89 |
|
| 90 | + event_signature = '{event_name}({canonical_types})'.format( |
| 91 | + event_name=name, |
| 92 | + canonical_types=','.join(event_types), |
| 93 | + ) |
36 | 94 |
|
37 |
| -class ContractTranslator(): |
| 95 | + return big_endian_to_int(utils.sha3(event_signature)) |
38 | 96 |
|
39 |
| - def __init__(self, full_signature): |
| 97 | + |
| 98 | +def _normalize_name(name): |
| 99 | + """ Return normalized event/function name. """ |
| 100 | + if '(' in name: |
| 101 | + return name[:name.find('(')] |
| 102 | + |
| 103 | + return name |
| 104 | + |
| 105 | + |
| 106 | +class ContractTranslator(object): |
| 107 | + |
| 108 | + def __init__(self, contract_interface): |
| 109 | + if is_string(contract_interface): |
| 110 | + contract_interface = json_decode(contract_interface) |
| 111 | + |
| 112 | + self.constructor_data = None |
40 | 113 | self.function_data = {}
|
41 | 114 | self.event_data = {}
|
42 |
| - v = vars(self) |
43 |
| - if is_string(full_signature): |
44 |
| - full_signature = json_decode(full_signature) |
45 |
| - for sig_item in full_signature: |
46 |
| - if sig_item['type'] == 'constructor': |
47 |
| - continue |
48 |
| - encode_types = [f['type'] for f in sig_item['inputs']] |
49 |
| - signature = [(f['type'], f['name']) for f in sig_item['inputs']] |
50 |
| - name = sig_item['name'] |
51 |
| - if '(' in name: |
52 |
| - name = name[:name.find('(')] |
53 |
| - if name in v: |
54 |
| - i = 2 |
55 |
| - while name + utils.to_string(i) in v: |
56 |
| - i += 1 |
57 |
| - name += utils.to_string(i) |
58 |
| - sys.stderr.write("Warning: multiple methods with the same " |
59 |
| - " name. Use %s to call %s with types %r" |
60 |
| - % (name, sig_item['name'], encode_types)) |
61 |
| - if sig_item['type'] == 'function': |
62 |
| - decode_types = [f['type'] for f in sig_item['outputs']] |
63 |
| - is_unknown_type = len(sig_item['outputs']) and \ |
64 |
| - sig_item['outputs'][0]['name'] == 'unknown_out' |
65 |
| - self.function_data[name] = { |
66 |
| - "prefix": method_id(name, encode_types), |
67 |
| - "encode_types": encode_types, |
68 |
| - "decode_types": decode_types, |
69 |
| - "is_unknown_type": is_unknown_type, |
70 |
| - "is_constant": sig_item.get('constant', False), |
71 |
| - "signature": signature |
| 115 | + |
| 116 | + for description in contract_interface: |
| 117 | + encode_types = [ |
| 118 | + element['type'] |
| 119 | + for element in description['inputs'] |
| 120 | + ] |
| 121 | + |
| 122 | + signature = [ |
| 123 | + (element['type'], element['name']) |
| 124 | + for element in description['inputs'] |
| 125 | + ] |
| 126 | + |
| 127 | + # type can be omitted, defaulting to function |
| 128 | + if description.get('type', 'function') == 'function': |
| 129 | + normalized_name = _normalize_name(description['name']) |
| 130 | + |
| 131 | + decode_types = [ |
| 132 | + element['type'] |
| 133 | + for element in description['outputs'] |
| 134 | + ] |
| 135 | + |
| 136 | + self.function_data[normalized_name] = { |
| 137 | + 'prefix': method_id(normalized_name, encode_types), |
| 138 | + 'encode_types': encode_types, |
| 139 | + 'decode_types': decode_types, |
| 140 | + 'is_constant': description.get('constant', False), |
| 141 | + 'signature': signature, |
72 | 142 | }
|
73 |
| - elif sig_item['type'] == 'event': |
74 |
| - indexed = [f['indexed'] for f in sig_item['inputs']] |
75 |
| - names = [f['name'] for f in sig_item['inputs']] |
76 |
| - self.event_data[event_id(name, encode_types)] = { |
77 |
| - "types": encode_types, |
78 |
| - "name": name, |
79 |
| - "names": names, |
80 |
| - "indexed": indexed, |
81 |
| - "anonymous": sig_item.get('anonymous', False) |
| 143 | + |
| 144 | + elif description['type'] == 'event': |
| 145 | + normalized_name = _normalize_name(description['name']) |
| 146 | + |
| 147 | + indexed = [ |
| 148 | + element['indexed'] |
| 149 | + for element in description['inputs'] |
| 150 | + ] |
| 151 | + names = [ |
| 152 | + element['name'] |
| 153 | + for element in description['inputs'] |
| 154 | + ] |
| 155 | + self.event_data[event_id(normalized_name, encode_types)] = { |
| 156 | + 'types': encode_types, |
| 157 | + 'name': normalized_name, |
| 158 | + 'names': names, |
| 159 | + 'indexed': indexed, |
| 160 | + 'anonymous': description.get('anonymous', False), |
82 | 161 | }
|
83 | 162 |
|
84 |
| - def encode(self, name, args): |
85 |
| - fdata = self.function_data[name] |
86 |
| - o = zpad(encode_int(fdata['prefix']), 4) + \ |
87 |
| - encode_abi(fdata['encode_types'], args) |
88 |
| - return o |
| 163 | + elif description['type'] == 'constructor': |
| 164 | + if self.constructor_data is not None: |
| 165 | + raise ValueError('Only one constructor is supported.') |
89 | 166 |
|
90 |
| - def decode(self, name, data): |
91 |
| - # print 'out', utils.encode_hex(data) |
92 |
| - fdata = self.function_data[name] |
93 |
| - if fdata['is_unknown_type']: |
94 |
| - o = [utils.to_signed(utils.big_endian_to_int(data[i:i + 32])) |
95 |
| - for i in range(0, len(data), 32)] |
96 |
| - return [0 if not o else o[0] if len(o) == 1 else o] |
97 |
| - o = decode_abi(fdata['decode_types'], data) |
98 |
| - return o |
| 167 | + self.constructor_data = { |
| 168 | + 'encode_types': encode_types, |
| 169 | + 'signature': signature, |
| 170 | + } |
| 171 | + |
| 172 | + else: |
| 173 | + raise ValueError('Unknown type {}'.format(description['type'])) |
| 174 | + |
| 175 | + def encode_function_call(self, function_name, args): |
| 176 | + """ Return the encoded function call. |
| 177 | +
|
| 178 | + Args: |
| 179 | + function_name (str): One of the existing functions described in the |
| 180 | + contract interface. |
| 181 | + args (List[object]): The function arguments that wll be encoded and |
| 182 | + used in the contract execution in the vm. |
| 183 | +
|
| 184 | + Return: |
| 185 | + bin: The encoded function name and arguments so that it can be used |
| 186 | + with the evm to execute a funcion call, the binary string follows |
| 187 | + the Ethereum Contract ABI. |
| 188 | + """ |
| 189 | + if function_name not in self.function_data: |
| 190 | + raise ValueError('Unkown function {}'.format(function_name)) |
| 191 | + |
| 192 | + description = self.function_data[function_name] |
| 193 | + |
| 194 | + function_selector = zpad(encode_int(description['prefix']), 4) |
| 195 | + arguments = encode_abi(description['encode_types'], args) |
99 | 196 |
|
100 |
| - def is_unknown_type(self, name): |
101 |
| - return self.function_data[name]["is_unknown_type"] |
| 197 | + return function_selector + arguments |
| 198 | + |
| 199 | + def encode(self, function_name, args): |
| 200 | + warnings.warn('encode is deprecated, please use encode_function_call', DeprecationWarning) |
| 201 | + return self.encode_function_call(function_name, args) |
| 202 | + |
| 203 | + def encode_constructor_arguments(self, args): |
| 204 | + """ Return the encoded constructor call. """ |
| 205 | + if self.constructor_data is None: |
| 206 | + raise ValueError("The contract interface didn't have a constructor") |
| 207 | + |
| 208 | + return encode_abi(self.constructor_data['encode_types'], args) |
| 209 | + |
| 210 | + def decode(self, function_name, data): |
| 211 | + description = self.function_data[function_name] |
| 212 | + return decode_abi(description['decode_types'], data) |
102 | 213 |
|
103 | 214 | def listen(self, log, noprint=True):
|
104 | 215 | if not len(log.topics) or log.topics[0] not in self.event_data:
|
@@ -130,13 +241,6 @@ def listen(self, log, noprint=True):
|
130 | 241 | return o
|
131 | 242 |
|
132 | 243 |
|
133 |
| -def split32(s): |
134 |
| - o = [] |
135 |
| - for i in range(0, len(s), 32): |
136 |
| - o.append(s[i:i + 32]) |
137 |
| - return o |
138 |
| - |
139 |
| - |
140 | 244 | class EncodingError(Exception):
|
141 | 245 | pass
|
142 | 246 |
|
|
0 commit comments