diff --git a/gnosis/eth/django/models.py b/gnosis/eth/django/models.py index 03229cb97..8ce88f810 100644 --- a/gnosis/eth/django/models.py +++ b/gnosis/eth/django/models.py @@ -24,9 +24,14 @@ connection = connections["default"] -class EthereumAddressField(models.CharField): +class EthereumAddressCharField(models.CharField): + """ + Stores Ethereum Addresses as Strings. Takes more space in database than `EthereumAddressBinaryField`, + but does not require the keccak256 calculation to calculate the EIP55 checksummed address. + """ + default_validators = [validate_checksumed_address] - description = "DEPRECATED. Use `EthereumAddressV2Field`. Ethereum address (EIP55)" + description = "Stores Ethereum Addresses (EIP55) as strings" default_error_messages = { "invalid": _('"%(value)s" value must be an EIP55 checksummed address.'), } @@ -62,9 +67,15 @@ def get_prep_value(self, value): return self.to_python(value) -class EthereumAddressV2Field(models.Field): +class EthereumAddressBinaryField(models.Field): + """ + Stores Ethereum Addresses in binary. Takes less space in Database than `EthereumAddressCharField`, + but does require a keccak256 calculation to calculate the EIP55 checksummed address, that it can take + a high impact on the CPU for a lot of addresses. + """ + default_validators = [validate_checksumed_address] - description = "Ethereum address (EIP55)" + description = "Stores Ethereum Addresses (EIP55) in binary" default_error_messages = { "invalid": _('"%(value)s" value must be an EIP55 checksummed address.'), } @@ -176,50 +187,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -class HexField(models.CharField): - """ - Field to store hex values (without 0x). Returns hex with 0x prefix. - - On Database side a CharField is used. - """ - - description = "Stores a hex value into a CharField. DEPRECATED, use a BinaryField" - - def from_db_value(self, value, expression, connection): - return self.to_python(value) - - def to_python(self, value): - return value if value is None else HexBytes(value).hex() - - def get_prep_value(self, value): - if value is None: - return value - elif isinstance(value, HexBytes): - return value.hex()[ - 2: - ] # HexBytes.hex() retrieves hexadecimal with '0x', remove it - elif isinstance(value, bytes): - return value.hex() # bytes.hex() retrieves hexadecimal without '0x' - else: # str - return HexBytes(value).hex()[2:] - - def formfield(self, **kwargs): - # We need max_lenght + 2 on forms because of `0x` - defaults = {"max_length": self.max_length + 2} - # TODO: Handle multiple backends with different feature flags. - if self.null and not connection.features.interprets_empty_strings_as_nulls: - defaults["empty_value"] = None - defaults.update(kwargs) - return super().formfield(**defaults) - - def clean(self, value, model_instance): - value = self.to_python(value) - self.validate(value, model_instance) - # Validation didn't work because of `0x` - self.run_validators(value[2:]) - return value - - class HexV2Field(models.BinaryField): def formfield(self, **kwargs): defaults = { @@ -229,19 +196,6 @@ def formfield(self, **kwargs): return super().formfield(**defaults) -class Sha3HashField(HexField): - description = "DEPRECATED. Use `Keccak256Field`" - - def __init__(self, *args, **kwargs): - kwargs["max_length"] = 64 - super().__init__(*args, **kwargs) - - def deconstruct(self): - name, path, args, kwargs = super().deconstruct() - del kwargs["max_length"] - return name, path, args, kwargs - - class Keccak256Field(models.BinaryField): description = "Keccak256 hash stored as binary" default_error_messages = { @@ -298,3 +252,47 @@ def formfield(self, **kwargs): } defaults.update(kwargs) return super().formfield(**defaults) + + +# --------- DEPRECATED, only for old migrations ---------------------- +class EthereumAddressField(models.CharField): + system_check_removed_details = { + "msg": ( + "EthereumAddressField has been removed except for support in " + "historical migrations." + ), + "hint": "Use EthereumAddressFastBinaryField instead.", + "id": "fields.E4815", # pick a unique ID for your field. + } + + +class EthereumAddressV2Field(EthereumAddressBinaryField): + system_check_removed_details = { + "msg": ( + "EthereumAddressField has been removed except for support in " + "historical migrations." + ), + "hint": "Use EthereumAddressFastBinaryField instead.", + "id": "fields.E4815", # pick a unique ID for your field. + } + + +class Sha3HashField(models.CharField): + system_check_removed_details = { + "msg": ( + "Sha3HashField has been removed except for support in " + "historical migrations." + ), + "hint": "Use Keccak256Field instead.", + "id": "fields.E4817", # pick a unique ID for your field. + } + + +class HexField(models.CharField): + system_check_removed_details = { + "msg": ( + "HexField has been removed except for support in " "historical migrations." + ), + "hint": "Use HexV2Field instead.", + "id": "fields.E4818", # pick a unique ID for your field. + } diff --git a/gnosis/eth/django/tests/models.py b/gnosis/eth/django/tests/models.py index 0b2497459..39834c0d4 100644 --- a/gnosis/eth/django/tests/models.py +++ b/gnosis/eth/django/tests/models.py @@ -1,10 +1,9 @@ from django.db import models from ..models import ( - EthereumAddressField, - EthereumAddressV2Field, + EthereumAddressBinaryField, + EthereumAddressCharField, Keccak256Field, - Sha3HashField, Uint32Field, Uint96Field, Uint256Field, @@ -12,11 +11,11 @@ class EthereumAddress(models.Model): - value = EthereumAddressField(null=True) + value = EthereumAddressCharField(null=True) class EthereumAddressV2(models.Model): - value = EthereumAddressV2Field(null=True) + value = EthereumAddressBinaryField(null=True) class Uint256(models.Model): @@ -31,9 +30,5 @@ class Uint32(models.Model): value = Uint32Field(null=True) -class Sha3Hash(models.Model): - value = Sha3HashField(null=True) - - class Keccak256Hash(models.Model): value = Keccak256Field(null=True) diff --git a/gnosis/eth/django/tests/test_models.py b/gnosis/eth/django/tests/test_models.py index b4046e3b4..7c94691c9 100644 --- a/gnosis/eth/django/tests/test_models.py +++ b/gnosis/eth/django/tests/test_models.py @@ -1,6 +1,6 @@ from django.core.exceptions import ValidationError from django.core.serializers import serialize -from django.db import DataError, transaction +from django.db import transaction from django.test import TestCase from eth_account import Account @@ -12,7 +12,6 @@ EthereumAddress, EthereumAddressV2, Keccak256Hash, - Sha3Hash, Uint32, Uint96, Uint256, @@ -115,35 +114,6 @@ def test_uint32_field(self): with self.assertRaises(ValidationError): Uint32.objects.create(value=-2) - def test_sha3_hash_field(self): - value_hexbytes = fast_keccak_text(faker.name()) - value_hex_with_0x: str = value_hexbytes.hex() - value_hex_without_0x: str = value_hex_with_0x[2:] - value: bytes = bytes(value_hexbytes) - - values = [value, value_hex_without_0x, value_hex_with_0x, value_hexbytes] - - for v in values: - sha3_hash = Sha3Hash.objects.create(value=v) - sha3_hash.refresh_from_db() - self.assertEqual(sha3_hash.value, value_hex_with_0x) - - for v in values: - self.assertEqual(Sha3Hash.objects.filter(value=v).count(), len(values)) - - # Hash null - sha3_hash = Sha3Hash.objects.create(value=None) - sha3_hash.refresh_from_db() - self.assertIsNone(sha3_hash.value) - - # Hash too big - value_hex_invalid: str = "0x" + value_hex_without_0x + "a" - with self.assertRaisesMessage( - DataError, "value too long for type character varying(64)" - ): - with transaction.atomic(): - Sha3Hash.objects.create(value=value_hex_invalid) - def test_keccak256_field(self): value_hexbytes = fast_keccak_text(faker.name()) value_hex_with_0x: str = value_hexbytes.hex() @@ -205,13 +175,6 @@ def test_serialize_keccak256_field_to_json(self): # hexvalue should be in serialized data self.assertIn(hexvalue, serialized) - def test_serialize_ethereum_address_field_to_json(self): - address: str = "0x5aFE3855358E112B5647B952709E6165e1c1eEEe" - EthereumAddress.objects.create(value=address) - serialized = serialize("json", EthereumAddress.objects.all()) - # address should be in serialized data - self.assertIn(address, serialized) - def test_serialize_ethereum_address_v2_field_to_json(self): address: str = "0x5aFE3855358E112B5647B952709E6165e1c1eEEe" EthereumAddressV2.objects.create(value=address) @@ -225,10 +188,3 @@ def test_serialize_uint256_field_to_json(self): serialized = serialize("json", Uint256.objects.all()) # value should be in serialized data self.assertIn(str(value), serialized) - - def test_serialize_sha3_hash_to_json(self): - hash = fast_keccak_text("testSerializer") - Sha3Hash.objects.create(value=hash) - serialized = serialize("json", Sha3Hash.objects.all()) - # hash should be in serialized data - self.assertIn(hash.hex(), serialized) diff --git a/gnosis/safe/serializers.py b/gnosis/safe/serializers.py index 984ecb597..df5e3a888 100644 --- a/gnosis/safe/serializers.py +++ b/gnosis/safe/serializers.py @@ -1,67 +1,11 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError -from gnosis.eth.constants import ( - SIGNATURE_R_MAX_VALUE, - SIGNATURE_R_MIN_VALUE, - SIGNATURE_S_MAX_VALUE, - SIGNATURE_S_MIN_VALUE, - SIGNATURE_V_MAX_VALUE, - SIGNATURE_V_MIN_VALUE, -) from gnosis.eth.django.serializers import EthereumAddressField, HexadecimalField from .enums import SafeOperationEnum -class SafeSignatureSerializer(serializers.Serializer): - """ - When using safe signatures `v` can have more values - """ - - v = serializers.IntegerField(min_value=0) - r = serializers.IntegerField(min_value=0) - s = serializers.IntegerField(min_value=0) - - def validate_v(self, v): - if v == 0: # Contract signature - return v - elif v == 1: # Approved hash - return v - elif v > 30 and self.check_v(v - 4): # Support eth_sign - return v - elif self.check_v(v): - return v - else: - raise serializers.ValidationError( - "v should be 0, 1 or be in %d-%d" - % (SIGNATURE_V_MIN_VALUE, SIGNATURE_V_MAX_VALUE) - ) - - def validate(self, data): - super().validate(data) - - v = data["v"] - r = data["r"] - s = data["s"] - - if v not in [0, 1]: # Disable checks for `r` and `s` if v is 0 or 1 - if not self.check_r(r): - raise serializers.ValidationError("r not valid") - elif not self.check_s(s): - raise serializers.ValidationError("s not valid") - return data - - def check_v(self, v): - return SIGNATURE_V_MIN_VALUE <= v <= SIGNATURE_V_MAX_VALUE - - def check_r(self, r): - return SIGNATURE_R_MIN_VALUE <= r <= SIGNATURE_R_MAX_VALUE - - def check_s(self, s): - return SIGNATURE_S_MIN_VALUE <= s <= SIGNATURE_S_MAX_VALUE - - class SafeMultisigEstimateTxSerializer(serializers.Serializer): safe = EthereumAddressField() to = EthereumAddressField() @@ -100,24 +44,6 @@ def validate(self, data): class SafeMultisigTxSerializer(SafeMultisigEstimateTxSerializer): - """ - DEPRECATED, use `SafeMultisigTxSerializerV1` instead - """ - - safe_tx_gas = serializers.IntegerField(min_value=0) - data_gas = serializers.IntegerField(min_value=0) - gas_price = serializers.IntegerField(min_value=0) - refund_receiver = EthereumAddressField( - default=None, allow_null=True, allow_zero_address=True - ) - nonce = serializers.IntegerField(min_value=0) - - -class SafeMultisigTxSerializerV1(SafeMultisigEstimateTxSerializer): - """ - Version 1.0.0 of the Safe changes `data_gas` to `base_gas` - """ - safe_tx_gas = serializers.IntegerField(min_value=0) base_gas = serializers.IntegerField(min_value=0) gas_price = serializers.IntegerField(min_value=0) diff --git a/gnosis/safe/tests/test_serializers.py b/gnosis/safe/tests/test_serializers.py index 11e7f38af..27c626874 100644 --- a/gnosis/safe/tests/test_serializers.py +++ b/gnosis/safe/tests/test_serializers.py @@ -2,82 +2,10 @@ from eth_account import Account -from gnosis.eth.constants import ( - SIGNATURE_R_MAX_VALUE, - SIGNATURE_R_MIN_VALUE, - SIGNATURE_S_MAX_VALUE, - SIGNATURE_S_MIN_VALUE, -) - -from ..serializers import SafeMultisigEstimateTxSerializer, SafeSignatureSerializer +from ..serializers import SafeMultisigEstimateTxSerializer class TestSerializers(TestCase): - def test_safe_signature_serializer(self): - for v in [0, 1]: - self.assertFalse( - SafeSignatureSerializer(data={"v": v, "r": -1, "s": 0}).is_valid() - ) - self.assertTrue( - SafeSignatureSerializer(data={"v": v, "r": 0, "s": 0}).is_valid() - ) - self.assertTrue( - SafeSignatureSerializer( - data={"v": v, "r": SIGNATURE_R_MAX_VALUE + 1, "s": 0} - ).is_valid() - ) - self.assertTrue( - SafeSignatureSerializer( - data={ - "v": v, - "r": SIGNATURE_R_MAX_VALUE + 1, - "s": SIGNATURE_S_MAX_VALUE + 1, - } - ).is_valid() - ) - - for v in [27, 28]: - self.assertFalse( - SafeSignatureSerializer(data={"v": v, "r": 0, "s": 0}).is_valid() - ) - self.assertTrue( - SafeSignatureSerializer( - data={ - "v": v, - "r": SIGNATURE_R_MIN_VALUE + 1, - "s": SIGNATURE_S_MAX_VALUE - 1, - } - ).is_valid() - ) - self.assertTrue( - SafeSignatureSerializer( - data={ - "v": v, - "r": SIGNATURE_R_MAX_VALUE - 1, - "s": SIGNATURE_S_MIN_VALUE + 1, - } - ).is_valid() - ) - - self.assertFalse( - SafeSignatureSerializer( - data={ - "v": v, - "r": SIGNATURE_R_MAX_VALUE + 1, - "s": SIGNATURE_S_MAX_VALUE - 1, - } - ).is_valid() - ) - self.assertFalse( - SafeSignatureSerializer( - data={ - "v": v, - "r": SIGNATURE_R_MIN_VALUE + 1, - "s": SIGNATURE_S_MAX_VALUE + 1, - } - ).is_valid() - ) - def test_safe_multisig_tx_estimate_serializer(self): safe_address = Account.create().address eth_address = Account.create().address