Merge pull request #1055 from szotov/economics

Improving TokenEconomics for tests
pull/1304/head
K Prasch 2019-09-06 00:40:42 -07:00 committed by GitHub
commit ee4b183950
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 690 additions and 361 deletions

View File

@ -21,6 +21,8 @@ from typing import Tuple
from math import log 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 from nucypher.blockchain.eth.token import NU
@ -28,6 +30,153 @@ LOG2 = Decimal(log(2))
class TokenEconomics: 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 Calculate parameters to use in token and escrow blockchain deployments
from high-level human-understandable parameters. from high-level human-understandable parameters.
@ -40,7 +189,7 @@ class TokenEconomics:
K2 - Staking coefficient K2 - Staking coefficient
K1 - Locked periods 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 kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2
...but also... ...but also...
@ -60,22 +209,6 @@ class TokenEconomics:
# Decimal # Decimal
_precision = 28 _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 # Supply
__default_initial_supply = NU(int(1_000_000_000), 'NU').to_nunits() __default_initial_supply = NU(int(1_000_000_000), 'NU').to_nunits()
__default_initial_inflation = 1 __default_initial_inflation = 1
@ -88,7 +221,8 @@ class TokenEconomics:
initial_inflation: int = __default_initial_inflation, initial_inflation: int = __default_initial_inflation,
halving_delay: int = __default_token_halving, halving_delay: int = __default_token_halving,
reward_saturation: int = __default_reward_saturation, 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 :param initial_supply: Tokens at t=0
@ -110,9 +244,6 @@ class TokenEconomics:
# ERC20 Token parameter (See Equation 4 in Mining paper) # ERC20 Token parameter (See Equation 4 in Mining paper)
total_supply = initial_supply * (1 + initial_inflation * halving_delay / LOG2) total_supply = initial_supply * (1 + initial_inflation * halving_delay / LOG2)
# Remaining / Reward Supply - Escrow Parameter
reward_supply = total_supply - initial_supply
# k2 - Escrow parameter # k2 - Escrow parameter
staking_coefficient = 365 ** 2 * reward_saturation * halving_delay / LOG2 / (1 - small_stake_multiplier) 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) locked_periods_coefficient = 365 * reward_saturation * small_stake_multiplier / (1 - small_stake_multiplier)
# Awarded periods- Escrow parameter # Awarded periods- Escrow parameter
maximum_locked_periods = reward_saturation * 365 maximum_rewarded_periods = reward_saturation * 365
# Injected # Injected
self.initial_supply = initial_supply
self.initial_inflation = initial_inflation self.initial_inflation = initial_inflation
self.token_halving = halving_delay self.token_halving = halving_delay
self.token_saturation = reward_saturation self.token_saturation = reward_saturation
self.small_stake_multiplier = small_stake_multiplier self.small_stake_multiplier = small_stake_multiplier
# Calculated super(StandardTokenEconomics, self).__init__(
self.__total_supply = total_supply initial_supply,
self.reward_supply = reward_supply total_supply,
self.staking_coefficient = staking_coefficient staking_coefficient,
self.locked_periods_coefficient = locked_periods_coefficient locked_periods_coefficient,
self.maximum_locked_periods = maximum_locked_periods maximum_rewarded_periods,
**kwargs
@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
) )
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 __economics = dict()
HASH_ALGORITHM_SHA256 = 1
HASH_ALGORITHM_RIPEMD160 = 2
hash_algorithm = HASH_ALGORITHM_SHA256 @classmethod
base_penalty = 100 def get_economics(cls, registry: BaseContractRegistry) -> TokenEconomics:
penalty_history_coefficient = 10 registry_id = registry.id
percentage_penalty_coefficient = 8 try:
reward_coefficient = 2 return cls.__economics[registry_id]
except KeyError:
economics = TokenEconomicsFactory.retrieve_from_blockchain(registry=registry)
cls.__economics[registry_id] = economics
return economics
@property @staticmethod
def deployment_parameters(self) -> Tuple[int, ...]: def retrieve_from_blockchain(registry: BaseContractRegistry) -> TokenEconomics:
"""Cast coefficient attributes to uint256 compatible type for solidity+EVM""" 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 = [ total_supply = token_agent.contract.functions.totalSupply().call()
self.hash_algorithm, reward_supply = staking_agent.contract.functions.getReservedReward().call()
self.base_penalty, # it's not real initial_supply value because used current reward instead of initial
self.penalty_history_coefficient, initial_supply = total_supply - reward_supply
self.percentage_penalty_coefficient,
self.reward_coefficient
]
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

View File

