def validate(self, data):
        super().validate(data)

        if not SafeContract.objects.filter(address=data['safe']).exists():
            raise ValidationError(
                f"Safe={data['safe']} does not exist or it's still not indexed"
            )

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier='pending')
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier='latest')

        signature = data['signature']
        delegate = data['delegate']
        operation_hash = DelegateSignatureHelper.calculate_hash(delegate)
        safe_signatures = SafeSignature.parse_signature(
            signature, operation_hash)
        if not safe_signatures:
            raise ValidationError('Cannot a valid signature')
        elif len(safe_signatures) > 1:
            raise ValidationError(
                'More than one signatures detected, just one is expected')

        safe_signature = safe_signatures[0]
        delegator = safe_signature.owner
        if delegator not in safe_owners:
            if safe_signature.signature_type == SafeSignatureType.EOA:
                # Maybe it's an `eth_sign` signature without Gnosis Safe `v + 4`, let's try
                safe_signatures = SafeSignature.parse_signature(
                    signature,
                    DelegateSignatureHelper.calculate_hash(delegate,
                                                           eth_sign=True))
                safe_signature = safe_signatures[0]
                delegator = safe_signature.owner
            if delegator not in safe_owners:
                raise ValidationError(
                    'Signing owner is not an owner of the Safe')

        if not safe_signature.is_valid():
            raise ValidationError(
                f'Signature of type={safe_signature.signature_type.name} for delegator={delegator} '
                f'is not valid')

        data['delegator'] = delegator
        return data
def add_signature_type(apps, schema_editor):
    MultisigConfirmation = apps.get_model("history", "MultisigConfirmation")
    MultisigTransaction = apps.get_model("history", "MultisigTransaction")

    for multisig_confirmation in MultisigConfirmation.objects.all():
        if not multisig_confirmation.signature:  # It's an approvedHash
            multisig_confirmation.signature = SafeSignatureApprovedHash.build_for_owner(
                multisig_confirmation.owner,
                multisig_confirmation.multisig_transaction_hash,
            ).export_signature()

            multisig_confirmation.signature_type = SafeSignatureType.APPROVED_HASH.value
            multisig_confirmation.save(
                update_fields=["signature_type", "signature"])
        else:
            for safe_signature in SafeSignature.parse_signature(
                    multisig_confirmation.signature,
                    multisig_confirmation.multisig_transaction_hash,
            ):
                multisig_confirmation.signature_type = (
                    safe_signature.signature_type.value)
                multisig_confirmation.save(update_fields=["signature_type"])

    for multisig_tx in MultisigTransaction.objects.exclude(signatures=None):
        for safe_signature in SafeSignature.parse_signature(
                multisig_tx.signatures.tobytes(),
                HexBytes(multisig_tx.safe_tx_hash)):
            multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                multisig_transaction_hash=multisig_tx.safe_tx_hash,
                owner=safe_signature.owner,
                defaults={
                    "ethereum_tx": None,
                    "multisig_transaction": multisig_tx,
                    "signature": safe_signature.export_signature(),
                    "signature_type": safe_signature.signature_type.value,
                },
            )
            if (multisig_confirmation.signature !=
                    safe_signature.export_signature()
                    or multisig_confirmation.signature_type !=
                    safe_signature.signature_type.value):

                multisig_confirmation.signature = safe_signature.export_signature(
                )
                multisig_confirmation.signature_type = (
                    safe_signature.signature_type.value)
                multisig_confirmation.save(
                    update_fields=["signature", "signature_type"])
    def check_signature(self, signature: bytes, operation_hash: bytes,
                        safe_owners: List[str]) -> Optional[str]:
        """
        Checks signature and returns a valid owner if found, None otherwise
        :param signature:
        :param operation_hash:
        :param safe_owners:
        :return: Valid delegator address if found, None otherwise
        """
        safe_signatures = SafeSignature.parse_signature(
            signature, operation_hash)
        if not safe_signatures:
            raise ValidationError('Signature is not valid')
        elif len(safe_signatures) > 1:
            raise ValidationError(
                'More than one signatures detected, just one is expected')

        safe_signature = safe_signatures[0]
        delegator = safe_signature.owner
        if delegator in safe_owners:
            if not safe_signature.is_valid():
                raise ValidationError(
                    f'Signature of type={safe_signature.signature_type.name} '
                    f'for delegator={delegator} is not valid')
            return delegator
    def check_signature(
        self,
        ethereum_client: EthereumClient,
        safe_address: ChecksumAddress,
        signature: bytes,
        operation_hash: bytes,
        valid_delegators: List[ChecksumAddress],
    ) -> Optional[ChecksumAddress]:
        """
        Checks signature and returns a valid owner if found, None otherwise

        :param ethereum_client:
        :param safe_address:
        :param signature:
        :param operation_hash:
        :param valid_delegators:
        :return: Valid delegator address if found, None otherwise
        """
        safe_signatures = SafeSignature.parse_signature(
            signature, operation_hash)
        if not safe_signatures:
            raise ValidationError("Signature is not valid")

        if len(safe_signatures) > 1:
            raise ValidationError(
                "More than one signatures detected, just one is expected")

        safe_signature = safe_signatures[0]
        delegator = safe_signature.owner
        if delegator in valid_delegators:
            if not safe_signature.is_valid(ethereum_client, safe_address):
                raise ValidationError(
                    f"Signature of type={safe_signature.signature_type.name} "
                    f"for delegator={delegator} is not valid")
            return delegator
    def check_delegate_signature(
        self,
        ethereum_client: EthereumClient,
        signature: bytes,
        operation_hash: bytes,
        delegator: ChecksumAddress,
    ) -> bool:
        """
        Checks signature and returns a valid owner if found, None otherwise

        :param ethereum_client:
        :param signature:
        :param operation_hash:
        :param delegator:
        :return: `True` if signature is valid for the delegator, `False` otherwise
        """
        safe_signatures = SafeSignature.parse_signature(
            signature, operation_hash)
        if not safe_signatures:
            raise ValidationError("Signature is not valid")

        if len(safe_signatures) > 1:
            raise ValidationError(
                "More than one signatures detected, just one is expected")

        safe_signature = safe_signatures[0]
        owner = safe_signature.owner
        if owner == delegator:
            if not safe_signature.is_valid(ethereum_client, owner):
                raise ValidationError(
                    f"Signature of type={safe_signature.signature_type.name} "
                    f"for delegator={delegator} is not valid")
            return True
        return False
