Ejemplo n.º 1
0
class InternalTxSerializer(serializers.ModelSerializer):
    class Meta:
        model = InternalTx
        exclude = ('id', )

    _from = EthereumAddressField(allow_null=False,
                                 allow_zero_address=True,
                                 source='_from')
    to = EthereumAddressField(allow_null=True, allow_zero_address=True)
    contract_address = EthereumAddressField(allow_null=True,
                                            allow_zero_address=True)
    data = HexadecimalField()
    code = HexadecimalField()
    output = HexadecimalField()
    tx_type = serializers.SerializerMethodField()
    call_type = serializers.SerializerMethodField()
    ethereum_tx = None

    def get_fields(self):
        result = super().get_fields()
        # Rename `_from` to `from`
        _from = result.pop('_from')
        result['from'] = _from
        return result

    def get_tx_type(self, obj) -> str:
        return EthereumTxType(obj.tx_type).name

    def get_call_type(self, obj) -> str:
        if obj.call_type is None:
            return None
        else:
            return EthereumTxCallType(obj.call_type).name
Ejemplo n.º 2
0
class EthereumTxWithTransfersResponseSerializer(serializers.Serializer):
    class Meta:
        model = EthereumTx
        exclude = ("block", )

    execution_date = serializers.DateTimeField()
    _from = EthereumAddressField(allow_null=False,
                                 allow_zero_address=True,
                                 source="_from")
    to = EthereumAddressField(allow_null=True, allow_zero_address=True)
    data = HexadecimalField()
    tx_hash = HexadecimalField()
    block_number = serializers.SerializerMethodField()
    transfers = TransferWithTokenInfoResponseSerializer(many=True)
    tx_type = serializers.SerializerMethodField()

    def get_tx_type(self, obj) -> str:
        return TxType.ETHEREUM_TRANSACTION.name

    def get_fields(self):
        result = super().get_fields()
        # Rename `_from` to `from`
        _from = result.pop("_from")
        result["from"] = _from
        return result

    def get_block_number(self, obj: EthereumTx) -> Optional[int]:
        if obj.block_id:
            return obj.block_id
Ejemplo n.º 3
0
class EthereumTxSerializer(serializers.ModelSerializer):
    class Meta:
        model = EthereumTx
        exclude = ('block', )

    _from = EthereumAddressField(allow_null=False,
                                 allow_zero_address=True,
                                 source='_from')
    to = EthereumAddressField(allow_null=True, allow_zero_address=True)
    data = HexadecimalField()
    tx_hash = HexadecimalField()
    block_number = serializers.SerializerMethodField()
    block_timestamp = serializers.SerializerMethodField()

    def get_fields(self):
        result = super().get_fields()
        # Rename `_from` to `from`
        _from = result.pop('_from')
        result['from'] = _from
        return result

    def get_block_number(self, obj: EthereumTx):
        if obj.block:
            return obj.block.number

    def get_block_timestamp(self, obj: EthereumTx):
        if obj.block:
            return obj.block.timestamp
class EthereumTxWithTransfersResponseSerializer(serializers.Serializer):
    class Meta:
        model = EthereumTx
        exclude = ('block', )

    _from = EthereumAddressField(allow_null=False,
                                 allow_zero_address=True,
                                 source='_from')
    to = EthereumAddressField(allow_null=True, allow_zero_address=True)
    data = HexadecimalField()
    tx_hash = HexadecimalField()
    block_number = serializers.SerializerMethodField()
    block_timestamp = serializers.SerializerMethodField()
    transfers = TransferResponseSerializer(many=True)
    tx_type = serializers.SerializerMethodField()

    def get_tx_type(self, obj):
        return TxType.ETHEREUM_TRANSACTION.name

    def get_fields(self):
        result = super().get_fields()
        # Rename `_from` to `from`
        _from = result.pop('_from')
        result['from'] = _from
        return result

    def get_block_number(self, obj: EthereumTx):
        if obj.block:
            return obj.block.number

    def get_block_timestamp(self, obj: EthereumTx):
        if obj.block:
            return obj.block.timestamp
