Merge pull request #2867 from KPrasch/staker-eol

Relinquish Staker
pull/2877/head
KPrasch 2022-02-21 17:52:09 -08:00 committed by GitHub
commit 9aead5f553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 330 additions and 9219 deletions

View File

@ -19,8 +19,8 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import json
import time
from decimal import Decimal
from typing import Callable, Union
from typing import Iterable, List, Optional, Tuple
from typing import Optional, Tuple
from typing import Union
import maya
from constant_sorrow.constants import FULL
@ -30,55 +30,27 @@ from web3 import Web3
from web3.types import TxReceipt
from nucypher.acumen.nicknames import Nickname
from nucypher.blockchain.economics import (
Economics,
EconomicsFactory,
)
from nucypher.blockchain.economics import Economics
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
ContractAgency,
NucypherTokenAgent,
StakingEscrowAgent,
PREApplicationAgent
)
from nucypher.blockchain.eth.constants import (
NULL_ADDRESS,
)
from nucypher.blockchain.eth.decorators import (
only_me,
save_receipt,
validate_checksum_address
)
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.decorators import save_receipt, validate_checksum_address
from nucypher.blockchain.eth.deployers import (
AdjudicatorDeployer,
BaseContractDeployer,
NucypherTokenDeployer,
StakingEscrowDeployer,
PREApplicationDeployer,
SubscriptionManagerDeployer
SubscriptionManagerDeployer, AdjudicatorDeployer
)
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.blockchain.eth.signers.base import Signer
from nucypher.blockchain.eth.token import (
NU,
Stake,
StakeList,
WorkTracker,
validate_prolong,
validate_increase,
validate_divide,
validate_merge
)
from nucypher.blockchain.eth.utils import (
calculate_period_duration,
datetime_to_period
)
from nucypher.characters.banners import STAKEHOLDER_BANNER
from nucypher.blockchain.eth.token import NU, WorkTracker
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.control.emitters import StdoutEmitter
from nucypher.crypto.powers import TransactingPower
from nucypher.types import NuNits, Period
from nucypher.utilities.logging import Logger
@ -183,7 +155,6 @@ class ContractAdministrator(BaseActor):
)
dispatched_upgradeable_deployer_classes = (
StakingEscrowDeployer,
AdjudicatorDeployer,
)
@ -323,570 +294,6 @@ class ContractAdministrator(BaseActor):
return filepath
class Staker(NucypherTokenActor):
"""
Baseclass for staking-related operations on the blockchain.
"""
class StakerError(NucypherTokenActor.ActorError):
pass
class InsufficientTokens(StakerError):
pass
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.log = Logger("staker")
self.is_me = bool(self.transacting_power)
self._operator_address = None
# Blockchain
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
self.economics = EconomicsFactory.get_economics(registry=self.registry)
# Check stakes
self.stakes = StakeList(registry=self.registry, checksum_address=self.checksum_address)
def refresh_stakes(self):
self.stakes.refresh()
def to_dict(self) -> dict:
stake_info = [stake.to_stake_info() for stake in self.stakes]
operator_address = self.operator_address or NULL_ADDRESS
staker_funds = {'ETH': int(self.eth_balance), 'NU': int(self.token_balance)}
staker_payload = {'staker': self.checksum_address,
'balances': staker_funds,
'operator': operator_address,
'stakes': stake_info}
return staker_payload
@property
def is_staking(self) -> bool:
"""Checks if this Staker currently has active stakes / locked tokens."""
return bool(self.stakes)
def owned_tokens(self) -> NU:
"""
Returns all tokens that belong to the staker, including locked, unlocked and rewards.
"""
raw_value = self.staking_agent.owned_tokens(staker_address=self.checksum_address)
value = NU.from_units(raw_value)
return value
def locked_tokens(self, periods: int = 0) -> NU:
"""Returns the amount of tokens this staker has locked for a given duration in periods."""
raw_value = self.staking_agent.get_locked_tokens(staker_address=self.checksum_address, periods=periods)
value = NU.from_units(raw_value)
return value
@property
def current_stake(self) -> NU:
"""The total number of staked tokens, i.e., tokens locked in the current period."""
return self.locked_tokens(periods=0)
def filtered_stakes(self,
parent_status: Stake.Status = None,
filter_function: Callable[[Stake], bool] = None
) -> Iterable[Stake]:
"""Returns stakes for this staker which filtered by status or by a provided function."""
if not parent_status and not filter_function:
raise ValueError("Pass parent status or filter function or both.")
# Read once from chain and reuse these values
staker_info = self.staking_agent.get_staker_info(self.checksum_address) # TODO related to #1514
current_period = self.staking_agent.get_current_period() # TODO #1514 this is online only.
stakes = list()
for stake in self.stakes:
if parent_status and not stake.status(staker_info, current_period).is_child(parent_status):
continue
if filter_function and not filter_function(stake):
continue
stakes.append(stake)
return stakes
def sorted_stakes(self,
parent_status: Stake.Status = None,
filter_function: Callable[[Stake], bool] = None
) -> List[Stake]:
"""Returns a list of filtered stakes sorted by account wallet index."""
if parent_status is not None or filter_function is not None:
filtered_stakes = self.filtered_stakes(parent_status, filter_function)
else:
filtered_stakes = self.stakes
stakes = sorted(filtered_stakes, key=lambda s: s.address_index_ordering_key)
return stakes
@only_me
def initialize_stake(self,
amount: NU = None,
lock_periods: int = None,
expiration: maya.MayaDT = None,
entire_balance: bool = False,
from_unlocked: bool = False
) -> TxReceipt:
"""Create a new stake."""
# Duration
if not (bool(lock_periods) ^ bool(expiration)):
raise ValueError(f"Pass either lock periods or expiration; got {'both' if lock_periods else 'neither'}")
if expiration:
lock_periods = calculate_period_duration(future_time=expiration,
seconds_per_period=self.economics.seconds_per_period)
# Value
if entire_balance and amount:
raise ValueError("Specify an amount or entire balance, not both")
elif not entire_balance and not amount:
raise ValueError("Specify an amount or entire balance, got neither")
token_balance = self.calculate_staking_reward() if from_unlocked else self.token_balance
if entire_balance:
amount = token_balance
if not token_balance >= amount:
raise self.InsufficientTokens(f"Insufficient token balance ({token_balance}) "
f"for new stake initialization of {amount}")
# Write to blockchain
new_stake = Stake.initialize_stake(staking_agent=self.staking_agent,
economics=self.economics,
checksum_address=self.checksum_address,
amount=amount,
lock_periods=lock_periods)
# Create stake on-chain
if from_unlocked:
receipt = self._lock_and_create(amount=new_stake.value.to_units(), lock_periods=new_stake.duration)
else:
receipt = self._deposit(amount=new_stake.value.to_units(), lock_periods=new_stake.duration)
# Log and return receipt
self.log.info(f"{self.checksum_address} initialized new stake: {amount} tokens for {lock_periods} periods")
# Update staking cache element
self.refresh_stakes()
return receipt
def _ensure_stake_exists(self, stake: Stake):
if len(self.stakes) <= stake.index:
raise ValueError(f"There is no stake with index {stake.index}")
if self.stakes[stake.index] != stake:
raise ValueError(f"Stake with index {stake.index} is not equal to provided stake")
@only_me
def divide_stake(self,
stake: Stake,
target_value: NU,
additional_periods: int = None,
expiration: maya.MayaDT = None
) -> TxReceipt:
self._ensure_stake_exists(stake)
if not (bool(additional_periods) ^ bool(expiration)):
raise ValueError(f"Pass either the number of lock periods or expiration; "
f"got {'both' if additional_periods else 'neither'}")
# Calculate stake duration in periods
if expiration:
additional_periods = datetime_to_period(datetime=expiration, seconds_per_period=self.economics.seconds_per_period) - stake.final_locked_period
if additional_periods <= 0:
raise ValueError(f"New expiration {expiration} must be at least 1 period from the "
f"current stake's end period ({stake.final_locked_period}).")
# Read on-chain stake and validate
stake.sync()
validate_divide(stake=stake, target_value=target_value, additional_periods=additional_periods)
# Do it already!
receipt = self._divide_stake(stake_index=stake.index,
additional_periods=additional_periods,
target_value=int(target_value))
# Update staking cache element
self.refresh_stakes()
return receipt
@only_me
def increase_stake(self,
stake: Stake,
amount: NU = None,
entire_balance: bool = False,
from_unlocked: bool = False
) -> TxReceipt:
"""Add tokens to existing stake."""
self._ensure_stake_exists(stake)
# Value
if not (bool(entire_balance) ^ bool(amount)):
raise ValueError(f"Pass either an amount or entire balance; "
f"got {'both' if entire_balance else 'neither'}")
token_balance = self.calculate_staking_reward() if from_unlocked else self.token_balance
if entire_balance:
amount = token_balance
if not token_balance >= amount:
raise self.InsufficientTokens(f"Insufficient token balance ({token_balance}) "
f"to increase stake by {amount}")
# Read on-chain stake and validate
stake.sync()
validate_increase(stake=stake, amount=amount)
# Write to blockchain
if from_unlocked:
receipt = self._lock_and_increase(stake_index=stake.index, amount=int(amount))
else:
receipt = self._deposit_and_increase(stake_index=stake.index, amount=int(amount))
# Update staking cache element
self.refresh_stakes()
return receipt
@only_me
def prolong_stake(self,
stake: Stake,
additional_periods: int = None,
expiration: maya.MayaDT = None
) -> TxReceipt:
self._ensure_stake_exists(stake)
if not (bool(additional_periods) ^ bool(expiration)):
raise ValueError(f"Pass either the number of lock periods or expiration; "
f"got {'both' if additional_periods else 'neither'}")
# Calculate stake duration in periods
if expiration:
additional_periods = datetime_to_period(datetime=expiration,
seconds_per_period=self.economics.seconds_per_period) - stake.final_locked_period
if additional_periods <= 0:
raise ValueError(f"New expiration {expiration} must be at least 1 period from the "
f"current stake's end period ({stake.final_locked_period}).")
# Read on-chain stake and validate
stake.sync()
validate_prolong(stake=stake, additional_periods=additional_periods)
receipt = self._prolong_stake(stake_index=stake.index, lock_periods=additional_periods)
# Update staking cache element
self.refresh_stakes()
return receipt
@only_me
def merge_stakes(self,
stake_1: Stake,
stake_2: Stake
) -> TxReceipt:
self._ensure_stake_exists(stake_1)
self._ensure_stake_exists(stake_2)
# Read on-chain stake and validate
stake_1.sync()
stake_2.sync()
validate_merge(stake_1=stake_1, stake_2=stake_2)
receipt = self._merge_stakes(stake_index_1=stake_1.index, stake_index_2=stake_2.index)
# Update staking cache element
self.refresh_stakes()
return receipt
def _prolong_stake(self, stake_index: int, lock_periods: int) -> TxReceipt:
"""Public facing method for stake prolongation."""
receipt = self.staking_agent.prolong_stake(stake_index=stake_index,
periods=lock_periods,
transacting_power=self.transacting_power)
return receipt
def _deposit(self, amount: int, lock_periods: int) -> TxReceipt:
"""Public facing method for token locking."""
self._ensure_allowance_equals(0)
receipt = self.token_agent.approve_and_call(amount=amount,
target_address=self.staking_agent.contract_address,
transacting_power=self.transacting_power,
call_data=Web3.toBytes(lock_periods))
return receipt
def _lock_and_create(self, amount: int, lock_periods: int) -> TxReceipt:
"""Public facing method for token locking without depositing."""
receipt = self.staking_agent.lock_and_create(amount=amount,
transacting_power=self.transacting_power,
lock_periods=lock_periods)
return receipt
def _divide_stake(self, stake_index: int, additional_periods: int, target_value: int) -> TxReceipt:
"""Public facing method for stake dividing."""
receipt = self.staking_agent.divide_stake(transacting_power=self.transacting_power,
stake_index=stake_index,
target_value=target_value,
periods=additional_periods)
return receipt
def _deposit_and_increase(self, stake_index: int, amount: int) -> TxReceipt:
"""Public facing method for deposit and increasing stake."""
self._ensure_allowance_equals(amount)
receipt = self.staking_agent.deposit_and_increase(transacting_power=self.transacting_power,
stake_index=stake_index,
amount=amount)
return receipt
def _ensure_allowance_equals(self, amount: int):
owner = self.transacting_power.account
spender = self.staking_agent.contract.address
current_allowance = self.token_agent.get_allowance(owner=owner, spender=spender)
if amount > current_allowance:
to_increase = amount - current_allowance
self.token_agent.increase_allowance(increase=to_increase,
transacting_power=self.transacting_power,
spender_address=spender)
self.log.info(f"{owner} increased token allowance for spender {spender} to {amount}")
elif amount < current_allowance:
to_decrease = current_allowance - amount
self.token_agent.decrease_allowance(decrease=to_decrease,
transacting_power=self.transacting_power,
spender_address=spender)
self.log.info(f"{owner} decreased token allowance for spender {spender} to {amount}")
def _lock_and_increase(self, stake_index: int, amount: int) -> TxReceipt:
"""Public facing method for increasing stake."""
receipt = self.staking_agent.lock_and_increase(transacting_power=self.transacting_power,
stake_index=stake_index,
amount=amount)
return receipt
def _merge_stakes(self, stake_index_1: int, stake_index_2: int) -> TxReceipt:
"""Public facing method for stakes merging."""
receipt = self.staking_agent.merge_stakes(stake_index_1=stake_index_1,
stake_index_2=stake_index_2,
transacting_power=self.transacting_power)
return receipt
@property
def is_restaking(self) -> bool:
restaking = self.staking_agent.is_restaking(staker_address=self.checksum_address)
return restaking
@only_me
@save_receipt
def _set_restaking(self, value: bool) -> TxReceipt:
receipt = self.staking_agent.set_restaking(transacting_power=self.transacting_power, value=value)
return receipt
def enable_restaking(self) -> TxReceipt:
receipt = self._set_restaking(value=True)
return receipt
def disable_restaking(self) -> TxReceipt:
receipt = self._set_restaking(value=False)
return receipt
@property
def is_winding_down(self) -> bool:
winding_down = self.staking_agent.is_winding_down(staker_address=self.checksum_address)
return winding_down
@only_me
@save_receipt
def _set_winding_down(self, value: bool) -> TxReceipt:
receipt = self.staking_agent.set_winding_down(transacting_power=self.transacting_power, value=value)
return receipt
def enable_winding_down(self) -> TxReceipt:
receipt = self._set_winding_down(value=True)
return receipt
def disable_winding_down(self) -> TxReceipt:
receipt = self._set_winding_down(value=False)
return receipt
@property
def is_taking_snapshots(self) -> bool:
taking_snapshots = self.staking_agent.is_taking_snapshots(staker_address=self.checksum_address)
return taking_snapshots
@only_me
@save_receipt
def _set_snapshots(self, value: bool) -> TxReceipt:
receipt = self.staking_agent.set_snapshots(transacting_power=self.transacting_power, activate=value)
return receipt
def enable_snapshots(self) -> TxReceipt:
receipt = self._set_snapshots(value=True)
return receipt
def disable_snapshots(self) -> TxReceipt:
receipt = self._set_snapshots(value=False)
return receipt
@property
def is_migrated(self) -> bool:
migrated = self.staking_agent.is_migrated(staker_address=self.checksum_address)
return migrated
def migrate(self, staker_address: Optional[ChecksumAddress] = None) -> TxReceipt:
receipt = self.staking_agent.migrate(transacting_power=self.transacting_power, staker_address=staker_address)
return receipt
@only_me
@save_receipt
def remove_inactive_stake(self, stake: Stake) -> TxReceipt:
self._ensure_stake_exists(stake)
# Read on-chain stake and validate
stake.sync()
if not stake.status().is_child(Stake.Status.INACTIVE):
raise ValueError(f"Stake with index {stake.index} is still active")
receipt = self._remove_inactive_stake(stake_index=stake.index)
# Update staking cache element
self.refresh_stakes()
return receipt
@only_me
@save_receipt
def _remove_inactive_stake(self, stake_index: int) -> TxReceipt:
receipt = self.staking_agent.remove_inactive_stake(transacting_power=self.transacting_power,
stake_index=stake_index)
return receipt
def non_withdrawable_stake(self) -> NU:
staked_amount: NuNits = self.staking_agent.non_withdrawable_stake(staker_address=self.checksum_address)
return NU.from_units(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]
"""
current_period: Period = self.staking_agent.get_current_period()
previous_period: int = current_period - 1
current_committed_period: Period = self.staking_agent.get_current_committed_period(staker_address=self.checksum_address)
next_committed_period: Period = self.staking_agent.get_next_committed_period(staker_address=self.checksum_address)
mintable_periods: int = 0
if 0 < current_committed_period <= previous_period:
mintable_periods += 1
if 0 < next_committed_period <= previous_period:
mintable_periods += 1
return mintable_periods
#
# Bonding with Worker
#
@only_me
@save_receipt
@validate_checksum_address
def bond_worker(self, worker_address: ChecksumAddress) -> TxReceipt:
receipt = self.staking_agent.bond_worker(transacting_power=self.transacting_power,
worker_address=worker_address)
self._worker_address = worker_address
return receipt
@property
def worker_address(self) -> str:
if not self._worker_address:
# TODO: This is broken for StakeHolder with different stakers - See #1358
worker_address = self.staking_agent.get_worker_from_staker(staker_address=self.checksum_address)
self._worker_address = worker_address
return self._worker_address
@only_me
@save_receipt
def unbond_worker(self) -> TxReceipt:
receipt = self.staking_agent.release_worker(transacting_power=self.transacting_power)
self._worker_address = NULL_ADDRESS
return receipt
#
# Reward and Collection
#
@only_me
@save_receipt
def mint(self) -> TxReceipt:
"""Computes and transfers tokens to the staker's account"""
receipt = self.staking_agent.mint(transacting_power=self.transacting_power)
return receipt
def calculate_staking_reward(self) -> NU:
staking_reward = self.staking_agent.calculate_staking_reward(staker_address=self.checksum_address)
return NU.from_units(staking_reward)
def calculate_policy_fee(self) -> int:
policy_fee = self.policy_agent.get_fee_amount(staker_address=self.checksum_address)
return policy_fee
@only_me
@save_receipt
@validate_checksum_address
def collect_policy_fee(self, collector_address=None) -> TxReceipt:
"""Collect fees (ETH) earned since last withdrawal"""
withdraw_address = collector_address or self.checksum_address
receipt = self.policy_agent.collect_policy_fee(collector_address=withdraw_address,
transacting_power=self.transacting_power)
return receipt
@only_me
@save_receipt
def collect_staking_reward(self, replace: bool = False) -> TxReceipt: # TODO: Support replacement for all actor transactions
"""Withdraw tokens rewarded for staking"""
receipt = self.staking_agent.collect_staking_reward(transacting_power=self.transacting_power, replace=replace)
return receipt
@only_me
@save_receipt
def withdraw(self, amount: NU, replace: bool = False) -> TxReceipt:
"""Withdraw tokens from StakingEscrow (assuming they're unlocked)"""
receipt = self.staking_agent.withdraw(transacting_power=self.transacting_power,
amount=NuNits(int(amount)),
replace=replace)
return receipt
@property
def missing_commitments(self) -> int:
staker_address = self.checksum_address
missing = self.staking_agent.get_missing_commitments(checksum_address=staker_address)
return missing
@only_me
@save_receipt
def set_min_fee_rate(self, min_rate: int) -> TxReceipt:
"""Public facing method for staker to set the minimum acceptable fee rate for their associated worker"""
minimum, _default, maximum = self.policy_agent.get_fee_rate_range()
if min_rate < minimum or min_rate > maximum:
raise ValueError(f"Minimum fee rate {min_rate} must fall within global fee range of [{minimum}, {maximum}]")
receipt = self.policy_agent.set_min_fee_rate(transacting_power=self.transacting_power, min_rate=min_rate)
return receipt
@property
def min_fee_rate(self) -> int:
"""Minimum fee rate that staker accepts"""
staker_address = self.checksum_address
min_fee = self.policy_agent.get_min_fee_rate(staker_address)
return min_fee
@property
def raw_min_fee_rate(self) -> int:
"""Minimum acceptable fee rate set by staker for their associated worker.
This fee rate is only used if it falls within the global fee range.
If it doesn't a default fee rate is used instead of the raw value (see `min_fee_rate`)"""
staker_address = self.checksum_address
min_fee = self.policy_agent.get_raw_min_fee_rate(staker_address)
return min_fee
class Operator(BaseActor):
READY_TIMEOUT = None # (None or 0) == indefinite
@ -1007,75 +414,3 @@ class Investigator(NucypherTokenActor):
def was_this_evidence_evaluated(self, evidence) -> bool:
result = self.adjudicator_agent.was_this_evidence_evaluated(evidence=evidence)
return result
class StakeHolder:
banner = STAKEHOLDER_BANNER
class UnknownAccount(KeyError):
pass
def __init__(self,
signer: Signer,
registry: BaseContractRegistry,
domain: str,
initial_address: str = None,
worker_data: dict = None):
self.worker_data = worker_data
self.log = Logger(f"stakeholder")
self.checksum_address = initial_address
self.registry = registry
self.domain = domain
self.staker = None
self.signer = signer
if initial_address:
# If an initial address was passed,
# it is safe to understand that it has already been used at a higher level.
if initial_address not in self.signer.accounts:
message = f"Account {initial_address} is not known by this Ethereum client. Is it a HW account? " \
f"If so, make sure that your device is plugged in and you use the --hw-wallet flag."
raise self.UnknownAccount(message)
self.assimilate(checksum_address=initial_address)
@validate_checksum_address
def assimilate(self, checksum_address: ChecksumAddress, password: str = None) -> None:
original_form = self.checksum_address
staking_address = checksum_address
self.checksum_address = staking_address
self.staker = self.get_staker(checksum_address=staking_address)
self.staker.refresh_stakes()
if password:
self.signer.unlock_account(account=checksum_address, password=password)
new_form = self.checksum_address
self.log.info(f"Setting Staker from {original_form} to {new_form}.")
@validate_checksum_address
def get_staker(self, checksum_address: ChecksumAddress):
if checksum_address not in self.signer.accounts:
raise ValueError(f"{checksum_address} is not a known client account.")
transacting_power = TransactingPower(account=checksum_address, signer=self.signer)
staker = Staker(transacting_power=transacting_power,
domain=self.domain,
registry=self.registry)
staker.refresh_stakes()
return staker
def get_stakers(self) -> List[Staker]:
stakers = list()
for account in self.signer.accounts:
staker = self.get_staker(checksum_address=account)
stakers.append(staker)
return stakers
@property
def total_stake(self) -> NU:
"""
The total number of staked tokens, either locked or unlocked in the current period for all stakers
controlled by the stakeholder's signer.
"""
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
stake = sum(staking_agent.owned_tokens(staker_address=account) for account in self.signer.accounts)
nu_stake = NU.from_units(stake)
return nu_stake

View File