@ -33,7 +33,7 @@ from eth_tester.exceptions import TransactionFailed
from eth_utils import keccak from eth_utils import keccak
from twisted.logger import Logger 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 ( from nucypher.blockchain.eth.agents import (
NucypherTokenAgent, NucypherTokenAgent,
StakingEscrowAgent, StakingEscrowAgent,
@ -157,7 +157,8 @@ class ContractAdministrator(NucypherTokenActor):
def __init__(self, def __init__(self,
registry: BaseContractRegistry, registry: BaseContractRegistry,
deployer_address: str = None, 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. Note: super() is not called here to avoid setting the token agent.
TODO: Review this logic ^^ "bare mode". TODO: Review this logic ^^ "bare mode".
@ -166,6 +167,7 @@ class ContractAdministrator(NucypherTokenActor):
self.deployer_address = deployer_address self.deployer_address = deployer_address
self.checksum_address = self.deployer_address self.checksum_address = self.deployer_address
self.economics = economics or StandardTokenEconomics()
self.registry = registry self.registry = registry
self.user_escrow_deployers = dict() self.user_escrow_deployers = dict()
@ -202,11 +204,17 @@ class ContractAdministrator(NucypherTokenActor):
contract_name: str, contract_name: str,
gas_limit: int = None, gas_limit: int = None,
plaintext_secret: str = None, plaintext_secret: str = None,
progress=None progress=None,
*args,
**kwargs,
) -> Tuple[dict, ContractDeployer]: ) -> Tuple[dict, ContractDeployer]:
Deployer = self.__get_deployer(contract_name=contract_name) 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 Deployer._upgradeable:
if not plaintext_secret: if not plaintext_secret:
raise ValueError("Upgrade plaintext_secret must be passed to deploy an upgradeable contract.") raise ValueError("Upgrade plaintext_secret must be passed to deploy an upgradeable contract.")
@ -395,10 +403,7 @@ class Staker(NucypherTokenActor):
class InsufficientTokens(StakerError): class InsufficientTokens(StakerError):
pass pass
def __init__(self, def __init__(self, is_me: bool, *args, **kwargs) -> None:
is_me: bool,
economics: TokenEconomics = None,
*args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.log = Logger("staker") self.log = Logger("staker")
@ -409,7 +414,7 @@ class Staker(NucypherTokenActor):
# Blockchain # Blockchain
self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=self.registry) self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=self.registry)
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, 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) self.stakes = StakeList(registry=self.registry, checksum_address=self.checksum_address)
def to_dict(self) -> dict: def to_dict(self) -> dict:
@ -651,7 +656,6 @@ class BlockchainPolicyAuthor(NucypherTokenActor):
def __init__(self, def __init__(self,
checksum_address: str, checksum_address: str,
economics: TokenEconomics = None,
rate: int = None, rate: int = None,
duration_periods: int = None, duration_periods: int = None,
first_period_reward: 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.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, 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.rate = rate
self.duration_periods = duration_periods self.duration_periods = duration_periods
self.first_period_reward = first_period_reward self.first_period_reward = first_period_reward
@ -700,7 +704,8 @@ class BlockchainPolicyAuthor(NucypherTokenActor):
# Calculate duration in periods and expiration datetime # Calculate duration in periods and expiration datetime
if expiration: 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: else:
duration_periods = duration_periods or self.duration_periods duration_periods = duration_periods or self.duration_periods
expiration = datetime_at_period(self.staking_agent.get_current_period() + duration_periods) expiration = datetime_at_period(self.staking_agent.get_current_period() + duration_periods)

View File

@ -621,3 +621,17 @@ class AdjudicatorAgent(EthereumContractAgent):
def penalty_history(self, staker_address: str) -> int: def penalty_history(self, staker_address: str) -> int:
return self.contract.functions.penaltyHistory(staker_address).call() 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

View File

@ -22,7 +22,7 @@ from constant_sorrow.constants import CONTRACT_NOT_DEPLOYED, NO_DEPLOYER_CONFIGU
from eth_utils import is_checksum_address from eth_utils import is_checksum_address
from web3.contract import Contract 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 ( from nucypher.blockchain.eth.agents import (
EthereumContractAgent, EthereumContractAgent,
StakingEscrowAgent, StakingEscrowAgent,
@ -67,10 +67,6 @@ class ContractDeployer:
if not isinstance(self.blockchain, BlockchainDeployerInterface): if not isinstance(self.blockchain, BlockchainDeployerInterface):
raise ValueError("No deployer interface connection available.") raise ValueError("No deployer interface connection available.")
if not economics:
economics = TokenEconomics()
self.__economics = economics
# #
# Defaults # Defaults
# #
@ -80,6 +76,7 @@ class ContractDeployer:
self.__proxy_contract = NotImplemented self.__proxy_contract = NotImplemented
self.__deployer_address = deployer_address self.__deployer_address = deployer_address
self.__ready_to_deploy = False self.__ready_to_deploy = False
self.__economics = economics or StandardTokenEconomics()
@property @property
def economics(self) -> TokenEconomics: def economics(self) -> TokenEconomics:
@ -272,11 +269,8 @@ class StakingEscrowDeployer(ContractDeployer):
_upgradeable = True _upgradeable = True
_proxy_deployer = DispatcherDeployer _proxy_deployer = DispatcherDeployer
def __init__(self, economics: TokenEconomics = None, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if not economics:
economics = TokenEconomics()
self.__economics = economics
self.__dispatcher_contract = None self.__dispatcher_contract = None
token_contract_name = NucypherTokenDeployer.contract_name token_contract_name = NucypherTokenDeployer.contract_name
@ -289,7 +283,7 @@ class StakingEscrowDeployer(ContractDeployer):
raise RuntimeError("PolicyManager contract is not initialized.") raise RuntimeError("PolicyManager contract is not initialized.")
def _deploy_essential(self, gas_limit: int = None): 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( the_escrow_contract, deploy_receipt = self.blockchain.deploy_contract(
self.deployer_address, self.deployer_address,
self.registry, self.registry,
@ -352,7 +346,7 @@ class StakingEscrowDeployer(ContractDeployer):
# 3 - Transfer the reward supply tokens to StakingEscrow # # 3 - Transfer the reward supply tokens to StakingEscrow #
reward_function = self.token_contract.functions.transfer(the_escrow_contract.address, 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 ?? # TODO: Confirmations / Successful Transaction Indicator / Events ??
reward_receipt = self.blockchain.send_transaction(contract_function=reward_function, reward_receipt = self.blockchain.send_transaction(contract_function=reward_function,
@ -430,7 +424,6 @@ class StakingEscrowDeployer(ContractDeployer):
return rollback_receipt return rollback_receipt
class PolicyManagerDeployer(ContractDeployer): class PolicyManagerDeployer(ContractDeployer):
""" """
Depends on StakingEscrow and NucypherTokenAgent Depends on StakingEscrow and NucypherTokenAgent
@ -446,11 +439,6 @@ class PolicyManagerDeployer(ContractDeployer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*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 proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name
staking_contract_name = StakingEscrowDeployer.contract_name staking_contract_name = StakingEscrowDeployer.contract_name
self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry, self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry,
@ -812,10 +800,8 @@ class AdjudicatorDeployer(ContractDeployer):
_upgradeable = True _upgradeable = True
_proxy_deployer = DispatcherDeployer _proxy_deployer = DispatcherDeployer
def __init__(self, economics: SlashingEconomics = None, *args, **kwargs): def __init__(self, *args, **kwargs):
if not economics: super().__init__(*args, **kwargs)
economics = SlashingEconomics()
super().__init__(*args, economics=economics, **kwargs)
staking_contract_name = StakingEscrowDeployer.contract_name staking_contract_name = StakingEscrowDeployer.contract_name
proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name
self.staking_contract = self.blockchain.get_contract_by_name(registry=self.registry, 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): def _deploy_essential(self, gas_limit: int = None):
constructor_args = (self.staking_contract.address, 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, adjudicator_contract, deploy_receipt = self.blockchain.deploy_contract(self.deployer_address,
self.registry, self.registry,
self.contract_name, self.contract_name,
@ -840,8 +826,8 @@ class AdjudicatorDeployer(ContractDeployer):
progress.update(1) progress.update(1)
proxy_deployer = self._proxy_deployer(registry=self.registry, proxy_deployer = self._proxy_deployer(registry=self.registry,
target_contract=adjudicator_contract, target_contract=adjudicator_contract,
deployer_address=self.deployer_address) deployer_address=self.deployer_address)
proxy_deploy_receipts = proxy_deployer.deploy(secret_hash=secret_hash, gas_limit=gas_limit) 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]] proxy_deploy_receipt = proxy_deploy_receipts[proxy_deployer.deployment_steps[0]]
@ -888,9 +874,9 @@ class AdjudicatorDeployer(ContractDeployer):
use_proxy_address=False) use_proxy_address=False)
proxy_deployer = self._proxy_deployer(registry=self.registry, proxy_deployer = self._proxy_deployer(registry=self.registry,
target_contract=existing_bare_contract, target_contract=existing_bare_contract,
deployer_address=self.deployer_address, deployer_address=self.deployer_address,
bare=True) bare=True)
adjudicator_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit) adjudicator_contract, deploy_receipt = self._deploy_essential(gas_limit=gas_limit)

View File

@ -121,21 +121,15 @@ contract Issuer is Upgradeable {
Math.min(currentSupply1, currentSupply2) : Math.min(currentSupply1, currentSupply2) :
Math.max(currentSupply1, currentSupply2); Math.max(currentSupply1, currentSupply2);
//totalSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2) - //(totalSupply - currentSupply) * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
//currentSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
uint256 allLockedPeriods = uint256(_allLockedPeriods <= rewardedPeriods ? uint256 allLockedPeriods = uint256(_allLockedPeriods <= rewardedPeriods ?
_allLockedPeriods : rewardedPeriods) _allLockedPeriods : rewardedPeriods)
.add(lockedPeriodsCoefficient); .add(lockedPeriodsCoefficient);
uint256 denominator = _totalLockedValue.mul(miningCoefficient); uint256 denominator = _totalLockedValue.mul(miningCoefficient);
amount = amount = totalSupply.sub(currentSupply)
totalSupply .mul(_lockedValue)
.mul(_lockedValue) .mul(allLockedPeriods)
.mul(allLockedPeriods) .div(denominator);
.div(denominator).sub(
currentSupply
.mul(_lockedValue)
.mul(allLockedPeriods)
.div(denominator));
// rounding the last reward // rounding the last reward
if (amount == 0) { if (amount == 0) {
amount = 1; amount = 1;

View File

@ -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 _pydecimal import Decimal
from collections import UserList from collections import UserList
from typing import Union, Tuple, List from typing import Union, Tuple, List
@ -151,7 +168,7 @@ class Stake:
first_locked_period: int, first_locked_period: int,
final_locked_period: int, final_locked_period: int,
index: int, index: int,
economics=None, economics,
validate_now: bool = True): validate_now: bool = True):
self.log = Logger(f'stake-{checksum_address}-{index}') 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. # and no confirmation can be performed in this period for this stake.
self.final_locked_period = final_locked_period 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 # Blockchain
self.staking_agent = staking_agent self.staking_agent = staking_agent
# Economics # Economics
from nucypher.blockchain.economics import TokenEconomics self.economics = economics
self.economics = economics or TokenEconomics()
self.minimum_nu = NU(int(self.economics.minimum_allowed_locked), 'NuNit') self.minimum_nu = NU(int(self.economics.minimum_allowed_locked), 'NuNit')
self.maximum_nu = NU(int(self.economics.maximum_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: if validate_now:
self.validate_duration() self.validate_duration()
@ -219,6 +237,7 @@ class Stake:
checksum_address: str, checksum_address: str,
index: int, index: int,
stake_info: Tuple[int, int, int], stake_info: Tuple[int, int, int],
economics,
*args, **kwargs *args, **kwargs
) -> 'Stake': ) -> 'Stake':
@ -230,6 +249,7 @@ class Stake:
first_locked_period=first_locked_period, first_locked_period=first_locked_period,
final_locked_period=final_locked_period, final_locked_period=final_locked_period,
value=NU(value, 'NuNit'), value=NU(value, 'NuNit'),
economics=economics,
*args, **kwargs) *args, **kwargs)
instance.worker_address = instance.staking_agent.get_worker_from_staker(staker_address=checksum_address) 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, first_locked_period=self.first_locked_period,
final_locked_period=self.final_locked_period, final_locked_period=self.final_locked_period,
value=remaining_stake_value, value=remaining_stake_value,
staking_agent=self.staking_agent) staking_agent=self.staking_agent,
economics=self.economics)
# New Derived Stake # New Derived Stake
end_period = self.final_locked_period + additional_periods end_period = self.final_locked_period + additional_periods
@ -387,7 +408,8 @@ class Stake:
final_locked_period=end_period, final_locked_period=end_period,
value=target_value, value=target_value,
index=NEW_STAKE, index=NEW_STAKE,
staking_agent=self.staking_agent) staking_agent=self.staking_agent,
economics=self.economics)
# #
# Validate # Validate
@ -425,7 +447,8 @@ class Stake:
final_locked_period=final_locked_period, final_locked_period=final_locked_period,
value=amount, value=amount,
index=NEW_STAKE, index=NEW_STAKE,
staking_agent=staker.staking_agent) staking_agent=staker.staking_agent,
economics=staker.economics)
# Validate # Validate
stake.validate_value() stake.validate_value()
@ -550,6 +573,8 @@ class StakeList(UserList):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.log = Logger('stake-tracker') self.log = Logger('stake-tracker')
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) 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 self.__terminal_period = NOT_STAKING
@ -590,7 +615,8 @@ class StakeList(UserList):
onchain_stake = Stake.from_stake_info(checksum_address=self.checksum_address, onchain_stake = Stake.from_stake_info(checksum_address=self.checksum_address,
stake_info=stake_info, stake_info=stake_info,
staking_agent=self.staking_agent, staking_agent=self.staking_agent,
index=onchain_index) index=onchain_index,
economics=self.economics)
# rack the latest terminal period # rack the latest terminal period
if onchain_stake.final_locked_period > terminal_period: if onchain_stake.final_locked_period > terminal_period:

