def __init__(self, checksum_address: str, policy_agent: PolicyAgent = None, economics: TokenEconomics = None, *args, **kwargs) -> None: """ :param policy_agent: A policy agent with the blockchain attached; If not passed, a default policy agent and blockchain connection will be created from default values. """ super().__init__(checksum_address=checksum_address, *args, **kwargs) # From defaults if not policy_agent: self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) self.policy_agent = PolicyAgent(blockchain=self.blockchain) # Injected else: self.policy_agent = policy_agent self.economics = economics or TokenEconomics()
def __init__(self, blockchain: BlockchainInterface, sync_now: bool = True, *args, **kwargs): super().__init__(*args, **kwargs) self.log = Logger(f"stakeholder") # Blockchain and Contract connection self.blockchain = blockchain self.staking_agent = StakingEscrowAgent(blockchain=blockchain) self.token_agent = NucypherTokenAgent(blockchain=blockchain) self.economics = TokenEconomics() # Mode self.connect(blockchain=blockchain) self.__accounts = list() self.__stakers = dict() self.__transacting_powers = dict() self.__get_accounts() if sync_now: self.read_onchain_stakes() # Stakes
def __init__(self, checksum_address: str, policy_agent=None, economics: TokenEconomics = None, *args, **kwargs) -> None: """ :param policy_agent: A policy agent with the blockchain attached; If not passed, a default policy agent and blockchain connection will be created from default values. """ super().__init__(checksum_address=checksum_address, *args, **kwargs) if not policy_agent: # From defaults self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.miner_agent = MinerAgent(blockchain=self.blockchain) self.policy_agent = PolicyAgent(blockchain=self.blockchain) else: # Injected self.policy_agent = policy_agent if not economics: economics = TokenEconomics() self.economics = economics
def __init__(self, economics: TokenEconomics = None, *args, **kwargs): super().__init__(*args, **kwargs) self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) if not economics: economics = TokenEconomics() self.__economics = economics
def __init__(self, db_filepath: str, rest_host: str, rest_port: int, client_password: str = None, crash_on_error: bool = False, economics: TokenEconomics = None, distribute_ether: bool = True, *args, **kwargs): # Character super().__init__(*args, **kwargs) self.log = Logger(f"felix-{self.checksum_address[-6::]}") # Network self.rest_port = rest_port self.rest_host = rest_host self.rest_app = NOT_RUNNING self.crash_on_error = crash_on_error # Database self.db_filepath = db_filepath self.db = NO_DATABASE_AVAILABLE self.db_engine = create_engine(f'sqlite:///{self.db_filepath}', convert_unicode=True) # Blockchain transacting_power = TransactingPower(blockchain=self.blockchain, password=client_password, account=self.checksum_address) self._crypto_power.consume_power_up(transacting_power) self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) self.reserved_addresses = [self.checksum_address, BlockchainInterface.NULL_ADDRESS] # Update reserved addresses with deployed contracts existing_entries = list(self.blockchain.registry.enrolled_addresses) self.reserved_addresses.extend(existing_entries) # Distribution self.__distributed = 0 # Track NU Output self.__airdrop = 0 # Track Batch self.__disbursement = 0 # Track Quantity self._distribution_task = LoopingCall(f=self.airdrop_tokens) 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 # Optionally send ether with each token transaction self.distribute_ether = distribute_ether # Banner self.log.info(FELIX_BANNER.format(self.checksum_address))
class FakeUrsula: token_agent, staking_agent, _policy_agent = agency burner_wallet = Web3().eth.account.create( INSECURE_DEVELOPMENT_PASSWORD) checksum_address = burner_wallet.address staking_agent = staking_agent token_agent = token_agent blockchain = testerchain economics = TokenEconomics()
class FakeUrsula: token_agent, miner_agent, _policy_agent = three_agents burner_wallet = Web3().eth.account.create( INSECURE_DEVELOPMENT_PASSWORD) checksum_public_address = burner_wallet.address miner_agent = miner_agent token_agent = token_agent blockchain = testerchain economics = TokenEconomics()
def __init__(self, deployer_address: str, economics: TokenEconomics = None, *args, **kwargs) -> None: super().__init__(deployer_address=deployer_address, *args, **kwargs) self._creator = deployer_address if not economics: economics = TokenEconomics() self.__economics = economics
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
def test_economic_parameter_aliases(): e = TokenEconomics() assert e.locked_periods_coefficient == 365 assert int(e.staking_coefficient) == 768812 assert e.maximum_locked_periods == 365 deployment_params = e.staking_deployment_parameters assert isinstance(deployment_params, tuple) for parameter in deployment_params: assert isinstance(parameter, int)
def __init__(self, is_me: bool, economics: TokenEconomics = None, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.log = Logger("staker") self.stake_tracker = StakeTracker( checksum_addresses=[self.checksum_address]) self.staking_agent = StakingEscrowAgent(blockchain=self.blockchain) self.economics = economics or TokenEconomics() self.is_me = is_me
def make_decentralized_ursulas(ursula_config: UrsulaConfiguration, ether_addresses: Union[list, int], stake: bool = False, know_each_other: bool = True, economics: TokenEconomics = None, **ursula_overrides) -> List[Ursula]: if not economics: economics = TokenEconomics() # Alternately accepts an int of the quantity of ursulas to make if isinstance(ether_addresses, int): ether_addresses = [to_checksum_address(secure_random(20)) for _ in range(ether_addresses)] if not MOCK_KNOWN_URSULAS_CACHE: starting_port = MOCK_URSULA_STARTING_PORT else: starting_port = max(MOCK_KNOWN_URSULAS_CACHE.keys()) + 1 ursulas = list() for port, checksum_address in enumerate(ether_addresses, start=starting_port): ursula = ursula_config.produce(checksum_public_address=checksum_address, db_filepath=MOCK_URSULA_DB_FILEPATH, rest_port=port + 100, **ursula_overrides) if stake is True: min_stake, balance = economics.minimum_allowed_locked, ursula.token_balance amount = random.randint(min_stake, balance) # for a random lock duration min_locktime, max_locktime = economics.minimum_locked_periods, economics.maximum_locked_periods periods = random.randint(min_locktime, max_locktime) ursula.initialize_stake(amount=amount, lock_periods=periods) ursulas.append(ursula) # Store this Ursula in our global cache. port = ursula.rest_information()[0].port MOCK_KNOWN_URSULAS_CACHE[port] = ursula if know_each_other: for ursula_to_teach in ursulas: # Add other Ursulas as known nodes. for ursula_to_learn_about in ursulas: ursula_to_teach.remember_node(ursula_to_learn_about) return ursulas
def __init__(self, is_me: bool, start_staking_loop: bool = True, economics: TokenEconomics = None, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.log = Logger("miner") self.is_me = is_me if not economics: economics = TokenEconomics() self.economics = economics # # Blockchain # if is_me: self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) # Staking Loop self.__current_period = None self._abort_on_staking_error = True self._staking_task = task.LoopingCall(self.heartbeat) else: self.token_agent = STRANGER_MINER self.miner_agent = MinerAgent(blockchain=self.blockchain) # # Stakes # self.__stakes = UNKNOWN_STAKES self.__start_time = NOT_STAKING self.__uptime_period = NOT_STAKING self.__terminal_period = UNKNOWN_STAKES self.__read_stakes() # "load-in": Read on-chain stakes # Start the callbacks if there are active stakes if (self.stakes is not NO_STAKES) and start_staking_loop: self.stake()
def test_rough_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2 K2 - Staking coefficient K1 - Locked periods coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 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)) assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper # Check that we have correct numbers in day 1 initial_rate = (e.erc20_total_supply - e.initial_supply) * ( e.locked_periods_coefficient + 365) / e.staking_coefficient assert int(initial_rate) == int(e.initial_inflation * e.initial_supply / 365) initial_rate_small = ( e.erc20_total_supply - e.initial_supply ) * e.locked_periods_coefficient / e.staking_coefficient assert int(initial_rate_small) == int(initial_rate / 2) # Sanity check that total and reward supply calculated correctly assert int(LOG2 / (e.token_halving * 365) * (e.erc20_total_supply - e.initial_supply)) == int(initial_rate) assert int(e.reward_supply) == int(e.erc20_total_supply - Decimal(int(1e9))) # Sanity check for locked_periods_coefficient (k1) and staking_coefficient (k2) assert e.locked_periods_coefficient * e.token_halving == e.staking_coefficient * LOG2 * e.small_stake_multiplier / 365
def __init__(self, checksum_address: str, value: NU, start_period: int, end_period: int, index: int, economics=None, validate_now: bool = True): self.log = Logger(f'stake-{checksum_address}-{index}') # Stake Metadata self.owner_address = checksum_address self.worker_address = UNKNOWN_WORKER_STATUS self.index = index self.value = value self.start_period = start_period self.end_period = end_period # Time self.start_datetime = datetime_at_period(period=start_period) self.end_datetime = datetime_at_period(period=end_period) self.duration_delta = self.end_datetime - self.start_datetime # Agency self.staking_agent = None self.token_agent = NucypherTokenAgent() # TODO: Use Agency self.blockchain = self.token_agent.blockchain # Economics from nucypher.blockchain.economics import TokenEconomics self.economics = economics or TokenEconomics() self.minimum_nu = NU(int(self.economics.minimum_allowed_locked), 'NuNit') self.maximum_nu = NU(int(self.economics.maximum_allowed_locked), 'NuNit') if validate_now: self.validate_duration() self.transactions = NO_STAKING_RECEIPT self.receipt = NO_STAKING_RECEIPT
def test_inflation_rate(testerchain, token, deploy_contract): """ Check decreasing of inflation rate after minting. 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( 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, economics.erc20_reward_supply).transact({'from': creator}) testerchain.wait_for_receipt(tx) tx = issuer.functions.initialize().transact({'from': creator}) testerchain.wait_for_receipt(tx) reward = issuer.functions.getReservedReward().call() # Mint some tokens and save result of minting period = issuer.functions.getCurrentPeriod().call() tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) one_period = token.functions.balanceOf(ursula).call() # Mint more tokens in the same period, inflation rate must be the same as in previous minting tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) assert 2 * one_period == token.functions.balanceOf(ursula).call() assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call() # Mint tokens in the next period, inflation rate must be lower than in previous minting tx = issuer.functions.testMint(period + 2, 1, 1, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) assert 3 * one_period > token.functions.balanceOf(ursula).call() assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call() minted_amount = token.functions.balanceOf(ursula).call() - 2 * one_period # Mint tokens in the first period again, inflation rate must be the same as in previous minting # but can't be equals as in first minting because rate can't be increased tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) assert 2 * one_period + 2 * minted_amount == token.functions.balanceOf(ursula).call() assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call() # Mint tokens in the next period, inflation rate must be lower than in previous minting tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) assert 2 * one_period + 3 * minted_amount > token.functions.balanceOf(ursula).call() assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call() # Return some tokens as a reward balance = token.functions.balanceOf(ursula).call() reward = issuer.functions.getReservedReward().call() tx = issuer.functions.testUnMint(2 * one_period + 2 * minted_amount).transact() testerchain.wait_for_receipt(tx) assert reward + 2 * one_period + 2 * minted_amount == issuer.functions.getReservedReward().call() # Rate will be increased because some tokens were returned tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) assert balance + one_period == token.functions.balanceOf(ursula).call() assert reward + one_period + 2 * minted_amount == issuer.functions.getReservedReward().call()
value, param, ctx)) class IPv4Address(click.ParamType): name = 'ipv4_address' def convert(self, value, param, ctx): try: _address = ip_address(value) except ValueError as e: self.fail(str(e)) else: return value token_economics = TokenEconomics() # Staking STAKE_DURATION = click.IntRange(min=token_economics.minimum_locked_periods, max=token_economics.maximum_locked_periods, clamp=False) STAKE_EXTENSION = click.IntRange(min=1, max=token_economics.maximum_allowed_locked, clamp=False) STAKE_VALUE = click.IntRange(min=NU(token_economics.minimum_allowed_locked, 'NuNit').to_tokens(), max=NU(token_economics.maximum_allowed_locked, 'NuNit').to_tokens(), clamp=False) # Filesystem
from umbral.keys import UmbralPrivateKey from umbral.signing import Signer from zope.interface import provider from nucypher.blockchain.economics import TokenEconomics from nucypher.blockchain.eth.agents import NucypherTokenAgent, StakingEscrowAgent, PolicyAgent, AdjudicatorAgent from nucypher.crypto.signing import SignatureStamp from nucypher.policy.models import Policy from nucypher.utilities.sandbox.blockchain import TesterBlockchain # FIXME: Needed to use a fixture here, but now estimate_gas.py only runs if executed from main directory sys.path.insert(0, abspath('tests')) from fixtures import _mock_ursula_reencrypts as mock_ursula_reencrypts ALGORITHM_SHA256 = 1 TOKEN_ECONOMICS = TokenEconomics() 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 class AnalyzeGas: """ Callable twisted log observer with built-in record-keeping for gas estimation runs. """ # Logging LOG_NAME = 'estimate-gas' LOG_FILENAME = '{}.log.json'.format(LOG_NAME) OUTPUT_DIR = os.path.join(abspath(dirname(__file__)), 'results')
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( 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( 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 tx = token.functions.transfer(issuer.address, economics.erc20_reward_supply).transact({'from': creator}) testerchain.wait_for_receipt(tx) # Only owner can initialize with pytest.raises((TransactionFailed, ValueError)): tx = issuer.functions.initialize().transact({'from': ursula}) testerchain.wait_for_receipt(tx) tx = issuer.functions.initialize().transact({'from': creator}) testerchain.wait_for_receipt(tx) events = events.get_all_entries() assert 1 == len(events) assert economics.erc20_reward_supply == events[0]['args']['reservedReward'] balance = token.functions.balanceOf(issuer.address).call() # Can't initialize second time with pytest.raises((TransactionFailed, ValueError)): tx = issuer.functions.initialize().transact({'from': creator}) testerchain.wait_for_receipt(tx) # Check result of minting tokens tx = issuer.functions.testMint(0, 1000, 2000, 0).transact({'from': ursula}) testerchain.wait_for_receipt(tx) 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) 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) 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) reward += calculate_reward(500, 500, 10 ** 4) assert reward == token.functions.balanceOf(ursula).call() assert balance - reward == token.functions.balanceOf(issuer.address).call()
def token_economics(): economics = TokenEconomics() return economics
def test_exact_economics(): """ Formula for staking in one period: (totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2 K2 - Staking coefficient K1 - Locked periods coefficient if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / k2 kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1 where allLockedPeriods == min(T, T1) """ # # Expected Output # # Supply expected_total_supply = 3885390081777926911255691439 expected_supply_ratio = Decimal('3.885390081777926911255691439') expected_initial_supply = 1000000000000000000000000000 # Reward expected_reward_supply = 2885390081777926911255691439 reward_saturation = 1 # Staking halving = 2 multiplier = 0.5 expected_locked_periods_coefficient = 365 expected_staking_coefficient = 768812 assert expected_locked_periods_coefficient * halving == round( expected_staking_coefficient * log(2) * multiplier / 365) # # Sanity # # Sanity check ratio accuracy expected_scaled_ratio = str(expected_supply_ratio).replace('.', '') assert str(expected_total_supply) == expected_scaled_ratio # Sanity check denomination size expected_scale = 28 assert len(str(expected_total_supply)) == expected_scale assert len(str(expected_initial_supply)) == expected_scale assert len(str(expected_reward_supply)) == expected_scale # Use same precision as economics class with localcontext() as ctx: ctx.prec = TokenEconomics._precision # Sanity check expected testing outputs assert Decimal(expected_total_supply ) / expected_initial_supply == expected_supply_ratio assert expected_reward_supply == expected_total_supply - expected_initial_supply assert reward_saturation * 365 * multiplier == expected_locked_periods_coefficient * ( 1 - multiplier) assert int(365**2 * reward_saturation * halving / log(2) / (1 - multiplier)) == expected_staking_coefficient # After sanity checking, assemble expected test deployment parameters expected_deployment_parameters = ( 24, # Hours in single period 768812, # Staking coefficient (k2) 365, # Locked periods coefficient (k1) 365, # Max periods that will be additionally rewarded (awarded_periods) 30, # Min amount of periods during which tokens can be locked 15000000000000000000000, # min locked NuNits 4000000000000000000000000) # max locked NuNits # # Token Economics # # Check creation e = TokenEconomics() with localcontext() as ctx: ctx.prec = TokenEconomics._precision # Check that total_supply calculated correctly assert Decimal( e.erc20_total_supply) / e.initial_supply == expected_supply_ratio assert e.erc20_total_supply == expected_total_supply # Check reward rates initial_rate = Decimal( (e.erc20_total_supply - e.initial_supply) * (e.locked_periods_coefficient + 365) / e.staking_coefficient) assert initial_rate == Decimal( (e.initial_inflation * e.initial_supply) / 365) initial_rate_small = ( e.erc20_total_supply - e.initial_supply ) * e.locked_periods_coefficient / e.staking_coefficient assert Decimal(initial_rate_small) == Decimal(initial_rate / 2) # Check reward supply assert Decimal( LOG2 / (e.token_halving * 365) * (e.erc20_total_supply - e.initial_supply)) == initial_rate assert e.reward_supply == expected_total_supply - expected_initial_supply # Check deployment parameters assert e.staking_deployment_parameters == expected_deployment_parameters assert e.erc20_initial_supply == expected_initial_supply assert e.erc20_reward_supply == expected_reward_supply