@ -14,12 +14,14 @@ 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 os
import random
import sys
from bisect import bisect_right
from itertools import accumulate
from typing import Dict, Iterable, List, Tuple, Type, Union, Any, Optional, cast, NamedTuple
from typing import Dict, Iterable, List, Tuple, Type, Any, Optional, cast, NamedTuple
from constant_sorrow.constants import ( # type: ignore
CONTRACT_CALL,
@ -28,7 +30,6 @@ from constant_sorrow.constants import ( # type: ignore
)
from eth_typing.evm import ChecksumAddress
from eth_utils.address import to_checksum_address
from hexbytes.main import HexBytes
from web3.contract import Contract, ContractFunction
from web3.types import Wei, Timestamp, TxReceipt, TxParams
@ -39,7 +40,6 @@ from nucypher.blockchain.eth.constants import (
NUCYPHER_TOKEN_CONTRACT_NAME,
NULL_ADDRESS,
SUBSCRIPTION_MANAGER_CONTRACT_NAME,
STAKING_ESCROW_CONTRACT_NAME,
PRE_APPLICATION_CONTRACT_NAME
)
from nucypher.blockchain.eth.decorators import contract_api
@ -55,15 +55,7 @@ from nucypher.crypto.utils import sha256_digest
from nucypher.types import (
Agent,
NuNits,
SubStakeInfo,
RawSubStakeInfo,
Period,
Work,
StakerFlags,
StakerInfo,
StakingProviderInfo,
PeriodDelta,
StakingEscrowParameters,
TuNits
)
from nucypher.utilities.logging import Logger # type: ignore
@ -96,7 +88,8 @@ class EthereumContractAgent:
registry: BaseContractRegistry = None, # TODO: Consider make it non-optional again. See comment in InstanceAgent.
eth_provider_uri: Optional[str] = None,
contract: Optional[Contract] = None,
transaction_gas: Optional[Wei] = None):
transaction_gas: Optional[Wei] = None,
contract_version: Optional[str] = None):
self.log = Logger(self.__class__.__name__)
self.registry_str = str(registry)
@ -107,6 +100,7 @@ class EthereumContractAgent:
contract = self.blockchain.get_contract_by_name(
registry=registry,
contract_name=self.contract_name,
contract_version=contract_version,
proxy_name=self._proxy_name,
use_proxy_address=self._forward_address
)
@ -238,575 +232,6 @@ class NucypherTokenAgent(EthereumContractAgent):
raise self.RequirementError(f"Token allowance for spender {target_address} must be 0")
class StakingEscrowAgent(EthereumContractAgent):
contract_name: str = STAKING_ESCROW_CONTRACT_NAME
_proxy_name: str = DISPATCHER_CONTRACT_NAME
_excluded_interfaces = (
'verifyState',
'finishUpgrade'
)
DEFAULT_STAKER_PAGINATION_SIZE_LIGHT_NODE: int = int(os.environ.get(
NUCYPHER_ENVVAR_STAKING_PROVIDERS_PAGINATION_SIZE_LIGHT_NODE, default=30))
DEFAULT_STAKER_PAGINATION_SIZE: int = int(os.environ.get(NUCYPHER_ENVVAR_STAKING_PROVIDERS_PAGINATION_SIZE, default=1000))
class NotEnoughStakers(Exception):
"""Raised when the are not enough stakers available to complete an operation"""
#
# Staker Network Status
#
@contract_api(CONTRACT_CALL)
def get_staker_population(self) -> int:
"""Returns the number of stakers on the blockchain"""
return self.contract.functions.getStakersLength().call()
@contract_api(CONTRACT_CALL)
def get_current_period(self) -> Period:
"""Returns the current period"""
return self.contract.functions.getCurrentPeriod().call()
@contract_api(CONTRACT_CALL)
def get_stakers(self) -> List[ChecksumAddress]:
"""Returns a list of stakers"""
num_stakers: int = self.get_staker_population()
stakers: List[ChecksumAddress] = [self.contract.functions.stakers(i).call() for i in range(num_stakers)]
return stakers
@contract_api(CONTRACT_CALL)
def partition_stakers_by_activity(self) -> Tuple[List[ChecksumAddress], List[ChecksumAddress], List[ChecksumAddress]]:
"""
Returns three lists of stakers depending on their commitments:
The first list contains stakers that already committed to next period.
The second, stakers that committed to current period but haven't committed to next yet.
The third contains stakers that have missed commitments before current period
"""
num_stakers: int = self.get_staker_population()
current_period: Period = self.get_current_period()
active_stakers: List[ChecksumAddress] = list()
pending_stakers: List[ChecksumAddress] = list()
missing_stakers: List[ChecksumAddress] = list()
for i in range(num_stakers):
staker = self.contract.functions.stakers(i).call()
last_committed_period = self.get_last_committed_period(staker)
if last_committed_period == current_period + 1:
active_stakers.append(staker)
elif last_committed_period == current_period:
pending_stakers.append(staker)
else:
locked_stake = self.non_withdrawable_stake(staker_address=staker)
if locked_stake == 0:
# don't include stakers with expired stakes
continue
missing_stakers.append(staker)
return active_stakers, pending_stakers, missing_stakers
@contract_api(CONTRACT_CALL)
def get_all_active_stakers(self, periods: int, pagination_size: Optional[int] = None) -> Tuple[NuNits, Dict[ChecksumAddress, NuNits]]:
"""Only stakers which committed to the current period (in the previous period) are used."""
if not periods > 0:
raise ValueError("Period must be > 0")
if pagination_size is None:
pagination_size = self.DEFAULT_STAKER_PAGINATION_SIZE_LIGHT_NODE if self.blockchain.is_light else self.DEFAULT_STAKER_PAGINATION_SIZE
self.log.debug(f"Defaulting to pagination size {pagination_size}")
elif pagination_size < 0:
raise ValueError("Pagination size must be >= 0")
if pagination_size > 0:
num_stakers: int = self.get_staker_population()
start_index: int = 0
n_tokens: int = 0
stakers: Dict[int, int] = dict()
attempts = 0
while start_index < num_stakers:
try:
attempts += 1
active_stakers = self.contract.functions.getActiveStakers(periods,
start_index,
pagination_size).call()
except Exception as e:
if 'timeout' not in str(e):
# exception unrelated to pagination size and timeout
raise e
elif pagination_size == 1 or attempts >= 3:
# we tried
raise e
else:
# reduce pagination size and retry
old_pagination_size = pagination_size
pagination_size = old_pagination_size // 2
self.log.debug(f"Failed stakers sampling using pagination size = {old_pagination_size}. "
f"Retrying with size {pagination_size}")
else:
temp_locked_tokens, temp_stakers = active_stakers
# temp_stakers is a list of length-2 lists (address -> locked tokens)
temp_stakers_map = {address: locked_tokens for address, locked_tokens in temp_stakers}
n_tokens = n_tokens + temp_locked_tokens
stakers.update(temp_stakers_map)
start_index += pagination_size
else:
n_tokens, temp_stakers = self.contract.functions.getActiveStakers(periods, 0, 0).call()
stakers = {address: locked_tokens for address, locked_tokens in temp_stakers}
# stakers' addresses are returned as uint256 by getActiveStakers(), convert to address objects
def checksum_address(address: int) -> ChecksumAddress:
return ChecksumAddress(to_checksum_address(address.to_bytes(ETH_ADDRESS_BYTE_LENGTH, 'big')))
typed_stakers = {checksum_address(address): NuNits(locked_tokens) for address, locked_tokens in stakers.items()}
return NuNits(n_tokens), typed_stakers
@contract_api(CONTRACT_CALL)
def get_all_locked_tokens(self, periods: int, pagination_size: Optional[int] = None) -> NuNits:
all_locked_tokens, _stakers = self.get_all_active_stakers(periods=periods, pagination_size=pagination_size)
return all_locked_tokens
#
# StakingEscrow Contract API
#
@contract_api(CONTRACT_CALL)
def get_global_locked_tokens(self, at_period: Optional[Period] = None) -> NuNits:
"""
Gets the number of locked tokens for *all* stakers that have
made a commitment to the specified period.
`at_period` values can be any valid period number past, present, or future:
PAST - Calling this function with an `at_period` value in the past will return the number
of locked tokens whose worker commitment was made to that past period.
PRESENT - This is the default value, when no `at_period` value is provided.
FUTURE - Calling this function with an `at_period` value greater than
the current period + 1 (next period), will result in a zero return value
because commitment cannot be made beyond the next period.
Returns an amount of NuNits.
"""
if at_period is None: # allow 0, vs default
# Get the current period on-chain by default.
at_period = self.contract.functions.getCurrentPeriod().call()
return NuNits(self.contract.functions.lockedPerPeriod(at_period).call())
@contract_api(CONTRACT_CALL)
def get_staker_info(self, staker_address: ChecksumAddress) -> StakerInfo:
# remove reserved fields
info: list = self.contract.functions.stakerInfo(staker_address).call()
return StakerInfo(*info[0:9])
@contract_api(CONTRACT_CALL)
def get_locked_tokens(self, staker_address: ChecksumAddress, periods: int = 0) -> NuNits:
"""
Returns the amount of tokens this staker has locked
for a given duration in periods measured from the current period forwards.
"""
if periods < 0:
raise ValueError(f"Periods value must not be negative, Got '{periods}'.")
return NuNits(self.contract.functions.getLockedTokens(staker_address, periods).call())
@contract_api(CONTRACT_CALL)
def owned_tokens(self, staker_address: ChecksumAddress) -> NuNits:
"""
Returns all tokens that belong to staker_address, including locked, unlocked and rewards.
"""
return NuNits(self.contract.functions.getAllTokens(staker_address).call())
@contract_api(CONTRACT_CALL)
def get_substake_info(self, staker_address: ChecksumAddress, stake_index: int) -> SubStakeInfo:
first_period, *others, locked_value = self.contract.functions.getSubStakeInfo(staker_address, stake_index).call()
last_period: Period = self.contract.functions.getLastPeriodOfSubStake(staker_address, stake_index).call()
return SubStakeInfo(first_period, last_period, locked_value)
@contract_api(CONTRACT_CALL)
def get_raw_substake_info(self, staker_address: ChecksumAddress, stake_index: int) -> RawSubStakeInfo:
result: RawSubStakeInfo = self.contract.functions.getSubStakeInfo(staker_address, stake_index).call()
return RawSubStakeInfo(*result)
@contract_api(CONTRACT_CALL)
def get_all_stakes(self, staker_address: ChecksumAddress) -> Iterable[SubStakeInfo]:
stakes_length: int = self.contract.functions.getSubStakesLength(staker_address).call()
if stakes_length == 0:
return iter(()) # Empty iterable, There are no stakes
for stake_index in range(stakes_length):
yield self.get_substake_info(staker_address=staker_address, stake_index=stake_index)
@contract_api(TRANSACTION)
def deposit_tokens(self,
amount: NuNits,
lock_periods: PeriodDelta,
transacting_power: TransactingPower,
staker_address: Optional[ChecksumAddress] = None,
) -> TxReceipt:
"""
Send tokens to the escrow from the sender's address to be locked on behalf of the staker address.
If the sender address is not provided, the stakers address is used.
Note that this resolved to two separate contract function signatures.
"""
if not staker_address:
staker_address = transacting_power.account
contract_function: ContractFunction = self.contract.functions.deposit(staker_address, amount, lock_periods)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def deposit_and_increase(self,
transacting_power: TransactingPower,
amount: NuNits,
stake_index: int
) -> TxReceipt:
"""
Send tokens to the escrow from the sender's address to be locked on behalf of the staker address.
This method will add tokens to the selected sub-stake.
Note that this resolved to two separate contract function signatures.
"""
contract_function: ContractFunction = self.contract.functions.depositAndIncrease(stake_index, amount)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def lock_and_create(self,
transacting_power: TransactingPower,
amount: NuNits,
lock_periods: PeriodDelta
) -> TxReceipt:
"""
Locks tokens amount and creates new sub-stake
"""
contract_function: ContractFunction = self.contract.functions.lockAndCreate(amount, lock_periods)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def lock_and_increase(self,
transacting_power: TransactingPower,
amount: NuNits,
stake_index: int
) -> TxReceipt:
"""
Locks tokens amount and add them to selected sub-stake
"""
contract_function: ContractFunction = self.contract.functions.lockAndIncrease(stake_index, amount)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def divide_stake(self,
transacting_power: TransactingPower,
stake_index: int,
target_value: NuNits,
periods: PeriodDelta
) -> TxReceipt:
contract_function: ContractFunction = self.contract.functions.divideStake(stake_index, target_value, periods)
receipt = self.blockchain.send_transaction(contract_function=contract_function, transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def prolong_stake(self, transacting_power: TransactingPower, stake_index: int, periods: PeriodDelta) -> TxReceipt:
contract_function: ContractFunction = self.contract.functions.prolongStake(stake_index, periods)
receipt = self.blockchain.send_transaction(contract_function=contract_function, transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def merge_stakes(self, transacting_power: TransactingPower, stake_index_1: int, stake_index_2: int) -> TxReceipt:
contract_function: ContractFunction = self.contract.functions.mergeStake(stake_index_1, stake_index_2)
receipt = self.blockchain.send_transaction(contract_function=contract_function, transacting_power=transacting_power)
return receipt
@contract_api(CONTRACT_CALL)
def get_current_committed_period(self, staker_address: ChecksumAddress) -> Period:
staker_info: StakerInfo = self.get_staker_info(staker_address)
period: int = staker_info.current_committed_period
return Period(period)
@contract_api(CONTRACT_CALL)
def get_next_committed_period(self, staker_address: ChecksumAddress) -> Period:
staker_info: StakerInfo = self.get_staker_info(staker_address)
period: int = staker_info.next_committed_period
return Period(period)
@contract_api(CONTRACT_CALL)
def get_last_committed_period(self, staker_address: ChecksumAddress) -> Period:
period: int = self.contract.functions.getLastCommittedPeriod(staker_address).call()
return Period(period)
@contract_api(CONTRACT_CALL)
def get_worker_from_staker(self, staker_address: ChecksumAddress) -> ChecksumAddress:
worker: str = self.contract.functions.getWorkerFromStaker(staker_address).call()
return to_checksum_address(worker)
@contract_api(CONTRACT_CALL)
def get_staker_from_worker(self, worker_address: ChecksumAddress) -> ChecksumAddress:
staker = self.contract.functions.stakerFromWorker(worker_address).call()
return to_checksum_address(staker)
@contract_api(TRANSACTION)
def bond_worker(self, transacting_power: TransactingPower, worker_address: ChecksumAddress) -> TxReceipt:
contract_function: ContractFunction = self.contract.functions.bondWorker(worker_address)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(TRANSACTION)
def release_worker(self, transacting_power: TransactingPower) -> TxReceipt:
return self.bond_worker(transacting_power=transacting_power, worker_address=NULL_ADDRESS)
@contract_api(TRANSACTION)
def commit_to_next_period(self,
transacting_power: TransactingPower,
fire_and_forget: bool = True, # TODO: rename to wait_for_receipt
) -> Union[TxReceipt, HexBytes]:
"""
For each period that the worker makes a commitment, the staker is rewarded.
"""
contract_function: ContractFunction = self.contract.functions.commitToNextPeriod()
txhash_or_receipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power,
gas_estimation_multiplier=1.5, # TODO: Workaround for #2337
fire_and_forget=fire_and_forget)
return txhash_or_receipt
@contract_api(TRANSACTION)
def mint(self, transacting_power: TransactingPower) -> TxReceipt:
"""
Computes reward tokens for the staker's account;
This is only used to calculate the reward for the final period of a stake,
when you intend to withdraw 100% of tokens.
"""
contract_function: ContractFunction = self.contract.functions.mint()
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(CONTRACT_CALL)
def non_withdrawable_stake(self, staker_address: ChecksumAddress) -> NuNits:
"""
Returns token amount that can not be withdrawn.
Opposite method for `calculate_staking_reward`.
Uses maximum of locked tokens in the current and next periods.
"""
staked_amount: int = max(self.contract.functions.getLockedTokens(staker_address, 0).call(),
self.contract.functions.getLockedTokens(staker_address, 1).call())
return NuNits(staked_amount)
@contract_api(CONTRACT_CALL)
def calculate_staking_reward(self, staker_address: ChecksumAddress) -> NuNits:
token_amount: NuNits = self.owned_tokens(staker_address)
reward_amount: int = token_amount - self.non_withdrawable_stake(staker_address)
return NuNits(reward_amount)
@contract_api(TRANSACTION)
def collect_staking_reward(self, transacting_power: TransactingPower, replace: bool = False) -> TxReceipt: # TODO: Support replacement for all agent transactions
"""Withdraw tokens rewarded for staking."""
staker_address = transacting_power.account
reward_amount: NuNits = self.calculate_staking_reward(staker_address=staker_address)
from nucypher.blockchain.eth.token import NU
self.log.debug(f"Withdrawing staking reward ({NU.from_units(reward_amount)}) to {staker_address}")
receipt: TxReceipt = self.withdraw(transacting_power=transacting_power, amount=reward_amount, replace=replace)
return receipt
@contract_api(TRANSACTION)
def withdraw(self, transacting_power: TransactingPower, amount: NuNits, replace: bool = False) -> TxReceipt:
"""Withdraw tokens"""
contract_function: ContractFunction = self.contract.functions.withdraw(amount)
receipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power,
replace=replace)
return receipt
@contract_api(CONTRACT_CALL)
def get_flags(self, staker_address: ChecksumAddress) -> StakerFlags:
flags: tuple = self.contract.functions.getFlags(staker_address).call()
wind_down_flag, restake_flag, measure_work_flag, snapshot_flag, migration_flag = flags
return StakerFlags(wind_down_flag, restake_flag, measure_work_flag, snapshot_flag, migration_flag)
@contract_api(CONTRACT_CALL)
def is_restaking(self, staker_address: ChecksumAddress) -> bool:
flags = self.get_flags(staker_address)
return flags.restake_flag
@contract_api(TRANSACTION)
def set_restaking(self, transacting_power: TransactingPower, value: bool) -> TxReceipt:
"""
Enable automatic restaking for a fixed duration of lock periods.
If set to True, then all staking rewards will be automatically added to locked stake.
"""
contract_function: ContractFunction = self.contract.functions.setReStake(value)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
# TODO: Handle ReStakeSet event (see #1193)
return receipt
@contract_api(CONTRACT_CALL)
def is_migrated(self, staker_address: ChecksumAddress) -> bool:
flags = self.get_flags(staker_address)
return flags.migration_flag
@contract_api(TRANSACTION)
def migrate(self, transacting_power: TransactingPower, staker_address: Optional[ChecksumAddress] = None) -> TxReceipt:
if not staker_address:
staker_address = transacting_power.account
contract_function: ContractFunction = self.contract.functions.migrate(staker_address)
receipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(CONTRACT_CALL)
def is_winding_down(self, staker_address: ChecksumAddress) -> bool:
flags = self.get_flags(staker_address)
return flags.wind_down_flag
@contract_api(TRANSACTION)
def set_winding_down(self, transacting_power: TransactingPower, value: bool) -> TxReceipt:
"""
Enable wind down for stake.
If set to True, then stakes duration will decrease in each period with `commitToNextPeriod()`.
"""
contract_function: ContractFunction = self.contract.functions.setWindDown(value)
receipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
# TODO: Handle WindDownSet event (see #1193)
return receipt
@contract_api(CONTRACT_CALL)
def is_taking_snapshots(self, staker_address: ChecksumAddress) -> bool:
flags = self.get_flags(staker_address)
return flags.snapshot_flag
@contract_api(TRANSACTION)
def set_snapshots(self, transacting_power: TransactingPower, activate: bool) -> TxReceipt:
"""
Activate/deactivate taking balance snapshots.
If set to True, then each time the balance changes, a snapshot associated to current block number is stored.
"""
contract_function: ContractFunction = self.contract.functions.setSnapshots(activate)
receipt = self.blockchain.send_transaction(contract_function=contract_function, transacting_power=transacting_power)
# TODO: Handle SnapshotSet event (see #1193)
return receipt
@contract_api(TRANSACTION)
def remove_inactive_stake(self, transacting_power: TransactingPower, stake_index: int) -> TxReceipt:
contract_function: ContractFunction = self.contract.functions.removeUnusedSubStake(stake_index)
receipt: TxReceipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@contract_api(CONTRACT_CALL)
def staking_parameters(self) -> StakingEscrowParameters:
parameter_signatures = (
# Period
'genesisSecondsPerPeriod', # Seconds in single period at genesis
'secondsPerPeriod', # Seconds in single period
# Coefficients
'mintingCoefficient', # Minting coefficient (d * k2)
'lockDurationCoefficient1', # Numerator of the lock duration coefficient (k1)
'lockDurationCoefficient2', # Denominator of the lock duration coefficient (k2)
'maximumRewardedPeriods', # Max periods that will be additionally rewarded (kmax)
'firstPhaseTotalSupply', # Total supply for the first phase
'firstPhaseMaxIssuance', # Max possible reward for one period for all stakers in the first phase
# Constraints
'minLockedPeriods', # Min amount of periods during which tokens can be locked
'minAllowableLockedTokens', # Min amount of tokens that can be locked
'maxAllowableLockedTokens', # Max amount of tokens that can be locked
'minWorkerPeriods' # Min amount of periods while a worker can't be changed
)
def _call_function_by_name(name: str) -> int:
return getattr(self.contract.functions, name)().call()
staking_parameters = StakingEscrowParameters(tuple(map(_call_function_by_name, parameter_signatures)))
return staking_parameters
#
# Contract Utilities
#
@contract_api(CONTRACT_CALL)
def swarm(self) -> Iterable[ChecksumAddress]:
"""
Returns an iterator of all staker addresses via cumulative sum, on-network.
Staker addresses are returned in the order in which they registered with the StakingEscrow contract's ledger
"""
for index in range(self.get_staker_population()):
staker_address: ChecksumAddress = self.contract.functions.stakers(index).call()
yield staker_address
def get_stakers_reservoir(self,
periods: int,
without: Iterable[ChecksumAddress] = None,
pagination_size: Optional[int] = None
) -> 'StakingProvidersReservoir':
n_tokens, stakers_map = self.get_all_active_stakers(periods=periods,
pagination_size=pagination_size)
filtered_out = 0
if without:
for address in without:
if address in stakers_map:
n_tokens -= stakers_map[address]
del stakers_map[address]
filtered_out += 1
self.log.debug(f"Got {len(stakers_map)} stakers with {n_tokens} total tokens "
f"({filtered_out} filtered out)")
if n_tokens == 0:
raise self.NotEnoughStakers(f'There are no locked tokens for duration {periods}.')
return StakingProvidersReservoir(stakers_map)
@contract_api(CONTRACT_CALL)
def get_completed_work(self, bidder_address: ChecksumAddress) -> Work:
total_completed_work = self.contract.functions.getCompletedWork(bidder_address).call()
return total_completed_work
@contract_api(CONTRACT_CALL)
def get_missing_commitments(self, checksum_address: ChecksumAddress) -> int:
# TODO: Move this up one layer, since it utilizes a combination of contract API methods.
last_committed_period = self.get_last_committed_period(checksum_address)
current_period = self.get_current_period()
missing_commitments = current_period - last_committed_period
if missing_commitments in (0, -1):
result = 0
elif last_committed_period == 0: # never committed
stakes = list(self.get_all_stakes(staker_address=checksum_address))
initial_staking_period = min(stakes, key=lambda s: s[0])[0]
result = current_period - initial_staking_period
else:
result = missing_commitments
return result
@property # type: ignore
@contract_api(CONTRACT_ATTRIBUTE)
def worklock(self) -> ChecksumAddress:
return self.contract.functions.workLock().call()
@property # type: ignore
@contract_api(CONTRACT_ATTRIBUTE)
def adjudicator(self) -> ChecksumAddress:
return self.contract.functions.adjudicator().call()
@property # type: ignore
@contract_api(CONTRACT_ATTRIBUTE)
def policy_manager(self) -> ChecksumAddress:
return self.contract.functions.policyManager().call()
class SubscriptionManagerAgent(EthereumContractAgent):
contract_name: str = SUBSCRIPTION_MANAGER_CONTRACT_NAME
@ -1141,7 +566,8 @@ class ContractAgency:
def get_agent(cls,
agent_class: Type[Agent],
registry: Optional[BaseContractRegistry] = None,
eth_provider_uri: Optional[str] = None
eth_provider_uri: Optional[str] = None,
contract_version: Optional[str] = None
) -> Agent:
if not issubclass(agent_class, EthereumContractAgent):
@ -1157,7 +583,7 @@ class ContractAgency:
try:
return cast(Agent, cls.__agents[registry_id][agent_class])
except KeyError:
agent = cast(Agent, agent_class(registry=registry, eth_provider_uri=eth_provider_uri))
agent = cast(Agent, agent_class(registry=registry, eth_provider_uri=eth_provider_uri, contract_version=contract_version))
cls.__agents[registry_id] = cls.__agents.get(registry_id, dict())
cls.__agents[registry_id][agent_class] = agent
return agent
@ -1174,12 +600,18 @@ class ContractAgency:
def get_agent_by_contract_name(cls,
contract_name: str,
registry: BaseContractRegistry,
eth_provider_uri: Optional[str] = None
eth_provider_uri: Optional[str] = None,
contract_version: Optional[str] = None
) -> EthereumContractAgent:
agent_name: str = cls._contract_name_to_agent_name(name=contract_name)
agents_module = sys.modules[__name__]
agent_class: Type[EthereumContractAgent] = getattr(agents_module, agent_name)
agent: EthereumContractAgent = cls.get_agent(agent_class=agent_class, registry=registry, eth_provider_uri=eth_provider_uri)
agent: EthereumContractAgent = cls.get_agent(
agent_class=agent_class,
registry=registry,
eth_provider_uri=eth_provider_uri,
contract_version=contract_version
)
return agent

View File

@ -112,7 +112,7 @@ def only_me(func: Callable) -> Callable:
@functools.wraps(func)
def wrapped(actor=None, *args, **kwargs):
if not actor.is_me:
raise actor.StakerError("You are not {}".format(actor.__class.__.__name__))
raise actor.ActorError("You are not {}".format(actor.__class.__.__name__))
return func(actor, *args, **kwargs)
return wrapped

View File