View File

@ -20,23 +20,22 @@ from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
from eth_utils import is_address, to_checksum_address, is_hex from eth_utils import is_address, to_checksum_address, is_hex
def epoch_to_period(epoch: int) -> int: def epoch_to_period(epoch: int, seconds_per_period: int) -> int:
from nucypher.blockchain.economics import TokenEconomics period = epoch // seconds_per_period
period = epoch // int(TokenEconomics.seconds_per_period)
return 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.""" """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) 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.""" """Returns the datetime object at a given period, future, or past."""
now = maya.now() 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 delta_periods = period - current_period
# + # +
@ -50,10 +49,10 @@ def datetime_at_period(period: int) -> maya.MayaDT:
return target_period 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""" """Takes a future MayaDT instance and calculates the duration from now, returning in periods"""
future_period = datetime_to_period(datetime=future_time) future_period = datetime_to_period(datetime=future_time, seconds_per_period=seconds_per_period)
current_period = datetime_to_period(datetime=maya.now()) current_period = datetime_to_period(datetime=maya.now(), seconds_per_period=seconds_per_period)
periods = future_period - current_period periods = future_period - current_period
return periods return periods

View File

@ -20,7 +20,7 @@ from twisted.logger import Logger
from hendrix.deploy.base import HendrixDeploy from hendrix.deploy.base import HendrixDeploy
from hendrix.experience import hey_joe 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.actors import NucypherTokenActor
from nucypher.blockchain.eth.agents import NucypherTokenAgent, ContractAgency from nucypher.blockchain.eth.agents import NucypherTokenAgent, ContractAgency
from nucypher.blockchain.eth.interfaces import BlockchainInterface from nucypher.blockchain.eth.interfaces import BlockchainInterface
@ -164,7 +164,6 @@ class Felix(Character, NucypherTokenActor):
rest_port: int, rest_port: int,
client_password: str = None, client_password: str = None,
crash_on_error: bool = False, crash_on_error: bool = False,
economics: TokenEconomics = None,
distribute_ether: bool = True, distribute_ether: bool = True,
registry: BaseContractRegistry = None, registry: BaseContractRegistry = None,
*args, **kwargs): *args, **kwargs):
@ -205,12 +204,9 @@ class Felix(Character, NucypherTokenActor):
self._distribution_task.clock = self._CLOCK self._distribution_task.clock = self._CLOCK
self.start_time = NOT_RUNNING self.start_time = NOT_RUNNING
if not economics: self.economics = TokenEconomicsFactory.get_economics(registry=registry)
economics = TokenEconomics() self.MAXIMUM_DISBURSEMENT = self.economics.maximum_allowed_locked
self.economics = economics self.INITIAL_DISBURSEMENT = self.economics.minimum_allowed_locked * 3
self.MAXIMUM_DISBURSEMENT = economics.maximum_allowed_locked
self.INITIAL_DISBURSEMENT = economics.minimum_allowed_locked * 3
# Optionally send ether with each token transaction # Optionally send ether with each token transaction
self.distribute_ether = distribute_ether self.distribute_ether = distribute_ether

View File

@ -42,7 +42,6 @@ from umbral.pre import UmbralCorrectnessError
from umbral.signing import Signature from umbral.signing import Signature
import nucypher import nucypher
from nucypher.blockchain.economics import TokenEconomics
from nucypher.blockchain.eth.actors import BlockchainPolicyAuthor, Worker, Staker from nucypher.blockchain.eth.actors import BlockchainPolicyAuthor, Worker, Staker
from nucypher.blockchain.eth.agents import StakingEscrowAgent, NucypherTokenAgent, ContractAgency from nucypher.blockchain.eth.agents import StakingEscrowAgent, NucypherTokenAgent, ContractAgency
from nucypher.blockchain.eth.decorators import validate_checksum_address from nucypher.blockchain.eth.decorators import validate_checksum_address

View File

