Exemplo n.º 1
0
    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