Exemplo n.º 6
0
    def validate_signature(self, signature: bytes):
        safe_tx_hash = self.context['safe_tx_hash']
        try:
            multisig_transaction = MultisigTransaction.objects.select_related(
                'ethereum_tx'
            ).get(safe_tx_hash=safe_tx_hash)
        except MultisigTransaction.DoesNotExist:
            raise NotFound(f'Multisig transaction with safe-tx-hash={safe_tx_hash} was not found')

        safe_address = multisig_transaction.safe
        if multisig_transaction.executed:
            raise ValidationError(f'Transaction with safe-tx-hash={safe_tx_hash} was already executed')

        ethereum_client = EthereumClientProvider()
        safe = Safe(safe_address, ethereum_client)
        try:
            safe_owners = safe.retrieve_owners(block_identifier='pending')
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier='latest')

        parsed_signatures = SafeSignature.parse_signature(signature, safe_tx_hash)
        signature_owners = []
        for safe_signature in parsed_signatures:
            owner = safe_signature.owner
            if owner not in safe_owners:
                raise ValidationError(f'Signer={owner} is not an owner. Current owners={safe_owners}')
            if not safe_signature.is_valid(ethereum_client, safe.address):
                raise ValidationError(f'Signature={safe_signature.signature.hex()} for owner={owner} is not valid')
            if owner in signature_owners:
                raise ValidationError(f'Signature for owner={owner} is duplicated')

            signature_owners.append(owner)
        return signature
 def owners(self) -> Optional[List[str]]:
     if not self.signatures:
         return None
     else:
         signatures = bytes(self.signatures)
         safe_signatures = SafeSignature.parse_signature(
             signatures, self.safe_tx_hash)
         return [safe_signature.owner for safe_signature in safe_signatures]
Exemplo n.º 8
0
 def signers(self) -> List[str]:
     if not self.signatures:
         return []
     else:
         signatures = bytes(self.signatures)
         safe_signatures = SafeSignature.parse_signature(
             signatures, self.safe_tx_hash)
         return [safe_signature.owner for safe_signature in safe_signatures]
Exemplo n.º 9
0
def add_signature_type(apps, schema_editor):
    MultisigConfirmation = apps.get_model('history', 'MultisigConfirmation')
    MultisigTransaction = apps.get_model('history', 'MultisigTransaction')

    for multisig_confirmation in MultisigConfirmation.objects.all():
        if not multisig_confirmation.signature:  # It's an approvedHash
            multisig_confirmation.signature = SafeSignatureApprovedHash.build_for_owner(
                multisig_confirmation.owner, multisig_confirmation.
                multisig_transaction_hash).export_signature()

            multisig_confirmation.signature_type = SafeSignatureType.APPROVED_HASH.value
            multisig_confirmation.save(
                update_fields=['signature_type', 'signature'])
        else:
            for safe_signature in SafeSignature.parse_signature(
                    multisig_confirmation.signature,
                    multisig_confirmation.multisig_transaction_hash):
                multisig_confirmation.signature_type = safe_signature.signature_type.value
                multisig_confirmation.save(update_fields=['signature_type'])

    for multisig_tx in MultisigTransaction.objects.exclude(signatures=None):
        for safe_signature in SafeSignature.parse_signature(
                multisig_tx.signatures.tobytes(),
                HexBytes(multisig_tx.safe_tx_hash)):
            multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                multisig_transaction_hash=multisig_tx.safe_tx_hash,
                owner=safe_signature.owner,
                defaults={
                    'ethereum_tx': None,
                    'multisig_transaction': multisig_tx,
                    'signature': safe_signature.export_signature(),
                    'signature_type': safe_signature.signature_type.value,
                })
            if multisig_confirmation.signature != safe_signature.export_signature() or \
                    multisig_confirmation.signature_type != safe_signature.signature_type.value:

                multisig_confirmation.signature = safe_signature.export_signature(
                )
                multisig_confirmation.signature_type = safe_signature.signature_type.value
                multisig_confirmation.save(
                    update_fields=['signature', 'signature_type'])
