Skip to content

Commit

Permalink
Add contract address configuration (#1175)
Browse files Browse the repository at this point in the history
This adds the fields from the`ContractNetworksConfig` of the `protocol-kit`](https://docs.safe.global/sdk/protocol-kit/reference/safe#init) in order to dynamically initialise it:

- Add new fields to `Chain` model (and `ChainFactory`) for:
  - Safe singleton
  - Safe ProxyFactory
  - MultiSend
  - MultiSendCallOnly
  - FallbackHandler
  - SignMessageLib
  - CreateCall
  - SimulateTxAccessor
  - SafeWebAuthnSignerFactoryAddress
- Serialise fields with a new `ContractAddressesSerializer` on the `ChainSerializer`
- Add test for view
- Generate and test respective migration
  • Loading branch information
iamacook authored Jul 15, 2024
1 parent 83075fc commit fcd40f1
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 0 deletions.
77 changes: 77 additions & 0 deletions src/chains/migrations/0043_chain_create_call_address_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 5.0.6 on 2024-07-10 14:14

import gnosis.eth.django.models
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("chains", "0042_chain_balances_provider_chain_name_and_more"),
]

operations = [
migrations.AddField(
model_name="chain",
name="create_call_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="fallback_handler_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="multi_send_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="multi_send_call_only_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="safe_proxy_factory_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="safe_singleton_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="sign_message_lib_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="simulate_tx_accessor_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
migrations.AddField(
model_name="chain",
name="safe_web_authn_signer_factory_address",
field=gnosis.eth.django.models.EthereumAddressField(
blank=True, null=True
), # type: ignore[no-untyped-call]
),
]
54 changes: 54 additions & 0 deletions src/chains/migrations/tests/test_migration_0043.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from django.db.migrations.state import StateApps

from chains.migrations.tests.utils import TestMigrations


class Migration0043TestCase(TestMigrations):
migrate_from = "0042_chain_balances_provider_chain_name_and_more"
migrate_to = "0043_chain_create_call_address_and_more"

def setUpBeforeMigration(self, apps: StateApps) -> None:
Chain = apps.get_model("chains", "Chain")
Chain.objects.create(
id=1,
name="Mainnet",
short_name="eth",
description="",
l2=False,
rpc_authentication="API_KEY_PATH",
rpc_uri="https://mainnet.infura.io/v3/",
safe_apps_rpc_authentication="API_KEY_PATH",
safe_apps_rpc_uri="https://mainnet.infura.io/v3/",
block_explorer_uri_address_template="https://etherscan.io/address/{{address}}",
block_explorer_uri_tx_hash_template="https://etherscan.io/tx/{{txHash}}",
currency_name="Ether",
currency_symbol="ETH",
currency_decimals=18,
currency_logo_uri="https://gnosis-safe-token-logos.s3.amazonaws.com/ethereum-eth-logo.png",
transaction_service_uri="http://mainnet-safe-transaction-web.safe.svc.cluster.local",
vpc_transaction_service_uri="",
theme_text_color="#001428",
theme_background_color="#E8E7E6",
ens_registry_address="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
recommended_master_copy_version="1.3.0",
prices_provider_native_coin="ethereum",
prices_provider_chain_name="ethereum",
hidden=False,
balances_provider_chain_name="ethereum",
balances_provider_enabled=True,
)

def test_new_fields(self) -> None:
Chain = self.apps_registry.get_model("chains", "Chain")

chain = Chain.objects.get(id=1)

self.assertEqual(chain.safe_singleton_address, None)
self.assertEqual(chain.safe_proxy_factory_address, None)
self.assertEqual(chain.multi_send_address, None)
self.assertEqual(chain.multi_send_call_only_address, None)
self.assertEqual(chain.fallback_handler_address, None)
self.assertEqual(chain.sign_message_lib_address, None)
self.assertEqual(chain.create_call_address, None)
self.assertEqual(chain.simulate_tx_accessor_address, None)
self.assertEqual(chain.safe_web_authn_signer_factory_address, None)
11 changes: 11 additions & 0 deletions src/chains/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ class RpcAuthentication(models.TextChoices):
help_text="This flag informs API clients whether the balances provider is enabled for the chain",
)
hidden = models.BooleanField(default=False)
safe_singleton_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
safe_proxy_factory_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
multi_send_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
multi_send_call_only_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
fallback_handler_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
sign_message_lib_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
create_call_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
simulate_tx_accessor_address = EthereumAddressField(null=True, blank=True) # type: ignore[no-untyped-call]
safe_web_authn_signer_factory_address = EthereumAddressField(
null=True, blank=True
) # type: ignore[no-untyped-call]

def get_disabled_wallets(self) -> QuerySet["Wallet"]:
all_wallets = Wallet.objects.all()
Expand Down
18 changes: 18 additions & 0 deletions src/chains/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ class BalancesProviderSerializer(serializers.Serializer[Chain]):
enabled = serializers.BooleanField(source="balances_provider_enabled")


