mirror of https://github.com/nucypher/nucypher.git
commit
9aead5f553
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
|
@ -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
|
@ -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}')
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)))
|
|
@ -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}")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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, []),
|
||||
|
|
|
@ -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()
|
|
@ -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
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 []):
|
||||
|
|
Loading…
Reference in New Issue