@ -21,19 +21,14 @@ from web3 import Web3
from nucypher.characters.lawful import StakeHolder from nucypher.characters.lawful import StakeHolder
from nucypher.blockchain.eth.interfaces import BlockchainInterface, BlockchainInterfaceFactory 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.token import NU
from nucypher.blockchain.eth.utils import datetime_at_period 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 import painting, actions
from nucypher.cli.actions import confirm_staged_stake, get_client_password, select_stake, select_client_account 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.config import nucypher_click_config
from nucypher.cli.painting import paint_receipt_summary from nucypher.cli.painting import paint_receipt_summary
from nucypher.cli.types import ( from nucypher.cli.types import (
EIP55_CHECKSUM_ADDRESS, EIP55_CHECKSUM_ADDRESS,
STAKE_VALUE,
STAKE_DURATION,
STAKE_EXTENSION,
EXISTING_READABLE_FILE EXISTING_READABLE_FILE
) )
from nucypher.config.characters import StakeHolderConfiguration from nucypher.config.characters import StakeHolderConfiguration
@ -142,6 +137,12 @@ def stake(click_config,
STAKEHOLDER = stakeholder_config.produce(initial_address=staking_address) STAKEHOLDER = stakeholder_config.produce(initial_address=staking_address)
blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri) # Eager connection 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 # Eager Actions
@ -179,10 +180,10 @@ def stake(click_config,
# TODO: Double-check dates # TODO: Double-check dates
current_period = STAKEHOLDER.staking_agent.get_current_period() 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] min_worker_periods = STAKEHOLDER.staking_agent.staking_parameters()[7]
release_period = current_period + min_worker_periods 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') emitter.echo(f"\nWorker {worker_address} successfully bonded to staker {staking_address}", color='green')
paint_receipt_summary(emitter=emitter, paint_receipt_summary(emitter=emitter,
@ -217,7 +218,7 @@ def stake(click_config,
# TODO: Double-check dates # TODO: Double-check dates
current_period = STAKEHOLDER.staking_agent.get_current_period() 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') emitter.echo(f"Successfully detached worker {worker_address} from staker {staking_address}", color='green')
paint_receipt_summary(emitter=emitter, paint_receipt_summary(emitter=emitter,
@ -249,13 +250,14 @@ def stake(click_config,
# #
if not value: if not value:
min_locked = STAKEHOLDER.economics.minimum_allowed_locked value = click.prompt(f"Enter stake value in NU",
value = click.prompt(f"Enter stake value in NU", type=STAKE_VALUE, default=NU.from_nunits(min_locked).to_tokens()) type=stake_value_range,
default=NU.from_nunits(min_locked).to_tokens())
value = NU.from_tokens(value) value = NU.from_tokens(value)
if not lock_periods: if not lock_periods:
prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)" 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() start_period = STAKEHOLDER.staking_agent.get_current_period()
end_period = start_period + lock_periods end_period = start_period + lock_periods
@ -302,12 +304,12 @@ def stake(click_config,
# Value # Value
if not value: if not value:
value = click.prompt(f"Enter target value (must be less than or equal to {str(current_stake.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') value = NU(value, 'NU')
# Duration # Duration
if not lock_periods: 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: else:
extension = lock_periods extension = lock_periods

View File

@ -18,9 +18,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
from ipaddress import ip_address from ipaddress import ip_address
import click 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 from nucypher.blockchain.eth.token import NU
@ -43,14 +43,8 @@ class IPv4Address(click.ParamType):
return value return value
token_economics = TokenEconomics()
WEI = click.IntRange(min=1, clamp=False) # TODO: Better validation for ether and wei values? 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 # Filesystem
EXISTING_WRITABLE_DIRECTORY = click.Path(exists=True, dir_okay=True, file_okay=False, writable=True) 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) EXISTING_READABLE_FILE = click.Path(exists=True, dir_okay=False, file_okay=True, readable=True)

View File

@ -24,7 +24,7 @@ from pytest_ethereum.deployer import Deployer
from twisted.logger import Logger from twisted.logger import Logger
from web3 import Web3 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.actors import ContractAdministrator
from nucypher.blockchain.eth.agents import EthereumContractAgent from nucypher.blockchain.eth.agents import EthereumContractAgent
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory 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 _FIRST_URSULA = _FIRST_STAKER + NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS
_ursulas_range = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS) _ursulas_range = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
_default_token_economics = StandardTokenEconomics()
def __init__(self, def __init__(self,
test_accounts=None, test_accounts=None,
poa=True, poa=True,
@ -181,8 +183,8 @@ class TesterBlockchain(BlockchainDeployerInterface):
raise ValueError("Specify hours, seconds, or periods, not a combination") raise ValueError("Specify hours, seconds, or periods, not a combination")
if periods: if periods:
duration = (TokenEconomics.hours_per_period * periods) * (60 * 60) duration = self._default_token_economics.seconds_per_period * periods
base = TokenEconomics.hours_per_period * 60 * 60 base = self._default_token_economics.seconds_per_period
elif hours: elif hours:
duration = hours * (60*60) duration = hours * (60*60)
base = 60 * 60 base = 60 * 60
@ -200,11 +202,13 @@ class TesterBlockchain(BlockchainDeployerInterface):
delta = maya.timedelta(seconds=end_timestamp-now) delta = maya.timedelta(seconds=end_timestamp-now)
self.log.info(f"Time traveled {delta} " 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}") f"| epoch {end_timestamp}")
@classmethod @classmethod
def bootstrap_network(cls) -> 'TesterBlockchain': def bootstrap_network(cls,
economics: TokenEconomics = None
) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']:
"""For use with metric testing scripts""" """For use with metric testing scripts"""
registry = InMemoryContractRegistry() registry = InMemoryContractRegistry()
@ -216,7 +220,9 @@ class TesterBlockchain(BlockchainDeployerInterface):
testerchain.transacting_power = power testerchain.transacting_power = power
origin = testerchain.client.etherbase 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() secrets = dict()
for deployer_class in deployer.upgradeable_deployer_classes: for deployer_class in deployer.upgradeable_deployer_classes:
secrets[deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD secrets[deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD

View File

@ -20,32 +20,61 @@ import pytest
from eth_tester.exceptions import TransactionFailed from eth_tester.exceptions import TransactionFailed
from web3.contract import Contract from web3.contract import Contract
from nucypher.blockchain.economics import TokenEconomics
from nucypher.blockchain.eth.token import NU
SECRET_LENGTH = 32 SECRET_LENGTH = 32
TOTAL_SUPPLY = 2 * 10 ** 40
@pytest.fixture() @pytest.fixture()
def token(testerchain, deploy_contract): def token(testerchain, deploy_contract):
# Create an ERC20 token # Create an ERC20 token
token, _ = deploy_contract('NuCypherToken', 2 * 10 ** 40) token, _ = deploy_contract('NuCypherToken', _totalSupply=TOTAL_SUPPLY)
return token return token
@pytest.mark.slow @pytest.mark.slow
def test_issuer(testerchain, token, deploy_contract): 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] creator = testerchain.client.accounts[0]
ursula = testerchain.client.accounts[1] ursula = testerchain.client.accounts[1]
# Only token contract is allowed in Issuer constructor # Only token contract is allowed in Issuer constructor
with pytest.raises((TransactionFailed, ValueError)): 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 # 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') events = issuer.events.Initialized.createFilter(fromBlock='latest')
# Give staker tokens for reward and initialize contract # Give staker tokens for reward and initialize contract
reserved_reward = 2 * 10 ** 40 - 10 ** 30 tx = token.functions.transfer(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.transfer(issuer.address, reserved_reward).transact({'from': creator})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
# Only owner can initialize # Only owner can initialize
@ -57,7 +86,7 @@ def test_issuer(testerchain, token, deploy_contract):
events = events.get_all_entries() events = events.get_all_entries()
assert 1 == len(events) 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() balance = token.functions.balanceOf(issuer.address).call()
# Can't initialize second time # Can't initialize second time
@ -68,26 +97,30 @@ def test_issuer(testerchain, token, deploy_contract):
# Check result of minting tokens # Check result of minting tokens
tx = issuer.functions.testMint(0, 1000, 2000, 0).transact({'from': ursula}) tx = issuer.functions.testMint(0, 1000, 2000, 0).transact({'from': ursula})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 10 == token.functions.balanceOf(ursula).call() reward = calculate_reward(1000, 2000, 0)
assert balance - 10 == token.functions.balanceOf(issuer.address).call() 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 # 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}) tx = issuer.functions.testMint(0, 500, 500, 0).transact({'from': ursula})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 30 == token.functions.balanceOf(ursula).call() reward += calculate_reward(500, 500, 0)
assert balance - 30 == token.functions.balanceOf(issuer.address).call() 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 # The result must be more because of bigger value of allLockedPeriods
tx = issuer.functions.testMint(0, 500, 500, 10 ** 4).transact({'from': ursula}) tx = issuer.functions.testMint(0, 500, 500, 10 ** 4).transact({'from': ursula})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 70 == token.functions.balanceOf(ursula).call() reward += calculate_reward(500, 500, 10 ** 4)
assert balance - 70 == token.functions.balanceOf(issuer.address).call() 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 # 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}) tx = issuer.functions.testMint(0, 500, 500, 2 * 10 ** 4).transact({'from': ursula})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 110 == token.functions.balanceOf(ursula).call() reward += calculate_reward(500, 500, 10 ** 4)
assert balance - 110 == token.functions.balanceOf(issuer.address).call() assert reward == token.functions.balanceOf(ursula).call()
assert balance - reward == token.functions.balanceOf(issuer.address).call()
@pytest.mark.slow @pytest.mark.slow
@ -97,14 +130,28 @@ def test_inflation_rate(testerchain, token, deploy_contract):
During one period inflation rate must be the same 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] creator = testerchain.client.accounts[0]
ursula = testerchain.client.accounts[1] ursula = testerchain.client.accounts[1]
# Creator deploys the contract # 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 # 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) testerchain.wait_for_receipt(tx)
tx = issuer.functions.initialize().transact({'from': creator}) tx = issuer.functions.initialize().transact({'from': creator})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
@ -166,11 +213,25 @@ def test_upgrading(testerchain, token, deploy_contract):
secret2_hash = testerchain.w3.keccak(secret2) secret2_hash = testerchain.w3.keccak(secret2)
# Deploy contract # 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) dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address, secret_hash)
# Deploy second version of the contract # 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( contract = testerchain.client.get_contract(
abi=contract_library_v2.abi, abi=contract_library_v2.abi,
address=dispatcher.address, address=dispatcher.address,
@ -208,7 +269,14 @@ def test_upgrading(testerchain, token, deploy_contract):
assert 3 == contract.functions.valueToCheck().call() assert 3 == contract.functions.valueToCheck().call()
# Can't upgrade to the previous version or to the bad version # 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)): with pytest.raises((TransactionFailed, ValueError)):
tx = dispatcher.functions.upgrade(contract_library_v1.address, secret2, secret_hash)\ tx = dispatcher.functions.upgrade(contract_library_v1.address, secret2, secret_hash)\
.transact({'from': creator}) .transact({'from': creator})

View File

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

View File

@ -24,10 +24,10 @@ from eth_tester.exceptions import TransactionFailed
from eth_utils import to_canonical_address from eth_utils import to_canonical_address
from web3.contract import Contract from web3.contract import Contract
from nucypher.blockchain.economics import TokenEconomics
from umbral.keys import UmbralPrivateKey from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer from umbral.signing import Signer
from nucypher.blockchain.eth.token import NU
from nucypher.crypto.api import sha256_digest from nucypher.crypto.api import sha256_digest
from nucypher.crypto.signing import SignatureStamp from nucypher.crypto.signing import SignatureStamp
@ -44,26 +44,34 @@ adjudicator_secret = os.urandom(SECRET_LENGTH)
@pytest.fixture() @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 # 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 return contract
@pytest.fixture() @pytest.fixture()
def escrow(testerchain, token, deploy_contract): def escrow(testerchain, token, token_economics, deploy_contract):
# Creator deploys the escrow # Creator deploys the escrow
contract, _ = deploy_contract( contract, _ = deploy_contract(
contract_name='StakingEscrow', 'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
_token=token.address,
_hoursPerPeriod=1,
_miningCoefficient=8*10**7,
_lockedPeriodsCoefficient=4,
_rewardedPeriods=4,
_minLockedPeriods=6,
_minAllowableLockedTokens=100,
_maxAllowableLockedTokens=2000,
_minWorkerPeriods=2
) )
secret_hash = testerchain.w3.keccak(escrow_secret) secret_hash = testerchain.w3.keccak(escrow_secret)
@ -101,22 +109,17 @@ def policy_manager(testerchain, escrow, deploy_contract):
@pytest.fixture() @pytest.fixture()
def adjudicator(testerchain, escrow, slashing_economics, deploy_contract): def adjudicator(testerchain, escrow, token_economics, deploy_contract):
escrow, _ = escrow escrow, _ = escrow
creator = testerchain.client.accounts[0] creator = testerchain.client.accounts[0]
secret_hash = testerchain.w3.keccak(adjudicator_secret) 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 # Creator deploys the contract
contract, _ = deploy_contract( contract, _ = deploy_contract(
'Adjudicator', 'Adjudicator',
escrow.address, escrow.address,
*deployment_parameters) *token_economics.slashing_deployment_parameters)
dispatcher, _ = deploy_contract('Dispatcher', contract.address, secret_hash) dispatcher, _ = deploy_contract('Dispatcher', contract.address, secret_hash)
@ -243,6 +246,7 @@ def execute_multisig_transaction(testerchain, multisig, accounts, tx):
@pytest.mark.slow @pytest.mark.slow
def test_all(testerchain, def test_all(testerchain,
token_economics,
token, token,
escrow, escrow,
policy_manager, policy_manager,
@ -250,7 +254,6 @@ def test_all(testerchain,
worklock, worklock,
user_escrow_proxy, user_escrow_proxy,
multisig, multisig,
slashing_economics,
mock_ursula_reencrypts, mock_ursula_reencrypts,
deploy_contract): deploy_contract):
@ -304,8 +307,7 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
# Initialize escrow # Initialize escrow
reward = 10 ** 9 tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.transfer(escrow.address, reward).transact({'from': creator})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().buildTransaction({'from': multisig.address, 'gasPrice': 0}) tx = escrow.functions.initialize().buildTransaction({'from': multisig.address, 'gasPrice': 0})
execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx) 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}) tx = worklock.functions.claim().transact({'from': ursula2, 'gas_price': 0})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(ursula2).call() == deposit_rate * deposited_eth 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 1000 == escrow.functions.getAllTokens(ursula2).call()
assert 0 == escrow.functions.getLockedTokens(ursula2).call() assert 0 == escrow.functions.getLockedTokens(ursula2).call()
assert 1000 == escrow.functions.getLockedTokens(ursula2, 1).call() assert 1000 == escrow.functions.getLockedTokens(ursula2, 1).call()
@ -474,7 +476,7 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
tx = escrow.functions.confirmActivity().transact({'from': ursula1}) tx = escrow.functions.confirmActivity().transact({'from': ursula1})
testerchain.wait_for_receipt(tx) 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 9000 == token.functions.balanceOf(ursula1).call()
assert 0 == escrow.functions.getLockedTokens(ursula1).call() assert 0 == escrow.functions.getLockedTokens(ursula1).call()
assert 1000 == escrow.functions.getLockedTokens(ursula1, 1).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, 1).call()
assert 1000 == escrow.functions.getLockedTokens(user_escrow_1.address, 10).call() assert 1000 == escrow.functions.getLockedTokens(user_escrow_1.address, 10).call()
assert 0 == escrow.functions.getLockedTokens(user_escrow_1.address, 11).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() assert 9000 == token.functions.balanceOf(user_escrow_1.address).call()
# Only user can deposit tokens to the staking escrow # 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() policy_manager_v1 = policy_manager.functions.target().call()
# Creator deploys the contracts as the second versions # Creator deploys the contracts as the second versions
escrow_v2, _ = deploy_contract( escrow_v2, _ = deploy_contract(
contract_name='StakingEscrow', 'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
_token=token.address,
_hoursPerPeriod=1,
_miningCoefficient=8 * 10 ** 7,
_lockedPeriodsCoefficient=4,
_rewardedPeriods=4,
_minLockedPeriods=6,
_minAllowableLockedTokens=100,
_maxAllowableLockedTokens=2000,
_minWorkerPeriods=2
) )
policy_manager_v2, _ = deploy_contract('PolicyManager', escrow.address) policy_manager_v2, _ = deploy_contract('PolicyManager', escrow.address)
# Ursula and Alice can't upgrade contracts, only owner can # 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() total_lock = escrow.functions.lockedPerPeriod(current_period).call()
alice1_balance = token.functions.balanceOf(alice1).call() alice1_balance = token.functions.balanceOf(alice1).call()
deployment_parameters = list(slashing_economics.deployment_parameters) algorithm_sha256, base_penalty, *coefficients = token_economics.slashing_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
penalty_history_coefficient, percentage_penalty_coefficient, reward_coefficient = coefficients penalty_history_coefficient, percentage_penalty_coefficient, reward_coefficient = coefficients
data_hash, slashing_args = generate_args_for_slashing(mock_ursula_reencrypts, ursula1_with_stamp) 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_v2, _ = deploy_contract(
'Adjudicator', 'Adjudicator',
escrow.address, escrow.address,
*slashing_economics.deployment_parameters) *token_economics.slashing_deployment_parameters)
adjudicator_secret2 = os.urandom(SECRET_LENGTH) adjudicator_secret2 = os.urandom(SECRET_LENGTH)
adjudicator_secret2_hash = testerchain.w3.keccak(adjudicator_secret2) adjudicator_secret2_hash = testerchain.w3.keccak(adjudicator_secret2)
# Ursula and Alice can't upgrade library, only owner can # Ursula and Alice can't upgrade library, only owner can

View File

@ -36,11 +36,11 @@ def escrow(testerchain, deploy_contract):
@pytest.fixture(params=[False, True]) @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( contract, _ = deploy_contract(
'Adjudicator', 'Adjudicator',
escrow.address, escrow.address,
*slashing_economics.deployment_parameters) *token_economics.slashing_deployment_parameters)
if request.param: if request.param:
secret = os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH) secret = os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH)

View File

@ -40,7 +40,7 @@ secret2 = (654321).to_bytes(32, byteorder='big')
def test_evaluate_cfrag(testerchain, def test_evaluate_cfrag(testerchain,
escrow, escrow,
adjudicator, adjudicator,
slashing_economics, token_economics,
blockchain_ursulas, blockchain_ursulas,
mock_ursula_reencrypts mock_ursula_reencrypts
): ):
@ -56,10 +56,10 @@ def test_evaluate_cfrag(testerchain,
number_of_evaluations = 0 number_of_evaluations = 0
def compute_penalty_and_reward(stake: int, penalty_history: int) -> Tuple[int, int]: def compute_penalty_and_reward(stake: int, penalty_history: int) -> Tuple[int, int]:
penalty_ = slashing_economics.base_penalty penalty_ = token_economics.base_penalty
penalty_ += slashing_economics.penalty_history_coefficient * penalty_history penalty_ += token_economics.penalty_history_coefficient * penalty_history
penalty_ = min(penalty_, stake // slashing_economics.percentage_penalty_coefficient) penalty_ = min(penalty_, stake // token_economics.percentage_penalty_coefficient)
reward_ = penalty_ // slashing_economics.reward_coefficient reward_ = penalty_ // token_economics.reward_coefficient
return penalty_, reward_ return penalty_, reward_
# Prepare one staker # Prepare one staker
@ -206,7 +206,7 @@ def test_evaluate_cfrag(testerchain,
previous_penalty = penalty previous_penalty = penalty
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history) penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
# Penalty was increased because it's the second violation # 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 worker_stake -= penalty
investigator_balance += reward investigator_balance += reward
worker_penalty_history += 1 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, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
# Penalty has reached maximum available percentage of value # 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 worker_stake -= penalty
investigator_balance += reward investigator_balance += reward
worker_penalty_history += 1 worker_penalty_history += 1

View File

@ -20,6 +20,7 @@ import pytest
from web3.contract import Contract from web3.contract import Contract
from eth_utils import keccak from eth_utils import keccak
from nucypher.blockchain.economics import TokenEconomics
from nucypher.blockchain.eth.token import NU from nucypher.blockchain.eth.token import NU
VALUE_FIELD = 0 VALUE_FIELD = 0
@ -28,29 +29,33 @@ secret = (123456).to_bytes(32, byteorder='big')
@pytest.fixture() @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 # 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 return token
@pytest.fixture(params=[False, True]) @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): def make_escrow(max_allowed_locked_tokens):
# Creator deploys the escrow # Creator deploys the escrow
_staking_coefficient = 2 * 10 ** 7 deploy_parameters = list(token_economics.staking_deployment_parameters)
contract, _ = deploy_contract( deploy_parameters[-2] = max_allowed_locked_tokens
contract_name='StakingEscrow', contract, _ = deploy_contract('StakingEscrow', token.address, *deploy_parameters)
_token=token.address,
_hoursPerPeriod=1,
_miningCoefficient=4 * _staking_coefficient,
_lockedPeriodsCoefficient=4,
_rewardedPeriods=4,
_minLockedPeriods=2,
_minAllowableLockedTokens=100,
_maxAllowableLockedTokens=max_allowed_locked_tokens,
_minWorkerPeriods=1
)
if request.param: if request.param:
secret_hash = keccak(secret) secret_hash = keccak(secret)

View File

@ -22,7 +22,8 @@ from web3.contract import Contract
@pytest.mark.slow @pytest.mark.slow
def test_mining(testerchain, token, escrow_contract): def test_mining(testerchain, token, escrow_contract, token_economics):
escrow = escrow_contract(1500) escrow = escrow_contract(1500)
policy_manager_interface = testerchain.get_contract_factory('PolicyManagerForStakingEscrowMock') policy_manager_interface = testerchain.get_contract_factory('PolicyManagerForStakingEscrowMock')
policy_manager = testerchain.client.get_contract( policy_manager = testerchain.client.get_contract(
@ -33,6 +34,13 @@ def test_mining(testerchain, token, escrow_contract):
ursula1 = testerchain.client.accounts[1] ursula1 = testerchain.client.accounts[1]
ursula2 = testerchain.client.accounts[2] 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') staking_log = escrow.events.Mined.createFilter(fromBlock='latest')
deposit_log = escrow.events.Deposited.createFilter(fromBlock='latest') deposit_log = escrow.events.Deposited.createFilter(fromBlock='latest')
lock_log = escrow.events.Locked.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') withdraw_log = escrow.events.Withdrawn.createFilter(fromBlock='latest')
# Give Escrow tokens for reward and initialize contract # 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) testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().transact({'from': creator}) tx = escrow.functions.initialize().transact({'from': creator})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
@ -49,7 +57,7 @@ def test_mining(testerchain, token, escrow_contract):
# Give Ursula and Ursula(2) some coins # Give Ursula and Ursula(2) some coins
tx = token.functions.transfer(ursula1, 10000).transact({'from': creator}) tx = token.functions.transfer(ursula1, 10000).transact({'from': creator})
testerchain.wait_for_receipt(tx) 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) testerchain.wait_for_receipt(tx)
# Ursula can't confirm and mint because no locked tokens # 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) testerchain.wait_for_receipt(tx)
# Ursula and Ursula(2) transfer some tokens to the escrow and lock them # 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) testerchain.wait_for_receipt(tx)
tx = escrow.functions.setWorker(ursula1).transact({'from': ursula1}) tx = escrow.functions.setWorker(ursula1).transact({'from': ursula1})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
tx = escrow.functions.confirmActivity().transact({'from': ursula1}) tx = escrow.functions.confirmActivity().transact({'from': ursula1})
testerchain.wait_for_receipt(tx) 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) testerchain.wait_for_receipt(tx)
tx = escrow.functions.setWorker(ursula2).transact({'from': ursula2}) tx = escrow.functions.setWorker(ursula2).transact({'from': ursula2})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
@ -129,8 +139,11 @@ def test_mining(testerchain, token, escrow_contract):
current_period = escrow.functions.getCurrentPeriod().call() current_period = escrow.functions.getCurrentPeriod().call()
# Check result of mining # Check result of mining
assert 1046 == escrow.functions.getAllTokens(ursula1).call() total_locked = ursula1_stake + ursula2_stake
assert 525 == escrow.functions.getAllTokens(ursula2).call() 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 # Check that downtime value has not changed
assert 1 == escrow.functions.getPastDowntimeLength(ursula1).call() assert 1 == escrow.functions.getPastDowntimeLength(ursula1).call()
assert 1 == escrow.functions.getPastDowntimeLength(ursula2).call() assert 1 == escrow.functions.getPastDowntimeLength(ursula2).call()
@ -141,11 +154,11 @@ def test_mining(testerchain, token, escrow_contract):
assert 2 == len(events) assert 2 == len(events)
event_args = events[0]['args'] event_args = events[0]['args']
assert ursula1 == event_args['staker'] 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'] assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
event_args = events[1]['args'] event_args = events[1]['args']
assert ursula2 == event_args['staker'] 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'] assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
# Check parameters in call of the policy manager mock # 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 # Ursula tries to mint again and doesn't receive a reward
# There are no more confirmed periods that are ready to mint # 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}) tx = escrow.functions.mint().transact({'from': ursula1})
testerchain.wait_for_receipt(tx) 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() events = staking_log.get_all_entries()
assert 2 == len(events) assert 2 == len(events)
# Ursula can't confirm next period because stake is unlocked in current period # Ursula can't confirm next period because stake is unlocked in current period
testerchain.time_travel(hours=1) testerchain.time_travel(hours=1)
current_supply += ursula1_reward + ursula2_reward
with pytest.raises((TransactionFailed, ValueError)): with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.confirmActivity().transact({'from': ursula1}) tx = escrow.functions.confirmActivity().transact({'from': ursula1})
testerchain.wait_for_receipt(tx) 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 # But Ursula(2) can't get reward because she did not confirm activity
tx = escrow.functions.mint().transact({'from': ursula2}) tx = escrow.functions.mint().transact({'from': ursula2})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 1152 == escrow.functions.getAllTokens(ursula1).call() ursula1_reward = calculate_reward(500, 1000, 0) + calculate_reward(500, 1000, 1) + calculate_reward(500, 500, 0)
assert 525 == escrow.functions.getAllTokens(ursula2).call() 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() events = staking_log.get_all_entries()
assert 3 == len(events) assert 3 == len(events)
event_args = events[2]['args'] event_args = events[2]['args']
assert ursula1 == event_args['staker'] assert ursula1 == event_args['staker']
assert 106 == event_args['value'] assert ursula1_reward == event_args['value']
assert current_period == event_args['period'] assert current_period == event_args['period']
assert 4 == policy_manager.functions.getPeriodsLength(ursula1).call() assert 4 == policy_manager.functions.getPeriodsLength(ursula1).call()
@ -204,16 +222,19 @@ def test_mining(testerchain, token, escrow_contract):
# Ursula(2) mints tokens # Ursula(2) mints tokens
testerchain.time_travel(hours=1) testerchain.time_travel(hours=1)
current_supply += ursula1_reward
tx = escrow.functions.mint().transact({'from': ursula2}) tx = escrow.functions.mint().transact({'from': ursula2})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 1152 == escrow.functions.getAllTokens(ursula1).call() ursula2_reward = calculate_reward(500, 500, 0)
assert 575 == escrow.functions.getAllTokens(ursula2).call() 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() events = staking_log.get_all_entries()
assert 4 == len(events) assert 4 == len(events)
event_args = events[3]['args'] event_args = events[3]['args']
assert ursula2 == event_args['staker'] 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'] assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
current_period = escrow.functions.getCurrentPeriod().call() - 1 current_period = escrow.functions.getCurrentPeriod().call() - 1
@ -231,7 +252,7 @@ def test_mining(testerchain, token, escrow_contract):
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
current_period = escrow.functions.getCurrentPeriod().call() current_period = escrow.functions.getCurrentPeriod().call()
assert current_period - 2 == escrow.functions.getLastActivePeriod(ursula1).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 # Ursula still can't confirm activity
with pytest.raises((TransactionFailed, ValueError)): with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.confirmActivity().transact({'from': ursula1}) tx = escrow.functions.confirmActivity().transact({'from': ursula1})
@ -244,6 +265,7 @@ def test_mining(testerchain, token, escrow_contract):
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
tx = escrow.functions.lock(500, 2).transact({'from': ursula2}) tx = escrow.functions.lock(500, 2).transact({'from': ursula2})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
ursula2_stake += 250
assert 3 == escrow.functions.getPastDowntimeLength(ursula2).call() assert 3 == escrow.functions.getPastDowntimeLength(ursula2).call()
downtime = escrow.functions.getPastDowntime(ursula2, 2).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) # Ursula(2) mints only one period (by using deposit/approveAndCall function)
testerchain.time_travel(hours=5) testerchain.time_travel(hours=5)
current_supply += ursula2_reward
current_period = escrow.functions.getCurrentPeriod().call() current_period = escrow.functions.getCurrentPeriod().call()
assert current_period - 4 == escrow.functions.getLastActivePeriod(ursula2).call() assert current_period - 4 == escrow.functions.getLastActivePeriod(ursula2).call()
tx = token.functions.approveAndCall(escrow.address, 100, testerchain.w3.toBytes(2))\ tx = token.functions.approveAndCall(escrow.address, 100, testerchain.w3.toBytes(2))\
.transact({'from': ursula2}) .transact({'from': ursula2})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
ursula2_stake += 100
tx = escrow.functions.confirmActivity().transact({'from': ursula2}) tx = escrow.functions.confirmActivity().transact({'from': ursula2})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
assert 1152 == escrow.functions.getAllTokens(ursula1).call() ursula2_reward = calculate_reward(250, 750, 4) + calculate_reward(500, 750, 4)
assert 1025 == escrow.functions.getAllTokens(ursula2).call() 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() assert 4 == escrow.functions.getPastDowntimeLength(ursula2).call()
downtime = escrow.functions.getPastDowntime(ursula2, 3).call() downtime = escrow.functions.getPastDowntime(ursula2, 3).call()
assert current_period - 3 == downtime[0] assert current_period - 3 == downtime[0]
assert current_period == downtime[1] assert current_period == downtime[1]
ursula2_stake += ursula2_reward
assert 4 == policy_manager.functions.getPeriodsLength(ursula2).call() assert 4 == policy_manager.functions.getPeriodsLength(ursula2).call()
assert current_period - 4 == policy_manager.functions.getPeriod(ursula2, 3).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) assert 5 == len(events)
event_args = events[4]['args'] event_args = events[4]['args']
assert ursula2 == event_args['staker'] 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'] assert escrow.functions.getCurrentPeriod().call() - 1 == event_args['period']
# Ursula(2) confirms activity for remaining periods # Ursula(2) confirms activity for remaining periods
@ -288,17 +314,18 @@ def test_mining(testerchain, token, escrow_contract):
# Ursula(2) withdraws all # Ursula(2) withdraws all
testerchain.time_travel(hours=2) testerchain.time_travel(hours=2)
ursula2_stake = escrow.functions.getAllTokens(ursula2).call()
assert 0 == escrow.functions.getLockedTokens(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) testerchain.wait_for_receipt(tx)
assert 0 == escrow.functions.getAllTokens(ursula2).call() 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() events = withdraw_log.get_all_entries()
assert 1 == len(events) assert 1 == len(events)
event_args = events[0]['args'] event_args = events[0]['args']
assert ursula2 == event_args['staker'] 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 4 == len(deposit_log.get_all_entries())
assert 6 == len(lock_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 @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) escrow = escrow_contract(1500)
adjudicator, _ = deploy_contract( adjudicator, _ = deploy_contract(
'AdjudicatorForStakingEscrowMock', escrow.address 'AdjudicatorForStakingEscrowMock', escrow.address
@ -321,7 +348,7 @@ def test_slashing(testerchain, token, escrow_contract, deploy_contract):
slashing_log = escrow.events.Slashed.createFilter(fromBlock='latest') slashing_log = escrow.events.Slashed.createFilter(fromBlock='latest')
# Give Escrow tokens for reward and initialize contract # 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) testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().transact({'from': creator}) tx = escrow.functions.initialize().transact({'from': creator})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)

View File

@ -32,7 +32,7 @@ secret2 = (654321).to_bytes(32, byteorder='big')
@pytest.mark.slow @pytest.mark.slow
def test_upgrading(testerchain, token, deploy_contract): def test_upgrading(testerchain, token, token_economics, deploy_contract):
creator = testerchain.client.accounts[0] creator = testerchain.client.accounts[0]
staker = testerchain.client.accounts[1] staker = testerchain.client.accounts[1]
@ -41,16 +41,7 @@ def test_upgrading(testerchain, token, deploy_contract):
# Deploy contract # Deploy contract
contract_library_v1, _ = deploy_contract( contract_library_v1, _ = deploy_contract(
contract_name='StakingEscrow', 'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
_token=token.address,
_hoursPerPeriod=1,
_miningCoefficient=8*10**7,
_lockedPeriodsCoefficient=4,
_rewardedPeriods=4,
_minLockedPeriods=2,
_minAllowableLockedTokens=100,
_maxAllowableLockedTokens=1500,
_minWorkerPeriods=1
) )
dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address, secret_hash) 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, abi=contract_library_v2.abi,
address=dispatcher.address, address=dispatcher.address,
ContractFactoryClass=Contract) 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 # Can't call `finishUpgrade` and `verifyState` methods outside upgrade lifecycle
with pytest.raises((TransactionFailed, ValueError)): with pytest.raises((TransactionFailed, ValueError)):
@ -116,7 +107,7 @@ def test_upgrading(testerchain, token, deploy_contract):
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
# Check constructor and storage values # Check constructor and storage values
assert contract_library_v2.address == dispatcher.functions.target().call() 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 policy_manager.address == contract.functions.policyManager().call()
assert 2 == contract.functions.valueToCheck().call() assert 2 == contract.functions.valueToCheck().call()
# Check new ABI # Check new ABI
@ -248,7 +239,7 @@ def test_re_stake(testerchain, token, escrow_contract):
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
tx = token.functions.approve(escrow.address, 10000).transact({'from': ursula}) tx = token.functions.approve(escrow.address, 10000).transact({'from': ursula})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
sub_stake = 1000 sub_stake = 100
tx = escrow.functions.deposit(sub_stake, 10).transact({'from': ursula}) tx = escrow.functions.deposit(sub_stake, 10).transact({'from': ursula})
testerchain.wait_for_receipt(tx) testerchain.wait_for_receipt(tx)
tx = escrow.functions.setWorker(ursula).transact({'from': ursula}) 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: # 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 # 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 // 3
re_stake_for_second_sub_stake = ursula_reward - re_stake_for_first_sub_stake re_stake_for_first_sub_stake = ursula_reward - re_stake_for_second_sub_stake
# Check re-stake for Ursula1's sub stakes # Check re-stake for Ursula1's sub stakes
assert stake + ursula_reward == escrow.functions.getLockedTokens(ursula).call() 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] assert sub_stake_1 + re_stake_for_first_sub_stake == escrow.functions.getSubStakeInfo(ursula, 0).call()[3]