@ -34,11 +34,10 @@ from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
EthereumContractAgent,
NucypherTokenAgent,
StakingEscrowAgent,
PREApplicationAgent,
SubscriptionManagerAgent
)
from nucypher.blockchain.eth.constants import DISPATCHER_CONTRACT_NAME, NULL_ADDRESS, STAKING_ESCROW_CONTRACT_NAME
from nucypher.blockchain.eth.constants import DISPATCHER_CONTRACT_NAME, STAKING_ESCROW_CONTRACT_NAME, NULL_ADDRESS
from nucypher.blockchain.eth.interfaces import (
BlockchainDeployerInterface,
BlockchainInterfaceFactory,
@ -530,8 +529,8 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna
Deploys the StakingEscrow ethereum contract to the blockchain. Depends on NucypherTokenAgent
"""
agency = StakingEscrowAgent
contract_name = agency.contract_name
agency = NotImplemented # StakingEscrowAgent was here
contract_name = 'StakingEscrow'
contract_name_stub = "StakingEscrowStub"
can_be_idle = True
@ -677,7 +676,7 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna
# 2 - Deploy the dispatcher used for updating this contract #
dispatcher_deployer = DispatcherDeployer(registry=self.registry, target_contract=the_escrow_contract)
dispatcher_receipts = dispatcher_deployer.deploy(transacting_power=transacting_power,
dispatcher_receipts = dispatcher_deployer.deploy(transacting_power=transacting_power,
gas_limit=gas_limit,
confirmations=confirmations)
dispatcher_deploy_receipt = dispatcher_receipts[dispatcher_deployer.deployment_steps[0]]

View File

@ -678,28 +678,46 @@ class BlockchainInterface:
Instantiate a deployed contract from registry data,
and assimilate it with its proxy if it is upgradeable.
"""
target_contract_records = registry.search(contract_name=contract_name, contract_version=contract_version)
target_contract_records = registry.search(contract_name=contract_name, contract_version=contract_version)
if not target_contract_records:
raise self.UnknownContract(f"No such contract records with name {contract_name}:{contract_version}.")
if contract_version and len(target_contract_records) != 1:
# Assert single contract record returned
raise self.InterfaceError(f"Registry is potentially corrupt - multiple {contract_name} "
f"contract records with the same version {contract_version}")
if proxy_name:
if contract_version:
# contract version was specified - need more information related to proxy
target_all_contract_records = registry.search(contract_name=contract_name)
else:
# we don't need a separate copy of original result
target_all_contract_records = target_contract_records
# Lookup proxies; Search for a published proxy that targets this contract record
proxy_records = registry.search(contract_name=proxy_name)
results = list()
for proxy_name, proxy_version, proxy_address, proxy_abi in proxy_records:
proxy_contract = self.client.w3.eth.contract(abi=proxy_abi,
address=proxy_address,
version=proxy_version,
ContractFactoryClass=self._CONTRACT_FACTORY)
# Read this dispatcher's target address from the blockchain
# Read this dispatcher's current target address from the blockchain
proxy_live_target_address = proxy_contract.functions.target().call()
for target_name, target_version, target_address, target_abi in target_contract_records:
# either proxy is targeting latest version of contract
# or
# use older version of the same contract
for target_name, target_version, target_address, target_abi in target_all_contract_records:
if target_address == proxy_live_target_address:
if contract_version:
# contract_version specified - use specific contract
target_version = target_contract_records[0][1]
target_abi = target_contract_records[0][3]
if use_proxy_address:
triplet = (proxy_address, target_version, target_abi)
else:
@ -713,7 +731,6 @@ class BlockchainInterface:
address, _version, _abi = results[0]
message = "Multiple {} deployments are targeting {}".format(proxy_name, address)
raise self.InterfaceError(message.format(contract_name))
else:
try:
selected_address, selected_version, selected_abi = results[0]
@ -723,7 +740,6 @@ class BlockchainInterface:
else:
# TODO: use_proxy_address doesnt' work in this case. Should we raise if used?
# NOTE: 0 must be allowed as a valid version number
if len(target_contract_records) != 1:
if enrollment_version is None:

View File

@ -17,16 +17,10 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import random
from _pydecimal import Decimal
from collections import UserList
from enum import Enum
from eth_typing.evm import ChecksumAddress
from typing import Callable, Dict, Union
import maya
from constant_sorrow.constants import (
EMPTY_STAKING_SLOT,
NEW_STAKE,
NOT_STAKING,
UNTRACKED_PENDING_TRANSACTION
)
@ -35,12 +29,8 @@ from hexbytes.main import HexBytes
from twisted.internet import reactor, task
from web3.exceptions import TransactionNotFound
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS, NULL_ADDRESS
from nucypher.blockchain.eth.decorators import validate_checksum_address
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.blockchain.eth.utils import datetime_at_period
from nucypher.types import SubStakeInfo, ERC20UNits, NuNits, TuNits, StakerInfo, Period
from nucypher.types import ERC20UNits, NuNits, TuNits
from nucypher.utilities.gas_strategies import EXPECTED_CONFIRMATION_TIME_IN_SECONDS
from nucypher.utilities.logging import Logger
@ -182,395 +172,6 @@ class TToken(ERC20):
_unit = TuNits
class Stake:
"""
A quantity of tokens and staking duration in periods for one stake for one staker.
"""
class StakingError(Exception):
"""Raised when a staking operation cannot be executed due to failure."""
class Status(Enum):
"""
Sub-stake status.
"""
INACTIVE = 1 # Unlocked inactive
UNLOCKED = 2 # Unlocked active
LOCKED = 3 # Locked but not editable
EDITABLE = 4 # Editable
DIVISIBLE = 5 # Editable and divisible
def is_child(self, other: 'Status') -> bool:
if other == self.INACTIVE:
return self == self.INACTIVE
elif other == self.UNLOCKED:
return self.value <= self.UNLOCKED.value
else:
return self.value >= other.value
@validate_checksum_address
def __init__(self,
staking_agent: StakingEscrowAgent,
checksum_address: str,
value: NU,
first_locked_period: int,
final_locked_period: int,
index: int,
economics):
self.log = Logger(f'stake-{checksum_address}-{index}')
# Ownership
self.staker_address = checksum_address
# Stake Metadata
self.index = index
self.value = value
# Periods
self.first_locked_period = first_locked_period
# TODO: #1502 - Move Me Brightly - Docs
# After this period has passes, workers can go offline, if this is the only stake.
# This is the last period that can be committed for this stake.
# Meaning, It must be committed in the previous period,
# and no commitment can be made in this period for this stake.
self.final_locked_period = final_locked_period
# Blockchain
self.staking_agent = staking_agent
# Economics
self.economics = economics
self.minimum_nu = NU(int(self.economics.min_authorization), NU._unit_name)
self.maximum_nu = NU(int(self.economics.maximum_allowed_locked), NU._unit_name)
# Time
self.start_datetime = datetime_at_period(period=first_locked_period,
seconds_per_period=self.economics.seconds_per_period,
start_of_period=True)
self.unlock_datetime = datetime_at_period(period=final_locked_period + 1,
seconds_per_period=self.economics.seconds_per_period,
start_of_period=True)
self._status = None
def __repr__(self) -> str:
r = f'Stake(' \
f'index={self.index}, ' \
f'value={self.value}, ' \
f'end_period={self.final_locked_period}, ' \
f'address={self.staker_address[:6]}, ' \
f'escrow={self.staking_agent.contract_address[:6]}' \
f')'
return r
def __eq__(self, other: 'Stake') -> bool:
this_stake = (self.index,
self.value,
self.first_locked_period,
self.final_locked_period,
self.staker_address,
self.staking_agent.contract_address)
try:
that_stake = (other.index,
other.value,
other.first_locked_period,
other.final_locked_period,
other.staker_address,
other.staking_agent.contract_address)
except AttributeError:
return False
return this_stake == that_stake
@property
def address_index_ordering_key(self):
"""To be used as a lexicographical order key for Stakes based on the tuple (staker_address, index)."""
return self.staker_address, self.index
#
# Metadata
#
def status(self, staker_info: StakerInfo = None, current_period: Period = None) -> Status:
"""
Returns status of sub-stake:
UNLOCKED - final period in the past
INACTIVE - UNLOCKED and sub-stake will not be included in any future calculations
LOCKED - sub-stake is still locked and final period is current period
EDITABLE - LOCKED and final period greater than current
DIVISIBLE - EDITABLE and locked value is greater than two times the minimum allowed locked
"""
if self._status:
return self._status
staker_info = staker_info or self.staking_agent.get_staker_info(self.staker_address) # TODO related to #1514
current_period = current_period or self.staking_agent.get_current_period() # TODO #1514 this is online only.
if self.final_locked_period < current_period:
if (staker_info.current_committed_period == 0 or
staker_info.current_committed_period > self.final_locked_period) and \
(staker_info.next_committed_period == 0 or
staker_info.next_committed_period > self.final_locked_period):
self._status = Stake.Status.INACTIVE
else:
self._status = Stake.Status.UNLOCKED
elif self.final_locked_period == current_period:
self._status = Stake.Status.LOCKED
elif self.value < 2 * self.economics.min_authorization:
self._status = Stake.Status.EDITABLE
else:
self._status = Stake.Status.DIVISIBLE
return self._status
@classmethod
@validate_checksum_address
def from_stake_info(cls,
checksum_address: str,
index: int,
stake_info: SubStakeInfo,
economics,
*args, **kwargs
) -> 'Stake':
"""Reads staking values as they exist on the blockchain"""
instance = cls(checksum_address=checksum_address,
index=index,
first_locked_period=stake_info.first_period,
final_locked_period=stake_info.last_period,
value=NU(stake_info.locked_value, NU._unit_name),
economics=economics,
*args, **kwargs)
return instance
def to_stake_info(self) -> SubStakeInfo:
"""Returns a tuple representing the blockchain record of a stake"""
return SubStakeInfo(self.first_locked_period, self.final_locked_period, self.value.to_units())
#
# Duration
#
@property
def duration(self) -> int:
"""Return stake duration in periods"""
result = (self.final_locked_period - self.first_locked_period) + 1
return result
@property
def periods_remaining(self) -> int:
"""Returns the number of periods remaining in the stake from now."""
current_period = self.staking_agent.get_current_period()
return self.final_locked_period - current_period + 1
def time_remaining(self, slang: bool = False) -> Union[int, str]:
"""
Returns the time delta remaining in the stake from now.
This method is designed for *UI* usage.
"""
if slang:
result = self.unlock_datetime.slang_date()
else:
# TODO - #1509 EthAgent?
blocktime_epoch = self.staking_agent.blockchain.client.get_blocktime()
delta = self.unlock_datetime.epoch - blocktime_epoch
result = delta
return result
@property
def kappa(self) -> float:
"""Returns the kappa factor for this substake based on its duration.
The kappa factor grows linearly with duration, but saturates when it's longer than the maximum rewarded periods.
See the economics papers for a more detailed description."""
T_max = self.economics.maximum_rewarded_periods
kappa = 0.5 * (1 + min(T_max, self.periods_remaining) / T_max)
return kappa
@property
def boost(self) -> float:
"""An alternative representation of the kappa coefficient, easier to understand.
Sub-stake boosts can range from 1x (i.e. no boost) to 2x (max boost). """
min_kappa = 0.5
boost = self.kappa / min_kappa
return boost
def describe(self) -> Dict[str, str]:
start_datetime = self.start_datetime.local_datetime().strftime("%b %d %Y")
end_datetime = self.unlock_datetime.local_datetime().strftime("%b %d %Y")
data = dict(index=self.index,
value=str(self.value),
remaining=self.periods_remaining,
enactment=start_datetime,
last_period=end_datetime,
boost=f"{self.boost:.2f}x",
status=self.status().name)
return data
#
# Blockchain
#
def sync(self) -> None:
"""Update this stakes attributes with on-chain values."""
# Read from blockchain
stake_info = self.staking_agent.get_substake_info(staker_address=self.staker_address,
stake_index=self.index) # < -- Read from blockchain
if not self.first_locked_period == stake_info.first_period:
raise self.StakingError("Inconsistent staking cache. Make sure your node is synced and try again.")
# Mutate the instance with the on-chain values
self.final_locked_period = stake_info.last_period
self.value = NU.from_units(stake_info.locked_value)
self._status = None
@classmethod
def initialize_stake(cls,
staking_agent,
economics,
checksum_address: str,
amount: NU,
lock_periods: int) -> 'Stake':
# Value
amount = NU(int(amount), NU._unit_name)
# Duration
current_period = staking_agent.get_current_period()
final_locked_period = current_period + lock_periods
stake = cls(checksum_address=checksum_address,
first_locked_period=current_period + 1,
final_locked_period=final_locked_period,
value=amount,
index=NEW_STAKE,
staking_agent=staking_agent,
economics=economics)
# Validate
validate_value(stake)
validate_duration(stake)
validate_max_value(stake)
return stake
def validate_value(stake: Stake) -> None:
"""Validate a single staking value against pre-defined requirements"""
if stake.minimum_nu > stake.value:
raise Stake.StakingError(f'Stake amount of {stake.value} is too low; must be at least {stake.minimum_nu}')
def validate_duration(stake: Stake) -> None:
"""Validate a single staking lock-time against pre-defined requirements"""
if stake.economics.min_operator_seconds > stake.duration:
raise Stake.StakingError(
'Stake duration of {duration} periods is too short; must be at least {minimum} periods.'
.format(minimum=stake.economics.min_operator_seconds, duration=stake.duration))
def validate_divide(stake: Stake, target_value: NU, additional_periods: int = None) -> None:
"""
Validates possibility to divide specified stake into two stakes using provided parameters.
"""
# Ensure selected stake is active
status = stake.status()
if not status.is_child(Stake.Status.DIVISIBLE):
raise Stake.StakingError(f'Cannot divide an non-divisible stake. '
f'Selected stake expired {stake.unlock_datetime} and has value {stake.value}.')
if target_value >= stake.value:
raise Stake.StakingError(f"Cannot divide stake; Target value ({target_value}) must be less "
f"than the existing stake value {stake.value}.")
#
# Generate SubStakes
#
# Modified Original Stake
remaining_stake_value = stake.value - target_value
modified_stake = Stake(checksum_address=stake.staker_address,
index=stake.index,
first_locked_period=stake.first_locked_period,
final_locked_period=stake.final_locked_period,
value=remaining_stake_value,
staking_agent=stake.staking_agent,
economics=stake.economics)
# New Derived Stake
end_period = stake.final_locked_period + additional_periods
new_stake = Stake(checksum_address=stake.staker_address,
first_locked_period=stake.first_locked_period,
final_locked_period=end_period,
value=target_value,
index=NEW_STAKE,
staking_agent=stake.staking_agent,
economics=stake.economics)
#
# Validate
#
# Ensure both halves are for valid amounts
validate_value(modified_stake)
validate_value(new_stake)
def validate_max_value(stake: Stake, amount: NU = None) -> None:
amount = amount or stake.value
# Ensure the new stake will not exceed the staking limit
locked_tokens = stake.staking_agent.get_locked_tokens(staker_address=stake.staker_address, periods=1)
if (locked_tokens + amount) > stake.economics.maximum_allowed_locked:
raise Stake.StakingError(f"Cannot initialize stake - "
f"Maximum stake value exceeded for {stake.staker_address} "
f"with a target value of {amount}.")
def validate_prolong(stake: Stake, additional_periods: int) -> None:
status = stake.status()
if not status.is_child(Stake.Status.EDITABLE):
raise Stake.StakingError(f'Cannot prolong a non-editable stake. '
f'Selected stake expired {stake.unlock_datetime}.')
new_duration = stake.periods_remaining + additional_periods - 1
if new_duration < stake.economics.min_operator_seconds:
raise stake.StakingError(f'Sub-stake duration of {new_duration} periods after prolongation '
f'is shorter than minimum allowed duration '
f'of {stake.economics.min_operator_seconds} periods.')
def validate_merge(stake_1: Stake, stake_2: Stake) -> None:
if stake_1.index == stake_2.index:
raise Stake.StakingError(f'Stakes must be different. '
f'Selected stakes have the same index {stake_1.index}.')
if stake_1.final_locked_period != stake_2.final_locked_period:
raise Stake.StakingError(f'Both stakes must have the same unlock date. '
f'Selected stakes expired {stake_1.unlock_datetime} and {stake_2.unlock_datetime}.')
status = stake_1.status()
if not status.is_child(Stake.Status.EDITABLE):
raise Stake.StakingError(f'Cannot merge a non-editable stakes. '
f'Selected stakes expired {stake_1.unlock_datetime}.')
def validate_increase(stake: Stake, amount: NU) -> None:
status = stake.status()
if not status.is_child(Stake.Status.EDITABLE):
raise Stake.StakingError(f'Cannot increase a non-editable stake. '
f'Selected stake expired {stake.unlock_datetime}.')
validate_max_value(stake=stake, amount=amount)
class WorkTrackerBase:
"""Baseclass for handling automated transaction tracking..."""
@ -837,101 +438,3 @@ class WorkTracker(WorkTrackerBase):
txhash = self.worker.confirm_address(fire_and_forget=True) # < --- blockchain WRITE
self.log.info(f"Confirming operator address {self.worker.operator_address} with staking provider {self.worker.staking_provider_address} - TxHash: {txhash.hex()}")
return txhash
class StakeList(UserList):
@validate_checksum_address
def __init__(self,
registry: BaseContractRegistry,
checksum_address: ChecksumAddress = None, # allow for lazy setting
*args, **kwargs):
super().__init__(*args, **kwargs)
self.log = Logger('stake-tracker')
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
from nucypher.blockchain.economics import EconomicsFactory
self.economics = EconomicsFactory.get_economics(registry=registry)
self.__initial_period = NOT_STAKING
self.__terminal_period = NOT_STAKING
# "load-in" Read on-chain stakes
self.checksum_address = checksum_address
self.__updated = None
@property
def updated(self) -> maya.MayaDT:
return self.__updated
@property
def initial_period(self) -> int:
return self.__initial_period
@property
def terminal_period(self) -> int:
return self.__terminal_period
@validate_checksum_address
def refresh(self) -> None:
"""Public staking cache invalidation method"""
return self.__read_stakes()
def __read_stakes(self) -> None:
"""Rewrite the local staking cache by reading on-chain stakes"""
existing_records = len(self)
# Candidate replacement cache values
current_period = self.staking_agent.get_current_period()
onchain_stakes, initial_period, terminal_period = list(), 0, current_period
# Read from blockchain
stakes_reader = self.staking_agent.get_all_stakes(staker_address=self.checksum_address)
inactive_substakes = []
for onchain_index, stake_info in enumerate(stakes_reader):
if not stake_info:
onchain_stake = EMPTY_STAKING_SLOT
else:
onchain_stake = Stake.from_stake_info(checksum_address=self.checksum_address,
stake_info=stake_info,
staking_agent=self.staking_agent,
index=onchain_index,
economics=self.economics)
# rack the earliest terminal period
if onchain_stake.first_locked_period:
if onchain_stake.first_locked_period < initial_period:
initial_period = onchain_stake.first_locked_period
# rack the latest terminal period
if onchain_stake.final_locked_period > terminal_period:
terminal_period = onchain_stake.final_locked_period
if onchain_stake.status().is_child(Stake.Status.INACTIVE):
inactive_substakes.append(onchain_index)
# Store the replacement stake
onchain_stakes.append(onchain_stake)
# Commit the new stake and terminal values to the cache
self.data = onchain_stakes
if onchain_stakes:
self.__initial_period = initial_period
self.__terminal_period = terminal_period
changed_records = abs(existing_records - len(onchain_stakes))
self.log.debug(f"Updated {changed_records} local staking cache entries.")
if inactive_substakes:
self.log.debug(f"The following sub-stakes are inactive: {inactive_substakes}")
# Record most recent cache update
self.__updated = maya.now()
@property
def has_active_substakes(self) -> bool:
current_period = self.staking_agent.get_current_period()
for stake in self.data:
if not stake.status(current_period=current_period).is_child(Stake.Status.INACTIVE):
return True

View File

@ -63,7 +63,6 @@ from nucypher.acumen.nicknames import Nickname
from nucypher.acumen.perception import ArchivedFleetState, RemoteUrsulaStatus
from nucypher.blockchain.eth.actors import Operator, BlockchainPolicyAuthor
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
from nucypher.blockchain.eth.agents import StakingEscrowAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.blockchain.eth.signers.software import Web3Signer
@ -382,9 +381,10 @@ class Alice(Character, BlockchainPolicyAuthor):
receipt, failed = dict(), dict()
if onchain and (not self.federated_only):
# TODO: Decouple onchain revocation from PolicyManager or deprecate.
receipt = self.policy_agent.revoke_policy(policy_id=bytes(policy.hrac),
transacting_power=self._crypto_power.power_ups(TransactingPower))
pass
# TODO: Decouple onchain revocation from SubscriptionManager or deprecate.
# receipt = self.policy_agent.revoke_policy(policy_id=bytes(policy.hrac),
# transacting_power=self._crypto_power.power_ups(TransactingPower))
if offchain:
"""
@ -674,7 +674,7 @@ class Ursula(Teacher, Character, Operator):
# TLSHostingPower # Still considered a default for Ursula, but needs the host context
]
class NotEnoughUrsulas(Learner.NotEnoughTeachers, StakingEscrowAgent.NotEnoughStakers):
class NotEnoughUrsulas(Learner.NotEnoughTeachers):
"""
All Characters depend on knowing about enough Ursulas to perform their role.
This exception is raised when a piece of logic can't proceed without more Ursulas.

View File

@ -14,6 +14,8 @@
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 glob
import json
from json.decoder import JSONDecodeError
@ -22,7 +24,6 @@ from typing import Optional, Type, List
import click
from nucypher.control.emitters import StdoutEmitter
from nucypher.characters.lawful import Ursula
from nucypher.cli.actions.confirm import confirm_destroy_configuration
from nucypher.cli.literature import (
@ -38,8 +39,8 @@ from nucypher.cli.literature import (
)
from nucypher.cli.types import OPERATOR_IP
from nucypher.config.base import CharacterConfiguration
from nucypher.config.characters import StakeHolderConfiguration
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.control.emitters import StdoutEmitter
from nucypher.utilities.networking import InvalidOperatorIP, validate_operator_ip
from nucypher.utilities.networking import determine_external_ip_address, UnknownIPAddress
@ -111,8 +112,6 @@ def handle_missing_configuration_file(character_config_class: Type[CharacterConf
config_file_location = config_file or character_config_class.default_filepath()
init_command = init_command_hint or f"{character_config_class.NAME} init"
name = character_config_class.NAME.capitalize()
if name == StakeHolderConfiguration.NAME.capitalize():
init_command = 'stake init-stakeholder'
message = MISSING_CONFIGURATION_FILE.format(name=name, init_command=init_command)
raise click.FileError(filename=str(config_file_location.absolute()), hint=message)

View File

@ -16,36 +16,25 @@
"""
from typing import Type, Union, Dict
import click
from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
from maya import MayaDT
from tabulate import tabulate
from typing import Type, Union, Dict
from web3.main import Web3
from nucypher.blockchain.economics import Economics
from nucypher.blockchain.eth.deployers import BaseContractDeployer
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, VersionedContract, BlockchainInterface
from nucypher.blockchain.eth.registry import LocalContractRegistry
from nucypher.blockchain.eth.token import NU
from nucypher.blockchain.eth.utils import calculate_period_duration
from nucypher.control.emitters import StdoutEmitter
from nucypher.cli.literature import (
ABORT_DEPLOYMENT,
CHARACTER_DESTRUCTION,
CONFIRM_ENABLE_RESTAKING,
CONFIRM_ENABLE_WINDING_DOWN,
CONFIRM_LARGE_STAKE_DURATION,
CONFIRM_LARGE_STAKE_VALUE,
CONFIRM_STAGED_STAKE,
RESTAKING_AGREEMENT,
WINDING_DOWN_AGREEMENT,
SNAPSHOTS_DISABLING_AGREEMENT,
CONFIRM_DISABLE_SNAPSHOTS
CHARACTER_DESTRUCTION
)
from nucypher.cli.literature import CONFIRM_VERSIONED_UPGRADE
from nucypher.config.base import CharacterConfiguration
from nucypher.control.emitters import StdoutEmitter
def confirm_deployment(emitter: StdoutEmitter, deployer_interface: BlockchainDeployerInterface) -> bool:
@ -65,47 +54,6 @@ def confirm_deployment(emitter: StdoutEmitter, deployer_interface: BlockchainDep
return True
def confirm_enable_restaking(emitter: StdoutEmitter, staking_address: str) -> bool:
"""Interactively confirm enabling of the restaking with user agreements."""
emitter.message(RESTAKING_AGREEMENT.format(staking_address=staking_address))
click.confirm(CONFIRM_ENABLE_RESTAKING.format(staking_address=staking_address), abort=True)
return True
def confirm_enable_winding_down(emitter: StdoutEmitter, staking_address: str) -> bool:
"""Interactively confirm enabling of winding down with user agreements."""
emitter.message(WINDING_DOWN_AGREEMENT)
click.confirm(CONFIRM_ENABLE_WINDING_DOWN.format(staking_address=staking_address), abort=True)
return True
def confirm_disable_snapshots(emitter: StdoutEmitter, staking_address: str) -> bool:
"""Interactively confirm disabling of taking snapshots with user agreements."""
emitter.message(SNAPSHOTS_DISABLING_AGREEMENT)
click.confirm(CONFIRM_DISABLE_SNAPSHOTS.format(staking_address=staking_address), abort=True)
return True
def confirm_staged_stake(staker_address: str, value: NU, lock_periods: int) -> bool:
"""Interactively confirm a new stake reviewing all staged stake details."""
click.confirm(CONFIRM_STAGED_STAKE.format(nunits=str(value.to_units()),
tokens=value,
staker_address=staker_address,
lock_periods=lock_periods), abort=True)
return True
def confirm_large_and_or_long_stake(value: NU = None, lock_periods: int = None, economics: Economics = None) -> bool:
"""Interactively confirm a large stake and/or a long stake duration."""
if economics and value and (value > (NU.from_units(economics.min_authorization) * 10)): # > 10x min stake
click.confirm(CONFIRM_LARGE_STAKE_VALUE.format(value=value), abort=True)
if economics and lock_periods and (lock_periods > economics.maximum_rewarded_periods): # > 1 year
lock_days = (lock_periods * economics.hours_per_period) // 24
click.confirm(CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=lock_periods, lock_days=lock_days),
abort=True)
return True
def confirm_destroy_configuration(config: CharacterConfiguration) -> bool:
"""Interactively confirm destruction of nucypher configuration files"""
# TODO: This is a workaround for ursula - needs follow up

View File

@ -17,68 +17,35 @@
import os
from pathlib import Path
from typing import Callable
from typing import Optional, Tuple, Type
from typing import Optional, Type
import click
from tabulate import tabulate
from web3.main import Web3
from nucypher.blockchain.eth.actors import StakeHolder, Staker
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry
from nucypher.blockchain.eth.signers.base import Signer
from nucypher.blockchain.eth.token import NU, Stake
from nucypher.control.emitters import StdoutEmitter
from nucypher.blockchain.eth.token import NU
from nucypher.cli.actions.configure import get_config_filepaths
from nucypher.cli.literature import (
GENERIC_SELECT_ACCOUNT,
NO_CONFIGURATIONS_ON_DISK,
NO_ETH_ACCOUNTS,
NO_STAKES_FOUND,
ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE,
SELECT_NETWORK,
SELECT_STAKE,
SELECT_STAKING_ACCOUNT_INDEX,
SELECTED_ACCOUNT,
IGNORE_OLD_CONFIGURATION,
DEFAULT_TO_LONE_CONFIG_FILE
)
from nucypher.cli.painting.policies import paint_cards
from nucypher.cli.painting.staking import paint_stakes
from nucypher.config.base import CharacterConfiguration
from nucypher.config.constants import NUCYPHER_ENVVAR_OPERATOR_ADDRESS, DEFAULT_CONFIG_ROOT
from nucypher.control.emitters import StdoutEmitter
from nucypher.policy.identity import Card
def select_stake(staker: Staker,
emitter: StdoutEmitter,
stakes_status: Stake.Status = Stake.Status.EDITABLE,
filter_function: Callable[[Stake], bool] = None
) -> Stake:
"""Interactively select a stake or abort if there are no eligible stakes."""
if stakes_status.is_child(Stake.Status.DIVISIBLE):
emitter.echo(ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE, color='yellow')
# Filter stakes by status
stakes = staker.sorted_stakes(parent_status=stakes_status, filter_function=filter_function)
if not stakes:
emitter.echo(NO_STAKES_FOUND, color='red')
raise click.Abort
# Interactive Selection
paint_unlocked = stakes_status.is_child(Stake.Status.UNLOCKED)
paint_stakes(staker=staker, emitter=emitter, stakes=stakes, paint_unlocked=paint_unlocked)
indexed_stakes = {stake.index: stake for stake in stakes}
indices = [str(index) for index in indexed_stakes.keys()]
choice = click.prompt(SELECT_STAKE, type=click.Choice(indices))
chosen_stake = indexed_stakes[int(choice)]
return chosen_stake
def select_client_account(emitter,
eth_provider_uri: str = None,
signer: Signer = None,
@ -169,33 +136,6 @@ def select_client_account(emitter,
return chosen_account
def select_client_account_for_staking(emitter: StdoutEmitter,
stakeholder: StakeHolder,
staking_address: Optional[str],
) -> Tuple[str, str]:
"""
Manages client account selection for stake-related operations.
It always returns a tuple of addresses: the first is the local client account and the second is the staking address.
When this is not a preallocation staker (which is the normal use case), both addresses are the same.
Otherwise, when the staker is a contract managed by a beneficiary account,
then the local client account is the beneficiary, and the staking address is the address of the staking contract.
"""
if staking_address:
client_account = staking_address
else:
client_account = select_client_account(prompt=SELECT_STAKING_ACCOUNT_INDEX,
emitter=emitter,
registry=stakeholder.registry,
network=stakeholder.domain,
signer=stakeholder.signer)
staking_address = client_account
stakeholder.assimilate(client_account)
return client_account, staking_address
def select_network(emitter: StdoutEmitter, message: Optional[str] = None) -> str:
"""Interactively select a network from nucypher networks inventory list"""
emitter.message(message=message or str(), color='yellow')

View File

@ -1,49 +0,0 @@
"""
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 click
from nucypher.blockchain.eth.actors import StakeHolder
from nucypher.blockchain.eth.token import Stake
from nucypher.cli.literature import PERIOD_ADVANCED_WARNING, SUCCESSFUL_STAKE_REMOVAL, CONFIRM_REMOVE_SUBSTAKE
from nucypher.cli.painting.staking import paint_stakes
from nucypher.cli.painting.transactions import paint_receipt_summary
def remove_inactive_substake(emitter,
stakeholder: StakeHolder,
action_period: int,
stake: Stake,
chain_name: str,
force: bool
) -> None:
# Non-interactive: Consistency check to prevent the above agreement from going stale.
last_second_current_period = stakeholder.staker.staking_agent.get_current_period()
if action_period != last_second_current_period:
emitter.echo(PERIOD_ADVANCED_WARNING, color='red')
raise click.Abort
if not force:
click.confirm(CONFIRM_REMOVE_SUBSTAKE.format(stake_index=stake.index), abort=True)
# Execute
receipt = stakeholder.staker.remove_inactive_stake(stake=stake)
# Report
emitter.echo(SUCCESSFUL_STAKE_REMOVAL, color='green', verbosity=1)
paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=chain_name)
paint_stakes(emitter=emitter, staker=stakeholder.staker)

View File

@ -18,6 +18,9 @@
import click
import os
from nucypher.cli.config import group_general_config
from nucypher.cli.options import option_config_file
try:
from nucypher.utilities.clouddeploy import CloudDeployers
except ImportError:
@ -26,8 +29,6 @@ except ImportError:
# exception similar to DevelopmentInstallationRequired.
CloudDeployers = None
from nucypher.cli.utils import setup_emitter
from nucypher.config.characters import StakeHolderConfiguration
from nucypher.cli.commands.stake import group_staker_options, option_config_file, group_general_config
def filter_staker_addresses(stakers, stakes):
@ -49,7 +50,7 @@ def cloudworkers():
@cloudworkers.command('up')
@group_staker_options
# @group_staker_options
@option_config_file
@click.option('--cloudprovider', help="aws or digitalocean", default='aws')
@click.option('--aws-profile', help="The cloud provider account profile you'd like to use (an aws profile)", default=None)
@ -154,7 +155,7 @@ def add(general_config, host_address, login_name, key_path, ssh_port, host_nickn
@cloudworkers.command('add_for_stake')
@group_staker_options
# @group_staker_options
@option_config_file
@click.option('--staker-address', help="The staker account address for whom you are adding a worker host.", required=True)
@click.option('--host-address', help="The IP address or Hostname of the host you are adding.", required=True)

File diff suppressed because it is too large Load Diff

View File

@ -15,18 +15,20 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
from pathlib import Path
import click
from nucypher.blockchain.eth.actors import Staker
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
from nucypher.blockchain.eth.constants import (
STAKING_ESCROW_CONTRACT_NAME
from nucypher.blockchain.eth.agents import (
ContractAgency,
PREApplicationAgent,
SubscriptionManagerAgent,
EthereumContractAgent
)
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.utils import estimate_block_number_for_period
from nucypher.cli.config import group_general_config
from nucypher.cli.options import (
group_options,
@ -37,10 +39,9 @@ from nucypher.cli.options import (
option_poa,
option_eth_provider_uri,
option_registry_filepath,
option_staking_address,
option_staking_provider
)
from nucypher.cli.painting.staking import paint_stakes
from nucypher.cli.painting.status import paint_contract_status, paint_locked_tokens_status, paint_stakers
from nucypher.cli.painting.status import paint_contract_status
from nucypher.cli.utils import (
connect_to_blockchain,
get_registry,
@ -51,6 +52,22 @@ from nucypher.cli.utils import (
from nucypher.config.constants import NUCYPHER_ENVVAR_ETH_PROVIDER_URI
from nucypher.utilities.events import generate_events_csv_filepath
STAKING_ESCROW = 'StakingEscrow'
POLICY_MANAGER = 'PolicyManager'
CONTRACT_NAMES = [
PREApplicationAgent.contract_name,
SubscriptionManagerAgent.contract_name,
STAKING_ESCROW,
POLICY_MANAGER
]
# The default contract version to use with the --legacy flag
LEGACY_CONTRACT_VERSIONS = {
STAKING_ESCROW: 'v5.7.1',
POLICY_MANAGER: 'v6.2.1'
}
class RegistryOptions:
@ -114,37 +131,17 @@ def network(general_config, registry_options):
paint_contract_status(registry, emitter=emitter)
@status.command()
@status.command('pre')
@group_registry_options
@option_staking_address
@click.option('--substakes', help="Print all sub-stakes for this staker", is_flag=True, default=False)
@option_staking_provider
@group_general_config
def stakers(general_config, registry_options, staking_address, substakes):
"""Show relevant information about stakers."""
if substakes and not staking_address:
raise click.BadOptionUsage(option_name="--substakes",
message="--substakes is only valid when used with --staking-address.")
def staking_providers(general_config, registry_options, staking_provider_address):
"""Show relevant information about staking providers."""
emitter, registry, blockchain = registry_options.setup(general_config=general_config)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
stakers_list = [staking_address] if staking_address else staking_agent.get_stakers()
paint_stakers(emitter=emitter, stakers=stakers_list, registry=registry)
if substakes:
staker = Staker(registry=registry,
domain=registry_options.network,
checksum_address=staking_address)
staker.stakes.refresh()
paint_stakes(emitter=emitter, staker=staker, paint_unlocked=True)
@status.command(name='locked-tokens')
@group_registry_options
@click.option('--periods', help="Number of periods", type=click.INT, default=90)
@group_general_config
def locked_tokens(general_config, registry_options, periods):
"""Display a graph of the number of locked tokens over time."""
emitter, registry, blockchain = registry_options.setup(general_config=general_config)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
paint_locked_tokens_status(emitter=emitter, agent=staking_agent, periods=periods)
application_agent = ContractAgency.get_agent(PREApplicationAgent, registry=registry)
staking_providers_list = [staking_provider_address] if staking_provider_address else application_agent.get_staking_providers()
emitter.echo(staking_providers_list) # TODO: staking provider painter
# paint_stakers(emitter=emitter, stakers=staking_providers_list, registry=registry)
@status.command()
@ -157,8 +154,8 @@ def locked_tokens(general_config, registry_options, periods):
@option_csv
@option_csv_file
@option_event_filters
# TODO: Add options for number of periods in the past (default current period), or range of blocks
def events(general_config, registry_options, contract_name, from_block, to_block, event_name, csv, csv_file, event_filters):
@click.option('--legacy', help="Events related to the NuCypher Network prior to the merge to Threshold Network", is_flag=True)
def events(general_config, registry_options, contract_name, from_block, to_block, event_name, csv, csv_file, event_filters, legacy):
"""Show events associated with NuCypher contracts."""
if csv or csv_file:
@ -178,7 +175,6 @@ def events(general_config, registry_options, contract_name, from_block, to_block
if event_name:
raise click.BadOptionUsage(option_name='--event-name', message='--event-name requires --contract-name')
# FIXME should we force a contract name to be specified?
contract_names = [STAKING_ESCROW_CONTRACT_NAME,]
else:
contract_names = [contract_name]
@ -186,12 +182,8 @@ def events(general_config, registry_options, contract_name, from_block, to_block
if from_block is None:
# by default, this command only shows events of the current period
last_block = blockchain.client.block_number
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
current_period = staking_agent.get_current_period()
from_block = estimate_block_number_for_period(period=current_period,
seconds_per_period=staking_agent.staking_parameters()[1],
latest_block=last_block)
blocks_since_yesterday_kinda = ((60*60*24)//AVERAGE_BLOCK_TIME_IN_SECONDS)
from_block = blockchain.client.block_number - blocks_since_yesterday_kinda
if to_block is None:
to_block = 'latest'
else:
@ -212,8 +204,29 @@ def events(general_config, registry_options, contract_name, from_block, to_block
f'the form `<name>=<value>` - {str(e)}')
emitter.echo(f"Retrieving events from block {from_block} to {to_block}")
for contract_name in contract_names:
agent = ContractAgency.get_agent_by_contract_name(contract_name, registry)
contract_version = None
if legacy and contract_name in LEGACY_CONTRACT_VERSIONS:
contract_version = LEGACY_CONTRACT_VERSIONS[contract_name]
for contract_name in CONTRACT_NAMES:
if legacy:
versioned_contract = blockchain.get_contract_by_name(
registry=registry,
contract_name=contract_name,
contract_version=contract_version,
proxy_name='Dispatcher',
use_proxy_address=True
)
agent = EthereumContractAgent(contract=versioned_contract)
agent.contract_name = contract_name
else:
agent = ContractAgency.get_agent_by_contract_name(
contract_name=contract_name,
contract_version=contract_version,
registry=registry
)
if event_name and event_name not in agent.events.names:
raise click.BadOptionUsage(option_name='--event-name, --contract_name',
message=f'{contract_name} contract does not have an event named {event_name}')

View File

@ -45,281 +45,35 @@ FEDERATED_WARNING = "WARNING: Running in Federated mode"
PERIOD_ADVANCED_WARNING = "Current period advanced before the action could be completed. Please try again."
#
# Staking
#
CONFIRM_STAGED_STAKE = """
* Ursula Node Operator Notice *
-------------------------------
By agreeing to stake {tokens} ({nunits} NuNits):
- Staked tokens will be locked for the stake duration.
- You are obligated to maintain a networked and available Ursula-Worker node
bonded to the staker address {staker_address} for the duration
of the stake(s) ({lock_periods} periods).
- Agree to allow NuCypher network users to carry out uninterrupted re-encryption
work orders at-will without interference.
Failure to keep your node online or fulfill re-encryption work orders will result
in loss of staked NU as described in the NuCypher slashing protocol:
https://docs.nucypher.com/en/latest/architecture/slashing.html.
Keeping your Ursula node online during the staking period and successfully
producing correct re-encryption work orders will result in rewards
paid out in ethers retro-actively and on-demand.
Accept ursula node operator obligation?"""
CONFIRM_LARGE_STAKE_VALUE = "Wow, {value} - That's a lot of NU - Are you sure this is correct?"
CONFIRM_LARGE_STAKE_DURATION = "Woah, {lock_periods} periods ({lock_days} days) is a long time - Are you sure this is correct?"
PROMPT_STAKE_CREATE_VALUE = "Enter stake value in NU ({lower_limit} - {upper_limit})"
PROMPT_STAKE_CREATE_LOCK_PERIODS = "Enter stake duration ({min_locktime} - {max_locktime})"
CONFIRM_STAKE_USE_UNLOCKED = "Confirm only use uncollected staking rewards and unlocked sub-stakes; not tokens from staker address"
CONFIRM_BROADCAST_CREATE_STAKE = "Publish staged stake to the blockchain?"
CONFIRM_INCREASING_STAKE = "Confirm increase stake (index: {stake_index}) by {value}?"
CONFIRM_INCREASING_STAKE_DISCLAIMER = """
NOTE: Due to a known issue with the StakingEscrow contract, using the increase operation may lead to reduced staking
rewards for the first period after the increase (GitHub Issue: https://github.com/nucypher/nucypher/issues/2691).
The workaround to increase stake size without reduced staking rewards is the following:
1. Create a new sub-stake with the same duration as the current sub-stake
2. Wait until there has been a Worker node commitment made in the period after the sub-stake was created
3. Once there has been a commitment made in the period after the sub-stake was created, merge the sub-stakes at any time afterwards
For example,
- If you created a sub-stake in period 10
- Wait until there has been a commitment made in the period after the sub-stake was created (i.e. in period 11)
- Then merge the sub-stake in period 11 after the commitment, or during any period afterwards
Are you sure you want to use the increase operation instead of the workaround?
"""
INSUFFICIENT_BALANCE_TO_INCREASE = "There are no tokens to increase stake"
INSUFFICIENT_BALANCE_TO_CREATE = "Insufficient NU for stake creation."
MAXIMUM_STAKE_REACHED = "Maximum stake reached, can't lock more"
PROMPT_STAKE_INCREASE_VALUE = "Enter stake value in NU (up to {upper_limit})"
SUCCESSFUL_STAKE_INCREASE = 'Successfully increased stake'
NO_STAKING_ACCOUNTS = "No staking accounts found."
SELECT_STAKING_ACCOUNT_INDEX = "Select index of staking account"
NO_ACTIVE_STAKES = "No active stakes found\n"
NO_STAKES_AT_ALL = "No Stakes found"
SELECT_STAKE = "Select Stake"
NO_STAKES_FOUND = "No stakes found."
CONFIRM_MANUAL_MIGRATION = "Confirm manual migration for staker {address}"
MIGRATION_ALREADY_PERFORMED = 'Staker {address} has already migrated.'
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/staking/running_a_worker.html
"""
#
# Events
#
CONFIRM_OVERWRITE_EVENTS_CSV_FILE = "Overwrite existing CSV events file - {csv_file}?"
#
# Remove Inactive
#
FETCHING_INACTIVE_STAKES = 'Fetching inactive stakes'
NO_INACTIVE_STAKES = "No inactive stakes found\n"
CONFIRM_REMOVE_ALL_INACTIVE_SUBSTAKES = """
This action will perform a series of transactions to remove all unused sub-stakes
(Indices {stakes}). It is recommended that you verify each staker transaction was successful (https://etherscan.io/address/{staker_address}).
Confirm removal of {quantity} unused sub-stakes?"""
#
# Minting
#
NO_MINTABLE_PERIODS = "There are no periods that can be rewarded."
STILL_LOCKED_TOKENS = """
WARNING: Some amount of tokens still locked.
It is *recommended* to run worker node until all tokens will be unlocked
and only after that call `mint`.
"""
CONFIRM_MINTING = "Confirm mint tokens for {mintable_periods} previous periods?"
SUCCESSFUL_MINTING = 'Reward successfully minted'
#
# Wind Down
#
WINDING_DOWN_AGREEMENT = """
Over time, as the locked stake duration decreases
i.e. `winds down`, you will receive decreasing inflationary rewards.
Instead, by disabling `wind down` (default) the locked stake duration
can remain constant until you specify that `wind down` should begin. By
keeping the locked stake duration constant, it ensures that you will
receive maximum inflation compensation.
If `wind down` was previously disabled, you can enable it at any point
and the locked duration will decrease after each period.
For more information see https://docs.nucypher.com/en/latest/architecture/sub_stakes.html#winding-down.
"""
CONFIRM_ENABLE_WINDING_DOWN = "Confirm enable automatic winding down for staker {staking_address}?"
SUCCESSFUL_ENABLE_WIND_DOWN = 'Successfully enabled winding down for {staking_address}'
CONFIRM_DISABLE_WIND_DOWN = "Confirm disable winding down for staker {staking_address}?"
SUCCESSFUL_DISABLE_WIND_DOWN = 'Successfully disabled winding down for {staking_address}'
#
# Restaking
#
RESTAKING_AGREEMENT = """
By enabling the re-staking for {staking_address}, all staking rewards will be automatically added to your existing stake.
"""
CONFIRM_ENABLE_RESTAKING = "Confirm enable automatic re-staking for staker {staking_address}?"
SUCCESSFUL_ENABLE_RESTAKING = 'Successfully enabled re-staking for {staking_address}'
CONFIRM_DISABLE_RESTAKING = "Confirm disable re-staking for staker {staking_address}?"
SUCCESSFUL_DISABLE_RESTAKING = 'Successfully disabled re-staking for {staking_address}'
#
# Snapshots
#
SNAPSHOTS_DISABLING_AGREEMENT = """
By disabling snapshots, staker {staking_address} will be excluded from all future DAO validations
until snapshots are enabled.
"""
CONFIRM_ENABLE_SNAPSHOTS = "Confirm enable automatic snapshots for staker {staking_address}?"
SUCCESSFUL_ENABLE_SNAPSHOTS = 'Successfully enabled snapshots for staker {staking_address}'
CONFIRM_DISABLE_SNAPSHOTS = "Confirm disable snapshots for staker {staking_address}?"
SUCCESSFUL_DISABLE_SNAPSHOTS = 'Successfully disabled snapshots for staker {staking_address}'
#
# Bonding
#
PROMPT_WORKER_ADDRESS = "Enter worker address"
PROMPT_OPERATOR_ADDRESS = "Enter operator address"
CONFIRM_WORKER_AND_STAKER_ADDRESSES_ARE_EQUAL = """
CONFIRM_PROVIDER_AND_OPERATOR_ADDRESSES_ARE_EQUAL = """
{address}
The worker address provided is the same as the staker.
It is *highly recommended* to use a different accounts for staker and worker roles.
The operator address provided is the same as the staking provider.
Continue using the same account for operator and staking provider?"""
Continue using the same account for worker and staker?"""
SUCCESSFUL_OPERATOR_BONDING = "\nOperator {operator_address} successfully bonded to staking provider {staking_provider_address}"
SUCCESSFUL_WORKER_BONDING = "\nWorker {worker_address} successfully bonded to staker {staking_address}"
BONDING_DETAILS = "Bonded at {bonded_date}"
BONDING_DETAILS = "Bonded at period #{current_period} ({bonded_date})"
BONDING_RELEASE_INFO = "This operator can be replaced or detached after {release_date}"
BONDING_RELEASE_INFO = "This worker can be replaced or detached after period #{release_period} ({release_date})"
SUCCESSFUL_UNBOND_OPERATOR = "Successfully unbonded operator {operator_address} from staking provider {staking_provider_address}"
SUCCESSFUL_DETACH_WORKER = "Successfully detached worker {worker_address} from staker {staking_address}"
DETACH_DETAILS = "Unbonded at {bonded_date}"
DETACH_DETAILS = "Detached at period #{current_period} ({bonded_date})"
#
# Worker Rate
#
PROMPT_STAKER_MIN_POLICY_RATE = "Enter new value (in GWEI) so the minimum fee rate falls within global fee range"
CONFIRM_NEW_MIN_POLICY_RATE = "Commit new value {min_rate} GWEI for minimum fee rate?"
SUCCESSFUL_SET_MIN_POLICY_RATE = "\nMinimum fee rate {min_rate} GWEI successfully set by staker {staking_address}"
#
# Divide, Prolong and Merge
#
ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE = "NOTE: Showing divisible stakes only"
ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE = "NOTE: Showing stakes with {final_period} final period only"
CONFIRM_BROADCAST_STAKE_DIVIDE = "Publish stake division to the blockchain?"
PROMPT_STAKE_EXTEND_VALUE = "Enter number of periods to extend"
PROMPT_STAKE_DIVIDE_VALUE = "Enter target value ({minimum} - {maximum})"
SUCCESSFUL_STAKE_DIVIDE = 'Successfully divided stake'
PROMPT_PROLONG_VALUE = "Enter number of periods to extend ({minimum}-{maximum})"
CONFIRM_PROLONG = "Publish stake extension of {lock_periods} period(s) to the blockchain?"
SUCCESSFUL_STAKE_PROLONG = 'Successfully Prolonged Stake'
CONFIRM_MERGE = "Publish merging of {stake_index_1} and {stake_index_2} stakes?"
CONFIRM_MERGE_DISCLAIMER = """
NOTE: Due to a known issue with the StakingEscrow contract, using the merge operation may lead to reduced staking
rewards for the first period after the merge (GitHub Issue: https://github.com/nucypher/nucypher/issues/2691).
Before merging a sub-stake, ensure that there has been a Worker node commitment that occurred in the period after the
sub-stake was created. For example,
- If you created a sub-stake in period 10
- Wait until there has been a Worker node commitment made in the period after the sub-stake was created (i.e. in period 11)
- Merge the sub-stake in period 11 after the commitment, or any time afterwards
Are you sure you want to merge now instead of waiting?
"""
SUCCESSFUL_STAKES_MERGE = 'Successfully Merged Stakes'
CONFIRM_REMOVE_SUBSTAKE = "Publish removal of {stake_index} stake?"
SUCCESSFUL_STAKE_REMOVAL = 'Successfully Removed Stake'
#
# Rewards
@ -327,17 +81,8 @@ SUCCESSFUL_STAKE_REMOVAL = 'Successfully Removed Stake'
COLLECTING_TOKEN_REWARD = 'Collecting {reward_amount} from staking rewards...'
CONFIRM_COLLECTING_WITHOUT_MINTING = """
There will still be a period to reward after withdrawing this portion of NU.
It is *recommended* to call `mint` before.
Continue?
"""
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 can be withdrawn."
NO_FEE_TO_WITHDRAW = "No policy fee can be withdrawn."

View File

@ -21,7 +21,6 @@ from nucypher.cli.commands import (
alice,
bob,
enrico,
stake,
status,
ursula,
cloudworkers,
@ -70,21 +69,23 @@ def nucypher_cli():
ENTRY_POINTS = (
# Characters & Actors
alice.alice, # Author of Policies
bob.bob, # Builder of Capsules
enrico.enrico, # Encryptor of Data
ursula.ursula, # Untrusted Re-Encryption Proxy
stake.stake, # Stake Management
ursula.ursula, # Untrusted Re-Encryption Proxy
enrico.enrico, # Encryptor of Data
# PRE Application
# PRE Application
bond.bond,
bond.unbond,
# Utility Commands
status.status, # Network Status
cloudworkers.cloudworkers, # Remote Operator node management
contacts.contacts, # Character "card" management
porter.porter
status.status, # Network status explorer
cloudworkers.cloudworkers, # Remote node management
porter.porter, # Network support services
# Demos
alice.alice, # Author of Policies
bob.bob, # Builder of Capsules
contacts.contacts, # "character card" management
)
for entry_point in ENTRY_POINTS:

View File

@ -16,11 +16,11 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import functools
from collections import namedtuple
from pathlib import Path
import click
import functools
from nucypher.blockchain.eth.constants import NUCYPHER_CONTRACT_NAMES
from nucypher.cli.types import (
@ -65,7 +65,6 @@ option_poa = click.option('--poa/--disable-poa', help="Inject POA middleware", i
option_registry_filepath = click.option('--registry-filepath', help="Custom contract registry filepath", type=EXISTING_READABLE_FILE)
option_shares = click.option('--shares', '-n', help="N-Total shares", type=click.INT)
option_signer_uri = click.option('--signer', 'signer_uri', '-S', default=None, type=str)
option_staking_address = click.option('--staking-address', help="Address of a NuCypher staker", type=EIP55_CHECKSUM_ADDRESS)
option_staking_provider = click.option('--staking-provider', help="Staking provider ethereum address", type=EIP55_CHECKSUM_ADDRESS, required=True)
option_teacher_uri = click.option('--teacher', 'teacher_uri', help="An Ursula URI to start learning from (seednode)", type=click.STRING)
option_threshold = click.option('--threshold', '-m', help="M-Threshold KFrags", type=click.INT)

View File

@ -15,9 +15,9 @@ 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 webbrowser
import maya
import webbrowser
from web3.main import Web3
from nucypher.blockchain.eth.agents import (

View File

@ -1,302 +0,0 @@
"""
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 maya
import tabulate
from typing import List
from web3.main import Web3
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
from nucypher.blockchain.eth.constants import STAKING_ESCROW_CONTRACT_NAME, NULL_ADDRESS
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.token import NU, Stake
from nucypher.blockchain.eth.utils import datetime_at_period, estimate_block_number_for_period, prettify_eth_amount
from nucypher.control.emitters import StdoutEmitter
from nucypher.cli.literature import (
POST_STAKING_ADVICE,
TOKEN_REWARD_CURRENT,
TOKEN_REWARD_NOT_FOUND,
TOKEN_REWARD_PAST,
TOKEN_REWARD_PAST_HEADER
)
from nucypher.cli.painting.transactions import paint_receipt_summary
STAKE_TABLE_COLUMNS = ('Slot', 'Value', 'Remaining', 'Enactment', 'Termination', 'Boost', 'Status')
STAKER_TABLE_COLUMNS = ('Status', 'Restaking', 'Winding Down', 'Snapshots', 'Unclaimed Fees', 'Min fee rate')
REWARDS_TABLE_COLUMNS = ('Date', 'Block Number', 'Period', 'Value (NU)')
TOKEN_DECIMAL_PLACE = 5
def paint_all_stakes(emitter: StdoutEmitter,
stakeholder: 'StakeHolder',
paint_unlocked: bool = False) -> None:
stakers = stakeholder.get_stakers()
if not stakers:
emitter.echo("No staking accounts found.")
total_stakers = 0
for staker in stakers:
if not staker.stakes:
# This staker has no active stakes.
# TODO: Something with non-staking accounts?
continue
paint_stakes(emitter=emitter, staker=staker, paint_unlocked=paint_unlocked, stakeholder=stakeholder)
total_stakers += 1
if not total_stakers:
emitter.echo("No Stakes found", color='red')
def paint_stakes(emitter: StdoutEmitter,
staker: 'Staker',
stakes: List[Stake] = None,
paint_unlocked: bool = False,
stakeholder=None) -> None:
stakes = stakes or staker.sorted_stakes()
fees = staker.policy_agent.get_fee_amount(staker.checksum_address)
pretty_fees = prettify_eth_amount(fees)
last_committed = staker.staking_agent.get_last_committed_period(staker.checksum_address)
missing = staker.missing_commitments
min_fee_rate = prettify_eth_amount(staker.min_fee_rate)
if missing == -1:
missing_info = "Never Made a Commitment (New Stake)"
else:
missing_info = f'Missing {missing} commitments{"s" if missing > 1 else ""}' if missing else f'Committed #{last_committed}'
staker_data = [missing_info,
"Yes" if staker.is_restaking else "No",
"Yes" if bool(staker.is_winding_down) else "No",
"Yes" if bool(staker.is_taking_snapshots) else "No",
pretty_fees,
min_fee_rate]
line_width = 54
if staker.registry.source: # TODO: #1580 - Registry source might be Falsy in tests.
network_snippet = f"\nNetwork {staker.registry.source.network.capitalize()} "
snippet_with_line = network_snippet + ''*(line_width-len(network_snippet)+1)
emitter.echo(snippet_with_line, bold=True)
emitter.echo(f"Staker {staker.checksum_address} ════", bold=True, color='red' if missing else 'green')
worker = staker.worker_address if staker.worker_address != NULL_ADDRESS else 'not bonded'
emitter.echo(f"Worker {worker} ════", color='red' if staker.worker_address == NULL_ADDRESS else None)
if stakeholder and stakeholder.worker_data:
worker_data = stakeholder.worker_data.get(staker.checksum_address)
if worker_data:
emitter.echo(f"\t public address: {worker_data['publicaddress']}")
if worker_data.get('nucypher version'):
emitter.echo(f"\t NuCypher Version: {worker_data['nucypher version']}")
if worker_data.get('blockchain_provider'):
emitter.echo(f"\t Blockchain Provider: {worker_data['blockchain_provider']}")
emitter.echo(tabulate.tabulate(zip(STAKER_TABLE_COLUMNS, staker_data), floatfmt="fancy_grid"))
rows, inactive_substakes = list(), list()
for index, stake in enumerate(stakes):
is_inactive = False
if stake.status().is_child(Stake.Status.INACTIVE):
inactive_substakes.append(index)
is_inactive = True
if stake.status().is_child(Stake.Status.UNLOCKED) and not paint_unlocked:
# This stake is unlocked.
continue
stake_description = stake.describe()
if is_inactive:
# stake is inactive - update display values since they don't make much sense to display
stake_description['remaining'] = 'N/A'
stake_description['last_period'] = 'N/A'
stake_description['boost'] = 'N/A'
rows.append(list(stake_description.values()))
if not rows:
emitter.echo(f"There are no locked stakes\n")
emitter.echo(tabulate.tabulate(rows, headers=STAKE_TABLE_COLUMNS, tablefmt="fancy_grid")) # newline
if not paint_unlocked and inactive_substakes:
emitter.echo(f"Note that some sub-stakes are inactive: {inactive_substakes}\n"
f"Run `nucypher stake list --all` to show all sub-stakes.\n"
f"Run `nucypher stake remove-inactive --all` to remove inactive sub-stakes; removal of inactive "
f"sub-stakes will reduce commitment gas costs.", color='yellow')
# TODO - it would be nice to provide remove-inactive hint when painting_unlocked - however, this same function
# is used by remove-inactive command is run, and it is redundant to be shown then
def prettify_stake(stake, index: int = None) -> str:
start_datetime = stake.start_datetime.local_datetime().strftime("%b %d %H:%M %Z")
expiration_datetime = stake.unlock_datetime.local_datetime().strftime("%b %d %H:%M %Z")
duration = stake.duration
pretty_periods = f'{duration} periods {"." if len(str(duration)) == 2 else ""}'
pretty = f'| {index if index is not None else "-"} ' \
f'| {stake.staker_address[:6]} ' \
f'| {stake.index} ' \
f'| {str(stake.value)} ' \
f'| {pretty_periods} ' \
f'| {start_datetime} - {expiration_datetime} ' \
return pretty
def paint_staged_stake_division(emitter,
blockchain,
stakeholder,
original_stake,
target_value,
extension):
new_end_period = original_stake.final_locked_period + extension
new_duration = new_end_period - original_stake.first_locked_period + 1
staking_address = original_stake.staker_address
division_message = f"""
Staking address: {staking_address}
~ Original Stake: {prettify_stake(stake=original_stake, index=None)}
"""
paint_staged_stake(emitter=emitter,
blockchain=blockchain,
stakeholder=stakeholder,
staking_address=staking_address,
stake_value=target_value,
lock_periods=new_duration,
start_period=original_stake.first_locked_period,
unlock_period=new_end_period + 1,
division_message=division_message)
def paint_staged_stake(emitter,
blockchain,
stakeholder,
staking_address,
stake_value,
lock_periods,
start_period,
unlock_period,
division_message: str = None):
economics = stakeholder.staker.economics
start_datetime = datetime_at_period(period=start_period,
seconds_per_period=economics.seconds_per_period,
start_of_period=True)
unlock_datetime = datetime_at_period(period=unlock_period,
seconds_per_period=economics.seconds_per_period,
start_of_period=True)
locked_days = (lock_periods * economics.hours_per_period) // 24
start_datetime_pretty = start_datetime.local_datetime().strftime("%b %d %Y %H:%M %Z")
unlock_datetime_pretty = unlock_datetime.local_datetime().strftime("%b %d %Y %H:%M %Z")
if division_message:
emitter.echo(f"\n{'' * 30} ORIGINAL STAKE {'' * 28}", bold=True)
emitter.echo(division_message)
emitter.echo(f"\n{'' * 30} STAGED STAKE {'' * 30}", bold=True)
emitter.echo(f"""
Staking address: {staking_address}
~ Chain -> ID # {blockchain.client.chain_id} | {blockchain.client.chain_name}
~ Value -> {stake_value} ({int(stake_value)} NuNits)
~ Duration -> {locked_days} Days ({lock_periods} Periods)
~ Enactment -> {start_datetime_pretty} (period #{start_period})
~ Expiration -> {unlock_datetime_pretty} (period #{unlock_period})
""")
# TODO: periods != Days - Do we inform the user here?
emitter.echo(''*73, bold=True)
def paint_staking_confirmation(emitter, staker, receipt):
emitter.echo("\nStake initialization transaction was successful.", color='green')
emitter.echo(f'\nTransaction details:')
paint_receipt_summary(emitter=emitter, receipt=receipt, transaction_type="deposit stake")
emitter.echo(f'\n{STAKING_ESCROW_CONTRACT_NAME} address: {staker.staking_agent.contract_address}', color='blue')
emitter.echo(POST_STAKING_ADVICE, color='green')
def paint_staking_accounts(emitter, signer, registry, domain):
from nucypher.blockchain.eth.actors import Staker
rows = list()
blockchain = BlockchainInterfaceFactory.get_interface()
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=registry)
for account in signer.accounts:
eth = str(Web3.fromWei(blockchain.client.get_balance(account), 'ether')) + " ETH"
nu = str(NU.from_units(token_agent.get_balance(account)))
staker = Staker(checksum_address=account,
domain=domain,
registry=registry)
staker.refresh_stakes()
is_staking = 'Yes' if bool(staker.stakes) else 'No'
rows.append((is_staking, account, eth, nu))
headers = ('Staking', 'Account', 'ETH', 'NU')
emitter.echo(tabulate.tabulate(rows, showindex=True, headers=headers, tablefmt="fancy_grid"))
def paint_staking_rewards(stakeholder, blockchain, emitter, past_periods, staking_address, staking_agent):
if not past_periods:
reward_amount = stakeholder.staker.calculate_staking_reward()
emitter.echo(message=TOKEN_REWARD_CURRENT.format(reward_amount=round(reward_amount, TOKEN_DECIMAL_PLACE)))
return
economics = stakeholder.staker.economics
seconds_per_period = economics.seconds_per_period
current_period = staking_agent.get_current_period()
from_period = current_period - past_periods
latest_block = blockchain.client.block_number
from_block = estimate_block_number_for_period(period=from_period,
seconds_per_period=seconds_per_period,
latest_block=latest_block)
argument_filters = {'staker': staking_address}
event_type = staking_agent.contract.events['Minted']
entries = event_type.getLogs(fromBlock=from_block,
toBlock='latest',
argument_filters=argument_filters)
rows = []
rewards_total = NU(0, 'NU')
for event_record in entries:
event_block_number = int(event_record['blockNumber'])
event_period = event_record['args']['period']
event_reward = NU(event_record['args']['value'], 'NuNit')
timestamp = blockchain.client.get_block(event_block_number).timestamp
event_date = maya.MayaDT(epoch=timestamp).local_datetime().strftime("%b %d %Y")
rows.append([
event_date,
event_block_number,
int(event_period),
round(event_reward, TOKEN_DECIMAL_PLACE),
])
rewards_total += event_reward
if not rows:
emitter.echo(TOKEN_REWARD_NOT_FOUND)
return
periods_as_days = economics.days_per_period * past_periods
emitter.echo(message=TOKEN_REWARD_PAST_HEADER.format(periods=past_periods, days=periods_as_days))
emitter.echo(tabulate.tabulate(rows, headers=REWARDS_TABLE_COLUMNS, tablefmt="fancy_grid"))
emitter.echo(message=TOKEN_REWARD_PAST.format(reward_amount=round(rewards_total, TOKEN_DECIMAL_PLACE)))

View File

@ -15,41 +15,22 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from collections import Counter
import maya
from typing import List
from web3.main import Web3
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.actors import Staker
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
ContractAgency,
NucypherTokenAgent,
StakingEscrowAgent
PREApplicationAgent,
)
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
def paint_contract_status(registry, emitter):
blockchain = BlockchainInterfaceFactory.get_interface()
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
adjudicator_agent = ContractAgency.get_agent(AdjudicatorAgent, registry=registry)
application_agent = ContractAgency.get_agent(PREApplicationAgent, registry=registry)
contracts = f"""
| Contract Deployments |
{token_agent.contract_name} ............ {token_agent.contract_address}
{staking_agent.contract_name} ............ {staking_agent.contract_address}
{adjudicator_agent.contract_name} .............. {adjudicator_agent.contract_address}
{application_agent.contract_name} .............. {application_agent.contract_address}
"""
blockchain = f"""
@ -59,17 +40,9 @@ ETH Provider URI ......... {blockchain.eth_provider_uri}
Registry ................. {registry.filepath}
"""
confirmed, pending, inactive = staking_agent.partition_stakers_by_activity()
staking = f"""
| Staking |
Current Period ........... {staking_agent.get_current_period()}
Actively Staked Tokens ... {NU.from_units(staking_agent.get_global_locked_tokens())}
Stakers population ....... {staking_agent.get_staker_population()}
Confirmed ............. {len(confirmed)}
Pending confirmation .. {len(pending)}
Inactive .............. {len(inactive)}
| PREApplication |
Staking Provider Population ....... {application_agent.get_staking_providers_population()}
"""
sep = '-' * 45
@ -80,131 +53,3 @@ Stakers population ....... {staking_agent.get_staker_population()}
emitter.echo(sep)
emitter.echo(staking)
emitter.echo(sep)
def paint_preallocation_status(emitter, preallocation_agent, token_agent) -> None:
blockchain = token_agent.blockchain
staking_address = preallocation_agent.principal_contract.address
token_balance = NU.from_units(token_agent.get_balance(staking_address))
eth_balance = Web3.fromWei(blockchain.client.get_balance(staking_address), 'ether')
initial_locked_amount = NU.from_units(preallocation_agent.initial_locked_amount)
current_locked_amount = NU.from_units(preallocation_agent.unvested_tokens)
available_amount = NU.from_units(preallocation_agent.available_balance)
end_timestamp = preallocation_agent.end_timestamp
width = 64
output = f"""
{" Addresses ".center(width, "-")}
Staking contract: ... {staking_address}
Beneficiary: ........ {preallocation_agent.beneficiary}
{" Locked Tokens ".center(width, "-")}
Initial locked amount: {initial_locked_amount}
Current locked amount: {current_locked_amount}
Locked until: ........ {maya.MayaDT(epoch=end_timestamp)}
{" NU and ETH Balance ".center(width, "-")}
NU balance: .......... {token_balance}
Available: ....... {available_amount}
ETH balance: ......... {eth_balance} ETH
"""
emitter.echo(output)
def paint_locked_tokens_status(emitter, agent, periods) -> None:
MAX_ROWS = 30
period_range = list(range(1, periods + 1))
token_counter = Counter({day: agent.get_all_locked_tokens(day) for day in period_range})
width = 60 # Adjust to desired width
longest_key = max(len(str(key)) for key in token_counter)
graph_width = width - longest_key - 2
widest = token_counter.most_common(1)[0][1]
scale = graph_width / float(widest)
bucket_size = periods // MAX_ROWS if periods > MAX_ROWS else 1
emitter.echo(f"\n| Locked Tokens for next {periods} periods |\n")
buckets = [period_range[i:i + bucket_size] for i in range(0, len(period_range), bucket_size)]
for bucket in buckets:
bucket_start = bucket[0]
bucket_end = bucket[-1]
bucket_max = max([token_counter[period] for period in bucket])
bucket_min = min([token_counter[period] for period in bucket])
delta = bucket_max - bucket_min
bucket_range = f"{bucket_start} - {bucket_end}"
box_plot = f"{int(bucket_min * scale) * ''}{int(delta * scale) * ''}"
emitter.echo(f"{bucket_range:>9}: {box_plot:60}"
f"Min: {NU.from_units(bucket_min)} - Max: {NU.from_units(bucket_max)}")
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_address in stakers:
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=staker_address,
registry=registry)
nickname = Nickname.from_seed(staker_address)
emitter.echo(f"{staker_address} {'Nickname:':10} {nickname} {nickname.icon}")
tab = " " * len(staker_address)
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
is_taking_snapshots = staker.is_taking_snapshots
missing_commitments = current_period - last_committed_period
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)
reward_amount = round(NU.from_units(staking_agent.calculate_staking_reward(staker_address=staker_address)), 2)
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}")
emitter.echo(f"{tab} Unlocked: {reward_amount}")
if is_restaking:
emitter.echo(f"{tab} {'Re-staking:':10} Yes")
else:
emitter.echo(f"{tab} {'Re-staking:':10} No")
emitter.echo(f"{tab} {'Winding down:':10} {'Yes' if is_winding_down else 'No'}")
emitter.echo(f"{tab} {'Snapshots:':10} {'Yes' if is_taking_snapshots else 'No'}")
emitter.echo(f"{tab} {'Activity:':10} ", nl=False)
if missing_commitments == -1:
emitter.echo(f"Next period committed (#{last_committed_period})", color='green')
elif missing_commitments == 0:
emitter.echo(f"Current period committed (#{last_committed_period}). "
f"Pending commitment to next period.", color='yellow')
elif missing_commitments == current_period:
emitter.echo(f"Never made a commitment", color='red')
else:
emitter.echo(f"Missing {missing_commitments} commitments "
f"(last time for period #{last_committed_period})", color='red')
emitter.echo(f"{tab} {'Worker:':10} ", nl=False)
if worker == NULL_ADDRESS:
emitter.echo(f"Worker not bonded", color='red')
else:
emitter.echo(f"{worker}")
fees = prettify_eth_amount(staker.calculate_policy_fee())
emitter.echo(f"{tab} Unclaimed fees: {fees}")
min_rate = prettify_eth_amount(staker.min_fee_rate)
emitter.echo(f"{tab} Min fee rate: {min_rate}")

View File

@ -93,16 +93,6 @@ class UrsulaCommandProtocol(LineReceiver):
from nucypher.cli.painting.nodes import paint_known_nodes
paint_known_nodes(emitter=self.emitter, ursula=self.ursula)
def paintStakes(self):
"""
Display a list of all active stakes.
"""
if self.ursula.stakes:
from nucypher.cli.painting.staking import paint_stakes
paint_stakes(self.emitter, stakes=self.ursula.stakes)
else:
self.emitter.echo("No active stakes.")
def paintStatus(self):
"""
Display the current status of the attached Ursula node.

View File

@ -18,16 +18,12 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Optional
from constant_sorrow.constants import UNINITIALIZED_CONFIGURATION
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
from cryptography.x509 import Certificate
from eth_utils import is_checksum_address
from nucypher.blockchain.eth.actors import StakeHolder
from nucypher.config.base import CharacterConfiguration
from nucypher.config.constants import (
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
@ -258,91 +254,3 @@ class BobConfiguration(CharacterConfiguration):
store_cards=self.store_cards
)
return {**super().static_payload(), **payload}
class StakeHolderConfiguration(CharacterConfiguration):
NAME = 'stakeholder'
CHARACTER_CLASS = StakeHolder
_CONFIG_FIELDS = (
*CharacterConfiguration._CONFIG_FIELDS,
'eth_provider_uri'
)
def __init__(self, checksum_addresses: set = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.checksum_addresses = checksum_addresses
def static_payload(self) -> dict:
"""Values to read/write from stakeholder JSON configuration files"""
if not self.signer_uri:
self.signer_uri = self.eth_provider_uri
payload = dict(eth_provider_uri=self.eth_provider_uri,
poa=self.poa,
light=self.is_light,
domain=self.domain,
signer_uri=self.signer_uri,
worker_data=self.worker_data
)
if self.registry_filepath:
payload.update(dict(registry_filepath=self.registry_filepath))
return payload
@property
def dynamic_payload(self) -> dict:
payload = dict(registry=self.registry, signer=self.signer)
return payload
def _setup_node_storage(self, node_storage=None) -> None:
pass
@classmethod
def assemble(cls, filepath: Optional[Path] = None, **overrides) -> dict:
payload = cls._read_configuration_file(filepath=filepath)
# Filter out None values from **overrides to detect, well, overrides...
# Acts as a shim for optional CLI flags.
overrides = {k: v for k, v in overrides.items() if v is not None}
payload = {**payload, **overrides}
return payload
@classmethod
def generate_runtime_filepaths(cls, config_root: Path) -> dict:
"""Dynamically generate paths based on configuration root directory"""
filepaths = dict(config_root=config_root,
config_file_location=config_root / cls.generate_filename())
return filepaths
def initialize(self, password: Optional[str] = None) -> Path:
"""Initialize a new configuration and write installation files to disk."""
# Development
if self.dev_mode:
self.__temp_dir = TemporaryDirectory(prefix=self.TEMP_CONFIGURATION_DIR_PREFIX)
self.config_root = Path(self.__temp_dir.name)
# Persistent
else:
self._ensure_config_root_exists()
self._cache_runtime_filepaths()
# Validate
if not self.dev_mode:
self.validate()
# Success
message = "Created nucypher installation files at {}".format(self.config_root)
self.log.debug(message)
return self.config_root
@classmethod
def generate(cls, *args, **kwargs):
"""Shortcut: Hook-up a new initial installation configuration."""
node_config = cls(dev_mode=False, *args, **kwargs)
node_config.initialize()
return node_config
def to_configuration_file(self, override: bool = True, *args, **kwargs) -> Path:
return super().to_configuration_file(override=True, *args, **kwargs)

View File

@ -29,7 +29,6 @@ from hendrix.deploy.base import HendrixDeploy
from hendrix.deploy.tls import HendrixDeployTLS
from twisted.internet import reactor, stdio
from nucypher.cli.processes import JSONRPCLineReceiver
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
from nucypher.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter, WebEmitter
from nucypher.control.interfaces import ControlInterface

View File

@ -45,7 +45,7 @@ from nucypher_core.umbral import Signature
from nucypher.acumen.nicknames import Nickname
from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.economics import EconomicsFactory
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, PREApplicationAgent
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.registry import BaseContractRegistry

View File

@ -16,10 +16,10 @@
"""
from typing import TypeVar, NewType, Tuple, NamedTuple, Union
from typing import TypeVar, NewType, NamedTuple, Union
from eth_typing.evm import ChecksumAddress
from web3.types import Wei, Timestamp, TxReceipt
from web3.types import Wei, TxReceipt
ERC20UNits = NewType("ERC20UNits", int)
NuNits = NewType("NuNits", ERC20UNits)
@ -32,61 +32,6 @@ PeriodDelta = NewType('PeriodDelta', int)
ContractReturnValue = TypeVar('ContractReturnValue', bound=Union[TxReceipt, Wei, int, str, bool])
class StakingEscrowParameters(Tuple):
seconds_per_period: int
minting_coefficient: int
lock_duration_coefficient_1: int
lock_duration_coefficient_2: int
maximum_rewarded_periods: int
first_phase_total_supply: NuNits
first_phase_max_issuance: NuNits
min_locked_periods: PeriodDelta
min_allowable_locked_tokens: NuNits
max_allowable_locked_tokens: NuNits
min_worker_periods: PeriodDelta
class SubStakeInfo(NamedTuple):
first_period: Period
last_period: Period
locked_value: NuNits
class RawSubStakeInfo(NamedTuple):
first_period: Period
last_period: Period
unlocking_duration: int
locked_value: NuNits
class Downtime(NamedTuple):
start_period: Period
end_period: Period
class StakerFlags(NamedTuple):
wind_down_flag: bool
restake_flag: bool
measure_work_flag: bool
snapshot_flag: bool
migration_flag: bool
class StakerInfo(NamedTuple):
value: NuNits
current_committed_period: Period
next_committed_period: Period
last_committed_period: Period
lock_restake_until_period: Period
completed_work: NuNits
worker_start_period: Period
worker: ChecksumAddress
flags: bytes
# downtime: Tuple[Downtime, ...]
# substake_info: Tuple[RawSubStakeInfo, ...]
# history: Tuple[NuNits, ...]
class StakingProviderInfo(NamedTuple):
operator: ChecksumAddress
operator_confirmed: bool

View File

@ -14,6 +14,8 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.blockchain.eth.events import ContractEventsThrottler
try:
@ -27,15 +29,12 @@ from eth_typing.evm import ChecksumAddress
import nucypher
from nucypher.blockchain.eth.actors import NucypherTokenActor
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, \
PREApplicationAgent
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent, EthereumContractAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.datastore.queries import get_reencryption_requests
from typing import Dict, Union, Type
ContractAgents = Union[StakingEscrowAgent]
from typing import Dict, Type
class MetricsCollector(ABC):
@ -246,7 +245,7 @@ class EventMetricsCollector(BaseMetricsCollector):
event_name: str,
event_args_config: Dict[str, tuple],
argument_filters: Dict[str, str],
contract_agent_class: Type[ContractAgents],
contract_agent_class: Type[EthereumContractAgent],
contract_registry: BaseContractRegistry):
super().__init__()
self.event_name = event_name

View File

@ -14,7 +14,6 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.exceptions import DevelopmentInstallationRequired
try:
@ -49,8 +48,6 @@ from typing import List
from twisted.internet import reactor, task
from twisted.web.resource import Resource
from nucypher.blockchain.eth.agents import StakingEscrowAgent
class PrometheusMetricsConfig:
"""Prometheus configuration."""

View File

@ -18,20 +18,21 @@
# Get an interactive Python session with all the NuCypher agents loaded by running:
# python -i scripts/hooks/nucypher_agents.py <NETWORK> <ETH_PROVIDER_URI>
import sys
import os
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, PolicyManagerAgent, NucypherTokenAgent
from nucypher.config.constants import NUCYPHER_ENVVAR_ETH_PROVIDER_URI
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.control.emitters import StdoutEmitter
from nucypher.utilities.logging import GlobalLoggerSettings
import sys
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION
from eth_utils import to_checksum_address
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
from nucypher.blockchain.eth.agents import (
PREApplicationAgent,
SubscriptionManagerAgent
)
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.config.constants import NUCYPHER_ENVVAR_ETH_PROVIDER_URI
from nucypher.control.emitters import StdoutEmitter
from nucypher.utilities.logging import GlobalLoggerSettings
NO_BLOCKCHAIN_CONNECTION.bool_value(False) # FIXME
@ -52,25 +53,18 @@ try:
except IndexError:
network = "ibex"
BlockchainInterfaceFactory.initialize_interface(eth_provider_uri=eth_provider_uri,
light=False,
emitter=emitter)
BlockchainInterfaceFactory.initialize_interface(eth_provider_uri=eth_provider_uri, light=False, emitter=emitter)
blockchain = BlockchainInterfaceFactory.get_interface(eth_provider_uri=eth_provider_uri)
emitter.echo(message="Reading Latest Chaindata...")
blockchain.connect()
registry = InMemoryContractRegistry.from_latest_publication(network=network)
emitter.echo(f"NOTICE: Connecting to {network} network", color='yellow')
staking_agent = ContractAgency.get_agent(agent_class=StakingEscrowAgent, registry=registry) # type: StakingEscrowAgent
policy_agent = ContractAgency.get_agent(agent_class=PolicyManagerAgent, registry=registry) # type: PolicyManagerAgent
token_agent = ContractAgency.get_agent(agent_class=NucypherTokenAgent, registry=registry) # type: NucypherTokenAgent
application_agent = ContractAgency.get_agent(agent_class=PREApplicationAgent, registry=registry) # type: PREApplicationAgent
subscription_agent = ContractAgency.get_agent(agent_class=SubscriptionManagerAgent, registry=registry) # type: SubscriptionManagerAgent
emitter.echo(message=f"Current period: {staking_agent.get_current_period()}", color='yellow')
emitter.echo(message=f"NuCypher agents pre-loaded in variables 'staking_agent', 'policy_agent', and 'token_agent'",
color='green')
message = f"NuCypher agents pre-loaded in variables 'token_agent', 'subscription_agent' and 'application_agent'"
emitter.echo(message=message, color='green')

View File

@ -14,29 +14,3 @@
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 nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.agents import NucypherTokenAgent, ContractAgency
from nucypher.blockchain.eth.actors import Staker
from tests.utils.blockchain import token_airdrop
from tests.constants import DEVELOPMENT_TOKEN_AIRDROP_AMOUNT
@pytest.fixture(scope='module')
def staker(testerchain, agency, test_registry, deployer_transacting_power):
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
origin, staker_account, *everybody_else = testerchain.client.accounts
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
token_airdrop(token_agent=token_agent,
transacting_power=deployer_transacting_power,
addresses=[staker_account],
amount=DEVELOPMENT_TOKEN_AIRDROP_AMOUNT)
staker = Staker(domain=TEMPORARY_DOMAIN,
transacting_power=staker_power,
registry=test_registry)
return staker

View File

@ -20,8 +20,8 @@ import pytest
from nucypher_core.umbral import SecretKeyFactory, Signer
from nucypher.blockchain.eth.actors import Investigator, Staker
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent, StakingEscrowAgent
from nucypher.blockchain.eth.actors import Investigator
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import NU

View File

@ -68,8 +68,8 @@ def test_work_tracker(mocker,
# Make the Worker
ursula = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
stakers_addresses=[staker.checksum_address],
workers_addresses=[worker_address],
staking_provider_addresses=[staker.checksum_address],
operator_addresses=[worker_address],
registry=test_registry).pop()
ursula.run(preflight=False,

View File

@ -1,387 +0,0 @@
"""
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 maya
import pytest
from eth_tester.exceptions import TransactionFailed
from nucypher.blockchain.eth.agents import NucypherTokenAgent, StakingEscrowAgent, ContractAgency
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import NU, Stake
from nucypher.blockchain.eth.utils import datetime_at_period
from nucypher.crypto.powers import TransactingPower
from tests.constants import FEE_RATE_RANGE, DEVELOPMENT_TOKEN_AIRDROP_AMOUNT
from tests.utils.blockchain import token_airdrop
from tests.utils.ursula import make_decentralized_ursulas
@pytest.mark.skip()
def test_staker_locking_tokens(testerchain, agency, staker, application_economics, test_registry):
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert NU(application_economics.min_authorization, 'NuNit') < staker.token_balance, "Insufficient staker balance"
# Make sure staking handles existing token allowance
staker.token_agent.approve_transfer(1000000000, staking_agent.contract_address, staker.transacting_power)
staker.initialize_stake(amount=NU(application_economics.min_authorization, 'NuNit'),
# Lock the minimum amount of tokens
lock_periods=application_economics.min_operator_seconds)
# Verify that the escrow is "approved" to receive tokens
allowance = token_agent.contract.functions.allowance(
staker.checksum_address,
staking_agent.contract_address).call()
assert 0 == allowance
# Staking starts after one period
locked_tokens = staker.locked_tokens()
assert 0 == locked_tokens
locked_tokens = staker.locked_tokens(periods=1)
assert application_economics.min_authorization == locked_tokens
@pytest.mark.skip()
@pytest.mark.usefixtures("agency")
def test_staker_divides_stake(staker, application_economics):
stake_value = NU(application_economics.min_authorization * 5, 'NuNit')
new_stake_value = NU(application_economics.min_authorization * 2, 'NuNit')
stake_index = 0
duration = int(application_economics.min_operator_seconds)
staker.initialize_stake(amount=stake_value, lock_periods=duration)
stake = staker.stakes[stake_index + 1]
# Can't use additional periods and expiration together
with pytest.raises(ValueError):
staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2, expiration=maya.now())
staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2)
current_period = staker.staking_agent.get_current_period()
expected_old_stake = (current_period + 1, current_period + duration, stake_value - new_stake_value)
expected_new_stake = (current_period + 1, current_period + duration + 2, new_stake_value)
assert 3 == len(staker.stakes), 'A new stake was not added to this stakers stakes'
assert expected_old_stake == staker.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid'
assert expected_new_stake == staker.stakes[stake_index + 2].to_stake_info(), 'New stake values are invalid'
# Provided stake must be part of current stakes
new_stake_value = NU.from_units(application_economics.min_authorization)
with pytest.raises(ValueError):
staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2)
stake = staker.stakes[stake_index + 1]
stake.index = len(staker.stakes)
with pytest.raises(ValueError):
staker.divide_stake(target_value=new_stake_value, stake=stake, additional_periods=2)
yet_another_stake_value = NU(application_economics.min_authorization, 'NuNit')
stake = staker.stakes[stake_index + 2]
# New expiration date must extend stake duration
origin_stake = stake
new_expiration = datetime_at_period(period=origin_stake.final_locked_period,
seconds_per_period=application_economics.seconds_per_period,
start_of_period=True)
with pytest.raises(ValueError):
staker.divide_stake(target_value=yet_another_stake_value, stake=stake, expiration=new_expiration)
new_expiration = datetime_at_period(period=origin_stake.final_locked_period + 2,
seconds_per_period=application_economics.seconds_per_period,
start_of_period=True)
staker.divide_stake(target_value=yet_another_stake_value, stake=stake, expiration=new_expiration)
expected_new_stake = (current_period + 1, current_period + duration + 2, new_stake_value)
expected_yet_another_stake = Stake(first_locked_period=current_period + 1,
final_locked_period=current_period + duration + 4,
value=yet_another_stake_value,
checksum_address=staker.checksum_address,
index=3,
staking_agent=staker.application_agent,
economics=application_economics)
assert 4 == len(staker.stakes), 'A new stake was not added after two stake divisions'
assert expected_old_stake == staker.stakes[
stake_index + 1].to_stake_info(), 'Old stake values are invalid after two stake divisions'
assert expected_new_stake == staker.stakes[
stake_index + 2].to_stake_info(), 'New stake values are invalid after two stake divisions'
assert expected_yet_another_stake.value == staker.stakes[stake_index + 3].value, 'Third stake values are invalid'
@pytest.mark.skip()
@pytest.mark.usefixtures("agency")
def test_staker_prolongs_stake(staker, application_economics):
stake_index = 0
origin_stake = staker.stakes[stake_index]
# Can't use additional periods and expiration together
new_expiration = datetime_at_period(period=origin_stake.final_locked_period + 3,
seconds_per_period=application_economics.seconds_per_period,
start_of_period=True)
with pytest.raises(ValueError):
staker.prolong_stake(stake=origin_stake, additional_periods=3, expiration=new_expiration)
staker.prolong_stake(stake=origin_stake, additional_periods=3)
stake = staker.stakes[stake_index]
assert stake.first_locked_period == origin_stake.first_locked_period
assert stake.final_locked_period == origin_stake.final_locked_period + 3
assert stake.value == origin_stake.value
# Provided stake must be part of current stakes
with pytest.raises(ValueError):
staker.prolong_stake(stake=origin_stake, additional_periods=2)
stake.index = len(staker.stakes)
with pytest.raises(ValueError):
staker.prolong_stake(stake=stake, additional_periods=2)
stake.index = stake_index
# New expiration date must extend stake duration
origin_stake = stake
new_expiration = datetime_at_period(period=origin_stake.final_locked_period,
seconds_per_period=application_economics.seconds_per_period,
start_of_period=True)
with pytest.raises(ValueError):
staker.prolong_stake(stake=origin_stake, expiration=new_expiration)
new_expiration = origin_stake.unlock_datetime
staker.prolong_stake(stake=origin_stake, expiration=new_expiration)
stake = staker.stakes[stake_index]
assert stake.first_locked_period == origin_stake.first_locked_period
assert stake.final_locked_period == origin_stake.final_locked_period + 1
assert stake.value == origin_stake.value
@pytest.mark.skip()
@pytest.mark.usefixtures("agency")
def test_staker_increases_stake(staker, application_economics):
stake_index = 0
origin_stake = staker.stakes[stake_index]
additional_amount = NU.from_units(application_economics.min_authorization // 100)
with pytest.raises(ValueError):
staker.increase_stake(stake=origin_stake)
# Can't use amount and entire balance flag together
with pytest.raises(ValueError):
staker.increase_stake(stake=origin_stake, amount=additional_amount, entire_balance=True)
staker.increase_stake(stake=origin_stake, amount=additional_amount)
stake = staker.stakes[stake_index]
assert stake.first_locked_period == origin_stake.first_locked_period
assert stake.final_locked_period == origin_stake.final_locked_period
assert stake.value == origin_stake.value + additional_amount
# Provided stake must be part of current stakes
with pytest.raises(ValueError):
staker.increase_stake(stake=origin_stake, amount=additional_amount)
stake.index = len(staker.stakes)
with pytest.raises(ValueError):
staker.increase_stake(stake=stake, amount=additional_amount)
stake.index = stake_index
# Try to increase again using entire balance
origin_stake = stake
balance = staker.token_balance
staker.increase_stake(stake=stake, entire_balance=True)
stake = staker.stakes[stake_index]
assert stake.first_locked_period == origin_stake.first_locked_period
assert stake.final_locked_period == origin_stake.final_locked_period
assert stake.value == origin_stake.value + balance
@pytest.mark.skip()
def test_staker_merges_stakes(agency, staker):
stake_index_1 = 0
stake_index_2 = 3
origin_stake_1 = staker.stakes[stake_index_1]
origin_stake_2 = staker.stakes[stake_index_2]
assert origin_stake_2.final_locked_period == origin_stake_1.final_locked_period
staker.merge_stakes(stake_1=origin_stake_1, stake_2=origin_stake_2)
stake = staker.stakes[stake_index_1]
assert stake.final_locked_period == origin_stake_1.final_locked_period
assert stake.value == origin_stake_1.value + origin_stake_2.value
# Provided stakes must be part of current stakes
with pytest.raises(ValueError):
staker.merge_stakes(stake_1=origin_stake_1, stake_2=stake)
with pytest.raises(ValueError):
staker.merge_stakes(stake_1=stake, stake_2=origin_stake_2)
stake.index = len(staker.stakes)
with pytest.raises(ValueError):
staker.merge_stakes(stake_1=stake, stake_2=staker.stakes[1])
with pytest.raises(ValueError):
staker.merge_stakes(stake_1=staker.stakes[1], stake_2=stake)
@pytest.mark.skip()
def test_remove_inactive_stake(agency, staker):
stake_index = 3
staker.refresh_stakes()
original_stakes = list(staker.stakes)
unused_stake = original_stakes[stake_index]
assert unused_stake.final_locked_period == 1
staker.remove_inactive_stake(stake=unused_stake)
stakes = staker.stakes
assert stakes == original_stakes[:-1]
@pytest.mark.skip()
def test_staker_manages_restaking(testerchain, test_registry, staker):
# Enable Restaking
receipt = staker.enable_restaking()
assert receipt['status'] == 1
receipt = staker.disable_restaking()
assert receipt['status'] == 1
@pytest.mark.skip()
def test_staker_collects_staking_reward(testerchain,
test_registry,
staker,
blockchain_ursulas,
agency,
application_economics,
ursula_decentralized_test_config):
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
tpower = TransactingPower(account=testerchain.etherbase_account,
signer=Web3Signer(testerchain.client))
# Give more tokens to staker
token_airdrop(token_agent=token_agent,
transacting_power=tpower,
addresses=[staker.checksum_address],
amount=DEVELOPMENT_TOKEN_AIRDROP_AMOUNT)
staker.initialize_stake(amount=NU(application_economics.min_authorization, 'NuNit'), # Lock the minimum amount of tokens
lock_periods=int(application_economics.min_operator_seconds)) # ... for the fewest number of periods
# Get an unused address for a new worker
worker_address = testerchain.unassigned_accounts[-1]
staker.bond_worker(worker_address=worker_address)
# Create this worker and bond it with the staker
ursula = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
stakers_addresses=[staker.checksum_address],
workers_addresses=[worker_address],
registry=test_registry,
commit_now=False).pop()
# ...mint few tokens...
for _ in range(2):
ursula.commit_to_next_period()
testerchain.time_travel(periods=1)
# Check mintable periods
assert staker.mintable_periods() == 1
ursula.commit_to_next_period()
# ...wait more...
assert staker.mintable_periods() == 0
testerchain.time_travel(periods=2)
assert staker.mintable_periods() == 2
# Capture the current token balance of the staker
initial_balance = staker.token_balance
assert token_agent.get_balance(staker.checksum_address) == initial_balance
# Profit!
staked = staker.non_withdrawable_stake()
owned = staker.owned_tokens()
staker.collect_staking_reward()
assert staker.owned_tokens() == staked
final_balance = staker.token_balance
assert final_balance == initial_balance + owned - staked
@pytest.mark.skip()
def test_staker_manages_winding_down(testerchain,
test_registry,
staker,
application_economics,
ursula_decentralized_test_config):
# Get worker
ursula = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
stakers_addresses=[staker.checksum_address],
workers_addresses=[staker.operator_address],
registry=test_registry).pop()
# Enable winding down
testerchain.time_travel(periods=1)
base_duration = application_economics.min_operator_seconds + 4
receipt = staker.enable_winding_down()
assert receipt['status'] == 1
assert staker.locked_tokens(base_duration) != 0
assert staker.locked_tokens(base_duration + 1) == 0
ursula.commit_to_next_period()
assert staker.locked_tokens(base_duration) != 0
assert staker.locked_tokens(base_duration + 1) == 0
# Disable winding down
testerchain.time_travel(periods=1)
receipt = staker.disable_winding_down()
assert receipt['status'] == 1
assert staker.locked_tokens(base_duration - 1) != 0
assert staker.locked_tokens(base_duration) == 0
ursula.commit_to_next_period()
assert staker.locked_tokens(base_duration - 1) != 0
assert staker.locked_tokens(base_duration) == 0
@pytest.mark.skip()
def test_staker_manages_snapshots(testerchain,
test_registry,
staker,
application_economics,
ursula_decentralized_test_config):
# Disable taking snapshots
testerchain.time_travel(periods=1)
assert staker.is_taking_snapshots
receipt = staker.disable_snapshots()
assert receipt['status'] == 1
assert not staker.is_taking_snapshots
# Enable taking snapshots
receipt = staker.enable_snapshots()
assert receipt['status'] == 1
assert staker.is_taking_snapshots
@pytest.mark.skip()
def test_set_min_fee_rate(testerchain, test_registry, staker):
# Check before set
_minimum, default, maximum = FEE_RATE_RANGE
assert staker.min_fee_rate == default
# New value must be within range
with pytest.raises((TransactionFailed, ValueError)):
staker.set_min_fee_rate(maximum + 1)
receipt = staker.set_min_fee_rate(maximum - 1)
assert receipt['status'] == 1
assert staker.min_fee_rate == maximum - 1

View File

@ -20,10 +20,9 @@ import pytest
from nucypher_core.umbral import SecretKeyFactory, Signer
from nucypher.blockchain.eth.actors import NucypherTokenActor, Staker
from nucypher.blockchain.eth.actors import NucypherTokenActor
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
StakingEscrowAgent,
ContractAgency,
NucypherTokenAgent
)

View File

@ -1,467 +0,0 @@
"""
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 os
import pytest
from eth_utils.address import is_address, to_checksum_address
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, NucypherTokenAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.types import StakerInfo
def test_unknown_contract(testerchain, test_registry):
with pytest.raises(BaseContractRegistry.UnknownContract) as exception:
_staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert exception.value.args[0] == StakingEscrowAgent.contract_name
@pytest.mark.skip()
def test_deposit_tokens(testerchain, agency, application_economics, test_registry):
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
locked_tokens = application_economics.min_authorization * 5
staker_account = testerchain.unassigned_accounts[0]
balance = token_agent.get_balance(address=staker_account)
assert balance == 0
# The staker receives an initial amount of tokens
tpower = TransactingPower(account=testerchain.etherbase_account,
signer=Web3Signer(testerchain.client))
_txhash = token_agent.transfer(amount=application_economics.min_authorization * 10,
target_address=staker_account,
transacting_power=tpower)
#
# Deposit: The staker deposits tokens in the StakingEscrow contract.
# Previously, she needs to approve this transfer on the token contract.
#
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
_receipt = token_agent.approve_transfer(amount=application_economics.min_authorization * 10, # Approve
spender_address=staking_agent.contract_address,
transacting_power=staker_power)
receipt = staking_agent.deposit_tokens(amount=locked_tokens,
lock_periods=application_economics.min_operator_seconds,
transacting_power=staker_power,
staker_address=staker_account)
# Check the receipt for the contract address success code
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][2]['address'] == staking_agent.contract_address
testerchain.time_travel(periods=1)
balance = token_agent.get_balance(address=staker_account)
assert balance == locked_tokens
assert staking_agent.get_locked_tokens(staker_address=staker_account) == locked_tokens
@pytest.mark.skip()
def test_locked_tokens(testerchain, agency, application_economics, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account = testerchain.unassigned_accounts[0]
locked_amount = staking_agent.get_locked_tokens(staker_address=staker_account)
assert application_economics.maximum_allowed_locked >= locked_amount >= application_economics.min_authorization
@pytest.mark.skip()
def test_get_all_stakes(testerchain, agency, application_economics, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account = testerchain.unassigned_accounts[0]
all_stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
assert len(all_stakes) == 1
stake_info = all_stakes[0]
assert len(stake_info) == 3
start_period, end_period, value = stake_info
assert end_period > start_period
assert application_economics.maximum_allowed_locked > value > application_economics.min_authorization
@pytest.mark.skip()
def test_stakers_and_workers_relationships(testerchain, agency, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
# The staker hasn't bond a worker yet
assert NULL_ADDRESS == staking_agent.get_worker_from_staker(staker_address=staker_account)
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
_txhash = staking_agent.bond_worker(transacting_power=tpower, operator_address=worker_account)
# We can check the staker-worker relation from both sides
assert worker_account == staking_agent.get_worker_from_staker(staker_address=staker_account)
assert staker_account == staking_agent.get_staker_from_worker(operator_address=worker_account)
# No staker-worker relationship
random_address = to_checksum_address(os.urandom(20))
assert NULL_ADDRESS == staking_agent.get_worker_from_staker(staker_address=random_address)
assert NULL_ADDRESS == staking_agent.get_staker_from_worker(operator_address=random_address)
@pytest.mark.skip()
def test_get_staker_population(agency, staking_providers, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
# Apart from all the stakers in the fixture, we also added a new staker above
assert staking_agent.get_staker_population() == len(staking_providers) + 1
@pytest.mark.skip()
def test_get_swarm(agency, blockchain_ursulas, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
swarm = staking_agent.swarm()
swarm_addresses = list(swarm)
assert len(swarm_addresses) == len(blockchain_ursulas) + 1
# Grab a staker address from the swarm
staker_addr = swarm_addresses[0]
assert isinstance(staker_addr, str)
assert is_address(staker_addr)
@pytest.mark.skip()
@pytest.mark.usefixtures("blockchain_ursulas")
def test_sample_stakers(agency, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
stakers_population = staking_agent.get_staker_population()
with pytest.raises(StakingEscrowAgent.NotEnoughStakers):
staking_agent.get_stakers_reservoir(periods=1).draw(stakers_population + 1) # One more than we have deployed
stakers = staking_agent.get_stakers_reservoir(periods=5).draw(3)
assert len(stakers) == 3 # Three...
assert len(set(stakers)) == 3 # ...unique addresses
# Same but with pagination
stakers = staking_agent.get_stakers_reservoir(periods=5, pagination_size=1).draw(3)
assert len(stakers) == 3
assert len(set(stakers)) == 3
light = staking_agent.blockchain.is_light
staking_agent.blockchain.is_light = not light
stakers = staking_agent.get_stakers_reservoir(periods=5).draw(3)
assert len(stakers) == 3
assert len(set(stakers)) == 3
staking_agent.blockchain.is_light = light
@pytest.mark.skip()
def test_get_current_period(agency, testerchain, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
start_period = staking_agent.get_current_period()
testerchain.time_travel(periods=1)
end_period = staking_agent.get_current_period()
assert end_period > start_period
@pytest.mark.skip()
def test_commit_to_next_period(agency, testerchain, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
tpower = TransactingPower(account=worker_account, signer=Web3Signer(testerchain.client))
txhash = staking_agent.commit_to_next_period(transacting_power=tpower)
receipt = testerchain.wait_for_receipt(txhash)
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][0]['address'] == staking_agent.contract_address
@pytest.mark.skip()
def test_get_staker_info(agency, testerchain, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
info: StakerInfo = staking_agent.get_staker_info(staker_address=staker_account)
assert info.value > 0
assert info.worker == worker_account
@pytest.mark.skip()
def test_divide_stake(agency, testerchain, application_economics, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
agent = staking_agent
staker_account = testerchain.unassigned_accounts[0]
locked_tokens = application_economics.min_authorization * 2
# Deposit
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
_txhash = agent.deposit_tokens(amount=locked_tokens,
lock_periods=application_economics.min_operator_seconds,
transacting_power=tpower,
staker_address=staker_account)
stakes = list(agent.get_all_stakes(staker_address=staker_account))
stakes_length = len(stakes)
origin_stake = stakes[-1]
receipt = agent.divide_stake(transacting_power=tpower,
stake_index=1,
target_value=application_economics.min_authorization,
periods=1)
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][0]['address'] == agent.contract_address
stakes = list(agent.get_all_stakes(staker_address=staker_account))
assert len(stakes) == stakes_length + 1
assert stakes[-2].locked_value == origin_stake.locked_value - application_economics.min_authorization
assert stakes[-2].last_period == origin_stake.last_period
assert stakes[-1].locked_value == application_economics.min_authorization
assert stakes[-1].last_period == origin_stake.last_period + 1
@pytest.mark.skip()
def test_prolong_stake(agency, testerchain, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
original_termination = stakes[0].last_period
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
receipt = staking_agent.prolong_stake(transacting_power=tpower, stake_index=0, periods=1)
assert receipt['status'] == 1
# Ensure stake was extended by one period.
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
new_termination = stakes[0].last_period
assert new_termination == original_termination + 1
@pytest.mark.skip()
def test_deposit_and_increase(agency, testerchain, test_registry, application_economics):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
original_stake = stakes[0]
locked_tokens = staking_agent.get_locked_tokens(staker_account, 1)
amount = application_economics.min_authorization // 2
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
receipt = staking_agent.deposit_and_increase(transacting_power=tpower,
stake_index=0,
amount=amount)
assert receipt['status'] == 1
# Ensure stake was extended by one period.
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
new_stake = stakes[0]
assert new_stake.locked_value == original_stake.locked_value + amount
assert staking_agent.get_locked_tokens(staker_account, 1) == locked_tokens + amount
@pytest.mark.skip()
def test_disable_restaking(agency, testerchain, test_registry):
staker_account, worker_account, *other = testerchain.unassigned_accounts
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
assert staking_agent.is_restaking(staker_account)
receipt = staking_agent.set_restaking(transacting_power=tpower, value=False)
assert receipt['status'] == 1, "Transaction Rejected"
assert not staking_agent.is_restaking(staker_account)
@pytest.mark.skip()
def test_collect_staking_reward(agency, testerchain, test_registry):
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
# Commit to next period
testerchain.time_travel(periods=1)
tpower = TransactingPower(account=worker_account, signer=Web3Signer(testerchain.client))
staking_agent.commit_to_next_period(transacting_power=tpower)
testerchain.time_travel(periods=2)
# Mint
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
_receipt = staking_agent.mint(transacting_power=staker_power)
old_balance = token_agent.get_balance(address=staker_account)
owned_tokens = staking_agent.owned_tokens(staker_address=staker_account)
staked = staking_agent.non_withdrawable_stake(staker_address=staker_account)
receipt = staking_agent.collect_staking_reward(transacting_power=staker_power)
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][-1]['address'] == staking_agent.contract_address
new_balance = token_agent.get_balance(address=staker_account) # not the shoes
assert new_balance == old_balance + owned_tokens - staked
assert staking_agent.owned_tokens(staker_address=staker_account) == staked
@pytest.mark.skip()
def test_winding_down(agency, testerchain, test_registry, application_economics):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
duration = application_economics.min_operator_seconds + 1
worker_power = TransactingPower(account=worker_account, signer=Web3Signer(testerchain.client))
def check_last_period():
assert staking_agent.get_locked_tokens(staker_account, duration) != 0, "Sub-stake is already unlocked"
assert staking_agent.get_locked_tokens(staker_account, duration + 1) == 0, "Sub-stake is still locked"
assert not staking_agent.is_winding_down(staker_account)
check_last_period()
staking_agent.commit_to_next_period(transacting_power=worker_power)
check_last_period()
# Examine the last periods of sub-stakes
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
testerchain.time_travel(periods=1)
check_last_period()
receipt = staking_agent.set_winding_down(transacting_power=staker_power, value=True)
assert receipt['status'] == 1
assert staking_agent.is_winding_down(staker_account)
check_last_period()
staking_agent.commit_to_next_period(transacting_power=worker_power)
check_last_period()
testerchain.time_travel(periods=1)
duration -= 1
check_last_period()
receipt = staking_agent.set_winding_down(transacting_power=staker_power, value=False)
assert receipt['status'] == 1
assert not staking_agent.is_winding_down(staker_account)
check_last_period()
staking_agent.commit_to_next_period(transacting_power=worker_power)
check_last_period()
@pytest.mark.skip()
def test_lock_and_create(agency, testerchain, test_registry, application_economics):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
stakes_length = len(stakes)
current_locked_tokens = staking_agent.get_locked_tokens(staker_account, 0)
next_locked_tokens = staking_agent.get_locked_tokens(staker_account, 1)
amount = application_economics.min_authorization
receipt = staking_agent.lock_and_create(transacting_power=staker_power,
lock_periods=application_economics.min_operator_seconds,
amount=amount)
assert receipt['status'] == 1
# Ensure stake was extended by one period.
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
assert len(stakes) == stakes_length + 1
new_stake = stakes[-1]
current_period = staking_agent.get_current_period()
assert new_stake.last_period == current_period + application_economics.min_operator_seconds
assert new_stake.first_period == current_period + 1
assert new_stake.locked_value == amount
assert staking_agent.get_locked_tokens(staker_account, 1) == next_locked_tokens + amount
assert staking_agent.get_locked_tokens(staker_account, 0) == current_locked_tokens
@pytest.mark.skip()
def test_lock_and_increase(agency, testerchain, test_registry, application_economics):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account, worker_account, *other = testerchain.unassigned_accounts
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
original_stake = stakes[0]
current_locked_tokens = staking_agent.get_locked_tokens(staker_account, 0)
next_locked_tokens = staking_agent.get_locked_tokens(staker_account, 1)
amount = staking_agent.calculate_staking_reward(staker_address=staker_account)
receipt = staking_agent.lock_and_increase(transacting_power=staker_power,
stake_index=0,
amount=amount)
assert receipt['status'] == 1
# Ensure stake was extended by one period.
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
new_stake = stakes[0]
assert new_stake.locked_value == original_stake.locked_value + amount
assert staking_agent.get_locked_tokens(staker_account, 1) == next_locked_tokens + amount
assert staking_agent.get_locked_tokens(staker_account, 0) == current_locked_tokens
@pytest.mark.skip()
def test_merge(agency, testerchain, test_registry, application_economics):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account = testerchain.unassigned_accounts[0]
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
original_stake_1 = stakes[0]
original_stake_2 = stakes[2]
assert original_stake_1.last_period == original_stake_2.last_period
current_locked_tokens = staking_agent.get_locked_tokens(staker_account, 0)
next_locked_tokens = staking_agent.get_locked_tokens(staker_account, 1)
receipt = staking_agent.merge_stakes(transacting_power=staker_power,
stake_index_1=0,
stake_index_2=2)
assert receipt['status'] == 1
# Ensure stake was extended by one period.
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
new_stake = stakes[0]
assert new_stake.locked_value == original_stake_1.locked_value + original_stake_2.locked_value
assert staking_agent.get_locked_tokens(staker_account, 1) == next_locked_tokens
assert staking_agent.get_locked_tokens(staker_account, 0) == current_locked_tokens
@pytest.mark.skip()
def test_remove_inactive_stake(agency, testerchain, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
staker_account = testerchain.unassigned_accounts[0]
staker_power = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
testerchain.time_travel(periods=1)
staking_agent.mint(transacting_power=staker_power)
current_period = staking_agent.get_current_period()
original_stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
assert original_stakes[2].last_period == current_period - 1
current_locked_tokens = staking_agent.get_locked_tokens(staker_account, 0)
next_locked_tokens = staking_agent.get_locked_tokens(staker_account, 1)
receipt = staking_agent.remove_inactive_stake(transacting_power=staker_power, stake_index=2)
assert receipt['status'] == 1
# Ensure stake was extended by one period.
stakes = list(staking_agent.get_all_stakes(staker_address=staker_account))
assert len(stakes) == len(original_stakes) - 1
assert stakes[0] == original_stakes[0]
assert stakes[1] == original_stakes[1]
assert stakes[2] == original_stakes[4]
assert stakes[3] == original_stakes[3]
assert staking_agent.get_locked_tokens(staker_account, 1) == next_locked_tokens
assert staking_agent.get_locked_tokens(staker_account, 0) == current_locked_tokens

View File

@ -18,13 +18,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import pytest
from nucypher.blockchain.eth.deployers import NucypherTokenDeployer
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.eth.deployers import (
NucypherTokenDeployer,
StakingEscrowDeployer,
)
from constant_sorrow.constants import (FULL, INIT)
@pytest.fixture(scope="module")
@ -40,25 +36,6 @@ def transacting_power(testerchain, test_registry):
return tpower
@pytest.fixture(scope="module")
def staking_escrow_stub_deployer(testerchain, token_deployer, test_registry, transacting_power):
token_deployer.deploy(transacting_power=transacting_power)
staking_escrow_deployer = StakingEscrowDeployer(registry=test_registry)
return staking_escrow_deployer
@pytest.fixture(scope="module")
def staking_escrow_deployer(testerchain,
staking_escrow_stub_deployer,
threshold_staking,
test_registry,
transacting_power):
staking_escrow_stub_deployer.deploy(deployment_mode=INIT, transacting_power=transacting_power)
staking_escrow_deployer = StakingEscrowDeployer(staking_interface=threshold_staking.address,
registry=test_registry)
return staking_escrow_deployer
@pytest.fixture(scope="function")
def deployment_progress():
class DeploymentProgress:

View File

@ -20,7 +20,6 @@ from nucypher.blockchain.eth.agents import AdjudicatorAgent
from nucypher.blockchain.eth.deployers import (
AdjudicatorDeployer,
NucypherTokenDeployer,
StakingEscrowDeployer,
)
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower

View File

@ -19,20 +19,16 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import pytest
from constant_sorrow import constants
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
ContractAgency,
NucypherTokenAgent,
StakingEscrowAgent
)
from nucypher.blockchain.eth.deployers import (
AdjudicatorDeployer,
BaseContractDeployer,
NucypherTokenDeployer,
StakingEscrowDeployer
)
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
@pytest.mark.skip()

View File

@ -14,51 +14,55 @@ 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 constant_sorrow import constants
from constant_sorrow.constants import BARE
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
from nucypher.blockchain.eth.deployers import (DispatcherDeployer, StakingEscrowDeployer)
@pytest.fixture()
def staking_escrow_deployer(testerchain, threshold_staking, application_economics, test_registry,
deployment_progress, transacting_power):
deployer = StakingEscrowDeployer(
staking_interface=threshold_staking.address,
economics=application_economics,
registry=test_registry
)
deployer.deploy(
progress=deployment_progress,
deployment_mode=constants.INIT,
transacting_power=transacting_power
)
return deployer
@pytest.mark.usefixtures('testerchain', 'agency', 'test_registry')
def test_staking_escrow_deployment(staking_escrow_deployer, deployment_progress, transacting_power):
deployment_receipts = staking_escrow_deployer.deploy(progress=deployment_progress,
deployment_mode=constants.FULL,
transacting_power=transacting_power)
# deployment steps must match expected number of steps
assert deployment_progress.num_steps == len(staking_escrow_deployer.deployment_steps) == len(deployment_receipts) == 2
# assert deployment_progress.num_steps == len(staking_escrow_deployer.deployment_steps) == len(deployment_receipts) == 2
for step in staking_escrow_deployer.deployment_steps:
assert deployment_receipts[step]['status'] == 1
def test_make_agent(staking_escrow_deployer, test_registry):
# Create a StakingEscrowAgent instance
staking_agent = staking_escrow_deployer.make_agent()
# Retrieve the StakingEscrowAgent singleton
same_staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert staking_agent == same_staking_agent
# Compare the contract address for equality
assert staking_agent.contract_address == same_staking_agent.contract_address
def test_staking_escrow_has_dispatcher(staking_escrow_deployer, testerchain, test_registry, transacting_power):
@pytest.mark.skip
def test_staking_escrow_has_dispatcher(testerchain, test_registry, transacting_power):
# Let's get the "bare" StakingEscrow contract (i.e., unwrapped, no dispatcher)
existing_bare_contract = testerchain.get_contract_by_name(registry=test_registry,
contract_name=staking_escrow_deployer.contract_name,
contract_name=StakingEscrowDeployer.contract_name,
proxy_name=DispatcherDeployer.contract_name,
use_proxy_address=False)
# This contract shouldn't be accessible directly through the deployer or the agent
assert staking_escrow_deployer.contract_address != existing_bare_contract.address
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert staking_agent.contract_address != existing_bare_contract
# The wrapped contract, on the other hand, points to the bare one.
target = staking_escrow_deployer.contract.functions.target().call()
@ -80,8 +84,12 @@ def test_rollback(testerchain, test_registry, transacting_power, threshold_staki
deployer = StakingEscrowDeployer(staking_interface=threshold_staking.address, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
current_target = staking_agent.contract.functions.target().call()
contract = testerchain.get_contract_by_name(registry=test_registry,
contract_name=deployer.contract_name,
proxy_name=DispatcherDeployer.contract_name,
use_proxy_address=True)
current_target = contract.functions.target().call()
# Let's do one more upgrade
receipts = deployer.upgrade(ignore_deployed=True, confirmations=0, transacting_power=transacting_power)
@ -90,14 +98,14 @@ def test_rollback(testerchain, test_registry, transacting_power, threshold_staki
assert receipt['status'] == 1
old_target = current_target
current_target = staking_agent.contract.functions.target().call()
current_target = contract.functions.target().call()
assert current_target != old_target
# It's time to rollback.
receipt = deployer.rollback(transacting_power=transacting_power)
assert receipt['status'] == 1
new_target = staking_agent.contract.functions.target().call()
new_target = contract.functions.target().call()
assert new_target != current_target
assert new_target == old_target

View File

@ -28,7 +28,7 @@ from nucypher.crypto.powers import TransactingPower
from tests.constants import (
DEVELOPMENT_ETH_AIRDROP_AMOUNT,
NUMBER_OF_ETH_TEST_ACCOUNTS,
NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS,
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS,
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS, INSECURE_DEVELOPMENT_PASSWORD
)
# Prevents TesterBlockchain to be picked up by py.test as a test class
@ -69,7 +69,7 @@ def test_testerchain_creation(testerchain, another_testerchain):
bob = chain.bob_account
assert bob == chain.client.accounts[2]
stakers = [chain.stake_provider_account(i) for i in range(NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS)]
stakers = [chain.stake_provider_account(i) for i in range(NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS)]
assert stakers == chain.stake_providers_accounts
ursulas = [chain.ursula_account(i) for i in range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)]

View File

@ -1,117 +0,0 @@
"""
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 web3 import Web3
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, NucypherTokenAgent
from nucypher.blockchain.eth.token import NU, Stake
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
@pytest.mark.skip()
def test_stake(testerchain, application_economics, agency, test_registry):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
class FakeUrsula:
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
burner_wallet = Web3().eth.account.create(INSECURE_DEVELOPMENT_PASSWORD)
checksum_address = burner_wallet.address
staking_agent = staking_agent
token_agent = token_agent
blockchain = testerchain
ursula = FakeUrsula()
stake = Stake(checksum_address=ursula.checksum_address,
first_locked_period=1,
final_locked_period=100,
value=NU(100, 'NU'),
index=0,
staking_agent=staking_agent,
economics=application_economics)
assert stake.value, 'NU' == NU(100, 'NU')
assert isinstance(stake.time_remaining(), int) # seconds
slang_remaining = stake.time_remaining(slang=True) # words
assert isinstance(slang_remaining, str)
@pytest.mark.skip()
def test_stake_equality(application_economics, get_random_checksum_address, mocker):
address = get_random_checksum_address()
a_different_address = get_random_checksum_address()
mock_agent = mocker.Mock(contract_address=a_different_address)
stake = Stake(checksum_address=address,
first_locked_period=1,
final_locked_period=2,
value=NU(100, 'NU'),
index=0,
staking_agent=mock_agent,
economics=application_economics)
assert stake == stake
duck_stake = mocker.Mock(index=0,
value=NU(100, 'NU'),
first_locked_period=1,
final_locked_period=2,
staker_address=address,
staking_agent=mock_agent)
assert stake == duck_stake
a_different_stake = Stake(checksum_address=address,
first_locked_period=0,
final_locked_period=2,
value=NU(100, 'NU'),
index=1,
staking_agent=mock_agent,
economics=application_economics)
assert stake != a_different_stake
undercover_agent = mocker.Mock(contract_address=address)
another_different_stake = Stake(checksum_address=a_different_address,
first_locked_period=1,
final_locked_period=2,
value=NU(100, 'NU'),
index=0,
staking_agent=undercover_agent,
economics=application_economics)
assert stake != another_different_stake
@pytest.mark.skip()
def test_stake_integration(staking_providers):
staker = list(staking_providers)[1]
stakes = staker.stakes
assert stakes
stake = stakes[0]
stake.sync()
blockchain_stakes = staker.application_agent.get_all_stakes(staker_address=staker.checksum_address)
stake_info = (stake.first_locked_period, stake.final_locked_period, int(stake.value))
published_stake_info = list(blockchain_stakes)[0]
assert stake_info == published_stake_info
assert stake_info == stake.to_stake_info()
assert stake.status() == Stake.Status.DIVISIBLE

View File

@ -113,7 +113,7 @@ def test_alice_web_character_control_grant_error_messages(alice_web_controller_t
assert response.status_code == 400
@pytest.mark.skip(reason="Current implementation requires Alice to directly depend on PolicyManager")
@pytest.mark.skip(reason="Current implementation requires Alice to directly depend on SubscriptionManager")
def test_alice_character_control_revoke(alice_web_controller_test_client, blockchain_bob):
bob_pubkey_enc = blockchain_bob.public_keys(DecryptingPower)

View File

@ -23,9 +23,6 @@ import pytest
from nucypher.characters.lawful import Enrico, Ursula
from nucypher.characters.unlawful import Amonia
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.policy.payment import SubscriptionManagerPayment
from tests.constants import TEST_ETH_PROVIDER_URI
def test_try_to_post_free_service_by_hacking_enact(blockchain_ursulas,
@ -57,43 +54,3 @@ def test_try_to_post_free_service_by_hacking_enact(blockchain_ursulas,
blockchain_bob.retrieve_and_decrypt([message_kit],
alice_verifying_key=amonia.stamp.as_umbral_pubkey(),
encrypted_treasure_map=bupkiss_policy.treasure_map)
@pytest.mark.skip()
def test_pay_a_flunky_instead_of_the_arranged_ursula(blockchain_alice,
blockchain_bob,
blockchain_ursulas,
ursula_decentralized_test_config,
testerchain):
# This test only applies to the PolicyManager payment method.
payment_method = SubscriptionManagerPayment(provider=TEST_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN)
blockchain_alice.payment_method = payment_method
amonia = Amonia.from_lawful_alice(blockchain_alice)
target_ursulas = blockchain_ursulas[0], blockchain_ursulas[1], blockchain_ursulas[2]
flunkies = [blockchain_ursulas[5], blockchain_ursulas[6], blockchain_ursulas[7]]
# Setup the policy details
shares = 3
policy_end_datetime = maya.now() + datetime.timedelta(days=35)
label = b"back_and_forth_forever"
bupkiss_policy = amonia.grant_while_paying_the_wrong_nodes(ursulas_to_trick_into_working_for_free=target_ursulas,
ursulas_to_pay_instead=flunkies,
bob=blockchain_bob,
label=label,
threshold=2,
shares=shares,
rate=int(1e18), # one ether
expiration=policy_end_datetime)
# Enrico becomes
enrico = Enrico(policy_encrypting_key=bupkiss_policy.public_key)
plaintext = b"A crafty campaign"
message_kit = enrico.encrypt_message(plaintext)
with pytest.raises(Ursula.NotEnoughUrsulas):
blockchain_bob.retrieve_and_decrypt([message_kit],
alice_verifying_key=amonia.stamp.as_umbral_pubkey(),
encrypted_treasure_map=bupkiss_policy.treasure_map)

View File

@ -1,153 +0,0 @@
"""
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 json
import pytest
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.actors import Operator
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
from nucypher.config.characters import StakeHolderConfiguration
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
@pytest.mark.skip()
def test_software_stakeholder_configuration(testerchain,
test_registry,
stakeholder_configuration,
stakeholder_config_file_location):
path = stakeholder_config_file_location
# Save the stakeholder JSON config
stakeholder_configuration.to_configuration_file(filepath=path)
with open(path, 'r') as file:
# Ensure file contents are serializable
contents = file.read()
first_config_contents = json.loads(contents)
# Destroy this stake holder, leaving only the configuration file behind
del stakeholder_configuration
# Restore StakeHolder instance from JSON config
stakeholder_config = StakeHolderConfiguration.from_configuration_file(filepath=path)
the_same_stakeholder = stakeholder_config.produce()
# Save the JSON config again
stakeholder_config.to_configuration_file(filepath=path, override=True)
with open(stakeholder_config.filepath, 'r') as file:
contents = file.read()
second_config_contents = json.loads(contents)
# Ensure the stakeholder was accurately restored from JSON config
assert first_config_contents == second_config_contents
@pytest.mark.skip()
def test_initialize_stake_with_existing_account(testerchain,
software_stakeholder,
stake_value,
application_economics,
test_registry):
assert len(software_stakeholder.staker.stakes) == 0
# No Stakes
with pytest.raises(IndexError):
_stake = software_stakeholder.staker.stakes[0]
# Really... there are no stakes.
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert staking_agent.get_staker_population() == 0
# Stake, deriving a new account with a password,
# sending tokens and ethers from the funding account
# to the staker's account, then initializing a new stake.
software_stakeholder.staker.initialize_stake(amount=stake_value,
lock_periods=application_economics.min_operator_seconds)
stake = software_stakeholder.staker.stakes[0]
# Wait for stake to begin
testerchain.time_travel(periods=1)
# Ensure the stakeholder is tracking the new staker and stake.
assert len(software_stakeholder.staker.stakes) == 1
# Ensure common stake perspective between stakeholder and stake
assert stake.value == stake_value
assert stake.duration == application_economics.min_operator_seconds
stakes = list(staking_agent.get_all_stakes(staker_address=stake.staker_address))
assert len(stakes) == 1
@pytest.mark.skip()
def test_divide_stake(software_stakeholder, application_economics, test_registry):
stake = software_stakeholder.staker.stakes[0]
target_value = application_economics.min_authorization
pre_divide_stake_value = stake.value
software_stakeholder.staker.divide_stake(stake=stake, additional_periods=10, target_value=target_value)
original_stake = software_stakeholder.staker.stakes[0]
new_stake = software_stakeholder.staker.stakes[-1]
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
stakes = list(staking_agent.get_all_stakes(staker_address=stake.staker_address))
assert len(stakes) == 2
assert new_stake.value == target_value
assert original_stake.value == (pre_divide_stake_value - target_value)
@pytest.mark.skip()
def test_bond_worker(software_stakeholder, manual_operator, test_registry):
software_stakeholder.staker.bond_worker(operator_address=manual_operator)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
assert staking_agent.get_worker_from_staker(staker_address=software_stakeholder.checksum_address) == manual_operator
@pytest.mark.skip()
def test_collect_inflation_rewards(software_stakeholder, manual_operator, testerchain, test_registry):
# Get stake
stake = software_stakeholder.staker.stakes[1]
# Make bonded Operator
tpower = TransactingPower(account=manual_operator, signer=Web3Signer(testerchain.client))
tpower.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
worker = Operator(is_me=True,
transacting_power=tpower,
domain=TEMPORARY_DOMAIN,
operator_address=manual_operator,
registry=test_registry)
# Wait out stake lock periods, manually make a commitment once per period.
for period in range(stake.periods_remaining-1):
worker.commit_to_next_period()
testerchain.time_travel(periods=1)
# Collect the staking reward in NU.
result = software_stakeholder.staker.collect_staking_reward()
# TODO: Make Assertions reasonable for this layer.
# Consider recycling logic from test_collect_reward_integration CLI test.
assert result

View File

@ -31,8 +31,8 @@ from tests.utils.ursula import make_decentralized_ursulas
@pytest.mark.usefixtures("blockchain_ursulas")
def test_stakers_bond_to_ursulas(testerchain, test_registry, staking_providers, ursula_decentralized_test_config):
ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
stakers_addresses=testerchain.stake_providers_accounts,
workers_addresses=testerchain.ursulas_accounts)
staking_provider_addresses=testerchain.stake_providers_accounts,
operator_addresses=testerchain.ursulas_accounts)
assert len(ursulas) == len(staking_providers)
for ursula in ursulas:

View File

@ -17,7 +17,6 @@
import json
import os
from pathlib import Path
from unittest.mock import patch, PropertyMock
@ -28,13 +27,12 @@ from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.registry import LocalContractRegistry
from nucypher.blockchain.eth.signers import Signer
from nucypher.blockchain.eth.sol import SOLIDITY_COMPILER_VERSION
from nucypher.cli.commands.deploy import deploy
from nucypher.config.constants import TEMPORARY_DOMAIN
from tests.constants import TEST_ETH_PROVIDER_URI, YES_ENTER
PLANNED_UPGRADES = 4
CONTRACTS_TO_UPGRADE = ('StakingEscrow', 'PolicyManager', 'Adjudicator', 'StakingInterface')
CONTRACTS_TO_UPGRADE = ('StakingEscrow', 'Adjudicator', 'StakingInterface')
@pytest.fixture(scope="module")
@ -120,7 +118,6 @@ def test_upgrade_contracts(click_runner, test_registry_source_manager, test_regi
#
contracts_to_upgrade = ('StakingEscrow', # v1 -> v2
'PolicyManager', # v1 -> v2
'Adjudicator', # v1 -> v2
'StakingInterface', # v1 -> v2
@ -128,11 +125,9 @@ def test_upgrade_contracts(click_runner, test_registry_source_manager, test_regi
'StakingEscrow', # v3 -> v4
'Adjudicator', # v2 -> v3
'PolicyManager', # v2 -> v3
'StakingInterface', # v2 -> v3
'StakingInterface', # v3 -> v4
'PolicyManager', # v3 -> v4
'Adjudicator', # v3 -> v4
) # NOTE: Keep all versions the same in this test (all version 4, for example)

View File

@ -22,16 +22,15 @@ import pytest
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
ContractAgency,
StakingEscrowAgent
)
from nucypher.blockchain.eth.constants import (
ADJUDICATOR_CONTRACT_NAME,
DISPATCHER_CONTRACT_NAME,
NUCYPHER_TOKEN_CONTRACT_NAME,
STAKING_ESCROW_CONTRACT_NAME, STAKING_ESCROW_STUB_CONTRACT_NAME
STAKING_ESCROW_CONTRACT_NAME,
STAKING_ESCROW_STUB_CONTRACT_NAME
)
from nucypher.blockchain.eth.deployers import (
StakingEscrowDeployer,
SubscriptionManagerDeployer
)
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, LocalContractRegistry
@ -136,8 +135,6 @@ def test_transfer_ownership(click_runner, testerchain, agency_local_registry):
assert staking_agent.owner == michwill
assert adjudicator_agent.owner == testerchain.etherbase_account
# Test transfer ownersh
def test_bare_contract_deployment_to_alternate_registry(click_runner, agency_local_registry):
@ -165,11 +162,6 @@ def test_bare_contract_deployment_to_alternate_registry(click_runner, agency_loc
new_registry = LocalContractRegistry(filepath=ALTERNATE_REGISTRY_FILEPATH)
assert agency_local_registry != new_registry
# FIXME
old_enrolled_names = list(agency_local_registry.enrolled_names).count(StakingEscrowDeployer.contract_name)
new_enrolled_names = list(new_registry.enrolled_names).count(StakingEscrowDeployer.contract_name)
# assert new_enrolled_names == old_enrolled_names + 1
@pytest.mark.skip()
def test_manual_proxy_retargeting(monkeypatch, testerchain, click_runner, application_economics):

View File

@ -77,7 +77,7 @@ def test_coexisting_configurations(click_runner,
# Parse node addresses
# TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)?
alice, ursula, another_ursula, staker, *all_yall = testerchain.unassigned_accounts
alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts
envvars = {NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
@ -231,7 +231,7 @@ def test_corrupted_configuration(click_runner,
shutil.rmtree(custom_filepath, ignore_errors=True)
assert not custom_filepath.exists()
alice, ursula, another_ursula, staker, *all_yall = testerchain.unassigned_accounts
alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts
#
# Chaos

View File

@ -14,24 +14,22 @@ 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 csv
import random
import re
import pytest
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
ContractAgency,
NucypherTokenAgent,
StakingEscrowAgent
)
from nucypher.blockchain.eth.token import NU
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.cli.commands.status import status
from nucypher.config.constants import TEMPORARY_DOMAIN
from tests.constants import FEE_RATE_RANGE, TEST_ETH_PROVIDER_URI, INSECURE_DEVELOPMENT_PASSWORD
from nucypher.crypto.powers import TransactingPower
@pytest.mark.skip()
@ -58,95 +56,6 @@ def test_nucypher_status_network(click_runner, testerchain, agency_local_registr
assert re.search(f"^Current Period \\.+ {staking_agent.get_current_period()}", result.output, re.MULTILINE)
@pytest.mark.skip()
def test_nucypher_status_stakers(click_runner, agency_local_registry, staking_providers):
# Get all stakers info
stakers_command = ('stakers',
'--registry-filepath', str(agency_local_registry.filepath.absolute()),
'--eth-provider', TEST_ETH_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN)
result = click_runner.invoke(status, stakers_command, catch_exceptions=False)
assert result.exit_code == 0
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
# TODO: Use regex matching instead of this
assert re.search(f"^Current period: {staking_agent.get_current_period()}", result.output, re.MULTILINE)
for staker in staking_providers:
assert re.search(f"^{staker.checksum_address}", result.output, re.MULTILINE)
# Get info of only one staker
some_dude = random.choice(staking_providers)
staking_address = some_dude.checksum_address
stakers_command = ('stakers', '--staking-address', staking_address,
'--registry-filepath', str(agency_local_registry.filepath.absolute()),
'--eth-provider', TEST_ETH_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN)
result = click_runner.invoke(status, stakers_command, catch_exceptions=False)
assert result.exit_code == 0
owned_tokens = NU.from_units(staking_agent.owned_tokens(staking_address))
current_locked_tokens = NU.from_units(staking_agent.get_locked_tokens(staking_address))
next_locked_tokens = NU.from_units(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"Operator:\s+" + some_dude.operator_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 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
@pytest.mark.skip()
def test_nucypher_status_fee_range(click_runner, agency_local_registry, staking_providers):
# Get information about global fee range (minimum rate, default rate, maximum rate)
stakers_command = ('fee-range',
'--registry-filepath', str(agency_local_registry.filepath),
'--eth-provider', TEST_ETH_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN)
result = click_runner.invoke(status, stakers_command, catch_exceptions=False)
assert result.exit_code == 0
minimum, default, maximum = FEE_RATE_RANGE
assert f"{minimum} wei" in result.output
assert f"{default} wei" in result.output
assert f"{maximum} wei" in result.output
@pytest.mark.skip()
def test_nucypher_status_locked_tokens(click_runner, testerchain, agency_local_registry, staking_providers):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
# All workers make a commitment
for ursula in testerchain.ursulas_accounts:
tpower = TransactingPower(account=ursula, signer=Web3Signer(testerchain.client))
tpower.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
staking_agent.commit_to_next_period(transacting_power=tpower)
testerchain.time_travel(periods=1)
periods = 2
status_command = ('locked-tokens',
'--registry-filepath', str(agency_local_registry.filepath.absolute()),
'--eth-provider', TEST_ETH_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--periods', periods)
light_parameter = [False, True]
for light in light_parameter:
testerchain.is_light = light
result = click_runner.invoke(status, status_command, catch_exceptions=False)
assert result.exit_code == 0
current_period = staking_agent.get_current_period()
all_locked = NU.from_units(staking_agent.get_global_locked_tokens(at_period=current_period))
assert re.search(f"Locked Tokens for next {periods} periods", result.output, re.MULTILINE)
assert re.search(f"Min: {all_locked} - Max: {all_locked}", result.output, re.MULTILINE)
@pytest.mark.skip()
def test_nucypher_status_events(click_runner, testerchain, agency_local_registry, staking_providers, temp_dir_path):
# All workers make a commitment

View File

@ -27,9 +27,8 @@ from web3 import Web3
from nucypher.blockchain.eth.agents import PREApplicationAgent, ContractAgency
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import StakeList
from nucypher.cli.main import nucypher_cli
from nucypher.config.characters import StakeHolderConfiguration, UrsulaConfiguration
from nucypher.config.characters import UrsulaConfiguration
from nucypher.config.constants import (
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD,
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
@ -82,7 +81,6 @@ def mock_funded_account_password_keystore(tmp_path_factory, testerchain, thresho
def test_ursula_and_local_keystore_signer_integration(click_runner,
tmp_path,
staking_providers,
stake_value,
application_economics,
mocker,
mock_funded_account_password_keystore,
@ -137,7 +135,6 @@ def test_ursula_and_local_keystore_signer_integration(click_runner,
ursula_config.keystore.unlock(password=password)
# Produce an Ursula with a Keystore signer correctly derived from the signer URI, and don't do anything else!
mocker.patch.object(StakeList, 'refresh', autospec=True)
ursula = ursula_config.produce()
ursula.signer.unlock_account(account=worker_account.address, password=password)

View File

@ -171,7 +171,7 @@ def test_persistent_node_storage_integration(click_runner,
blockchain_ursulas,
agency_local_registry):
alice, ursula, another_ursula, staker, *all_yall = testerchain.unassigned_accounts
alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts
filename = UrsulaConfiguration.generate_filename()
another_ursula_configuration_file_location = custom_filepath / filename

View File

@ -1,853 +0,0 @@
"""
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 json
import os
import random
import tempfile
from pathlib import Path
from unittest import mock
import maya
import pytest
from web3 import Web3
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.signers.software import Web3Signer
from nucypher.blockchain.eth.token import NU, Stake
from nucypher.blockchain.eth.utils import prettify_eth_amount
from nucypher.characters.lawful import Enrico, Ursula
from nucypher.cli.literature import SUCCESSFUL_MINTING
from nucypher.cli.main import nucypher_cli
from nucypher.config.characters import StakeHolderConfiguration, UrsulaConfiguration
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.policy.payment import SubscriptionManagerPayment
from nucypher.utilities.logging import Logger
from nucypher.utilities.networking import LOOPBACK_ADDRESS
from tests.constants import (
FAKE_PASSWORD_CONFIRMED,
FEE_RATE_RANGE,
INSECURE_DEVELOPMENT_PASSWORD,
MOCK_IP_ADDRESS,
TEST_ETH_PROVIDER_URI,
YES_ENTER
)
from tests.utils.middleware import MockRestMiddleware
from tests.utils.ursula import MOCK_KNOWN_URSULAS_CACHE, select_test_port
@mock.patch('nucypher.config.characters.StakeHolderConfiguration.default_filepath', return_value=Path('/non/existent/file'))
def test_missing_configuration_file(default_filepath_mock, click_runner):
cmd_args = ('stake', 'list')
result = click_runner.invoke(nucypher_cli, cmd_args, catch_exceptions=False)
assert result.exit_code != 0
assert default_filepath_mock.called
assert "nucypher stake init-stakeholder" in result.output
@pytest.mark.skip()
def test_new_stakeholder(click_runner,
custom_filepath,
agency_local_registry,
testerchain):
init_args = ('stake', 'init-stakeholder',
'--config-root', str(custom_filepath.absolute()),
'--eth-provider', TEST_ETH_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--registry-filepath', str(agency_local_registry.filepath.absolute()))
result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False)
assert result.exit_code == 0
# Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist'
custom_config_filepath = custom_filepath / StakeHolderConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
with open(custom_config_filepath, 'r') as config_file:
raw_config_data = config_file.read()
config_data = json.loads(raw_config_data)
assert config_data['eth_provider_uri'] == TEST_ETH_PROVIDER_URI
@pytest.mark.skip()
def test_stake_init(click_runner,
stakeholder_configuration_file_location,
stake_value,
application_economics,
testerchain,
agency_local_registry,
manual_staker):
# Staker address has not stakes
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
assert not stakes
stake_args = ('stake', 'create',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--value', stake_value.to_tokens(),
'--lock-periods', application_economics.min_operator_seconds,
'--force')
# TODO: This test is writing to the default system directory and ignoring updates to the passed filepath
user_input = f'0\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + YES_ENTER
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Test integration with BaseConfiguration
with open(stakeholder_configuration_file_location, 'r') as config_file:
_config_data = json.loads(config_file.read())
# Verify the stake is on-chain
# Test integration with Agency
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
assert len(stakes) == 1
# Test integration with NU
start_period, end_period, value = stakes[0]
assert NU(int(value), 'NuNit') == stake_value
assert (end_period - start_period) == application_economics.min_operator_seconds - 1
# Test integration with Stake
stake = Stake.from_stake_info(index=0,
checksum_address=manual_staker,
stake_info=stakes[0],
staking_agent=staking_agent,
economics=application_economics)
assert stake.value == stake_value
assert stake.duration == application_economics.min_operator_seconds
@pytest.mark.skip()
def test_stake_list(click_runner,
stakeholder_configuration_file_location,
stake_value,
agency_local_registry,
testerchain):
stake_args = ('stake', 'list',
'--config-file', str(stakeholder_configuration_file_location.absolute()))
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
assert str(stake_value) in result.output
_minimum, default, _maximum = FEE_RATE_RANGE
assert f"{default} wei" in result.output
@pytest.mark.skip()
def test_staker_divide_stakes(click_runner,
stakeholder_configuration_file_location,
application_economics,
manual_staker,
testerchain,
agency_local_registry):
divide_args = ('stake', 'divide',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--force',
'--staking-address', manual_staker,
'--index', 0,
'--value', NU(application_economics.min_authorization, 'NuNit').to_tokens(),
'--lock-periods', 10)
result = click_runner.invoke(nucypher_cli,
divide_args,
catch_exceptions=False,
env=dict(NUCYPHER_KEYSTORE_PASSWORD=INSECURE_DEVELOPMENT_PASSWORD))
assert result.exit_code == 0
stake_args = ('stake', 'list', '--config-file', str(stakeholder_configuration_file_location.absolute()))
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
assert str(NU(application_economics.min_authorization, 'NuNit').to_tokens()) in result.output
@pytest.mark.skip()
def test_stake_prolong(click_runner,
testerchain,
agency_local_registry,
manual_staker,
manual_worker,
stakeholder_configuration_file_location):
prolong_args = ('stake', 'prolong',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--index', 0,
'--lock-periods', 1,
'--staking-address', manual_staker,
'--force')
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
staker.refresh_stakes()
stake = staker.stakes[0]
old_termination = stake.final_locked_period
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(nucypher_cli,
prolong_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
# Ensure Integration with Stakes
stake.sync()
new_termination = stake.final_locked_period
assert new_termination == old_termination + 1
@pytest.mark.skip()
def test_stake_increase(click_runner,
stakeholder_configuration_file_location,
application_economics,
testerchain,
agency_local_registry,
manual_staker):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
stakes_length = len(stakes)
assert stakes_length > 0
selection = 0
new_value = NU.from_units(application_economics.min_authorization // 10)
origin_stake = stakes[selection]
stake_args = ('stake', 'increase',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--value', new_value.to_tokens(),
'--index', selection,
'--force')
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n'
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Verify the stake is on-chain
# Test integration with Agency
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
assert len(stakes) == stakes_length
# Test integration with NU
_start_period, end_period, value = stakes[selection]
assert NU(int(value), 'NuNit') == origin_stake.locked_value + new_value
assert end_period == origin_stake.last_period
@pytest.mark.skip()
def test_merge_stakes(click_runner,
stakeholder_configuration_file_location,
application_economics,
testerchain,
agency_local_registry,
manual_staker,
stake_value):
# Prepare new stake
stake_args = ('stake', 'create',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--value', stake_value.to_tokens(),
'--lock-periods', application_economics.min_operator_seconds + 1,
'--force')
user_input = f'0\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + YES_ENTER
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
stakes_length = len(stakes)
assert stakes_length > 0
selection_1 = 0
selection_2 = 2
origin_stake_1 = stakes[selection_1]
origin_stake_2 = stakes[selection_2]
assert origin_stake_1.last_period == origin_stake_2.last_period
stake_args = ('stake', 'merge',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--index-1', selection_1,
'--index-2', selection_2,
'--force')
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n'
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Verify the tx is on-chain
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
assert len(stakes) == stakes_length
assert stakes[selection_1].locked_value == origin_stake_1.locked_value + origin_stake_2.locked_value
assert stakes[selection_2].last_period == 1
@pytest.mark.skip()
def test_remove_inactive(click_runner,
stakeholder_configuration_file_location,
application_economics,
testerchain,
agency_local_registry,
manual_staker,
stake_value):
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
original_stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
selection = 2
assert original_stakes[selection].last_period == 1
stake_args = ('stake', 'remove-inactive',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--index', selection,
'--force')
user_input = f'0\n' + f'{INSECURE_DEVELOPMENT_PASSWORD}\n' + YES_ENTER
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
stakes = list(staking_agent.get_all_stakes(staker_address=manual_staker))
assert len(stakes) == len(original_stakes) - 1
@pytest.mark.skip()
def test_stake_bond_worker(click_runner,
testerchain,
agency_local_registry,
manual_staker,
manual_worker,
stakeholder_configuration_file_location):
init_args = ('stake', 'bond-worker',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--worker-address', manual_worker,
'--force')
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(nucypher_cli,
init_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.worker_address == manual_worker
@pytest.mark.skip()
def test_ursula_init(click_runner,
custom_filepath,
agency_local_registry,
manual_staker,
manual_worker,
testerchain):
deploy_port = select_test_port()
init_args = ('ursula', 'init',
'--network', TEMPORARY_DOMAIN,
'--payment-network', TEMPORARY_DOMAIN,
'--worker-address', manual_worker,
'--config-root', str(custom_filepath.absolute()),
'--eth-provider', TEST_ETH_PROVIDER_URI,
'--registry-filepath', str(agency_local_registry.filepath.absolute()),
'--rest-host', MOCK_IP_ADDRESS,
'--rest-port', deploy_port)
result = click_runner.invoke(nucypher_cli,
init_args,
input=FAKE_PASSWORD_CONFIRMED,
catch_exceptions=False)
assert result.exit_code == 0
# Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'KEYSTORE does not exist'
assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory does not exist'
custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
with open(custom_config_filepath, 'r') as config_file:
raw_config_data = config_file.read()
config_data = json.loads(raw_config_data)
assert config_data['eth_provider_uri'] == TEST_ETH_PROVIDER_URI
assert config_data['worker_address'] == manual_worker
assert TEMPORARY_DOMAIN == config_data['domain']
@pytest.mark.skip()
def test_ursula_run(click_runner,
manual_worker,
manual_staker,
custom_filepath,
testerchain):
custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
# Now start running your Ursula!
init_args = ('ursula', 'run',
'--dry-run',
'--config-file', str(custom_config_filepath.absolute()))
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
result = click_runner.invoke(nucypher_cli,
init_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
@pytest.mark.skip()
def test_stake_restake(click_runner,
manual_staker,
custom_filepath,
testerchain,
agency_local_registry,
stakeholder_configuration_file_location):
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.is_restaking
restake_args = ('stake', 'restake',
'--disable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
restake_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert not staker.is_restaking
assert "Successfully disabled" in result.output
disable_args = ('stake', 'restake',
'--enable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
disable_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert staker.is_restaking
assert "Successfully enabled" in result.output
# Disable again
disable_args = ('stake', 'restake',
'--disable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
disable_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
@pytest.mark.skip()
def test_stake_winddown(click_runner,
manual_staker,
custom_filepath,
testerchain,
agency_local_registry,
stakeholder_configuration_file_location):
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert not staker.is_winding_down
restake_args = ('stake', 'winddown',
'--enable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
restake_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert staker.is_winding_down
assert "Successfully enabled" in result.output
disable_args = ('stake', 'winddown',
'--disable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
disable_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert not staker.is_winding_down
assert "Successfully disabled" in result.output
@pytest.mark.skip()
def test_stake_snapshots(click_runner,
manual_staker,
custom_filepath,
testerchain,
agency_local_registry,
stakeholder_configuration_file_location):
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.is_taking_snapshots
restake_args = ('stake', 'snapshots',
'--disable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
restake_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert not staker.is_taking_snapshots
assert "Successfully disabled" in result.output
disable_args = ('stake', 'snapshots',
'--enable',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
disable_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert staker.is_taking_snapshots
assert "Successfully enabled" in result.output
@pytest.mark.skip()
def test_collect_rewards_integration(click_runner,
testerchain,
agency_local_registry,
stakeholder_configuration_file_location,
blockchain_alice,
blockchain_bob,
random_policy_label,
manual_staker,
manual_worker,
application_economics,
policy_value):
half_stake_time = 2 * application_economics.min_operator_seconds # Test setup
logger = Logger("Test-CLI") # Enter the Teacher's Logger, and
current_period = 0 # State the initial period for incrementing
staker_address = manual_staker
worker_address = manual_worker
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=staker_address,
registry=agency_local_registry)
staker.refresh_stakes()
# The staker is staking.
assert staker.is_staking
assert staker.stakes
assert staker.worker_address == worker_address
# TODO: Test for SubscriptionManager?
payment_method = SubscriptionManagerPayment(provider=TEST_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN)
ursula_port = select_test_port()
ursula = Ursula(is_me=True,
checksum_address=staker_address,
signer=Web3Signer(testerchain.client),
worker_address=worker_address,
registry=agency_local_registry,
rest_host=LOOPBACK_ADDRESS,
rest_port=ursula_port,
eth_provider_uri=TEST_ETH_PROVIDER_URI,
network_middleware=MockRestMiddleware(),
db_filepath=tempfile.mkdtemp(),
domain=TEMPORARY_DOMAIN,
payment_method=payment_method)
MOCK_KNOWN_URSULAS_CACHE[ursula_port] = ursula
assert ursula.worker_address == worker_address
assert ursula.checksum_address == staker_address
# Make a commitment for half the first stake duration
testerchain.time_travel(periods=1)
for _ in range(half_stake_time):
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.commit_to_next_period()
testerchain.time_travel(periods=1)
current_period += 1
# Alice creates a policy and grants Bob access
blockchain_alice.selection_buffer = 1
threshold, shares = 1, 1
duration_in_periods = 3
days = (duration_in_periods - 1) * (application_economics.hours_per_period // 24)
now = testerchain.w3.eth.getBlock('latest').timestamp
expiration = maya.MayaDT(now).add(days=days)
blockchain_policy = blockchain_alice.grant(bob=blockchain_bob,
label=random_policy_label,
threshold=threshold,
shares=shares,
value=policy_value,
expiration=expiration,
payment_method=payment_method,
ursulas={ursula})
# Ensure that the handpicked Ursula was selected for the policy
treasure_map = blockchain_bob._decrypt_treasure_map(blockchain_policy.treasure_map,
blockchain_policy.publisher_verifying_key)
assert ursula.canonical_address in treasure_map.destinations
# Bob learns about the new staker and joins the policy
blockchain_bob.start_learning_loop()
blockchain_bob.remember_node(node=ursula)
# Enrico Encrypts (of course)
enrico = Enrico(policy_encrypting_key=blockchain_policy.public_key,
network_middleware=MockRestMiddleware())
verifying_key = blockchain_alice.stamp.as_umbral_pubkey()
for index in range(half_stake_time - 5):
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.commit_to_next_period()
# Encrypt
random_data = os.urandom(random.randrange(20, 100))
message_kit = enrico.encrypt_message(plaintext=random_data)
# Decrypt
cleartexts = blockchain_bob.retrieve_and_decrypt([message_kit],
alice_verifying_key=verifying_key,
encrypted_treasure_map=blockchain_policy.treasure_map)
assert random_data == cleartexts[0]
# Ursula Staying online and the clock advancing
testerchain.time_travel(periods=1)
current_period += 1
# Finish the passage of time for the first Stake
for _ in range(5): # plus the extended periods from stake division
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
ursula.commit_to_next_period()
testerchain.time_travel(periods=1)
current_period += 1
#
# WHERES THE MONEY URSULA?? - Collecting Rewards
#
# The address the client wants Ursula to send rewards to
burner_wallet = testerchain.w3.eth.account.create(INSECURE_DEVELOPMENT_PASSWORD)
# The rewards wallet is initially empty, because it is freshly created
assert testerchain.client.get_balance(burner_wallet.address) == 0
# Rewards will be unlocked after the
# final committed period has passed (+1).
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
testerchain.time_travel(periods=1)
current_period += 1
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
# At least half of the tokens are unlocked (restaking was enabled for some prior periods)
assert staker.locked_tokens() >= application_economics.min_authorization
# Collect Policy Fee
collection_args = ('stake', 'rewards', 'withdraw',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--fees',
'--no-tokens',
'--staking-address', staker_address,
'--withdraw-address', burner_wallet.address)
result = click_runner.invoke(nucypher_cli,
collection_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
# Policy Fee
collected_policy_fee = testerchain.client.get_balance(burner_wallet.address)
assert collected_policy_fee # TODO: staker CLI is pending removal
# Finish the passage of time... once and for all
# Extended periods from stake division
for _ in range(9):
ursula.commit_to_next_period()
current_period += 1
logger.debug(f">>>>>>>>>>> TEST PERIOD {current_period} <<<<<<<<<<<<<<<<")
testerchain.time_travel(periods=1)
#
# Collect Staking Reward
#
balance_before_collecting = staker.token_agent.get_balance(address=staker_address)
collection_args = ('stake', 'rewards', 'withdraw',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--no-fees',
'--tokens',
'--staking-address', staker_address,
'--force')
result = click_runner.invoke(nucypher_cli,
collection_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
# The staker has withdrawn her staking rewards
assert staker.token_agent.get_balance(address=staker_address) > balance_before_collecting
@pytest.mark.skip()
def test_stake_unbond_worker(click_runner,
testerchain,
manual_staker,
manual_worker,
agency_local_registry,
stakeholder_configuration_file_location):
testerchain.time_travel(periods=1)
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.worker_address == manual_worker
init_args = ('stake', 'unbond-worker',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force'
)
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
result = click_runner.invoke(nucypher_cli,
init_args,
input=user_input,
catch_exceptions=False)
assert result.exit_code == 0
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.worker_address == NULL_ADDRESS
@pytest.mark.skip()
def test_set_min_rate(click_runner,
manual_staker,
testerchain,
agency_local_registry,
stakeholder_configuration_file_location):
_minimum, _default, maximum = FEE_RATE_RANGE
min_rate = maximum - 1
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.raw_min_fee_rate == 0
min_rate_in_gwei = Web3.fromWei(min_rate, 'gwei')
restake_args = ('stake', 'set-min-rate',
'--min-rate', min_rate_in_gwei,
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
restake_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert staker.raw_min_fee_rate == min_rate
assert "successfully set" in result.output
stake_args = ('stake', 'list',
'--config-file', str(stakeholder_configuration_file_location.absolute()))
user_input = INSECURE_DEVELOPMENT_PASSWORD
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
assert f"{prettify_eth_amount(min_rate)}" in result.output
@pytest.mark.skip()
def test_mint(click_runner,
manual_staker,
testerchain,
agency_local_registry,
stakeholder_configuration_file_location):
testerchain.time_travel(periods=2)
staker = Staker(domain=TEMPORARY_DOMAIN,
checksum_address=manual_staker,
registry=agency_local_registry)
assert staker.mintable_periods() > 0
owned_tokens = staker.owned_tokens()
mint_args = ('stake', 'mint',
'--config-file', str(stakeholder_configuration_file_location.absolute()),
'--staking-address', manual_staker,
'--force')
result = click_runner.invoke(nucypher_cli,
mint_args,
input=INSECURE_DEVELOPMENT_PASSWORD,
catch_exceptions=False)
assert result.exit_code == 0
assert staker.owned_tokens() > owned_tokens
assert staker.mintable_periods() == 0
assert SUCCESSFUL_MINTING in result.output

View File

@ -15,14 +15,14 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from contextlib import contextmanager
import pytest
import sys
from contextlib import contextmanager
from io import StringIO
from nucypher.control.emitters import StdoutEmitter
import pytest
from nucypher.cli.processes import UrsulaCommandProtocol
from nucypher.control.emitters import StdoutEmitter
@contextmanager

View File

@ -22,7 +22,7 @@ from twisted.logger import LogLevel, globalLogPublisher
from nucypher.acumen.perception import FleetSensor
from nucypher.config.constants import TEMPORARY_DOMAIN
from tests.utils.middleware import MockRestMiddleware
from tests.utils.ursula import make_ursula_for_staker
from tests.utils.ursula import make_ursula_for_staking_provider
# @pytest.mark.skip()
@ -89,14 +89,14 @@ def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, mock
@pytest.mark.skip("See Issue #1075") # TODO: Issue #1075
def test_invalid_workers_tolerance(testerchain,
test_registry,
blockchain_ursulas,
agency,
idle_staker,
application_economics,
ursula_decentralized_test_config
):
def test_invalid_operators_tolerance(testerchain,
test_registry,
blockchain_ursulas,
agency,
idle_staker,
application_economics,
ursula_decentralized_test_config
):
#
# Setup
#
@ -124,11 +124,11 @@ def test_invalid_workers_tolerance(testerchain,
idle_staker.stake_tracker.refresh()
# We create an active worker node for this staker
worker = make_ursula_for_staker(staker=idle_staker,
operator_address=testerchain.unassigned_accounts[-1],
ursula_config=ursula_decentralized_test_config,
blockchain=testerchain,
ursulas_to_learn_about=None)
worker = make_ursula_for_staking_provider(staking_provider=idle_staker,
operator_address=testerchain.unassigned_accounts[-1],
ursula_config=ursula_decentralized_test_config,
blockchain=testerchain,
ursulas_to_learn_about=None)
# Since we made a commitment, we need to advance one period
testerchain.time_travel(periods=1)

View File

@ -16,19 +16,15 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import datetime
import maya
import pytest
from eth_utils import to_checksum_address
from twisted.logger import LogLevel, globalLogPublisher
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent, PREApplicationAgent
from nucypher.acumen.nicknames import Nickname
from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
from nucypher.characters.unlawful import Vladimir
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.powers import SigningPower
from tests.utils.middleware import MockRestMiddleware

View File

@ -32,10 +32,10 @@ from nucypher.config.constants import NUCYPHER_ENVVAR_KEYSTORE_PASSWORD, NUCYPHE
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS = 10
NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS
# Ursulas (Operators) and Stakers have their own account
NUMBER_OF_ETH_TEST_ACCOUNTS = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS + 10
# Ursulas (Operators) and Staking Providers have their own account
NUMBER_OF_ETH_TEST_ACCOUNTS = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS + NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS + 10
NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS

View File

@ -35,11 +35,11 @@ from web3.contract import Contract
from web3.types import TxReceipt
from nucypher.blockchain.economics import Economics
from nucypher.blockchain.eth.actors import StakeHolder, Staker, Operator
from nucypher.blockchain.eth.actors import Operator
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent, PREApplicationAgent
from nucypher.blockchain.eth.deployers import (
PREApplicationDeployer,
SubscriptionManagerDeployer
SubscriptionManagerDeployer, NucypherTokenDeployer
)
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, LocalContractRegistry
@ -49,7 +49,6 @@ from nucypher.characters.lawful import Enrico
from nucypher.config.characters import (
AliceConfiguration,
BobConfiguration,
StakeHolderConfiguration,
UrsulaConfiguration
)
from nucypher.config.constants import TEMPORARY_DOMAIN
@ -58,7 +57,6 @@ from nucypher.crypto.keystore import Keystore
from nucypher.crypto.powers import TransactingPower
from nucypher.datastore import datastore
from nucypher.network.nodes import TEACHER_NODES
from nucypher.policy.policies import BlockchainPolicy
from nucypher.utilities.logging import GlobalLoggerSettings, Logger
from nucypher.utilities.porter.porter import Porter
from tests.constants import (
@ -98,8 +96,12 @@ from tests.utils.config import (
)
from tests.utils.middleware import MockRestMiddleware, MockRestMiddlewareForLargeFleetTests
from tests.utils.policy import generate_random_label
from tests.utils.ursula import (MOCK_KNOWN_URSULAS_CACHE, MOCK_URSULA_STARTING_PORT,
make_decentralized_ursulas, make_federated_ursulas)
from tests.utils.ursula import (
MOCK_KNOWN_URSULAS_CACHE,
MOCK_URSULA_STARTING_PORT,
make_decentralized_ursulas,
make_federated_ursulas
)
test_logger = Logger("test-logger")
@ -570,6 +572,9 @@ def deployer_transacting_power(testerchain):
def _make_agency(test_registry, token_economics, deployer_transacting_power, threshold_staking):
transacting_power = deployer_transacting_power
token_deployer = NucypherTokenDeployer(economics=token_economics, registry=test_registry)
token_deployer.deploy(transacting_power=transacting_power)
pre_application_deployer = PREApplicationDeployer(economics=token_economics,
registry=test_registry,
staking_interface=threshold_staking.address)
@ -658,8 +663,8 @@ def blockchain_ursulas(testerchain, staking_providers, ursula_decentralized_test
MOCK_KNOWN_URSULAS_CACHE.clear()
_ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
stakers_addresses=testerchain.stake_providers_accounts,
workers_addresses=testerchain.ursulas_accounts)
staking_provider_addresses=testerchain.stake_providers_accounts,
operator_addresses=testerchain.ursulas_accounts)
for u in _ursulas:
u.synchronous_query_timeout = .01 # We expect to never have to wait for content that is actually on-chain during tests.
#testerchain.time_travel(periods=1)
@ -687,30 +692,6 @@ def blockchain_ursulas(testerchain, staking_providers, ursula_decentralized_test
_ursulas.clear()
@pytest.fixture(scope="module")
def idle_staker(testerchain, agency, test_registry):
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
idle_staker_account = testerchain.unassigned_accounts[-2]
transacting_power = TransactingPower(account=testerchain.etherbase_account,
signer=Web3Signer(testerchain.client))
token_airdrop(transacting_power=transacting_power,
addresses=[idle_staker_account],
token_agent=token_agent,
amount=DEVELOPMENT_TOKEN_AIRDROP_AMOUNT)
# Prepare idle staker
idle_staker = Staker(transacting_power=transacting_power,
domain=TEMPORARY_DOMAIN,
blockchain=testerchain)
yield idle_staker
@pytest.fixture(scope='module')
def stake_value(application_economics):
value = NU(application_economics.min_authorization * 2, 'NuNit')
return value
@pytest.fixture(scope='module')
def policy_rate():
rate = Web3.toWei(21, 'gwei')

View File

@ -1,308 +0,0 @@
"""
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 nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.token import NU, Stake, validate_divide, validate_prolong, validate_increase, \
validate_merge
from nucypher.types import StakerInfo
def test_child_status():
for status in Stake.Status:
assert status.is_child(status)
# Check relations for inactive status
assert Stake.Status.INACTIVE.is_child(Stake.Status.UNLOCKED)
assert not Stake.Status.INACTIVE.is_child(Stake.Status.LOCKED)
assert not Stake.Status.INACTIVE.is_child(Stake.Status.EDITABLE)
assert not Stake.Status.INACTIVE.is_child(Stake.Status.DIVISIBLE)
# Check relations for unlocked status
assert not Stake.Status.UNLOCKED.is_child(Stake.Status.INACTIVE)
assert not Stake.Status.UNLOCKED.is_child(Stake.Status.LOCKED)
assert not Stake.Status.UNLOCKED.is_child(Stake.Status.EDITABLE)
assert not Stake.Status.UNLOCKED.is_child(Stake.Status.DIVISIBLE)
# Check relations for locked status
assert not Stake.Status.LOCKED.is_child(Stake.Status.INACTIVE)
assert not Stake.Status.LOCKED.is_child(Stake.Status.UNLOCKED)
assert not Stake.Status.LOCKED.is_child(Stake.Status.EDITABLE)
assert not Stake.Status.LOCKED.is_child(Stake.Status.DIVISIBLE)
# Check relations for editable status
assert not Stake.Status.EDITABLE.is_child(Stake.Status.INACTIVE)
assert not Stake.Status.EDITABLE.is_child(Stake.Status.UNLOCKED)
assert Stake.Status.EDITABLE.is_child(Stake.Status.LOCKED)
assert not Stake.Status.EDITABLE.is_child(Stake.Status.DIVISIBLE)
# Check relations for divisible status
assert not Stake.Status.DIVISIBLE.is_child(Stake.Status.INACTIVE)
assert not Stake.Status.DIVISIBLE.is_child(Stake.Status.UNLOCKED)
assert Stake.Status.DIVISIBLE.is_child(Stake.Status.LOCKED)
assert Stake.Status.DIVISIBLE.is_child(Stake.Status.EDITABLE)
@pytest.mark.skip('reuse me')
def test_stake_status(mock_testerchain, application_economics, mock_staking_agent):
address = mock_testerchain.etherbase_account
current_period = 3
staker_info = StakerInfo(current_committed_period=current_period-1,
next_committed_period=current_period,
value=0,
last_committed_period=0,
lock_restake_until_period=False,
completed_work=0,
worker_start_period=0,
worker=NULL_ADDRESS,
flags=bytes())
mock_staking_agent.get_current_period.return_value = current_period
mock_staking_agent.get_staker_info.return_value = staker_info
def make_sub_stake(value, first_locked_period, final_locked_period):
return Stake(checksum_address=address,
first_locked_period=first_locked_period,
final_locked_period=final_locked_period,
value=value,
index=0,
staking_agent=mock_staking_agent,
economics=application_economics)
# Prepare unlocked sub-stake
nu = NU.from_units(2 * application_economics.min_authorization - 1)
stake = make_sub_stake(nu, current_period - 2, current_period - 1)
assert stake.status() == Stake.Status.UNLOCKED
# Prepare inactive sub-stake
# Update staker info and create new state
stake = make_sub_stake(nu, current_period - 2, current_period - 1)
staker_info = staker_info._replace(current_committed_period=current_period,
next_committed_period=current_period + 1)
mock_staking_agent.get_staker_info.return_value = staker_info
assert stake.status() == Stake.Status.INACTIVE
# Prepare locked sub-stake
stake = make_sub_stake(nu, current_period - 2, current_period)
assert stake.status() == Stake.Status.LOCKED
# Prepare editable sub-stake
stake = make_sub_stake(nu, current_period - 2, current_period + 1)
assert stake.status() == Stake.Status.EDITABLE
# Prepare divisible sub-stake
nu = NU.from_units(2 * application_economics.min_authorization)
stake = make_sub_stake(nu, current_period - 2, current_period + 1)
assert stake.status() == Stake.Status.DIVISIBLE
@pytest.mark.skip('remove me')
def test_stake_sync(mock_testerchain, application_economics, mock_staking_agent):
address = mock_testerchain.etherbase_account
current_period = 3
staker_info = StakerInfo(current_committed_period=current_period-1,
next_committed_period=current_period,
value=0,
last_committed_period=0,
lock_restake_until_period=False,
completed_work=0,
worker_start_period=0,
worker=NULL_ADDRESS,
flags=bytes())
mock_staking_agent.get_current_period.return_value = current_period
mock_staking_agent.get_staker_info.return_value = staker_info
# Prepare sub-stake
nu = NU.from_units(2 * application_economics.min_authorization - 1)
stake = Stake(checksum_address=address,
first_locked_period=current_period - 2,
final_locked_period=current_period + 1,
value=nu,
index=0,
staking_agent=mock_staking_agent,
economics=application_economics)
assert stake.status() == Stake.Status.EDITABLE
# Update locked value and sync
sub_stake_info = stake.to_stake_info()
nunits = 2 * application_economics.min_authorization
sub_stake_info = sub_stake_info._replace(locked_value=nunits)
mock_staking_agent.get_substake_info.return_value = sub_stake_info
stake.sync()
assert stake.status() == Stake.Status.DIVISIBLE
assert stake.value == NU.from_units(nunits)
# Update current period and sync
mock_staking_agent.get_current_period.return_value = current_period + 1
sub_stake_info = sub_stake_info._replace(locked_value=nunits)
mock_staking_agent.get_substake_info.return_value = sub_stake_info
stake.sync()
assert stake.status() == Stake.Status.LOCKED
assert stake.final_locked_period == current_period + 1
# Update final period and sync
sub_stake_info = sub_stake_info._replace(last_period=current_period)
mock_staking_agent.get_substake_info.return_value = sub_stake_info
stake.sync()
assert stake.status() == Stake.Status.UNLOCKED
assert stake.final_locked_period == current_period
# Update first period and sync
sub_stake_info = sub_stake_info._replace(first_period=current_period)
mock_staking_agent.get_substake_info.return_value = sub_stake_info
with pytest.raises(Stake.StakingError):
stake.sync()
@pytest.mark.skip('remove me')
def test_stake_validation(mock_testerchain, application_economics, mock_staking_agent):
address = mock_testerchain.etherbase_account
# Validate stake initialization
with pytest.raises(Stake.StakingError):
Stake.initialize_stake(staking_agent=mock_staking_agent,
checksum_address=address,
economics=application_economics,
amount=application_economics.min_authorization - 1,
lock_periods=application_economics.min_operator_seconds)
with pytest.raises(Stake.StakingError):
Stake.initialize_stake(staking_agent=mock_staking_agent,
checksum_address=address,
economics=application_economics,
amount=application_economics.min_authorization,
lock_periods=application_economics.min_operator_seconds - 1)
with pytest.raises(Stake.StakingError):
Stake.initialize_stake(staking_agent=mock_staking_agent,
checksum_address=address,
economics=application_economics,
amount=application_economics.maximum_allowed_locked + 1,
lock_periods=application_economics.min_operator_seconds)
mock_staking_agent.get_locked_tokens.return_value = 0
Stake.initialize_stake(staking_agent=mock_staking_agent,
checksum_address=address,
economics=application_economics,
amount=application_economics.maximum_allowed_locked,
lock_periods=application_economics.min_operator_seconds)
# Validate divide method
current_period = 10
mock_staking_agent.get_current_period.return_value = 10
def make_sub_stake(value, first_locked_period, final_locked_period, index=0):
return Stake(checksum_address=address,
first_locked_period=first_locked_period,
final_locked_period=final_locked_period,
value=value,
index=index,
staking_agent=mock_staking_agent,
economics=application_economics)
nu = NU.from_units(2 * application_economics.min_authorization - 1)
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period + 1,
value=nu)
with pytest.raises(Stake.StakingError):
validate_divide(stake=stake, target_value=application_economics.min_authorization, additional_periods=1)
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period,
value=nu + 1)
with pytest.raises(Stake.StakingError):
validate_divide(stake=stake, target_value=application_economics.min_authorization, additional_periods=1)
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period + 1,
value=nu + 1)
with pytest.raises(Stake.StakingError):
validate_divide(stake=stake, target_value=application_economics.min_authorization - 1, additional_periods=1)
validate_divide(stake=stake, target_value=application_economics.min_authorization, additional_periods=1)
# Validate prolong method
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period,
value=nu)
with pytest.raises(Stake.StakingError):
validate_prolong(stake=stake, additional_periods=1)
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period + 2,
value=nu)
with pytest.raises(Stake.StakingError):
validate_prolong(stake=stake, additional_periods=1)
with pytest.raises(Stake.StakingError):
validate_prolong(stake=stake, additional_periods=application_economics.min_operator_seconds - 3)
validate_prolong(stake=stake, additional_periods=application_economics.min_operator_seconds - 2)
# Validate increase method
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period,
value=nu)
with pytest.raises(Stake.StakingError):
validate_increase(stake=stake, amount=nu)
stake = make_sub_stake(first_locked_period=current_period - 2,
final_locked_period=current_period + 1,
value=nu)
stake.staking_agent.get_locked_tokens.return_value = nu
with pytest.raises(Stake.StakingError):
validate_increase(stake=stake, amount=NU.from_units(application_economics.maximum_allowed_locked - int(nu) + 1))
validate_increase(stake=stake, amount=NU.from_units(application_economics.maximum_allowed_locked - int(nu)))
# Validate merge method
stake_1 = make_sub_stake(first_locked_period=current_period - 1,
final_locked_period=current_period,
value=nu,
index=0)
stake_2 = make_sub_stake(first_locked_period=current_period - 1,
final_locked_period=current_period,
value=nu,
index=1)
with pytest.raises(Stake.StakingError):
validate_merge(stake_1=stake_1, stake_2=stake_2)
stake_1 = make_sub_stake(first_locked_period=current_period - 1,
final_locked_period=current_period + 1,
value=nu,
index=2)
stake_2 = make_sub_stake(first_locked_period=current_period - 1,
final_locked_period=current_period + 2,
value=nu,
index=3)
with pytest.raises(Stake.StakingError):
validate_merge(stake_1=stake_1, stake_2=stake_2)
with pytest.raises(Stake.StakingError):
validate_merge(stake_1=stake_1, stake_2=stake_1)
stake_2 = make_sub_stake(first_locked_period=current_period - 3,
final_locked_period=current_period + 1,
value=nu,
index=4)
validate_merge(stake_1=stake_1, stake_2=stake_2)

View File

@ -18,16 +18,9 @@
import click
import pytest
from nucypher.blockchain.economics import Economics
from nucypher.blockchain.eth.clients import EthereumTesterClient, PUBLIC_CHAINS
from nucypher.blockchain.eth.token import NU
from nucypher.cli.actions.confirm import (confirm_deployment, confirm_enable_restaking,
confirm_enable_winding_down, confirm_large_and_or_long_stake, confirm_staged_stake)
from nucypher.cli.literature import (ABORT_DEPLOYMENT, RESTAKING_AGREEMENT,
WINDING_DOWN_AGREEMENT, CONFIRM_STAGED_STAKE,
CONFIRM_LARGE_STAKE_VALUE, CONFIRM_LARGE_STAKE_DURATION)
from tests.constants import YES, NO
from nucypher.cli.actions.confirm import confirm_deployment
from nucypher.cli.literature import ABORT_DEPLOYMENT
def test_confirm_deployment_cli_action(mocker, mock_stdin, test_emitter, capsys, mock_testerchain):
@ -82,142 +75,3 @@ def test_confirm_deployment_cli_action(mocker, mock_stdin, test_emitter, capsys,
assert mock_stdin.empty()
captured = capsys.readouterr()
assert f"Type '{llamanet.upper()}' to continue: " in captured.out
@pytest.mark.skip('remove me')
def test_confirm_enable_restaking_cli_action(test_emitter, mock_stdin, capsys):
# Positive Case
mock_stdin.line(YES)
staking_address = '0xdeadbeef'
result = confirm_enable_restaking(emitter=test_emitter, staking_address=staking_address)
assert result
assert mock_stdin.empty()
captured = capsys.readouterr()
restake_agreement = RESTAKING_AGREEMENT.format(staking_address=staking_address)
assert restake_agreement in captured.out
# Negative case
mock_stdin.line(NO)
with pytest.raises(click.Abort):
confirm_enable_restaking(emitter=test_emitter, staking_address=staking_address)
captured = capsys.readouterr()
assert mock_stdin.empty()
restake_agreement = RESTAKING_AGREEMENT.format(staking_address=staking_address)
assert restake_agreement in captured.out
@pytest.mark.skip('remove me')
def test_confirm_enable_winding_down_cli_action(test_emitter, mock_stdin, capsys):
# Positive Case
mock_stdin.line(YES)
staking_address = '0xdeadbeef'
result = confirm_enable_winding_down(emitter=test_emitter, staking_address=staking_address)
assert result
assert mock_stdin.empty()
captured = capsys.readouterr()
assert WINDING_DOWN_AGREEMENT in captured.out
# Negative case
mock_stdin.line(NO)
with pytest.raises(click.Abort):
confirm_enable_winding_down(emitter=test_emitter, staking_address=staking_address)
captured = capsys.readouterr()
assert mock_stdin.empty()
assert WINDING_DOWN_AGREEMENT in captured.out
@pytest.mark.skip('remove me')
def test_confirm_staged_stake_cli_action(test_emitter, mock_stdin, capsys):
staking_address, value, lock_periods = '0xdeadbeef', NU.from_tokens(1), 1
confirmation = CONFIRM_STAGED_STAKE.format(staker_address=staking_address,
lock_periods=lock_periods,
tokens=value,
nunits=value.to_units())
# Positive Case
mock_stdin.line(YES)
result = confirm_staged_stake(staker_address=staking_address,
value=value,
lock_periods=lock_periods)
assert result
assert mock_stdin.empty()
captured = capsys.readouterr()
assert confirmation in captured.out
# Negative case
mock_stdin.line(NO)
with pytest.raises(click.Abort):
confirm_staged_stake(staker_address=staking_address,
value=value,
lock_periods=lock_periods)
captured = capsys.readouterr()
assert confirmation in captured.out
assert mock_stdin.empty()
STANDARD_ECONOMICS = Economics()
MIN_ALLOWED_LOCKED = STANDARD_ECONOMICS.min_authorization
@pytest.mark.skip('remove me')
@pytest.mark.parametrize('value,duration,must_confirm_value,must_confirm_duration', (
(NU.from_tokens(1), 1, False, False),
(NU.from_tokens(1), STANDARD_ECONOMICS.min_operator_seconds + 1, False, False),
(NU.from_tokens(15), STANDARD_ECONOMICS.min_operator_seconds + 1, False, False),
(((NU.from_units(MIN_ALLOWED_LOCKED) * 10) + 1), STANDARD_ECONOMICS.min_operator_seconds + 1, True, False),
# (NU.from_units(MIN_ALLOWED_LOCKED) * 10, STANDARD_ECONOMICS.maximum_rewarded_periods + 1, False, True),
# (((NU.from_units(MIN_ALLOWED_LOCKED) * 10) + 1), STANDARD_ECONOMICS.maximum_rewarded_periods + 1, True, True),
))
def test_confirm_large_stake_cli_action(test_emitter,
mock_stdin,
capsys,
value,
duration,
must_confirm_value,
must_confirm_duration):
asked_about_value = lambda output: CONFIRM_LARGE_STAKE_VALUE.format(value=value) in output
lock_days = (duration * STANDARD_ECONOMICS.hours_per_period) // 24
asked_about_duration = lambda output: CONFIRM_LARGE_STAKE_DURATION.format(lock_periods=duration,
lock_days=lock_days) in output
# Positive Cases - either do not need to confirm anything, or say yes
if must_confirm_value:
mock_stdin.line(YES)
if must_confirm_duration:
mock_stdin.line(YES)
result = confirm_large_and_or_long_stake(value=value, lock_periods=duration, economics=STANDARD_ECONOMICS)
assert result
captured = capsys.readouterr()
assert must_confirm_value == asked_about_value(captured.out)
assert must_confirm_duration == asked_about_duration(captured.out)
assert mock_stdin.empty()
if must_confirm_value or must_confirm_duration:
# Negative cases - must confirm something and say no
if must_confirm_value and must_confirm_duration:
# yes to the former but not to the latter
mock_stdin.line(YES)
mock_stdin.line(NO)
else:
# no to whatever one we are asked about
mock_stdin.line(NO)
with pytest.raises(click.Abort):
confirm_large_and_or_long_stake(value=value, lock_periods=duration, economics=STANDARD_ECONOMICS)
captured = capsys.readouterr()
assert must_confirm_value == asked_about_value(captured.out)
assert must_confirm_duration == asked_about_duration(captured.out)
assert mock_stdin.empty()

View File

@ -16,16 +16,17 @@
"""
from unittest.mock import Mock
import click
import pytest
from eth_utils import is_checksum_address
from unittest.mock import Mock
from web3 import Web3
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import NU
from nucypher.cli.actions.select import select_client_account
from nucypher.cli.literature import (
@ -33,8 +34,7 @@ from nucypher.cli.literature import (
GENERIC_SELECT_ACCOUNT,
)
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.types import SubStakeInfo
from tests.constants import MOCK_ETH_PROVIDER_URI, MOCK_SIGNER_URI, NUMBER_OF_ETH_TEST_ACCOUNTS
from tests.constants import MOCK_SIGNER_URI, NUMBER_OF_ETH_TEST_ACCOUNTS, MOCK_ETH_PROVIDER_URI
@pytest.mark.parametrize('selection', range(NUMBER_OF_ETH_TEST_ACCOUNTS))
@ -85,7 +85,6 @@ def test_select_client_account_ambiguous_source(mock_stdin, # used to assert th
select_client_account(emitter=test_emitter, signer=Mock(), signer_uri=MOCK_SIGNER_URI)
@pytest.mark.parametrize('selection', range(NUMBER_OF_ETH_TEST_ACCOUNTS))
def test_select_client_account_valid_sources(mocker,
mock_stdin,
@ -143,8 +142,6 @@ def test_select_client_account_valid_sources(mocker,
(1, True, True, True, []),
(5, True, True, True, []),
(NUMBER_OF_ETH_TEST_ACCOUNTS-1, True, True, True, []),
(4, True, True, True, [SubStakeInfo(1, 2, 3)]),
(7, True, True, True, [SubStakeInfo(1, 2, 3), SubStakeInfo(1, 2, 3)]),
(0, False, True, True, []),
(0, False, False, True, []),
(0, False, False, False, []),

View File

@ -1,52 +0,0 @@
"""
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/>.
"""
from nucypher.blockchain.eth.actors import StakeHolder
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.cli.actions.select import select_client_account_for_staking
from nucypher.config.constants import TEMPORARY_DOMAIN
def test_select_client_account_for_staking_cli_action(test_emitter,
test_registry,
test_registry_source_manager,
mock_stdin,
mock_testerchain,
capsys,
mock_staking_agent):
"""Fine-grained assertions about the return value of interactive client account selection"""
mock_staking_agent.get_all_stakes.return_value = []
selected_index = 0
selected_account = mock_testerchain.client.accounts[selected_index]
stakeholder = StakeHolder(registry=test_registry,
domain=TEMPORARY_DOMAIN,
signer=Web3Signer(mock_testerchain.client))
client_account, staking_address = select_client_account_for_staking(emitter=test_emitter,
stakeholder=stakeholder,
staking_address=selected_account)
assert client_account == staking_address == selected_account
mock_stdin.line(str(selected_index))
client_account, staking_address = select_client_account_for_staking(emitter=test_emitter,
stakeholder=stakeholder,
staking_address=None)
assert client_account == staking_address == selected_account
assert mock_stdin.empty()

View File

@ -1,381 +0,0 @@
"""
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 click
import pytest
from typing import Callable, List
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
from nucypher.blockchain.eth.actors import StakeHolder
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import Stake
from nucypher.cli.actions.select import select_stake
from nucypher.cli.literature import NO_STAKES_FOUND, ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE
from nucypher.cli.painting.staking import STAKER_TABLE_COLUMNS, STAKE_TABLE_COLUMNS
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.types import SubStakeInfo, StakerInfo
def make_sub_stakes(current_period, token_economics, sub_stakes_functions: List[Callable]) -> List[SubStakeInfo]:
sub_stakes = []
for function in sub_stakes_functions:
sub_stakes.extend(function(current_period, token_economics))
return sub_stakes
def empty_sub_stakes(_current_period, _token_economics) -> List[SubStakeInfo]:
return []
def inactive_sub_stakes(current_period, token_economics) -> List[SubStakeInfo]:
stakes = [SubStakeInfo(first_period=1,
last_period=current_period - 2,
locked_value=token_economics.min_authorization),
SubStakeInfo(first_period=current_period - 4,
last_period=current_period - 3,
locked_value=2 * token_economics.min_authorization + 1)]
return stakes
def unlocked_sub_stakes(current_period, token_economics) -> List[SubStakeInfo]:
stakes = [SubStakeInfo(first_period=1,
last_period=current_period - 1,
locked_value=token_economics.min_authorization),
SubStakeInfo(first_period=current_period - 3,
last_period=current_period - 1,
locked_value=2 * token_economics.min_authorization + 1)]
return stakes
def not_editable_sub_stakes(current_period, token_economics) -> List[SubStakeInfo]:
stakes = [SubStakeInfo(first_period=1,
last_period=current_period,
locked_value=token_economics.min_authorization),
SubStakeInfo(first_period=current_period - 3,
last_period=current_period,
locked_value=2 * token_economics.min_authorization + 1)]
return stakes
def non_divisible_sub_stakes(current_period, token_economics) -> List[SubStakeInfo]:
stakes = [SubStakeInfo(first_period=1,
last_period=current_period + 1,
locked_value=token_economics.min_authorization),
SubStakeInfo(first_period=current_period - 3,
last_period=current_period + 2,
locked_value=2 * token_economics.min_authorization - 1),
SubStakeInfo(first_period=current_period - 1,
last_period=current_period + 2,
locked_value=token_economics.min_authorization + 1)]
return stakes
def divisible_sub_stakes(current_period, token_economics) -> List[SubStakeInfo]:
stakes = [SubStakeInfo(first_period=1,
last_period=current_period + 1,
locked_value=2 * token_economics.min_authorization),
SubStakeInfo(first_period=current_period - 3,
last_period=current_period + 2,
locked_value=2 * token_economics.min_authorization + 1)]
return stakes
@pytest.fixture()
def current_period(mock_staking_agent):
current_period = 10
return current_period
@pytest.fixture()
def stakeholder(current_period, mock_staking_agent, test_registry, mock_testerchain):
mock_staking_agent.get_current_period.return_value = current_period
staker_info = StakerInfo(current_committed_period=current_period-1,
next_committed_period=current_period,
value=0,
last_committed_period=0,
lock_restake_until_period=False,
completed_work=0,
worker_start_period=0,
worker=NULL_ADDRESS,
flags=bytes())
mock_staking_agent.get_staker_info.return_value = staker_info
return StakeHolder(registry=test_registry,
domain=TEMPORARY_DOMAIN,
signer=Web3Signer(mock_testerchain.client))
def assert_stake_table_painted(output: str) -> None:
for column_name in (*STAKER_TABLE_COLUMNS, *STAKE_TABLE_COLUMNS):
assert column_name in output
def assert_stake_table_not_painted(output: str) -> None:
for column_name in (*STAKER_TABLE_COLUMNS, *STAKE_TABLE_COLUMNS):
assert column_name not in output
@pytest.mark.skip()
@pytest.mark.parametrize('sub_stakes_functions', [
[empty_sub_stakes],
[inactive_sub_stakes],
[unlocked_sub_stakes],
[not_editable_sub_stakes],
[inactive_sub_stakes, unlocked_sub_stakes, not_editable_sub_stakes]
])
def test_handle_selection_with_with_no_editable_stakes(test_emitter,
stakeholder,
mock_staking_agent,
mock_testerchain,
mock_stdin, # used to assert user hasn't been prompted
capsys,
current_period,
application_economics,
sub_stakes_functions):
mock_stakes = make_sub_stakes(current_period, application_economics, sub_stakes_functions)
mock_staking_agent.get_all_stakes.return_value = mock_stakes
staker = mock_testerchain.unassigned_accounts[0]
stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD)
# Test
with pytest.raises(click.Abort):
select_stake(emitter=test_emitter, staker=stakeholder.staker)
# Examine
captured = capsys.readouterr()
assert NO_STAKES_FOUND in captured.out
assert_stake_table_not_painted(output=captured.out)
assert mock_stdin.empty()
@pytest.mark.skip()
@pytest.mark.parametrize('sub_stakes_functions', [
[non_divisible_sub_stakes],
[divisible_sub_stakes],
[inactive_sub_stakes, non_divisible_sub_stakes],
[unlocked_sub_stakes, non_divisible_sub_stakes],
[not_editable_sub_stakes, non_divisible_sub_stakes],
[unlocked_sub_stakes, divisible_sub_stakes],
[not_editable_sub_stakes, divisible_sub_stakes],
[inactive_sub_stakes, divisible_sub_stakes],
[inactive_sub_stakes, not_editable_sub_stakes, non_divisible_sub_stakes, unlocked_sub_stakes, divisible_sub_stakes]
])
def test_select_editable_stake(test_emitter,
stakeholder,
mock_staking_agent,
mock_testerchain,
mock_stdin, # used to assert user hasn't been prompted
capsys,
current_period,
application_economics,
sub_stakes_functions):
mock_stakes = make_sub_stakes(current_period, application_economics, sub_stakes_functions)
mock_staking_agent.get_all_stakes.return_value = mock_stakes
staker = mock_testerchain.unassigned_accounts[0]
stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD)
selection = len(mock_stakes) - 1
expected_stake = Stake.from_stake_info(stake_info=mock_stakes[selection],
staking_agent=mock_staking_agent, # stakinator
index=selection,
checksum_address=stakeholder.checksum_address,
economics=application_economics)
# User's selection
mock_stdin.line(str(selection))
selected_stake = select_stake(emitter=test_emitter, staker=stakeholder.staker)
# Check stake accuracy
assert isinstance(selected_stake, Stake)
assert selected_stake == expected_stake
# Examine the output
captured = capsys.readouterr()
assert NO_STAKES_FOUND not in captured.out
assert ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE not in captured.out
assert_stake_table_painted(output=captured.out)
assert mock_stdin.empty()
@pytest.mark.skip()
def test_handle_selection_with_no_divisible_stakes(test_emitter,
stakeholder,
mock_staking_agent,
mock_testerchain,
mock_stdin, # used to assert user hasn't been prompted
capsys,
current_period,
application_economics):
# Setup
mock_stakes = make_sub_stakes(current_period, application_economics, [non_divisible_sub_stakes])
mock_staking_agent.get_all_stakes.return_value = mock_stakes
staker = mock_testerchain.unassigned_accounts[0]
stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD)
# FAILURE: Divisible only with no divisible stakes on chain
with pytest.raises(click.Abort):
select_stake(emitter=test_emitter, staker=stakeholder.staker, stakes_status=Stake.Status.DIVISIBLE)
# Divisible warning was displayed, but having
# no divisible stakes cases an expected failure
captured = capsys.readouterr()
assert NO_STAKES_FOUND in captured.out
assert ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE in captured.out
assert_stake_table_not_painted(output=captured.out)
assert mock_stdin.empty()
@pytest.mark.skip()
@pytest.mark.parametrize('sub_stakes_functions', [
[divisible_sub_stakes],
[inactive_sub_stakes, divisible_sub_stakes],
[unlocked_sub_stakes, divisible_sub_stakes],
[not_editable_sub_stakes, divisible_sub_stakes],
[non_divisible_sub_stakes, divisible_sub_stakes],
[inactive_sub_stakes, not_editable_sub_stakes, non_divisible_sub_stakes, unlocked_sub_stakes, divisible_sub_stakes]
])
def test_select_divisible_stake(test_emitter,
stakeholder,
mock_staking_agent,
mock_testerchain,
mock_stdin, # used to assert user hasn't been prompted
capsys,
current_period,
application_economics,
sub_stakes_functions):
# Setup
mock_stakes = make_sub_stakes(current_period, application_economics, sub_stakes_functions)
mock_staking_agent.get_all_stakes.return_value = mock_stakes
staker = mock_testerchain.unassigned_accounts[0]
stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD)
selection = len(mock_stakes) - 1
expected_stake = Stake.from_stake_info(stake_info=mock_stakes[selection],
staking_agent=mock_staking_agent, # stakinator
index=selection,
checksum_address=stakeholder.checksum_address,
economics=application_economics)
# SUCCESS: Display all divisible-only stakes and make a selection
mock_stdin.line(str(selection))
selected_stake = select_stake(emitter=test_emitter, staker=stakeholder.staker, stakes_status=Stake.Status.DIVISIBLE)
assert isinstance(selected_stake, Stake)
assert selected_stake == expected_stake
# Examine the output
captured = capsys.readouterr()
assert NO_STAKES_FOUND not in captured.out
assert ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE in captured.out
assert_stake_table_painted(output=captured.out)
assert mock_stdin.empty()
@pytest.mark.skip()
@pytest.mark.parametrize('sub_stakes_functions', [
[not_editable_sub_stakes],
[inactive_sub_stakes, not_editable_sub_stakes],
[unlocked_sub_stakes, not_editable_sub_stakes],
[divisible_sub_stakes, not_editable_sub_stakes],
[non_divisible_sub_stakes, not_editable_sub_stakes],
[inactive_sub_stakes, non_divisible_sub_stakes, unlocked_sub_stakes, divisible_sub_stakes, not_editable_sub_stakes]
])
def test_select_using_filter_function(test_emitter,
stakeholder,
mock_staking_agent,
mock_testerchain,
mock_stdin, # used to assert user hasn't been prompted
capsys,
current_period,
application_economics,
sub_stakes_functions):
# Setup
mock_stakes = make_sub_stakes(current_period, application_economics, sub_stakes_functions)
mock_staking_agent.get_all_stakes.return_value = mock_stakes
staker = mock_testerchain.unassigned_accounts[0]
stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD)
selection = len(mock_stakes) - 1
expected_stake = Stake.from_stake_info(stake_info=mock_stakes[selection],
staking_agent=mock_staking_agent, # stakinator
index=selection,
checksum_address=stakeholder.checksum_address,
economics=application_economics)
# SUCCESS: Display all editable-only stakes with specified final period
mock_stdin.line(str(selection))
selected_stake = select_stake(emitter=test_emitter,
staker=stakeholder.staker,
stakes_status=Stake.Status.LOCKED,
filter_function=lambda stake: stake.final_locked_period == current_period)
assert isinstance(selected_stake, Stake)
assert selected_stake == expected_stake
# Examine the output
captured = capsys.readouterr()
assert NO_STAKES_FOUND not in captured.out
assert_stake_table_painted(output=captured.out)
assert mock_stdin.empty()
@pytest.mark.skip()
@pytest.mark.parametrize('sub_stakes_functions', [
[inactive_sub_stakes],
[unlocked_sub_stakes],
[divisible_sub_stakes],
[non_divisible_sub_stakes],
[inactive_sub_stakes, non_divisible_sub_stakes, unlocked_sub_stakes, divisible_sub_stakes]
])
def test_no_stakes_with_filter_function(test_emitter,
stakeholder,
mock_staking_agent,
mock_testerchain,
mock_stdin, # used to assert user hasn't been prompted
capsys,
current_period,
application_economics,
sub_stakes_functions):
# Setup
mock_stakes = make_sub_stakes(current_period, application_economics, sub_stakes_functions)
mock_staking_agent.get_all_stakes.return_value = mock_stakes
staker = mock_testerchain.unassigned_accounts[0]
stakeholder.assimilate(staker, password=INSECURE_DEVELOPMENT_PASSWORD)
# FAILURE: no stakes with specified final period
with pytest.raises(click.Abort):
select_stake(emitter=test_emitter,
staker=stakeholder.staker,
stakes_status=Stake.Status.LOCKED,
filter_function=lambda stake: stake.final_locked_period == current_period)
# Divisible warning was displayed, but having
# no divisible stakes causes an expected failure
captured = capsys.readouterr()
assert NO_STAKES_FOUND in captured.out
assert_stake_table_not_painted(output=captured.out)
assert mock_stdin.empty()

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,6 @@ import pytest
from eth_account import Account
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.token import StakeList
from nucypher.cli.main import nucypher_cli
from nucypher.config.characters import UrsulaConfiguration
from nucypher.config.constants import (
@ -107,7 +106,6 @@ def test_ursula_init_with_local_keystore_signer(click_runner,
ursula_config.keystore.unlock(password=password)
# Produce an ursula with a Keystore signer correctly derived from the signer URI, and don't do anything else!
mocker.patch.object(StakeList, 'refresh', autospec=True)
ursula = ursula_config.produce()
ursula.signer.unlock_account(account=worker_account.address, password=password)

View File

@ -22,7 +22,6 @@ import pytest
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, NO_KEYSTORE_ATTACHED
from nucypher_core.umbral import SecretKey
from nucypher.blockchain.eth.actors import StakeHolder
from nucypher.characters.lawful import Alice, Bob, Ursula
from nucypher.cli.actions.configure import destroy_configuration
from nucypher.cli.literature import SUCCESSFUL_DESTRUCTION
@ -30,7 +29,6 @@ from nucypher.config.base import CharacterConfiguration
from nucypher.config.characters import (
AliceConfiguration,
BobConfiguration,
StakeHolderConfiguration,
UrsulaConfiguration
)
from nucypher.config.constants import TEMPORARY_DOMAIN
@ -43,10 +41,6 @@ from tests.constants import MOCK_IP_ADDRESS
configurations = (AliceConfiguration, BobConfiguration, UrsulaConfiguration)
characters = (Alice, Bob, Ursula)
# Auxiliary Support
blockchain_only_configurations = (StakeHolderConfiguration, )
blockchain_only_characters = (StakeHolder, )
# Assemble
characters_and_configurations = list(zip(characters, configurations))
all_characters = tuple(characters, )
@ -118,11 +112,7 @@ def test_default_character_configuration_preservation(configuration_class, teste
expected_filepath.unlink()
assert not expected_filepath.exists()
if configuration_class == StakeHolderConfiguration:
# special case for defaults
character_config = StakeHolderConfiguration(eth_provider_uri=testerchain.eth_provider_uri, domain=network)
elif configuration_class == UrsulaConfiguration:
if configuration_class == UrsulaConfiguration:
# special case for rest_host & dev mode
# use keystore
keystore = Keystore.generate(password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=tmpdir)

View File

@ -14,6 +14,8 @@ 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/>.
"""
from pathlib import Path
import pytest
@ -23,13 +25,12 @@ from nucypher.blockchain.economics import EconomicsFactory
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
ContractAgency,
NucypherTokenAgent,
StakingEscrowAgent, PREApplicationAgent
PREApplicationAgent
)
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.config.characters import StakeHolderConfiguration, UrsulaConfiguration
from nucypher.config.characters import UrsulaConfiguration
from tests.constants import (
KEYFILE_NAME_TEMPLATE,
MOCK_KEYSTORE_PATH,
@ -56,24 +57,6 @@ def mock_contract_agency(monkeypatch, module_mocker, application_economics):
mock_agency.reset()
@pytest.fixture(scope='function', autouse=True)
def mock_token_agent(mock_testerchain, application_economics, mock_contract_agency):
mock_agent = mock_contract_agency.get_agent(NucypherTokenAgent)
yield mock_agent
mock_agent.reset()
@pytest.fixture(scope='function', autouse=True)
def mock_staking_agent(mock_testerchain, application_economics, mock_contract_agency, mocker):
mock_agent = mock_contract_agency.get_agent(StakingEscrowAgent)
# Handle the special case of commit_to_next_period, which returns a txhash due to the fire_and_forget option
mock_agent.commit_to_next_period = mocker.Mock(return_value=MockContractAgent.FAKE_TX_HASH)
yield mock_agent
mock_agent.reset()
@pytest.fixture(scope='function', autouse=True)
def mock_application_agent(mock_testerchain, application_economics, mock_contract_agency, mocker):
mock_agent = mock_contract_agency.get_agent(PREApplicationAgent)
@ -85,7 +68,6 @@ def mock_application_agent(mock_testerchain, application_economics, mock_contrac
mock_agent.reset()
@pytest.fixture(scope='function', autouse=True)
def mock_adjudicator_agent(mock_testerchain, application_economics, mock_contract_agency):
mock_agent = mock_contract_agency.get_agent(AdjudicatorAgent)
yield mock_agent
@ -168,14 +150,14 @@ def mock_account(mock_accounts):
@pytest.fixture(scope='module')
def worker_account(mock_accounts, mock_testerchain):
def operator_account(mock_accounts, mock_testerchain):
account = list(mock_accounts.values())[0]
return account
@pytest.fixture(scope='module')
def operator_address(worker_account):
address = worker_account.address
def operator_address(operator_account):
address = operator_account.address
return address
@ -207,16 +189,6 @@ def patch_keystore(mock_accounts, monkeypatch, mocker):
monkeypatch.delattr(KeystoreSigner, '_KeystoreSigner__read_keystore')
@pytest.fixture(scope='function')
def patch_stakeholder_configuration(mock_accounts, monkeypatch):
def mock_read_configuration_file(filepath: Path) -> dict:
return dict()
monkeypatch.setattr(StakeHolderConfiguration, '_read_configuration_file', mock_read_configuration_file)
yield
monkeypatch.delattr(StakeHolderConfiguration, '_read_configuration_file')
@pytest.fixture(scope='function')
def mock_keystore(mocker):
mocker.patch.object(KeystoreSigner, '_KeystoreSigner__read_keystore')

View File

@ -19,39 +19,34 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import json
import io
import json
import os
import re
import time
from pathlib import Path
from unittest.mock import Mock
import tabulate
import time
import tabulate
from nucypher_core.umbral import SecretKey, Signer
from twisted.logger import ILogObserver, globalLogPublisher, jsonFileLogObserver
from web3.contract import Contract
from zope.interface import provider
from nucypher_core.umbral import SecretKey, Signer
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.economics import Economics
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
NucypherTokenAgent,
PolicyManagerAgent,
StakingEscrowAgent
)
from nucypher.blockchain.eth.constants import NUCYPHER_CONTRACT_NAMES, NULL_ADDRESS, POLICY_ID_LENGTH
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.crypto.signing import SignatureStamp
from nucypher.exceptions import DevelopmentInstallationRequired
from nucypher.policy.policies import Policy
from nucypher.utilities.logging import Logger
from tests.utils.blockchain import TesterBlockchain
ALGORITHM_SHA256 = 1
TOKEN_ECONOMICS = Economics()
MIN_ALLOWED_LOCKED = TOKEN_ECONOMICS.min_authorization

View File

@ -16,25 +16,25 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import maya
import os
from typing import List, Tuple, Union, Optional
import maya
from constant_sorrow.constants import INIT
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_canonical_address
from hexbytes import HexBytes
from typing import List, Tuple, Union, Optional
from web3 import Web3
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.economics import Economics, Economics
from nucypher.blockchain.economics import Economics
from nucypher.blockchain.eth.actors import ContractAdministrator
from nucypher.blockchain.eth.deployers import StakingEscrowDeployer
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.sol.compile.constants import TEST_SOLIDITY_SOURCE_ROOT, SOLIDITY_SOURCE_ROOT
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
from nucypher.blockchain.eth.token import NU
from nucypher.blockchain.eth.utils import epoch_to_period
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.powers import TransactingPower
from nucypher.utilities.gas_strategies import EXPECTED_CONFIRMATION_TIME_IN_SECONDS
from nucypher.utilities.logging import Logger
@ -42,11 +42,10 @@ from tests.constants import (
DEVELOPMENT_ETH_AIRDROP_AMOUNT,
INSECURE_DEVELOPMENT_PASSWORD,
NUMBER_OF_ETH_TEST_ACCOUNTS,
NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS,
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS,
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS,
PYEVM_DEV_URI
)
from constant_sorrow.constants import INIT
def token_airdrop(token_agent, amount: NU, transacting_power: TransactingPower, addresses: List[str]):
@ -96,12 +95,12 @@ class TesterBlockchain(BlockchainDeployerInterface):
_ETHERBASE = 0
_ALICE = 1
_BOB = 2
_FIRST_STAKER = 5
_FIRST_URSULA = _FIRST_STAKER + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS
_FIRST_STAKING_PROVIDER = 5
_FIRST_URSULA = _FIRST_STAKING_PROVIDER + NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS
# Internal
__STAKERS_RANGE = range(NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS)
__WORKERS_RANGE = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
__STAKING_PROVIDERS_RANGE = range(NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS)
__OPERATORS_RANGE = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
__ACCOUNT_CACHE = list()
# Defaults
@ -237,13 +236,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
gas_limit = None # TODO: Gas management - #842
for deployer_class in admin.primary_deployer_classes:
if deployer_class is StakingEscrowDeployer:
admin.deploy_contract(contract_name=deployer_class.contract_name,
gas_limit=gas_limit,
deployment_mode=INIT)
else:
admin.deploy_contract(contract_name=deployer_class.contract_name, gas_limit=gas_limit)
admin.deploy_contract(contract_name=StakingEscrowDeployer.contract_name, gas_limit=gas_limit)
admin.deploy_contract(contract_name=deployer_class.contract_name, gas_limit=gas_limit)
return testerchain, registry
@property
@ -259,22 +252,22 @@ class TesterBlockchain(BlockchainDeployerInterface):
return self.client.accounts[self._BOB]
def ursula_account(self, index):
if index not in self.__WORKERS_RANGE:
if index not in self.__OPERATORS_RANGE:
raise ValueError(f"Ursula index must be lower than {NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS}")
return self.client.accounts[index + self._FIRST_URSULA]
def stake_provider_account(self, index):
if index not in self.__STAKERS_RANGE:
raise ValueError(f"Stake provider index must be lower than {NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS}")
return self.client.accounts[index + self._FIRST_STAKER]
if index not in self.__STAKING_PROVIDERS_RANGE:
raise ValueError(f"Stake provider index must be lower than {NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS}")
return self.client.accounts[index + self._FIRST_STAKING_PROVIDER]
@property
def ursulas_accounts(self):
return list(self.ursula_account(i) for i in self.__WORKERS_RANGE)
return list(self.ursula_account(i) for i in self.__OPERATORS_RANGE)
@property
def stake_providers_accounts(self):
return list(self.stake_provider_account(i) for i in self.__STAKERS_RANGE)
return list(self.stake_provider_account(i) for i in self.__STAKING_PROVIDERS_RANGE)
@property
def unassigned_accounts(self):

View File

@ -21,11 +21,7 @@ from typing import Iterable, List, Optional, Set
from cryptography.x509 import Certificate
from nucypher_core.umbral import SecretKey, Signer, generate_kfrags
from nucypher.blockchain.eth.actors import Staker
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.characters.lawful import Bob
from nucypher.characters.lawful import Ursula
from nucypher.config.characters import UrsulaConfiguration
from tests.constants import NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
@ -84,8 +80,8 @@ def make_federated_ursulas(ursula_config: UrsulaConfiguration,
def make_decentralized_ursulas(ursula_config: UrsulaConfiguration,
stakers_addresses: Iterable[str],
workers_addresses: Iterable[str],
staking_provider_addresses: Iterable[str],
operator_addresses: Iterable[str],
commit_now=True,
**ursula_overrides) -> List[Ursula]:
@ -94,11 +90,11 @@ def make_decentralized_ursulas(ursula_config: UrsulaConfiguration,
else:
starting_port = max(MOCK_KNOWN_URSULAS_CACHE.keys()) + 1
stakers_and_workers = zip(stakers_addresses, workers_addresses)
providers_and_operators = zip(staking_provider_addresses, operator_addresses)
ursulas = list()
for port, (staker_address, operator_address) in enumerate(stakers_and_workers, start=starting_port):
ursula = ursula_config.produce(checksum_address=staker_address,
for port, (staking_provider_address, operator_address) in enumerate(providers_and_operators, start=starting_port):
ursula = ursula_config.produce(checksum_address=staking_provider_address,
operator_address=operator_address,
db_filepath=MOCK_DB,
rest_port=port + 100,
@ -117,20 +113,20 @@ def make_decentralized_ursulas(ursula_config: UrsulaConfiguration,
return ursulas
def make_ursula_for_staker(staker: Staker,
operator_address: str,
blockchain: BlockchainInterface,
ursula_config: UrsulaConfiguration,
ursulas_to_learn_about: Optional[List[Ursula]] = None,
**ursula_overrides) -> Ursula:
def make_ursula_for_staking_provider(staking_provider,
operator_address: str,
blockchain: BlockchainInterface,
ursula_config: UrsulaConfiguration,
ursulas_to_learn_about: Optional[List[Ursula]] = None,
**ursula_overrides) -> Ursula:
# Assign worker to this staker
staker.bond_worker(operator_address=operator_address)
# Assign worker to this staking provider
staking_provider.bond_worker(operator_address=operator_address)
worker = make_decentralized_ursulas(ursula_config=ursula_config,
blockchain=blockchain,
stakers_addresses=[staker.checksum_address],
workers_addresses=[operator_address],
staking_provider_addresses=[staking_provider.checksum_address],
operator_addresses=[operator_address],
**ursula_overrides).pop()
for ursula_to_learn_about in (ursulas_to_learn_about or []):