Exemplo n.º 10
0
    def validate(self, data: Dict[str, Any]):
        data = super().validate(data)
        signature_owners = []
        owners_without_safe = []
        signatures = data.get("signatures") or []
        safe_addresses = data["safes"]
        if signatures:
            valid_owners = self.get_valid_owners(safe_addresses)
            for signature in signatures:
                hash_to_sign = calculate_device_registration_hash(
                    data["timestamp"],
                    data["uuid"],
                    data["cloud_messaging_token"],
                    data["safes"],
                )
                parsed_signatures = SafeSignature.parse_signature(
                    signature, hash_to_sign)
                if not parsed_signatures:
                    raise ValidationError("Signature cannot be parsed")
                for safe_signature in parsed_signatures:
                    if (safe_signature.signature_type != SafeSignatureType.EOA
                            or not safe_signature.is_valid()):
                        raise ValidationError(
                            "An externally owned account signature was expected"
                        )
                    owner = safe_signature.owner
                    if owner in (signature_owners + owners_without_safe):
                        raise ValidationError(
                            f"Signature for owner={owner} is duplicated")

                    if owner not in valid_owners:
                        owners_without_safe.append(owner)
                        # raise ValidationError(f'Owner={owner} is not an owner of any of the safes={data["safes"]}. '
                        #                       f'Expected hash to sign {hash_to_sign.hex()}')
                    else:
                        signature_owners.append(owner)
            if len(signatures) > len(signature_owners + owners_without_safe):
                raise ValidationError(
                    "Number of signatures is less than the number of owners detected"
                )

        data["owners_registered"] = signature_owners
        data["owners_not_registered"] = owners_without_safe
        return data
    def validate(self, data: Dict[str, Any]):
        data = super().validate(data)
        signature_owners = []
        owners_without_safe = []
        signatures = data.get('signatures') or []
        if signatures:
            current_owners = {
                owner
                for safe in data['safes'] for owner in get_safe_owners(safe)
            }
            for signature in signatures:
                hash_to_sign = calculate_device_registration_hash(
                    data['timestamp'], data['uuid'],
                    data['cloud_messaging_token'], data['safes'])
                parsed_signatures = SafeSignature.parse_signature(
                    signature, hash_to_sign)
                if not parsed_signatures:
                    raise ValidationError('Signature cannot be parsed')
                for safe_signature in parsed_signatures:
                    if safe_signature.signature_type != SafeSignatureType.EOA or not safe_signature.is_valid(
                    ):
                        raise ValidationError(
                            'An externally owned account signature was expected'
                        )
                    owner = safe_signature.owner
                    if owner in (signature_owners + owners_without_safe):
                        raise ValidationError(
                            f'Signature for owner={owner} is duplicated')
                    elif owner not in current_owners:
                        owners_without_safe.append(owner)
                        # raise ValidationError(f'Owner={owner} is not an owner of any of the safes={data["safes"]}. '
                        #                       f'Expected hash to sign {hash_to_sign.hex()}')
                    else:
                        signature_owners.append(owner)
            if len(signatures) > len(signature_owners + owners_without_safe):
                raise ValidationError(
                    'Number of signatures is less than the number of owners detected'
                )

        data['owners_registered'] = signature_owners
        data['owners_not_registered'] = owners_without_safe
        return data
