def process_decoded_transaction(self, internal_tx_decoded: InternalTxDecoded) -> bool: """ Decode internal tx and creates needed models :param internal_tx_decoded: InternalTxDecoded to process. It will be set as `processed` :return: True if tx could be processed, False otherwise """ function_name = internal_tx_decoded.function_name arguments = internal_tx_decoded.arguments internal_tx = internal_tx_decoded.internal_tx contract_address = internal_tx._from master_copy = internal_tx.to processed_successfully = True if function_name == 'setup' and contract_address != NULL_ADDRESS: owners = arguments['_owners'] threshold = arguments['_threshold'] _, created = SafeContract.objects.get_or_create(address=contract_address, defaults={ 'ethereum_tx': internal_tx.ethereum_tx, 'erc20_block_number': internal_tx.ethereum_tx.block_id, }) if created: logger.info('Found new Safe=%s', contract_address) SafeStatus.objects.create(internal_tx=internal_tx, address=contract_address, owners=owners, threshold=threshold, nonce=0, master_copy=master_copy) elif function_name in ('addOwnerWithThreshold', 'removeOwner', 'removeOwnerWithThreshold'): safe_status = SafeStatus.objects.last_for_address(contract_address) safe_status.threshold = arguments['_threshold'] owner = arguments['owner'] try: if function_name == 'addOwnerWithThreshold': safe_status.owners.append(owner) else: # removeOwner, removeOwnerWithThreshold safe_status.owners.remove(owner) except ValueError: logger.error('Error processing trace=%s for contract=%s with tx-hash=%s', internal_tx.trace_address, contract_address, internal_tx.ethereum_tx_id) safe_status.store_new(internal_tx) elif function_name == 'swapOwner': old_owner = arguments['oldOwner'] new_owner = arguments['newOwner'] safe_status = SafeStatus.objects.last_for_address(contract_address) safe_status.owners.remove(old_owner) safe_status.owners.append(new_owner) safe_status.store_new(internal_tx) elif function_name == 'changeThreshold': safe_status = SafeStatus.objects.last_for_address(contract_address) safe_status.threshold = arguments['_threshold'] safe_status.store_new(internal_tx) elif function_name == 'changeMasterCopy': # TODO Ban address if it doesn't have a valid master copy safe_status = SafeStatus.objects.last_for_address(contract_address) safe_status.master_copy = arguments['_masterCopy'] safe_status.store_new(internal_tx) elif function_name == 'execTransaction': safe_status = SafeStatus.objects.last_for_address(contract_address) nonce = safe_status.nonce if 'baseGas' in arguments: # `dataGas` was renamed to `baseGas` in v1.0.0 base_gas = arguments['baseGas'] safe_version = '1.0.0' else: base_gas = arguments['dataGas'] safe_version = '0.0.1' safe_tx = SafeTx(None, contract_address, arguments['to'], arguments['value'], arguments['data'], arguments['operation'], arguments['safeTxGas'], base_gas, arguments['gasPrice'], arguments['gasToken'], arguments['refundReceiver'], HexBytes(arguments['signatures']), safe_nonce=nonce, safe_version=safe_version) safe_tx_hash = safe_tx.safe_tx_hash ethereum_tx = internal_tx.ethereum_tx # Remove existing transaction with same nonce in case of bad indexing (one of the master copies can be # outdated and a tx with a wrong nonce could be indexed) MultisigTransaction.objects.filter( ethereum_tx=ethereum_tx, nonce=safe_tx.safe_nonce, safe=contract_address ).exclude( safe_tx_hash=safe_tx_hash ).delete() # Remove old txs not used MultisigTransaction.objects.filter( ethereum_tx=None, nonce__lt=safe_tx.safe_nonce, safe=contract_address ) multisig_tx, created = MultisigTransaction.objects.get_or_create( safe_tx_hash=safe_tx_hash, defaults={ 'safe': contract_address, 'ethereum_tx': ethereum_tx, 'to': safe_tx.to, 'value': safe_tx.value, 'data': safe_tx.data if safe_tx.data else None, 'operation': safe_tx.operation, 'safe_tx_gas': safe_tx.safe_tx_gas, 'base_gas': safe_tx.base_gas, 'gas_price': safe_tx.gas_price, 'gas_token': safe_tx.gas_token, 'refund_receiver': safe_tx.refund_receiver, 'nonce': safe_tx.safe_nonce, 'signatures': safe_tx.signatures, }) if not created and not multisig_tx.ethereum_tx: multisig_tx.ethereum_tx = ethereum_tx multisig_tx.signatures = HexBytes(arguments['signatures']) multisig_tx.save(update_fields=['ethereum_tx', 'signatures']) for safe_signature in SafeSignature.parse_signatures(safe_tx.signatures, safe_tx_hash): multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create( multisig_transaction_hash=safe_tx_hash, owner=safe_signature.owner, defaults={ 'ethereum_tx': None, 'multisig_transaction': multisig_tx, 'signature': safe_signature.signature, } ) if multisig_confirmation.signature != safe_signature.signature: multisig_confirmation.signature = safe_signature.signature multisig_confirmation.save(update_fields=['signature']) safe_status.nonce = nonce + 1 safe_status.store_new(internal_tx) elif function_name == 'approveHash': multisig_transaction_hash = arguments['hashToApprove'] ethereum_tx = internal_tx.ethereum_tx owner = internal_tx.get_previous_trace()._from (multisig_confirmation, created) = MultisigConfirmation.objects.get_or_create(multisig_transaction_hash=multisig_transaction_hash, owner=owner, defaults={ 'ethereum_tx': ethereum_tx, }) if not created and not multisig_confirmation.ethereum_tx_id: multisig_confirmation.ethereum_tx = ethereum_tx multisig_confirmation.save() elif function_name == 'execTransactionFromModule': # No side effects or nonce increasing, but trace will be set as processed pass else: processed_successfully = False internal_tx_decoded.set_processed() return processed_successfully
def __process_decoded_transaction(self, internal_tx_decoded: InternalTxDecoded) -> bool: """ Decode internal tx and creates needed models :param internal_tx_decoded: InternalTxDecoded to process. It will be set as `processed` :return: True if tx could be processed, False otherwise """ function_name = internal_tx_decoded.function_name arguments = internal_tx_decoded.arguments internal_tx = internal_tx_decoded.internal_tx contract_address = internal_tx._from master_copy = internal_tx.to processed_successfully = True if function_name == 'setup' and contract_address != NULL_ADDRESS: logger.debug('Processing Safe setup') owners = arguments['_owners'] threshold = arguments['_threshold'] fallback_handler = arguments.get('fallbackHandler', NULL_ADDRESS) nonce = 0 try: safe_contract: SafeContract = SafeContract.objects.get(address=contract_address) if not safe_contract.ethereum_tx_id or not safe_contract.erc20_block_number: safe_contract.ethereum_tx = internal_tx.ethereum_tx safe_contract.erc20_block_number = internal_tx.ethereum_tx.block_id safe_contract.save(update_fields=['ethereum_tx', 'erc20_block_number']) except SafeContract.DoesNotExist: SafeContract.objects.create(address=contract_address, ethereum_tx=internal_tx.ethereum_tx, erc20_block_number=max(internal_tx.ethereum_tx.block_id - 5760, 0)) logger.info('Found new Safe=%s', contract_address) SafeStatus.objects.create(internal_tx=internal_tx, address=contract_address, owners=owners, threshold=threshold, nonce=nonce, master_copy=master_copy, fallback_handler=fallback_handler) elif function_name in ('addOwnerWithThreshold', 'removeOwner', 'removeOwnerWithThreshold'): logger.debug('Processing owner/threshold modification') safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.threshold = arguments['_threshold'] owner = arguments['owner'] try: if function_name == 'addOwnerWithThreshold': safe_status.owners.append(owner) else: # removeOwner, removeOwnerWithThreshold safe_status.owners.remove(owner) except ValueError: logger.error('Error processing trace=%s for contract=%s with tx-hash=%s', internal_tx.trace_address, contract_address, internal_tx.ethereum_tx_id) self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'swapOwner': logger.debug('Processing owner swap') old_owner = arguments['oldOwner'] new_owner = arguments['newOwner'] safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.owners.remove(old_owner) safe_status.owners.append(new_owner) self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'changeThreshold': logger.debug('Processing threshold change') safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.threshold = arguments['_threshold'] self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'changeMasterCopy': logger.debug('Processing master copy change') # TODO Ban address if it doesn't have a valid master copy safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.master_copy = arguments['_masterCopy'] self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'setFallbackHandler': logger.debug('Setting FallbackHandler') safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.fallback_handler = arguments['handler'] self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'enableModule': logger.debug('Enabling Module') safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.enabled_modules.append(arguments['module']) self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'disableModule': logger.debug('Disabling Module') safe_status = self.get_last_safe_status_for_address(contract_address) safe_status.enabled_modules.remove(arguments['module']) self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'execTransactionFromModule': logger.debug('Executing Tx from Module') module_internal_tx = internal_tx.get_previous_module_trace() module_address = module_internal_tx.to if module_internal_tx else NULL_ADDRESS ModuleTransaction.objects.get_or_create( internal_tx=internal_tx, defaults={ 'created': internal_tx.ethereum_tx.block.timestamp, 'safe': contract_address, 'module': module_address, 'to': arguments['to'], 'value': arguments['value'], 'data': HexBytes(arguments['data']), 'operation': arguments['operation'] } ) elif function_name == 'approveHash': logger.debug('Processing hash approval') multisig_transaction_hash = arguments['hashToApprove'] ethereum_tx = internal_tx.ethereum_tx # TODO Check previous trace is not a delegate call owner = internal_tx.get_previous_trace()._from (multisig_confirmation, _) = MultisigConfirmation.objects.get_or_create(multisig_transaction_hash=multisig_transaction_hash, owner=owner, defaults={ 'ethereum_tx': ethereum_tx, }) if not multisig_confirmation.ethereum_tx_id: multisig_confirmation.ethereum_tx = ethereum_tx multisig_confirmation.save(update_fields=['ethereum_tx']) elif function_name == 'execTransaction': logger.debug('Processing transaction execution') safe_status = self.get_last_safe_status_for_address(contract_address) nonce = safe_status.nonce if 'baseGas' in arguments: # `dataGas` was renamed to `baseGas` in v1.0.0 base_gas = arguments['baseGas'] safe_version = '1.0.0' else: base_gas = arguments['dataGas'] safe_version = '0.0.1' safe_tx = SafeTx(None, contract_address, arguments['to'], arguments['value'], arguments['data'], arguments['operation'], arguments['safeTxGas'], base_gas, arguments['gasPrice'], arguments['gasToken'], arguments['refundReceiver'], HexBytes(arguments['signatures']), safe_nonce=nonce, safe_version=safe_version) safe_tx_hash = safe_tx.safe_tx_hash ethereum_tx = internal_tx.ethereum_tx # Remove existing transaction with same nonce in case of bad indexing (one of the master copies can be # outdated and a tx with a wrong nonce could be indexed) # MultisigTransaction.objects.filter( # ethereum_tx=ethereum_tx, # nonce=safe_tx.safe_nonce, # safe=contract_address # ).exclude( # safe_tx_hash=safe_tx_hash # ).delete() # Remove old txs not used # MultisigTransaction.objects.filter( # ethereum_tx=None, # nonce__lt=safe_tx.safe_nonce, # safe=contract_address # ).delete() failed = self.is_failed(ethereum_tx, safe_tx_hash) multisig_tx, _ = MultisigTransaction.objects.get_or_create( safe_tx_hash=safe_tx_hash, defaults={ 'created': internal_tx.ethereum_tx.block.timestamp, 'safe': contract_address, 'ethereum_tx': ethereum_tx, 'to': safe_tx.to, 'value': safe_tx.value, 'data': safe_tx.data if safe_tx.data else None, 'operation': safe_tx.operation, 'safe_tx_gas': safe_tx.safe_tx_gas, 'base_gas': safe_tx.base_gas, 'gas_price': safe_tx.gas_price, 'gas_token': safe_tx.gas_token, 'refund_receiver': safe_tx.refund_receiver, 'nonce': safe_tx.safe_nonce, 'signatures': safe_tx.signatures, 'failed': failed, }) if not multisig_tx.ethereum_tx_id: multisig_tx.ethereum_tx = ethereum_tx multisig_tx.failed = failed multisig_tx.signatures = HexBytes(arguments['signatures']) multisig_tx.save(update_fields=['ethereum_tx', 'failed', 'signatures']) for safe_signature in SafeSignature.parse_signatures(safe_tx.signatures, safe_tx_hash): multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create( multisig_transaction_hash=safe_tx_hash, owner=safe_signature.owner, defaults={ 'ethereum_tx': None, 'multisig_transaction': multisig_tx, 'signature': safe_signature.signature, } ) if multisig_confirmation.signature != safe_signature.signature: multisig_confirmation.signature = safe_signature.signature multisig_confirmation.save(update_fields=['signature']) safe_status.nonce = nonce + 1 self.store_new_safe_status(safe_status, internal_tx) elif function_name == 'execTransactionFromModule': logger.debug('Not processing execTransactionFromModule') # No side effects or nonce increasing, but trace will be set as processed else: processed_successfully = False logger.debug('End processing') return processed_successfully