Merge commit from main (and merge candidate into main.)

pull/2199/head
jMyles 2020-08-21 20:32:45 -07:00
commit 0de8e25132
31 changed files with 573 additions and 235 deletions

View File

@ -31,14 +31,14 @@ 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
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

View File

@ -0,0 +1,91 @@
.. _service-fees:
Service Fees (Pricing)
======================
Minimum fee rate
----------------
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 Alices offered per period rate (contained alongside the sharing policys 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*.
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.
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
----------------
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**
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.*
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 ranges 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
---------------------------------------
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 NuCyphers 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 protocols 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 <LINK> 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
--------------------------------
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.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 applications 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 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 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.

View File

@ -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

View File

@ -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

View File

@ -154,6 +154,7 @@ Whitepapers
architecture/upgradeable_proxy_contracts
architecture/sub_stakes
architecture/slashing
architecture/service_fees
.. toctree::
:maxdepth: 1

View File

@ -20,6 +20,10 @@ import json
import os
import sys
import time
from web3.types import TxReceipt
from constant_sorrow.constants import FULL, NO_WORKER_BONDED, WORKER_NOT_RUNNING
from decimal import Decimal
from web3.types import TxReceipt
import traceback
@ -815,7 +819,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)
@ -1249,6 +1253,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
@ -1279,6 +1288,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]
@ -1308,21 +1322,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
@ -1331,7 +1341,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
#
@ -1843,6 +1853,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()

View File

@ -100,13 +100,13 @@ class EthereumContractAgent:
transaction_gas: Optional[Wei] = None):
self.log = Logger(self.__class__.__name__)
self.registry = registry
self.registry_str = 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_str))
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_str, 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
@ -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)

View File

@ -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:

View File

@ -466,16 +466,11 @@ class BlockchainInterface:
self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}")
@validate_checksum_address
def build_transaction(self,
contract_function: ContractFunction,
sender_address: str,
payload: dict = None,
transaction_gas_limit: int = None,
) -> dict:
#
# Build Payload
#
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'),
@ -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_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
@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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
#

View File

@ -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)
@ -345,14 +348,16 @@ 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]} "
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}")
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

View File

@ -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_uri=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,

View File

@ -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('--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, 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()

View File

@ -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, registry=registry)
@status.command(name='locked-tokens')

View File

@ -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"
@ -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
"""
#
@ -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}"

View File

@ -19,14 +19,14 @@ 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
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')
@ -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 '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"))
rows = list()

View File

@ -22,10 +22,12 @@ 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
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.acumen.nicknames import nickname_from_seed
@ -140,33 +142,38 @@ 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], 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")
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=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 +198,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}")

View File

@ -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

View File

@ -233,6 +233,7 @@ class FelixConfiguration(CharacterConfiguration):
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}

View File

@ -147,7 +147,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

View File

@ -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

View File

@ -0,0 +1,35 @@
"""
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 <https://www.gnu.org/licenses/>.
"""
import pytest
from decimal import Decimal, InvalidOperation
from nucypher.blockchain.eth.agents import StakingEscrowAgent, ContractAgency
from nucypher.blockchain.eth.token import NU
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_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_str == str(test_registry)
assert staking_agent_2 is staking_agent_1

View File

@ -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

View File

@ -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.

View File

@ -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,

View File

@ -14,13 +14,15 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import re
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.token import NU
from nucypher.blockchain.eth.constants import MAX_UINT16, NULL_ADDRESS
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,12 +505,12 @@ 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
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,
@ -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',
'--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)

View File

@ -18,7 +18,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
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()
new_registry.write(test_registry.read())
assert new_registry.id == test_registry.id