Exemplo n.º 12
0
    def save(self, **kwargs):
        safe_tx_hash = self.context['safe_tx_hash']
        signature = self.validated_data['signature']
        multisig_confirmations = []
        parsed_signatures = SafeSignature.parse_signature(signature, safe_tx_hash)
        for safe_signature in parsed_signatures:
            multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                multisig_transaction_hash=safe_tx_hash,
                owner=safe_signature.owner,
                defaults={
                    'multisig_transaction_id': safe_tx_hash,
                    'signature': safe_signature.export_signature(),
                    'signature_type': safe_signature.signature_type.value,
                }
            )
            multisig_confirmations.append(multisig_confirmation)

        if self.validated_data['signature']:
            MultisigTransaction.objects.filter(safe_tx_hash=safe_tx_hash).update(trusted=True)
        return multisig_confirmations
    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)
        safe_tx = safe.build_multisig_tx(data['to'],
                                         data['value'],
                                         data['data'],
                                         data['operation'],
                                         data['safe_tx_gas'],
                                         data['base_gas'],
                                         data['gas_price'],
                                         data['gas_token'],
                                         data['refund_receiver'],
                                         safe_nonce=data['nonce'])
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data['contract_transaction_hash']:
            raise ValidationError(
                f'Contract-transaction-hash={contract_transaction_hash.hex()} '
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` or same `safeTxHash` for the same Safe.
        # We allow duplicated if existing tx is not executed
        multisig_transactions = MultisigTransaction.objects.filter(
            safe=safe.address, nonce=data['nonce']).executed()
        if multisig_transactions:
            for multisig_transaction in multisig_transactions:
                if multisig_transaction.safe_tx_hash == contract_transaction_hash.hex(
                ):
                    raise ValidationError(
                        f'Tx with safe-tx-hash={contract_transaction_hash.hex()} '
                        f'for safe={safe.address} was already executed in '
                        f'tx-hash={multisig_transaction.ethereum_tx_id}')

            raise ValidationError(
                f'Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} '
                f'already executed in tx-hash={multisig_transactions[0].ethereum_tx_id}'
            )

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier='pending')
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier='latest')

        data['safe_owners'] = safe_owners

        delegates = SafeContractDelegate.objects.get_delegates_for_safe(
            safe.address)
        allowed_senders = safe_owners + delegates
        if not data['sender'] in allowed_senders:
            raise ValidationError(
                f'Sender={data["sender"]} is not an owner or delegate. '
                f'Current owners={safe_owners}. Delegates={delegates}')

        signature_owners = []
        # TODO Make signature mandatory
        signature = data.get('signature', b'')
        parsed_signatures = SafeSignature.parse_signature(
            signature, contract_transaction_hash)
        data['parsed_signatures'] = parsed_signatures
        for safe_signature in parsed_signatures:
            owner = safe_signature.owner
            if not safe_signature.is_valid(ethereum_client, safe.address):
                raise ValidationError(
                    f'Signature={safe_signature.signature.hex()} for owner={owner} is not valid'
                )

            if owner in delegates and len(parsed_signatures) > 1:
                raise ValidationError(
                    f'Just one signature is expected if using delegates')
            if owner not in allowed_senders:
                raise ValidationError(
                    f'Signer={owner} is not an owner or delegate. '
                    f'Current owners={safe_owners}. Delegates={delegates}')
            if owner in signature_owners:
                raise ValidationError(
                    f'Signature for owner={owner} is duplicated')

            signature_owners.append(owner)

        # TODO Make signature mandatory. len(signature_owners) must be >= 1
        if signature_owners and data['sender'] not in signature_owners:
            raise ValidationError(
                f'Signature does not match sender={data["sender"]}. '
                f'Calculated owners={signature_owners}')

        return data
Exemplo n.º 14
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')
            ethereum_tx = internal_tx.ethereum_tx
            module_internal_tx = internal_tx.get_previous_module_trace()
            module_address = module_internal_tx.to if module_internal_tx else NULL_ADDRESS
            module_data = HexBytes(arguments['data'])
            failed = self.is_module_failed(ethereum_tx, module_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': module_data if module_data else None,
                    'operation': arguments['operation'],
                    'failed': failed,
                }
            )

        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
            safe_signature = SafeSignatureApprovedHash.build_for_owner(owner, multisig_transaction_hash)
            (multisig_confirmation,
             _) = MultisigConfirmation.objects.get_or_create(multisig_transaction_hash=multisig_transaction_hash,
                                                             owner=owner,
                                                             defaults={
                                                                 'ethereum_tx': ethereum_tx,
                                                                 'signature': safe_signature.export_signature(),
                                                                 'signature_type': safe_signature.signature_type.value,
                                                             })
            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,
                    'trusted': True,
                })
            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.trusted = True
                multisig_tx.save(update_fields=['ethereum_tx', 'failed', 'signatures', 'trusted'])

            for safe_signature in SafeSignature.parse_signature(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.export_signature(),
                        'signature_type': safe_signature.signature_type.value,
                    }
                )
                if multisig_confirmation.signature != safe_signature.signature:
                    multisig_confirmation.signature = safe_signature.export_signature()
                    multisig_confirmation.signature_type = safe_signature.signature_type.value
                    multisig_confirmation.save(update_fields=['signature', 'signature_type'])

            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
    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)
        safe_tx = safe.build_multisig_tx(data['to'],
                                         data['value'],
                                         data['data'],
                                         data['operation'],
                                         data['safe_tx_gas'],
                                         data['base_gas'],
                                         data['gas_price'],
                                         data['gas_token'],
                                         data['refund_receiver'],
                                         safe_nonce=data['nonce'])
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data['contract_transaction_hash']:
            raise ValidationError(
                f'Contract-transaction-hash={contract_transaction_hash.hex()} '
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` for the same Safe.
        # We allow duplicated if existing tx is not executed
        try:
            multisig_transaction: MultisigTransaction = MultisigTransaction.objects.exclude(
                ethereum_tx=None).exclude(
                    safe_tx_hash=contract_transaction_hash).get(
                        safe=safe.address, nonce=data['nonce'])
            if multisig_transaction.safe_tx_hash != contract_transaction_hash:
                raise ValidationError(
                    f'Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} already executed in '
                    f'tx-hash={multisig_transaction.ethereum_tx_id}')
        except MultisigTransaction.DoesNotExist:
            pass

        # Check owners and old owners, owner might be removed but that tx can still be signed by that owner
        if not safe.retrieve_is_owner(data['sender']):
            try:
                # TODO Fix this, we can use SafeStatus now
                if not safe.retrieve_is_owner(
                        data['sender'],
                        block_identifier=max(
                            0, ethereum_client.current_block_number - 20)):
                    raise ValidationError('User is not an owner')
            except BadFunctionCallOutput:  # If it didn't exist 20 blocks ago
                raise ValidationError('User is not an owner')

        signature = data.get('signature')
        if signature is not None:
            #  TODO Support signatures with multiple owners
            if len(signature) != 65:
                raise ValidationError(
                    'Signatures with more than one owner still not supported')

            safe_signature = SafeSignature(signature,
                                           contract_transaction_hash)
            #  TODO Support contract signatures and approved hashes
            if safe_signature.signature_type == SafeSignatureType.CONTRACT_SIGNATURE:
                raise ValidationError('Contract signatures not supported')
            elif safe_signature.signature_type == SafeSignatureType.APPROVED_HASH:
                # Index it automatically later
                del data['signature']

            address = safe_signature.owner
            if address != data['sender']:
                raise ValidationError(
                    f'Signature does not match sender={data["sender"]}. Calculated owner={address}'
                )

        return data
Exemplo n.º 17
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
        if internal_tx.gas_used < 1000:
            # When calling a non existing function, fallback of the proxy does not return any error but we can detect
            # this kind of functions due to little gas used. Some of this transactions get decoded as they were
            # valid in old versions of the proxies, like changes to `setup`
            return False

        processed_successfully = True
        logger.debug(
            'Start processing InternalTxDecoded in tx-hash=%s',
            HexBytes(internal_tx_decoded.internal_tx.ethereum_tx_id).hex())
        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:
                blocks_one_day = int(24 * 60 * 60 / 15)  # 15 seconds block
                SafeContract.objects.create(
                    address=contract_address,
                    ethereum_tx=internal_tx.ethereum_tx,
                    erc20_block_number=max(
                        internal_tx.ethereum_tx.block_id - blocks_one_day, 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)
            self.clear_cache(contract_address)
        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']
            if function_name == 'addOwnerWithThreshold':
                safe_status.owners.append(owner)
            else:  # removeOwner, removeOwnerWithThreshold
                self.remove_owner(internal_tx, safe_status, owner)
            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)
            self.remove_owner(internal_tx, safe_status, 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 == 'setGuard':
            safe_status = self.get_last_safe_status_for_address(
                contract_address)
            safe_status.guard = arguments[
                'guard'] if arguments['guard'] != NULL_ADDRESS else None
            if safe_status.guard:
                logger.debug('Setting Guard')
            else:
                logger.debug('Unsetting Guard')
            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 in {
                'execTransactionFromModule',
                'execTransactionFromModuleReturnData'
        }:
            logger.debug('Executing Tx from Module')
            # TODO Add test with previous traces for processing a module transaction
            ethereum_tx = internal_tx.ethereum_tx
            # Someone calls Module -> Module calls Safe Proxy -> Safe Proxy delegate calls Master Copy
            # The trace that is been processed is the last one, so indexer needs to go at least 2 traces back
            previous_trace = self.ethereum_client.parity.get_previous_trace(
                internal_tx.ethereum_tx_id,
                internal_tx.trace_address_as_list,
                number_traces=2,
                skip_delegate_calls=True)
            if not previous_trace:
                message = f'Cannot find previous trace for tx-hash={HexBytes(internal_tx.ethereum_tx_id).hex()} and ' \
                          f'trace-address={internal_tx.trace_address}'
                logger.warning(message)
                raise ValueError(message)
            module_internal_tx = InternalTx.objects.build_from_trace(
                previous_trace, internal_tx.ethereum_tx)
            module_address = module_internal_tx.to if module_internal_tx else NULL_ADDRESS
            module_data = HexBytes(arguments['data'])
            failed = self.is_module_failed(ethereum_tx, module_address,
                                           contract_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': module_data if module_data else None,
                    'operation': arguments['operation'],
                    'failed': failed,
                })

        elif function_name == 'approveHash':
            logger.debug('Processing hash approval')
            multisig_transaction_hash = arguments['hashToApprove']
            ethereum_tx = internal_tx.ethereum_tx
            previous_trace = self.ethereum_client.parity.get_previous_trace(
                internal_tx.ethereum_tx_id,
                internal_tx.trace_address_as_list,
                skip_delegate_calls=True)
            if not previous_trace:
                message = f'Cannot find previous trace for tx-hash={HexBytes(internal_tx.ethereum_tx_id).hex()} and ' \
                          f'trace-address={internal_tx.trace_address}'
                logger.warning(message)
                raise ValueError(message)
            previous_internal_tx = InternalTx.objects.build_from_trace(
                previous_trace, internal_tx.ethereum_tx)
            owner = previous_internal_tx._from
            safe_signature = SafeSignatureApprovedHash.build_for_owner(
                owner, multisig_transaction_hash)
            (multisig_confirmation,
             _) = MultisigConfirmation.objects.get_or_create(
                 multisig_transaction_hash=multisig_transaction_hash,
                 owner=owner,
                 defaults={
                     'created': internal_tx.ethereum_tx.block.timestamp,
                     'ethereum_tx': ethereum_tx,
                     'signature': safe_signature.export_signature(),
                     'signature_type': safe_signature.signature_type.value,
                 })
            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,
                    'trusted': True,
                })
            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.trusted = True
                multisig_tx.save(update_fields=[
                    'ethereum_tx', 'failed', 'signatures', 'trusted'
                ])

            for safe_signature in SafeSignature.parse_signature(
                    safe_tx.signatures, safe_tx_hash):
                multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                    multisig_transaction_hash=safe_tx_hash,
                    owner=safe_signature.owner,
                    defaults={
                        'created': internal_tx.ethereum_tx.block.timestamp,
                        'ethereum_tx': None,
                        'multisig_transaction': multisig_tx,
                        'signature': safe_signature.export_signature(),
                        'signature_type': safe_signature.signature_type.value,
                    })
                if multisig_confirmation.signature != safe_signature.signature:
                    multisig_confirmation.signature = safe_signature.export_signature(
                    )
                    multisig_confirmation.signature_type = safe_signature.signature_type.value
                    multisig_confirmation.save(
                        update_fields=['signature', 'signature_type'])

            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
