contract.py (17638B)
1 # standard imports 2 import enum 3 import re 4 import logging 5 6 # external imports 7 from hexathon import ( 8 strip_0x, 9 add_0x, 10 pad, 11 same as same_hex, 12 ) 13 14 # local imports 15 from chainlib.hash import keccak256_string_to_hex 16 from chainlib.block import BlockSpec 17 from chainlib.jsonrpc import JSONRPCRequest 18 from .address import to_checksum_address 19 20 logg = logging.getLogger(__name__) 21 22 23 re_method = r'^[a-zA-Z0-9_]+$' 24 25 class ABIContractType(enum.Enum): 26 """Data types used by ABI encoders 27 """ 28 BYTES32 = 'bytes32' 29 BYTES4 = 'bytes4' 30 UINT256 = 'uint256' 31 UINT128 = 'uint128' 32 UINT64 = 'uint64' 33 UINT32 = 'uint32' 34 UINT16 = 'uint16' 35 UINT8 = 'uint8' 36 ADDRESS = 'address' 37 STRING = 'string' 38 BYTES = 'bytes' 39 BOOLEAN = 'bool' 40 TUPLE = 'tuple' 41 42 dynamic_contract_types = [ 43 ABIContractType.STRING, 44 ABIContractType.BYTES, 45 ] 46 47 pointer_contract_types = [ 48 ABIContractType.TUPLE, 49 ] + dynamic_contract_types 50 51 52 class ABIContract: 53 """Base class for Ethereum smart contract encoder 54 """ 55 def __init__(self): 56 self.types = [] 57 self.contents = [] 58 self.dirty = False 59 60 61 def add_type(self, v): 62 self.types.append(v) 63 self.dirty = True 64 65 66 class ABIMethodEncoder(ABIContract): 67 """Generate ABI method signatures from method signature string. 68 """ 69 def __init__(self): 70 super(ABIMethodEncoder, self).__init__() 71 self.method_name = None 72 self.method_contents = [] 73 74 75 def method(self, m): 76 """Set method name. 77 78 :param m: Method name 79 :type m: str 80 :raises ValueError: Invalid method name 81 """ 82 if re.match(re_method, m) == None: 83 raise ValueError('Invalid method {}, must match regular expression {}'.format(re_method)) 84 self.method_name = m 85 self.__log_method() 86 87 88 def get_method(self): 89 """Return currently set method signature string. 90 91 :rtype: str 92 :returns: Method signature 93 """ 94 contents = '(' + ','.join(self.method_contents) + ')' 95 if self.method_name == None: 96 return contents 97 return self.method_name + contents 98 99 100 def typ(self, v): 101 """Add argument type to argument vector. 102 103 Method name must be set before this is called. 104 105 :param v: Type to add 106 :type v: chainlib.eth.contract.ABIContractType 107 :raises AttributeError: Type set before method name 108 :raises TypeError: Invalid type 109 """ 110 if isinstance(v, ABIContractEncoder): 111 return self.typ_tuple(v) 112 if not isinstance(v, ABIContractType): 113 raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__)) 114 self.method_contents.append(v.value) 115 self.__log_method() 116 117 118 def typ_literal(self, v): 119 if type(v).__name__ != 'str': 120 raise ValueError('literal type must be string') 121 self.method_contents.append(v) 122 self.__log_method() 123 124 125 def typ_tuple(self, v): 126 if not isinstance(v, ABIContractEncoder): 127 raise TypeError('tuple type not valid; expected {}, got {}'.format(type(ABIContractEncoder).__name__, type(v).__name__)) 128 r = v.get_method() 129 self.method_contents.append(r) 130 self.__log_method() 131 132 133 def __log_method(self): 134 logg.debug('method set to {}'.format(self.get_method())) 135 136 137 def get_signature(self): 138 """Generate topic signature from set topic. 139 140 :rtype: str 141 :returns: Topic signature, in hex 142 """ 143 if self.method_name == None: 144 return '' 145 s = self.get_method() 146 return keccak256_string_to_hex(s) 147 148 149 def get_method_signature(self): 150 s = self.get_signature() 151 if s == '': 152 return s 153 return s[:8] 154 155 156 157 class ABIContractDecoder(ABIContract): 158 """Decode serialized ABI contract input data to corresponding python primitives. 159 """ 160 161 def typ(self, v): 162 """Add type to argument array to parse input against. 163 164 :param v: Type 165 :type v: chainlib.eth.contract.ABIContractType 166 :raises TypeError: Invalid type 167 """ 168 if not isinstance(v, ABIContractType): 169 raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__)) 170 if v == ABIContractType.TUPLE: 171 raise NotImplementedError('sorry, tuple decoding not yet implemented') 172 self.add_type(v.value) 173 self.__log_typ() 174 175 176 def val(self, v): 177 """Add value to value array. 178 179 :param v: Value, in hex 180 :type v: str 181 """ 182 self.contents.append(v) 183 logg.debug('content is now {}'.format(self.contents)) 184 185 186 def uint256(self, v): 187 """Parse value as uint256. 188 189 :param v: Value, in hex 190 :type v: str 191 :rtype: int 192 :returns: Int value 193 """ 194 return int(v, 16) 195 196 197 def uintn(self, v, bitsize): 198 # all uints no matter what size are returned to 256 bit boundary 199 return self.uint256(v) 200 201 l = len(v) * 8 * 2 202 if bitsize % 8 > 0: 203 raise ValueError('must be 8 multiple') 204 elif bitsize > 256: 205 raise ValueError('max 256 bits') 206 elif l < bitsize: 207 raise ValueError('input value length {} shorter than bitsize {}'.format(l, bitsize)) 208 return int(v[:int(bitsize/8)], 16) 209 210 211 def bytes32(self, v): 212 """Parse value as bytes32. 213 214 :param v: Value, in hex 215 :type v: str 216 :rtype: str 217 :returns: Value, in hex 218 """ 219 return v 220 221 222 def bool(self, v): 223 """Parse value as bool. 224 225 :param v: Value, in hex 226 :type v: str 227 :rtype: bool 228 :returns: Value 229 """ 230 return bool(self.uint256(v)) 231 232 233 def boolean(self, v): 234 """Alias of chainlib.eth.contract.ABIContractDecoder.bool 235 """ 236 return bool(self.uint256(v)) 237 238 239 def address(self, v): 240 """Parse value as address. 241 242 :param v: Value, in hex 243 :type v: str 244 :rtype: str 245 :returns: Value. in hex 246 """ 247 a = strip_0x(v)[64-40:] 248 return to_checksum_address(a) 249 250 251 def string(self, v): 252 """Parse value as string. 253 254 :param v: Value, in hex 255 :type v: str 256 :rtype: str 257 :returns: Value 258 """ 259 s = strip_0x(v) 260 b = bytes.fromhex(s) 261 cursor = 0 262 offset = int.from_bytes(b[cursor:cursor+32], 'big') 263 cursor += 32 264 length = int.from_bytes(b[cursor:cursor+32], 'big') 265 cursor += 32 266 content = b[cursor:cursor+length] 267 logg.debug('parsing string offset {} length {} content {}'.format(offset, length, content)) 268 return content.decode('utf-8') 269 270 271 def __log_typ(self): 272 logg.debug('types set to ({})'.format(','.join(self.types))) 273 274 275 def decode(self): 276 """Apply decoder on value array using argument type array. 277 278 :rtype: list 279 :returns: List of decoded values 280 """ 281 r = [] 282 logg.debug('contents {}'.format(self.contents)) 283 for i in range(len(self.types)): 284 m = None 285 try: 286 m = getattr(self, self.types[i]) 287 logg.debug('executing module {}'.format(m)) 288 s = self.contents[i] 289 r.append(m(s)) 290 except AttributeError as e: 291 if len(self.types[i]) > 4 and self.types[i][:4] == 'uint': 292 m = getattr(self, 'uintn') 293 s = self.contents[i] 294 v = m(s, int(self.types[i][4:])) 295 r.append(v) 296 else: 297 raise e 298 299 return r 300 301 302 def get(self): 303 """Alias of chainlib.eth.contract.ABIContractDecoder.decode 304 """ 305 return self.decode() 306 307 308 def __str__(self): 309 return self.decode() 310 311 312 class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder): 313 """Decoder utils for log entries of an Ethereum network transaction receipt. 314 315 Uses chainlib.eth.contract.ABIContractDecoder.decode to render output from template. 316 """ 317 def __init__(self): 318 super(ABIContractLogDecoder, self).__init__() 319 self.method_name = None 320 self.indexed_content = [] 321 322 323 def topic(self, event): 324 """Set topic to match. 325 326 :param event: Topic name 327 :type event: str 328 """ 329 self.method(event) 330 331 def typ(self, v): 332 """Add type to event argument array. 333 334 :param v: Type 335 :type v: chainlib.eth.contract.ABIContractType 336 """ 337 super(ABIContractLogDecoder, self).typ(v) 338 self.add_type(v.value) 339 340 341 342 def apply(self, topics, data): 343 """Set log entry data to parse. 344 345 After set, self.decode can be used to render the output. 346 347 :param topics: The topics array of the receipt, list of hex 348 :type topics: list 349 :param data: Non-indexed data, in hex 350 :type data: str 351 :raises ValueError: Topic of input does not match topic set in parser 352 """ 353 t = self.get_signature() 354 if not same_hex(topics[0], t): 355 raise ValueError('topic mismatch') 356 for i in range(len(topics) - 1): 357 self.contents.append(topics[i+1]) 358 self.contents += data 359 360 361 # Backwards compatibility 362 def get_method_signature(self): 363 logg.warning('ABIContractLogDecoder.get_method_signature() is deprecated. Use ABIContractLogDecoder.get_signature() instead') 364 return self.get_signature() 365 366 367 class ABIContractEncoder(ABIMethodEncoder): 368 369 def __log_latest(self, v): 370 l = len(self.types) - 1 371 logg.debug('Encoder added {} -> {} ({})'.format(v, self.contents[l], self.types[l].value)) 372 373 374 def uint256(self, v): 375 """Encode value to uint256 and add to input value vector. 376 377 :param v: Integer value 378 :type v: int 379 """ 380 v = int(v) 381 b = v.to_bytes(32, 'big') 382 self.contents.append(b.hex()) 383 self.add_type(ABIContractType.UINT256) 384 self.__log_latest(v) 385 386 387 def uintn(self, v, bitsize): 388 """Encode value to uint256 and add to input value vector. 389 390 :param v: Integer value 391 :type v: int 392 """ 393 if bitsize % 8 > 0: 394 raise ValueError('must be 8 multiple') 395 elif bitsize > 256: 396 raise ValueError('max 256 bits') 397 398 # encodings of all uint types are padded to word boundary 399 return self.uint256(v) 400 401 v = int(v) 402 b = v.to_bytes(int(bitsize / 8), 'big') 403 self.contents.append(b.hex()) 404 typ = getattr(ABIContractType, 'UINT' + str(bitsize)) 405 self.add_type(typ) 406 self.__log_latest(v) 407 408 409 def bool(self, v): 410 """Alias of chainlib.eth.contract.ABIContractEncoder.boolean. 411 """ 412 return self.boolean(v) 413 414 415 def boolean(self, v): 416 """Encode value to boolean and add to input value vector. 417 418 :param v: Trueish or falsish value 419 :type v: any 420 :rtype: See chainlib.eth.contract.ABIContractEncoder.uint256 421 :returns: See chainlib.eth.contract.ABIContractEncoder.uint256 422 """ 423 if bool(v): 424 return self.uint256(1) 425 return self.uint256(0) 426 427 428 def address(self, v): 429 """Encode value to address and add to input value vector. 430 431 :param v: Ethereum address, in hex 432 :type v: str 433 """ 434 self.bytes_fixed(32, v, exact=20) 435 self.add_type(ABIContractType.ADDRESS) 436 self.__log_latest(v) 437 438 439 def bytes32(self, v): 440 """Encode value to bytes32 and add to input value vector. 441 442 :param v: Bytes, in hex 443 :type v: str 444 """ 445 self.bytes_fixed(32, v) 446 self.add_type(ABIContractType.BYTES32) 447 self.__log_latest(v) 448 449 450 def bytes4(self, v): 451 """Encode value to bytes4 and add to input value vector. 452 453 :param v: Bytes, in hex 454 :type v: str 455 """ 456 self.bytes_fixed(4, v) 457 self.add_type(ABIContractType.BYTES4) 458 self.__log_latest(v) 459 460 461 def tuple(self, v): 462 if type(v).__name__ != 'ABIContractEncoder': 463 raise ValueError('Type for tuple must be ABIContractEncoder') 464 r = v.get_contents() 465 self.bytes_fixed(int(len(r) / 2), r) 466 self.add_type(ABIContractType.TUPLE) 467 self.__log_latest(v) 468 469 470 def string(self, v): 471 """Encode value to string and add to input value vector. 472 473 :param v: String input 474 :type v: str 475 """ 476 b = v.encode('utf-8') 477 return self._bytes(b, pad=True) 478 479 480 def bytes(self, v): 481 b = bytes.fromhex(v) 482 return self._bytes(b, pad=True) 483 484 485 def _bytes(self, v, pad=False): 486 l = len(v) 487 contents = l.to_bytes(32, 'big') 488 contents += v 489 padlen = 32 - (l % 32) 490 if pad: 491 contents += padlen * b'\x00' 492 self.bytes_fixed(len(contents), contents) 493 self.add_type(ABIContractType.STRING) 494 self.__log_latest(v) 495 return contents 496 497 498 def bytes_fixed(self, mx, v, exact=0, enforce_word=False): 499 """Add arbirary length byte data to value vector. 500 501 :param mx: Max length of input data. 502 :type mx: int 503 :param v: Byte input, hex or bytes 504 :type v: str | bytes 505 :param exact: Fail parsing if input does not translate to given byte length. 506 :type exact: int 507 :raises ValueError: Input length or input format mismatch. 508 """ 509 typ = type(v).__name__ 510 if typ == 'str': 511 v = strip_0x(v) 512 l = len(v) 513 if mx == 0: 514 mx = l 515 if exact > 0 and l != exact * 2: 516 raise ValueError('value wrong size; expected {}, got {})'.format(mx, l)) 517 if enforce_word and mx % 32 > 0: 518 raise ValueError('value size {} does not match word boundary'.format(mx)) 519 if l > mx * 2: 520 raise ValueError('value too long ({})'.format(l)) 521 v = pad(v, mx) 522 elif typ == 'bytes': 523 l = len(v) 524 if mx == 0: 525 mx = l 526 if exact > 0 and l != exact: 527 raise ValueError('value wrong size; expected {}, got {})'.format(mx, l)) 528 if enforce_word and mx % 32 > 0: 529 raise ValueError('value size {} does not match word boundary'.format(mx)) 530 b = bytearray(mx) 531 b[mx-l:] = v 532 v = pad(b.hex(), mx) 533 else: 534 raise ValueError('invalid input {}'.format(typ)) 535 self.contents.append(v.ljust(64, '0')) 536 537 538 def get_contents(self): 539 """Encode value array. 540 541 :rtype: str 542 :returns: ABI encoded values, in hex 543 """ 544 direct_contents = '' 545 pointer_contents = '' 546 l = len(self.types) 547 pointer_cursor = 32 * l 548 for i in range(l): 549 if self.types[i] in pointer_contract_types: 550 content_length = len(self.contents[i]) 551 pointer_contents += self.contents[i] 552 direct_contents += pointer_cursor.to_bytes(32, 'big').hex() 553 pointer_cursor += int(content_length / 2) 554 else: 555 direct_contents += self.contents[i] 556 s = ''.join(direct_contents + pointer_contents) 557 for i in range(0, len(s), 64): 558 l = len(s) - i 559 if l > 64: 560 l = 64 561 logg.debug('code word {} {}'.format(int(i / 64), s[i:i+64])) 562 self.dirty = False 563 return s 564 565 566 def get(self): 567 """Alias of chainlib.eth.contract.ABIContractEncoder.encode 568 """ 569 return self.encode() 570 571 572 def encode(self): 573 """Encode method and value array. 574 575 The data generated by this method is the literal data used as input to contract calls or transactions. 576 577 :rtype: str 578 :returns: ABI encoded contract input data, in hex 579 """ 580 m = self.get_method_signature() 581 c = self.get_contents() 582 return m + c 583 584 585 def __str__(self): 586 return self.encode() 587 588 589 590 def abi_decode_single(typ, v): 591 """Convenience function to decode a single ABI encoded value against a given type. 592 593 :param typ: Type to parse value as 594 :type typ: chainlib.eth.contract.ABIContractEncoder 595 :param v: Value to parse, in hex 596 :type v: str 597 """ 598 d = ABIContractDecoder() 599 d.typ(typ) 600 d.val(v) 601 r = d.decode() 602 return r[0] 603 604 605 def code(address, block_spec=BlockSpec.LATEST, id_generator=None): 606 """Generate json-rpc query to retrieve code stored at an Ethereum address. 607 608 :param address: Address to use for query, in hex 609 :type address: str 610 :param block_spec: Block height spec 611 :type block_spec: chainlib.block.BlockSpec 612 :param id_generator: json-rpc id generator 613 :type id_generator: chainlib.jsonrpc.JSONRPCIdGenerator 614 :rtype: dict 615 :returns: rpc query object 616 """ 617 block_height = None 618 if block_spec == BlockSpec.LATEST: 619 block_height = 'latest' 620 elif block_spec == BlockSpec.PENDING: 621 block_height = 'pending' 622 else: 623 block_height = int(block_spec) 624 block_height = block_height.to_bytes(8, byteorder='big') 625 block_height = add_0x(block_height.hex()) 626 j = JSONRPCRequest(id_generator) 627 o = j.template() 628 o['method'] = 'eth_getCode' 629 o['params'].append(address) 630 o['params'].append(block_height) 631 return j.finalize(o)