View File

@ -83,7 +83,7 @@ def test_rapid_deployment(token_economics, test_registry):
beneficiary_address = acct.address beneficiary_address = acct.address
amount = random.randint(token_economics.minimum_allowed_locked, token_economics.maximum_allowed_locked) 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, 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} random_allocation = {'beneficiary_address': beneficiary_address, 'amount': amount, 'duration_seconds': duration}
allocation_data.append(random_allocation) allocation_data.append(random_allocation)

View File

@ -45,8 +45,7 @@ def test_investigator_requests_slashing(testerchain,
test_registry, test_registry,
session_agency, session_agency,
mock_ursula_reencrypts, mock_ursula_reencrypts,
token_economics, token_economics):
slashing_economics):
testerchain = testerchain testerchain = testerchain
staker_account = testerchain.staker_account(0) staker_account = testerchain.staker_account(0)
@ -109,5 +108,5 @@ def test_investigator_requests_slashing(testerchain,
investigator_reward = investigator.token_balance - bobby_old_balance investigator_reward = investigator.token_balance - bobby_old_balance
assert investigator_reward > 0 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 assert staker.locked_tokens(periods=1) < locked_tokens

View File

@ -81,7 +81,8 @@ def test_staker_divides_stake(staker, token_economics):
value=yet_another_stake_value, value=yet_another_stake_value,
checksum_address=staker.checksum_address, checksum_address=staker.checksum_address,
index=3, 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 4 == len(staker.stakes), 'A new stake was not added after two stake divisions'
assert expected_old_stake == staker.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid after two stake divisions' assert expected_old_stake == staker.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid after two stake divisions'

View File

@ -47,7 +47,6 @@ def test_adjudicator_slashes(agency,
testerchain, testerchain,
mock_ursula_reencrypts, mock_ursula_reencrypts,
token_economics, token_economics,
slashing_economics,
test_registry): test_registry):
staker_account = testerchain.staker_account(0) staker_account = testerchain.staker_account(0)
@ -113,5 +112,5 @@ def test_adjudicator_slashes(agency,
investigator_reward = bobby.token_balance - bobby_old_balance investigator_reward = bobby.token_balance - bobby_old_balance
assert investigator_reward > 0 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 assert staker.locked_tokens(periods=1) < locked_tokens

View File

@ -30,7 +30,7 @@ from nucypher.blockchain.eth.deployers import (
@pytest.mark.slow() @pytest.mark.slow()
def test_adjudicator_deployer(testerchain, def test_adjudicator_deployer(testerchain,
slashing_economics, token_economics,
deployment_progress, deployment_progress,
test_registry): test_registry):
testerchain = testerchain testerchain = testerchain
@ -61,11 +61,11 @@ def test_adjudicator_deployer(testerchain,
# Check default Adjudicator deployment parameters # Check default Adjudicator deployment parameters
assert staking_escrow_deployer.deployer_address != staking_agent.contract_address assert staking_escrow_deployer.deployer_address != staking_agent.contract_address
assert adjudicator_agent.staking_escrow_contract == 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.hash_algorithm == token_economics.hash_algorithm
assert adjudicator_agent.base_penalty == slashing_economics.base_penalty assert adjudicator_agent.base_penalty == token_economics.base_penalty
assert adjudicator_agent.penalty_history_coefficient == slashing_economics.penalty_history_coefficient assert adjudicator_agent.penalty_history_coefficient == token_economics.penalty_history_coefficient
assert adjudicator_agent.percentage_penalty_coefficient == slashing_economics.percentage_penalty_coefficient assert adjudicator_agent.percentage_penalty_coefficient == token_economics.percentage_penalty_coefficient
assert adjudicator_agent.reward_coefficient == slashing_economics.reward_coefficient assert adjudicator_agent.reward_coefficient == token_economics.reward_coefficient
# Retrieve the AdjudicatorAgent singleton # Retrieve the AdjudicatorAgent singleton
some_policy_agent = AdjudicatorAgent(registry=test_registry) some_policy_agent = AdjudicatorAgent(registry=test_registry)

View File

@ -74,7 +74,7 @@ def test_deploy_and_allocate(session_agency, user_escrow_proxy, token_economics,
for address, deployer in deployments.items(): for address, deployer in deployments.items():
assert deployer.deployer_address == origin 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) deposit_receipts.append(deposit_receipt)
beneficiary = random.choice(testerchain.unassigned_accounts) beneficiary = random.choice(testerchain.unassigned_accounts)

View File

@ -19,7 +19,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
from decimal import Decimal, localcontext from decimal import Decimal, localcontext
from math import log from math import log
from nucypher.blockchain.economics import TokenEconomics, LOG2 from nucypher.blockchain.economics import LOG2, StandardTokenEconomics
def test_rough_economics(): def test_rough_economics():
@ -37,11 +37,11 @@ def test_rough_economics():
where allLockedPeriods == min(T, T1) where allLockedPeriods == min(T, T1)
""" """
e = TokenEconomics(initial_supply=int(1e9), e = StandardTokenEconomics(initial_supply=int(1e9),
initial_inflation=1, initial_inflation=1,
halving_delay=2, halving_delay=2,
reward_saturation=1, reward_saturation=1,
small_stake_multiplier=Decimal(0.5)) small_stake_multiplier=Decimal(0.5))
assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper 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 # Use same precision as economics class
with localcontext() as ctx: with localcontext() as ctx:
ctx.prec = TokenEconomics._precision ctx.prec = StandardTokenEconomics._precision
# Sanity check expected testing outputs # Sanity check expected testing outputs
assert Decimal(expected_total_supply) / expected_initial_supply == expected_supply_ratio assert Decimal(expected_total_supply) / expected_initial_supply == expected_supply_ratio
@ -134,10 +134,10 @@ def test_exact_economics():
# #
# Check creation # Check creation
e = TokenEconomics() e = StandardTokenEconomics()
with localcontext() as ctx: with localcontext() as ctx:
ctx.prec = TokenEconomics._precision ctx.prec = StandardTokenEconomics._precision
# Check that total_supply calculated correctly # Check that total_supply calculated correctly
assert Decimal(e.erc20_total_supply) / e.initial_supply == expected_supply_ratio 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_initial_supply == expected_initial_supply
assert e.erc20_reward_supply == expected_reward_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(): def test_economic_parameter_aliases():
e = TokenEconomics() e = StandardTokenEconomics()
assert e.locked_periods_coefficient == 365 assert e.locked_periods_coefficient == 365
assert int(e.staking_coefficient) == 768812 assert int(e.staking_coefficient) == 768812
assert e.maximum_locked_periods == 365 assert e.maximum_rewarded_periods == 365
deployment_params = e.staking_deployment_parameters deployment_params = e.staking_deployment_parameters
assert isinstance(deployment_params, tuple) assert isinstance(deployment_params, tuple)

View File

@ -3,7 +3,7 @@ from decimal import InvalidOperation, Decimal
import pytest import pytest
from web3 import Web3 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.blockchain.eth.token import NU, Stake
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
@ -99,7 +99,7 @@ def test_NU(token_economics):
_nan = NU(float('NaN'), 'NU') _nan = NU(float('NaN'), 'NU')
def test_stake(testerchain, agency): def test_stake(testerchain, token_economics, agency):
token_agent, staking_agent, _policy_agent = agency token_agent, staking_agent, _policy_agent = agency
class FakeUrsula: class FakeUrsula:
@ -110,7 +110,6 @@ def test_stake(testerchain, agency):
staking_agent = staking_agent staking_agent = staking_agent
token_agent = token_agent token_agent = token_agent
blockchain = testerchain blockchain = testerchain
economics = TokenEconomics()
ursula = FakeUrsula() ursula = FakeUrsula()
stake = Stake(checksum_address=ursula.checksum_address, stake = Stake(checksum_address=ursula.checksum_address,
@ -118,7 +117,8 @@ def test_stake(testerchain, agency):
final_locked_period=100, final_locked_period=100,
value=NU(100, 'NU'), value=NU(100, 'NU'),
index=0, index=0,
staking_agent=staking_agent) staking_agent=staking_agent,
economics=token_economics)
assert stake.value, 'NU' == NU(100, 'NU') assert stake.value, 'NU' == NU(100, 'NU')

View File

@ -133,7 +133,8 @@ def test_stake_init(click_runner,
stake = Stake.from_stake_info(index=0, stake = Stake.from_stake_info(index=0,
checksum_address=manual_staker, checksum_address=manual_staker,
stake_info=stakes[0], stake_info=stakes[0],
staking_agent=staking_agent) staking_agent=staking_agent,
economics=token_economics)
assert stake.value == stake_value assert stake.value == stake_value
assert stake.duration == token_economics.minimum_locked_periods assert stake.duration == token_economics.minimum_locked_periods

View File

@ -32,7 +32,7 @@ from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer from umbral.signing import Signer
from web3 import Web3 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.actors import Staker
from nucypher.blockchain.eth.agents import NucypherTokenAgent from nucypher.blockchain.eth.agents import NucypherTokenAgent
from nucypher.blockchain.eth.clients import NuCypherGethDevProcess from nucypher.blockchain.eth.clients import NuCypherGethDevProcess
@ -343,13 +343,7 @@ def federated_ursulas(ursula_federated_test_config):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def token_economics(): def token_economics():
economics = TokenEconomics() economics = StandardTokenEconomics()
return economics
@pytest.fixture(scope='session')
def slashing_economics():
economics = SlashingEconomics()
return economics return economics
@ -432,7 +426,7 @@ def _make_agency(testerchain, test_registry):
adjudicator_deployer.deploy(secret_hash=os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH)) adjudicator_deployer.deploy(secret_hash=os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH))
token_agent = token_deployer.make_agent() # 1 Token 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 policy_agent = policy_manager_deployer.make_agent() # 3 Policy Agent
_adjudicator_agent = adjudicator_deployer.make_agent() # 4 Adjudicator _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) amount = random.randint(min_stake, balance)
# for a random lock duration # 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) periods = random.randint(min_locktime, max_locktime)
staker.initialize_stake(amount=amount, lock_periods=periods) staker.initialize_stake(amount=amount, lock_periods=periods)

View File

@ -32,7 +32,7 @@ from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer from umbral.signing import Signer
from zope.interface import provider 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.agents import NucypherTokenAgent, StakingEscrowAgent, PolicyManagerAgent, AdjudicatorAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry from nucypher.blockchain.eth.registry import InMemoryContractRegistry
@ -46,11 +46,11 @@ from fixtures import _mock_ursula_reencrypts as mock_ursula_reencrypts
ALGORITHM_SHA256 = 1 ALGORITHM_SHA256 = 1
TOKEN_ECONOMICS = TokenEconomics() TOKEN_ECONOMICS = StandardTokenEconomics()
MIN_ALLOWED_LOCKED = TOKEN_ECONOMICS.minimum_allowed_locked MIN_ALLOWED_LOCKED = TOKEN_ECONOMICS.minimum_allowed_locked
MIN_LOCKED_PERIODS = TOKEN_ECONOMICS.minimum_locked_periods MIN_LOCKED_PERIODS = TOKEN_ECONOMICS.minimum_locked_periods
MAX_ALLOWED_LOCKED = TOKEN_ECONOMICS.maximum_allowed_locked 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: class AnalyzeGas:
@ -155,7 +155,13 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
log = Logger(AnalyzeGas.LOG_NAME) log = Logger(AnalyzeGas.LOG_NAME)
# Blockchain # 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 web3 = testerchain.w3
# Accounts # Accounts