Exemplo n.º 18
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
        """
        internal_tx = internal_tx_decoded.internal_tx
        logger.debug(
            "Start processing InternalTxDecoded in tx-hash=%s",
            HexBytes(internal_tx_decoded.internal_tx.ethereum_tx_id).hex(),
        )

        if internal_tx.gas_used < 1000:
            # When calling a non existing function, fallback of the proxy does not return any error but we can detect
            # this kind of functions due to little gas used. Some of this transactions get decoded as they were
            # valid in old versions of the proxies, like changes to `setup`
            logger.debug(
                "Calling a non existing function, will not process it", )
            return False

        function_name = internal_tx_decoded.function_name
        arguments = internal_tx_decoded.arguments
        contract_address = internal_tx._from
        master_copy = internal_tx.to
        processed_successfully = True

        if function_name == "setup" and contract_address != NULL_ADDRESS:
            # Index new Safes
            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.block_number
                    safe_contract.save(
                        update_fields=["ethereum_tx", "erc20_block_number"])
            except SafeContract.DoesNotExist:
                blocks_one_day = int(24 * 60 * 60 / 15)  # 15 seconds block
                SafeContract.objects.create(
                    address=contract_address,
                    ethereum_tx=internal_tx.ethereum_tx,
                    erc20_block_number=max(
                        internal_tx.block_number - blocks_one_day, 0),
                )
                logger.info("Found new Safe=%s", contract_address)

            self.store_new_safe_status(
                SafeLastStatus(
                    internal_tx=internal_tx,
                    address=contract_address,
                    owners=owners,
                    threshold=threshold,
                    nonce=nonce,
                    master_copy=master_copy,
                    fallback_handler=fallback_handler,
                ),
                internal_tx,
            )
        else:
            safe_last_status = self.get_last_safe_status_for_address(
                contract_address)
            if not safe_last_status:
                # Usually this happens from Safes coming from a not supported Master Copy
                # TODO When archive node is available, build SafeStatus from blockchain status
                logger.debug(
                    "Cannot process trace as `SafeLastStatus` is not found for Safe=%s",
                    contract_address,
                )
                processed_successfully = False
            elif function_name in (
                    "addOwnerWithThreshold",
                    "removeOwner",
                    "removeOwnerWithThreshold",
            ):
                logger.debug("Processing owner/threshold modification")
                safe_last_status.threshold = (arguments["_threshold"]
                                              or safe_last_status.threshold
                                              )  # Event doesn't have threshold
                owner = arguments["owner"]
                if function_name == "addOwnerWithThreshold":
                    safe_last_status.owners.insert(0, owner)
                else:  # removeOwner, removeOwnerWithThreshold
                    self.swap_owner(internal_tx, safe_last_status, owner, None)
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name == "swapOwner":
                logger.debug("Processing owner swap")
                old_owner = arguments["oldOwner"]
                new_owner = arguments["newOwner"]
                self.swap_owner(internal_tx, safe_last_status, old_owner,
                                new_owner)
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name == "changeThreshold":
                logger.debug("Processing threshold change")
                safe_last_status.threshold = arguments["_threshold"]
                self.store_new_safe_status(safe_last_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
                old_safe_version = self.get_safe_version_from_master_copy(
                    safe_last_status.master_copy)
                safe_last_status.master_copy = arguments["_masterCopy"]
                new_safe_version = self.get_safe_version_from_master_copy(
                    safe_last_status.master_copy)
                if (old_safe_version and new_safe_version
                        and self.is_version_breaking_signatures(
                            old_safe_version, new_safe_version)):
                    # Transactions queued not executed are not valid anymore
                    MultisigTransaction.objects.queued(
                        contract_address).delete()
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name == "setFallbackHandler":
                logger.debug("Setting FallbackHandler")
                safe_last_status.fallback_handler = arguments["handler"]
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name == "setGuard":
                safe_last_status.guard = (arguments["guard"]
                                          if arguments["guard"] != NULL_ADDRESS
                                          else None)
                if safe_last_status.guard:
                    logger.debug("Setting Guard")
                else:
                    logger.debug("Unsetting Guard")
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name == "enableModule":
                logger.debug("Enabling Module")
                safe_last_status.enabled_modules.append(arguments["module"])
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name == "disableModule":
                logger.debug("Disabling Module")
                safe_last_status.enabled_modules.remove(arguments["module"])
                self.store_new_safe_status(safe_last_status, internal_tx)
            elif function_name in {
                    "execTransactionFromModule",
                    "execTransactionFromModuleReturnData",
            }:
                logger.debug("Executing Tx from Module")
                # TODO Add test with previous traces for processing a module transaction
                ethereum_tx = internal_tx.ethereum_tx
                if "module" in arguments:
                    # L2 Safe with event SafeModuleTransaction indexed using events
                    module_address = arguments["module"]
                else:
                    # Regular Safe indexed using tracing
                    # Someone calls Module -> Module calls Safe Proxy -> Safe Proxy delegate calls Master Copy
                    # The trace that is been processed is the last one, so indexer needs to get the previous trace
                    previous_trace = self.ethereum_client.parity.get_previous_trace(
                        internal_tx.ethereum_tx_id,
                        internal_tx.trace_address_as_list,
                        skip_delegate_calls=True,
                    )
                    if not previous_trace:
                        message = (
                            f"Cannot find previous trace for tx-hash={HexBytes(internal_tx.ethereum_tx_id).hex()} "
                            f"and trace-address={internal_tx.trace_address}")
                        logger.warning(message)
                        raise ValueError(message)
                    module_internal_tx = InternalTx.objects.build_from_trace(
                        previous_trace, internal_tx.ethereum_tx)
                    module_address = (module_internal_tx._from
                                      if module_internal_tx else NULL_ADDRESS)
                failed = self.is_module_failed(ethereum_tx, module_address,
                                               contract_address)
                module_data = HexBytes(arguments["data"])
                ModuleTransaction.objects.get_or_create(
                    internal_tx=internal_tx,
                    defaults={
                        "created": internal_tx.timestamp,
                        "safe": contract_address,
                        "module": module_address,
                        "to": arguments["to"],
                        "value": arguments["value"],
                        "data": module_data if module_data else None,
                        "operation": arguments["operation"],
                        "failed": failed,
                    },
                )

            elif function_name == "approveHash":
                logger.debug("Processing hash approval")
                multisig_transaction_hash = arguments["hashToApprove"]
                ethereum_tx = internal_tx.ethereum_tx
                if "owner" in arguments:  # Event approveHash
                    owner = arguments["owner"]
                else:
                    previous_trace = self.ethereum_client.parity.get_previous_trace(
                        internal_tx.ethereum_tx_id,
                        internal_tx.trace_address_as_list,
                        skip_delegate_calls=True,
                    )
                    if not previous_trace:
                        message = (
                            f"Cannot find previous trace for tx-hash={HexBytes(internal_tx.ethereum_tx_id).hex()} and "
                            f"trace-address={internal_tx.trace_address}")
                        logger.warning(message)
                        raise ValueError(message)
                    previous_internal_tx = InternalTx.objects.build_from_trace(
                        previous_trace, internal_tx.ethereum_tx)
                    owner = previous_internal_tx._from
                safe_signature = SafeSignatureApprovedHash.build_for_owner(
                    owner, multisig_transaction_hash)
                (multisig_confirmation,
                 _) = MultisigConfirmation.objects.get_or_create(
                     multisig_transaction_hash=multisig_transaction_hash,
                     owner=owner,
                     defaults={
                         "created": internal_tx.timestamp,
                         "ethereum_tx": ethereum_tx,
                         "signature": safe_signature.export_signature(),
                         "signature_type": safe_signature.signature_type.value,
                     },
                 )
                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")
                # Events for L2 Safes store information about nonce
                nonce = (arguments["nonce"]
                         if "nonce" in arguments else safe_last_status.nonce)
                if ("baseGas" in arguments
                    ):  # `dataGas` was renamed to `baseGas` in v1.0.0
                    base_gas = arguments["baseGas"]
                    safe_version = (self.get_safe_version_from_master_copy(
                        safe_last_status.master_copy) or "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,
                    chain_id=self.ethereum_client.get_chain_id(),
                )
                safe_tx_hash = safe_tx.safe_tx_hash

                ethereum_tx = internal_tx.ethereum_tx

                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.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,
                        "trusted": True,
                    },
                )

                # Don't modify created
                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.trusted = True
                    multisig_tx.save(update_fields=[
                        "ethereum_tx", "failed", "signatures", "trusted"
                    ])

                for safe_signature in SafeSignature.parse_signature(
                        safe_tx.signatures, safe_tx_hash):
                    (
                        multisig_confirmation,
                        _,
                    ) = MultisigConfirmation.objects.get_or_create(
                        multisig_transaction_hash=safe_tx_hash,
                        owner=safe_signature.owner,
                        defaults={
                            "created": internal_tx.timestamp,
                            "ethereum_tx": None,
                            "multisig_transaction": multisig_tx,
                            "signature": safe_signature.export_signature(),
                            "signature_type":
                            safe_signature.signature_type.value,
                        },
                    )
                    if multisig_confirmation.signature != safe_signature.signature:
                        multisig_confirmation.signature = (
                            safe_signature.export_signature())
                        multisig_confirmation.signature_type = (
                            safe_signature.signature_type.value)
                        multisig_confirmation.save(
                            update_fields=["signature", "signature_type"])

                safe_last_status.nonce = nonce + 1
                self.store_new_safe_status(safe_last_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.warning(
                    "Cannot process InternalTxDecoded function_name=%s and arguments=%s",
                    function_name,
                    arguments,
                )
        logger.debug("End processing")
        return processed_successfully
Exemplo n.º 19
0
    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data["safe"], ethereum_client)
        try:
            safe_version = safe.retrieve_version()
        except BadFunctionCallOutput as e:
            raise ValidationError(
                f"Could not get Safe version from blockchain, check contract exists on network "
                f"{ethereum_client.get_network().name}") from e
        except IOError:
            raise ValidationError(
                "Problem connecting to the ethereum node, please try again later"
            )

        safe_tx = safe.build_multisig_tx(
            data["to"],
            data["value"],
            data["data"],
            data["operation"],
            data["safe_tx_gas"],
            data["base_gas"],
            data["gas_price"],
            data["gas_token"],
            data["refund_receiver"],
            safe_nonce=data["nonce"],
            safe_version=safe_version,
        )
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data["contract_transaction_hash"]:
            raise ValidationError(
                f"Contract-transaction-hash={contract_transaction_hash.hex()} "
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` or same `safeTxHash` for the same Safe.
        # We allow duplicated if existing tx is not executed
        multisig_transactions = MultisigTransaction.objects.filter(
            safe=safe.address, nonce=data["nonce"]).executed()
        if multisig_transactions:
            for multisig_transaction in multisig_transactions:
                if multisig_transaction.safe_tx_hash == contract_transaction_hash.hex(
                ):
                    raise ValidationError(
                        f"Tx with safe-tx-hash={contract_transaction_hash.hex()} "
                        f"for safe={safe.address} was already executed in "
                        f"tx-hash={multisig_transaction.ethereum_tx_id}")

            raise ValidationError(
                f"Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} "
                f"already executed in tx-hash={multisig_transactions[0].ethereum_tx_id}"
            )

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier="pending")
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier="latest")
        except IOError:
            raise ValidationError(
                "Problem connecting to the ethereum node, please try again later"
            )

        data["safe_owners"] = safe_owners

        delegates = SafeContractDelegate.objects.get_delegates_for_safe_and_owners(
            safe.address, safe_owners)
        allowed_senders = set(safe_owners) | delegates
        if not data["sender"] in allowed_senders:
            raise ValidationError(
                f'Sender={data["sender"]} is not an owner or delegate. '
                f"Current owners={safe_owners}. Delegates={delegates}")

        signature_owners = []
        # TODO Make signature mandatory
        signature = data.get("signature", b"")
        parsed_signatures = SafeSignature.parse_signature(
            signature, contract_transaction_hash)
        data["parsed_signatures"] = parsed_signatures
        # If there's at least one signature, transaction is trusted (until signatures are mandatory)
        data["trusted"] = bool(parsed_signatures)
        for safe_signature in parsed_signatures:
            owner = safe_signature.owner
            if not safe_signature.is_valid(ethereum_client, safe.address):
                raise ValidationError(
                    f"Signature={safe_signature.signature.hex()} for owner={owner} is not valid"
                )

            if owner in delegates and len(parsed_signatures) > 1:
                raise ValidationError(
                    "Just one signature is expected if using delegates")
            if owner not in allowed_senders:
                raise ValidationError(
                    f"Signer={owner} is not an owner or delegate. "
                    f"Current owners={safe_owners}. Delegates={delegates}")
            if owner in signature_owners:
                raise ValidationError(
                    f"Signature for owner={owner} is duplicated")

            signature_owners.append(owner)

        # TODO Make signature mandatory. len(signature_owners) must be >= 1
        if signature_owners and data["sender"] not in signature_owners:
            raise ValidationError(
                f'Signature does not match sender={data["sender"]}. '
                f"Calculated owners={signature_owners}")

        return data