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