Ejemplo n.º 5
0
class SafeMultisigEstimateTxSerializer(serializers.Serializer):
    safe = EthereumAddressField()
    to = EthereumAddressField()
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField(default=None, allow_null=True, allow_blank=True)
    operation = serializers.IntegerField(min_value=0)
    gas_token = EthereumAddressField(
        default=None, allow_null=True, allow_zero_address=True
    )

    def validate_operation(self, value):
        try:
            return SafeOperation(value).value
        except ValueError:
            raise ValidationError("Unknown operation")

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

        if not data["to"] and not data["data"]:
            raise ValidationError("`data` and `to` cannot both be null")

        if not data["to"] and not data["data"]:
            raise ValidationError("`data` and `to` cannot both be null")

        if data["operation"] == SafeOperation.CREATE.value:
            raise ValidationError(
                "Operation CREATE not supported. Please use Gnosis Safe CreateLib"
            )
            #  if data['to']:
            #      raise ValidationError('Operation is Create, but `to` was provided')
            #  elif not data['data']:
            #      raise ValidationError('Operation is Create, but not `data` was provided')

        return data
class SafeModuleTransactionResponseSerializer(serializers.ModelSerializer):
    execution_date = serializers.DateTimeField()
    data = HexadecimalField(allow_null=True, allow_blank=True)
    data_decoded = serializers.SerializerMethodField()
    transaction_hash = serializers.SerializerMethodField()
    block_number = serializers.SerializerMethodField()
    is_successful = serializers.SerializerMethodField()

    class Meta:
        model = ModuleTransaction
        fields = ('created', 'execution_date', 'block_number', 'is_successful',
                  'transaction_hash', 'safe', 'module', 'to', 'value', 'data',
                  'operation', 'data_decoded')

    def get_block_number(self, obj: ModuleTransaction) -> Optional[int]:
        return obj.internal_tx.ethereum_tx.block_id

    def get_data_decoded(self, obj: SafeCreationInfo) -> Dict[str, Any]:
        return get_data_decoded_from_data(
            obj.data.tobytes() if obj.data else b'')

    def get_is_successful(self, obj: ModuleTransaction) -> bool:
        return not obj.failed

    def get_transaction_hash(self, obj: ModuleTransaction) -> str:
        return obj.internal_tx.ethereum_tx_id
Ejemplo n.º 7
0
class DelegateDeleteSerializer(DelegateSignatureCheckerMixin,
                               serializers.Serializer):
    delegate = EthereumAddressField()
    delegator = EthereumAddressField()
    signature = HexadecimalField(min_length=65)

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

        signature = data["signature"]
        delegate = data["delegate"]  # Delegate address to be added/removed
        delegator = data["delegator"]  # Delegator

        ethereum_client = EthereumClientProvider()
        # Tries to find a valid delegator using multiple strategies
        for operation_hash in DelegateSignatureHelper.calculate_all_possible_hashes(
                delegate):
            for signer in (delegate, delegator):
                if self.check_delegate_signature(ethereum_client, signature,
                                                 operation_hash, signer):
                    return data

        raise ValidationError(
            f"Signature does not match provided delegate={delegate} or delegator={delegator}"
        )
class SafeCreationInfoResponseSerializer(serializers.Serializer):
    created = serializers.DateTimeField()
    creator = EthereumAddressField()
    factory_address = EthereumAddressField()
    master_copy = EthereumAddressField(allow_null=True)
    setup_data = HexadecimalField(allow_null=True)
    transaction_hash = Sha3HashField()
Ejemplo n.º 9
0
class SafeMultisigTxResponseSerializer(serializers.Serializer):
    to = EthereumAddressField(allow_null=True, allow_zero_address=True)
    ethereum_tx = EthereumTxSerializer()
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField()
    timestamp = serializers.DateTimeField(source='created')
    operation = serializers.SerializerMethodField()
    safe_tx_gas = serializers.IntegerField(min_value=0)
    data_gas = serializers.IntegerField(min_value=0)
    gas_price = serializers.IntegerField(min_value=0)
    gas_token = EthereumAddressField(allow_null=True, allow_zero_address=True)
    refund_receiver = EthereumAddressField(allow_null=True,
                                           allow_zero_address=True)
    nonce = serializers.IntegerField(min_value=0)
    safe_tx_hash = Sha3HashField()
    tx_hash = serializers.SerializerMethodField()
    transaction_hash = serializers.SerializerMethodField(
        method_name='get_tx_hash')  # Retro compatibility

    def get_operation(self, obj):
        """
        Filters confirmations queryset
        :param obj: MultisigConfirmation instance
        :return: serialized queryset
        """
        return SafeOperation(obj.operation).name

    def get_tx_hash(self, obj):
        tx_hash = obj.ethereum_tx.tx_hash
        if tx_hash and isinstance(tx_hash, bytes):
            return tx_hash.hex()
        return tx_hash
