mirror of https://github.com/nucypher/nucypher.git
commit
ee4b183950
|
@ -21,6 +21,8 @@ from typing import Tuple
|
|||
|
||||
from math import log
|
||||
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent, StakingEscrowAgent, AdjudicatorAgent
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
|
||||
|
||||
|
@ -28,6 +30,153 @@ LOG2 = Decimal(log(2))
|
|||
|
||||
|
||||
class TokenEconomics:
|
||||
"""
|
||||
Parameters to use in token and escrow blockchain deployments
|
||||
from high-level human-understandable parameters.
|
||||
|
||||
--------------------------
|
||||
|
||||
Formula for staking in one period:
|
||||
(totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2
|
||||
|
||||
K2 - Staking coefficient
|
||||
K1 - Locked periods coefficient
|
||||
|
||||
if allLockedPeriods > maximum_rewarded_periods then allLockedPeriods = maximum_rewarded_periods
|
||||
kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2
|
||||
|
||||
"""
|
||||
|
||||
# Token Denomination
|
||||
__token_decimals = 18
|
||||
nunits_per_token = 10 ** __token_decimals # Smallest unit designation
|
||||
|
||||
# Period Definition
|
||||
__default_hours_per_period = 24
|
||||
|
||||
# Time Constraints
|
||||
__default_minimum_worker_periods = 2
|
||||
__default_minimum_locked_periods = 30 # 720 Hours minimum
|
||||
|
||||
# Value Constraints
|
||||
__default_minimum_allowed_locked = NU(15_000, 'NU').to_nunits()
|
||||
__default_maximum_allowed_locked = NU(4_000_000, 'NU').to_nunits()
|
||||
|
||||
# Slashing parameters
|
||||
HASH_ALGORITHM_KECCAK256 = 0
|
||||
HASH_ALGORITHM_SHA256 = 1
|
||||
HASH_ALGORITHM_RIPEMD160 = 2
|
||||
|
||||
__default_hash_algorithm = HASH_ALGORITHM_SHA256
|
||||
__default_base_penalty = 100
|
||||
__default_penalty_history_coefficient = 10
|
||||
__default_percentage_penalty_coefficient = 8
|
||||
__default_reward_coefficient = 2
|
||||
|
||||
def __init__(self,
|
||||
initial_supply: int,
|
||||
total_supply: int,
|
||||
staking_coefficient: int,
|
||||
locked_periods_coefficient: int,
|
||||
maximum_rewarded_periods: int,
|
||||
hours_per_period: int = __default_hours_per_period,
|
||||
minimum_locked_periods: int = __default_minimum_locked_periods,
|
||||
minimum_allowed_locked: int = __default_minimum_allowed_locked,
|
||||
maximum_allowed_locked: int = __default_maximum_allowed_locked,
|
||||
minimum_worker_periods: int = __default_minimum_worker_periods,
|
||||
|
||||
hash_algorithm: int = __default_hash_algorithm,
|
||||
base_penalty: int = __default_base_penalty,
|
||||
penalty_history_coefficient: int = __default_penalty_history_coefficient,
|
||||
percentage_penalty_coefficient: int = __default_percentage_penalty_coefficient,
|
||||
reward_coefficient: int = __default_reward_coefficient
|
||||
):
|
||||
"""
|
||||
:param initial_supply: Tokens at t=0
|
||||
:param total_supply: Tokens at t=8
|
||||
:param staking_coefficient: K2
|
||||
:param locked_periods_coefficient: K1
|
||||
:param maximum_rewarded_periods: Max periods that will be additionally rewarded
|
||||
:param hours_per_period: Hours in single period
|
||||
:param minimum_locked_periods: Min amount of periods during which tokens can be locked
|
||||
:param minimum_allowed_locked: Min amount of tokens that can be locked
|
||||
:param maximum_allowed_locked: Max amount of tokens that can be locked
|
||||
:param minimum_worker_periods: Min amount of periods while a worker can't be changed
|
||||
|
||||
:param hash_algorithm: Hashing algorithm
|
||||
:param base_penalty: Base for the penalty calculation
|
||||
:param penalty_history_coefficient: Coefficient for calculating the penalty depending on the history
|
||||
:param percentage_penalty_coefficient: Coefficient for calculating the percentage penalty
|
||||
:param reward_coefficient: Coefficient for calculating the reward
|
||||
"""
|
||||
|
||||
self.initial_supply = initial_supply
|
||||
# Remaining / Reward Supply - Escrow Parameter
|
||||
self.reward_supply = total_supply - initial_supply
|
||||
self.total_supply = total_supply
|
||||
self.staking_coefficient = staking_coefficient
|
||||
self.locked_periods_coefficient = locked_periods_coefficient
|
||||
self.maximum_rewarded_periods = maximum_rewarded_periods
|
||||
self.hours_per_period = hours_per_period
|
||||
self.minimum_locked_periods = minimum_locked_periods
|
||||
self.minimum_allowed_locked = minimum_allowed_locked
|
||||
self.maximum_allowed_locked = maximum_allowed_locked
|
||||
self.minimum_worker_periods = minimum_worker_periods
|
||||
self.seconds_per_period = hours_per_period * 60 * 60 # Seconds in single period
|
||||
|
||||
self.hash_algorithm = hash_algorithm
|
||||
self.base_penalty = base_penalty
|
||||
self.penalty_history_coefficient = penalty_history_coefficient
|
||||
self.percentage_penalty_coefficient = percentage_penalty_coefficient
|
||||
self.reward_coefficient = reward_coefficient
|
||||
|
||||
@property
|
||||
def erc20_initial_supply(self) -> int:
|
||||
return int(self.initial_supply)
|
||||
|
||||
@property
|
||||
def erc20_reward_supply(self) -> int:
|
||||
return int(self.reward_supply)
|
||||
|
||||
@property
|
||||
def erc20_total_supply(self) -> int:
|
||||
return int(self.total_supply)
|
||||
|
||||
@property
|
||||
def staking_deployment_parameters(self) -> Tuple[int, ...]:
|
||||
"""Cast coefficient attributes to uint256 compatible type for solidity+EVM"""
|
||||
deploy_parameters = (
|
||||
|
||||
# Period
|
||||
self.hours_per_period, # Hours in single period
|
||||
|
||||
# Coefficients
|
||||
self.staking_coefficient, # Staking coefficient (k2)
|
||||
self.locked_periods_coefficient, # Locked periods coefficient (k1)
|
||||
self.maximum_rewarded_periods, # Max periods that will be additionally rewarded (awarded_periods)
|
||||
|
||||
# Constraints
|
||||
self.minimum_locked_periods, # Min amount of periods during which tokens can be locked
|
||||
self.minimum_allowed_locked, # Min amount of tokens that can be locked
|
||||
self.maximum_allowed_locked, # Max amount of tokens that can be locked
|
||||
self.minimum_worker_periods # Min amount of periods while a worker can't be changed
|
||||
)
|
||||
return tuple(map(int, deploy_parameters))
|
||||
|
||||
@property
|
||||
def slashing_deployment_parameters(self) -> Tuple[int, ...]:
|
||||
"""Cast coefficient attributes to uint256 compatible type for solidity+EVM"""
|
||||
deployment_parameters = [
|
||||
self.hash_algorithm,
|
||||
self.base_penalty,
|
||||
self.penalty_history_coefficient,
|
||||
self.percentage_penalty_coefficient,
|
||||
self.reward_coefficient
|
||||
]
|
||||
return tuple(map(int, deployment_parameters))
|
||||
|
||||
|
||||
class StandardTokenEconomics(TokenEconomics):
|
||||
"""
|
||||
Calculate parameters to use in token and escrow blockchain deployments
|
||||
from high-level human-understandable parameters.
|
||||
|
@ -40,7 +189,7 @@ class TokenEconomics:
|
|||
K2 - Staking coefficient
|
||||
K1 - Locked periods coefficient
|
||||
|
||||
if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods
|
||||
if allLockedPeriods > maximum_rewarded_periods then allLockedPeriods = maximum_rewarded_periods
|
||||
kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2
|
||||
|
||||
...but also...
|
||||
|
@ -60,22 +209,6 @@ class TokenEconomics:
|
|||
# Decimal
|
||||
_precision = 28
|
||||
|
||||
# Token Denomination
|
||||
__token_decimals = 18
|
||||
nunits_per_token = 10 ** __token_decimals # Smallest unit designation
|
||||
|
||||
# Period Definition
|
||||
hours_per_period = 24 # Hours in single period
|
||||
seconds_per_period = hours_per_period * 60 * 60 # Seconds in single period
|
||||
|
||||
# Time Constraints
|
||||
minimum_worker_periods = 2
|
||||
minimum_locked_periods = 30 # 720 Hours minimum
|
||||
|
||||
# Value Constraints
|
||||
minimum_allowed_locked = NU(15_000, 'NU').to_nunits()
|
||||
maximum_allowed_locked = NU(4_000_000, 'NU').to_nunits()
|
||||
|
||||
# Supply
|
||||
__default_initial_supply = NU(int(1_000_000_000), 'NU').to_nunits()
|
||||
__default_initial_inflation = 1
|
||||
|
@ -88,7 +221,8 @@ class TokenEconomics:
|
|||
initial_inflation: int = __default_initial_inflation,
|
||||
halving_delay: int = __default_token_halving,
|
||||
reward_saturation: int = __default_reward_saturation,
|
||||
small_stake_multiplier: Decimal = __default_small_stake_multiplier
|
||||
small_stake_multiplier: Decimal = __default_small_stake_multiplier,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
:param initial_supply: Tokens at t=0
|
||||
|
@ -110,9 +244,6 @@ class TokenEconomics:
|
|||
# ERC20 Token parameter (See Equation 4 in Mining paper)
|
||||
total_supply = initial_supply * (1 + initial_inflation * halving_delay / LOG2)
|
||||
|
||||
# Remaining / Reward Supply - Escrow Parameter
|
||||
reward_supply = total_supply - initial_supply
|
||||
|
||||
# k2 - Escrow parameter
|
||||
staking_coefficient = 365 ** 2 * reward_saturation * halving_delay / LOG2 / (1 - small_stake_multiplier)
|
||||
|
||||
|
@ -120,78 +251,82 @@ class TokenEconomics:
|
|||
locked_periods_coefficient = 365 * reward_saturation * small_stake_multiplier / (1 - small_stake_multiplier)
|
||||
|
||||
# Awarded periods- Escrow parameter
|
||||
maximum_locked_periods = reward_saturation * 365
|
||||
maximum_rewarded_periods = reward_saturation * 365
|
||||
|
||||
# Injected
|
||||
self.initial_supply = initial_supply
|
||||
self.initial_inflation = initial_inflation
|
||||
self.token_halving = halving_delay
|
||||
self.token_saturation = reward_saturation
|
||||
self.small_stake_multiplier = small_stake_multiplier
|
||||
|
||||
# Calculated
|
||||
self.__total_supply = total_supply
|
||||
self.reward_supply = reward_supply
|
||||
self.staking_coefficient = staking_coefficient
|
||||
self.locked_periods_coefficient = locked_periods_coefficient
|
||||
self.maximum_locked_periods = maximum_locked_periods
|
||||
|
||||
@property
|
||||
def erc20_initial_supply(self) -> int:
|
||||
return int(self.initial_supply)
|
||||
|
||||
@property
|
||||
def erc20_reward_supply(self) -> int:
|
||||
return int(self.reward_supply)
|
||||
|
||||
@property
|
||||
def erc20_total_supply(self) -> int:
|
||||
return int(self.__total_supply)
|
||||
|
||||
@property
|
||||
def staking_deployment_parameters(self) -> Tuple[int, ...]:
|
||||
"""Cast coefficient attributes to uint256 compatible type for solidity+EVM"""
|
||||
deploy_parameters = (
|
||||
|
||||
# Period
|
||||
self.hours_per_period, # Hours in single period
|
||||
|
||||
# Coefficients
|
||||
self.staking_coefficient, # Staking coefficient (k2)
|
||||
self.locked_periods_coefficient, # Locked periods coefficient (k1)
|
||||
self.maximum_locked_periods, # Max periods that will be additionally rewarded (awarded_periods)
|
||||
|
||||
# Constraints
|
||||
self.minimum_locked_periods, # Min amount of periods during which tokens can be locked
|
||||
self.minimum_allowed_locked, # Min amount of tokens that can be locked
|
||||
self.maximum_allowed_locked, # Max amount of tokens that can be locked
|
||||
self.minimum_worker_periods # Min amount of periods while a worker can't be changed
|
||||
super(StandardTokenEconomics, self).__init__(
|
||||
initial_supply,
|
||||
total_supply,
|
||||
staking_coefficient,
|
||||
locked_periods_coefficient,
|
||||
maximum_rewarded_periods,
|
||||
**kwargs
|
||||
)
|
||||
return tuple(map(int, deploy_parameters))
|
||||
|
||||
def token_supply_at_period(self, period: int) -> int:
|
||||
if period < 0:
|
||||
raise ValueError("Period must be a positive integer")
|
||||
|
||||
with localcontext() as ctx:
|
||||
ctx.prec = self._precision
|
||||
|
||||
# Eq. 3 of the mining paper
|
||||
# https://github.com/nucypher/mining-paper/blob/master/mining-paper.pdf
|
||||
t = Decimal(period)
|
||||
S_0 = self.erc20_initial_supply
|
||||
i_0 = 1
|
||||
I_0 = i_0 * S_0 # in 1/years
|
||||
T_half = self.token_halving # in years
|
||||
T_half_in_days = T_half * 365
|
||||
|
||||
S_t = S_0 + I_0 * T_half * (1 - 2**(-t / T_half_in_days)) / LOG2
|
||||
return int(S_t)
|
||||
|
||||
def cumulative_rewards_at_period(self, period: int) -> int:
|
||||
return self.token_supply_at_period(period) - self.erc20_initial_supply
|
||||
|
||||
def rewards_during_period(self, period: int) -> int:
|
||||
return self.cumulative_rewards_at_period(period) - self.cumulative_rewards_at_period(period-1)
|
||||
|
||||
|
||||
class SlashingEconomics:
|
||||
class TokenEconomicsFactory:
|
||||
# TODO: Enforce singleton
|
||||
|
||||
HASH_ALGORITHM_KECCAK256 = 0
|
||||
HASH_ALGORITHM_SHA256 = 1
|
||||
HASH_ALGORITHM_RIPEMD160 = 2
|
||||
__economics = dict()
|
||||
|
||||
hash_algorithm = HASH_ALGORITHM_SHA256
|
||||
base_penalty = 100
|
||||
penalty_history_coefficient = 10
|
||||
percentage_penalty_coefficient = 8
|
||||
reward_coefficient = 2
|
||||
@classmethod
|
||||
def get_economics(cls, registry: BaseContractRegistry) -> TokenEconomics:
|
||||
registry_id = registry.id
|
||||
try:
|
||||
return cls.__economics[registry_id]
|
||||
except KeyError:
|
||||
economics = TokenEconomicsFactory.retrieve_from_blockchain(registry=registry)
|
||||
cls.__economics[registry_id] = economics
|
||||
return economics
|
||||
|
||||
@property
|
||||
def deployment_parameters(self) -> Tuple[int, ...]:
|
||||
"""Cast coefficient attributes to uint256 compatible type for solidity+EVM"""
|
||||
@staticmethod
|
||||
def retrieve_from_blockchain(registry: BaseContractRegistry) -> TokenEconomics:
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=registry)
|
||||
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
|
||||
adjudicator_agent = ContractAgency.get_agent(AdjudicatorAgent, registry=registry)
|
||||
|
||||
deployment_parameters = [
|
||||
self.hash_algorithm,
|
||||
self.base_penalty,
|
||||
self.penalty_history_coefficient,
|
||||
self.percentage_penalty_coefficient,
|
||||
self.reward_coefficient
|
||||
]
|
||||
total_supply = token_agent.contract.functions.totalSupply().call()
|
||||
reward_supply = staking_agent.contract.functions.getReservedReward().call()
|
||||
# it's not real initial_supply value because used current reward instead of initial
|
||||
initial_supply = total_supply - reward_supply
|
||||
|
||||
return tuple(map(int, deployment_parameters))
|
||||
staking_parameters = list(staking_agent.staking_parameters())
|
||||
seconds_per_period = staking_parameters.pop(0)
|
||||
staking_parameters.insert(3, seconds_per_period // 60 // 60) # hours_per_period
|
||||
slashing_parameters = adjudicator_agent.slashing_parameters()
|
||||
economics_parameters = (initial_supply,
|
||||
total_supply,
|
||||
*staking_parameters,
|
||||
*slashing_parameters)
|
||||
economics = TokenEconomics(*economics_parameters)
|
||||
return economics
|
||||
|
|
|
@ -33,7 +33,7 @@ from eth_tester.exceptions import TransactionFailed
|
|||
from eth_utils import keccak
|
||||
from twisted.logger import Logger
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics, TokenEconomicsFactory
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
NucypherTokenAgent,
|
||||
StakingEscrowAgent,
|
||||
|
@ -157,7 +157,8 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
def __init__(self,
|
||||
registry: BaseContractRegistry,
|
||||
deployer_address: str = None,
|
||||
client_password: str = None):
|
||||
client_password: str = None,
|
||||
economics: TokenEconomics = None):
|
||||
"""
|
||||
Note: super() is not called here to avoid setting the token agent.
|
||||
TODO: Review this logic ^^ "bare mode".
|
||||
|
@ -166,6 +167,7 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
|
||||
self.deployer_address = deployer_address
|
||||
self.checksum_address = self.deployer_address
|
||||
self.economics = economics or StandardTokenEconomics()
|
||||
|
||||
self.registry = registry
|
||||
self.user_escrow_deployers = dict()
|
||||
|
@ -202,11 +204,17 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
contract_name: str,
|
||||
gas_limit: int = None,
|
||||
plaintext_secret: str = None,
|
||||
progress=None
|
||||
progress=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> Tuple[dict, ContractDeployer]:
|
||||
|
||||
Deployer = self.__get_deployer(contract_name=contract_name)
|
||||
deployer = Deployer(registry=self.registry, deployer_address=self.deployer_address)
|
||||
deployer = Deployer(registry=self.registry,
|
||||
deployer_address=self.deployer_address,
|
||||
economics=self.economics,
|
||||
*args,
|
||||
**kwargs)
|
||||
if Deployer._upgradeable:
|
||||
if not plaintext_secret:
|
||||
raise ValueError("Upgrade plaintext_secret must be passed to deploy an upgradeable contract.")
|
||||
|
@ -395,10 +403,7 @@ class Staker(NucypherTokenActor):
|
|||
class InsufficientTokens(StakerError):
|
||||
pass
|
||||
|
||||
def __init__(self,
|
||||
is_me: bool,
|
||||
economics: TokenEconomics = None,
|
||||
*args, **kwargs) -> None:
|
||||
def __init__(self, is_me: bool, *args, **kwargs) -> None:
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
self.log = Logger("staker")
|
||||
|
@ -409,7 +414,7 @@ class Staker(NucypherTokenActor):
|
|||
# Blockchain
|
||||
self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=self.registry)
|
||||
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
|
||||
self.economics = economics or TokenEconomics()
|
||||
self.economics = TokenEconomicsFactory.get_economics(registry=self.registry)
|
||||
self.stakes = StakeList(registry=self.registry, checksum_address=self.checksum_address)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
|
@ -651,7 +656,6 @@ class BlockchainPolicyAuthor(NucypherTokenActor):
|
|||
|
||||
def __init__(self,
|
||||
checksum_address: str,
|
||||
economics: TokenEconomics = None,
|
||||
rate: int = None,
|
||||
duration_periods: int = None,
|
||||
first_period_reward: int = None,
|
||||
|
@ -668,7 +672,7 @@ class BlockchainPolicyAuthor(NucypherTokenActor):
|
|||
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
|
||||
self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=self.registry)
|
||||
|
||||
self.economics = economics or TokenEconomics()
|
||||
self.economics = TokenEconomicsFactory.get_economics(registry=self.registry)
|
||||
self.rate = rate
|
||||
self.duration_periods = duration_periods
|
||||
self.first_period_reward = first_period_reward
|
||||
|
@ -700,7 +704,8 @@ class BlockchainPolicyAuthor(NucypherTokenActor):
|
|||
|
||||
# Calculate duration in periods and expiration datetime
|
||||
if expiration:
|
||||
duration_periods = calculate_period_duration(future_time=expiration)
|
||||
duration_periods = calculate_period_duration(future_time=expiration,
|
||||
seconds_per_period=self.economics.seconds_per_period)
|
||||
else:
|
||||
duration_periods = duration_periods or self.duration_periods
|
||||
expiration = datetime_at_period(self.staking_agent.get_current_period() + duration_periods)
|
||||
|
|
|
@ -621,3 +621,17 @@ class AdjudicatorAgent(EthereumContractAgent):
|
|||
def penalty_history(self, staker_address: str) -> int:
|
||||
return self.contract.functions.penaltyHistory(staker_address).call()
|
||||
|
||||
def slashing_parameters(self) -> Tuple:
|
||||
parameter_signatures = (
|
||||
'hashAlgorithm', # Hashing algorithm
|
||||
'basePenalty', # Base for the penalty calculation
|
||||
'penaltyHistoryCoefficient', # Coefficient for calculating the penalty depending on the history
|
||||
'percentagePenaltyCoefficient', # Coefficient for calculating the percentage penalty
|
||||
'rewardCoefficient', # Coefficient for calculating the reward
|
||||
)
|
||||
|
||||
def _call_function_by_name(name: str):
|
||||
return getattr(self.contract.functions, name)().call()
|
||||
|
||||
staking_parameters = tuple(map(_call_function_by_name, parameter_signatures))
|
||||
return staking_parameters
|
||||
|
|
|
@ -22,7 +22,7 @@ from constant_sorrow.constants import CONTRACT_NOT_DEPLOYED, NO_DEPLOYER_CONFIGU
|
|||
from eth_utils import is_checksum_address
|
||||
from web3.contract import Contract
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics, SlashingEconomics
|
||||
from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
EthereumContractAgent,
|
||||
StakingEscrowAgent,
|
||||
|
@ -67,10 +67,6 @@ class ContractDeployer:
|
|||
if not isinstance(self.blockchain, BlockchainDeployerInterface):
|
||||
raise ValueError("No deployer interface connection available.")
|
||||
|
||||
if not economics:
|
||||
economics = TokenEconomics()
|
||||
self.__economics = economics
|
||||
|
||||
#
|
||||
# Defaults
|
||||
#
|
||||
|
@ -80,6 +76,7 @@ class ContractDeployer:
|
|||
self.__proxy_contract = NotImplemented
|
||||
self.__deployer_address = deployer_address
|
||||
self.__ready_to_deploy = False
|
||||
self.__economics = economics or StandardTokenEconomics()
|
||||
|
||||
@property
|
||||
def economics(self) -> TokenEconomics:
|
||||
|
@ -272,11 +269,8 @@ class StakingEscrowDeployer(ContractDeployer):
|
|||
_upgradeable = True
|
||||
_proxy_deployer = DispatcherDeployer
|
||||
|
||||
def __init__(self, economics: TokenEconomics = None, *args, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not economics:
|
||||
economics = TokenEconomics()
|
||||
self.__economics = economics
|
||||
self.__dispatcher_contract = None
|
||||
|
||||
token_contract_name = NucypherTokenDeployer.contract_name
|
||||
|
@ -289,7 +283,7 @@ class StakingEscrowDeployer(ContractDeployer):
|
|||
raise RuntimeError("PolicyManager contract is not initialized.")
|
||||
|
||||
def _deploy_essential(self, gas_limit: int = None):
|
||||
escrow_constructor_args = (self.token_contract.address, *self.__economics.staking_deployment_parameters)
|
||||
escrow_constructor_args = (self.token_contract.address, *self.economics.staking_deployment_parameters)
|
||||
the_escrow_contract, deploy_receipt = self.blockchain.deploy_contract(
|
||||
self.deployer_address,
|
||||
self.registry,
|
||||
|
@ -352,7 +346,7 @@ class StakingEscrowDeployer(ContractDeployer):
|
|||
|
||||
# 3 - Transfer the reward supply tokens to StakingEscrow #
|
||||
reward_function = self.token_contract.functions.transfer(the_escrow_contract.address,
|
||||
self.__economics.erc20_reward_supply)
|
||||
self.economics.erc20_reward_supply)
|
||||
|
||||
# TODO: Confirmations / Successful Transaction Indicator / Events ??
|
||||
reward_receipt = self.blockchain.send_transaction(contract_function=reward_function,
|
||||
|
@ -430,7 +424,6 @@ class StakingEscrowDeployer(ContractDeployer):
|
|||
return rollback_receipt
|
||||
|
||||
|
||||
|
||||
class PolicyManagerDeployer(ContractDeployer):
|
||||
"""
|
||||
Depends on StakingEscrow and NucypherTokenAgent
|
||||
|
@ -446,11 +439,6 @@ class PolicyManagerDeployer(ContractDeployer):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
token_contract_name = NucypherTokenDeployer.contract_name
|
||||
self.token_contract = self.blockchain.get_contract_by_name(registry=self.registry,
|
||||
name=token_contract_name)
|
||||
|
||||
proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name
|
||||
staking_contract_name = StakingEscrowDeployer.contract_name
|
||||
self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry,
|
||||
|
@ -812,10 +800,8 @@ class AdjudicatorDeployer(ContractDeployer):
|
|||
_upgradeable = True
|
||||
_proxy_deployer = DispatcherDeployer
|
||||
|
||||
def __init__(self, economics: SlashingEconomics = None, *args, **kwargs):
|
||||
if not economics:
|
||||
economics = SlashingEconomics()
|
||||
super().__init__(*args, economics=economics, **kwargs)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
staking_contract_name = StakingEscrowDeployer.contract_name
|
||||
proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name
|
||||
self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry,
|
||||
|
@ -824,7 +810,7 @@ class AdjudicatorDeployer(ContractDeployer):
|
|||
|
||||
def _deploy_essential(self, gas_limit: int = None):
|
||||
constructor_args = (self.staking_contract.address,
|
||||
*self.economics.deployment_parameters)
|
||||
*self.economics.slashing_deployment_parameters)
|
||||
adjudicator_contract, deploy_receipt = self.blockchain.deploy_contract(self.deployer_address,
|
||||
self.registry,
|
||||
self.contract_name,
|
||||
|
@ -840,8 +826,8 @@ class AdjudicatorDeployer(ContractDeployer):
|
|||
progress.update(1)
|
||||
|
||||
proxy_deployer = self._proxy_deployer(registry=self.registry,
|
||||
target_contract=adjudicator_contract,
|
||||
deployer_address=self.deployer_address)
|
||||
target_contract=adjudicator_contract,
|
||||
deployer_address=self.deployer_address)
|
||||
|
||||
proxy_deploy_receipts = proxy_deployer.deploy(secret_hash=secret_hash, gas_limit=gas_limit)
|
||||
proxy_deploy_receipt = proxy_deploy_receipts[proxy_deployer.deployment_steps[0]]
|
||||
|
@ -888,9 +874,9 @@ class AdjudicatorDeployer(ContractDeployer):
|
|||
use_proxy_address=False)
|
||||
|
||||
proxy_deployer = self._proxy_deployer(registry=self.registry,
|
||||
target_contract=existing_bare_contract,
|
||||
deployer_address=self.deployer_address,
|
||||
bare=True)
|
||||
target_contract=existing_bare_contract,
|
||||
deployer_address=self.deployer_address,
|
||||
bare=True)
|
||||
|
||||
adjudicator_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit)
|
||||
|
||||
|
|
|
@ -121,21 +121,15 @@ contract Issuer is Upgradeable {
|
|||
Math.min(currentSupply1, currentSupply2) :
|
||||
Math.max(currentSupply1, currentSupply2);
|
||||
|
||||
//totalSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2) -
|
||||
//currentSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
|
||||
//(totalSupply - currentSupply) * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
|
||||
uint256 allLockedPeriods = uint256(_allLockedPeriods <= rewardedPeriods ?
|
||||
_allLockedPeriods : rewardedPeriods)
|
||||
.add(lockedPeriodsCoefficient);
|
||||
uint256 denominator = _totalLockedValue.mul(miningCoefficient);
|
||||
amount =
|
||||
totalSupply
|
||||
.mul(_lockedValue)
|
||||
.mul(allLockedPeriods)
|
||||
.div(denominator).sub(
|
||||
currentSupply
|
||||
.mul(_lockedValue)
|
||||
.mul(allLockedPeriods)
|
||||
.div(denominator));
|
||||
amount = totalSupply.sub(currentSupply)
|
||||
.mul(_lockedValue)
|
||||
.mul(allLockedPeriods)
|
||||
.div(denominator);
|
||||
// rounding the last reward
|
||||
if (amount == 0) {
|
||||
amount = 1;
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
"""
|
||||
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 _pydecimal import Decimal
|
||||
from collections import UserList
|
||||
from typing import Union, Tuple, List
|
||||
|
@ -151,7 +168,7 @@ class Stake:
|
|||
first_locked_period: int,
|
||||
final_locked_period: int,
|
||||
index: int,
|
||||
economics=None,
|
||||
economics,
|
||||
validate_now: bool = True):
|
||||
|
||||
self.log = Logger(f'stake-{checksum_address}-{index}')
|
||||
|
@ -174,19 +191,20 @@ class Stake:
|
|||
# and no confirmation can be performed in this period for this stake.
|
||||
self.final_locked_period = final_locked_period
|
||||
|
||||
# Time
|
||||
self.start_datetime = datetime_at_period(period=first_locked_period)
|
||||
self.unlock_datetime = datetime_at_period(period=final_locked_period + 1)
|
||||
|
||||
# Blockchain
|
||||
self.staking_agent = staking_agent
|
||||
|
||||
# Economics
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
self.economics = economics or TokenEconomics()
|
||||
self.economics = economics
|
||||
self.minimum_nu = NU(int(self.economics.minimum_allowed_locked), 'NuNit')
|
||||
self.maximum_nu = NU(int(self.economics.maximum_allowed_locked), 'NuNit')
|
||||
|
||||
# Time
|
||||
self.start_datetime = datetime_at_period(period=first_locked_period,
|
||||
seconds_per_period=self.economics.seconds_per_period)
|
||||
self.unlock_datetime = datetime_at_period(period=final_locked_period + 1,
|
||||
seconds_per_period=self.economics.seconds_per_period)
|
||||
|
||||
if validate_now:
|
||||
self.validate_duration()
|
||||
|
||||
|
@ -219,6 +237,7 @@ class Stake:
|
|||
checksum_address: str,
|
||||
index: int,
|
||||
stake_info: Tuple[int, int, int],
|
||||
economics,
|
||||
*args, **kwargs
|
||||
) -> 'Stake':
|
||||
|
||||
|
@ -230,6 +249,7 @@ class Stake:
|
|||
first_locked_period=first_locked_period,
|
||||
final_locked_period=final_locked_period,
|
||||
value=NU(value, 'NuNit'),
|
||||
economics=economics,
|
||||
*args, **kwargs)
|
||||
|
||||
instance.worker_address = instance.staking_agent.get_worker_from_staker(staker_address=checksum_address)
|
||||
|
@ -378,7 +398,8 @@ class Stake:
|
|||
first_locked_period=self.first_locked_period,
|
||||
final_locked_period=self.final_locked_period,
|
||||
value=remaining_stake_value,
|
||||
staking_agent=self.staking_agent)
|
||||
staking_agent=self.staking_agent,
|
||||
economics=self.economics)
|
||||
|
||||
# New Derived Stake
|
||||
end_period = self.final_locked_period + additional_periods
|
||||
|
@ -387,7 +408,8 @@ class Stake:
|
|||
final_locked_period=end_period,
|
||||
value=target_value,
|
||||
index=NEW_STAKE,
|
||||
staking_agent=self.staking_agent)
|
||||
staking_agent=self.staking_agent,
|
||||
economics=self.economics)
|
||||
|
||||
#
|
||||
# Validate
|
||||
|
@ -425,7 +447,8 @@ class Stake:
|
|||
final_locked_period=final_locked_period,
|
||||
value=amount,
|
||||
index=NEW_STAKE,
|
||||
staking_agent=staker.staking_agent)
|
||||
staking_agent=staker.staking_agent,
|
||||
economics=staker.economics)
|
||||
|
||||
# Validate
|
||||
stake.validate_value()
|
||||
|
@ -550,6 +573,8 @@ class StakeList(UserList):
|
|||
super().__init__(*args, **kwargs)
|
||||
self.log = Logger('stake-tracker')
|
||||
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
|
||||
from nucypher.blockchain.economics import TokenEconomicsFactory
|
||||
self.economics = TokenEconomicsFactory.get_economics(registry=registry)
|
||||
|
||||
self.__terminal_period = NOT_STAKING
|
||||
|
||||
|
@ -590,7 +615,8 @@ class StakeList(UserList):
|
|||
onchain_stake = Stake.from_stake_info(checksum_address=self.checksum_address,
|
||||
stake_info=stake_info,
|
||||
staking_agent=self.staking_agent,
|
||||
index=onchain_index)
|
||||
index=onchain_index,
|
||||
economics=self.economics)
|
||||
|
||||
# rack the latest terminal period
|
||||
if onchain_stake.final_locked_period > terminal_period:
|
||||
|
|
|
@ -20,23 +20,22 @@ from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
|
|||
from eth_utils import is_address, to_checksum_address, is_hex
|
||||
|
||||
|
||||
def epoch_to_period(epoch: int) -> int:
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
period = epoch // int(TokenEconomics.seconds_per_period)
|
||||
def epoch_to_period(epoch: int, seconds_per_period: int) -> int:
|
||||
period = epoch // seconds_per_period
|
||||
return period
|
||||
|
||||
|
||||
def datetime_to_period(datetime: maya.MayaDT) -> int:
|
||||
def datetime_to_period(datetime: maya.MayaDT, seconds_per_period: int) -> int:
|
||||
"""Converts a MayaDT instance to a period number."""
|
||||
future_period = epoch_to_period(epoch=datetime.epoch)
|
||||
future_period = epoch_to_period(epoch=datetime.epoch, seconds_per_period=seconds_per_period)
|
||||
return int(future_period)
|
||||
|
||||
|
||||
def datetime_at_period(period: int) -> maya.MayaDT:
|
||||
def datetime_at_period(period: int, seconds_per_period: int) -> maya.MayaDT:
|
||||
"""Returns the datetime object at a given period, future, or past."""
|
||||
|
||||
now = maya.now()
|
||||
current_period = datetime_to_period(datetime=now)
|
||||
current_period = datetime_to_period(datetime=now, seconds_per_period=seconds_per_period)
|
||||
delta_periods = period - current_period
|
||||
|
||||
# +
|
||||
|
@ -50,10 +49,10 @@ def datetime_at_period(period: int) -> maya.MayaDT:
|
|||
return target_period
|
||||
|
||||
|
||||
def calculate_period_duration(future_time: maya.MayaDT) -> int:
|
||||
def calculate_period_duration(future_time: maya.MayaDT, seconds_per_period: int) -> int:
|
||||
"""Takes a future MayaDT instance and calculates the duration from now, returning in periods"""
|
||||
future_period = datetime_to_period(datetime=future_time)
|
||||
current_period = datetime_to_period(datetime=maya.now())
|
||||
future_period = datetime_to_period(datetime=future_time, seconds_per_period=seconds_per_period)
|
||||
current_period = datetime_to_period(datetime=maya.now(), seconds_per_period=seconds_per_period)
|
||||
periods = future_period - current_period
|
||||
return periods
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ from twisted.logger import Logger
|
|||
|
||||
from hendrix.deploy.base import HendrixDeploy
|
||||
from hendrix.experience import hey_joe
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.economics import TokenEconomicsFactory
|
||||
from nucypher.blockchain.eth.actors import NucypherTokenActor
|
||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, ContractAgency
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterface
|
||||
|
@ -164,7 +164,6 @@ class Felix(Character, NucypherTokenActor):
|
|||
rest_port: int,
|
||||
client_password: str = None,
|
||||
crash_on_error: bool = False,
|
||||
economics: TokenEconomics = None,
|
||||
distribute_ether: bool = True,
|
||||
registry: BaseContractRegistry = None,
|
||||
*args, **kwargs):
|
||||
|
@ -205,12 +204,9 @@ class Felix(Character, NucypherTokenActor):
|
|||
self._distribution_task.clock = self._CLOCK
|
||||
self.start_time = NOT_RUNNING
|
||||
|
||||
if not economics:
|
||||
economics = TokenEconomics()
|
||||
self.economics = economics
|
||||
|
||||
self.MAXIMUM_DISBURSEMENT = economics.maximum_allowed_locked
|
||||
self.INITIAL_DISBURSEMENT = economics.minimum_allowed_locked * 3
|
||||
self.economics = TokenEconomicsFactory.get_economics(registry=registry)
|
||||
self.MAXIMUM_DISBURSEMENT = self.economics.maximum_allowed_locked
|
||||
self.INITIAL_DISBURSEMENT = self.economics.minimum_allowed_locked * 3
|
||||
|
||||
# Optionally send ether with each token transaction
|
||||
self.distribute_ether = distribute_ether
|
||||
|
|
|
@ -42,7 +42,6 @@ from umbral.pre import UmbralCorrectnessError
|
|||
from umbral.signing import Signature
|
||||
|
||||
import nucypher
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.eth.actors import BlockchainPolicyAuthor, Worker, Staker
|
||||
from nucypher.blockchain.eth.agents import StakingEscrowAgent, NucypherTokenAgent, ContractAgency
|
||||
from nucypher.blockchain.eth.decorators import validate_checksum_address
|
||||
|
|
|
@ -21,19 +21,14 @@ from web3 import Web3
|
|||
|
||||
from nucypher.characters.lawful import StakeHolder
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.blockchain.eth.utils import datetime_at_period
|
||||
from nucypher.characters.banners import NU_BANNER
|
||||
from nucypher.cli import painting, actions
|
||||
from nucypher.cli.actions import confirm_staged_stake, get_client_password, select_stake, select_client_account
|
||||
from nucypher.cli.config import nucypher_click_config
|
||||
from nucypher.cli.painting import paint_receipt_summary
|
||||
from nucypher.cli.types import (
|
||||
EIP55_CHECKSUM_ADDRESS,
|
||||
STAKE_VALUE,
|
||||
STAKE_DURATION,
|
||||
STAKE_EXTENSION,
|
||||
EXISTING_READABLE_FILE
|
||||
)
|
||||
from nucypher.config.characters import StakeHolderConfiguration
|
||||
|
@ -142,6 +137,12 @@ def stake(click_config,
|
|||
|
||||
STAKEHOLDER = stakeholder_config.produce(initial_address=staking_address)
|
||||
blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri) # Eager connection
|
||||
economics = STAKEHOLDER.economics
|
||||
|
||||
min_locked = economics.minimum_allowed_locked
|
||||
stake_value_range = click.FloatRange(min=NU.from_nunits(min_locked).to_tokens(), clamp=False)
|
||||
stake_duration_range = click.IntRange(min=economics.minimum_locked_periods, clamp=False)
|
||||
stake_extension_range = click.IntRange(min=1, max=economics.maximum_allowed_locked, clamp=False)
|
||||
|
||||
#
|
||||
# Eager Actions
|
||||
|
@ -179,10 +180,10 @@ def stake(click_config,
|
|||
|
||||
# TODO: Double-check dates
|
||||
current_period = STAKEHOLDER.staking_agent.get_current_period()
|
||||
bonded_date = datetime_at_period(period=current_period)
|
||||
bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period)
|
||||
min_worker_periods = STAKEHOLDER.staking_agent.staking_parameters()[7]
|
||||
release_period = current_period + min_worker_periods
|
||||
release_date = datetime_at_period(period=release_period)
|
||||
release_date = datetime_at_period(period=release_period, seconds_per_period=economics.seconds_per_period)
|
||||
|
||||
emitter.echo(f"\nWorker {worker_address} successfully bonded to staker {staking_address}", color='green')
|
||||
paint_receipt_summary(emitter=emitter,
|
||||
|
@ -217,7 +218,7 @@ def stake(click_config,
|
|||
|
||||
# TODO: Double-check dates
|
||||
current_period = STAKEHOLDER.staking_agent.get_current_period()
|
||||
bonded_date = datetime_at_period(period=current_period)
|
||||
bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period)
|
||||
|
||||
emitter.echo(f"Successfully detached worker {worker_address} from staker {staking_address}", color='green')
|
||||
paint_receipt_summary(emitter=emitter,
|
||||
|
@ -249,13 +250,14 @@ def stake(click_config,
|
|||
#
|
||||
|
||||
if not value:
|
||||
min_locked = STAKEHOLDER.economics.minimum_allowed_locked
|
||||
value = click.prompt(f"Enter stake value in NU", type=STAKE_VALUE, default=NU.from_nunits(min_locked).to_tokens())
|
||||
value = click.prompt(f"Enter stake value in NU",
|
||||
type=stake_value_range,
|
||||
default=NU.from_nunits(min_locked).to_tokens())
|
||||
value = NU.from_tokens(value)
|
||||
|
||||
if not lock_periods:
|
||||
prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)"
|
||||
lock_periods = click.prompt(prompt, type=STAKE_DURATION)
|
||||
lock_periods = click.prompt(prompt, type=stake_duration_range)
|
||||
|
||||
start_period = STAKEHOLDER.staking_agent.get_current_period()
|
||||
end_period = start_period + lock_periods
|
||||
|
@ -302,12 +304,12 @@ def stake(click_config,
|
|||
# Value
|
||||
if not value:
|
||||
value = click.prompt(f"Enter target value (must be less than or equal to {str(current_stake.value)})",
|
||||
type=STAKE_VALUE)
|
||||
type=stake_value_range)
|
||||
value = NU(value, 'NU')
|
||||
|
||||
# Duration
|
||||
if not lock_periods:
|
||||
extension = click.prompt("Enter number of periods to extend", type=STAKE_EXTENSION)
|
||||
extension = click.prompt("Enter number of periods to extend", type=stake_extension_range)
|
||||
else:
|
||||
extension = lock_periods
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
from ipaddress import ip_address
|
||||
|
||||
import click
|
||||
from eth_utils import is_checksum_address, to_checksum_address
|
||||
from eth_utils import to_checksum_address
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.economics import StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
|
||||
|
||||
|
@ -43,14 +43,8 @@ class IPv4Address(click.ParamType):
|
|||
return value
|
||||
|
||||
|
||||
token_economics = TokenEconomics()
|
||||
WEI = click.IntRange(min=1, clamp=False) # TODO: Better validation for ether and wei values?
|
||||
|
||||
# Staking
|
||||
STAKE_DURATION = click.IntRange(min=token_economics.minimum_locked_periods, clamp=False)
|
||||
STAKE_EXTENSION = click.IntRange(min=1, max=token_economics.maximum_allowed_locked, clamp=False)
|
||||
STAKE_VALUE = click.FloatRange(min=NU.from_nunits(token_economics.minimum_allowed_locked).to_tokens(), clamp=False)
|
||||
|
||||
# Filesystem
|
||||
EXISTING_WRITABLE_DIRECTORY = click.Path(exists=True, dir_okay=True, file_okay=False, writable=True)
|
||||
EXISTING_READABLE_FILE = click.Path(exists=True, dir_okay=False, file_okay=True, readable=True)
|
||||
|
|
|
@ -24,7 +24,7 @@ from pytest_ethereum.deployer import Deployer
|
|||
from twisted.logger import Logger
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.actors import ContractAdministrator
|
||||
from nucypher.blockchain.eth.agents import EthereumContractAgent
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
|
@ -87,6 +87,8 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
_FIRST_URSULA = _FIRST_STAKER + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS
|
||||
_ursulas_range = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
|
||||
|
||||
_default_token_economics = StandardTokenEconomics()
|
||||
|
||||
def __init__(self,
|
||||
test_accounts=None,
|
||||
poa=True,
|
||||
|
@ -181,8 +183,8 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
raise ValueError("Specify hours, seconds, or periods, not a combination")
|
||||
|
||||
if periods:
|
||||
duration = (TokenEconomics.hours_per_period * periods) * (60 * 60)
|
||||
base = TokenEconomics.hours_per_period * 60 * 60
|
||||
duration = self._default_token_economics.seconds_per_period * periods
|
||||
base = self._default_token_economics.seconds_per_period
|
||||
elif hours:
|
||||
duration = hours * (60*60)
|
||||
base = 60 * 60
|
||||
|
@ -200,11 +202,13 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
|
||||
delta = maya.timedelta(seconds=end_timestamp-now)
|
||||
self.log.info(f"Time traveled {delta} "
|
||||
f"| period {epoch_to_period(epoch=end_timestamp)} "
|
||||
f"| period {epoch_to_period(epoch=end_timestamp, seconds_per_period=self._default_token_economics.seconds_per_period)} "
|
||||
f"| epoch {end_timestamp}")
|
||||
|
||||
@classmethod
|
||||
def bootstrap_network(cls) -> 'TesterBlockchain':
|
||||
def bootstrap_network(cls,
|
||||
economics: TokenEconomics = None
|
||||
) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']:
|
||||
"""For use with metric testing scripts"""
|
||||
|
||||
registry = InMemoryContractRegistry()
|
||||
|
@ -216,7 +220,9 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
testerchain.transacting_power = power
|
||||
|
||||
origin = testerchain.client.etherbase
|
||||
deployer = ContractAdministrator(deployer_address=origin, registry=registry)
|
||||
deployer = ContractAdministrator(deployer_address=origin,
|
||||
registry=registry,
|
||||
economics=economics or cls._default_token_economics)
|
||||
secrets = dict()
|
||||
for deployer_class in deployer.upgradeable_deployer_classes:
|
||||
secrets[deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
|
|
@ -20,32 +20,61 @@ import pytest
|
|||
from eth_tester.exceptions import TransactionFailed
|
||||
from web3.contract import Contract
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
|
||||
SECRET_LENGTH = 32
|
||||
TOTAL_SUPPLY = 2 * 10 ** 40
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def token(testerchain, deploy_contract):
|
||||
# Create an ERC20 token
|
||||
token, _ = deploy_contract('NuCypherToken', 2 * 10 ** 40)
|
||||
token, _ = deploy_contract('NuCypherToken', _totalSupply=TOTAL_SUPPLY)
|
||||
return token
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_issuer(testerchain, token, deploy_contract):
|
||||
economics = TokenEconomics(initial_supply=10 ** 30,
|
||||
total_supply=TOTAL_SUPPLY,
|
||||
staking_coefficient=10 ** 43,
|
||||
locked_periods_coefficient=10 ** 4,
|
||||
maximum_rewarded_periods=10 ** 4,
|
||||
hours_per_period=1)
|
||||
|
||||
def calculate_reward(locked, total_locked, locked_periods):
|
||||
return economics.erc20_reward_supply * locked * \
|
||||
(locked_periods + economics.locked_periods_coefficient) // \
|
||||
(total_locked * economics.staking_coefficient)
|
||||
|
||||
creator = testerchain.client.accounts[0]
|
||||
ursula = testerchain.client.accounts[1]
|
||||
|
||||
# Only token contract is allowed in Issuer constructor
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
deploy_contract('IssuerMock', ursula, 1, 10 ** 43, 10 ** 4, 10 ** 4)
|
||||
deploy_contract(
|
||||
contract_name='IssuerMock',
|
||||
_token=ursula,
|
||||
_hoursPerPeriod=economics.hours_per_period,
|
||||
_miningCoefficient=economics.staking_coefficient,
|
||||
_lockedPeriodsCoefficient=economics.locked_periods_coefficient,
|
||||
_rewardedPeriods=economics.maximum_rewarded_periods
|
||||
)
|
||||
|
||||
# Creator deploys the issuer
|
||||
issuer, _ = deploy_contract('IssuerMock', token.address, 1, 10 ** 43, 10 ** 4, 10 ** 4)
|
||||
issuer, _ = deploy_contract(
|
||||
contract_name='IssuerMock',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=economics.hours_per_period,
|
||||
_miningCoefficient=economics.staking_coefficient,
|
||||
_lockedPeriodsCoefficient=economics.locked_periods_coefficient,
|
||||
_rewardedPeriods=economics.maximum_rewarded_periods
|
||||
)
|
||||
events = issuer.events.Initialized.createFilter(fromBlock='latest')
|
||||
|
||||
# Give staker tokens for reward and initialize contract
|
||||
reserved_reward = 2 * 10 ** 40 - 10 ** 30
|
||||
tx = token.functions.transfer(issuer.address, reserved_reward).transact({'from': creator})
|
||||
tx = token.functions.transfer(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Only owner can initialize
|
||||
|
@ -57,7 +86,7 @@ def test_issuer(testerchain, token, deploy_contract):
|
|||
|
||||
events = events.get_all_entries()
|
||||
assert 1 == len(events)
|
||||
assert reserved_reward == events[0]['args']['reservedReward']
|
||||
assert economics.erc20_reward_supply == events[0]['args']['reservedReward']
|
||||
balance = token.functions.balanceOf(issuer.address).call()
|
||||
|
||||
# Can't initialize second time
|
||||
|
@ -68,26 +97,30 @@ def test_issuer(testerchain, token, deploy_contract):
|
|||
# Check result of minting tokens
|
||||
tx = issuer.functions.testMint(0, 1000, 2000, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 10 == token.functions.balanceOf(ursula).call()
|
||||
assert balance - 10 == token.functions.balanceOf(issuer.address).call()
|
||||
reward = calculate_reward(1000, 2000, 0)
|
||||
assert reward == token.functions.balanceOf(ursula).call()
|
||||
assert balance - reward == token.functions.balanceOf(issuer.address).call()
|
||||
|
||||
# The result must be more because of a different proportion of lockedValue and totalLockedValue
|
||||
tx = issuer.functions.testMint(0, 500, 500, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 30 == token.functions.balanceOf(ursula).call()
|
||||
assert balance - 30 == token.functions.balanceOf(issuer.address).call()
|
||||
reward += calculate_reward(500, 500, 0)
|
||||
assert reward == token.functions.balanceOf(ursula).call()
|
||||
assert balance - reward == token.functions.balanceOf(issuer.address).call()
|
||||
|
||||
# The result must be more because of bigger value of allLockedPeriods
|
||||
tx = issuer.functions.testMint(0, 500, 500, 10 ** 4).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 70 == token.functions.balanceOf(ursula).call()
|
||||
assert balance - 70 == token.functions.balanceOf(issuer.address).call()
|
||||
reward += calculate_reward(500, 500, 10 ** 4)
|
||||
assert reward == token.functions.balanceOf(ursula).call()
|
||||
assert balance - reward == token.functions.balanceOf(issuer.address).call()
|
||||
|
||||
# The result is the same because allLockedPeriods more then specified coefficient _rewardedPeriods
|
||||
tx = issuer.functions.testMint(0, 500, 500, 2 * 10 ** 4).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 110 == token.functions.balanceOf(ursula).call()
|
||||
assert balance - 110 == token.functions.balanceOf(issuer.address).call()
|
||||
reward += calculate_reward(500, 500, 10 ** 4)
|
||||
assert reward == token.functions.balanceOf(ursula).call()
|
||||
assert balance - reward == token.functions.balanceOf(issuer.address).call()
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
|
@ -97,14 +130,28 @@ def test_inflation_rate(testerchain, token, deploy_contract):
|
|||
During one period inflation rate must be the same
|
||||
"""
|
||||
|
||||
economics = TokenEconomics(initial_supply=10 ** 30,
|
||||
total_supply=TOTAL_SUPPLY,
|
||||
staking_coefficient=2 * 10 ** 19,
|
||||
locked_periods_coefficient=1,
|
||||
maximum_rewarded_periods=1,
|
||||
hours_per_period=1)
|
||||
|
||||
creator = testerchain.client.accounts[0]
|
||||
ursula = testerchain.client.accounts[1]
|
||||
|
||||
# Creator deploys the contract
|
||||
issuer, _ = deploy_contract('IssuerMock', token.address, 1, 2 * 10 ** 19, 1, 1)
|
||||
issuer, _ = deploy_contract(
|
||||
contract_name='IssuerMock',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=economics.hours_per_period,
|
||||
_miningCoefficient=economics.staking_coefficient,
|
||||
_lockedPeriodsCoefficient=economics.locked_periods_coefficient,
|
||||
_rewardedPeriods=economics.maximum_rewarded_periods
|
||||
)
|
||||
|
||||
# Give staker tokens for reward and initialize contract
|
||||
tx = token.functions.transfer(issuer.address, 2 * 10 ** 40 - 10 ** 30).transact({'from': creator})
|
||||
tx = token.functions.transfer(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = issuer.functions.initialize().transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -166,11 +213,25 @@ def test_upgrading(testerchain, token, deploy_contract):
|
|||
secret2_hash = testerchain.w3.keccak(secret2)
|
||||
|
||||
# Deploy contract
|
||||
contract_library_v1, _ = deploy_contract('Issuer', token.address, 1, 1, 1, 1)
|
||||
contract_library_v1, _ = deploy_contract(
|
||||
contract_name='Issuer',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=1,
|
||||
_miningCoefficient=1,
|
||||
_lockedPeriodsCoefficient=1,
|
||||
_rewardedPeriods=1
|
||||
)
|
||||
dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address, secret_hash)
|
||||
|
||||
# Deploy second version of the contract
|
||||
contract_library_v2, _ = deploy_contract('IssuerV2Mock', token.address, 2, 2, 2, 2)
|
||||
contract_library_v2, _ = deploy_contract(
|
||||
contract_name='IssuerV2Mock',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=2,
|
||||
_miningCoefficient=2,
|
||||
_lockedPeriodsCoefficient=2,
|
||||
_rewardedPeriods=2
|
||||
)
|
||||
contract = testerchain.client.get_contract(
|
||||
abi=contract_library_v2.abi,
|
||||
address=dispatcher.address,
|
||||
|
@ -208,7 +269,14 @@ def test_upgrading(testerchain, token, deploy_contract):
|
|||
assert 3 == contract.functions.valueToCheck().call()
|
||||
|
||||
# Can't upgrade to the previous version or to the bad version
|
||||
contract_library_bad, _ = deploy_contract('IssuerBad', token.address, 2, 2, 2, 2)
|
||||
contract_library_bad, _ = deploy_contract(
|
||||
contract_name='IssuerBad',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=2,
|
||||
_miningCoefficient=2,
|
||||
_lockedPeriodsCoefficient=2,
|
||||
_rewardedPeriods=2
|
||||
)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = dispatcher.functions.upgrade(contract_library_v1.address, secret2, secret_hash)\
|
||||
.transact({'from': creator})
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
"""
|
||||
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.crypto.powers import TransactingPower
|
||||
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
||||
|
||||
# Experimental max error
|
||||
MAX_ERROR = 0.0004751
|
||||
MAX_PERIODS = 100
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_reward(testerchain, agency, token_economics):
|
||||
testerchain.time_travel(hours=1)
|
||||
token_agent, staking_agent, _policy_agent = agency
|
||||
origin = testerchain.etherbase_account
|
||||
ursula = testerchain.ursula_account(0)
|
||||
|
||||
# Prepare one staker
|
||||
_txhash = token_agent.transfer(amount=token_economics.minimum_allowed_locked,
|
||||
target_address=ursula,
|
||||
sender_address=origin)
|
||||
testerchain.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
account=ursula)
|
||||
testerchain.transacting_power.activate()
|
||||
_txhash = token_agent.approve_transfer(amount=token_economics.minimum_allowed_locked,
|
||||
target_address=staking_agent.contract_address,
|
||||
sender_address=ursula)
|
||||
_txhash = staking_agent.deposit_tokens(amount=token_economics.minimum_allowed_locked,
|
||||
lock_periods=100 * token_economics.maximum_rewarded_periods,
|
||||
sender_address=ursula)
|
||||
_txhash = staking_agent.set_worker(staker_address=ursula, worker_address=ursula)
|
||||
|
||||
# Get a reward for one period
|
||||
_txhash = staking_agent.confirm_activity(worker_address=ursula)
|
||||
testerchain.time_travel(periods=1)
|
||||
_txhash = staking_agent.confirm_activity(worker_address=ursula)
|
||||
assert staking_agent.calculate_staking_reward(staker_address=ursula) == 0
|
||||
testerchain.time_travel(periods=1)
|
||||
_txhash = staking_agent.confirm_activity(worker_address=ursula)
|
||||
|
||||
contract_reward = staking_agent.calculate_staking_reward(staker_address=ursula)
|
||||
calculations_reward = token_economics.cumulative_rewards_at_period(1)
|
||||
error = (contract_reward - calculations_reward) / calculations_reward
|
||||
assert error > 0
|
||||
assert error < MAX_ERROR
|
||||
|
||||
# Get a reward for other periods
|
||||
for i in range(1, MAX_PERIODS):
|
||||
testerchain.time_travel(periods=1)
|
||||
_txhash = staking_agent.confirm_activity(worker_address=ursula)
|
||||
contract_reward = staking_agent.calculate_staking_reward(staker_address=ursula)
|
||||
calculations_reward = token_economics.cumulative_rewards_at_period(i + 1)
|
||||
next_error = (contract_reward - calculations_reward) / calculations_reward
|
||||
assert next_error > 0
|
||||
assert next_error < error
|
||||
error = next_error
|
|
@ -24,10 +24,10 @@ from eth_tester.exceptions import TransactionFailed
|
|||
from eth_utils import to_canonical_address
|
||||
from web3.contract import Contract
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer
|
||||
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.crypto.api import sha256_digest
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
|
||||
|
@ -44,26 +44,34 @@ adjudicator_secret = os.urandom(SECRET_LENGTH)
|
|||
|
||||
|
||||
@pytest.fixture()
|
||||
def token(testerchain, deploy_contract):
|
||||
def token_economics():
|
||||
economics = TokenEconomics(initial_supply=10 ** 9,
|
||||
total_supply=2 * 10 ** 9,
|
||||
staking_coefficient=8 * 10 ** 7,
|
||||
locked_periods_coefficient=4,
|
||||
maximum_rewarded_periods=4,
|
||||
hours_per_period=1,
|
||||
minimum_locked_periods=6,
|
||||
minimum_allowed_locked=100,
|
||||
maximum_allowed_locked=2000,
|
||||
minimum_worker_periods=2,
|
||||
base_penalty=300,
|
||||
percentage_penalty_coefficient=2)
|
||||
return economics
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def token(token_economics, deploy_contract):
|
||||
# Create an ERC20 token
|
||||
contract, _ = deploy_contract('NuCypherToken', _totalSupply=int(NU(2 * 10 ** 9, 'NuNit')))
|
||||
contract, _ = deploy_contract('NuCypherToken', _totalSupply=token_economics.erc20_total_supply)
|
||||
return contract
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def escrow(testerchain, token, deploy_contract):
|
||||
def escrow(testerchain, token, token_economics, deploy_contract):
|
||||
# Creator deploys the escrow
|
||||
contract, _ = deploy_contract(
|
||||
contract_name='StakingEscrow',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=1,
|
||||
_miningCoefficient=8*10**7,
|
||||
_lockedPeriodsCoefficient=4,
|
||||
_rewardedPeriods=4,
|
||||
_minLockedPeriods=6,
|
||||
_minAllowableLockedTokens=100,
|
||||
_maxAllowableLockedTokens=2000,
|
||||
_minWorkerPeriods=2
|
||||
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
|
||||
)
|
||||
|
||||
secret_hash = testerchain.w3.keccak(escrow_secret)
|
||||
|
@ -101,22 +109,17 @@ def policy_manager(testerchain, escrow, deploy_contract):
|
|||
|
||||
|
||||
@pytest.fixture()
|
||||
def adjudicator(testerchain, escrow, slashing_economics, deploy_contract):
|
||||
def adjudicator(testerchain, escrow, token_economics, deploy_contract):
|
||||
escrow, _ = escrow
|
||||
creator = testerchain.client.accounts[0]
|
||||
|
||||
secret_hash = testerchain.w3.keccak(adjudicator_secret)
|
||||
|
||||
deployment_parameters = list(slashing_economics.deployment_parameters)
|
||||
# TODO: For some reason this test used non-standard slashing parameters (#354)
|
||||
deployment_parameters[1] = 300
|
||||
deployment_parameters[3] = 2
|
||||
|
||||
# Creator deploys the contract
|
||||
contract, _ = deploy_contract(
|
||||
'Adjudicator',
|
||||
escrow.address,
|
||||
*deployment_parameters)
|
||||
*token_economics.slashing_deployment_parameters)
|
||||
|
||||
dispatcher, _ = deploy_contract('Dispatcher', contract.address, secret_hash)
|
||||
|
||||
|
@ -243,6 +246,7 @@ def execute_multisig_transaction(testerchain, multisig, accounts, tx):
|
|||
|
||||
@pytest.mark.slow
|
||||
def test_all(testerchain,
|
||||
token_economics,
|
||||
token,
|
||||
escrow,
|
||||
policy_manager,
|
||||
|
@ -250,7 +254,6 @@ def test_all(testerchain,
|
|||
worklock,
|
||||
user_escrow_proxy,
|
||||
multisig,
|
||||
slashing_economics,
|
||||
mock_ursula_reencrypts,
|
||||
deploy_contract):
|
||||
|
||||
|
@ -304,8 +307,7 @@ def test_all(testerchain,
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Initialize escrow
|
||||
reward = 10 ** 9
|
||||
tx = token.functions.transfer(escrow.address, reward).transact({'from': creator})
|
||||
tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.initialize().buildTransaction({'from': multisig.address, 'gasPrice': 0})
|
||||
execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx)
|
||||
|
@ -361,7 +363,7 @@ def test_all(testerchain,
|
|||
tx = worklock.functions.claim().transact({'from': ursula2, 'gas_price': 0})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert worklock.functions.getRemainingWork(ursula2).call() == deposit_rate * deposited_eth
|
||||
assert reward + 1000 == token.functions.balanceOf(escrow.address).call()
|
||||
assert token_economics.erc20_reward_supply + 1000 == token.functions.balanceOf(escrow.address).call()
|
||||
assert 1000 == escrow.functions.getAllTokens(ursula2).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert 1000 == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
|
@ -474,7 +476,7 @@ def test_all(testerchain,
|
|||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert reward + 2000 == token.functions.balanceOf(escrow.address).call()
|
||||
assert token_economics.erc20_reward_supply + 2000 == token.functions.balanceOf(escrow.address).call()
|
||||
assert 9000 == token.functions.balanceOf(ursula1).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula1).call()
|
||||
assert 1000 == escrow.functions.getLockedTokens(ursula1, 1).call()
|
||||
|
@ -494,7 +496,7 @@ def test_all(testerchain,
|
|||
assert 1000 == escrow.functions.getLockedTokens(user_escrow_1.address, 1).call()
|
||||
assert 1000 == escrow.functions.getLockedTokens(user_escrow_1.address, 10).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(user_escrow_1.address, 11).call()
|
||||
assert reward + 3000 == token.functions.balanceOf(escrow.address).call()
|
||||
assert token_economics.erc20_reward_supply + 3000 == token.functions.balanceOf(escrow.address).call()
|
||||
assert 9000 == token.functions.balanceOf(user_escrow_1.address).call()
|
||||
|
||||
# Only user can deposit tokens to the staking escrow
|
||||
|
@ -680,16 +682,7 @@ def test_all(testerchain,
|
|||
policy_manager_v1 = policy_manager.functions.target().call()
|
||||
# Creator deploys the contracts as the second versions
|
||||
escrow_v2, _ = deploy_contract(
|
||||
contract_name='StakingEscrow',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=1,
|
||||
_miningCoefficient=8 * 10 ** 7,
|
||||
_lockedPeriodsCoefficient=4,
|
||||
_rewardedPeriods=4,
|
||||
_minLockedPeriods=6,
|
||||
_minAllowableLockedTokens=100,
|
||||
_maxAllowableLockedTokens=2000,
|
||||
_minWorkerPeriods=2
|
||||
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
|
||||
)
|
||||
policy_manager_v2, _ = deploy_contract('PolicyManager', escrow.address)
|
||||
# Ursula and Alice can't upgrade contracts, only owner can
|
||||
|
@ -839,12 +832,7 @@ def test_all(testerchain,
|
|||
total_lock = escrow.functions.lockedPerPeriod(current_period).call()
|
||||
alice1_balance = token.functions.balanceOf(alice1).call()
|
||||
|
||||
deployment_parameters = list(slashing_economics.deployment_parameters)
|
||||
# TODO: For some reason this test used non-stadard slashing parameters (#354)
|
||||
deployment_parameters[1] = 300
|
||||
deployment_parameters[3] = 2
|
||||
|
||||
algorithm_sha256, base_penalty, *coefficients = deployment_parameters
|
||||
algorithm_sha256, base_penalty, *coefficients = token_economics.slashing_deployment_parameters
|
||||
penalty_history_coefficient, percentage_penalty_coefficient, reward_coefficient = coefficients
|
||||
|
||||
data_hash, slashing_args = generate_args_for_slashing(mock_ursula_reencrypts, ursula1_with_stamp)
|
||||
|
@ -912,7 +900,7 @@ def test_all(testerchain,
|
|||
adjudicator_v2, _ = deploy_contract(
|
||||
'Adjudicator',
|
||||
escrow.address,
|
||||
*slashing_economics.deployment_parameters)
|
||||
*token_economics.slashing_deployment_parameters)
|
||||
adjudicator_secret2 = os.urandom(SECRET_LENGTH)
|
||||
adjudicator_secret2_hash = testerchain.w3.keccak(adjudicator_secret2)
|
||||
# Ursula and Alice can't upgrade library, only owner can
|
||||
|
|
|
@ -36,11 +36,11 @@ def escrow(testerchain, deploy_contract):
|
|||
|
||||
|
||||
@pytest.fixture(params=[False, True])
|
||||
def adjudicator(testerchain, escrow, request, slashing_economics, deploy_contract):
|
||||
def adjudicator(testerchain, escrow, request, token_economics, deploy_contract):
|
||||
contract, _ = deploy_contract(
|
||||
'Adjudicator',
|
||||
escrow.address,
|
||||
*slashing_economics.deployment_parameters)
|
||||
*token_economics.slashing_deployment_parameters)
|
||||
|
||||
if request.param:
|
||||
secret = os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH)
|
||||
|
|
|
@ -40,7 +40,7 @@ secret2 = (654321).to_bytes(32, byteorder='big')
|
|||
def test_evaluate_cfrag(testerchain,
|
||||
escrow,
|
||||
adjudicator,
|
||||
slashing_economics,
|
||||
token_economics,
|
||||
blockchain_ursulas,
|
||||
mock_ursula_reencrypts
|
||||
):
|
||||
|
@ -56,10 +56,10 @@ def test_evaluate_cfrag(testerchain,
|
|||
number_of_evaluations = 0
|
||||
|
||||
def compute_penalty_and_reward(stake: int, penalty_history: int) -> Tuple[int, int]:
|
||||
penalty_ = slashing_economics.base_penalty
|
||||
penalty_ += slashing_economics.penalty_history_coefficient * penalty_history
|
||||
penalty_ = min(penalty_, stake // slashing_economics.percentage_penalty_coefficient)
|
||||
reward_ = penalty_ // slashing_economics.reward_coefficient
|
||||
penalty_ = token_economics.base_penalty
|
||||
penalty_ += token_economics.penalty_history_coefficient * penalty_history
|
||||
penalty_ = min(penalty_, stake // token_economics.percentage_penalty_coefficient)
|
||||
reward_ = penalty_ // token_economics.reward_coefficient
|
||||
return penalty_, reward_
|
||||
|
||||
# Prepare one staker
|
||||
|
@ -206,7 +206,7 @@ def test_evaluate_cfrag(testerchain,
|
|||
previous_penalty = penalty
|
||||
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
|
||||
# Penalty was increased because it's the second violation
|
||||
assert penalty == previous_penalty + slashing_economics.penalty_history_coefficient
|
||||
assert penalty == previous_penalty + token_economics.penalty_history_coefficient
|
||||
worker_stake -= penalty
|
||||
investigator_balance += reward
|
||||
worker_penalty_history += 1
|
||||
|
@ -252,7 +252,7 @@ def test_evaluate_cfrag(testerchain,
|
|||
|
||||
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
|
||||
# Penalty has reached maximum available percentage of value
|
||||
assert penalty == worker_stake // slashing_economics.percentage_penalty_coefficient
|
||||
assert penalty == worker_stake // token_economics.percentage_penalty_coefficient
|
||||
worker_stake -= penalty
|
||||
investigator_balance += reward
|
||||
worker_penalty_history += 1
|
||||
|
|
|
@ -20,6 +20,7 @@ import pytest
|
|||
from web3.contract import Contract
|
||||
from eth_utils import keccak
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
|
||||
VALUE_FIELD = 0
|
||||
|
@ -28,29 +29,33 @@ secret = (123456).to_bytes(32, byteorder='big')
|
|||
|
||||
|
||||
@pytest.fixture()
|
||||
def token(testerchain, deploy_contract):
|
||||
def token_economics():
|
||||
economics = TokenEconomics(initial_supply=10 ** 9,
|
||||
total_supply=2 * 10 ** 9,
|
||||
staking_coefficient=8 * 10 ** 7,
|
||||
locked_periods_coefficient=4,
|
||||
maximum_rewarded_periods=4,
|
||||
hours_per_period=1,
|
||||
minimum_locked_periods=2,
|
||||
minimum_allowed_locked=100,
|
||||
minimum_worker_periods=1)
|
||||
return economics
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def token(deploy_contract, token_economics):
|
||||
# Create an ERC20 token
|
||||
token, _ = deploy_contract('NuCypherToken', _totalSupply=int(NU(2 * 10 ** 9, 'NuNit')))
|
||||
token, _ = deploy_contract('NuCypherToken', _totalSupply=token_economics.erc20_total_supply)
|
||||
return token
|
||||
|
||||
|
||||
@pytest.fixture(params=[False, True])
|
||||
def escrow_contract(testerchain, token, request, deploy_contract):
|
||||
def escrow_contract(testerchain, token, token_economics, request, deploy_contract):
|
||||
def make_escrow(max_allowed_locked_tokens):
|
||||
# Creator deploys the escrow
|
||||
_staking_coefficient = 2 * 10 ** 7
|
||||
contract, _ = deploy_contract(
|
||||
contract_name='StakingEscrow',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=1,
|
||||
_miningCoefficient=4 * _staking_coefficient,
|
||||
_lockedPeriodsCoefficient=4,
|
||||
_rewardedPeriods=4,
|
||||
_minLockedPeriods=2,
|
||||
_minAllowableLockedTokens=100,
|
||||
_maxAllowableLockedTokens=max_allowed_locked_tokens,
|
||||
_minWorkerPeriods=1
|
||||
)
|
||||
deploy_parameters = list(token_economics.staking_deployment_parameters)
|
||||
deploy_parameters[-2] = max_allowed_locked_tokens
|
||||
contract, _ = deploy_contract('StakingEscrow', token.address, *deploy_parameters)
|
||||
|
||||
if request.param:
|
||||
secret_hash = keccak(secret)
|
||||
|
|
|
@ -22,7 +22,8 @@ from web3.contract import Contract
|
|||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_mining(testerchain, token, escrow_contract):
|
||||
def test_mining(testerchain, token, escrow_contract, token_economics):
|
||||
|
||||
escrow = escrow_contract(1500)
|
||||
policy_manager_interface = testerchain.get_contract_factory('PolicyManagerForStakingEscrowMock')
|
||||
policy_manager = testerchain.client.get_contract(
|
||||
|
@ -33,6 +34,13 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
ursula1 = testerchain.client.accounts[1]
|
||||
ursula2 = testerchain.client.accounts[2]
|
||||
|
||||
current_supply = token_economics.erc20_initial_supply
|
||||
|
||||
def calculate_reward(locked, total_locked, locked_periods):
|
||||
return (token_economics.erc20_total_supply - current_supply) * locked * \
|
||||
(locked_periods + token_economics.locked_periods_coefficient) // \
|
||||
(total_locked * token_economics.staking_coefficient)
|
||||
|
||||
staking_log = escrow.events.Mined.createFilter(fromBlock='latest')
|
||||
deposit_log = escrow.events.Deposited.createFilter(fromBlock='latest')
|
||||
lock_log = escrow.events.Locked.createFilter(fromBlock='latest')
|
||||
|
@ -41,7 +49,7 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
withdraw_log = escrow.events.Withdrawn.createFilter(fromBlock='latest')
|
||||
|
||||
# Give Escrow tokens for reward and initialize contract
|
||||
tx = token.functions.transfer(escrow.address, 10 ** 9).transact({'from': creator})
|
||||
tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.initialize().transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -49,7 +57,7 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
# Give Ursula and Ursula(2) some coins
|
||||
tx = token.functions.transfer(ursula1, 10000).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token.functions.transfer(ursula2, 10000).transact({'from': creator})
|
||||
tx = token.functions.transfer(ursula2, 850).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Ursula can't confirm and mint because no locked tokens
|
||||
|
@ -67,13 +75,15 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Ursula and Ursula(2) transfer some tokens to the escrow and lock them
|
||||
tx = escrow.functions.deposit(1000, 2).transact({'from': ursula1})
|
||||
ursula1_stake = 1000
|
||||
ursula2_stake = 500
|
||||
tx = escrow.functions.deposit(ursula1_stake, 2).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.setWorker(ursula1).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.deposit(500, 2).transact({'from': ursula2})
|
||||
tx = escrow.functions.deposit(ursula2_stake, 2).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.setWorker(ursula2).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -129,8 +139,11 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
|
||||
current_period = escrow.functions.getCurrentPeriod().call()
|
||||
# Check result of mining
|
||||
assert 1046 == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert 525 == escrow.functions.getAllTokens(ursula2).call()
|
||||
total_locked = ursula1_stake + ursula2_stake
|
||||
ursula1_reward = calculate_reward(500, total_locked, 1) + calculate_reward(500, total_locked, 2)
|
||||
assert ursula1_stake + ursula1_reward == escrow.functions.getAllTokens(ursula1).call()
|
||||
ursula2_reward = calculate_reward(500, total_locked, 2)
|
||||
assert ursula2_stake + ursula2_reward == escrow.functions.getAllTokens(ursula2).call()
|
||||
# Check that downtime value has not changed
|
||||
assert 1 == escrow.functions.getPastDowntimeLength(ursula1).call()
|
||||
assert 1 == escrow.functions.getPastDowntimeLength(ursula2).call()
|
||||
|
@ -141,11 +154,11 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
assert 2 == len(events)
|
||||
event_args = events[0]['args']
|
||||
assert ursula1 == event_args['staker']
|
||||
assert 46 == event_args['value']
|
||||
assert ursula1_reward == event_args['value']
|
||||
assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
|
||||
event_args = events[1]['args']
|
||||
assert ursula2 == event_args['staker']
|
||||
assert 25 == event_args['value']
|
||||
assert ursula2_reward == event_args['value']
|
||||
assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
|
||||
|
||||
# Check parameters in call of the policy manager mock
|
||||
|
@ -157,14 +170,17 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
|
||||
# Ursula tries to mint again and doesn't receive a reward
|
||||
# There are no more confirmed periods that are ready to mint
|
||||
ursula1_stake += ursula1_reward
|
||||
ursula2_stake += ursula2_reward
|
||||
tx = escrow.functions.mint().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 1046 == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert ursula1_stake == escrow.functions.getAllTokens(ursula1).call()
|
||||
events = staking_log.get_all_entries()
|
||||
assert 2 == len(events)
|
||||
|
||||
# Ursula can't confirm next period because stake is unlocked in current period
|
||||
testerchain.time_travel(hours=1)
|
||||
current_supply += ursula1_reward + ursula2_reward
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -187,14 +203,16 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
# But Ursula(2) can't get reward because she did not confirm activity
|
||||
tx = escrow.functions.mint().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 1152 == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert 525 == escrow.functions.getAllTokens(ursula2).call()
|
||||
ursula1_reward = calculate_reward(500, 1000, 0) + calculate_reward(500, 1000, 1) + calculate_reward(500, 500, 0)
|
||||
assert ursula1_stake + ursula1_reward == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert ursula2_stake == escrow.functions.getAllTokens(ursula2).call()
|
||||
ursula1_stake += ursula1_reward
|
||||
|
||||
events = staking_log.get_all_entries()
|
||||
assert 3 == len(events)
|
||||
event_args = events[2]['args']
|
||||
assert ursula1 == event_args['staker']
|
||||
assert 106 == event_args['value']
|
||||
assert ursula1_reward == event_args['value']
|
||||
assert current_period == event_args['period']
|
||||
|
||||
assert 4 == policy_manager.functions.getPeriodsLength(ursula1).call()
|
||||
|
@ -204,16 +222,19 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
|
||||
# Ursula(2) mints tokens
|
||||
testerchain.time_travel(hours=1)
|
||||
current_supply += ursula1_reward
|
||||
tx = escrow.functions.mint().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 1152 == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert 575 == escrow.functions.getAllTokens(ursula2).call()
|
||||
ursula2_reward = calculate_reward(500, 500, 0)
|
||||
assert ursula1_stake == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert ursula2_stake + ursula2_reward == escrow.functions.getAllTokens(ursula2).call()
|
||||
ursula2_stake += ursula2_reward
|
||||
|
||||
events = staking_log.get_all_entries()
|
||||
assert 4 == len(events)
|
||||
event_args = events[3]['args']
|
||||
assert ursula2 == event_args['staker']
|
||||
assert 50 == event_args['value']
|
||||
assert ursula2_reward == event_args['value']
|
||||
assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
|
||||
|
||||
current_period = escrow.functions.getCurrentPeriod().call() - 1
|
||||
|
@ -231,7 +252,7 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
current_period = escrow.functions.getCurrentPeriod().call()
|
||||
assert current_period - 2 == escrow.functions.getLastActivePeriod(ursula1).call()
|
||||
assert 1152 == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert ursula1_stake == escrow.functions.getAllTokens(ursula1).call()
|
||||
# Ursula still can't confirm activity
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
|
@ -244,6 +265,7 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.lock(500, 2).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
ursula2_stake += 250
|
||||
|
||||
assert 3 == escrow.functions.getPastDowntimeLength(ursula2).call()
|
||||
downtime = escrow.functions.getPastDowntime(ursula2, 2).call()
|
||||
|
@ -252,20 +274,24 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
|
||||
# Ursula(2) mints only one period (by using deposit/approveAndCall function)
|
||||
testerchain.time_travel(hours=5)
|
||||
current_supply += ursula2_reward
|
||||
current_period = escrow.functions.getCurrentPeriod().call()
|
||||
assert current_period - 4 == escrow.functions.getLastActivePeriod(ursula2).call()
|
||||
tx = token.functions.approveAndCall(escrow.address, 100, testerchain.w3.toBytes(2))\
|
||||
.transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
ursula2_stake += 100
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
assert 1152 == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert 1025 == escrow.functions.getAllTokens(ursula2).call()
|
||||
ursula2_reward = calculate_reward(250, 750, 4) + calculate_reward(500, 750, 4)
|
||||
assert ursula1_stake == escrow.functions.getAllTokens(ursula1).call()
|
||||
assert ursula2_stake + ursula2_reward == escrow.functions.getAllTokens(ursula2).call()
|
||||
assert 4 == escrow.functions.getPastDowntimeLength(ursula2).call()
|
||||
downtime = escrow.functions.getPastDowntime(ursula2, 3).call()
|
||||
assert current_period - 3 == downtime[0]
|
||||
assert current_period == downtime[1]
|
||||
ursula2_stake += ursula2_reward
|
||||
|
||||
assert 4 == policy_manager.functions.getPeriodsLength(ursula2).call()
|
||||
assert current_period - 4 == policy_manager.functions.getPeriod(ursula2, 3).call()
|
||||
|
@ -274,7 +300,7 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
assert 5 == len(events)
|
||||
event_args = events[4]['args']
|
||||
assert ursula2 == event_args['staker']
|
||||
assert 100 == event_args['value']
|
||||
assert ursula2_reward == event_args['value']
|
||||
assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
|
||||
|
||||
# Ursula(2) confirms activity for remaining periods
|
||||
|
@ -288,17 +314,18 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
|
||||
# Ursula(2) withdraws all
|
||||
testerchain.time_travel(hours=2)
|
||||
ursula2_stake = escrow.functions.getAllTokens(ursula2).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
tx = escrow.functions.withdraw(1083).transact({'from': ursula2})
|
||||
tx = escrow.functions.withdraw(ursula2_stake).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 0 == escrow.functions.getAllTokens(ursula2).call()
|
||||
assert 10233 == token.functions.balanceOf(ursula2).call()
|
||||
assert ursula2_stake == token.functions.balanceOf(ursula2).call()
|
||||
|
||||
events = withdraw_log.get_all_entries()
|
||||
assert 1 == len(events)
|
||||
event_args = events[0]['args']
|
||||
assert ursula2 == event_args['staker']
|
||||
assert 1083 == event_args['value']
|
||||
assert ursula2_stake == event_args['value']
|
||||
|
||||
assert 4 == len(deposit_log.get_all_entries())
|
||||
assert 6 == len(lock_log.get_all_entries())
|
||||
|
@ -307,7 +334,7 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_slashing(testerchain, token, escrow_contract, deploy_contract):
|
||||
def test_slashing(testerchain, token, escrow_contract, token_economics, deploy_contract):
|
||||
escrow = escrow_contract(1500)
|
||||
adjudicator, _ = deploy_contract(
|
||||
'AdjudicatorForStakingEscrowMock', escrow.address
|
||||
|
@ -321,7 +348,7 @@ def test_slashing(testerchain, token, escrow_contract, deploy_contract):
|
|||
slashing_log = escrow.events.Slashed.createFilter(fromBlock='latest')
|
||||
|
||||
# Give Escrow tokens for reward and initialize contract
|
||||
tx = token.functions.transfer(escrow.address, 10 ** 9).transact({'from': creator})
|
||||
tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.initialize().transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
|
|
@ -32,7 +32,7 @@ secret2 = (654321).to_bytes(32, byteorder='big')
|
|||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_upgrading(testerchain, token, deploy_contract):
|
||||
def test_upgrading(testerchain, token, token_economics, deploy_contract):
|
||||
creator = testerchain.client.accounts[0]
|
||||
staker = testerchain.client.accounts[1]
|
||||
|
||||
|
@ -41,16 +41,7 @@ def test_upgrading(testerchain, token, deploy_contract):
|
|||
|
||||
# Deploy contract
|
||||
contract_library_v1, _ = deploy_contract(
|
||||
contract_name='StakingEscrow',
|
||||
_token=token.address,
|
||||
_hoursPerPeriod=1,
|
||||
_miningCoefficient=8*10**7,
|
||||
_lockedPeriodsCoefficient=4,
|
||||
_rewardedPeriods=4,
|
||||
_minLockedPeriods=2,
|
||||
_minAllowableLockedTokens=100,
|
||||
_maxAllowableLockedTokens=1500,
|
||||
_minWorkerPeriods=1
|
||||
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
|
||||
)
|
||||
dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address, secret_hash)
|
||||
|
||||
|
@ -73,7 +64,7 @@ def test_upgrading(testerchain, token, deploy_contract):
|
|||
abi=contract_library_v2.abi,
|
||||
address=dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
assert 1500 == contract.functions.maxAllowableLockedTokens().call()
|
||||
assert token_economics.maximum_allowed_locked == contract.functions.maxAllowableLockedTokens().call()
|
||||
|
||||
# Can't call `finishUpgrade` and `verifyState` methods outside upgrade lifecycle
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
|
@ -116,7 +107,7 @@ def test_upgrading(testerchain, token, deploy_contract):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
# Check constructor and storage values
|
||||
assert contract_library_v2.address == dispatcher.functions.target().call()
|
||||
assert 1500 == contract.functions.maxAllowableLockedTokens().call()
|
||||
assert token_economics.maximum_allowed_locked == contract.functions.maxAllowableLockedTokens().call()
|
||||
assert policy_manager.address == contract.functions.policyManager().call()
|
||||
assert 2 == contract.functions.valueToCheck().call()
|
||||
# Check new ABI
|
||||
|
@ -248,7 +239,7 @@ def test_re_stake(testerchain, token, escrow_contract):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
tx = token.functions.approve(escrow.address, 10000).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
sub_stake = 1000
|
||||
sub_stake = 100
|
||||
tx = escrow.functions.deposit(sub_stake, 10).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.setWorker(ursula).transact({'from': ursula})
|
||||
|
@ -398,8 +389,8 @@ def test_re_stake(testerchain, token, escrow_contract):
|
|||
|
||||
# To calculate amount of re-stake we can split Ursula1's reward according sub stakes ratio:
|
||||
# first sub stake is 2/3 of entire stake and second sub stake is 1/3
|
||||
re_stake_for_first_sub_stake = ursula_reward * 2 // 3
|
||||
re_stake_for_second_sub_stake = ursula_reward - re_stake_for_first_sub_stake
|
||||
re_stake_for_second_sub_stake = ursula_reward // 3
|
||||
re_stake_for_first_sub_stake = ursula_reward - re_stake_for_second_sub_stake
|
||||
# Check re-stake for Ursula1's sub stakes
|
||||
assert stake + ursula_reward == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert sub_stake_1 + re_stake_for_first_sub_stake == escrow.functions.getSubStakeInfo(ursula, 0).call()[3]
|
||||
|
|
|
@ -83,7 +83,7 @@ def test_rapid_deployment(token_economics, test_registry):
|
|||
beneficiary_address = acct.address
|
||||
amount = random.randint(token_economics.minimum_allowed_locked, token_economics.maximum_allowed_locked)
|
||||
duration = random.randint(token_economics.minimum_locked_periods*ONE_YEAR_IN_SECONDS,
|
||||
(token_economics.maximum_locked_periods*ONE_YEAR_IN_SECONDS)*3)
|
||||
(token_economics.maximum_rewarded_periods*ONE_YEAR_IN_SECONDS)*3)
|
||||
random_allocation = {'beneficiary_address': beneficiary_address, 'amount': amount, 'duration_seconds': duration}
|
||||
allocation_data.append(random_allocation)
|
||||
|
||||
|
|
|
@ -45,8 +45,7 @@ def test_investigator_requests_slashing(testerchain,
|
|||
test_registry,
|
||||
session_agency,
|
||||
mock_ursula_reencrypts,
|
||||
token_economics,
|
||||
slashing_economics):
|
||||
token_economics):
|
||||
testerchain = testerchain
|
||||
|
||||
staker_account = testerchain.staker_account(0)
|
||||
|
@ -109,5 +108,5 @@ def test_investigator_requests_slashing(testerchain,
|
|||
investigator_reward = investigator.token_balance - bobby_old_balance
|
||||
|
||||
assert investigator_reward > 0
|
||||
assert investigator_reward == slashing_economics.base_penalty / slashing_economics.reward_coefficient
|
||||
assert investigator_reward == token_economics.base_penalty / token_economics.reward_coefficient
|
||||
assert staker.locked_tokens(periods=1) < locked_tokens
|
||||
|
|
|
@ -81,7 +81,8 @@ def test_staker_divides_stake(staker, token_economics):
|
|||
value=yet_another_stake_value,
|
||||
checksum_address=staker.checksum_address,
|
||||
index=3,
|
||||
staking_agent=staker.staking_agent)
|
||||
staking_agent=staker.staking_agent,
|
||||
economics=token_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'
|
||||
|
|
|
@ -47,7 +47,6 @@ def test_adjudicator_slashes(agency,
|
|||
testerchain,
|
||||
mock_ursula_reencrypts,
|
||||
token_economics,
|
||||
slashing_economics,
|
||||
test_registry):
|
||||
|
||||
staker_account = testerchain.staker_account(0)
|
||||
|
@ -113,5 +112,5 @@ def test_adjudicator_slashes(agency,
|
|||
investigator_reward = bobby.token_balance - bobby_old_balance
|
||||
|
||||
assert investigator_reward > 0
|
||||
assert investigator_reward == slashing_economics.base_penalty / slashing_economics.reward_coefficient
|
||||
assert investigator_reward == token_economics.base_penalty / token_economics.reward_coefficient
|
||||
assert staker.locked_tokens(periods=1) < locked_tokens
|
||||
|
|
|
@ -30,7 +30,7 @@ from nucypher.blockchain.eth.deployers import (
|
|||
|
||||
@pytest.mark.slow()
|
||||
def test_adjudicator_deployer(testerchain,
|
||||
slashing_economics,
|
||||
token_economics,
|
||||
deployment_progress,
|
||||
test_registry):
|
||||
testerchain = testerchain
|
||||
|
@ -61,11 +61,11 @@ def test_adjudicator_deployer(testerchain,
|
|||
# Check default Adjudicator deployment parameters
|
||||
assert staking_escrow_deployer.deployer_address != staking_agent.contract_address
|
||||
assert adjudicator_agent.staking_escrow_contract == staking_agent.contract_address
|
||||
assert adjudicator_agent.hash_algorithm == slashing_economics.hash_algorithm
|
||||
assert adjudicator_agent.base_penalty == slashing_economics.base_penalty
|
||||
assert adjudicator_agent.penalty_history_coefficient == slashing_economics.penalty_history_coefficient
|
||||
assert adjudicator_agent.percentage_penalty_coefficient == slashing_economics.percentage_penalty_coefficient
|
||||
assert adjudicator_agent.reward_coefficient == slashing_economics.reward_coefficient
|
||||
assert adjudicator_agent.hash_algorithm == token_economics.hash_algorithm
|
||||
assert adjudicator_agent.base_penalty == token_economics.base_penalty
|
||||
assert adjudicator_agent.penalty_history_coefficient == token_economics.penalty_history_coefficient
|
||||
assert adjudicator_agent.percentage_penalty_coefficient == token_economics.percentage_penalty_coefficient
|
||||
assert adjudicator_agent.reward_coefficient == token_economics.reward_coefficient
|
||||
|
||||
# Retrieve the AdjudicatorAgent singleton
|
||||
some_policy_agent = AdjudicatorAgent(registry=test_registry)
|
||||
|
|
|
@ -74,7 +74,7 @@ def test_deploy_and_allocate(session_agency, user_escrow_proxy, token_economics,
|
|||
for address, deployer in deployments.items():
|
||||
assert deployer.deployer_address == origin
|
||||
|
||||
deposit_receipt = deployer.initial_deposit(value=allocation, duration_seconds=token_economics.maximum_locked_periods)
|
||||
deposit_receipt = deployer.initial_deposit(value=allocation, duration_seconds=token_economics.maximum_rewarded_periods)
|
||||
deposit_receipts.append(deposit_receipt)
|
||||
|
||||
beneficiary = random.choice(testerchain.unassigned_accounts)
|
||||
|
|
|
@ -19,7 +19,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
from decimal import Decimal, localcontext
|
||||
from math import log
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics, LOG2
|
||||
from nucypher.blockchain.economics import LOG2, StandardTokenEconomics
|
||||
|
||||
|
||||
def test_rough_economics():
|
||||
|
@ -37,11 +37,11 @@ def test_rough_economics():
|
|||
where allLockedPeriods == min(T, T1)
|
||||
"""
|
||||
|
||||
e = TokenEconomics(initial_supply=int(1e9),
|
||||
initial_inflation=1,
|
||||
halving_delay=2,
|
||||
reward_saturation=1,
|
||||
small_stake_multiplier=Decimal(0.5))
|
||||
e = StandardTokenEconomics(initial_supply=int(1e9),
|
||||
initial_inflation=1,
|
||||
halving_delay=2,
|
||||
reward_saturation=1,
|
||||
small_stake_multiplier=Decimal(0.5))
|
||||
|
||||
assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper
|
||||
|
||||
|
@ -112,7 +112,7 @@ def test_exact_economics():
|
|||
|
||||
# Use same precision as economics class
|
||||
with localcontext() as ctx:
|
||||
ctx.prec = TokenEconomics._precision
|
||||
ctx.prec = StandardTokenEconomics._precision
|
||||
|
||||
# Sanity check expected testing outputs
|
||||
assert Decimal(expected_total_supply) / expected_initial_supply == expected_supply_ratio
|
||||
|
@ -134,10 +134,10 @@ def test_exact_economics():
|
|||
#
|
||||
|
||||
# Check creation
|
||||
e = TokenEconomics()
|
||||
e = StandardTokenEconomics()
|
||||
|
||||
with localcontext() as ctx:
|
||||
ctx.prec = TokenEconomics._precision
|
||||
ctx.prec = StandardTokenEconomics._precision
|
||||
|
||||
# Check that total_supply calculated correctly
|
||||
assert Decimal(e.erc20_total_supply) / e.initial_supply == expected_supply_ratio
|
||||
|
@ -159,14 +159,35 @@ def test_exact_economics():
|
|||
assert e.erc20_initial_supply == expected_initial_supply
|
||||
assert e.erc20_reward_supply == expected_reward_supply
|
||||
|
||||
# Additional checks on supply
|
||||
assert e.token_supply_at_period(period=0) == expected_initial_supply
|
||||
assert e.cumulative_rewards_at_period(0) == 0
|
||||
|
||||
# Last NuNit is mined after 184 years (or 67000 periods).
|
||||
# That's the year 2203, if token is launched in 2019.
|
||||
# 23rd century schizoid man!
|
||||
assert expected_total_supply == e.token_supply_at_period(period=67000)
|
||||
|
||||
# After 1 year:
|
||||
assert 1_845_111_188_584347879497984668 == e.token_supply_at_period(period=365)
|
||||
assert 845_111_188_584347879497984668 == e.cumulative_rewards_at_period(365)
|
||||
assert e.erc20_initial_supply + e.cumulative_rewards_at_period(365) == e.token_supply_at_period(period=365)
|
||||
|
||||
# Checking that the supply function is monotonic
|
||||
todays_supply = e.token_supply_at_period(period=0)
|
||||
for t in range(67000):
|
||||
tomorrows_supply = e.token_supply_at_period(period=t + 1)
|
||||
assert tomorrows_supply >= todays_supply
|
||||
todays_supply = tomorrows_supply
|
||||
|
||||
|
||||
def test_economic_parameter_aliases():
|
||||
|
||||
e = TokenEconomics()
|
||||
e = StandardTokenEconomics()
|
||||
|
||||
assert e.locked_periods_coefficient == 365
|
||||
assert int(e.staking_coefficient) == 768812
|
||||
assert e.maximum_locked_periods == 365
|
||||
assert e.maximum_rewarded_periods == 365
|
||||
|
||||
deployment_params = e.staking_deployment_parameters
|
||||
assert isinstance(deployment_params, tuple)
|
||||
|
|
|
@ -3,7 +3,7 @@ from decimal import InvalidOperation, Decimal
|
|||
import pytest
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.token import NU, Stake
|
||||
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
||||
|
@ -99,7 +99,7 @@ def test_NU(token_economics):
|
|||
_nan = NU(float('NaN'), 'NU')
|
||||
|
||||
|
||||
def test_stake(testerchain, agency):
|
||||
def test_stake(testerchain, token_economics, agency):
|
||||
token_agent, staking_agent, _policy_agent = agency
|
||||
|
||||
class FakeUrsula:
|
||||
|
@ -110,7 +110,6 @@ def test_stake(testerchain, agency):
|
|||
staking_agent = staking_agent
|
||||
token_agent = token_agent
|
||||
blockchain = testerchain
|
||||
economics = TokenEconomics()
|
||||
|
||||
ursula = FakeUrsula()
|
||||
stake = Stake(checksum_address=ursula.checksum_address,
|
||||
|
@ -118,7 +117,8 @@ def test_stake(testerchain, agency):
|
|||
final_locked_period=100,
|
||||
value=NU(100, 'NU'),
|
||||
index=0,
|
||||
staking_agent=staking_agent)
|
||||
staking_agent=staking_agent,
|
||||
economics=token_economics)
|
||||
|
||||
assert stake.value, 'NU' == NU(100, 'NU')
|
||||
|
||||
|
|
|
@ -133,7 +133,8 @@ def test_stake_init(click_runner,
|
|||
stake = Stake.from_stake_info(index=0,
|
||||
checksum_address=manual_staker,
|
||||
stake_info=stakes[0],
|
||||
staking_agent=staking_agent)
|
||||
staking_agent=staking_agent,
|
||||
economics=token_economics)
|
||||
assert stake.value == stake_value
|
||||
assert stake.duration == token_economics.minimum_locked_periods
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ from umbral.keys import UmbralPrivateKey
|
|||
from umbral.signing import Signer
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics, SlashingEconomics
|
||||
from nucypher.blockchain.economics import StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.actors import Staker
|
||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent
|
||||
from nucypher.blockchain.eth.clients import NuCypherGethDevProcess
|
||||
|
@ -343,13 +343,7 @@ def federated_ursulas(ursula_federated_test_config):
|
|||
|
||||
@pytest.fixture(scope='session')
|
||||
def token_economics():
|
||||
economics = TokenEconomics()
|
||||
return economics
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def slashing_economics():
|
||||
economics = SlashingEconomics()
|
||||
economics = StandardTokenEconomics()
|
||||
return economics
|
||||
|
||||
|
||||
|
@ -432,7 +426,7 @@ def _make_agency(testerchain, test_registry):
|
|||
adjudicator_deployer.deploy(secret_hash=os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH))
|
||||
|
||||
token_agent = token_deployer.make_agent() # 1 Token
|
||||
staking_agent = staking_escrow_deployer.make_agent() # 2 Miner Escrow
|
||||
staking_agent = staking_escrow_deployer.make_agent() # 2 Staking Escrow
|
||||
policy_agent = policy_manager_deployer.make_agent() # 3 Policy Agent
|
||||
_adjudicator_agent = adjudicator_deployer.make_agent() # 4 Adjudicator
|
||||
|
||||
|
@ -493,7 +487,7 @@ def stakers(testerchain, agency, token_economics, test_registry):
|
|||
amount = random.randint(min_stake, balance)
|
||||
|
||||
# for a random lock duration
|
||||
min_locktime, max_locktime = token_economics.minimum_locked_periods, token_economics.maximum_locked_periods
|
||||
min_locktime, max_locktime = token_economics.minimum_locked_periods, token_economics.maximum_rewarded_periods
|
||||
periods = random.randint(min_locktime, max_locktime)
|
||||
|
||||
staker.initialize_stake(amount=amount, lock_periods=periods)
|
||||
|
|
|
@ -32,7 +32,7 @@ from umbral.keys import UmbralPrivateKey
|
|||
from umbral.signing import Signer
|
||||
from zope.interface import provider
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics
|
||||
from nucypher.blockchain.economics import StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, StakingEscrowAgent, PolicyManagerAgent, AdjudicatorAgent
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
|
@ -46,11 +46,11 @@ from fixtures import _mock_ursula_reencrypts as mock_ursula_reencrypts
|
|||
|
||||
|
||||
ALGORITHM_SHA256 = 1
|
||||
TOKEN_ECONOMICS = TokenEconomics()
|
||||
TOKEN_ECONOMICS = StandardTokenEconomics()
|
||||
MIN_ALLOWED_LOCKED = TOKEN_ECONOMICS.minimum_allowed_locked
|
||||
MIN_LOCKED_PERIODS = TOKEN_ECONOMICS.minimum_locked_periods
|
||||
MAX_ALLOWED_LOCKED = TOKEN_ECONOMICS.maximum_allowed_locked
|
||||
MAX_MINTING_PERIODS = TOKEN_ECONOMICS.maximum_locked_periods
|
||||
MAX_MINTING_PERIODS = TOKEN_ECONOMICS.maximum_rewarded_periods
|
||||
|
||||
|
||||
class AnalyzeGas:
|
||||
|
@ -155,7 +155,13 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
log = Logger(AnalyzeGas.LOG_NAME)
|
||||
|
||||
# Blockchain
|
||||
testerchain, registry = TesterBlockchain.bootstrap_network()
|
||||
economics = StandardTokenEconomics(
|
||||
base_penalty=MIN_ALLOWED_LOCKED - 1,
|
||||
penalty_history_coefficient=0,
|
||||
percentage_penalty_coefficient=2,
|
||||
reward_coefficient=2
|
||||
)
|
||||
testerchain, registry = TesterBlockchain.bootstrap_network(economics=economics)
|
||||
web3 = testerchain.w3
|
||||
|
||||
# Accounts
|
||||
|
|
Loading…
Reference in New Issue