diff --git a/scenario_player/exceptions/config.py b/scenario_player/exceptions/config.py index d52a9f602..e5ff93dbb 100644 --- a/scenario_player/exceptions/config.py +++ b/scenario_player/exceptions/config.py @@ -2,6 +2,14 @@ class ConfigurationError(ValueError): """Generic error thrown if there was an error while reading the scenario file.""" +class UDCTokenConfigError(ConfigurationError): + """Invalid Configuration parameters. Most likely there is an issue with Token amounts""" + + +class InsufficientMintingAmount(ConfigurationError): + """The minted amount set in token.min_balance is not sufficient""" + + class NodeConfigurationError(ConfigurationError): """An error occurred while validating the nodes setting of a scenario file.""" diff --git a/scenario_player/scenario.py b/scenario_player/scenario.py index 8c92d5342..10567ce30 100644 --- a/scenario_player/scenario.py +++ b/scenario_player/scenario.py @@ -6,6 +6,7 @@ import yaml from scenario_player.constants import GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL +from scenario_player.exceptions.config import InsufficientMintingAmount from scenario_player.utils.configuration import ( NodesConfig, ScenarioConfig, @@ -33,6 +34,7 @@ def __init__(self, yaml_path: pathlib.Path, data_path: pathlib.Path) -> None: self.scenario = ScenarioConfig(self._loaded) self.token = TokenConfig(self._loaded, data_path.joinpath("token.info")) self.spaas = SPaaSConfig(self._loaded) + self.validate() self.gas_limit = GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL * 2 @@ -40,3 +42,16 @@ def __init__(self, yaml_path: pathlib.Path, data_path: pathlib.Path) -> None: def name(self) -> str: """Return the name of the scenario file, sans extension.""" return self.path.stem + + def validate(self): + """Validate cross-config section requirements of the scenario. + + :raises InsufficientMintingAmount: + If token.min_balance < settings.services.udc.token.max_funding + """ + + # Check that the amount of minted tokens is >= than the amount of deposited tokens + try: + assert self.token.min_balance >= self.settings.services.udc.token.max_funding + except AssertionError: + raise InsufficientMintingAmount diff --git a/scenario_player/utils/configuration/settings.py b/scenario_player/utils/configuration/settings.py index 168685c10..906654e6e 100644 --- a/scenario_player/utils/configuration/settings.py +++ b/scenario_player/utils/configuration/settings.py @@ -3,7 +3,11 @@ import structlog from scenario_player.constants import GAS_STRATEGIES, TIMEOUT -from scenario_player.exceptions.config import ScenarioConfigurationError, ServiceConfigurationError +from scenario_player.exceptions.config import ( + ScenarioConfigurationError, + ServiceConfigurationError, + UDCTokenConfigError, +) from scenario_player.utils.configuration.base import ConfigMapping log = structlog.get_logger(__name__) @@ -41,8 +45,12 @@ class UDCTokenSettings(ConfigMapping): def __init__(self, loaded_yaml: dict): udc_settings = ((loaded_yaml.get("settings") or {}).get("services") or {}).get("udc") or {} super(UDCTokenSettings, self).__init__(udc_settings.get("token")) + self.validate() print(self.dict) + def validate(self): + self.assert_option(self.max_funding >= self.balance_per_node, UDCTokenConfigError) + @property def deposit(self): return self.get("deposit", False) diff --git a/scenario_player/utils/token.py b/scenario_player/utils/token.py index bbeb954b3..979b7983d 100644 --- a/scenario_player/utils/token.py +++ b/scenario_player/utils/token.py @@ -353,6 +353,10 @@ def effective_balance(self, at_target): """Get the effective balance of the target address.""" return self.contract_proxy.contract.functions.effectiveBalance(at_target).call() + def total_deposit(self, at_target): + """"Get the so far deposted amount""" + return self.contract_proxy.contract.functions.total_deposit(at_target).call() + def mint(self, target_address) -> Union[str, None]: """The mint function isn't present on the UDC, pass the UDTC address instead.""" return super(UserDepositContract, self).mint( @@ -397,6 +401,7 @@ def deposit(self, target_address) -> Union[str, None]: TODO: Allow setting max funding parameter, similar to the token `funding_min` setting. """ balance = self.effective_balance(target_address) + total_deposit = self.total_deposit(target_address) min_deposit = self.config.settings.services.udc.token.balance_per_node max_funding = self.config.settings.services.udc.token.max_funding log.debug( @@ -409,6 +414,6 @@ def deposit(self, target_address) -> Union[str, None]: return log.debug("deposit call required - insufficient funds") - deposit_amount = max_funding - balance + deposit_amount = total_deposit + (max_funding - balance) params = {"amount": deposit_amount, "target_address": target_address} return self.transact("deposit", params) diff --git a/tests/unittests/utils/configuration/test_settings.py b/tests/unittests/utils/configuration/test_settings.py index e9ec7f53f..a2e07fdbf 100644 --- a/tests/unittests/utils/configuration/test_settings.py +++ b/tests/unittests/utils/configuration/test_settings.py @@ -1,8 +1,9 @@ -from unittest.mock import patch - import pytest +import yaml from web3.gas_strategies.time_based import fast_gas_price_strategy, medium_gas_price_strategy +from scenario_player.exceptions.config import InsufficientMintingAmount, UDCTokenConfigError +from scenario_player.scenario import ScenarioYAML from scenario_player.utils.configuration.base import ConfigMapping from scenario_player.utils.configuration.settings import ( PFSSettingsConfig, @@ -14,6 +15,16 @@ ) +@pytest.fixture() +def file_for_insufficient_minting_test(tmp_path, minimal_yaml_dict): + minimal_yaml_dict["settings"] = {"services": {"udc": {"token": {"max_funding": 6000}}}} + minimal_yaml_dict["token"] = {"min_balance": 5999} + tmp_file = tmp_path.joinpath("tmp.yaml") + with open(tmp_file, "w") as outfile: + yaml.dump(minimal_yaml_dict, outfile, default_flow_style=False) + yield tmp_file + + class TestSettingsConfig: def test_is_subclass_of_config_mapping(self, minimal_yaml_dict): """The class is a subclass of :class:`ConfigMapping`.""" @@ -158,3 +169,16 @@ def test_attributes_whose_key_is_absent_return_expected_default( config = UDCTokenSettings(minimal_yaml_dict) MISSING = object() assert getattr(config, key, MISSING) == expected + + def test_balance_per_node_must_not_be_greater_than_max_funding(self, minimal_yaml_dict): + minimal_yaml_dict["settings"] = { + "services": {"udc": {"token": {"max_funding": 6000, "balance_per_node": 6001}}} + } + with pytest.raises(UDCTokenConfigError): + UDCTokenSettings(minimal_yaml_dict) + + def test_insufficient_minting(self, file_for_insufficient_minting_test): + with pytest.raises(InsufficientMintingAmount): + ScenarioYAML( + file_for_insufficient_minting_test, file_for_insufficient_minting_test.parent + )