Ejemplo n.º 10
0
class SafeMultisigTransactionEstimateSerializer(serializers.Serializer):
    to = EthereumAddressField()
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField(default=None, allow_null=True, allow_blank=True)
    operation = serializers.IntegerField(min_value=0)

    def save(self, **kwargs):
        safe_address = self.context["safe_address"]
        ethereum_client = EthereumClientProvider()
        safe = Safe(safe_address, ethereum_client)
        exc = None
        # Retry thrice to get an estimation
        for _ in range(3):
            try:
                safe_tx_gas = safe.estimate_tx_gas(
                    self.validated_data["to"],
                    self.validated_data["value"],
                    self.validated_data["data"],
                    self.validated_data["operation"],
                )
                return {"safe_tx_gas": safe_tx_gas}
            except (IOError, ValueError) as _exc:
                exc = _exc
        raise NodeConnectionException(
            f"Node connection error when estimating gas for Safe {safe_address}"
        ) from exc
class SafeDelegateDeleteSerializer(serializers.Serializer):
    safe = EthereumAddressField()
    delegate = EthereumAddressField()
    signature = HexadecimalField(min_length=130)

    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 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']  # Delegate address to be added

        # Tries to find a valid delegator using multiple strategies
        for operation_hash in (DelegateSignatureHelper.calculate_hash(delegate),
                               DelegateSignatureHelper.calculate_hash(delegate, eth_sign=True),
                               DelegateSignatureHelper.calculate_hash(delegate, previous_topt=True),
                               DelegateSignatureHelper.calculate_hash(delegate, eth_sign=True, previous_topt=True)):
            delegator = self.check_signature(signature, operation_hash, safe_owners)
            if delegator:
                break

        if not delegator:
            raise ValidationError('Signing owner is not an owner of the Safe')

        data['delegator'] = delegator
        return data
