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
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
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
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
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()
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
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'')
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
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'')
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}
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
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
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
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)
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
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
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)