From 8a34a781b3a9541627dff1c6b914dc9e15304104 Mon Sep 17 00:00:00 2001 From: "Kieran R. Prasch" Date: Tue, 28 Jul 2020 10:46:24 -0700 Subject: [PATCH 01/26] Signer support for Felix. --- nucypher/characters/chaotic.py | 5 ++++- nucypher/cli/commands/felix.py | 10 ++++++++-- nucypher/config/characters.py | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/nucypher/characters/chaotic.py b/nucypher/characters/chaotic.py index 41c84a065..2be230c84 100644 --- a/nucypher/characters/chaotic.py +++ b/nucypher/characters/chaotic.py @@ -112,7 +112,10 @@ class Felix(Character, NucypherTokenActor): self.db_engine = create_engine(f'sqlite:///{self.db_filepath}', convert_unicode=True) # Blockchain - transacting_power = TransactingPower(password=client_password, account=self.checksum_address, cache=True) + transacting_power = TransactingPower(password=client_password, + account=self.checksum_address, + signer=self.signer, + cache=True) self._crypto_power.consume_power_up(transacting_power) self.token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=registry) diff --git a/nucypher/cli/commands/felix.py b/nucypher/cli/commands/felix.py index d73630f03..8e26cc1fe 100644 --- a/nucypher/cli/commands/felix.py +++ b/nucypher/cli/commands/felix.py @@ -52,7 +52,7 @@ from nucypher.cli.options import ( option_poa, option_provider_uri, option_registry_filepath, - option_teacher_uri, + option_teacher_uri, option_signer_uri, ) from nucypher.cli.painting.help import paint_new_installation_help from nucypher.cli.types import NETWORK_PORT @@ -71,6 +71,7 @@ class FelixConfigOptions: dev, network, provider_uri, + signer_uri, host, db_filepath, checksum_address, @@ -85,6 +86,7 @@ class FelixConfigOptions: self.eth_node = eth_node self.provider_uri = provider_uri + self.signer_uri = signer_uri self.domains = {network} if network else None self.dev = dev self.host = host @@ -104,6 +106,7 @@ class FelixConfigOptions: registry_filepath=self.registry_filepath, provider_process=self.eth_node, provider_uri=self.provider_uri, + signer=self.signer_uri, rest_host=self.host, rest_port=self.port, db_filepath=self.db_filepath, @@ -111,7 +114,8 @@ class FelixConfigOptions: except FileNotFoundError: return handle_missing_configuration_file( character_config_class=FelixConfiguration, - config_file=config_file) + config_file=config_file + ) def generate_config(self, config_root, discovery_port): return FelixConfiguration.generate( @@ -124,6 +128,7 @@ class FelixConfigOptions: checksum_address=self.checksum_address, registry_filepath=self.registry_filepath, provider_uri=self.provider_uri, + signer=self.signer_uri, provider_process=self.eth_node, poa=self.poa) @@ -134,6 +139,7 @@ group_config_options = group_options( dev=option_dev, network=option_network(), provider_uri=option_provider_uri(), + signer_uri=option_signer_uri, host=click.option('--host', help="The host to run Felix HTTP services on", type=click.STRING, default='127.0.0.1'), db_filepath=option_db_filepath, checksum_address=option_checksum_address, diff --git a/nucypher/config/characters.py b/nucypher/config/characters.py index b887dcaf3..12d7ece4a 100644 --- a/nucypher/config/characters.py +++ b/nucypher/config/characters.py @@ -229,10 +229,13 @@ class FelixConfiguration(CharacterConfiguration): self.db_filepath = db_filepath or os.path.join(self.config_root, self.DEFAULT_DB_NAME) def static_payload(self) -> dict: + if not self.signer_uri: + self.signer_uri = self.provider_uri payload = dict( rest_host=self.rest_host, rest_port=self.rest_port, db_filepath=self.db_filepath, + signer_uri=self.signer_uri ) return {**super().static_payload(), **payload} From f46850643b0cb9cc71ab834410786e6ef60858b0 Mon Sep 17 00:00:00 2001 From: "Kieran R. Prasch" Date: Tue, 28 Jul 2020 13:26:34 -0700 Subject: [PATCH 02/26] Felix uses sign-and-broadcast API. --- nucypher/characters/chaotic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nucypher/characters/chaotic.py b/nucypher/characters/chaotic.py index 2be230c84..a184876ef 100644 --- a/nucypher/characters/chaotic.py +++ b/nucypher/characters/chaotic.py @@ -343,9 +343,9 @@ class Felix(Character, NucypherTokenActor): 'from': self.checksum_address, 'value': ether, 'gasPrice': self.blockchain.client.gas_price} - ether_txhash = self.blockchain.client.send_transaction(transaction) - self.log.info(f"Disbursement #{self.__disbursement} OK | NU {txhash.hex()[-6:]} | ETH {ether_txhash.hex()[:-6]} " + receipt = self.blockchain.sign_and_broadcast_transaction(transaction_dict=transaction, transaction_name='transfer') + self.log.info(f"Disbursement #{self.__disbursement} OK | NU {txhash.hex()[-6:]}" f"({str(NU(disbursement, 'NuNit'))} + {self.ETHER_AIRDROP_AMOUNT} wei) -> {recipient_address}") else: From 307d9ba073f1c4ec5d33b0236decfcbdac8e3311 Mon Sep 17 00:00:00 2001 From: "Kieran R. Prasch" Date: Tue, 28 Jul 2020 15:30:48 -0700 Subject: [PATCH 03/26] Generalize transaction build logic for Felix. --- nucypher/blockchain/eth/agents.py | 12 ++++++------ nucypher/blockchain/eth/deployers.py | 6 +++--- nucypher/blockchain/eth/interfaces.py | 28 ++++++++++++++------------- nucypher/characters/chaotic.py | 6 +++--- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index d97401681..826354e17 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -1510,8 +1510,8 @@ class MultiSigAgent(EthereumContractAgent): if self.is_owner(new_owner_address): raise self.RequirementError(f"{new_owner_address} is already an owner of the MultiSig.") transaction_function: ContractFunction = self.contract.functions.addOwner(new_owner_address) - transaction: TxParams = self.blockchain.build_transaction(contract_function=transaction_function, - sender_address=self.contract_address) + transaction: TxParams = self.blockchain.build_contract_transaction(contract_function=transaction_function, + sender_address=self.contract_address) return transaction @contract_api(TRANSACTION) @@ -1522,8 +1522,8 @@ class MultiSigAgent(EthereumContractAgent): raise self.RequirementError(f"{owner_address} is not owner of the MultiSig.") transaction_function: ContractFunction = self.contract.functions.removeOwner(owner_address) - transaction: TxParams = self.blockchain.build_transaction(contract_function=transaction_function, - sender_address=self.contract_address) + transaction: TxParams = self.blockchain.build_contract_transaction(contract_function=transaction_function, + sender_address=self.contract_address) return transaction @contract_api(TRANSACTION) @@ -1532,8 +1532,8 @@ class MultiSigAgent(EthereumContractAgent): raise self.RequirementError(f"New threshold {threshold} does not satisfy " f"0 < threshold ≤ number of owners = {self.number_of_owners}") transaction_function: ContractFunction = self.contract.functions.changeRequirement(threshold) - transaction: TxParams = self.blockchain.build_transaction(contract_function=transaction_function, - sender_address=self.contract_address) + transaction: TxParams = self.blockchain.build_contract_transaction(contract_function=transaction_function, + sender_address=self.contract_address) return transaction @contract_api(CONTRACT_CALL) diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index 9d81f96c9..b563cec69 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -452,9 +452,9 @@ class ProxyContractDeployer(BaseContractDeployer): gas_limit: int = None) -> dict: self._validate_retarget(new_target) upgrade_function = self._contract.functions.upgrade(new_target) - unsigned_transaction = self.blockchain.build_transaction(contract_function=upgrade_function, - sender_address=self.deployer_address, - transaction_gas_limit=gas_limit) + unsigned_transaction = self.blockchain.build_contract_transaction(contract_function=upgrade_function, + sender_address=self.deployer_address, + transaction_gas_limit=gas_limit) return unsigned_transaction def rollback(self, gas_limit: int = None) -> dict: diff --git a/nucypher/blockchain/eth/interfaces.py b/nucypher/blockchain/eth/interfaces.py index d6d534eae..7819952e8 100644 --- a/nucypher/blockchain/eth/interfaces.py +++ b/nucypher/blockchain/eth/interfaces.py @@ -467,16 +467,11 @@ class BlockchainInterface: @validate_checksum_address def build_transaction(self, - contract_function: ContractFunction, sender_address: str, payload: dict = None, transaction_gas_limit: int = None, ) -> dict: - # - # Build Payload - # - base_payload = {'chainId': int(self.client.chain_id), 'nonce': self.client.w3.eth.getTransactionCount(sender_address, 'pending'), 'from': sender_address, @@ -489,11 +484,18 @@ class BlockchainInterface: # Explicit gas override - will skip gas estimation in next operation. if transaction_gas_limit: payload['gas'] = int(transaction_gas_limit) + return payload - # - # Build Transaction - # - + @validate_checksum_address + def build_contract_transaction(self, + contract_function: ContractFunction, + sender_address: str, + payload: dict = None, + transaction_gas_limit: int = None, + ) -> dict: + payload = self.build_transaction(sender_address=sender_address, + payload=payload, + transaction_gas_limit=transaction_gas_limit) self.__log_transaction(transaction_dict=payload, contract_function=contract_function) try: transaction_dict = contract_function.buildTransaction(payload) # Gas estimation occurs here @@ -590,10 +592,10 @@ class BlockchainInterface: confirmations: int = 0 ) -> dict: - transaction = self.build_transaction(contract_function=contract_function, - sender_address=sender_address, - payload=payload, - transaction_gas_limit=transaction_gas_limit) + transaction = self.build_contract_transaction(contract_function=contract_function, + sender_address=sender_address, + payload=payload, + transaction_gas_limit=transaction_gas_limit) # Get transaction name try: diff --git a/nucypher/characters/chaotic.py b/nucypher/characters/chaotic.py index a184876ef..75b0f2cf8 100644 --- a/nucypher/characters/chaotic.py +++ b/nucypher/characters/chaotic.py @@ -344,13 +344,13 @@ class Felix(Character, NucypherTokenActor): 'value': ether, 'gasPrice': self.blockchain.client.gas_price} - receipt = self.blockchain.sign_and_broadcast_transaction(transaction_dict=transaction, transaction_name='transfer') + transaction_dict = self.blockchain.build_transaction(sender_address=self.checksum_address, payload=transaction) + _receipt = self.blockchain.sign_and_broadcast_transaction(transaction_dict=transaction_dict, transaction_name='transfer') self.log.info(f"Disbursement #{self.__disbursement} OK | NU {txhash.hex()[-6:]}" f"({str(NU(disbursement, 'NuNit'))} + {self.ETHER_AIRDROP_AMOUNT} wei) -> {recipient_address}") - else: self.log.info( - f"Disbursement #{self.__disbursement} OK | {txhash.hex()[-6:]} |" + f"Disbursement #{self.__disbursement} OK" f"({str(NU(disbursement, 'NuNit'))} -> {recipient_address}") return txhash From 65e88975ed6b4dffb3e6468a9ad93f4ccde30b82 Mon Sep 17 00:00:00 2001 From: "Kieran R. Prasch" Date: Tue, 28 Jul 2020 16:32:50 -0700 Subject: [PATCH 04/26] hardcode felix eth transfer transaction gas limit --- nucypher/characters/chaotic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nucypher/characters/chaotic.py b/nucypher/characters/chaotic.py index 75b0f2cf8..509058828 100644 --- a/nucypher/characters/chaotic.py +++ b/nucypher/characters/chaotic.py @@ -344,7 +344,9 @@ class Felix(Character, NucypherTokenActor): 'value': ether, 'gasPrice': self.blockchain.client.gas_price} - transaction_dict = self.blockchain.build_transaction(sender_address=self.checksum_address, payload=transaction) + transaction_dict = self.blockchain.build_transaction(sender_address=self.checksum_address, + payload=transaction, + transaction_gas_limit=22000) _receipt = self.blockchain.sign_and_broadcast_transaction(transaction_dict=transaction_dict, transaction_name='transfer') self.log.info(f"Disbursement #{self.__disbursement} OK | NU {txhash.hex()[-6:]}" f"({str(NU(disbursement, 'NuNit'))} + {self.ETHER_AIRDROP_AMOUNT} wei) -> {recipient_address}") From 8bda413197537fc5abb01f134b0432a3066626ce Mon Sep 17 00:00:00 2001 From: Kieran Prasch Date: Tue, 18 Aug 2020 09:29:54 -0700 Subject: [PATCH 05/26] Responds to RFCs in #2159; Allow signer URI to be optional for configuration. --- nucypher/blockchain/eth/interfaces.py | 16 ++++++++-------- nucypher/characters/chaotic.py | 6 +++--- nucypher/cli/commands/felix.py | 2 +- nucypher/config/characters.py | 4 +--- nucypher/config/node.py | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/nucypher/blockchain/eth/interfaces.py b/nucypher/blockchain/eth/interfaces.py index 7819952e8..52ef1cc6a 100644 --- a/nucypher/blockchain/eth/interfaces.py +++ b/nucypher/blockchain/eth/interfaces.py @@ -466,11 +466,11 @@ class BlockchainInterface: self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}") @validate_checksum_address - def build_transaction(self, - sender_address: str, - payload: dict = None, - transaction_gas_limit: int = None, - ) -> dict: + def build_payload(self, + sender_address: str, + payload: dict = None, + transaction_gas_limit: int = None, + ) -> dict: base_payload = {'chainId': int(self.client.chain_id), 'nonce': self.client.w3.eth.getTransactionCount(sender_address, 'pending'), @@ -493,9 +493,9 @@ class BlockchainInterface: payload: dict = None, transaction_gas_limit: int = None, ) -> dict: - payload = self.build_transaction(sender_address=sender_address, - payload=payload, - transaction_gas_limit=transaction_gas_limit) + payload = self.build_payload(sender_address=sender_address, + payload=payload, + transaction_gas_limit=transaction_gas_limit) self.__log_transaction(transaction_dict=payload, contract_function=contract_function) try: transaction_dict = contract_function.buildTransaction(payload) # Gas estimation occurs here diff --git a/nucypher/characters/chaotic.py b/nucypher/characters/chaotic.py index 509058828..66acc3050 100644 --- a/nucypher/characters/chaotic.py +++ b/nucypher/characters/chaotic.py @@ -344,9 +344,9 @@ class Felix(Character, NucypherTokenActor): 'value': ether, 'gasPrice': self.blockchain.client.gas_price} - transaction_dict = self.blockchain.build_transaction(sender_address=self.checksum_address, - payload=transaction, - transaction_gas_limit=22000) + transaction_dict = self.blockchain.build_payload(sender_address=self.checksum_address, + payload=transaction, + transaction_gas_limit=22000) _receipt = self.blockchain.sign_and_broadcast_transaction(transaction_dict=transaction_dict, transaction_name='transfer') self.log.info(f"Disbursement #{self.__disbursement} OK | NU {txhash.hex()[-6:]}" f"({str(NU(disbursement, 'NuNit'))} + {self.ETHER_AIRDROP_AMOUNT} wei) -> {recipient_address}") diff --git a/nucypher/cli/commands/felix.py b/nucypher/cli/commands/felix.py index 8e26cc1fe..27b527057 100644 --- a/nucypher/cli/commands/felix.py +++ b/nucypher/cli/commands/felix.py @@ -128,7 +128,7 @@ class FelixConfigOptions: checksum_address=self.checksum_address, registry_filepath=self.registry_filepath, provider_uri=self.provider_uri, - signer=self.signer_uri, + signer_uri=self.signer_uri, provider_process=self.eth_node, poa=self.poa) diff --git a/nucypher/config/characters.py b/nucypher/config/characters.py index 12d7ece4a..f00af3bce 100644 --- a/nucypher/config/characters.py +++ b/nucypher/config/characters.py @@ -229,13 +229,11 @@ class FelixConfiguration(CharacterConfiguration): self.db_filepath = db_filepath or os.path.join(self.config_root, self.DEFAULT_DB_NAME) def static_payload(self) -> dict: - if not self.signer_uri: - self.signer_uri = self.provider_uri payload = dict( rest_host=self.rest_host, rest_port=self.rest_port, db_filepath=self.db_filepath, - signer_uri=self.signer_uri + signer_uri=self.signer_uri ) return {**super().static_payload(), **payload} diff --git a/nucypher/config/node.py b/nucypher/config/node.py index 56c81704e..610e325af 100644 --- a/nucypher/config/node.py +++ b/nucypher/config/node.py @@ -141,7 +141,7 @@ class CharacterConfiguration(BaseConfiguration): self.is_light = light self.provider_uri = provider_uri or NO_BLOCKCHAIN_CONNECTION self.provider_process = provider_process or NO_BLOCKCHAIN_CONNECTION - self.signer_uri = signer_uri or NO_BLOCKCHAIN_CONNECTION + self.signer_uri = signer_uri or None # Learner self.federated_only = federated_only From 6ae4a3f1c2dfdf59de89ccf840d6b37ba0cb789b Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 18 Aug 2020 16:59:04 -0400 Subject: [PATCH 06/26] Random documentation improvements including language audit of literature.py --- Makefile | 4 ++-- docs/source/guides/contribution_guide.rst | 15 ++++++++++++--- docs/source/guides/installation_guide.rst | 2 +- nucypher/cli/literature.py | 20 ++++++++++---------- scripts/circle/compare_reqs.sh | 4 ++-- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 3e106ab80..e4e19d272 100644 --- a/Makefile +++ b/Makefile @@ -31,14 +31,14 @@ build-docs: $(MAKE) -C docs html validate-docs: build-docs - # Required dependencies from docs/requirements.txt + # Required dependencies from docs-requirements.txt python newsfragments/validate_files.py towncrier --draft docs: build-docs validate-docs readlink -f docs/build/html/index.html -mac-docs: build-docs +mac-docs: build-docs validate-docs open docs/build/html/index.html release: clean diff --git a/docs/source/guides/contribution_guide.rst b/docs/source/guides/contribution_guide.rst index 9555d57b5..df91115ef 100644 --- a/docs/source/guides/contribution_guide.rst +++ b/docs/source/guides/contribution_guide.rst @@ -180,15 +180,24 @@ Building Documentation Documentation for ``nucypher`` is hosted on `Read The Docs`_, and is automatically built without intervention by following the release procedure. However, you may want to build the documentation html locally for development. -To build the project dependencies locally: +To build the project dependencies locally on Linux: .. code:: bash (nucypher)$ make docs +or on MacOS: -If the build is successful, the resulting html output can be found in ``nucypher/docs/build/html``; -Opening ``nucypher/docs/build/html/index.html`` in a web browser is a reasonable next step. +.. code:: bash + + (nucypher)$ make mac-docs + +If the build is successful, the resulting local documentation homepage, ``nucypher/docs/build/html/index.html``, will +be automatically opened in the web browser. + +.. note:: + + If you would rather not have the homepage automatically opened, then run ``make build-docs`` instead. Building Docker diff --git a/docs/source/guides/installation_guide.rst b/docs/source/guides/installation_guide.rst index 187ea65b0..6d8499d0d 100644 --- a/docs/source/guides/installation_guide.rst +++ b/docs/source/guides/installation_guide.rst @@ -221,7 +221,7 @@ Alternately, you can install the development dependencies with pip: .. code-block:: bash - $ pip3 install -e .[development] + $ pip3 install -e .[dev] $ ./scripts/installation/install_solc.sh diff --git a/nucypher/cli/literature.py b/nucypher/cli/literature.py index 3d9f6a10d..1f31a6fb8 100644 --- a/nucypher/cli/literature.py +++ b/nucypher/cli/literature.py @@ -106,7 +106,7 @@ NO_STAKING_ACCOUNTS = "No staking accounts found." SELECT_STAKING_ACCOUNT_INDEX = "Select index of staking account" -NO_ACTIVE_STAKES = "There are no active stakes\n" +NO_ACTIVE_STAKES = "No active stakes found\n" NO_STAKES_AT_ALL = "No Stakes found" @@ -272,9 +272,9 @@ COLLECTING_ETH_FEE = 'Collecting {fee_amount} ETH from policy fees...' COLLECTING_PREALLOCATION_REWARD = 'Collecting {unlocked_tokens} from PreallocationEscrow contract {staking_address}...' -NO_TOKENS_TO_WITHDRAW = "No tokens that can be withdrawn." +NO_TOKENS_TO_WITHDRAW = "No tokens can be withdrawn." -NO_FEE_TO_WITHDRAW = "No policy fee that can be withdrawn." +NO_FEE_TO_WITHDRAW = "No policy fee can be withdrawn." # # Configuration @@ -345,7 +345,7 @@ DECRYPTING_CHARACTER_KEYRING = 'Decrypting {name} keyring...' CONFIRM_URSULA_IPV4_ADDRESS = "Is this the public-facing IPv4 address ({rest_host}) you want to use for Ursula?" -COLLECT_URSULA_IPV4_ADDRESS = "Please enter Ursula's public-facing IPv4 address here:" +COLLECT_URSULA_IPV4_ADDRESS = "Enter Ursula's public-facing IPv4 address:" # @@ -358,9 +358,9 @@ UNREADABLE_SEEDNODE_ADVISORY = "Failed to connect to teacher: {uri}" FORCE_DETECT_URSULA_IP_WARNING = "WARNING: --force is set, using auto-detected IP '{rest_host}'" -NO_DOMAIN_PEERS = "WARNING - No Peers Available for domains: {domains}" +NO_DOMAIN_PEERS = "WARNING: No Peers Available for domains: {domains}" -SEEDNODE_NOT_STAKING_WARNING = "Teacher: {uri} is not actively staking, skipping" +SEEDNODE_NOT_STAKING_WARNING = "Teacher ({uri}) is not actively staking, skipping" # @@ -423,7 +423,7 @@ CONFIRM_BEGIN_UPGRADE = "Confirm deploy new version of {contract_name} and retar SUCCESSFUL_RETARGET = "Successfully re-targeted {contract_name} proxy to {target_address}" -SUCCESSFUL_RETARGET_TX_BUILT = "Transaction to retarget {contract_name} proxy to {target_address} was built:" +SUCCESSFUL_RETARGET_TX_BUILT = "Successfully built transaction to retarget {contract_name} proxy to {target_address}:" CONFIRM_BUILD_RETARGET_TRANSACTION = """ Confirm building a re-target transaction for {contract_name}'s proxy to {target_address}? @@ -510,7 +510,7 @@ Accept WorkLock terms and node operator obligation?""" # TODO: Show a special m BIDDING_WINDOW_CLOSED = "❌ You can't escrow, the escrow period is closed." -CANCELLATION_WINDOW_CLOSED = "❌ You can't cancel your escrow. The cancellation period is closed." +CANCELLATION_WINDOW_CLOSED = "❌ You can't cancel your escrow, the cancellation period is closed." SUCCESSFUL_BID_CANCELLATION = "✅ Escrow canceled\n" @@ -523,12 +523,12 @@ CONFIRM_REQUEST_WORKLOCK_COMPENSATION = """ Before requesting the NU allocation for {bidder_address}, you will need to be refunded your unspent escrow amount. -Would you like to proceed? +Proceed with request? """ REQUESTING_WORKLOCK_COMPENSATION = "Requesting refund of unspent escrow amount..." -CLAIMING_NOT_AVAILABLE = "❌ You can't request a NU allocation yet. Allocations are not currently available." +CLAIMING_NOT_AVAILABLE = "❌ You can't request a NU allocation yet, allocations are not currently available." CLAIM_ALREADY_PLACED = "⚠️ An allocation was already assigned to {bidder_address}" diff --git a/scripts/circle/compare_reqs.sh b/scripts/circle/compare_reqs.sh index e3368959b..761b6514f 100755 --- a/scripts/circle/compare_reqs.sh +++ b/scripts/circle/compare_reqs.sh @@ -16,7 +16,7 @@ if [ $REQSHASH == $TESTHASH ]; then else echo "- requirements.txt contains inconsistencies ...." - echo "- you may want to run `pipenv sync --dev` and then ./scripts/installation/rebuild_pipenv.sh ...." + echo "- you may want to run `pipenv sync --dev` and then ./scripts/circle/rebuild_pipenv.sh ...." echo "- which will rebuild your *requirements.txt files ...." diff requirements.txt circlereqs.txt exit 2 @@ -34,7 +34,7 @@ if [ $REQSHASH == $TESTHASH ]; then else echo "- dev-requirements.txt contains inconsistencies ...." - echo "- you may want to run `pipenv sync --dev` and then ./scripts/installation/rebuild_pipenv.sh ...." + echo "- you may want to run `pipenv sync --dev` and then ./scripts/circle/rebuild_pipenv.sh ...." echo "- which will rebuild your *requirements.txt files ...." diff dev-requirements.txt dev-circlereqs.txt exit 2 From 6ad9a65d1aa678f0c998fd92c8280304aaad1d50 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 17 Aug 2020 17:59:46 -0700 Subject: [PATCH 07/26] Fix pytest discovery error - make the names of test files different --- ...test_token_and_stake.py => test_token_and_stake_acceptance.py} | 0 ...est_token_and_stake.py => test_token_and_stake_integration.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/acceptance/blockchain/interfaces/{test_token_and_stake.py => test_token_and_stake_acceptance.py} (100%) rename tests/integration/blockchain/{test_token_and_stake.py => test_token_and_stake_integration.py} (100%) diff --git a/tests/acceptance/blockchain/interfaces/test_token_and_stake.py b/tests/acceptance/blockchain/interfaces/test_token_and_stake_acceptance.py similarity index 100% rename from tests/acceptance/blockchain/interfaces/test_token_and_stake.py rename to tests/acceptance/blockchain/interfaces/test_token_and_stake_acceptance.py diff --git a/tests/integration/blockchain/test_token_and_stake.py b/tests/integration/blockchain/test_token_and_stake_integration.py similarity index 100% rename from tests/integration/blockchain/test_token_and_stake.py rename to tests/integration/blockchain/test_token_and_stake_integration.py From 3d3d55e80204b5423cb27aa49ec0757da65389d0 Mon Sep 17 00:00:00 2001 From: arjunhassard Date: Thu, 13 Aug 2020 22:21:38 +0100 Subject: [PATCH 08/26] minimum fee rate --- docs/source/architecture/servicefees.rst | 13 +++++++++++++ docs/source/index.rst | 1 + 2 files changed, 14 insertions(+) create mode 100644 docs/source/architecture/servicefees.rst diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst new file mode 100644 index 000000000..3020fcc51 --- /dev/null +++ b/docs/source/architecture/servicefees.rst @@ -0,0 +1,13 @@ +.. _service-fees: + +Service Fees (Pricing) +====================== + +Minimum Fee Rate +---------------- + +At network launch, stakers will choose a minimum fee rate, on a *per sharing policy* and *per 24h period basis*, that their worker machine (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offer and deposit (contained alongside the sharing policy’s parameters in an ``Arrangement`` object broadcasted to the network), for a specified policy duration, computes as equal to or greater than the minimum fee rate, the sharing policy will be automatically accepted and Ursula’s access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging fee rate, until the specified expiration date or until an early revocation instigated by Alice. + +The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursula, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. + +Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period, after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. diff --git a/docs/source/index.rst b/docs/source/index.rst index cfa114144..34c33613a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -154,6 +154,7 @@ Whitepapers architecture/upgradeable_proxy_contracts architecture/sub_stakes architecture/slashing + architecture/servicefees .. toctree:: :maxdepth: 1 From de7152505ecdd14862128bc6121384ea40ac05dc Mon Sep 17 00:00:00 2001 From: arjunhassard Date: Thu, 13 Aug 2020 22:44:49 +0100 Subject: [PATCH 09/26] quasi-universal pricing via global fee range --- docs/source/architecture/servicefees.rst | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index 3020fcc51..e4ec55f9d 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -11,3 +11,37 @@ At network launch, stakers will choose a minimum fee rate, on a *per sharing pol The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursula, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period, after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. + + +Global Fee Range +---------------- + +The global fee range is a means of establishing quasi-universal pricing for the NuCypher access control service in the early eras of the network’s existence. It is enforced via the function ``feeRateRange`` (in ``PolicyManager.sol``), which specifies parameters expressed in GWEI per 24h period. + +The minimum fee rate rate chosen by the staker must fall within the global fee range. The network will launch with the following fee constraints for each sharing policy: + +**Minimum fee rate** + +XX GWEI (~$X.XX x10^-X) per period + +XX,XXX GWEI ($X.XXX) per year + +**Maximum fee rate** + +X,XXX GWEI (~$X.XX x10^-X) per period + +XXX,XXX GWEI ($X.XXX) per year + +**Default fee rate** + +XXX GWEI (~$X.XX x10^-X) per period + +XX,XXX GWEI ($X.XXXX) per year + +1 GWEI = 10^-9 ETH +USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20) + +Note that the minimum and maximum fee rate are an upper and lower bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves. + +The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes besides policy duration and the number of assigned Ursulas (``n``). It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance channels (NuCypher DAO). If a parameter update of this sort occurs, sharing policies that were previously established (but have not yet expired) should not have the per-period fee rate retroactively modified. + From 9673debdc7845292355134f6fb0e7b0f7a1245f5 Mon Sep 17 00:00:00 2001 From: arjunhassard Date: Thu, 13 Aug 2020 23:29:22 +0100 Subject: [PATCH 10/26] discretionary fee rates, operational costs, sustainability --- docs/source/architecture/servicefees.rst | 47 +++++++++++++++++------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index e4ec55f9d..c6f24f4d2 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -3,45 +3,66 @@ Service Fees (Pricing) ====================== -Minimum Fee Rate +Minimum fee rate ---------------- -At network launch, stakers will choose a minimum fee rate, on a *per sharing policy* and *per 24h period basis*, that their worker machine (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offer and deposit (contained alongside the sharing policy’s parameters in an ``Arrangement`` object broadcasted to the network), for a specified policy duration, computes as equal to or greater than the minimum fee rate, the sharing policy will be automatically accepted and Ursula’s access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging fee rate, until the specified expiration date or until an early revocation instigated by Alice. +At network launch, stakers will choose a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker machine (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offer and deposit (contained alongside the sharing policy’s parameters in an ``Arrangement`` object), for a specified policy duration, computes as equal to or greater than the minimum fee rate, the sharing policy will be automatically accepted and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging fee rate, until the specified expiration date or until an early revocation instigated by Alice. The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursula, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period, after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. -Global Fee Range +Global fee range ---------------- -The global fee range is a means of establishing quasi-universal pricing for the NuCypher access control service in the early eras of the network’s existence. It is enforced via the function ``feeRateRange`` (in ``PolicyManager.sol``), which specifies parameters expressed in GWEI per 24h period. - -The minimum fee rate rate chosen by the staker must fall within the global fee range. The network will launch with the following fee constraints for each sharing policy: +The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` (in ``PolicyManager.sol``), which specifies constraints expressed in GWEI per sharing policy and per 24h period. The minimum fee rate rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: **Minimum fee rate** -XX GWEI (~$X.XX x10^-X) per period +XX GWEI (~$X.XX x10^-X) *per period* -XX,XXX GWEI ($X.XXX) per year +XX,XXX GWEI ($X.XXX) *per year* **Maximum fee rate** -X,XXX GWEI (~$X.XX x10^-X) per period +X,XXX GWEI (~$X.XX x10^-X) *per period* -XXX,XXX GWEI ($X.XXX) per year +XXX,XXX GWEI ($X.XXX) *per year* **Default fee rate** -XXX GWEI (~$X.XX x10^-X) per period +XXX GWEI (~$X.XX x10^-X) *per period* -XX,XXX GWEI ($X.XXXX) per year +XX,XXX GWEI ($X.XXXX) *per year* 1 GWEI = 10^-9 ETH USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20) Note that the minimum and maximum fee rate are an upper and lower bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves. -The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes besides policy duration and the number of assigned Ursulas (``n``). It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance channels (NuCypher DAO). If a parameter update of this sort occurs, sharing policies that were previously established (but have not yet expired) should not have the per-period fee rate retroactively modified. +The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes besides policy duration and the number of assigned Ursulas (``n``). It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance channels (see below). If a parameter update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. +Setting a discretionary fee rate +-------------------------------- + +Stakers should use the ``setMinFeeRate`` function to specify the minimum fee rate that their Ursula (worker machine) will accept. + +Note that Alices seeking to instantiate a new sharing policy are able to first discover all current minimum fee rates available to them, by retrieving the list of active stakers’ addresses, then querying the public variable ``PolicyManager.nodes(staker_address).minRewardRate`` with each ``staker_address``. + +Setting a price point, even within a tight range, requires the evaluation and weighting of many factors against one another. Many of these considerations are unique to the staker, such as their ongoing operational costs, economy of scale (e.g. through participation in other networks) and participation timeframe. However, the most important factors to consider pertain to the holistic service from the perspective of network users – for example, the affordability, congruency, and stability, of all offered price points – i.e. how probable it is that prices remain affordable to a developer after they are irreversibly committed to integrating NuCypher access control into their application’s technology stack. + +For an overview of price setting considerations, see the *Pricing Strategies* section of the Pricing Protocol & Economics paper. + +Operational costs +----------------- + +The cost of operating a typical Ursula, at network genesis, is estimated to be between $X and $Y per month. The variability of these estimates is driven primarily by diverse infrastructure costs across geographical locations, the range of feasible strategies for minimizing gas costs, and the economies of scale associated with service provision in multiple decentralized networks. This does not include the risks and opportunity costs of locking the Nu token for an extended duration of time. For a full derivation of overhead scenarios and the underlying assumptions, see the *Service-driven Pricing* section of the Pricing Protocol & Economics paper. + + +Note on staker sustainability +----------------------------- + +Although the maximum fee rate parameter constrains the income from fees in one plane, it is a component of a strategy to maximize long-term network revenue through predictable, affordable and congruent pricing. Operational costs will almost certainly exceed fee income in the near-term, but the subsidy mechanism is designed to steadily support service-providers for the first 5 to 8 years – see *Demand uncertainty & fragility* section of the Staking & Economic Protocol paper for more detail. This stable source of income provides an extended window for the NuCypher community to trial various fee range parameters until a balance is struck between the extremes of 1) unaffordability for early customers leading to low demand, and 2) unsustainability for service-providers leading to low participation. + +See the *Price point derivation* section, in particular the *Reconciling demand-side and service-side constraints* sub-section, of the Pricing Protocol & Economics paper for a deeper analysis of this trade-off. \ No newline at end of file From 9daf80c54512a4e09dfe3b94ba5aa56fcf380911 Mon Sep 17 00:00:00 2001 From: arjunhassard Date: Thu, 13 Aug 2020 23:37:03 +0100 Subject: [PATCH 11/26] governance & pricing paper resource --- docs/source/architecture/servicefees.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index c6f24f4d2..00f43d34d 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -43,6 +43,16 @@ Note that the minimum and maximum fee rate are an upper and lower bound to const The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes besides policy duration and the number of assigned Ursulas (``n``). It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance channels (see below). If a parameter update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. +Governance & pricing paper +--------------------------------------- + +In order to successfully execute the PolicyManager contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile (except perhaps with limited, existing, trust-heavy relationships), given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. + +Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts, and controlled by stakers in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. + +The **Pricing Protocol & Economics paper** serves as a key resource and reference for community debate, proposals for modification and DAO-driven upgrades and redeployments in the future. The paper discusses the merits and risks of quasi-universal pricing and the enforcement of an upper and lower bound on all offered price points. It includes a price point analysis from a demand-side, service-side and theoretical standpoint to produce the constraints in absolute terms (i.e. in GWEI) that the network will launch with. + + Setting a discretionary fee rate -------------------------------- From 8a5c5c17b2f188e8e1bc8fe87c235094618fdd28 Mon Sep 17 00:00:00 2001 From: arjunhassard Date: Fri, 14 Aug 2020 15:48:42 +0100 Subject: [PATCH 12/26] minor edits --- docs/source/architecture/servicefees.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index 00f43d34d..832c60cf1 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -6,9 +6,9 @@ Service Fees (Pricing) Minimum fee rate ---------------- -At network launch, stakers will choose a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker machine (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offer and deposit (contained alongside the sharing policy’s parameters in an ``Arrangement`` object), for a specified policy duration, computes as equal to or greater than the minimum fee rate, the sharing policy will be automatically accepted and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging fee rate, until the specified expiration date or until an early revocation instigated by Alice. +When they join the network, stakers choose a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker machine (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offered rate and commensurate deposit (contained alongside the sharing policy’s parameters in an ``Arrangement`` object), for a specified policy duration, computes as equal to or greater than the minimum fee rate, the sharing policy will be accepted and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging fee rate, until the specified expiration date or an early revocation instigated by Alice. -The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursula, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. +The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursulas, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period, after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. @@ -16,7 +16,7 @@ Alices are required to escrow a deposit covering the cost of the entire duration Global fee range ---------------- -The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` (in ``PolicyManager.sol``), which specifies constraints expressed in GWEI per sharing policy and per 24h period. The minimum fee rate rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: +The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` in ``PolicyManager.sol``, which specifies constraints, expressed in GWEI, per sharing policy and per 24h period. The minimum fee rate rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: **Minimum fee rate** @@ -36,17 +36,18 @@ XXX GWEI (~$X.XX x10^-X) *per period* XX,XXX GWEI ($X.XXXX) *per year* -1 GWEI = 10^-9 ETH -USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20) +*1 GWEI = 10^-9 ETH* + +*USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20)* Note that the minimum and maximum fee rate are an upper and lower bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves. -The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes besides policy duration and the number of assigned Ursulas (``n``). It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance channels (see below). If a parameter update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. +The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes. It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance processes (see below). If an update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. Governance & pricing paper --------------------------------------- -In order to successfully execute the PolicyManager contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile (except perhaps with limited, existing, trust-heavy relationships), given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. +In order to successfully execute the ``PolicyManager.sol`` contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile (except perhaps with limited, existing, trust-heavy relationships), given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts, and controlled by stakers in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. @@ -73,6 +74,6 @@ The cost of operating a typical Ursula, at network genesis, is estimated to be b Note on staker sustainability ----------------------------- -Although the maximum fee rate parameter constrains the income from fees in one plane, it is a component of a strategy to maximize long-term network revenue through predictable, affordable and congruent pricing. Operational costs will almost certainly exceed fee income in the near-term, but the subsidy mechanism is designed to steadily support service-providers for the first 5 to 8 years – see *Demand uncertainty & fragility* section of the Staking & Economic Protocol paper for more detail. This stable source of income provides an extended window for the NuCypher community to trial various fee range parameters until a balance is struck between the extremes of 1) unaffordability for early customers leading to low demand, and 2) unsustainability for service-providers leading to low participation. +Although the maximum fee rate parameter constrains the income from fees in one plane, it is a component of a strategy to maximize long-term network revenue through predictable, affordable and congruent pricing. Operational costs will almost certainly exceed fee income in the near-term, but the subsidy mechanism is designed to steadily support service-providers for the first 5 to 8 years – see the *Demand uncertainty & fragility* section of the Staking & Economic Protocol paper for more detail. This stable source of income provides an extended window for the NuCypher community to trial various fee range parameters until a balance is struck between the extremes of 1) unaffordability for early customers leading to low demand, and 2) unsustainability for service-providers leading to low participation. See the *Price point derivation* section, in particular the *Reconciling demand-side and service-side constraints* sub-section, of the Pricing Protocol & Economics paper for a deeper analysis of this trade-off. \ No newline at end of file From 05e94f7ace5ccd7002308424490b7badec9137ba Mon Sep 17 00:00:00 2001 From: Arjun Hassard Date: Mon, 17 Aug 2020 14:02:00 +0100 Subject: [PATCH 13/26] Apply suggestions from code review Co-authored-by: Derek Pierre --- docs/source/architecture/servicefees.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index 832c60cf1..7bdad7ce7 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -6,17 +6,17 @@ Service Fees (Pricing) Minimum fee rate ---------------- -When they join the network, stakers choose a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker machine (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offered rate and commensurate deposit (contained alongside the sharing policy’s parameters in an ``Arrangement`` object), for a specified policy duration, computes as equal to or greater than the minimum fee rate, the sharing policy will be accepted and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging fee rate, until the specified expiration date or an early revocation instigated by Alice. +When they join the network, stakers specify a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offered per period rate (contained alongside the sharing policy’s parameters in an ``Arrangement`` object) for a specified policy duration computes as equal to or greater than the minimum fee rate, the sharing policy will be accepted by Ursula and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging per period fee rate, until the specified expiration date or an early revocation is instigated by Alice. The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursulas, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. -Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period, after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. +When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. Global fee range ---------------- -The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` in ``PolicyManager.sol``, which specifies constraints, expressed in GWEI, per sharing policy and per 24h period. The minimum fee rate rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: +The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` in ``PolicyManager.sol``, which specifies per sharing policy and per 24h period constraints expressed in GWEI. The minimum fee rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: **Minimum fee rate** @@ -40,7 +40,7 @@ XX,XXX GWEI ($X.XXXX) *per year* *USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20)* -Note that the minimum and maximum fee rate are an upper and lower bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves. +Note that the minimum and maximum fee rate are a lower and upper bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves. The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes. It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance processes (see below). If an update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. @@ -49,7 +49,7 @@ Governance & pricing paper In order to successfully execute the ``PolicyManager.sol`` contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile (except perhaps with limited, existing, trust-heavy relationships), given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. -Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts, and controlled by stakers in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. +Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts - by submitting a proposal which is voted on by stakers weighted in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. The **Pricing Protocol & Economics paper** serves as a key resource and reference for community debate, proposals for modification and DAO-driven upgrades and redeployments in the future. The paper discusses the merits and risks of quasi-universal pricing and the enforcement of an upper and lower bound on all offered price points. It includes a price point analysis from a demand-side, service-side and theoretical standpoint to produce the constraints in absolute terms (i.e. in GWEI) that the network will launch with. @@ -59,7 +59,7 @@ Setting a discretionary fee rate Stakers should use the ``setMinFeeRate`` function to specify the minimum fee rate that their Ursula (worker machine) will accept. -Note that Alices seeking to instantiate a new sharing policy are able to first discover all current minimum fee rates available to them, by retrieving the list of active stakers’ addresses, then querying the public variable ``PolicyManager.nodes(staker_address).minRewardRate`` with each ``staker_address``. +Note that Alices seeking to instantiate new sharing policies are able to first discover all current minimum fee rates available to them, by retrieving the list of active stakers’ addresses, then querying the public variable ``PolicyManager.nodes(staker_address).minRewardRate`` with each ``staker_address``. Setting a price point, even within a tight range, requires the evaluation and weighting of many factors against one another. Many of these considerations are unique to the staker, such as their ongoing operational costs, economy of scale (e.g. through participation in other networks) and participation timeframe. However, the most important factors to consider pertain to the holistic service from the perspective of network users – for example, the affordability, congruency, and stability, of all offered price points – i.e. how probable it is that prices remain affordable to a developer after they are irreversibly committed to integrating NuCypher access control into their application’s technology stack. @@ -68,7 +68,7 @@ For an overview of price setting considerations, see the *Pricing Strategies* se Operational costs ----------------- -The cost of operating a typical Ursula, at network genesis, is estimated to be between $X and $Y per month. The variability of these estimates is driven primarily by diverse infrastructure costs across geographical locations, the range of feasible strategies for minimizing gas costs, and the economies of scale associated with service provision in multiple decentralized networks. This does not include the risks and opportunity costs of locking the Nu token for an extended duration of time. For a full derivation of overhead scenarios and the underlying assumptions, see the *Service-driven Pricing* section of the Pricing Protocol & Economics paper. +The cost of operating a typical Ursula, at network genesis, is estimated to be between $X and $Y per month. The variability of these estimates is driven primarily by diverse infrastructure costs across geographical locations, the range of feasible strategies for minimizing gas costs, and the economies of scale associated with service provision in multiple decentralized networks. This does not include the risks and opportunity costs of locking the NU token for an extended duration of time. For a full derivation of overhead scenarios and the underlying assumptions, see the *Service-driven Pricing* section of the Pricing Protocol & Economics paper. Note on staker sustainability @@ -76,4 +76,4 @@ Note on staker sustainability Although the maximum fee rate parameter constrains the income from fees in one plane, it is a component of a strategy to maximize long-term network revenue through predictable, affordable and congruent pricing. Operational costs will almost certainly exceed fee income in the near-term, but the subsidy mechanism is designed to steadily support service-providers for the first 5 to 8 years – see the *Demand uncertainty & fragility* section of the Staking & Economic Protocol paper for more detail. This stable source of income provides an extended window for the NuCypher community to trial various fee range parameters until a balance is struck between the extremes of 1) unaffordability for early customers leading to low demand, and 2) unsustainability for service-providers leading to low participation. -See the *Price point derivation* section, in particular the *Reconciling demand-side and service-side constraints* sub-section, of the Pricing Protocol & Economics paper for a deeper analysis of this trade-off. \ No newline at end of file +See the *Price point derivation* section, in particular the *Reconciling demand-side and service-side constraints* sub-section, of the Pricing Protocol & Economics paper for a deeper analysis of this trade-off. From 84349a45e512fe42a952871ed38905d64e40112e Mon Sep 17 00:00:00 2001 From: Arjun Hassard Date: Mon, 17 Aug 2020 14:04:04 +0100 Subject: [PATCH 14/26] Per machine + highest rate from set of mins --- docs/source/architecture/servicefees.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index 7bdad7ce7..6daefecbf 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -8,7 +8,9 @@ Minimum fee rate When they join the network, stakers specify a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offered per period rate (contained alongside the sharing policy’s parameters in an ``Arrangement`` object) for a specified policy duration computes as equal to or greater than the minimum fee rate, the sharing policy will be accepted by Ursula and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging per period fee rate, until the specified expiration date or an early revocation is instigated by Alice. -The minimum fee rate is also calculated *per Ursula*. If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same fee rate. Although Alices may attempt price optimization strategies to find the cheapest group of Ursulas, they will nevertheless have to pay the highest rate from the set of minimum fee rates from the stakers with which they end up engaging. +The minimum fee rate applies to each individual worker machine managing a given sharing policy. In other words, the rate is also *per Ursula*. + +If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same exact fee rate. This rate will be the highest from the set of *minimum* fee rates specified by the stakers with which they engage. Alices may attempt price optimization strategies to find the cheapest group of Ursulas. When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. From b239710d44acac50284b4f6e2b7df40194efa590 Mon Sep 17 00:00:00 2001 From: arjunhassard Date: Mon, 17 Aug 2020 16:02:52 +0100 Subject: [PATCH 15/26] @vzotova edits --- docs/source/architecture/servicefees.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index 6daefecbf..3a88b1a0e 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -12,13 +12,15 @@ The minimum fee rate applies to each individual worker machine managing a given If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same exact fee rate. This rate will be the highest from the set of *minimum* fee rates specified by the stakers with which they engage. Alices may attempt price optimization strategies to find the cheapest group of Ursulas. -When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy, but fees are paid out to stakers once per period after their Ursula confirms activity. Therefore the minimum sum of fees a staker will receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. +When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy. The deposit is paid out in tranches to stakers once per period, in the period after their Ursula makes a commitment (formerly referred to as *confirm activity*). Therefore, the minimum sum of fees a staker can receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. Global fee range ---------------- -The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` in ``PolicyManager.sol``, which specifies per sharing policy and per 24h period constraints expressed in GWEI. The minimum fee rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: +The global fee range is a means of establishing quasi-universal pricing for the NuCypher service. It is enforced via the function ``feeRateRange`` in ``PolicyManager.sol``, which specifies per sharing policy and per 24h period constraints expressed in **WEI**. In other material, rates are discussed in **GWEI** (and fiat). + +The minimum fee rate chosen by stakers must fall within the global fee range. The network will launch with the following parameters: **Minimum fee rate** @@ -42,7 +44,7 @@ XX,XXX GWEI ($X.XXXX) *per year* *USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20)* -Note that the minimum and maximum fee rate are a lower and upper bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves. +Note that the minimum and maximum fee rate are a lower and upper bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves, or chooses a rate outside the boundaries of the global fee range. The default rate will also be used if the range's boundaries are updated, a staker's specified rate *now* falls outside the range, and they fail to change it. The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes. It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance processes (see below). If an update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. @@ -51,7 +53,7 @@ Governance & pricing paper In order to successfully execute the ``PolicyManager.sol`` contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile (except perhaps with limited, existing, trust-heavy relationships), given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. -Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts - by submitting a proposal which is voted on by stakers weighted in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. +Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts - by submitting a proposal which is validated by stakers weighted in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. The **Pricing Protocol & Economics paper** serves as a key resource and reference for community debate, proposals for modification and DAO-driven upgrades and redeployments in the future. The paper discusses the merits and risks of quasi-universal pricing and the enforcement of an upper and lower bound on all offered price points. It includes a price point analysis from a demand-side, service-side and theoretical standpoint to produce the constraints in absolute terms (i.e. in GWEI) that the network will launch with. @@ -61,7 +63,7 @@ Setting a discretionary fee rate Stakers should use the ``setMinFeeRate`` function to specify the minimum fee rate that their Ursula (worker machine) will accept. -Note that Alices seeking to instantiate new sharing policies are able to first discover all current minimum fee rates available to them, by retrieving the list of active stakers’ addresses, then querying the public variable ``PolicyManager.nodes(staker_address).minRewardRate`` with each ``staker_address``. +Note that Alices seeking to instantiate new sharing policies are able to first discover all current minimum fee rates available to them, by retrieving the list of active stakers’ addresses, then querying the public variable ``PolicyManager.getMinFeeRate(staker_address)`` with each ``staker_address``. Setting a price point, even within a tight range, requires the evaluation and weighting of many factors against one another. Many of these considerations are unique to the staker, such as their ongoing operational costs, economy of scale (e.g. through participation in other networks) and participation timeframe. However, the most important factors to consider pertain to the holistic service from the perspective of network users – for example, the affordability, congruency, and stability, of all offered price points – i.e. how probable it is that prices remain affordable to a developer after they are irreversibly committed to integrating NuCypher access control into their application’s technology stack. From 290f4911d20e829e9db7c5d4012cef276d17d4de Mon Sep 17 00:00:00 2001 From: Arjun Hassard Date: Tue, 18 Aug 2020 18:10:47 +0100 Subject: [PATCH 16/26] Update docs/source/architecture/service_fees.rst --- docs/source/architecture/servicefees.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index 3a88b1a0e..ef4c9b4fe 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -12,7 +12,13 @@ The minimum fee rate applies to each individual worker machine managing a given If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same exact fee rate. This rate will be the highest from the set of *minimum* fee rates specified by the stakers with which they engage. Alices may attempt price optimization strategies to find the cheapest group of Ursulas. -When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy. The deposit is paid out in tranches to stakers once per period, in the period after their Ursula makes a commitment (formerly referred to as *confirm activity*). Therefore, the minimum sum of fees a staker can receive each period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. +When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy. This deposit is split and paid out in tranches to stakers once per period. + +The precise payment flow follows a repeated three-period cycle; in the first period the Ursula *makes a commitment* to the second period. Then, the Ursula services the policy during the second period (and makes a commitment to the third period). In the third period, they receive the fee tranche for their work during the second period, and continue servicing the policy, etc. This cycle continues until all the policies that Ursula is responsible for have expired. + +The minimum sum of fees a staker can receive period-to-period is the product of their specified minimum fee rate and the number of active sharing policies their Ursula has been assigned. + +Note that *making a commitment* was formerly referred to as *confirming activity*. Global fee range @@ -47,7 +53,7 @@ XX,XXX GWEI ($X.XXXX) *per year* Note that the minimum and maximum fee rate are a lower and upper bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves, or chooses a rate outside the boundaries of the global fee range. The default rate will also be used if the range's boundaries are updated, a staker's specified rate *now* falls outside the range, and they fail to change it. The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes. It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance processes (see below). If an update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. - +Note that the global fee range is only applicable to stakers and Ursulas. Alices are free to pay as high a rate as they like. Governance & pricing paper --------------------------------------- From 2b73dc73524191cd56062f35c524524a9440b7b6 Mon Sep 17 00:00:00 2001 From: Arjun Hassard Date: Wed, 19 Aug 2020 12:57:10 +0100 Subject: [PATCH 17/26] Apply suggestions from code review Co-authored-by: Derek Pierre --- docs/source/architecture/servicefees.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/servicefees.rst index ef4c9b4fe..d08fa7e26 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/servicefees.rst @@ -10,7 +10,7 @@ When they join the network, stakers specify a minimum fee rate, on a *per sharin The minimum fee rate applies to each individual worker machine managing a given sharing policy. In other words, the rate is also *per Ursula*. -If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same exact fee rate. This rate will be the highest from the set of *minimum* fee rates specified by the stakers with which they engage. Alices may attempt price optimization strategies to find the cheapest group of Ursulas. +If Alice wishes to employ multiple Ursulas to service a single sharing policy (``n`` > 1), a common configuration, then they must pay each staker associated with that policy the same fee rate. This rate will be the highest from the set of *minimum* fee rates specified by the stakers with which they engage. Alices may attempt price optimization strategies to find the cheapest group of Ursulas. When issuing a sharing policy, Alices are required to escrow a deposit covering the cost of the entire duration of the policy. This deposit is split and paid out in tranches to stakers once per period. @@ -48,7 +48,7 @@ XX,XXX GWEI ($X.XXXX) *per year* *1 GWEI = 10^-9 ETH* -*USD conversion utilizes the ETHUSD 100-day rolling average of 1 ETH = $222 (08.08.20)* +*USD conversion utilizes the ETHUSD 100-day rolling average.* Note that the minimum and maximum fee rate are a lower and upper bound to constrain the fee rate a staker may offer. The default fee rate is the rate that will be displayed and offered to Alices if the staker chooses not to configure this parameter themselves, or chooses a rate outside the boundaries of the global fee range. The default rate will also be used if the range's boundaries are updated, a staker's specified rate *now* falls outside the range, and they fail to change it. @@ -57,7 +57,7 @@ Note that the global fee range is only applicable to stakers and Ursulas. Alices Governance & pricing paper --------------------------------------- -In order to successfully execute the ``PolicyManager.sol`` contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile (except perhaps with limited, existing, trust-heavy relationships), given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. +In order to successfully interact with the ``PolicyManager.sol`` contract, the global fee range must be adhered to by the Ursula (and Alice). Failing this, the contract will throw up an error and it will not be possible to commence a commercial engagement or pay/receive fees. Attempts to circumvent NuCypher’s smart contracts are likely to be futile given the requirement of coordinated modification and redeployment by network users and a critical mass of other stakers. Given its high enforceability, the presence of an inflexible fee range dictating the bounds of every transaction is arguably the most critical component of the NuCypher protocol’s economic design and parametrization, particularly over the long-term and with respect to the sustainability of the network. From a governance perspective, it is also amongst the most malleable. If a quorum of stakers wish to set prices outside the range, then they have the right to lobby and propose a widening of the global fee range, its removal altogether, or some other design modification (e.g. narrowing the range). They may do so via the NuCypher DAO – the owner of all NuCypher smart contracts - by submitting a proposal which is validated by stakers weighted in proportion to their stake size. See for guidance on the NuCypher DAO and official NuCypher governance processes. From 2d8be15a5be0e67621b36885b6cc80c40964fa4a Mon Sep 17 00:00:00 2001 From: Arjun Hassard Date: Wed, 19 Aug 2020 14:43:01 +0100 Subject: [PATCH 18/26] Apply suggestions from code review & file rename Co-authored-by: MacLane S Wilkison --- .../architecture/{servicefees.rst => service_fees.rst} | 8 +++++--- docs/source/index.rst | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) rename docs/source/architecture/{servicefees.rst => service_fees.rst} (92%) diff --git a/docs/source/architecture/servicefees.rst b/docs/source/architecture/service_fees.rst similarity index 92% rename from docs/source/architecture/servicefees.rst rename to docs/source/architecture/service_fees.rst index d08fa7e26..f5bfb38e4 100644 --- a/docs/source/architecture/servicefees.rst +++ b/docs/source/architecture/service_fees.rst @@ -6,7 +6,7 @@ Service Fees (Pricing) Minimum fee rate ---------------- -When they join the network, stakers specify a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offered per period rate (contained alongside the sharing policy’s parameters in an ``Arrangement`` object) for a specified policy duration computes as equal to or greater than the minimum fee rate, the sharing policy will be accepted by Ursula and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging per period fee rate, until the specified expiration date or an early revocation is instigated by Alice. +When stakers join the network, they specify a minimum fee rate, on a *per sharing policy* and *per 24h period* basis, that their worker (Ursula) will accept at the point of engagement with a network user (Alice). If Alice’s offered per period rate (contained alongside the sharing policy’s parameters in an ``Arrangement`` object) for a specified policy duration computes as equal to or greater than the minimum fee rate, the sharing policy will be accepted by Ursula and the access control service will commence. Ursula will service the sharing policy by being online and answering access requests, at that unchanging per period fee rate, until the specified expiration date or an early revocation is instigated by Alice. The minimum fee rate applies to each individual worker machine managing a given sharing policy. In other words, the rate is also *per Ursula*. @@ -54,6 +54,8 @@ Note that the minimum and maximum fee rate are a lower and upper bound to constr The fee range must be adhered to in identical fashion by all NuCypher stakers, regardless of their stake size or capacity. The fee range applies to all sharing policies, irrespective of the volume of re-encryption requests or other distinguishing attributes. It also applies equally to all periods in the future, until the moment that the global fee range’s parameters are adjusted or the range is removed, via official governance processes (see below). If an update of this sort occurs, sharing policies that were previously established, but have not yet expired, should not have the per-period fee rate retroactively modified. Note that the global fee range is only applicable to stakers and Ursulas. Alices are free to pay as high a rate as they like. + + Governance & pricing paper --------------------------------------- @@ -78,12 +80,12 @@ For an overview of price setting considerations, see the *Pricing Strategies* se Operational costs ----------------- -The cost of operating a typical Ursula, at network genesis, is estimated to be between $X and $Y per month. The variability of these estimates is driven primarily by diverse infrastructure costs across geographical locations, the range of feasible strategies for minimizing gas costs, and the economies of scale associated with service provision in multiple decentralized networks. This does not include the risks and opportunity costs of locking the NU token for an extended duration of time. For a full derivation of overhead scenarios and the underlying assumptions, see the *Service-driven Pricing* section of the Pricing Protocol & Economics paper. +The cost of operating a typical Ursula, at network genesis, is estimated to be between $X and $Y per month. The variability of these estimates is driven primarily by diverse infrastructure costs across geographical locations, the range of feasible strategies for minimizing gas costs, and the economies of scale associated with service provision in multiple decentralized networks. This does not include the risks and opportunity costs of locking NU for an extended duration of time. For a full derivation of overhead scenarios and the underlying assumptions, see the *Service-driven Pricing* section of the Pricing Protocol & Economics paper. Note on staker sustainability ----------------------------- -Although the maximum fee rate parameter constrains the income from fees in one plane, it is a component of a strategy to maximize long-term network revenue through predictable, affordable and congruent pricing. Operational costs will almost certainly exceed fee income in the near-term, but the subsidy mechanism is designed to steadily support service-providers for the first 5 to 8 years – see the *Demand uncertainty & fragility* section of the Staking & Economic Protocol paper for more detail. This stable source of income provides an extended window for the NuCypher community to trial various fee range parameters until a balance is struck between the extremes of 1) unaffordability for early customers leading to low demand, and 2) unsustainability for service-providers leading to low participation. +Although the maximum fee rate parameter constrains the income from fees in one plane, it is a component of a strategy to maximize long-term network revenue through predictable, affordable and congruent pricing. Operational costs will almost certainly exceed fee income in the near-term, but the subsidy mechanism is designed to steadily support service-providers for the first 5 to 8 years – see the *Demand uncertainty & fragility* section of the Staking & Economic Protocol paper for more detail. This subsidy provides an extended window for the NuCypher community to trial various fee range parameters until a balance is struck between the extremes of 1) unaffordability for early customers leading to low demand, and 2) unsustainability for service-providers leading to low participation. See the *Price point derivation* section, in particular the *Reconciling demand-side and service-side constraints* sub-section, of the Pricing Protocol & Economics paper for a deeper analysis of this trade-off. diff --git a/docs/source/index.rst b/docs/source/index.rst index 34c33613a..14fdb3afe 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -154,7 +154,7 @@ Whitepapers architecture/upgradeable_proxy_contracts architecture/sub_stakes architecture/slashing - architecture/servicefees + architecture/service_fees .. toctree:: :maxdepth: 1 From bb6cee46137046a46972d76974e409d3c1881d75 Mon Sep 17 00:00:00 2001 From: vzotova Date: Fri, 7 Aug 2020 21:06:13 +0300 Subject: [PATCH 19/26] Removed NO_WORKER_BONDED constant from `Staker.worker_address`, used NULL_ADDRESS instead --- nucypher/blockchain/eth/actors.py | 19 ++++++++----------- nucypher/cli/painting/staking.py | 5 +++-- .../test_stake_via_allocation_contract.py | 3 ++- .../cli/ursula/test_stakeholder_and_ursula.py | 3 ++- .../cli/test_stake_cli_functionality.py | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index e3df194e6..34496c1e9 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -27,7 +27,7 @@ import time from web3.types import TxReceipt -from constant_sorrow.constants import FULL, NO_WORKER_BONDED, WORKER_NOT_RUNNING +from constant_sorrow.constants import FULL, WORKER_NOT_RUNNING from decimal import Decimal from eth_tester.exceptions import TransactionFailed as TestTransactionFailed from eth_utils import to_canonical_address, to_checksum_address @@ -817,7 +817,7 @@ class Staker(NucypherTokenActor): self.log = Logger("staker") self.is_me = is_me - self.__worker_address = None + self._worker_address = None # Blockchain self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=self.registry) @@ -1268,21 +1268,17 @@ class Staker(NucypherTokenActor): else: receipt = self.staking_agent.bond_worker(staker_address=self.checksum_address, worker_address=worker_address) - self.__worker_address = worker_address + self._worker_address = worker_address return receipt @property def worker_address(self) -> str: - if self.__worker_address: + if not self._worker_address: # TODO: This is broken for StakeHolder with different stakers - See #1358 - return self.__worker_address - else: worker_address = self.staking_agent.get_worker_from_staker(staker_address=self.checksum_address) - self.__worker_address = worker_address + self._worker_address = worker_address - if self.__worker_address == NULL_ADDRESS: - return NO_WORKER_BONDED.bool_value(False) - return self.__worker_address + return self._worker_address @only_me @save_receipt @@ -1291,7 +1287,7 @@ class Staker(NucypherTokenActor): receipt = self.preallocation_escrow_agent.release_worker() else: receipt = self.staking_agent.release_worker(staker_address=self.checksum_address) - self.__worker_address = NULL_ADDRESS + self._worker_address = NULL_ADDRESS return receipt # @@ -1798,6 +1794,7 @@ class StakeHolder(Staker): self.preallocation_escrow_agent = None self.checksum_address = staking_address + self._worker_address = None self.stakes = StakeList(registry=self.registry, checksum_address=staking_address) self.refresh_stakes() diff --git a/nucypher/cli/painting/staking.py b/nucypher/cli/painting/staking.py index 9336df34b..4e4c7e052 100644 --- a/nucypher/cli/painting/staking.py +++ b/nucypher/cli/painting/staking.py @@ -19,7 +19,7 @@ from typing import List import tabulate from web3.main import Web3 -from nucypher.blockchain.eth.constants import STAKING_ESCROW_CONTRACT_NAME +from nucypher.blockchain.eth.constants import STAKING_ESCROW_CONTRACT_NAME, NULL_ADDRESS from nucypher.blockchain.eth.token import NU, Stake from nucypher.blockchain.eth.utils import datetime_at_period, prettify_eth_amount from nucypher.characters.control.emitters import StdoutEmitter @@ -82,7 +82,8 @@ def paint_stakes(emitter: StdoutEmitter, snippet_with_line = network_snippet + '═'*(line_width-len(network_snippet)+1) emitter.echo(snippet_with_line, bold=True) emitter.echo(f"Staker {staker.checksum_address} ════", bold=True, color='red' if missing else 'green') - emitter.echo(f"Worker {staker.worker_address} ════") + worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'NO_WORKER_BONDED' + emitter.echo(f"Worker {worker} ════", color='red' if staker.worker_address == NULL_ADDRESS else None) emitter.echo(tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data), floatfmt="fancy_grid")) rows = list() diff --git a/tests/acceptance/cli/ursula/test_stake_via_allocation_contract.py b/tests/acceptance/cli/ursula/test_stake_via_allocation_contract.py index 8bf2cb35a..23cb38014 100644 --- a/tests/acceptance/cli/ursula/test_stake_via_allocation_contract.py +++ b/tests/acceptance/cli/ursula/test_stake_via_allocation_contract.py @@ -28,6 +28,7 @@ from web3 import Web3 from nucypher.blockchain.eth.actors import Staker from nucypher.blockchain.eth.agents import (ContractAgency, NucypherTokenAgent, PreallocationEscrowAgent, StakingEscrowAgent) +from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.deployers import PreallocationEscrowDeployer from nucypher.blockchain.eth.registry import InMemoryAllocationRegistry, IndividualAllocationRegistry from nucypher.blockchain.eth.token import NU, Stake, StakeList @@ -257,7 +258,7 @@ def test_stake_unbond_worker(click_runner, individual_allocation=individual_allocation, registry=agency_local_registry) - assert not staker.worker_address + assert staker.worker_address == NULL_ADDRESS # Ok ok, let's bond the worker again. diff --git a/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py b/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py index 39987fc34..16b6622c1 100644 --- a/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py +++ b/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py @@ -25,6 +25,7 @@ import maya from nucypher.blockchain.eth.actors import Staker from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent +from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.token import NU, Stake from nucypher.characters.lawful import Enrico, Ursula from nucypher.cli.literature import SUCCESSFUL_MINTING @@ -696,7 +697,7 @@ def test_stake_unbond_worker(click_runner, checksum_address=manual_staker, registry=agency_local_registry) - assert not staker.worker_address + assert staker.worker_address == NULL_ADDRESS def test_set_min_rate(click_runner, diff --git a/tests/integration/cli/test_stake_cli_functionality.py b/tests/integration/cli/test_stake_cli_functionality.py index 6b3ed5571..537a36524 100644 --- a/tests/integration/cli/test_stake_cli_functionality.py +++ b/tests/integration/cli/test_stake_cli_functionality.py @@ -19,7 +19,7 @@ import pytest from web3 import Web3 from nucypher.blockchain.eth.actors import Staker, StakeHolder -from nucypher.blockchain.eth.constants import MAX_UINT16 +from nucypher.blockchain.eth.constants import MAX_UINT16, NULL_ADDRESS from nucypher.blockchain.eth.token import NU from nucypher.cli.actions.select import select_client_account_for_staking from nucypher.cli.commands.stake import stake, StakeHolderConfigOptions, StakerOptions, TransactingStakerOptions @@ -460,7 +460,7 @@ def test_divide_interactive(click_runner, min_allowed_locked = token_economics.minimum_allowed_locked target_value = min_allowed_locked - mock_staking_agent.get_worker_from_staker.return_value = surrogate_staker.checksum_address + mock_staking_agent.get_worker_from_staker.return_value = NULL_ADDRESS command = ('divide', '--provider', MOCK_PROVIDER_URI, From ef9665170d82b30296a72a304a6f7003a1a20aef Mon Sep 17 00:00:00 2001 From: vzotova Date: Sun, 9 Aug 2020 15:11:43 +0300 Subject: [PATCH 20/26] Show staked tokens in current and next periods for status command --- nucypher/blockchain/eth/actors.py | 10 ++++++++ nucypher/cli/commands/status.py | 3 +-- nucypher/cli/painting/status.py | 39 ++++++++++++++++------------- tests/acceptance/cli/test_status.py | 6 +++-- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index 34496c1e9..3f4d3c7ea 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -1209,6 +1209,11 @@ class Staker(NucypherTokenActor): status = self.staking_agent.is_restaking_locked(staker_address=self.checksum_address) return status + @property + def restake_unlock_period(self) -> int: + period = self.staking_agent.get_restake_unlock_period(staker_address=self.checksum_address) + return period + def disable_restaking(self) -> TxReceipt: receipt = self._set_restaking(value=False) return receipt @@ -1239,6 +1244,11 @@ class Staker(NucypherTokenActor): staked_amount: NuNits = self.staking_agent.non_withdrawable_stake(staker_address=self.checksum_address) return NU.from_nunits(staked_amount) + @property + def last_committed_period(self) -> int: + period = self.staking_agent.get_last_committed_period(staker_address=self.checksum_address) + return period + def mintable_periods(self) -> int: """ Returns number of periods that can be rewarded in the current period. Value in range [0, 2] diff --git a/nucypher/cli/commands/status.py b/nucypher/cli/commands/status.py index a365f01e1..d7b9717ce 100644 --- a/nucypher/cli/commands/status.py +++ b/nucypher/cli/commands/status.py @@ -97,9 +97,8 @@ def stakers(general_config, registry_options, staking_address): """Show relevant information about stakers.""" emitter, registry, blockchain = registry_options.setup(general_config=general_config) staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) - policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=registry) stakers_list = [staking_address] if staking_address else staking_agent.get_stakers() - paint_stakers(emitter=emitter, stakers=stakers_list, staking_agent=staking_agent, policy_agent=policy_agent) + paint_stakers(emitter=emitter, stakers=stakers_list, staking_agent=staking_agent) @status.command(name='locked-tokens') diff --git a/nucypher/cli/painting/status.py b/nucypher/cli/painting/status.py index b9a96a359..796114628 100644 --- a/nucypher/cli/painting/status.py +++ b/nucypher/cli/painting/status.py @@ -22,6 +22,7 @@ import maya from typing import List from web3.main import Web3 +from nucypher.blockchain.eth.actors import Staker from nucypher.blockchain.eth.agents import AdjudicatorAgent, ContractAgency, NucypherTokenAgent, PolicyManagerAgent, \ StakingEscrowAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS @@ -140,33 +141,37 @@ def paint_locked_tokens_status(emitter, agent, periods) -> None: f"Min: {NU.from_nunits(bucket_min)} - Max: {NU.from_nunits(bucket_max)}") -def paint_stakers(emitter, stakers: List[str], staking_agent, policy_agent) -> None: +def paint_stakers(emitter, stakers: List[str], staking_agent: StakingEscrowAgent) -> None: current_period = staking_agent.get_current_period() emitter.echo(f"\nCurrent period: {current_period}") emitter.echo("\n| Stakers |\n") emitter.echo(f"{'Checksum address':42} Staker information") emitter.echo('=' * (42 + 2 + 53)) - for staker in stakers: - nickname, pairs = nickname_from_seed(staker) + for staker_address in stakers: + staker = Staker(is_me=False, checksum_address=staker_address, registry=staking_agent.registry) + nickname, pairs = nickname_from_seed(staker_address) symbols = f"{pairs[0][1]} {pairs[1][1]}" - emitter.echo(f"{staker} {'Nickname:':10} {nickname} {symbols}") - tab = " " * len(staker) + emitter.echo(f"{staker_address} {'Nickname:':10} {nickname} {symbols}") + tab = " " * len(staker_address) - owned_tokens = staking_agent.owned_tokens(staker) - last_committed_period = staking_agent.get_last_committed_period(staker) - worker = staking_agent.get_worker_from_staker(staker) - is_restaking = staking_agent.is_restaking(staker) - is_winding_down = staking_agent.is_winding_down(staker) + owned_tokens = staker.owned_tokens() + last_committed_period = staker.last_committed_period + worker = staker.worker_address + is_restaking = staker.is_restaking + is_winding_down = staker.is_winding_down missing_commitments = current_period - last_committed_period - owned_in_nu = round(NU.from_nunits(owned_tokens), 2) - locked_tokens = round(NU.from_nunits(staking_agent.get_locked_tokens(staker)), 2) + owned_in_nu = round(owned_tokens, 2) + current_locked_tokens = round(staker.locked_tokens(periods=0), 2) + next_locked_tokens = round(staker.locked_tokens(periods=1), 2) - emitter.echo(f"{tab} {'Owned:':10} {owned_in_nu} (Staked: {locked_tokens})") + emitter.echo(f"{tab} {'Owned:':10} {owned_in_nu}") + emitter.echo(f"{tab} Staked in current period: {current_locked_tokens}") + emitter.echo(f"{tab} Staked in next period: {next_locked_tokens}") if is_restaking: - if staking_agent.is_restaking_locked(staker): - unlock_period = staking_agent.get_restake_unlock_period(staker) + if staker.restaking_lock_enabled: + unlock_period = staker.restake_unlock_period emitter.echo(f"{tab} {'Re-staking:':10} Yes (Locked until period: {unlock_period})") else: emitter.echo(f"{tab} {'Re-staking:':10} Yes (Unlocked)") @@ -191,8 +196,8 @@ def paint_stakers(emitter, stakers: List[str], staking_agent, policy_agent) -> N else: emitter.echo(f"{worker}") - fees = prettify_eth_amount(policy_agent.get_fee_amount(staker)) + fees = prettify_eth_amount(staker.calculate_policy_fee()) emitter.echo(f"{tab} Unclaimed fees: {fees}") - min_rate = prettify_eth_amount(policy_agent.get_min_fee_rate(staker)) + min_rate = prettify_eth_amount(staker.min_fee_rate) emitter.echo(f"{tab} Min fee rate: {min_rate}") diff --git a/tests/acceptance/cli/test_status.py b/tests/acceptance/cli/test_status.py index 05a304290..bc017cb9a 100644 --- a/tests/acceptance/cli/test_status.py +++ b/tests/acceptance/cli/test_status.py @@ -86,12 +86,14 @@ def test_nucypher_status_stakers(click_runner, agency_local_registry, stakers): assert result.exit_code == 0 owned_tokens = NU.from_nunits(staking_agent.owned_tokens(staking_address)) - locked_tokens = NU.from_nunits(staking_agent.get_locked_tokens(staking_address)) + current_locked_tokens = NU.from_nunits(staking_agent.get_locked_tokens(staking_address)) + next_locked_tokens = NU.from_nunits(staking_agent.get_locked_tokens(staking_address, 1)) assert re.search(f"^Current period: {staking_agent.get_current_period()}", result.output, re.MULTILINE) assert re.search(r"Worker:\s+" + some_dude.worker_address, result.output, re.MULTILINE) assert re.search(r"Owned:\s+" + str(round(owned_tokens, 2)), result.output, re.MULTILINE) - assert re.search(r"Staked: " + str(round(locked_tokens, 2)), result.output, re.MULTILINE) + assert re.search(r"Staked in current period: " + str(round(current_locked_tokens, 2)), result.output, re.MULTILINE) + assert re.search(r"Staked in next period: " + str(round(next_locked_tokens, 2)), result.output, re.MULTILINE) _minimum, default, _maximum = FEE_RATE_RANGE assert f"Min fee rate: {default} wei" in result.output From 434b7c677bbbd3fc755bacd2678be08ea73284a1 Mon Sep 17 00:00:00 2001 From: vzotova Date: Tue, 11 Aug 2020 20:28:20 +0300 Subject: [PATCH 21/26] `nucypher stake list`: show sub-stake status, rename `--all` option to `--show-all` --- nucypher/blockchain/eth/token.py | 3 +- nucypher/cli/commands/stake.py | 6 +- nucypher/cli/painting/staking.py | 2 +- .../cli/test_stake_cli_functionality.py | 388 ++++++++++++------ 4 files changed, 275 insertions(+), 124 deletions(-) diff --git a/nucypher/blockchain/eth/token.py b/nucypher/blockchain/eth/token.py index 839f98dd5..c1f25fadf 100644 --- a/nucypher/blockchain/eth/token.py +++ b/nucypher/blockchain/eth/token.py @@ -360,7 +360,8 @@ class Stake: value=str(self.value), remaining=self.periods_remaining, enactment=start_datetime, - last_period=end_datetime) + last_period=end_datetime, + status=self.status().name) return data # diff --git a/nucypher/cli/commands/stake.py b/nucypher/cli/commands/stake.py index 187779071..561239db7 100644 --- a/nucypher/cli/commands/stake.py +++ b/nucypher/cli/commands/stake.py @@ -320,13 +320,13 @@ def config(general_config, config_file, config_options): @stake.command('list') @group_staker_options @option_config_file -@click.option('--all', help="List all stakes, including unlocked", is_flag=True) +@click.option('--show-all', help="List all stakes, including unlocked and inactive", is_flag=True) @group_general_config -def list_stakes(general_config, staker_options, config_file, all): +def list_stakes(general_config, staker_options, config_file, show_all): """List active stakes for current stakeholder.""" emitter = setup_emitter(general_config) STAKEHOLDER = staker_options.create_character(emitter, config_file) - paint_all_stakes(emitter=emitter, stakeholder=STAKEHOLDER, paint_unlocked=all) + paint_all_stakes(emitter=emitter, stakeholder=STAKEHOLDER, paint_unlocked=show_all) @stake.command() diff --git a/nucypher/cli/painting/staking.py b/nucypher/cli/painting/staking.py index 4e4c7e052..e7a40aba4 100644 --- a/nucypher/cli/painting/staking.py +++ b/nucypher/cli/painting/staking.py @@ -26,7 +26,7 @@ from nucypher.characters.control.emitters import StdoutEmitter from nucypher.cli.literature import POST_STAKING_ADVICE from nucypher.cli.painting.transactions import paint_receipt_summary -STAKE_TABLE_COLUMNS = ('Idx', 'Value', 'Remaining', 'Enactment', 'Termination') +STAKE_TABLE_COLUMNS = ('Idx', 'Value', 'Remaining', 'Enactment', 'Termination', 'Status') STAKER_TABLE_COLUMNS = ('Status', 'Restaking', 'Winding Down', 'Unclaimed Fees', 'Min fee rate') diff --git a/tests/integration/cli/test_stake_cli_functionality.py b/tests/integration/cli/test_stake_cli_functionality.py index 537a36524..c803fab5e 100644 --- a/tests/integration/cli/test_stake_cli_functionality.py +++ b/tests/integration/cli/test_stake_cli_functionality.py @@ -14,13 +14,15 @@ You should have received a copy of the GNU Affero General Public License along with nucypher. If not, see . """ +import re import pytest from web3 import Web3 from nucypher.blockchain.eth.actors import Staker, StakeHolder from nucypher.blockchain.eth.constants import MAX_UINT16, NULL_ADDRESS -from nucypher.blockchain.eth.token import NU +from nucypher.blockchain.eth.token import NU, Stake +from nucypher.blockchain.eth.utils import datetime_at_period from nucypher.cli.actions.select import select_client_account_for_staking from nucypher.cli.commands.stake import stake, StakeHolderConfigOptions, StakerOptions, TransactingStakerOptions from nucypher.cli.literature import ( @@ -34,40 +36,87 @@ from nucypher.cli.literature import ( INSUFFICIENT_BALANCE_TO_CREATE, ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE, CONFIRM_MERGE, SUCCESSFUL_STAKES_MERGE ) from nucypher.config.constants import TEMPORARY_DOMAIN -from nucypher.types import SubStakeInfo +from nucypher.types import SubStakeInfo, StakerInfo from tests.constants import MOCK_PROVIDER_URI, YES, INSECURE_DEVELOPMENT_PASSWORD, NO @pytest.fixture() -def surrogate_staker(mock_testerchain, test_registry, mock_staking_agent): - address = mock_testerchain.etherbase_account - staker = Staker(is_me=True, checksum_address=address, registry=test_registry) +def surrogate_stakers(mock_testerchain, test_registry, mock_staking_agent): + address_1 = mock_testerchain.etherbase_account + address_2 = mock_testerchain.unassigned_accounts[0] + mock_staking_agent.get_all_stakes.return_value = [] - return staker + def get_missing_commitments(checksum_address): + if checksum_address == address_2: + return 0 + else: + return 1 + mock_staking_agent.get_missing_commitments.side_effect = get_missing_commitments + + return address_1, address_2 @pytest.fixture() -def surrogate_stakes(mock_staking_agent, token_economics, surrogate_staker): - nu = 2 * token_economics.minimum_allowed_locked + 1 +def surrogate_stakes(mock_staking_agent, token_economics, surrogate_stakers): + value = 2 * token_economics.minimum_allowed_locked + 1 current_period = 10 duration = token_economics.minimum_locked_periods + 1 final_period = current_period + duration - stakes = [SubStakeInfo(current_period - 1, final_period - 1, nu), - SubStakeInfo(current_period - 1, final_period, nu), - SubStakeInfo(current_period + 1, final_period, nu)] + stakes_1 = [SubStakeInfo(current_period - 1, final_period - 1, value), + SubStakeInfo(current_period - 1, final_period, value), + SubStakeInfo(current_period + 1, final_period, value), + SubStakeInfo(current_period - 2, current_period, value // 2), + SubStakeInfo(current_period - 2, current_period + 1, value // 2), + SubStakeInfo(current_period - 2, current_period - 1, value), + SubStakeInfo(current_period - 2, current_period - 2, value)] + stakes_2 = [SubStakeInfo(current_period - 2, final_period + 2, value)] mock_staking_agent.get_current_period.return_value = current_period def get_all_stakes(staker_address): - return stakes if staker_address == surrogate_staker.checksum_address else [] + if staker_address == surrogate_stakers[0]: + return stakes_1 + elif staker_address == surrogate_stakers[1]: + return stakes_2 + else: + return [] mock_staking_agent.get_all_stakes.side_effect = get_all_stakes def get_substake_info(staker_address, stake_index): - return stakes[stake_index] if staker_address == surrogate_staker.checksum_address else [] + if staker_address == surrogate_stakers[0]: + return stakes_1[stake_index] + elif staker_address == surrogate_stakers[1]: + return stakes_2[stake_index] + else: + return [] mock_staking_agent.get_substake_info.side_effect = get_substake_info - return stakes + # only for calculating sub-stake status + def get_staker_info(staker_address): + if staker_address == surrogate_stakers[0]: + return StakerInfo(value=0, + current_committed_period=current_period-1, + next_committed_period=current_period, + last_committed_period=0, + lock_restake_until_period=0, + completed_work=0, + worker_start_period=0, + worker=NULL_ADDRESS, + flags=bytes(0)) + else: + return StakerInfo(value=0, + current_committed_period=0, + next_committed_period=0, + last_committed_period=0, + lock_restake_until_period=0, + completed_work=0, + worker_start_period=0, + worker=NULL_ADDRESS, + flags=bytes(0)) + mock_staking_agent.get_staker_info.side_effect = get_staker_info + + return stakes_1, stakes_2 @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") @@ -121,7 +170,7 @@ def test_stakeholder_configuration(test_emitter, test_registry, mock_testerchain @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_no_token_reward(click_runner, surrogate_staker, mock_staking_agent): +def test_no_token_reward(click_runner, surrogate_stakers, mock_staking_agent): # No tokens at all mock_staking_agent.calculate_staking_reward.return_value = 0 @@ -130,20 +179,20 @@ def test_no_token_reward(click_runner, surrogate_staker, mock_staking_agent): '--staking-reward', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(stake, collection_args, input=user_input, catch_exceptions=False) assert result.exit_code == 1 assert NO_TOKENS_TO_WITHDRAW in result.output - mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_staking_agent.collect_staking_reward.assert_not_called() mock_staking_agent.assert_no_transactions() @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_collecting_token_reward(click_runner, surrogate_staker, mock_staking_agent, mocker): +def test_collecting_token_reward(click_runner, surrogate_stakers, mock_staking_agent, mocker): mock_mintable_periods = mocker.spy(Staker, 'mintable_periods') # Collect some reward @@ -157,22 +206,22 @@ def test_collecting_token_reward(click_runner, surrogate_staker, mock_staking_ag '--staking-reward', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(stake, collection_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 assert COLLECTING_TOKEN_REWARD.format(reward_amount=reward) in result.output - mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.collect_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.collect_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_mintable_periods.assert_not_called() mock_staking_agent.assert_only_transactions([mock_staking_agent.collect_staking_reward]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_collecting_whole_reward_with_warning(click_runner, surrogate_staker, mock_staking_agent, mocker): +def test_collecting_whole_reward_with_warning(click_runner, surrogate_stakers, mock_staking_agent, mocker): mock_mintable_periods = mocker.spy(Staker, 'mintable_periods') # Collect last portion of NU with warning about periods to mint @@ -188,7 +237,7 @@ def test_collecting_whole_reward_with_warning(click_runner, surrogate_staker, mo '--staking-reward', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = '\n'.join((INSECURE_DEVELOPMENT_PASSWORD, YES)) result = click_runner.invoke(stake, collection_args, input=user_input, catch_exceptions=False) @@ -196,18 +245,18 @@ def test_collecting_whole_reward_with_warning(click_runner, surrogate_staker, mo assert COLLECTING_TOKEN_REWARD.format(reward_amount=reward) in result.output assert CONFIRM_COLLECTING_WITHOUT_MINTING in result.output - mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.collect_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.collect_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_staking_agent.get_current_period.assert_called() - mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_mintable_periods.assert_called_once() mock_staking_agent.assert_only_transactions([mock_staking_agent.collect_staking_reward]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_collecting_whole_reward_without_warning(click_runner, surrogate_staker, mock_staking_agent, mocker): +def test_collecting_whole_reward_without_warning(click_runner, surrogate_stakers, mock_staking_agent, mocker): mock_mintable_periods = mocker.spy(Staker, 'mintable_periods') # Collect last portion of NU without warning @@ -222,7 +271,7 @@ def test_collecting_whole_reward_without_warning(click_runner, surrogate_staker, '--staking-reward', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(stake, collection_args, input=user_input, catch_exceptions=False) @@ -230,18 +279,18 @@ def test_collecting_whole_reward_without_warning(click_runner, surrogate_staker, assert COLLECTING_TOKEN_REWARD.format(reward_amount=reward) in result.output assert CONFIRM_COLLECTING_WITHOUT_MINTING not in result.output - mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.collect_staking_reward.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.calculate_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.collect_staking_reward.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_staking_agent.get_current_period.assert_called() - mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_mintable_periods.assert_called_once() mock_staking_agent.assert_only_transactions([mock_staking_agent.collect_staking_reward]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_no_policy_fee(click_runner, surrogate_staker, mock_policy_manager_agent): +def test_no_policy_fee(click_runner, surrogate_stakers, mock_policy_manager_agent): mock_policy_manager_agent.get_fee_amount.return_value = 0 collection_args = ('collect-reward', @@ -249,20 +298,20 @@ def test_no_policy_fee(click_runner, surrogate_staker, mock_policy_manager_agent '--no-staking-reward', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(stake, collection_args, input=user_input, catch_exceptions=False) assert result.exit_code == 1 assert NO_FEE_TO_WITHDRAW in result.output - mock_policy_manager_agent.get_fee_amount.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_policy_manager_agent.get_fee_amount.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_policy_manager_agent.collect_policy_fee.assert_not_called() mock_policy_manager_agent.assert_no_transactions() @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_collecting_fee(click_runner, surrogate_staker, mock_policy_manager_agent): +def test_collecting_fee(click_runner, surrogate_stakers, mock_policy_manager_agent): fee_amount_eth = 11 mock_policy_manager_agent.get_fee_amount.return_value = Web3.toWei(fee_amount_eth, 'ether') @@ -271,20 +320,20 @@ def test_collecting_fee(click_runner, surrogate_staker, mock_policy_manager_agen '--no-staking-reward', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(stake, collection_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 assert COLLECTING_ETH_FEE.format(fee_amount=fee_amount_eth) in result.output - mock_policy_manager_agent.get_fee_amount.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_policy_manager_agent.get_fee_amount.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_policy_manager_agent.collect_policy_fee.assert_called_once() mock_policy_manager_agent.assert_only_transactions([mock_policy_manager_agent.collect_policy_fee]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_nothing_to_mint(click_runner, surrogate_staker, mock_staking_agent, mocker): +def test_nothing_to_mint(click_runner, surrogate_stakers, mock_staking_agent, mocker): mock_mintable_periods = mocker.spy(Staker, 'mintable_periods') mock_staking_agent.get_current_committed_period.return_value = 0 mock_staking_agent.get_next_committed_period.return_value = 0 @@ -292,7 +341,7 @@ def test_nothing_to_mint(click_runner, surrogate_staker, mock_staking_agent, moc mint_command = ('mint', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = INSECURE_DEVELOPMENT_PASSWORD result = click_runner.invoke(stake, mint_command, input=user_input, catch_exceptions=False) @@ -301,14 +350,14 @@ def test_nothing_to_mint(click_runner, surrogate_staker, mock_staking_agent, moc mock_staking_agent.non_withdrawable_stake.assert_not_called() mock_staking_agent.get_current_period.assert_called() - mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_mintable_periods.assert_called_once() mock_staking_agent.assert_no_transactions() @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_mint_with_warning(click_runner, surrogate_staker, mock_staking_agent, mocker): +def test_mint_with_warning(click_runner, surrogate_stakers, mock_staking_agent, mocker): mock_mintable_periods = mocker.spy(Staker, 'mintable_periods') mock_staking_agent.get_current_period.return_value = 10 mock_staking_agent.get_current_committed_period.return_value = 9 @@ -318,7 +367,7 @@ def test_mint_with_warning(click_runner, surrogate_staker, mock_staking_agent, m mint_command = ('mint', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = '\n'.join((INSECURE_DEVELOPMENT_PASSWORD, YES)) result = click_runner.invoke(stake, mint_command, input=user_input, catch_exceptions=False) @@ -326,16 +375,16 @@ def test_mint_with_warning(click_runner, surrogate_staker, mock_staking_agent, m assert STILL_LOCKED_TOKENS in result.output assert CONFIRM_MINTING.format(mintable_periods=2) in result.output - mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_staking_agent.get_current_period.assert_called() - mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_mintable_periods.assert_called_once() mock_staking_agent.assert_only_transactions([mock_staking_agent.mint]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") -def test_mint_without_warning(click_runner, surrogate_staker, mock_staking_agent, mocker): +def test_mint_without_warning(click_runner, surrogate_stakers, mock_staking_agent, mocker): mock_mintable_periods = mocker.spy(Staker, 'mintable_periods') mock_staking_agent.get_current_period.return_value = 10 mock_staking_agent.get_current_committed_period.return_value = 0 @@ -345,7 +394,7 @@ def test_mint_without_warning(click_runner, surrogate_staker, mock_staking_agent mint_command = ('mint', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[0]) user_input = '\n'.join((INSECURE_DEVELOPMENT_PASSWORD, YES)) result = click_runner.invoke(stake, mint_command, input=user_input, catch_exceptions=False) @@ -353,10 +402,10 @@ def test_mint_without_warning(click_runner, surrogate_staker, mock_staking_agent assert STILL_LOCKED_TOKENS not in result.output assert CONFIRM_MINTING.format(mintable_periods=1) in result.output - mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.non_withdrawable_stake.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_staking_agent.get_current_period.assert_called() - mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) - mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_staker.checksum_address) + mock_staking_agent.get_current_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) + mock_staking_agent.get_next_committed_period.assert_called_once_with(staker_address=surrogate_stakers[0]) mock_mintable_periods.assert_called_once() mock_staking_agent.assert_only_transactions([mock_staking_agent.mint]) @@ -364,7 +413,7 @@ def test_mint_without_warning(click_runner, surrogate_staker, mock_staking_agent @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_prolong_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, @@ -374,7 +423,7 @@ def test_prolong_interactive(click_runner, selected_index = 0 sub_stake_index = 1 lock_periods = 10 - final_period = surrogate_stakes[sub_stake_index][1] + final_period = surrogate_stakes[selected_index][sub_stake_index][1] command = ('prolong', '--provider', MOCK_PROVIDER_URI, @@ -395,32 +444,33 @@ def test_prolong_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.prolong_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.prolong_stake.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index, periods=lock_periods) mock_staking_agent.assert_only_transactions([mock_staking_agent.prolong_stake]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_prolong_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') + selected_index = 0 sub_stake_index = 1 lock_periods = 10 - final_period = surrogate_stakes[sub_stake_index][1] + final_period = surrogate_stakes[selected_index][sub_stake_index][1] command = ('prolong', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[0], '--index', sub_stake_index, '--lock-periods', lock_periods, '--force') @@ -436,18 +486,18 @@ def test_prolong_non_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.prolong_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.prolong_stake.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index, periods=lock_periods) mock_staking_agent.assert_only_transactions([mock_staking_agent.prolong_stake]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_divide_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, @@ -455,7 +505,7 @@ def test_divide_interactive(click_runner, mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') selected_index = 0 - sub_stake_index = len(surrogate_stakes) - 1 + sub_stake_index = 1 lock_periods = 10 min_allowed_locked = token_economics.minimum_allowed_locked target_value = min_allowed_locked @@ -483,36 +533,36 @@ def test_divide_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.divide_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.divide_stake.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index, target_value=target_value, periods=lock_periods) mock_staking_agent.assert_only_transactions([mock_staking_agent.divide_stake]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_divide_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') - sub_stake_index = len(surrogate_stakes) - 1 + sub_stake_index = 1 lock_periods = 10 min_allowed_locked = token_economics.minimum_allowed_locked target_value = min_allowed_locked - mock_staking_agent.get_worker_from_staker.return_value = surrogate_staker.checksum_address + mock_staking_agent.get_worker_from_staker.return_value = surrogate_stakers[0] command = ('divide', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[0], '--index', sub_stake_index, '--lock-periods', lock_periods, '--value', NU.from_nunits(target_value).to_tokens(), @@ -530,19 +580,19 @@ def test_divide_non_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.divide_stake.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.divide_stake.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index, target_value=target_value, periods=lock_periods) mock_staking_agent.assert_only_transactions([mock_staking_agent.divide_stake]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_increase_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_token_agent, mock_staking_agent, @@ -551,7 +601,7 @@ def test_increase_interactive(click_runner, mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') selected_index = 0 - sub_stake_index = len(surrogate_stakes) - 1 + sub_stake_index = 1 additional_value = NU.from_nunits(token_economics.minimum_allowed_locked // 10) mock_token_agent.get_balance.return_value = 0 @@ -596,13 +646,13 @@ def test_increase_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.deposit_and_increase.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.deposit_and_increase.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index, amount=additional_value.to_nunits()) mock_staking_agent.assert_only_transactions([mock_staking_agent.deposit_and_increase]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index) - mock_token_agent.increase_allowance.assert_called_once_with(sender_address=surrogate_staker.checksum_address, + mock_token_agent.increase_allowance.assert_called_once_with(sender_address=surrogate_stakers[0], spender_address=mock_staking_agent.contract.address, increase=additional_value.to_nunits()) mock_token_agent.assert_only_transactions([mock_token_agent.increase_allowance]) @@ -611,7 +661,7 @@ def test_increase_interactive(click_runner, @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_increase_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_token_agent, mock_staking_agent, @@ -619,7 +669,7 @@ def test_increase_non_interactive(click_runner, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') - sub_stake_index = len(surrogate_stakes) - 1 + sub_stake_index = 1 additional_value = NU.from_nunits(token_economics.minimum_allowed_locked // 10) locked_tokens = token_economics.minimum_allowed_locked * 5 @@ -629,7 +679,7 @@ def test_increase_non_interactive(click_runner, command = ('increase', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[0], '--index', sub_stake_index, '--value', additional_value.to_tokens(), '--force') @@ -647,13 +697,13 @@ def test_increase_non_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.deposit_and_increase.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.deposit_and_increase.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index, amount=additional_value.to_nunits()) mock_staking_agent.assert_only_transactions([mock_staking_agent.deposit_and_increase]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[0], stake_index=sub_stake_index) - mock_token_agent.increase_allowance.assert_called_once_with(sender_address=surrogate_staker.checksum_address, + mock_token_agent.increase_allowance.assert_called_once_with(sender_address=surrogate_stakers[0], spender_address=mock_staking_agent.contract.address, increase=additional_value.to_nunits()) mock_token_agent.assert_only_transactions([mock_token_agent.increase_allowance]) @@ -662,7 +712,7 @@ def test_increase_non_interactive(click_runner, @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_increase_lock_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, @@ -717,24 +767,25 @@ def test_increase_lock_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.lock_and_increase.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.lock_and_increase.assert_called_once_with(staker_address=surrogate_stakers[selected_index], stake_index=sub_stake_index, amount=additional_value.to_nunits()) mock_staking_agent.assert_only_transactions([mock_staking_agent.lock_and_increase]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[selected_index], stake_index=sub_stake_index) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_increase_lock_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') + selected_index = 0 sub_stake_index = len(surrogate_stakes) - 1 additional_value = NU.from_nunits(token_economics.minimum_allowed_locked // 10) @@ -745,7 +796,7 @@ def test_increase_lock_non_interactive(click_runner, command = ('increase', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[selected_index], '--index', sub_stake_index, '--value', additional_value.to_tokens(), '--only-lock', @@ -764,18 +815,18 @@ def test_increase_lock_non_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.lock_and_increase.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.lock_and_increase.assert_called_once_with(staker_address=surrogate_stakers[selected_index], stake_index=sub_stake_index, amount=additional_value.to_nunits()) mock_staking_agent.assert_only_transactions([mock_staking_agent.lock_and_increase]) - mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.get_substake_info.assert_called_once_with(staker_address=surrogate_stakers[selected_index], stake_index=sub_stake_index) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_create_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_token_agent, mock_staking_agent, @@ -832,7 +883,7 @@ def test_create_interactive(click_runner, assert PROMPT_STAKE_CREATE_LOCK_PERIODS.format(min_locktime=min_locktime, max_locktime=max_locktime) in result.output assert CONFIRM_STAGED_STAKE.format(nunits=str(value.to_nunits()), tokens=value, - staker_address=surrogate_staker.checksum_address, + staker_address=surrogate_stakers[selected_index], lock_periods=lock_periods) in result.output assert CONFIRM_BROADCAST_CREATE_STAKE in result.output assert PROMPT_DEPOSIT_OR_LOCK in result.output @@ -844,7 +895,7 @@ def test_create_interactive(click_runner, mock_refresh_stakes.assert_called() mock_token_agent.approve_and_call.assert_called_once_with(amount=value.to_nunits(), target_address=mock_staking_agent.contract_address, - sender_address=surrogate_staker.checksum_address, + sender_address=surrogate_stakers[selected_index], call_data=Web3.toBytes(lock_periods)) mock_token_agent.assert_only_transactions([mock_token_agent.approve_and_call]) mock_staking_agent.assert_no_transactions() @@ -853,7 +904,7 @@ def test_create_interactive(click_runner, @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_create_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_token_agent, mock_staking_agent, @@ -861,6 +912,8 @@ def test_create_non_interactive(click_runner, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') + selected_index = 0 + lock_periods = token_economics.minimum_locked_periods value = NU.from_nunits(token_economics.minimum_allowed_locked * 2) @@ -871,7 +924,7 @@ def test_create_non_interactive(click_runner, command = ('create', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[selected_index], '--lock-periods', lock_periods, '--value', value.to_tokens(), '--force') @@ -888,7 +941,7 @@ def test_create_non_interactive(click_runner, assert PROMPT_STAKE_CREATE_LOCK_PERIODS.format(min_locktime=min_locktime, max_locktime=max_locktime) not in result.output assert CONFIRM_STAGED_STAKE.format(nunits=str(value.to_nunits()), tokens=value, - staker_address=surrogate_staker.checksum_address, + staker_address=surrogate_stakers[selected_index], lock_periods=lock_periods) not in result.output assert CONFIRM_BROADCAST_CREATE_STAKE in result.output assert PROMPT_DEPOSIT_OR_LOCK not in result.output @@ -900,7 +953,7 @@ def test_create_non_interactive(click_runner, mock_refresh_stakes.assert_called() mock_token_agent.approve_and_call.assert_called_once_with(amount=value.to_nunits(), target_address=mock_staking_agent.contract_address, - sender_address=surrogate_staker.checksum_address, + sender_address=surrogate_stakers[selected_index], call_data=Web3.toBytes(lock_periods)) mock_token_agent.assert_only_transactions([mock_token_agent.approve_and_call]) mock_staking_agent.assert_no_transactions() @@ -909,7 +962,7 @@ def test_create_non_interactive(click_runner, @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_create_lock_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, @@ -965,7 +1018,7 @@ def test_create_lock_interactive(click_runner, assert PROMPT_STAKE_CREATE_LOCK_PERIODS.format(min_locktime=min_locktime, max_locktime=max_locktime) in result.output assert CONFIRM_STAGED_STAKE.format(nunits=str(value.to_nunits()), tokens=value, - staker_address=surrogate_staker.checksum_address, + staker_address=surrogate_stakers[selected_index], lock_periods=lock_periods) in result.output assert CONFIRM_BROADCAST_CREATE_STAKE in result.output assert PROMPT_DEPOSIT_OR_LOCK in result.output @@ -977,20 +1030,22 @@ def test_create_lock_interactive(click_runner, mock_refresh_stakes.assert_called() mock_staking_agent.lock_and_create.assert_called_once_with(amount=value.to_nunits(), lock_periods=lock_periods, - staker_address=surrogate_staker.checksum_address) + staker_address=surrogate_stakers[selected_index]) mock_staking_agent.assert_only_transactions([mock_staking_agent.lock_and_create]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_create_lock_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') + selected_index = 0 + lock_periods = token_economics.minimum_locked_periods value = NU.from_nunits(token_economics.minimum_allowed_locked * 11) @@ -1001,7 +1056,7 @@ def test_create_lock_non_interactive(click_runner, command = ('create', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[selected_index], '--lock-periods', lock_periods, '--value', value.to_tokens(), '--only-lock', @@ -1020,7 +1075,7 @@ def test_create_lock_non_interactive(click_runner, assert PROMPT_STAKE_CREATE_LOCK_PERIODS.format(min_locktime=min_locktime, max_locktime=max_locktime) not in result.output assert CONFIRM_STAGED_STAKE.format(nunits=str(value.to_nunits()), tokens=value, - staker_address=surrogate_staker.checksum_address, + staker_address=surrogate_stakers[selected_index], lock_periods=lock_periods) not in result.output assert CONFIRM_BROADCAST_CREATE_STAKE in result.output assert PROMPT_DEPOSIT_OR_LOCK not in result.output @@ -1032,14 +1087,14 @@ def test_create_lock_non_interactive(click_runner, mock_refresh_stakes.assert_called() mock_staking_agent.lock_and_create.assert_called_once_with(amount=value.to_nunits(), lock_periods=lock_periods, - staker_address=surrogate_staker.checksum_address) + staker_address=surrogate_stakers[selected_index]) mock_staking_agent.assert_only_transactions([mock_staking_agent.lock_and_create]) @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_merge_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, @@ -1063,7 +1118,7 @@ def test_merge_interactive(click_runner, result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 - final_period = surrogate_stakes[sub_stake_index_1].last_period + final_period = surrogate_stakes[selected_index][sub_stake_index_1].last_period assert ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE.format(final_period=final_period) in result.output assert CONFIRM_MERGE.format(stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) in result.output assert SUCCESSFUL_STAKES_MERGE in result.output @@ -1071,7 +1126,7 @@ def test_merge_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.merge_stakes.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.merge_stakes.assert_called_once_with(staker_address=surrogate_stakers[selected_index], stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) mock_staking_agent.assert_only_transactions([mock_staking_agent.merge_stakes]) @@ -1080,20 +1135,21 @@ def test_merge_interactive(click_runner, @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_merge_partially_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') + selected_index = 0 sub_stake_index_1 = 1 sub_stake_index_2 = 2 base_command = ('merge', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address) + '--staking-address', surrogate_stakers[selected_index]) user_input = '\n'.join((str(sub_stake_index_2), YES, INSECURE_DEVELOPMENT_PASSWORD)) @@ -1102,7 +1158,7 @@ def test_merge_partially_interactive(click_runner, result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 - final_period = surrogate_stakes[sub_stake_index_1].last_period + final_period = surrogate_stakes[selected_index][sub_stake_index_1].last_period assert ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE.format(final_period=final_period) in result.output assert CONFIRM_MERGE.format(stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) in result.output assert SUCCESSFUL_STAKES_MERGE in result.output @@ -1111,7 +1167,7 @@ def test_merge_partially_interactive(click_runner, result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 - final_period = surrogate_stakes[sub_stake_index_1].last_period + final_period = surrogate_stakes[selected_index][sub_stake_index_1].last_period assert ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE.format(final_period=final_period) in result.output assert CONFIRM_MERGE.format(stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) in result.output assert SUCCESSFUL_STAKES_MERGE in result.output @@ -1119,7 +1175,7 @@ def test_merge_partially_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.merge_stakes.assert_called_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.merge_stakes.assert_called_with(staker_address=surrogate_stakers[selected_index], stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) mock_staking_agent.assert_only_transactions([mock_staking_agent.merge_stakes]) @@ -1128,13 +1184,14 @@ def test_merge_partially_interactive(click_runner, @pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") def test_merge_non_interactive(click_runner, mocker, - surrogate_staker, + surrogate_stakers, surrogate_stakes, mock_staking_agent, token_economics, mock_testerchain): mock_refresh_stakes = mocker.spy(Staker, 'refresh_stakes') + selected_index = 0 sub_stake_index_1 = 1 sub_stake_index_2 = 2 @@ -1145,7 +1202,7 @@ def test_merge_non_interactive(click_runner, command = ('merge', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN, - '--staking-address', surrogate_staker.checksum_address, + '--staking-address', surrogate_stakers[selected_index], '--index-1', sub_stake_index_1, '--index-2', sub_stake_index_2, '--force') @@ -1154,7 +1211,7 @@ def test_merge_non_interactive(click_runner, result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 - final_period = surrogate_stakes[sub_stake_index_1].last_period + final_period = surrogate_stakes[selected_index][sub_stake_index_1].last_period assert ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE.format(final_period=final_period) not in result.output assert CONFIRM_MERGE.format(stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) not in result.output assert SUCCESSFUL_STAKES_MERGE in result.output @@ -1162,7 +1219,100 @@ def test_merge_non_interactive(click_runner, mock_staking_agent.get_all_stakes.assert_called() mock_staking_agent.get_current_period.assert_called() mock_refresh_stakes.assert_called() - mock_staking_agent.merge_stakes.assert_called_once_with(staker_address=surrogate_staker.checksum_address, + mock_staking_agent.merge_stakes.assert_called_once_with(staker_address=surrogate_stakers[selected_index], stake_index_1=sub_stake_index_1, stake_index_2=sub_stake_index_2) mock_staking_agent.assert_only_transactions([mock_staking_agent.merge_stakes]) + + +@pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") +def test_stake_list_active(click_runner, surrogate_stakers, surrogate_stakes, token_economics): + + command = ('list', + '--provider', MOCK_PROVIDER_URI, + '--network', TEMPORARY_DOMAIN,) + + user_input = None + result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) + assert result.exit_code == 0 + + assert re.search("Status\\s+Missing 1 commitments", result.output, re.MULTILINE) + assert re.search("Status\\s+Committed #1", result.output, re.MULTILINE) + + statuses = [Stake.Status.DIVISIBLE, + Stake.Status.DIVISIBLE, + Stake.Status.DIVISIBLE, + Stake.Status.LOCKED, + Stake.Status.EDITABLE, + Stake.Status.UNLOCKED, + Stake.Status.INACTIVE] + + current_period = 10 + for stakes in surrogate_stakes: + for index, sub_stake in enumerate(stakes): + value = NU.from_nunits(sub_stake.locked_value) + remaining = sub_stake.last_period - current_period + 1 + start_datetime = datetime_at_period(period=sub_stake.first_period, + seconds_per_period=token_economics.seconds_per_period, + start_of_period=True) + unlock_datetime = datetime_at_period(period=sub_stake.last_period + 1, + seconds_per_period=token_economics.seconds_per_period, + start_of_period=True) + enactment = start_datetime.local_datetime().strftime("%b %d %Y") + termination = unlock_datetime.local_datetime().strftime("%b %d %Y") + search = f"{index}\\s+│\\s+" \ + f"{value}\\s+│\\s+" \ + f"{remaining}\\s+│\\s+" \ + f"{enactment}\\s+│\\s+" \ + f"{termination}\\s+│\\s+" \ + f"{statuses[index].name}" + # locked sub-stakes + if index < 5: + assert re.search(search, result.output, re.MULTILINE) + # unlocked sub-stakes + else: + assert not re.search(search, result.output, re.MULTILINE) + + +@pytest.mark.usefixtures("test_registry_source_manager", "patch_stakeholder_configuration") +def test_stake_list_all(click_runner, surrogate_stakers, surrogate_stakes, token_economics): + + command = ('list', + '--show-all', + '--provider', MOCK_PROVIDER_URI, + '--network', TEMPORARY_DOMAIN,) + + user_input = None + result = click_runner.invoke(stake, command, input=user_input, catch_exceptions=False) + assert result.exit_code == 0 + + assert re.search("Status\\s+Missing 1 commitments", result.output, re.MULTILINE) + assert re.search("Status\\s+Committed #1", result.output, re.MULTILINE) + + statuses = [Stake.Status.DIVISIBLE, + Stake.Status.DIVISIBLE, + Stake.Status.DIVISIBLE, + Stake.Status.LOCKED, + Stake.Status.EDITABLE, + Stake.Status.UNLOCKED, + Stake.Status.INACTIVE] + + current_period = 10 + for stakes in surrogate_stakes: + for index, sub_stake in enumerate(stakes): + value = NU.from_nunits(sub_stake.locked_value) + remaining = sub_stake.last_period - current_period + 1 + start_datetime = datetime_at_period(period=sub_stake.first_period, + seconds_per_period=token_economics.seconds_per_period, + start_of_period=True) + unlock_datetime = datetime_at_period(period=sub_stake.last_period + 1, + seconds_per_period=token_economics.seconds_per_period, + start_of_period=True) + enactment = start_datetime.local_datetime().strftime("%b %d %Y") + termination = unlock_datetime.local_datetime().strftime("%b %d %Y") + assert re.search(f"{index}\\s+│\\s+" + f"{value}\\s+│\\s+" + f"{remaining}\\s+│\\s+" + f"{enactment}\\s+│\\s+" + f"{termination}\\s+│\\s+" + f"{statuses[index].name}", result.output, re.MULTILINE) From ea6f3be73b83d34671dcc68c6ad377235c0acfc7 Mon Sep 17 00:00:00 2001 From: vzotova Date: Wed, 12 Aug 2020 16:15:44 +0300 Subject: [PATCH 22/26] Fix link to staking guide in `nucypher stake create` --- nucypher/cli/literature.py | 2 +- nucypher/cli/processes.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nucypher/cli/literature.py b/nucypher/cli/literature.py index 3d9f6a10d..b75e8d50c 100644 --- a/nucypher/cli/literature.py +++ b/nucypher/cli/literature.py @@ -118,7 +118,7 @@ POST_STAKING_ADVICE = """ View your stakes by running 'nucypher stake list' or set your Ursula worker node address by running 'nucypher stake bond-worker'. -See https://docs.nucypher.com/en/latest/guides/staking_guide.html +See https://docs.nucypher.com/en/latest/guides/network_node/staking_guide.html """ # diff --git a/nucypher/cli/processes.py b/nucypher/cli/processes.py index ae9d743d6..52b2af543 100644 --- a/nucypher/cli/processes.py +++ b/nucypher/cli/processes.py @@ -56,7 +56,6 @@ class UrsulaCommandProtocol(LineReceiver): 'status': self.paintStatus, 'known_nodes': self.paintKnownNodes, 'fleet_state': self.paintFleetState, - # 'stakes': self.paintStakes, # TODO # Blockchain Control 'commit_next': self.commit_to_next_period, # hidden From f85d1f6b69154cd546fd1057ddbf1732834b5cc0 Mon Sep 17 00:00:00 2001 From: vzotova Date: Thu, 13 Aug 2020 16:59:29 +0300 Subject: [PATCH 23/26] Cashing registry id --- nucypher/blockchain/eth/registry.py | 15 +++++--- .../blockchain/agents/test_contract_agency.py | 36 +++++++++++++++++++ tests/unit/test_registry_basics.py | 14 +++++++- 3 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 tests/acceptance/blockchain/agents/test_contract_agency.py diff --git a/nucypher/blockchain/eth/registry.py b/nucypher/blockchain/eth/registry.py index 1db833b14..1f7aa9e06 100644 --- a/nucypher/blockchain/eth/registry.py +++ b/nucypher/blockchain/eth/registry.py @@ -225,6 +225,7 @@ class BaseContractRegistry(ABC): def __init__(self, source=NO_REGISTRY_SOURCE, *args, **kwargs): self.__source = source self.log = Logger("registry") + self._id = None def __eq__(self, other) -> bool: if self is other: @@ -238,11 +239,11 @@ class BaseContractRegistry(ABC): @property def id(self) -> str: """Returns a hexstr of the registry contents.""" - blake = hashlib.blake2b() - blake.update(self.__class__.__name__.encode()) - blake.update(json.dumps(self.read()).encode()) - digest = blake.digest().hex() - return digest + if not self._id: + blake = hashlib.blake2b() + blake.update(json.dumps(self.read()).encode()) + self._id = blake.digest().hex() + return self._id @abstractmethod def _destroy(self) -> None: @@ -413,6 +414,8 @@ class LocalContractRegistry(BaseContractRegistry): registry_file.write(json.dumps(registry_data)) registry_file.truncate() + self._id = None + def _destroy(self) -> None: os.remove(self.filepath) @@ -468,6 +471,7 @@ class InMemoryContractRegistry(BaseContractRegistry): def write(self, registry_data: list) -> None: self.__registry_data = json.dumps(registry_data) + self._id = None def read(self) -> list: try: @@ -593,6 +597,7 @@ class InMemoryAllocationRegistry(AllocationRegistry): def write(self, registry_data: dict) -> None: self.__registry_data = json.dumps(registry_data) + self._id = None def read(self) -> dict: try: diff --git a/tests/acceptance/blockchain/agents/test_contract_agency.py b/tests/acceptance/blockchain/agents/test_contract_agency.py new file mode 100644 index 000000000..10bc93142 --- /dev/null +++ b/tests/acceptance/blockchain/agents/test_contract_agency.py @@ -0,0 +1,36 @@ +""" + This file is part of nucypher. + + nucypher is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + nucypher is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with nucypher. If not, see . +""" + +import pytest +from decimal import Decimal, InvalidOperation + +from nucypher.blockchain.eth.agents import StakingEscrowAgent, ContractAgency +from nucypher.blockchain.eth.token import NU + + +# TODO rename +def test_get_agent_with_different_registries(token_economics, agency, test_registry, agency_local_registry): + # Get agents using same registry instance + staking_agent_1 = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) + staking_agent_2 = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) + assert staking_agent_2.registry is staking_agent_1.registry is test_registry + assert staking_agent_2 is staking_agent_1 + + # Same content but different classes of registries + staking_agent_2 = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) + assert staking_agent_2.registry is agency_local_registry + assert staking_agent_2 is not staking_agent_1 diff --git a/tests/unit/test_registry_basics.py b/tests/unit/test_registry_basics.py index f6423a49a..bb31a3185 100644 --- a/tests/unit/test_registry_basics.py +++ b/tests/unit/test_registry_basics.py @@ -18,7 +18,7 @@ along with nucypher. If not, see . import pytest from nucypher.blockchain.eth.interfaces import BaseContractRegistry -from nucypher.blockchain.eth.registry import LocalContractRegistry +from nucypher.blockchain.eth.registry import LocalContractRegistry, InMemoryContractRegistry def test_contract_registry(tempfile_path): @@ -35,6 +35,8 @@ def test_contract_registry(tempfile_path): test_registry = LocalContractRegistry(filepath=tempfile_path) assert test_registry.read() == list() + registry_id = test_registry.id + assert test_registry.id == registry_id # Test contract enrollment and dump_chain test_name = 'TestContract' @@ -47,6 +49,10 @@ def test_contract_registry(tempfile_path): contract_abi=test_abi, contract_version=test_version) + assert test_registry.id != registry_id + registry_id = test_registry.id + assert test_registry.id == registry_id + # Search by name... contract_records = test_registry.search(contract_name=test_name) assert len(contract_records) == 1, 'More than one record for {}'.format(test_name) @@ -75,7 +81,13 @@ def test_contract_registry(tempfile_path): # Corrupt the registry with a duplicate address current_dataset.append([test_name, test_addr, test_abi]) test_registry.write(current_dataset) + assert test_registry.id != registry_id # Check that searching for an unknown contract raises with pytest.raises(BaseContractRegistry.InvalidRegistry): test_registry.search(contract_address=test_addr) + + # Check id of new registry with the same content + new_registry = InMemoryContractRegistry() # TODO finish this + new_registry.write(test_registry.read()) + assert new_registry.id == test_registry.id From bf76e2dd6a44e3cba0f317ecd70a4b1d2471d54e Mon Sep 17 00:00:00 2001 From: vzotova Date: Sun, 16 Aug 2020 12:42:58 +0300 Subject: [PATCH 24/26] Change registry field to representation of registry in Agent to highlight immutability of agents --- nucypher/blockchain/eth/agents.py | 24 +++++++++---------- nucypher/blockchain/eth/multisig.py | 1 - nucypher/cli/commands/status.py | 2 +- nucypher/cli/painting/status.py | 6 +++-- .../blockchain/agents/test_contract_agency.py | 7 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 826354e17..e2b56031d 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -100,13 +100,13 @@ class EthereumContractAgent: transaction_gas: Optional[Wei] = None): self.log = Logger(self.__class__.__name__) - self.registry = registry + self.registry_repr = str(registry) self.blockchain = BlockchainInterfaceFactory.get_or_create_interface(provider_uri=provider_uri) if not contract: # Fetch the contract contract = self.blockchain.get_contract_by_name( - registry=self.registry, + registry=registry, contract_name=self.contract_name, proxy_name=self._proxy_name, use_proxy_address=self._forward_address @@ -122,12 +122,12 @@ class EthereumContractAgent: self.log.info("Initialized new {} for {} with {} and {}".format(self.__class__.__name__, self.contract.address, self.blockchain.provider_uri, - self.registry)) + self.registry_repr)) def __repr__(self) -> str: class_name = self.__class__.__name__ r = "{}(registry={}, contract={})" - return r.format(class_name, self.registry, self.contract_name) + return r.format(class_name, self.registry_repr, self.contract_name) def __eq__(self, other: Any) -> bool: return bool(self.contract.address == other.contract.address) @@ -945,6 +945,9 @@ class PreallocationEscrowAgent(EthereumContractAgent): self.__read_principal() self.__read_interface(registry) + self.token_agent: NucypherTokenAgent = ContractAgency.get_agent(NucypherTokenAgent, registry) + self.staking_agent: StakingEscrowAgent = ContractAgency.get_agent(StakingEscrowAgent, registry) + super().__init__(contract=self.principal_contract, registry=registry, *args, **kwargs) def __read_interface(self, registry: BaseContractRegistry) -> None: @@ -1003,18 +1006,15 @@ class PreallocationEscrowAgent(EthereumContractAgent): @property # type: ignore @contract_api(CONTRACT_ATTRIBUTE) def available_balance(self) -> int: - token_agent: NucypherTokenAgent = ContractAgency.get_agent(NucypherTokenAgent, self.registry) - staking_agent: StakingEscrowAgent = ContractAgency.get_agent(StakingEscrowAgent, self.registry) - - overall_balance = token_agent.get_balance(self.principal_contract.address) - seconds_per_period = staking_agent.contract.functions.secondsPerPeriod().call() - current_period = staking_agent.get_current_period() + overall_balance = self.token_agent.get_balance(self.principal_contract.address) + seconds_per_period = self.staking_agent.contract.functions.secondsPerPeriod().call() + current_period = self.staking_agent.get_current_period() end_lock_period = epoch_to_period(self.end_timestamp, seconds_per_period=seconds_per_period) available_balance = overall_balance if current_period <= end_lock_period: - staked_tokens = staking_agent.get_locked_tokens(staker_address=self.principal_contract.address, - periods=end_lock_period - current_period) + staked_tokens = self.staking_agent.get_locked_tokens(staker_address=self.principal_contract.address, + periods=end_lock_period - current_period) if self.unvested_tokens > staked_tokens: # The staked amount is deducted from the locked amount available_balance -= self.unvested_tokens - staked_tokens diff --git a/nucypher/blockchain/eth/multisig.py b/nucypher/blockchain/eth/multisig.py index 86ecc159f..b0bea0331 100644 --- a/nucypher/blockchain/eth/multisig.py +++ b/nucypher/blockchain/eth/multisig.py @@ -81,7 +81,6 @@ class Proposal: if self.data: if not contract: agent = ContractAgency.get_agent(MultiSigAgent, registry=registry) - registry = agent.registry blockchain = agent.blockchain name, version, address, abi = registry.search(contract_address=self.target_address) diff --git a/nucypher/cli/commands/status.py b/nucypher/cli/commands/status.py index d7b9717ce..b3ca5f261 100644 --- a/nucypher/cli/commands/status.py +++ b/nucypher/cli/commands/status.py @@ -98,7 +98,7 @@ def stakers(general_config, registry_options, staking_address): emitter, registry, blockchain = registry_options.setup(general_config=general_config) staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) stakers_list = [staking_address] if staking_address else staking_agent.get_stakers() - paint_stakers(emitter=emitter, stakers=stakers_list, staking_agent=staking_agent) + paint_stakers(emitter=emitter, stakers=stakers_list, registry=registry) @status.command(name='locked-tokens') diff --git a/nucypher/cli/painting/status.py b/nucypher/cli/painting/status.py index 796114628..f73777e97 100644 --- a/nucypher/cli/painting/status.py +++ b/nucypher/cli/painting/status.py @@ -27,6 +27,7 @@ from nucypher.blockchain.eth.agents import AdjudicatorAgent, ContractAgency, Nuc StakingEscrowAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory +from nucypher.blockchain.eth.registry import BaseContractRegistry from nucypher.blockchain.eth.token import NU from nucypher.blockchain.eth.utils import prettify_eth_amount from nucypher.network.nicknames import nickname_from_seed @@ -141,7 +142,8 @@ def paint_locked_tokens_status(emitter, agent, periods) -> None: f"Min: {NU.from_nunits(bucket_min)} - Max: {NU.from_nunits(bucket_max)}") -def paint_stakers(emitter, stakers: List[str], staking_agent: StakingEscrowAgent) -> None: +def paint_stakers(emitter, stakers: List[str], registry: BaseContractRegistry) -> None: + staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) current_period = staking_agent.get_current_period() emitter.echo(f"\nCurrent period: {current_period}") emitter.echo("\n| Stakers |\n") @@ -149,7 +151,7 @@ def paint_stakers(emitter, stakers: List[str], staking_agent: StakingEscrowAgent emitter.echo('=' * (42 + 2 + 53)) for staker_address in stakers: - staker = Staker(is_me=False, checksum_address=staker_address, registry=staking_agent.registry) + staker = Staker(is_me=False, checksum_address=staker_address, registry=registry) nickname, pairs = nickname_from_seed(staker_address) symbols = f"{pairs[0][1]} {pairs[1][1]}" emitter.echo(f"{staker_address} {'Nickname:':10} {nickname} {symbols}") diff --git a/tests/acceptance/blockchain/agents/test_contract_agency.py b/tests/acceptance/blockchain/agents/test_contract_agency.py index 10bc93142..93ad888ae 100644 --- a/tests/acceptance/blockchain/agents/test_contract_agency.py +++ b/tests/acceptance/blockchain/agents/test_contract_agency.py @@ -22,15 +22,14 @@ from nucypher.blockchain.eth.agents import StakingEscrowAgent, ContractAgency from nucypher.blockchain.eth.token import NU -# TODO rename def test_get_agent_with_different_registries(token_economics, agency, test_registry, agency_local_registry): # Get agents using same registry instance staking_agent_1 = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) staking_agent_2 = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) - assert staking_agent_2.registry is staking_agent_1.registry is test_registry + assert staking_agent_2.registry_repr == staking_agent_1.registry_repr == str(test_registry) assert staking_agent_2 is staking_agent_1 # Same content but different classes of registries staking_agent_2 = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) - assert staking_agent_2.registry is agency_local_registry - assert staking_agent_2 is not staking_agent_1 + assert staking_agent_2.registry_repr == str(test_registry) + assert staking_agent_2 is staking_agent_1 From bfa2da251f5ad0e671e7b61fe0b0abc33173dd83 Mon Sep 17 00:00:00 2001 From: vzotova Date: Tue, 18 Aug 2020 12:55:26 +0300 Subject: [PATCH 25/26] Apply suggestions from code review #2170 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: K Prasch Co-authored-by: David Núñez --- nucypher/blockchain/eth/agents.py | 6 +++--- nucypher/cli/commands/stake.py | 2 +- nucypher/cli/painting/staking.py | 2 +- tests/acceptance/blockchain/agents/test_contract_agency.py | 4 ++-- tests/integration/cli/test_stake_cli_functionality.py | 2 +- tests/unit/test_registry_basics.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index e2b56031d..08ed85160 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -100,7 +100,7 @@ class EthereumContractAgent: transaction_gas: Optional[Wei] = None): self.log = Logger(self.__class__.__name__) - self.registry_repr = str(registry) + self.registry_str = str(registry) self.blockchain = BlockchainInterfaceFactory.get_or_create_interface(provider_uri=provider_uri) @@ -122,12 +122,12 @@ class EthereumContractAgent: self.log.info("Initialized new {} for {} with {} and {}".format(self.__class__.__name__, self.contract.address, self.blockchain.provider_uri, - self.registry_repr)) + self.registry_str)) def __repr__(self) -> str: class_name = self.__class__.__name__ r = "{}(registry={}, contract={})" - return r.format(class_name, self.registry_repr, self.contract_name) + return r.format(class_name, self.registry_str, self.contract_name) def __eq__(self, other: Any) -> bool: return bool(self.contract.address == other.contract.address) diff --git a/nucypher/cli/commands/stake.py b/nucypher/cli/commands/stake.py index 561239db7..0f30f3968 100644 --- a/nucypher/cli/commands/stake.py +++ b/nucypher/cli/commands/stake.py @@ -320,7 +320,7 @@ def config(general_config, config_file, config_options): @stake.command('list') @group_staker_options @option_config_file -@click.option('--show-all', help="List all stakes, including unlocked and inactive", is_flag=True) +@click.option('--all', 'show_all', help="List all stakes, including unlocked and inactive", is_flag=True) @group_general_config def list_stakes(general_config, staker_options, config_file, show_all): """List active stakes for current stakeholder.""" diff --git a/nucypher/cli/painting/staking.py b/nucypher/cli/painting/staking.py index e7a40aba4..c64d639a1 100644 --- a/nucypher/cli/painting/staking.py +++ b/nucypher/cli/painting/staking.py @@ -82,7 +82,7 @@ def paint_stakes(emitter: StdoutEmitter, snippet_with_line = network_snippet + '═'*(line_width-len(network_snippet)+1) emitter.echo(snippet_with_line, bold=True) emitter.echo(f"Staker {staker.checksum_address} ════", bold=True, color='red' if missing else 'green') - worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'NO_WORKER_BONDED' + worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'not bonded' emitter.echo(f"Worker {worker} ════", color='red' if staker.worker_address == NULL_ADDRESS else None) emitter.echo(tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data), floatfmt="fancy_grid")) diff --git a/tests/acceptance/blockchain/agents/test_contract_agency.py b/tests/acceptance/blockchain/agents/test_contract_agency.py index 93ad888ae..3e9dec623 100644 --- a/tests/acceptance/blockchain/agents/test_contract_agency.py +++ b/tests/acceptance/blockchain/agents/test_contract_agency.py @@ -26,10 +26,10 @@ def test_get_agent_with_different_registries(token_economics, agency, test_regis # Get agents using same registry instance staking_agent_1 = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) staking_agent_2 = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry) - assert staking_agent_2.registry_repr == staking_agent_1.registry_repr == str(test_registry) + assert staking_agent_2.registry_str == staking_agent_1.registry_str == str(test_registry) assert staking_agent_2 is staking_agent_1 # Same content but different classes of registries staking_agent_2 = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry) - assert staking_agent_2.registry_repr == str(test_registry) + assert staking_agent_2.registry_str == str(test_registry) assert staking_agent_2 is staking_agent_1 diff --git a/tests/integration/cli/test_stake_cli_functionality.py b/tests/integration/cli/test_stake_cli_functionality.py index c803fab5e..149c75787 100644 --- a/tests/integration/cli/test_stake_cli_functionality.py +++ b/tests/integration/cli/test_stake_cli_functionality.py @@ -1278,7 +1278,7 @@ def test_stake_list_active(click_runner, surrogate_stakers, surrogate_stakes, to def test_stake_list_all(click_runner, surrogate_stakers, surrogate_stakes, token_economics): command = ('list', - '--show-all', + '--all', '--provider', MOCK_PROVIDER_URI, '--network', TEMPORARY_DOMAIN,) diff --git a/tests/unit/test_registry_basics.py b/tests/unit/test_registry_basics.py index bb31a3185..932bf4193 100644 --- a/tests/unit/test_registry_basics.py +++ b/tests/unit/test_registry_basics.py @@ -88,6 +88,6 @@ def test_contract_registry(tempfile_path): test_registry.search(contract_address=test_addr) # Check id of new registry with the same content - new_registry = InMemoryContractRegistry() # TODO finish this + new_registry = InMemoryContractRegistry() new_registry.write(test_registry.read()) assert new_registry.id == test_registry.id From 5359f1238de41297dd210d93a6ee74faf5e17f2c Mon Sep 17 00:00:00 2001 From: Derek Pierre Date: Thu, 20 Aug 2020 11:25:40 -0400 Subject: [PATCH 26/26] Apply RFCs to #2183 Co-authored-by: K Prasch --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e4e19d272..f7552658a 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ build-docs: $(MAKE) -C docs html validate-docs: build-docs - # Required dependencies from docs-requirements.txt + # Requires dependencies from docs-requirements.txt python newsfragments/validate_files.py towncrier --draft