class ContractAddressesSerializer(serializers.Serializer[Chain]):
safe_singleton_address = serializers.CharField()
safe_proxy_factory_address = serializers.CharField()
multi_send_address = serializers.CharField()
multi_send_call_only_address = serializers.CharField()
fallback_handler_address = serializers.CharField()
sign_message_lib_address = serializers.CharField()
create_call_address = serializers.CharField()
simulate_tx_accessor_address = serializers.CharField()
safe_web_authn_signer_factory_address = serializers.CharField()


class BaseRpcUriSerializer(serializers.Serializer[Chain]):
authentication = serializers.SerializerMethodField()
value = serializers.SerializerMethodField(method_name="get_rpc_value")
Expand Down Expand Up @@ -167,6 +179,7 @@ class ChainSerializer(serializers.ModelSerializer[Chain]):
block_explorer_uri_template = serializers.SerializerMethodField()
native_currency = serializers.SerializerMethodField()
prices_provider = serializers.SerializerMethodField()
contract_addresses = serializers.SerializerMethodField()
balances_provider = serializers.SerializerMethodField()
transaction_service = serializers.URLField(
source="transaction_service_uri", default=None
Expand Down Expand Up @@ -195,6 +208,7 @@ class Meta:
"native_currency",
"prices_provider",
"balances_provider",
"contract_addresses",
"transaction_service",
"vpc_transaction_service",
"theme",
Expand Down Expand Up @@ -258,3 +272,7 @@ def get_prices_provider(self, instance: Chain) -> ReturnDict[Any, Any]:
@swagger_serializer_method(serializer_or_field=BalancesProviderSerializer) # type: ignore[misc]
def get_balances_provider(self, instance: Chain) -> ReturnDict[Any, Any]:
return BalancesProviderSerializer(instance).data

@swagger_serializer_method(serializer_or_field=ContractAddressesSerializer) # type: ignore[misc]
def get_contract_addresses(self, instance: Chain) -> ReturnDict[Any, Any]:
return ContractAddressesSerializer(instance).data
23 changes: 23 additions & 0 deletions src/chains/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ class Meta:
balances_provider_chain_name = factory.Faker("company")
balances_provider_enabled = factory.Faker("pybool")
hidden = False
safe_singleton_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)
safe_proxy_factory_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)
multi_send_address = factory.LazyAttribute(lambda o: web3.Account.create().address)
multi_send_call_only_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)
fallback_handler_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)
sign_message_lib_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)
create_call_address = factory.LazyAttribute(lambda o: web3.Account.create().address)
simulate_tx_accessor_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)
safe_web_authn_signer_factory_address = factory.LazyAttribute(
lambda o: web3.Account.create().address
)


class GasPriceFactory(DjangoModelFactory): # type: ignore[misc]
Expand Down
22 changes: 22 additions & 0 deletions src/chains/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ def test_json_payload_format(self) -> None:
"recommendedMasterCopyVersion": chain.recommended_master_copy_version,
"disabledWallets": [],
"features": [],
"contractAddresses": {
"safeSingletonAddress": chain.safe_singleton_address,
"safeProxyFactoryAddress": chain.safe_proxy_factory_address,
"multiSendAddress": chain.multi_send_address,
"multiSendCallOnlyAddress": chain.multi_send_call_only_address,
"fallbackHandlerAddress": chain.fallback_handler_address,
"signMessageLibAddress": chain.sign_message_lib_address,
"createCallAddress": chain.create_call_address,
"simulateTxAccessorAddress": chain.simulate_tx_accessor_address,
"safeWebAuthnSignerFactoryAddress": chain.safe_web_authn_signer_factory_address,
},
}
],
}
Expand Down Expand Up @@ -217,6 +228,17 @@ def test_json_payload_format(self) -> None:
"recommendedMasterCopyVersion": chain.recommended_master_copy_version,
"disabledWallets": [],
"features": [],
"contractAddresses": {
"safeSingletonAddress": chain.safe_singleton_address,
"safeProxyFactoryAddress": chain.safe_proxy_factory_address,
"multiSendAddress": chain.multi_send_address,
"multiSendCallOnlyAddress": chain.multi_send_call_only_address,
"fallbackHandlerAddress": chain.fallback_handler_address,
"signMessageLibAddress": chain.sign_message_lib_address,
"createCallAddress": chain.create_call_address,
"simulateTxAccessorAddress": chain.simulate_tx_accessor_address,
"safeWebAuthnSignerFactoryAddress": chain.safe_web_authn_signer_factory_address,
},
}

response = self.client.get(path=url, data=None, format="json")
Expand Down

0 comments on commit fcd40f1

Please sign in to comment.