def to_representation(self, obj): if not obj: return '0x' # We can get another types like `memoryview` from django models. `to_internal_value` is not used # when you provide an object instead of a json using `data`. Make sure everything is HexBytes. if hasattr(obj, 'hex'): obj = HexBytes(obj.hex()) elif not isinstance(obj, HexBytes): obj = HexBytes(obj) return obj.hex()
def transaction(request): if request.method == "GET": return render(request, 'transaction.html') else: dict_keystore = request.session["dict_keystore"] from_address = request.session['from_address'] to_address = request.POST.get("to_address") if dict_keystore and from_address and to_address: to_address = Web3.toChecksumAddress(to_address) # assert Web3.isAddress(to_address),"invalid address" value = Web3.toWei(float(request.POST.get("value")), 'ether') #print("value",value) gas_limit = int(request.POST.get("gas")) print("gaslimit", gas_limit) gas_price = int(request.POST.get("gas_price")) print("gaslimit", gas_price) password = request.POST.get("password") #print(">>>>>>>>>pw",password) privatekey = HexBytes( account.Account.decrypt( dict_keystore, password)) ##convert the binary private key to hex type pk = privatekey.hex() #print(">>>>>>>>>pk",pk) txhash_b = send_transaction(from_address, to_address, value, gas_price, gas_limit, pk) txhash = HexBytes(txhash_b).hex() data = {} data["txn"] = txhash print(txhash) return render(request, 'outcome.html', context=data) else: return redirect(reverse("balance"))
def get_order_by_hash(self, order_hash: hexbytes.HexBytes) -> Optional[Order]: # remove 0x to keep backwards compatibility order_orm = self.session.query(OrderORM).get( remove_0x_prefix(HexStr(order_hash.hex()))) if order_orm is None: return None return order_orm.to_order()
def send_same_trans(self, sender_addr, sender_priv, receive_addr): """ Check network for sending identical transactions :param receive_addr: receive eth-address :param sender_priv: sender eth-private key :param sender_addr: sender eth-address :return: None """ sender_addr = self.w3.toChecksumAddress(sender_addr) receive_addr = self.w3.toChecksumAddress(receive_addr) while True: try: params = dict( nonce=self.w3.eth.getTransactionCount(sender_addr), gasPrice=self.w3.eth.gasPrice, gas=100000, to=receive_addr, value=hex(10000000000000000)) params2 = params.copy() params2["value"] = hex(1) params2["gasPrice"] += 10000000000000000000 signed_txn = self.w3.eth.account.signTransaction( params, sender_priv) _signed_txn = self.w3.eth.account.signTransaction( params2, sender_priv) trans1 = self.w3.eth.sendRawTransaction( signed_txn.rawTransaction) trans2 = self.w3.eth.sendRawTransaction( _signed_txn.rawTransaction) trans1_hash = HexBytes(trans1) trans2_hash = HexBytes(trans2) self.logger.info("\tSend same transactions:\n" "\t\t\t\t\t\t\t\t\t\t\t{0}\n" "\t\t\t\t\t\t\t\t\t\t\t{1}".format( trans1_hash.hex(), trans2_hash.hex())) return None except ValueError as e: self.logger.warning("\t{0}".format(e.args[0]["message"])) time.sleep(2) continue except Exception as e: raise e
def sign_bytearray(w3, barray, account_adr): # Returns hex strings like '0x3532..' h = HexBytes(barray) h_hash = w3.sha3(hexstr = h.hex()) sig = w3.eth.sign(account_adr, h_hash) # sig is HexBytes r = w3.toBytes(hexstr = HexBytes(sig[0 : 32]).hex()) s = w3.toBytes(hexstr = HexBytes(sig[32 : 64]).hex()) v = sig[64 : 65] v_int = int.from_bytes(v, byteorder='big') h_hash = w3.toBytes(hexstr = h_hash.hex()) return h_hash, v_int, r, s
def wait_and_check_transaction(self, txhash): """Wait for a transaction to be mined, then check if it succeeded (blocking). :param txhash: Transaction hash to wait on and check :return: Receipt if transaction succeeded """ txhash = HexBytes(txhash) receipt = self.wait_for_transaction(txhash) if not self.check_transaction(txhash): raise TransactionFailedError( 'Transaction {0} failed, check network state'.format( txhash.hex())) return receipt
def check_transaction(self, txhash): """Check that a transaction succeeded. :param txhash: Transaction hash to check :return: True if transaction succeeded, else False """ txhash = HexBytes(txhash) tx = self.w3.eth.getTransaction(txhash) receipt = self.w3.eth.getTransactionReceipt(txhash) logger.info('Receipt for %s: %s', txhash.hex(), dict(receipt)) return receipt is not None and receipt['gasUsed'] < tx[ 'gas'] and receipt['status'] == 1
def test_many_light_trans(self, accounts, time_live): """ Checking the network for resistance to a large number of light transaction requests :param time_live: means how long will the thread run :param accounts: a list or a tuple that includes address and a key from two accounts for transferring Ethereum """ tries = 0 success = 0 start_time = time.time() address1 = self.w3.toChecksumAddress(accounts[0]) key1 = accounts[1] address2 = self.w3.toChecksumAddress(accounts[2]) nonce = self.get_current_nonce(address1) self.logger.info('Flow "{0}" with main account: {1}'.format( threading.current_thread().name, address1)) while True: try: tries += 1 trans_param = dict(nonce=nonce, gasPrice=self.w3.eth.gasPrice, gas=21000, to=address2, value=hex(10000000000000000)) signed_txn = self.w3.eth.account.signTransaction( trans_param, key1) trans = self.w3.eth.sendRawTransaction( signed_txn.rawTransaction) trans_hash = HexBytes(trans) self.logger.info( "\tSend transaction: {0} with nonce: {1}".format( trans_hash.hex(), nonce)) success += 1 except ValueError as e: self.logger.warning("\t{0}".format(e.args[0]["message"])) continue except Exception as e: raise e finally: nonce += 1 if time.time() - start_time >= time_live: self.logger.info( "Percent of successfully sent transactions: {0:.0%}". format(success / tries)) break
def send_eth(self, account1, account2, funds): sender = self.w3.toChecksumAddress(account1[0]) sender_key = account1[1] receiver = self.w3.toChecksumAddress(account2[0]) trans_param = dict(nonce=self.w3.eth.getTransactionCount(sender), gasPrice=self.w3.eth.gasPrice, gas=21000, to=receiver, value=hex(funds)) signed_txn = self.w3.eth.account.signTransaction( trans_param, sender_key) trans = self.w3.eth.sendRawTransaction(signed_txn.rawTransaction) trans_hash = HexBytes(trans) self.logger.info("\tSend transaction: {0}".format(trans_hash.hex()))
def order_cancelled( self, order_hash: hexbytes.HexBytes, cancelled_maker_token_amount: int, cancelled_taker_token_amount: int, ) -> None: order_orm = ( self.session.query(OrderORM) # remove 0x to keep backwards compatibility .filter_by(msg_hash=remove_0x_prefix(order_hash.hex())).first() ) if order_orm is not None: order_orm.cancelled_maker_token_amount += cancelled_maker_token_amount order_orm.cancelled_taker_token_amount += cancelled_taker_token_amount if order_orm.to_order().is_filled(): self.session.delete(order_orm) self.session.commit()
def registered(): """ Calling a contract function and interact with it using the data from the input provided previously. """ call_contract_function = ASSETREGISTER.functions.setRegistration( request.form['some_string'], w3.eth.accounts[int(request.form['ethereum_address'])]).transact( ) # create the transaction transaction_info = w3.eth.getTransaction(call_contract_function) return render_template( 'registered.html', # pass these variables to the html template reg_ethaddress=w3.eth.accounts[int(request.form['ethereum_address'])], reg_serial=request.form['some_string'], reg_accountnumber=request.form['ethereum_address'], reg_receipt=w3.eth.getTransactionReceipt(call_contract_function), reg_txhash=HexBytes.hex(transaction_info['hash']), reg_txdata=HexBytes(transaction_info['input']), contractaddress=ASSETREGISTER.address)
def biggest_tx(transaction_hashes): tx_values = [] #query_times = [] for tx_hash in transaction_hashes: #start = time.time() tx = web3.eth.getTransaction(tx_hash) #end = time.time() tx_values.append(tx["value"]) #query_times.append(end-start) highest_value = max(tx_values) index_of_largest = tx_values.index(max(tx_values)) largest_tx_hash = HexBytes.hex(transaction_hashes[index_of_largest]) #average_q_time = statistics.mean(query_times) #print(average_q_time) return highest_value, largest_tx_hash
def test_json_encoder(self): base_address = 'b58d5491d17ebf46e9db7f18cea7c556ae80d53B' ipfs_hash_string = 'Qme4GBhwNJharbu83iNEsd5WnUhQYM1rBAgCgsSuFMdjcS' ipfs_hash_bytes = ipfs_hash_string.encode() # b'...' json = {'ipfs_hash': ipfs_hash_bytes} # Simulate string encoding and convert back to dict encoded_json = loads(dumps(json, cls=JsonBytesEncoder)) self.assertEqual(ipfs_hash_bytes.decode(), encoded_json['ipfs_hash']) json = {'ipfs_hash': ipfs_hash_string} # Simulate string encoding and convert back to dict encoded_json = loads(dumps(json, cls=JsonBytesEncoder)) self.assertEqual(ipfs_hash_string, encoded_json['ipfs_hash']) hex_bytes_address = HexBytes(base_address) json = {'address': hex_bytes_address} encoded_json = loads(dumps(json, cls=JsonBytesEncoder)) self.assertEqual(hex_bytes_address.hex(), encoded_json['address']) bytes_address = bytes.fromhex(base_address) json = {'address': bytes_address} encoded_json = loads(dumps(json, cls=JsonBytesEncoder)) self.assertEqual('0x' + bytes_address.hex(), encoded_json['address'])
def split_hex_32bytes(hex_obj: HexBytes) -> list: hex_str = str(hex_obj.hex()) return [hex_str[i:i + 64] for i in range(0, len(hex_str), 64)]
def test_safe_multisig_tx_serializer(self): safe = get_eth_address_with_key()[0] to = None value = int(10e18) tx_data = None operation = 0 safe_tx_gas = 1 data_gas = 1 gas_price = 1 gas_token = None refund_receiver = None nonce = 0 data = { "safe": safe, "to": to, "value": value, # 1 ether "data": tx_data, "operation": operation, "safe_tx_gas": safe_tx_gas, "data_gas": data_gas, "gas_price": gas_price, "gas_token": gas_token, "nonce": nonce, "signatures": [ { 'r': 5, 's': 7, 'v': 27 }, { 'r': 17, 's': 29, 'v': 28 }]} serializer = SafeRelayMultisigTxSerializer(data=data) self.assertFalse(serializer.is_valid()) # Less signatures than threshold # Signatures must be sorted! accounts = [Account.create() for _ in range(2)] accounts.sort(key=lambda x: x.address.lower()) safe = get_eth_address_with_key()[0] data['safe'] = safe serializer = SafeRelayMultisigTxSerializer(data=data) self.assertFalse(serializer.is_valid()) # To and data cannot both be null tx_data = HexBytes('0xabcd') data['data'] = tx_data.hex() serializer = SafeRelayMultisigTxSerializer(data=data) self.assertFalse(serializer.is_valid()) # Operation is not create, but no to provided # Now we fix the signatures to = accounts[-1].address data['to'] = to multisig_tx_hash = SafeTx( None, safe, to, value, tx_data, operation, safe_tx_gas, data_gas, gas_price, gas_token, refund_receiver, safe_nonce=nonce ).safe_tx_hash signatures = [account.signHash(multisig_tx_hash) for account in accounts] data['signatures'] = [{'v': s.v, 'r': s.r, 's': s.s} for s in signatures] serializer = SafeRelayMultisigTxSerializer(data=data) self.assertTrue(serializer.is_valid()) data = { "safe": safe, "to": to, "value": value, # 1 ether "data": tx_data, "operation": operation, "safe_tx_gas": safe_tx_gas, "data_gas": data_gas, "gas_price": gas_price, "gas_token": gas_token, "nonce": nonce, "refund_receiver": accounts[0].address, # Refund receiver must be empty or relay service sender "signatures": [ { 'r': 5, 's': 7, 'v': 27 }] } serializer = SafeRelayMultisigTxSerializer(data=data) self.assertFalse(serializer.is_valid()) data['refund_receiver'] = Account.from_key(settings.SAFE_TX_SENDER_PRIVATE_KEY).address serializer = SafeRelayMultisigTxSerializer(data=data) self.assertTrue(serializer.is_valid()) data['refund_receiver'] = NULL_ADDRESS serializer = SafeRelayMultisigTxSerializer(data=data) self.assertTrue(serializer.is_valid())
class SafeTx: tx: TxParams # If executed, `tx` is set tx_hash: bytes # If executed, `tx_hash` is set def __init__( self, ethereum_client: EthereumClient, safe_address: str, to: Optional[str], value: int, data: bytes, operation: int, safe_tx_gas: int, base_gas: int, gas_price: int, gas_token: Optional[str], refund_receiver: Optional[str], signatures: Optional[bytes] = None, safe_nonce: Optional[int] = None, safe_version: str = None, chain_id: Optional[int] = None, ): """ :param ethereum_client: :param safe_address: :param to: :param value: :param data: :param operation: :param safe_tx_gas: :param base_gas: :param gas_price: :param gas_token: :param refund_receiver: :param signatures: :param safe_nonce: Current nonce of the Safe. If not provided, it will be retrieved from network :param safe_version: Safe version 1.0.0 renamed `baseGas` to `dataGas`. Safe version 1.3.0 added `chainId` to the `domainSeparator`. If not provided, it will be retrieved from network :param chain_id: Ethereum network chain_id is used in hash calculation for Safes >= 1.3.0. If not provided, it will be retrieved from the provided ethereum_client """ self.ethereum_client = ethereum_client self.safe_address = safe_address self.to = to or NULL_ADDRESS self.value = value self.data = HexBytes(data) if data else b"" self.operation = operation self.safe_tx_gas = safe_tx_gas self.base_gas = base_gas self.gas_price = gas_price self.gas_token = gas_token or NULL_ADDRESS self.refund_receiver = refund_receiver or NULL_ADDRESS self.signatures = signatures or b"" self._safe_nonce = safe_nonce self._safe_version = safe_version self._chain_id = chain_id def __str__(self): return ( f"SafeTx - safe={self.safe_address} - to={self.to} - value={self.value} - data={self.data.hex()} - " f"operation={self.operation} - safe-tx-gas={self.safe_tx_gas} - base-gas={self.base_gas} - " f"gas-price={self.gas_price} - gas-token={self.gas_token} - refund-receiver={self.refund_receiver} - " f"signers = {self.signers}") @property def w3(self): return self.ethereum_client.w3 @cached_property def contract(self): return get_safe_contract(self.w3, address=self.safe_address) @cached_property def chain_id(self) -> int: if self._chain_id is not None: return self._chain_id else: return self.w3.eth.chain_id @cached_property def safe_nonce(self) -> str: if self._safe_nonce is not None: return self._safe_nonce else: return self.contract.functions.nonce().call() @cached_property def safe_version(self) -> str: if self._safe_version is not None: return self._safe_version else: return self.contract.functions.VERSION().call() @property def _eip712_payload(self) -> StructTuple: data = self.data.hex() if self.data else "" safe_version = Version(self.safe_version) cls = EIP712SafeTx if safe_version >= Version( "1.0.0") else EIP712LegacySafeTx message = cls( to=self.to, value=self.value, data=data, operation=self.operation, safeTxGas=self.safe_tx_gas, baseGas=self.base_gas, dataGas=self.base_gas, gasPrice=self.gas_price, gasToken=self.gas_token, refundReceiver=self.refund_receiver, nonce=self.safe_nonce, ) domain = make_domain( verifyingContract=self.safe_address, chainId=self.chain_id if safe_version >= Version("1.3.0") else None, ) return StructTuple(message, domain) @property def eip712_structured_data(self) -> Dict: message, domain = self._eip712_payload return message.to_message(domain) @property def safe_tx_hash(self) -> HexBytes: message, domain = self._eip712_payload signable_bytes = message.signable_bytes(domain) return HexBytes(Web3.keccak(signable_bytes)) @property def signers(self) -> List[str]: if not self.signatures: return [] else: return [ safe_signature.owner for safe_signature in SafeSignature.parse_signature( self.signatures, self.safe_tx_hash) ] @property def sorted_signers(self): return sorted(self.signers, key=lambda x: int(x, 16)) @property def w3_tx(self): """ :return: Web3 contract tx prepared for `call`, `transact` or `buildTransaction` """ return self.contract.functions.execTransaction( self.to, self.value, self.data, self.operation, self.safe_tx_gas, self.base_gas, self.gas_price, self.gas_token, self.refund_receiver, self.signatures, ) def _raise_safe_vm_exception(self, message: str) -> NoReturn: error_with_exception: Dict[str, Type[InvalidMultisigTx]] = { # https://github.com/gnosis/safe-contracts/blob/v1.3.0/docs/error_codes.md "GS000": CouldNotFinishInitialization, "GS001": ThresholdNeedsToBeDefined, "Could not pay gas costs with ether": CouldNotPayGasWithEther, "GS011": CouldNotPayGasWithEther, "Could not pay gas costs with token": CouldNotPayGasWithToken, "GS012": CouldNotPayGasWithToken, "GS013": SafeTransactionFailedWhenGasPriceAndSafeTxGasEmpty, "Hash has not been approved": HashHasNotBeenApproved, "Hash not approved": HashHasNotBeenApproved, "GS025": HashHasNotBeenApproved, "Invalid contract signature location: data not complete": InvalidContractSignatureLocation, "GS023": InvalidContractSignatureLocation, "Invalid contract signature location: inside static part": InvalidContractSignatureLocation, "GS021": InvalidContractSignatureLocation, "Invalid contract signature location: length not present": InvalidContractSignatureLocation, "GS022": InvalidContractSignatureLocation, "Invalid contract signature provided": InvalidContractSignatureLocation, "GS024": InvalidContractSignatureLocation, "Invalid owner provided": InvalidOwnerProvided, "Invalid owner address provided": InvalidOwnerProvided, "GS026": InvalidOwnerProvided, "Invalid signatures provided": InvalidSignaturesProvided, "Not enough gas to execute safe transaction": NotEnoughSafeTransactionGas, "GS010": NotEnoughSafeTransactionGas, "Only owners can approve a hash": OnlyOwnersCanApproveAHash, "GS030": OnlyOwnersCanApproveAHash, "GS031": MethodCanOnlyBeCalledFromThisContract, "Signature not provided by owner": SignatureNotProvidedByOwner, "Signatures data too short": SignaturesDataTooShort, "GS020": SignaturesDataTooShort, # ModuleManager "GS100": ModuleManagerException, "Invalid module address provided": ModuleManagerException, "GS101": ModuleManagerException, "GS102": ModuleManagerException, "Invalid prevModule, module pair provided": ModuleManagerException, "GS103": ModuleManagerException, "Method can only be called from an enabled module": ModuleManagerException, "GS104": ModuleManagerException, "Module has already been added": ModuleManagerException, # OwnerManager "Address is already an owner": OwnerManagerException, "GS200": OwnerManagerException, # Owners have already been setup "GS201": OwnerManagerException, # Threshold cannot exceed owner count "GS202": OwnerManagerException, # Invalid owner address provided "GS203": OwnerManagerException, # Invalid ower address provided "GS204": OwnerManagerException, # Address is already an owner "GS205": OwnerManagerException, # Invalid prevOwner, owner pair provided "Invalid prevOwner, owner pair provided": OwnerManagerException, "New owner count needs to be larger than new threshold": OwnerManagerException, "Threshold cannot exceed owner count": OwnerManagerException, "Threshold needs to be greater than 0": OwnerManagerException, } for reason, custom_exception in error_with_exception.items(): if reason in message: raise custom_exception(message) raise InvalidMultisigTx(message) def call( self, tx_sender_address: Optional[str] = None, tx_gas: Optional[int] = None, block_identifier: Optional[BlockIdentifier] = "latest", ) -> int: """ :param tx_sender_address: :param tx_gas: Force a gas limit :param block_identifier: :return: `1` if everything ok """ parameters: Dict[str, Any] = { "from": tx_sender_address if tx_sender_address else self.safe_address } if tx_gas: parameters["gas"] = tx_gas try: success = self.w3_tx.call(parameters, block_identifier=block_identifier) if not success: raise InvalidInternalTx( "Success bit is %d, should be equal to 1" % success) return success except (ContractLogicError, BadFunctionCallOutput, ValueError) as exc: # e.g. web3.exceptions.ContractLogicError: execution reverted: Invalid owner provided return self._raise_safe_vm_exception(str(exc)) except ValueError as exc: # Parity """ Parity throws a ValueError, e.g. {'code': -32015, 'message': 'VM execution error.', 'data': 'Reverted 0x08c379a0000000000000000000000000000000000000000000000000000000000000020000000000000000 000000000000000000000000000000000000000000000001b496e76616c6964207369676e6174757265732070726f7669 6465640000000000' } """ error_dict = exc.args[0] data = error_dict.get("data") if data and isinstance(data, str) and "Reverted " in data: # Parity result = HexBytes(data.replace("Reverted ", "")) return self._raise_safe_vm_exception(str(result)) else: raise exc def recommended_gas(self) -> Wei: """ :return: Recommended gas to use on the ethereum_tx """ return Wei(self.base_gas + self.safe_tx_gas + 75000) def execute( self, tx_sender_private_key: str, tx_gas: Optional[int] = None, tx_gas_price: Optional[int] = None, tx_nonce: Optional[int] = None, block_identifier: Optional[BlockIdentifier] = "latest", eip1559_speed: Optional[TxSpeed] = None, ) -> Tuple[HexBytes, TxParams]: """ Send multisig tx to the Safe :param tx_sender_private_key: Sender private key :param tx_gas: Gas for the external tx. If not, `(safe_tx_gas + base_gas) * 2` will be used :param tx_gas_price: Gas price of the external tx. If not, `gas_price` will be used :param tx_nonce: Force nonce for `tx_sender` :param block_identifier: `latest` or `pending` :param eip1559_speed: If provided, use EIP1559 transaction :return: Tuple(tx_hash, tx) :raises: InvalidMultisigTx: If user tx cannot go through the Safe """ sender_account = Account.from_key(tx_sender_private_key) if eip1559_speed and self.ethereum_client.is_eip1559_supported(): tx_parameters = self.ethereum_client.set_eip1559_fees( { "from": sender_account.address, }, tx_speed=eip1559_speed, ) else: tx_parameters = { "from": sender_account.address, "gasPrice": tx_gas_price or self.w3.eth.gas_price, } if tx_gas: tx_parameters["gas"] = tx_gas if tx_nonce is not None: tx_parameters["nonce"] = tx_nonce self.tx = self.w3_tx.buildTransaction(tx_parameters) self.tx["gas"] = Wei( tx_gas or (max(self.tx["gas"] + 75000, self.recommended_gas()))) self.tx_hash = self.ethereum_client.send_unsigned_transaction( self.tx, private_key=sender_account.key, retry=False if tx_nonce is not None else True, block_identifier=block_identifier, ) # Set signatures empty after executing the tx. `Nonce` is increased even if it fails, # so signatures are not valid anymore self.signatures = b"" return self.tx_hash, self.tx def sign(self, private_key: str) -> bytes: """ {bytes32 r}{bytes32 s}{uint8 v} :param private_key: :return: Signature """ account = Account.from_key(private_key) signature_dict = account.signHash(self.safe_tx_hash) signature = signature_to_bytes(signature_dict["v"], signature_dict["r"], signature_dict["s"]) # Insert signature sorted if account.address not in self.signers: new_owners = self.signers + [account.address] new_owner_pos = sorted(new_owners, key=lambda x: int(x, 16)).index( account.address) self.signatures = (self.signatures[:65 * new_owner_pos] + signature + self.signatures[65 * new_owner_pos:]) return signature def unsign(self, address: str) -> bool: for pos, signer in enumerate(self.signers): if signer == address: self.signatures = self.signatures.replace( self.signatures[pos * 65:pos * 65 + 65], b"") return True return False
def test_decode_multisend(self): # Change Safe contract master copy and set fallback manager multisend transaction safe_contract_address = "0x5B9ea52Aaa931D4EEf74C8aEaf0Fe759434FeD74" value = "0" operation = MultiSendOperation.CALL.value data = HexBytes( "0x8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000" "00000000000000000000000000000000000000000000f2005b9ea52aaa931d4eef74c8aeaf0fe759434fed740000" "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e9422" "7e3583fe3f5f005b9ea52aaa931d4eef74c8aeaf0fe759434fed7400000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f0" "8a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf440000000000000000000000" "000000") change_master_copy_data = HexBytes( "0x7de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f" ) change_fallback_manager_data = HexBytes( "0xf08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cd" "bdf44") tx_decoder = get_tx_decoder() expected = [ { "operation": operation, "to": safe_contract_address, "value": value, "data": change_master_copy_data.hex(), "data_decoded": { "method": "changeMasterCopy", "parameters": [{ "name": "_masterCopy", "type": "address", "value": "0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F", }], }, }, { "operation": operation, "to": safe_contract_address, "value": value, "data": change_fallback_manager_data.hex(), "data_decoded": { "method": "setFallbackHandler", "parameters": [{ "name": "handler", "type": "address", "value": "0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44", }], }, }, ] # Get just the multisend object self.assertEqual(tx_decoder.decode_multisend_data(data), expected) # Now decode all the data expected = ( "multiSend", [{ "name": "transactions", "type": "bytes", "value": "0x005b9ea52aaa931d4eef74c8aeaf0fe759434fed74000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f005b9ea52aaa931d4eef74c8aeaf0fe759434fed7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf44", "value_decoded": [ { "operation": operation, "to": safe_contract_address, "value": value, "data": change_master_copy_data.hex(), "data_decoded": { "method": "changeMasterCopy", "parameters": [{ "name": "_masterCopy", "type": "address", "value": "0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F", }], }, }, { "operation": operation, "to": safe_contract_address, "value": value, "data": change_fallback_manager_data.hex(), "data_decoded": { "method": "setFallbackHandler", "parameters": [{ "name": "handler", "type": "address", "value": "0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44", }], }, }, ], }], ) self.assertEqual(tx_decoder.decode_transaction_with_types(data), expected) # Safe tx decoder cannot decode it. It would be problematic for the internal tx indexer safe_tx_decoder = get_safe_tx_decoder() with self.assertRaises(CannotDecode): safe_tx_decoder.decode_transaction_with_types(data)
class SignedTransaction: raw_transaction: str signature: HexBytes def __init__(self, raw_transaction: str, signature: Union[bytes, str, int]): self.raw_transaction = raw_transaction self.signature = HexBytes(signature) @property def payload(self) -> Dict[str, str]: return { 'raw_transaction': self.raw_transaction, 'signature': self.signature.hex() } def ecrecover(self) -> ChecksumAddress: public_key = self.__recover() self.__validate(public_key) return public_key.to_checksum_address() def __recover(self) -> PublicKey: message_hash = Transaction.hash(self.raw_transaction) return PublicKey.recover_from_msg_hash(message_hash, self.__load_signature()) def __load_signature(self) -> Signature: try: return Signature(signature_bytes=self.signature) except (TypeError, ValidationError, BadSignature): raise exceptions.InvalidSignatureError( f'{self.signature} is not a valid signature') def __validate(self, public_key: PublicKey): transaction_address = json.loads(self.raw_transaction)['from'] recovered_address = public_key.to_checksum_address() if transaction_address != recovered_address: raise exceptions.WrongSignatureError( f'{recovered_address} did not match expected {transaction_address}' ) def transaction(self) -> Transaction: loaded = self.__load_validated_transaction() transaction = self.__import_transaction(loaded) return transaction(**loaded['data']) def __load_validated_transaction(self) -> Dict[str, Any]: loaded = json.loads(self.raw_transaction) jsonschema.validate(loaded, TRANSACTION_SCHEMA) return loaded def __import_transaction( self, loaded_transaction: Dict[str, Any]) -> Type[Transaction]: module_name, class_name = self.__get_transaction_module_name( loaded_transaction) module = self.__import_transaction_module(module_name) return self.__import_transaction_class(module, class_name) @staticmethod def __get_transaction_module_name( loaded_transaction: Dict[str, Any]) -> Tuple[str, str]: parts = loaded_transaction['name'].rsplit(':', 1) # Assuming this was checked by the schema first return parts[0], parts[1] @staticmethod def __import_transaction_module(module_name: str) -> ModuleType: mod_spec = importlib.util.find_spec(module_name) if not mod_spec: raise exceptions.UnsupportedTransactionError( f'Missing {module_name}') return importlib.import_module(mod_spec.name) @staticmethod def __import_transaction_class(module: ModuleType, class_name: str) -> Type[Transaction]: if not hasattr(module, class_name): raise exceptions.UnsupportedTransactionError( f'{module.__name__} has no class {class_name}') transaction = getattr(module, class_name) if not issubclass(transaction, Transaction): raise exceptions.UnsupportedTransactionError( f'{module.__name__}:{class_name} is not a Transaction') return transaction
def _decode(inputs: List, topics: List, data: str) -> List: indexed_count = len([i for i in inputs if i["indexed"]]) if indexed_count and not topics: # special case - if the ABI has indexed values but the log does not, # we should still be able to decode the data unindexed_types = inputs else: if indexed_count < len(topics): raise EventError( "Event log does not contain enough topics for the given ABI - this" " is usually because an event argument is not marked as indexed" ) if indexed_count > len(topics): raise EventError( "Event log contains more topics than expected for the given ABI - this is" " usually because an event argument is incorrectly marked as indexed" ) unindexed_types = [i for i in inputs if not i["indexed"]] # decode the unindexed event data try: unindexed_types = _params(unindexed_types) except (KeyError, TypeError): raise ABIError("Invalid ABI") if unindexed_types and data == "0x": length = len(unindexed_types) * 32 data = f"0x{bytes(length).hex()}" try: decoded = list(decode_abi(unindexed_types, HexBytes(data)))[::-1] except InsufficientDataBytes: raise EventError("Event data has insufficient length") except NonEmptyPaddingBytes: raise EventError("Malformed data field in event log") except OverflowError: raise EventError("Cannot decode event due to overflow error") # decode the indexed event data and create the returned dict topics = topics[::-1] result = [] for i in inputs: result.append({"name": i["name"], "type": i["type"]}) if "components" in i: result[-1]["components"] = i["components"] if topics and i["indexed"]: encoded = HexBytes(topics.pop()) try: value = decode_single(i["type"], encoded) except (InsufficientDataBytes, NoEntriesFound, OverflowError): # an array or other data type that uses multiple slots result[-1].update({"value": encoded.hex(), "decoded": False}) continue else: value = decoded.pop() if isinstance(value, bytes): # converting to `HexBytes` first ensures the leading `0x` value = HexBytes(value).hex() result[-1].update({"value": value, "decoded": True}) return result
class SafeTx: tx: TxParams # If executed, `tx` is set tx_hash: bytes # If executed, `tx_hash` is set def __init__(self, ethereum_client: EthereumClient, safe_address: str, to: str, value: int, data: bytes, operation: int, safe_tx_gas: int, base_gas: int, gas_price: int, gas_token: str, refund_receiver: str, signatures: bytes = b'', safe_nonce: Optional[int] = None, safe_version: str = '1.0.0'): assert isinstance(signatures, bytes), "Signatures must be bytes" self.ethereum_client = ethereum_client self.safe_address = safe_address self.to = to or NULL_ADDRESS self.value = value self.data = HexBytes(data) if data else b'' self.operation = operation self.safe_tx_gas = safe_tx_gas self.base_gas = base_gas self.gas_price = gas_price self.gas_token = gas_token or NULL_ADDRESS self.refund_receiver = refund_receiver or NULL_ADDRESS self.safe_nonce = safe_nonce self.signatures = signatures self.safe_version = safe_version @property def w3(self): return self.ethereum_client.w3 @property def safe_tx_hash(self) -> HexBytes: if self.safe_nonce is None: raise ValueError('`safe_nonce` must be set to calculate hash') data = self.data.hex() if self.data else '' base_gas_name = 'baseGas' if Version( self.safe_version) >= Version('1.0.0') else 'dataGas' structured_data = { 'types': { 'EIP712Domain': [ { 'name': 'verifyingContract', 'type': 'address' }, ], 'SafeTx': [{ 'name': 'to', 'type': 'address' }, { 'name': 'value', 'type': 'uint256' }, { 'name': 'data', 'type': 'bytes' }, { 'name': 'operation', 'type': 'uint8' }, { 'name': 'safeTxGas', 'type': 'uint256' }, { 'name': base_gas_name, 'type': 'uint256' }, { 'name': 'gasPrice', 'type': 'uint256' }, { 'name': 'gasToken', 'type': 'address' }, { 'name': 'refundReceiver', 'type': 'address' }, { 'name': 'nonce', 'type': 'uint256' }] }, 'primaryType': 'SafeTx', 'domain': { 'verifyingContract': self.safe_address, }, 'message': { 'to': self.to, 'value': self.value, 'data': data, 'operation': self.operation, 'safeTxGas': self.safe_tx_gas, base_gas_name: self.base_gas, 'gasPrice': self.gas_price, 'gasToken': self.gas_token, 'refundReceiver': self.refund_receiver, 'nonce': self.safe_nonce, }, } return HexBytes(encode_typed_data(structured_data)) @property def signers(self) -> List[str]: return list([ safe_signature.owner for safe_signature in SafeSignature.parse_signature( self.signatures, self.safe_tx_hash) ]) @property def sorted_signers(self): return sorted(self.signers, key=lambda x: x.lower()) @property def w3_tx(self): """ :return: Web3 contract tx prepared for `call`, `transact` or `buildTransaction` """ safe_contract = get_safe_contract(self.w3, address=self.safe_address) return safe_contract.functions.execTransaction( self.to, self.value, self.data, self.operation, self.safe_tx_gas, self.base_gas, self.gas_price, self.gas_token, self.refund_receiver, self.signatures) def _raise_safe_vm_exception(self, message: str) -> NoReturn: error_with_exception: Dict[str, Type[InvalidMultisigTx]] = { 'Could not pay gas costs with ether': CouldNotPayGasWithEther, 'Could not pay gas costs with token': CouldNotPayGasWithToken, 'Hash has not been approved': HashHasNotBeenApproved, 'Hash not approved': HashHasNotBeenApproved, 'Invalid contract signature location: data not complete': InvalidContractSignatureLocation, 'Invalid contract signature location: inside static part': InvalidContractSignatureLocation, 'Invalid contract signature location: length not present': InvalidContractSignatureLocation, 'Invalid contract signature provided': InvalidContractSignatureLocation, 'Invalid owner provided': InvalidOwnerProvided, 'Invalid owner address provided': InvalidOwnerProvided, 'Invalid signatures provided': InvalidSignaturesProvided, 'Not enough gas to execute safe transaction': NotEnoughSafeTransactionGas, 'Only owners can approve a hash': OnlyOwnersCanApproveAHash, 'Signature not provided by owner': SignatureNotProvidedByOwner, 'Signatures data too short': SignaturesDataTooShort, # OwnerManager 'Address is already an owner': OwnerManagerException, 'Invalid prevOwner, owner pair provided': OwnerManagerException, 'New owner count needs to be larger than new threshold': OwnerManagerException, 'Threshold cannot exceed owner count': OwnerManagerException, 'Threshold needs to be greater than 0': OwnerManagerException, # ModuleManager 'Invalid module address provided': ModuleManagerException, 'Invalid prevModule, module pair provided': ModuleManagerException, 'Method can only be called from an enabled module': ModuleManagerException, 'Module has already been added': ModuleManagerException, } for reason, custom_exception in error_with_exception.items(): if reason in message: raise custom_exception(message) raise InvalidMultisigTx(message) def call(self, tx_sender_address: Optional[str] = None, tx_gas: Optional[int] = None, block_identifier: Optional[BlockIdentifier] = 'latest') -> int: """ :param tx_sender_address: :param tx_gas: Force a gas limit :param block_identifier: :return: `1` if everything ok """ parameters: Dict[str, Any] = { 'from': tx_sender_address if tx_sender_address else self.safe_address } if tx_gas: parameters['gas'] = tx_gas try: success = self.w3_tx.call(parameters, block_identifier=block_identifier) if not success: raise InvalidInternalTx( 'Success bit is %d, should be equal to 1' % success) return success except BadFunctionCallOutput as exc: # Geth return self._raise_safe_vm_exception(str(exc)) except ValueError as exc: # Parity """ Parity throws a ValueError, e.g. {'code': -32015, 'message': 'VM execution error.', 'data': 'Reverted 0x08c379a0000000000000000000000000000000000000000000000000000000000000020000000000000000 000000000000000000000000000000000000000000000001b496e76616c6964207369676e6174757265732070726f7669 6465640000000000' } """ error_dict = exc.args[0] data = error_dict.get('data') if data and isinstance(data, str) and 'Reverted ' in data: # Parity result = HexBytes(data.replace('Reverted ', '')) return self._raise_safe_vm_exception(str(result)) else: raise exc def execute( self, tx_sender_private_key: str, tx_gas: Optional[int] = None, tx_gas_price: Optional[int] = None, tx_nonce: Optional[int] = None, block_identifier: Optional[BlockIdentifier] = 'latest' ) -> Tuple[HexBytes, TxParams]: """ Send multisig tx to the Safe :param tx_sender_private_key: Sender private key :param tx_gas: Gas for the external tx. If not, `(safe_tx_gas + base_gas) * 2` will be used :param tx_gas_price: Gas price of the external tx. If not, `gas_price` will be used :param tx_nonce: Force nonce for `tx_sender` :param block_identifier: `latest` or `pending` :return: Tuple(tx_hash, tx) :raises: InvalidMultisigTx: If user tx cannot go through the Safe """ sender_account = Account.from_key(tx_sender_private_key) tx_gas_price = tx_gas_price or self.gas_price or self.w3.eth.gasPrice tx_parameters = { 'from': sender_account.address, 'gasPrice': tx_gas_price, } if tx_gas: tx_parameters['gas'] = tx_gas if tx_nonce is not None: tx_parameters['nonce'] = tx_nonce self.tx = self.w3_tx.buildTransaction(tx_parameters) self.tx['gas'] = Wei( tx_gas or (max(self.tx['gas'], self.base_gas + self.safe_tx_gas) + 25000)) self.tx_hash = self.ethereum_client.send_unsigned_transaction( self.tx, private_key=sender_account.key, retry=True, block_identifier=block_identifier) # Set signatures empty after executing the tx. `Nonce` is increased even if it fails, # so signatures are not valid anymore self.signatures = b'' return self.tx_hash, self.tx def sign(self, private_key: str) -> bytes: """ {bytes32 r}{bytes32 s}{uint8 v} :param private_key: :return: """ account = Account.from_key(private_key) signature_dict = account.signHash(self.safe_tx_hash) signature = signature_to_bytes( (signature_dict['v'], signature_dict['r'], signature_dict['s'])) # Insert signature sorted if account.address not in self.signers: new_owners = self.signers + [account.address] new_owner_pos = sorted(new_owners, key=lambda x: x.lower()).index( account.address) self.signatures = (self.signatures[:65 * new_owner_pos] + signature + self.signatures[65 * new_owner_pos:]) return signature def unsign(self, address: str) -> bool: for pos, signer in enumerate(self.signers): if signer == address: self.signatures = self.signatures.replace( self.signatures[pos * 65:pos * 65 + 65], b'') return True return False
def random_32bytes(): value = HexBytes(os.urandom(32)) return value.hex()
def query_block(): textbox.delete('1.0',END) # Connecting to the database file #conn = sqlite3.connect(sqlite_file) #c = conn.cursor() n = int(user_entry.get()) #Block block = web3.eth.getBlock(n) #This converts the time from seconds since unix epoch to a date-time timestamp formatted_time = str(time.strftime('%d-%m-%Y %H:%M:%S', time.localtime(block['timestamp']))) all_txs_hash = block["transactions"] if not all_txs_hash: #if the block contained no transactions highest_value_tx_in_block = 0 hash_of_containing_transaction = '-' else: #calls function biggest_tx and passed an array of the tx hashes in the block being queried highest_value_tx_in_block, hash_of_containing_transaction = biggest_tx(all_txs_hash) eth_price = es.get_eth_price() eth_price_float = float(eth_price['ethusd']) dollar_value = str("{:,.2f}".format((eth_price_float*(float(web3.fromWei(highest_value_tx_in_block, 'ether')))))) blocknum=str(block["number"]) timestampyo=str(formatted_time) sizebytes=str(block["size"]) txcount=str(web3.eth.getBlockTransactionCount(n)) nonce=str(block["nonce"]) miner=str(block["miner"]) gasused=str("{:,}".format(block["gasUsed"])) gaslimit=str("{:,}".format(block["gasLimit"])) difficulty=str("{:,.2f}".format(block["difficulty"]*0.000000000001)) combined='' gasused_float = float(block["gasUsed"]) gaslimit_float = float(block["gasLimit"]) gasused_percent_of_limit = (100*(gasused_float/gaslimit_float)) textbox.configure(font='courier 13')# = Text(root, font='courier 18', background='gray90') #inserting the data and making the titles bold textbox.insert('1.0', 'BLOCK #:............................. '+blocknum+'\n\n') textbox.tag_add("BOLD","1.0","1.36") textbox.insert('3.0', 'TIMESTAMP:........................... '+timestampyo+'\n\n') textbox.tag_add("BOLD","3.0","3.36") textbox.insert('5.0', 'SIZE (BYTES):........................ '+sizebytes+'\n\n') textbox.tag_add("BOLD","5.0","5.36") textbox.insert('7.0', 'TX COUNT:............................ '+txcount+'\n\n') textbox.tag_add("BOLD","7.0","7.36") textbox.insert('9.0', 'MINER:............................... '+miner+'\n\n') textbox.tag_add("BOLD","9.0","9.36") textbox.insert('11.0', 'GAS LIMIT:........................... '+gaslimit+'\n\n') textbox.tag_add("BOLD","11.0","11.36") textbox.insert('13.0', 'GAS USED:............................ '+gasused+' ('+str("{:.2f}".format(gasused_percent_of_limit))+'%)'+'\n\n') textbox.tag_add("BOLD","13.0","13.36") textbox.insert('15.0', 'DIFFICULTY (TH):..................... '+difficulty+'\n\n') textbox.tag_add("BOLD","15.0","15.36") textbox.insert('17.0', '------------------------'+'\n\n') textbox.tag_add("BOLD","17.0","17.37") textbox.insert('18.0', 'LARGEST TRANSACTION WITHIN BLOCK:\n\n') textbox.tag_add("BOLD","18.0","18.36") textbox.insert('19.0', 'HASH:.................................'+str(hash_of_containing_transaction)+'\n\n') textbox.tag_add("BOLD","19.0","19.37") textbox.insert('20.0', 'VALUE:................................'+str((web3.fromWei(highest_value_tx_in_block, 'ether')))+' ETH'+ ' ($' +str(dollar_value)+')'+'\n\n') textbox.tag_add("BOLD","20.0","20.37") textbox.insert('21.0', '------------------------'+'\n\n') textbox.tag_add("BOLD","21.0","21.37") textbox.insert('23.0', 'All Transactions in block:'+'\n\n') textbox.tag_add("BOLD","23.0","23.37") for tx in all_txs_hash: formatted = HexBytes.hex(tx) textbox.insert('25.0', formatted+'\n\n') textbox.tag_config("BOLD", font=bold_font_bigger)
if receipt._subcalls == None: receipt._subcalls = [] receipt._subcalls.append( {"from": step["address"], "to": EthAddress(address), "op": step["op"]} ) if step["op"] in ("CALL", "CALLCODE"): receipt._subcalls[-1]["value"] = int(step["stack"][-3], 16) if calldata and last_map[trace[i]["depth"]].get("function"): fn = last_map[trace[i]["depth"]]["function"] receipt._subcalls[-1]["function"] = fn._input_sig try: zip_ = zip(fn.abi["inputs"], fn.decode_input(calldata)) inputs = {i[0]["name"]: i[1] for i in zip_} # type: ignore receipt._subcalls[-1]["inputs"] = inputs except Exception: receipt._subcalls[-1]["calldata"] = calldata.hex() elif calldata: receipt._subcalls[-1]["calldata"] = calldata.hex() # update trace from last_map last = last_map[trace[i]["depth"]] trace[i].update( address=last["address"], fn=last["internal_calls"][-1], jumpDepth=last["jumpDepth"], source=False, ) opcode = trace[i]["op"] if opcode == "CALL" and int(trace[i]["stack"][-3], 16): receipt._add_internal_xfer(
def delete_order_by_hash(self, order_hash: hexbytes.HexBytes) -> None: self.session.query(OrderORM).filter_by( # remove 0x to keep backwards compatibility msg_hash=remove_0x_prefix(order_hash.hex()) ).delete(synchronize_session=False) self.session.commit()
def parse_string(hex_obj: HexBytes) -> str: hex_str = str(hex_obj.hex()) # chunk = web3.toInt(hexstr=hex_str[:64]) len = web3.toInt(hexstr=hex_str[64:128]) data = hex_str[128:128 + len * 2] return web3.toText(hexstr=data)
def _expand_trace(self) -> None: """Adds the following attributes to each step of the stack trace: address: The address executing this contract. contractName: The name of the contract. fn: The name of the function. jumpDepth: Number of jumps made since entering this contract. The initial value is 0. source: { filename: path to the source file for this step offset: Start and end offset associated source code } """ if self._raw_trace is None: self._get_trace() if self._trace is not None: # in case `_get_trace` also expanded the trace, do not repeat return self._trace = trace = self._raw_trace self._new_contracts = [] self._internal_transfers = [] self._subcalls = [] if self.contract_address or not trace: coverage._add_transaction(self.coverage_hash, {}) return if trace[0]["depth"] == 1: self._trace_origin = "geth" self._call_cost = self.gas_used - trace[0]["gas"] + trace[-1]["gas"] for t in trace: t["depth"] = t["depth"] - 1 else: self._trace_origin = "ganache" if trace[0]["gasCost"] >= 21000: # in ganache <6.10.0, gas costs are shifted by one step - we can # identify this when the first step has a gas cost >= 21000 self._call_cost = trace[0]["gasCost"] for i in range(len(trace) - 1): trace[i]["gasCost"] = trace[i + 1]["gasCost"] trace[-1]["gasCost"] = 0 else: self._call_cost = self.gas_used - trace[0]["gas"] + trace[-1][ "gas"] # last_map gives a quick reference of previous values at each depth last_map = { 0: _get_last_map(self.receiver, self.input[:10]) } # type: ignore coverage_eval: Dict = {last_map[0]["name"]: {}} for i in range(len(trace)): # if depth has increased, tx has called into a different contract if trace[i]["depth"] > trace[i - 1]["depth"]: step = trace[i - 1] if step["op"] in ("CREATE", "CREATE2"): # creating a new contract out = next(x for x in trace[i:] if x["depth"] == step["depth"]) address = out["stack"][-1][-40:] sig = f"<{step['op']}>" calldata = None self._new_contracts.append(EthAddress(address)) if int(step["stack"][-1], 16): self._add_internal_xfer(step["address"], address, step["stack"][-1]) else: # calling an existing contract stack_idx = -4 if step["op"] in ("CALL", "CALLCODE") else -3 offset = int(step["stack"][stack_idx], 16) length = int(step["stack"][stack_idx - 1], 16) calldata = HexBytes("".join( step["memory"]))[offset:offset + length] sig = calldata[:4].hex() address = step["stack"][-2][-40:] last_map[trace[i]["depth"]] = _get_last_map(address, sig) coverage_eval.setdefault(last_map[trace[i]["depth"]]["name"], {}) self._subcalls.append({ "from": step["address"], "to": EthAddress(address), "op": step["op"] }) if step["op"] in ("CALL", "CALLCODE"): self._subcalls[-1]["value"] = int(step["stack"][-3], 16) if calldata and last_map[trace[i]["depth"]].get("function"): fn = last_map[trace[i]["depth"]]["function"] zip_ = zip(fn.abi["inputs"], fn.decode_input(calldata)) self._subcalls[-1].update( inputs={i[0]["name"]: i[1] for i in zip_}, # type:ignore function=fn._input_sig, ) elif calldata: self._subcalls[-1]["calldata"] = calldata.hex() # update trace from last_map last = last_map[trace[i]["depth"]] trace[i].update( address=last["address"], contractName=last["name"], fn=last["internal_calls"][-1], jumpDepth=last["jumpDepth"], source=False, ) opcode = trace[i]["op"] if opcode == "CALL" and int(trace[i]["stack"][-3], 16): self._add_internal_xfer(last["address"], trace[i]["stack"][-2][-40:], trace[i]["stack"][-3]) try: pc = last["pc_map"][trace[i]["pc"]] except (KeyError, TypeError): # we don't have enough information about this contract continue if trace[i]["depth"] and opcode in ("RETURN", "REVERT", "INVALID", "SELFDESTRUCT"): subcall: dict = next( i for i in self._subcalls[::-1] if i["to"] == last["address"] # type: ignore ) if opcode == "RETURN": returndata = _get_memory(trace[i], -1) if returndata: fn = last["function"] try: return_values = fn.decode_output(returndata) if len(fn.abi["outputs"]) == 1: return_values = (return_values, ) subcall["return_value"] = return_values except Exception: subcall["returndata"] = returndata.hex() else: subcall["return_value"] = None elif opcode == "SELFDESTRUCT": subcall["selfdestruct"] = True else: if opcode == "REVERT": data = _get_memory(trace[i], -1)[4:] if data: subcall["revert_msg"] = decode_abi(["string"], data)[0] if "revert_msg" not in subcall and "dev" in pc: subcall["revert_msg"] = pc["dev"] if "path" not in pc: continue trace[i]["source"] = { "filename": last["path_map"][pc["path"]], "offset": pc["offset"] } if "fn" not in pc: continue # calculate coverage if last["coverage"]: if pc["path"] not in coverage_eval[last["name"]]: coverage_eval[last["name"]][pc["path"]] = [ set(), set(), set() ] if "statement" in pc: coverage_eval[last["name"]][pc["path"]][0].add( pc["statement"]) if "branch" in pc: if pc["op"] != "JUMPI": last["active_branches"].add(pc["branch"]) elif "active_branches" not in last or pc["branch"] in last[ "active_branches"]: # false, true key = 1 if trace[i + 1]["pc"] == trace[i]["pc"] + 1 else 2 coverage_eval[last["name"]][pc["path"]][key].add( pc["branch"]) if "active_branches" in last: last["active_branches"].remove(pc["branch"]) # ignore jumps with no function - they are compiler optimizations if "jump" in pc: # jump 'i' is calling into an internal function if pc["jump"] == "i": try: fn = last["pc_map"][trace[i + 1]["pc"]]["fn"] except (KeyError, IndexError): continue if fn != last["internal_calls"][-1]: last["internal_calls"].append(fn) last["jumpDepth"] += 1 # jump 'o' is returning from an internal function elif last["jumpDepth"] > 0: del last["internal_calls"][-1] last["jumpDepth"] -= 1 coverage._add_transaction( self.coverage_hash, dict((k, v) for k, v in coverage_eval.items() if v))
def test_decode_multisend(self): # Change Safe contract master copy and set fallback manager multisend transaction safe_contract_address = '0x5B9ea52Aaa931D4EEf74C8aEaf0Fe759434FeD74' value = 0 operation = MultiSendOperation.CALL.value data = HexBytes( '0x8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000' '00000000000000000000000000000000000000000000f2005b9ea52aaa931d4eef74c8aeaf0fe759434fed740000' '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' '000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e9422' '7e3583fe3f5f005b9ea52aaa931d4eef74c8aeaf0fe759434fed7400000000000000000000000000000000000000' '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f0' '8a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf440000000000000000000000' '000000') change_master_copy_data = HexBytes( '0x7de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f' ) change_fallback_manager_data = HexBytes( '0xf08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cd' 'bdf44') tx_decoder = get_tx_decoder() expected = [{ 'operation': operation, 'to': safe_contract_address, 'value': value, 'data': change_master_copy_data.hex(), 'data_decoded': { 'method': 'changeMasterCopy', 'parameters': [{ 'name': '_masterCopy', 'type': 'address', 'value': '0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F' }] } }, { 'operation': operation, 'to': safe_contract_address, 'value': value, 'data': change_fallback_manager_data.hex(), 'data_decoded': { 'method': 'setFallbackHandler', 'parameters': [{ 'name': 'handler', 'type': 'address', 'value': '0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44' }] } }] # Get just the multisend object self.assertEqual(tx_decoder.get_data_decoded_for_multisend(data), expected) # Now decode all the data expected = ('multiSend', [{ 'name': 'transactions', 'type': 'bytes', 'value': '0x005b9ea52aaa931d4eef74c8aeaf0fe759434fed74000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f005b9ea52aaa931d4eef74c8aeaf0fe759434fed7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf44', 'value_decoded': [{ 'operation': operation, 'to': safe_contract_address, 'value': value, 'data': change_master_copy_data.hex(), 'data_decoded': { 'method': 'changeMasterCopy', 'parameters': [{ 'name': '_masterCopy', 'type': 'address', 'value': '0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F' }] } }, { 'operation': operation, 'to': safe_contract_address, 'value': value, 'data': change_fallback_manager_data.hex(), 'data_decoded': { 'method': 'setFallbackHandler', 'parameters': [{ 'name': 'handler', 'type': 'address', 'value': '0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44' }] } }] }]) self.assertEqual(tx_decoder.decode_transaction_with_types(data), expected) # Safe tx decoder cannot decode it. It would be problematic for the internal tx indexer safe_tx_decoder = get_safe_tx_decoder() with self.assertRaises(CannotDecode): safe_tx_decoder.decode_transaction_with_types(data)
def _process_decoded_element( self, decoded_element: EventData ) -> Optional[InternalTx]: safe_address = decoded_element["address"] event_name = decoded_element["event"] # As log log_index = decoded_element["logIndex"] trace_address = str(log_index) args = dict(decoded_element["args"]) ethereum_tx_hash = decoded_element["transactionHash"] ethereum_block = EthereumBlock.objects.values("number", "timestamp").get( txs=ethereum_tx_hash ) internal_tx = InternalTx( ethereum_tx_id=ethereum_tx_hash, timestamp=ethereum_block["timestamp"], block_number=ethereum_block["number"], _from=safe_address, gas=50000, data=b"", to=NULL_ADDRESS, # It should be Master copy address but we cannot detect it value=0, gas_used=50000, contract_address=None, code=None, output=None, refund_address=None, tx_type=InternalTxType.CALL.value, call_type=EthereumTxCallType.DELEGATE_CALL.value, trace_address=trace_address, error=None, ) child_internal_tx = None # For Ether transfers internal_tx_decoded = InternalTxDecoded( internal_tx=internal_tx, function_name="", arguments=args, ) if event_name == "ProxyCreation": # Should be the 2nd event to be indexed, after `SafeSetup` safe_address = args.pop("proxy") if self._is_setup_indexed(safe_address): internal_tx = None else: new_trace_address = f"{trace_address},0" to = args.pop("singleton") # Try to update InternalTx created by SafeSetup (if Safe was created using the ProxyFactory) with # the master copy used. Without tracing it cannot be detected otherwise InternalTx.objects.filter( contract_address=safe_address, decoded_tx__function_name="setup" ).update(to=to, contract_address=None, trace_address=new_trace_address) # Add creation internal tx. _from is the address of the proxy instead of the safe_address internal_tx.contract_address = safe_address internal_tx.tx_type = InternalTxType.CREATE.value internal_tx.call_type = None internal_tx_decoded = None elif event_name == "SafeSetup": # Should be the 1st event to be indexed, unless custom `to` and `data` are set if self._is_setup_indexed(safe_address): internal_tx = None else: # Usually ProxyCreation is called before SafeSetup, but it can be the opposite if someone # creates a Safe and configure it in the next transaction. Remove it if that's the case InternalTx.objects.filter(contract_address=safe_address).delete() internal_tx.contract_address = safe_address internal_tx_decoded.function_name = "setup" args["payment"] = 0 args["paymentReceiver"] = NULL_ADDRESS args["_threshold"] = args.pop("threshold") args["_owners"] = args.pop("owners") elif event_name == "SafeMultiSigTransaction": internal_tx_decoded.function_name = "execTransaction" data = HexBytes(args["data"]) args["data"] = data.hex() args["signatures"] = HexBytes(args["signatures"]).hex() args["nonce"], args["sender"], args["threshold"] = decode_abi( ["uint256", "address", "uint256"], internal_tx_decoded.arguments.pop("additionalInfo"), ) if args["value"] and not data: # Simulate ether transfer child_internal_tx = InternalTx( ethereum_tx_id=ethereum_tx_hash, timestamp=ethereum_block["timestamp"], block_number=ethereum_block["number"], _from=safe_address, gas=23000, data=b"", to=args["to"], value=args["value"], gas_used=23000, contract_address=None, code=None, output=None, refund_address=None, tx_type=InternalTxType.CALL.value, call_type=EthereumTxCallType.CALL.value, trace_address=f"{trace_address},0", error=None, ) elif event_name == "SafeModuleTransaction": internal_tx_decoded.function_name = "execTransactionFromModule" args["data"] = HexBytes(args["data"]).hex() elif event_name == "ApproveHash": internal_tx_decoded.function_name = "approveHash" args["hashToApprove"] = args.pop("approvedHash").hex() elif event_name == "EnabledModule": internal_tx_decoded.function_name = "enableModule" elif event_name == "DisabledModule": internal_tx_decoded.function_name = "disableModule" elif event_name == "AddedOwner": internal_tx_decoded.function_name = "addOwnerWithThreshold" args["_threshold"] = None elif event_name == "RemovedOwner": internal_tx_decoded.function_name = "removeOwner" args["_threshold"] = None elif event_name == "ChangedThreshold": internal_tx_decoded.function_name = "changeThreshold" args["_threshold"] = args.pop("threshold") elif event_name == "ChangedFallbackHandler": internal_tx_decoded.function_name = "setFallbackHandler" elif event_name == "ChangedGuard": internal_tx_decoded.function_name = "setGuard" elif event_name == "SafeReceived": # Received ether internal_tx.call_type = EthereumTxCallType.CALL.value internal_tx._from = args["sender"] internal_tx.to = safe_address internal_tx.value = args["value"] internal_tx_decoded = None elif event_name == "ChangedMasterCopy": internal_tx_decoded.function_name = "changeMasterCopy" internal_tx.arguments = { "_masterCopy": args.get("singleton") or args.get("masterCopy") } else: # 'SignMsg', 'ExecutionFailure', 'ExecutionSuccess', # 'ExecutionFromModuleSuccess', 'ExecutionFromModuleFailure' internal_tx_decoded = None if internal_tx: with transaction.atomic(): try: internal_tx.save() if child_internal_tx: child_internal_tx.save() if internal_tx_decoded: internal_tx_decoded.save() except IntegrityError as exc: logger.info( "Ignoring already processed event %s for Safe %s on tx-hash=%s: %s", event_name, safe_address, decoded_element["transactionHash"].hex(), exc, ) return None return internal_tx