class SafeMultisigTransactionResponseSerializer(SafeMultisigTxSerializerV1):
    execution_date = serializers.DateTimeField()
    submission_date = serializers.DateTimeField(source='created')  # First seen by this service
    modified = serializers.DateTimeField()
    block_number = serializers.SerializerMethodField()
    transaction_hash = Sha3HashField(source='ethereum_tx_id')
    safe_tx_hash = Sha3HashField()
    executor = serializers.SerializerMethodField()
    value = serializers.CharField()
    is_executed = serializers.BooleanField(source='executed')
    is_successful = serializers.SerializerMethodField()
    gas_price = serializers.CharField()
    eth_gas_price = serializers.SerializerMethodField()
    gas_used = serializers.SerializerMethodField()
    fee = serializers.SerializerMethodField()
    origin = serializers.CharField()
    data_decoded = serializers.SerializerMethodField()
    confirmations_required = serializers.IntegerField()
    confirmations = serializers.SerializerMethodField()
    signatures = HexadecimalField()

    def get_block_number(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx.block_id

    def get_confirmations(self, obj: MultisigTransaction) -> Dict[str, Any]:
        """
        Filters confirmations queryset
        :param obj: MultisigConfirmation instance
        :return: Serialized queryset
        """
        return SafeMultisigConfirmationResponseSerializer(obj.confirmations, many=True).data

    def get_executor(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx._from

    def get_fee(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx:
            if obj.ethereum_tx.gas_used and obj.ethereum_tx.gas_price:
                return str(obj.ethereum_tx.gas_used * obj.ethereum_tx.gas_price)

    def get_eth_gas_price(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_price:
            return str(obj.ethereum_tx.gas_price)

    def get_gas_used(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_used:
            return obj.ethereum_tx.gas_used

    def get_is_successful(self, obj: MultisigTransaction) -> Optional[bool]:
        if obj.failed is None:
            return None
        else:
            return not obj.failed

    def get_data_decoded(self, obj: MultisigTransaction) -> Dict[str, Any]:
        return get_data_decoded_from_data(obj.data.tobytes() if obj.data else b'')
Ejemplo n.º 13
0
class SafeMultisigConfirmationSerializer(serializers.Serializer):
    signature = HexadecimalField(min_length=65)  # Signatures must be at least 65 bytes

    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 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
Ejemplo n.º 14
0
class SafeCreation2ResponseSerializer(serializers.Serializer):
    safe = EthereumAddressField()
    master_copy = EthereumAddressField()
    proxy_factory = EthereumAddressField()
    payment_token = EthereumAddressField(allow_zero_address=True)
    payment = serializers.CharField()
    payment_receiver = EthereumAddressField(allow_zero_address=True)
    setup_data = HexadecimalField()
    gas_estimated = serializers.CharField()
    gas_price_estimated = serializers.CharField()
class SafeDelegateDeleteSerializer(serializers.Serializer):
    safe = EthereumAddressField()
    delegate = EthereumAddressField()
    signature = HexadecimalField(min_length=130)

    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
class SafeCreationInfoResponseSerializer(serializers.Serializer):
    created = serializers.DateTimeField()
    creator = EthereumAddressField()
    transaction_hash = Sha3HashField()
    factory_address = EthereumAddressField()
    master_copy = EthereumAddressField(allow_null=True)
    setup_data = HexadecimalField(allow_null=True)
    data_decoded = serializers.SerializerMethodField()

    def get_data_decoded(self, obj: SafeCreationInfo) -> Dict[str, Any]:
        return get_data_decoded_from_data(obj.setup_data or b'')
Ejemplo n.º 17
0
class SafeMultisigTransactionEstimateSerializer(serializers.Serializer):
    to = EthereumAddressField()
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField(default=None, allow_null=True, allow_blank=True)
    operation = serializers.IntegerField(min_value=0)

    def save(self, **kwargs):
        safe_address = self.context['safe_address']
        ethereum_client = EthereumClientProvider()
        safe = Safe(safe_address, ethereum_client)
        safe_tx_gas = safe.estimate_tx_gas(self.validated_data['to'], self.validated_data['value'],
                                           self.validated_data['data'], self.validated_data['operation'])
        return {'safe_tx_gas': safe_tx_gas}
class SafeModuleTransactionResponseSerializer(serializers.ModelSerializer):
    data = HexadecimalField(allow_null=True, allow_blank=True)
    transaction_hash = serializers.SerializerMethodField()
    block_number = serializers.SerializerMethodField()

    class Meta:
        model = ModuleTransaction
        fields = ('created', 'block_number', 'transaction_hash', 'safe',
                  'module', 'to', 'value', 'data', 'operation')

    def get_block_number(self, obj: ModuleTransaction) -> Optional[int]:
        return obj.internal_tx.ethereum_tx.block_id

    def get_transaction_hash(self, obj: ModuleTransaction) -> str:
        return obj.internal_tx.ethereum_tx_id
class SafeMultisigConfirmationResponseSerializer(serializers.ModelSerializer):
    submission_date = serializers.DateTimeField(source='created')
    confirmation_type = serializers.SerializerMethodField()
    transaction_hash = serializers.SerializerMethodField()
    signature = HexadecimalField()

    class Meta:
        model = MultisigConfirmation
        fields = ('owner', 'submission_date', 'transaction_hash',
                  'confirmation_type', 'signature')

    def get_confirmation_type(self, obj: MultisigConfirmation):
        #TODO Remove this field
        return ConfirmationType.CONFIRMATION.name

    def get_transaction_hash(self, obj: MultisigConfirmation):
        return obj.ethereum_tx_id
class SafeMultisigTransactionEstimateSerializer(serializers.Serializer):
    to = EthereumAddressField()
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField(default=None, allow_null=True, allow_blank=True)
    operation = serializers.IntegerField(min_value=0)

    def save(self, **kwargs):
        safe_address = self.context['safe_address']
        ethereum_client = EthereumClientProvider()
        safe = Safe(safe_address, ethereum_client)
        try:
            safe_tx_gas = safe.estimate_tx_gas(
                self.validated_data['to'], self.validated_data['value'],
                self.validated_data['data'], self.validated_data['operation'])
        except IOError as exc:
            raise NodeConnectionError(
                f'Node connection error when estimating gas for safe {safe_address}'
            ) from exc
        return {'safe_tx_gas': safe_tx_gas}
Ejemplo n.º 21
0
class SafeMultisigConfirmationResponseSerializer(GnosisBaseModelSerializer):
    submission_date = serializers.DateTimeField(source="created")
    transaction_hash = serializers.SerializerMethodField()
    signature = HexadecimalField()
    signature_type = serializers.SerializerMethodField()

    class Meta:
        model = MultisigConfirmation
        fields = (
            "owner",
            "submission_date",
            "transaction_hash",
            "signature",
            "signature_type",
        )

    def get_transaction_hash(self, obj: MultisigConfirmation) -> str:
        return obj.ethereum_tx_id

    def get_signature_type(self, obj: MultisigConfirmation) -> str:
        return SafeSignatureType(obj.signature_type).name
Ejemplo n.º 22
0
class SafeModuleTransactionResponseSerializer(GnosisBaseModelSerializer):
    execution_date = serializers.DateTimeField()
    data = HexadecimalField(allow_null=True, allow_blank=True)
    data_decoded = serializers.SerializerMethodField()
    transaction_hash = serializers.SerializerMethodField()
    block_number = serializers.SerializerMethodField()
    is_successful = serializers.SerializerMethodField()

    class Meta:
        model = ModuleTransaction
        fields = (
            "created",
            "execution_date",
            "block_number",
            "is_successful",
            "transaction_hash",
            "safe",
            "module",
            "to",
            "value",
            "data",
            "operation",
            "data_decoded",
        )

    def get_block_number(self, obj: ModuleTransaction) -> Optional[int]:
        return obj.internal_tx.block_number

    def get_data_decoded(self, obj: ModuleTransaction) -> Dict[str, Any]:
        return get_data_decoded_from_data(
            obj.data.tobytes() if obj.data else b"", address=obj.to)

    def get_is_successful(self, obj: ModuleTransaction) -> bool:
        return not obj.failed

    def get_transaction_hash(self, obj: ModuleTransaction) -> str:
        return obj.internal_tx.ethereum_tx_id
Ejemplo n.º 23
0
class SafeMultisigEstimateTxSerializer(serializers.Serializer):
    safe = EthereumAddressField()
    to = EthereumAddressField(default=None, allow_null=True)
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField(default=None, allow_null=True, allow_blank=True)
    operation = serializers.IntegerField(min_value=0)
    gas_token = EthereumAddressField(default=None,
                                     allow_null=True,
                                     allow_zero_address=True)

    def validate_operation(self, value):
        try:
            return SafeOperation(value).value
        except ValueError:
            raise ValidationError('Unknown operation')

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

        if not data['to'] and not data['data']:
            raise ValidationError('`data` and `to` cannot both be null')

        if not data['to'] and not data['data']:
            raise ValidationError('`data` and `to` cannot both be null')

        if data['operation'] == SafeOperation.CREATE.value:
            if data['to']:
                raise ValidationError(
                    'Operation is Create, but `to` was provided')
            elif not data['data']:
                raise ValidationError(
                    'Operation is Create, but not `data` was provided')
        elif not data['to']:
            raise ValidationError(
                'Operation is not Create, but `to` was not provided')

        return data
Ejemplo n.º 24
0
class SafeMultisigTransactionResponseSerializer(SafeMultisigTxSerializerV1):
    execution_date = serializers.DateTimeField()
    submission_date = serializers.DateTimeField(
        source="created")  # First seen by this service
    modified = serializers.DateTimeField()
    block_number = serializers.SerializerMethodField()
    transaction_hash = Sha3HashField(source="ethereum_tx_id")
    safe_tx_hash = Sha3HashField()
    executor = serializers.SerializerMethodField()
    value = serializers.CharField()
    is_executed = serializers.BooleanField(source="executed")
    is_successful = serializers.SerializerMethodField()
    gas_price = serializers.CharField()
    eth_gas_price = serializers.SerializerMethodField()
    max_fee_per_gas = serializers.SerializerMethodField()
    max_priority_fee_per_gas = serializers.SerializerMethodField()
    gas_used = serializers.SerializerMethodField()
    fee = serializers.SerializerMethodField()
    origin = serializers.CharField()
    data_decoded = serializers.SerializerMethodField()
    confirmations_required = serializers.IntegerField()
    confirmations = serializers.SerializerMethodField()
    trusted = serializers.BooleanField()
    signatures = HexadecimalField(allow_null=True, required=False)

    def get_block_number(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx.block_id

    @swagger_serializer_method(
        serializer_or_field=SafeMultisigConfirmationResponseSerializer)
    def get_confirmations(self, obj: MultisigTransaction) -> Dict[str, Any]:
        """
        Filters confirmations queryset
        :param obj: MultisigConfirmation instance
        :return: Serialized queryset
        """
        return SafeMultisigConfirmationResponseSerializer(obj.confirmations,
                                                          many=True).data

    def get_executor(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx._from

    def get_fee(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx:
            if obj.ethereum_tx.gas_used and obj.ethereum_tx.gas_price:
                return str(obj.ethereum_tx.gas_used *
                           obj.ethereum_tx.gas_price)

    def get_eth_gas_price(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_price:
            return str(obj.ethereum_tx.gas_price)

    def get_max_fee_per_gas(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.max_fee_per_gas:
            return str(obj.ethereum_tx.max_fee_per_gas)

    def get_max_priority_fee_per_gas(
            self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.max_priority_fee_per_gas:
            return str(obj.ethereum_tx.max_priority_fee_per_gas)

    def get_gas_used(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_used:
            return obj.ethereum_tx.gas_used

    def get_is_successful(self, obj: MultisigTransaction) -> Optional[bool]:
        if obj.failed is None:
            return None
        else:
            return not obj.failed

    def get_data_decoded(self, obj: MultisigTransaction) -> Dict[str, Any]:
        # If delegate call contract must be whitelisted (security)
        if obj.data_should_be_decoded():
            return get_data_decoded_from_data(
                obj.data.tobytes() if obj.data else b"", address=obj.to)
Ejemplo n.º 25
0
class SafeDelegateDeleteSerializer(serializers.Serializer):
    """
    Deprecated in favour of DelegateDeleteSerializer
    """

    safe = EthereumAddressField()
    delegate = EthereumAddressField()
    signature = HexadecimalField(min_length=65)

    def get_safe_owners(
            self, ethereum_client: EthereumClient,
            safe_address: ChecksumAddress) -> List[ChecksumAddress]:
        safe = Safe(safe_address, ethereum_client)
        try:
            return safe.retrieve_owners(block_identifier="pending")
        except BadFunctionCallOutput:  # Error using pending block identifier
            return safe.retrieve_owners(block_identifier="latest")

    def get_valid_delegators(
        self,
        ethereum_client: EthereumClient,
        safe_address: ChecksumAddress,
        delegate: ChecksumAddress,
    ) -> List[ChecksumAddress]:
        """
        :param ethereum_client:
        :param safe_address:
        :param delegate:
        :return: Valid delegators for a Safe. A delegate should be able to remove itself
        """
        return self.get_safe_owners(ethereum_client, safe_address) + [delegate]

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

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

        signature = data["signature"]
        delegate = data["delegate"]  # Delegate address to be added/removed

        ethereum_client = EthereumClientProvider()
        valid_delegators = self.get_valid_delegators(ethereum_client,
                                                     safe_address, delegate)

        # Tries to find a valid delegator using multiple strategies
        for operation_hash in DelegateSignatureHelper.calculate_all_possible_hashes(
                delegate):
            delegator = self.check_signature(
                ethereum_client,
                safe_address,
                signature,
                operation_hash,
                valid_delegators,
            )
            if delegator:
                break

        if not delegator:
            raise ValidationError("Signing owner is not an owner of the Safe")

        data["delegator"] = delegator
        return data
class SafeMultisigTransactionSerializer(SafeMultisigTxSerializerV1):
    contract_transaction_hash = Sha3HashField()
    sender = EthereumAddressField()
    # TODO Make signature mandatory
    signature = HexadecimalField(
        required=False, min_length=130)  # Signatures must be at least 65 bytes
    origin = serializers.CharField(max_length=100,
                                   allow_null=True,
                                   default=None)

    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

    def save(self, **kwargs):
        safe_tx_hash = self.validated_data['contract_transaction_hash']
        multisig_transaction, _ = MultisigTransaction.objects.get_or_create(
            safe_tx_hash=safe_tx_hash,
            defaults={
                'safe':
                self.validated_data['safe'],
                'to':
                self.validated_data['to'],
                'value':
                self.validated_data['value'],
                'data':
                self.validated_data['data']
                if self.validated_data['data'] else None,
                'operation':
                self.validated_data['operation'],
                'safe_tx_gas':
                self.validated_data['safe_tx_gas'],
                'base_gas':
                self.validated_data['base_gas'],
                'gas_price':
                self.validated_data['gas_price'],
                'gas_token':
                self.validated_data['gas_token'],
                'refund_receiver':
                self.validated_data['refund_receiver'],
                'nonce':
                self.validated_data['nonce'],
                'origin':
                self.validated_data['origin'],
            })

        for safe_signature in self.validated_data.get('parsed_signatures'):
            owner = safe_signature.owner
            if safe_signature.owner in self.validated_data['safe_owners']:
                multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                    multisig_transaction_hash=safe_tx_hash,
                    owner=owner,
                    defaults={
                        'multisig_transaction': multisig_transaction,
                        'signature': safe_signature.export_signature(),
                        'signature_type': safe_signature.signature_type.value,
                    })
        return multisig_transaction
Ejemplo n.º 27
0
class FirebaseDeviceSerializer(serializers.Serializer):
    uuid = serializers.UUIDField(default=uuid4)  # TODO Make it required
    safes = serializers.ListField(allow_empty=False,
                                  child=EthereumAddressField())
    cloud_messaging_token = serializers.CharField(min_length=100,
                                                  max_length=200)
    build_number = serializers.IntegerField(min_value=0)  # e.g. 1644
    bundle = serializers.CharField(min_length=1, max_length=100)
    device_type = serializers.ChoiceField(
        choices=[element.name for element in DeviceTypeEnum])
    version = serializers.CharField(min_length=1,
                                    max_length=100)  # e.g. 1.0.0-beta
    timestamp = serializers.IntegerField(
        required=False)  # TODO Make it required
    signatures = serializers.ListField(
        required=False,
        child=HexadecimalField(required=False, min_length=65,
                               max_length=65)  # Signatures must be 65 bytes
    )

    def validate_safes(self, safes: Sequence[str]):
        if SafeContract.objects.filter(
                address__in=safes).count() != len(safes):
            raise serializers.ValidationError(
                'At least one Safe provided was not found or is duplicated')
        return safes

    def validate_timestamp(self, timestamp: int):
        """
        Validate if timestamp is not on a range within 5 minutes
        :param timestamp:
        :return:
        """
        if timestamp is not None:
            minutes_allowed = 5
            current_epoch = int(time.time())
            time_delta = abs(current_epoch - timestamp)
            if time_delta > (
                    60 * minutes_allowed):  # Timestamp older than 5 minutes
                raise ValidationError(
                    f'Provided timestamp is not in a range within {minutes_allowed} minutes'
                )
        return timestamp

    def validate_version(self, value: str):
        try:
            semantic_version.Version(value)
        except semantic_version.InvalidVersion:
            raise serializers.ValidationError('Semantic version was expected')
        return value

    def validate(self, data: Dict[str, Any]):
        data = super().validate(data)
        signature_owners = []
        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:
                        raise ValidationError(
                            f'Signature for owner={owner} is duplicated')
                    elif owner not in current_owners:
                        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):
                raise ValidationError(
                    'Number of signatures is less than the number of owners detected'
                )

        data['owners'] = signature_owners
        return data

    def save(self, **kwargs):
        try:
            uuid = self.validated_data['uuid']
            firebase_device, _ = FirebaseDevice.objects.update_or_create(
                uuid=uuid,
                defaults={
                    'cloud_messaging_token':
                    self.validated_data['cloud_messaging_token'],
                    'build_number':
                    self.validated_data['build_number'],
                    'bundle':
                    self.validated_data['bundle'],
                    'device_type':
                    DeviceTypeEnum[self.validated_data['device_type']].value,
                    'version':
                    self.validated_data['version'],
                })
        except IntegrityError as e:
            raise serializers.ValidationError(
                'Cloud messaging token is linked to another device')

        # Remove every owner registered for the device and add the provided ones
        firebase_device.owners.all().delete()
        for owner in self.validated_data['owners']:
            FirebaseDeviceOwner.objects.create(firebase_device=firebase_device,
                                               owner=owner)

        # Remove every Safe registered for the device and add the provided ones
        firebase_device.safes.clear()
        safe_contracts = SafeContract.objects.filter(
            address__in=self.validated_data['safes'])
        firebase_device.safes.add(*safe_contracts)
        return firebase_device
class SafeMultisigTransactionSerializer(SafeMultisigTxSerializerV1):
    contract_transaction_hash = Sha3HashField()
    sender = EthereumAddressField()
    signature = HexadecimalField(required=False)
    origin = serializers.CharField(max_length=100,
                                   allow_null=True,
                                   default=None)

    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

    def save(self, **kwargs):
        multisig_transaction, _ = MultisigTransaction.objects.get_or_create(
            safe_tx_hash=self.validated_data['contract_transaction_hash'],
            defaults={
                'safe':
                self.validated_data['safe'],
                'to':
                self.validated_data['to'],
                'value':
                self.validated_data['value'],
                'data':
                self.validated_data['data']
                if self.validated_data['data'] else None,
                'operation':
                self.validated_data['operation'],
                'safe_tx_gas':
                self.validated_data['safe_tx_gas'],
                'base_gas':
                self.validated_data['base_gas'],
                'gas_price':
                self.validated_data['gas_price'],
                'gas_token':
                self.validated_data['gas_token'],
                'refund_receiver':
                self.validated_data['refund_receiver'],
                'nonce':
                self.validated_data['nonce'],
                'origin':
                self.validated_data['origin'],
            })

        if self.validated_data.get('signature'):
            MultisigConfirmation.objects.get_or_create(
                multisig_transaction_hash=multisig_transaction.safe_tx_hash,
                owner=self.validated_data['sender'],
                defaults={
                    'multisig_transaction': multisig_transaction,
                    'signature': self.validated_data.get('signature'),
                })
        return multisig_transaction
Ejemplo n.º 29
0
class FirebaseDeviceSerializer(serializers.Serializer):
    uuid = serializers.UUIDField(default=uuid4)  # TODO Make it required
    safes = serializers.ListField(allow_empty=False,
                                  child=EthereumAddressField())
    cloud_messaging_token = serializers.CharField(min_length=100,
                                                  max_length=200)
    build_number = serializers.IntegerField(min_value=0)  # e.g. 1644
    bundle = serializers.CharField(min_length=1, max_length=100)
    device_type = serializers.ChoiceField(
        choices=[element.name for element in DeviceTypeEnum])
    version = serializers.CharField(min_length=1,
                                    max_length=100)  # e.g. 1.0.0-beta
    timestamp = serializers.IntegerField(
        required=False)  # TODO Make it required
    signatures = serializers.ListField(
        required=False,
        child=HexadecimalField(required=False, min_length=65,
                               max_length=65),  # Signatures must be 65 bytes
    )

    def validate_safes(
            self,
            safes: Sequence[ChecksumAddress]) -> Sequence[ChecksumAddress]:
        if SafeContract.objects.filter(
                address__in=safes).count() != len(safes):
            raise serializers.ValidationError(
                "At least one Safe provided was not found or is duplicated")
        return safes

    def validate_timestamp(self, timestamp: int) -> int:
        """
        Validate if timestamp is not on a range within 5 minutes
        :param timestamp:
        :return:
        """
        if timestamp is not None:
            minutes_allowed = 5
            current_epoch = int(time.time())
            time_delta = abs(current_epoch - timestamp)
            if time_delta > (
                    60 * minutes_allowed):  # Timestamp older than 5 minutes
                raise ValidationError(
                    f"Provided timestamp is not in a range within {minutes_allowed} minutes"
                )
        return timestamp

    def validate_version(self, value: str) -> str:
        try:
            semantic_version.Version(value)
        except semantic_version.InvalidVersion:
            raise serializers.ValidationError("Semantic version was expected")
        return value

    def get_valid_owners(
            self,
            safe_addresses: Sequence[ChecksumAddress]) -> Set[ChecksumAddress]:
        """
        Return safe owners and delegates

        :param safe_addresses:
        :return:
        """
        valid_owners = set()
        for safe_address in safe_addresses:
            owners = get_safe_owners(safe_address)
            delegates = SafeContractDelegate.objects.get_delegates_for_safe_and_owners(
                safe_address, owners)
            valid_owners = valid_owners.union(owners, delegates)

        return valid_owners

    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

    @transaction.atomic
    def save(self, **kwargs):
        try:
            uuid = self.validated_data["uuid"]
            firebase_device, _ = FirebaseDevice.objects.update_or_create(
                uuid=uuid,
                defaults={
                    "cloud_messaging_token":
                    self.validated_data["cloud_messaging_token"],
                    "build_number":
                    self.validated_data["build_number"],
                    "bundle":
                    self.validated_data["bundle"],
                    "device_type":
                    DeviceTypeEnum[self.validated_data["device_type"]].value,
                    "version":
                    self.validated_data["version"],
                },
            )
        except IntegrityError:
            raise serializers.ValidationError(
                "Cloud messaging token is linked to another device")

        # Remove every owner registered for the device and add the provided ones
        firebase_device.owners.all().delete()
        for owner in self.validated_data["owners_registered"]:
            try:
                FirebaseDeviceOwner.objects.create(
                    firebase_device=firebase_device, owner=owner)
            except IntegrityError:
                raise serializers.ValidationError(
                    f"Owner {owner} already created for firebase_device")

        # Remove every Safe registered for the device and add the provided ones
        firebase_device.safes.clear()
        safe_contracts = SafeContract.objects.filter(
            address__in=self.validated_data["safes"])
        firebase_device.safes.add(*safe_contracts)
        return firebase_device
class DataDecoderSerializer(serializers.Serializer):
    data = HexadecimalField(allow_null=False, allow_blank=False, min_length=4)