From f1b5ed4861a2d0372eb1f5801382cbdf6356f51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Wed, 29 May 2019 11:11:54 +0200 Subject: [PATCH 01/14] Additional functions in TokenEconomics for checking token supply --- nucypher/blockchain/economics.py | 22 +++++++++++++++++++ .../eth/entities/deployers/test_economics.py | 21 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index ad3a2fcd0..621f624e4 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -169,6 +169,28 @@ class TokenEconomics: ) return tuple(map(int, deploy_parameters)) + def token_supply_function(self, at_period: int) -> int: + if at_period < 0: + raise ValueError("Period must be a positive integer") + + # Eq. 3 of the mining paper + t = Decimal(at_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, at_period: int) -> int: + return self.token_supply_function(at_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: diff --git a/tests/blockchain/eth/entities/deployers/test_economics.py b/tests/blockchain/eth/entities/deployers/test_economics.py index 8a71789ca..08c85f95a 100644 --- a/tests/blockchain/eth/entities/deployers/test_economics.py +++ b/tests/blockchain/eth/entities/deployers/test_economics.py @@ -159,6 +159,27 @@ 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_function(at_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_function(at_period=67000) + + # After 1 year: + assert 1845111188584347879497984668 == e.token_supply_function(at_period=365) + assert 845111188584347879497984668 == e.cumulative_rewards_at_period(365) + assert e.erc20_initial_supply + e.cumulative_rewards_at_period(365) == e.token_supply_function(at_period=365) + + # Checking that the supply function is monotonic + todays_supply = e.token_supply_function(at_period=0) + for t in range(67000): + tomorrows_supply = e.token_supply_function(at_period=t+1) + assert tomorrows_supply >= todays_supply + todays_supply = tomorrows_supply + def test_economic_parameter_aliases(): From adc7b3f826f7322094dfcc65107fbb2ca8efb028 Mon Sep 17 00:00:00 2001 From: szotov Date: Thu, 6 Jun 2019 18:37:57 +0300 Subject: [PATCH 02/14] Test for economics in contract --- nucypher/blockchain/economics.py | 21 +++--- .../integration/test_contract_economics.py | 65 +++++++++++++++++++ 2 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 tests/blockchain/eth/contracts/integration/test_contract_economics.py diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index 621f624e4..b050d7307 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -173,16 +173,19 @@ class TokenEconomics: if at_period < 0: raise ValueError("Period must be a positive integer") - # Eq. 3 of the mining paper - t = Decimal(at_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 + with localcontext() as ctx: + ctx.prec = self._precision - S_t = S_0 + I_0 * T_half * (1 - 2**(-t / T_half_in_days)) / LOG2 - return int(S_t) + # Eq. 3 of the mining paper + t = Decimal(at_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, at_period: int) -> int: return self.token_supply_function(at_period) - self.erc20_initial_supply 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..8fb708c26 --- /dev/null +++ b/tests/blockchain/eth/contracts/integration/test_contract_economics.py @@ -0,0 +1,65 @@ +""" +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 + + +# Experimental max error +MAX_ERROR = 0.0004751 +MAX_PERIODS = 100 + + +@pytest.mark.slow +def test_reward(testerchain, three_agents, token_economics): + testerchain.time_travel(hours=1) + token_agent, miner_agent, _policy_agent = three_agents + 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) + _txhash = token_agent.approve_transfer(amount=token_economics.minimum_allowed_locked, + target_address=miner_agent.contract_address, + sender_address=ursula) + _txhash = miner_agent.deposit_tokens(amount=token_economics.minimum_allowed_locked, + lock_periods=100 * token_economics.maximum_locked_periods, + sender_address=ursula) + + # Get a reward for one period + _txhash = miner_agent.confirm_activity(node_address=ursula) + testerchain.time_travel(periods=1) + _txhash = miner_agent.confirm_activity(node_address=ursula) + assert miner_agent.calculate_staking_reward(checksum_address=ursula) == 0 + testerchain.time_travel(periods=1) + _txhash = miner_agent.confirm_activity(node_address=ursula) + + contract_reward = miner_agent.calculate_staking_reward(checksum_address=ursula) + calculations_reward = token_economics.cumulative_rewards_at_period(1) + error = (contract_reward - calculations_reward) / calculations_reward + assert error < MAX_ERROR + + # Get a reward for ten periods + for i in range(1, MAX_PERIODS): + testerchain.time_travel(periods=1) + _txhash = miner_agent.confirm_activity(node_address=ursula) + contract_reward = miner_agent.calculate_staking_reward(checksum_address=ursula) + calculations_reward = token_economics.cumulative_rewards_at_period(i + 1) + next_error = (contract_reward - calculations_reward) / calculations_reward + assert next_error < error + error = next_error From 15c9a7569bfc0cd3c4befb3da1c218a45b38c71d Mon Sep 17 00:00:00 2001 From: szotov Date: Sun, 9 Jun 2019 19:09:33 +0300 Subject: [PATCH 03/14] Fixes #642 --- .../eth/sol/source/contracts/Issuer.sol | 16 ++-- .../eth/contracts/base/test_issuer.py | 53 ++++++++++--- .../main/staking_escrow/test_staking.py | 74 +++++++++++++------ .../test_staking_escrow_additional.py | 6 +- 4 files changed, 101 insertions(+), 48 deletions(-) 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/tests/blockchain/eth/contracts/base/test_issuer.py b/tests/blockchain/eth/contracts/base/test_issuer.py index 23baa9df1..c82e53041 100644 --- a/tests/blockchain/eth/contracts/base/test_issuer.py +++ b/tests/blockchain/eth/contracts/base/test_issuer.py @@ -20,31 +20,56 @@ import pytest from eth_tester.exceptions import TransactionFailed from web3.contract import Contract +from nucypher.blockchain.eth.token import NU + SECRET_LENGTH = 32 @pytest.fixture() def token(testerchain, deploy_contract): # Create an ERC20 token - token, _ = deploy_contract('NuCypherToken', 2 * 10 ** 40) + token, _ = deploy_contract('NuCypherToken', _totalSupply=int(NU(2 * 10 ** 40, 'NuNit'))) return token @pytest.mark.slow def test_issuer(testerchain, token, deploy_contract): + mining_coefficient = 10 ** 43 + locked_periods_coefficient = 10 ** 4 + total_supply = token.functions.totalSupply().call() + current_supply = 10 ** 30 + reserved_reward = total_supply - current_supply + + def calculate_reward(locked, total_locked, locked_periods): + return reserved_reward * locked * (locked_periods + locked_periods_coefficient) // \ + (total_locked * mining_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=1, + _miningCoefficient=mining_coefficient, + _lockedPeriodsCoefficient=locked_periods_coefficient, + _rewardedPeriods=10 ** 4 + ) # 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=1, + _miningCoefficient=mining_coefficient, + _lockedPeriodsCoefficient=locked_periods_coefficient, + _rewardedPeriods=10 ** 4 + ) 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}) testerchain.wait_for_receipt(tx) @@ -68,26 +93,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 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..947ffcf87 100644 --- a/tests/blockchain/eth/contracts/main/staking_escrow/test_staking.py +++ b/tests/blockchain/eth/contracts/main/staking_escrow/test_staking.py @@ -23,6 +23,7 @@ from web3.contract import Contract @pytest.mark.slow def test_mining(testerchain, token, escrow_contract): + escrow = escrow_contract(1500) policy_manager_interface = testerchain.get_contract_factory('PolicyManagerForStakingEscrowMock') policy_manager = testerchain.client.get_contract( @@ -33,6 +34,16 @@ def test_mining(testerchain, token, escrow_contract): ursula1 = testerchain.client.accounts[1] ursula2 = testerchain.client.accounts[2] + mining_coefficient = escrow.functions.miningCoefficient().call() + locked_periods_coefficient = escrow.functions.lockedPeriodsCoefficient().call() + total_supply = token.functions.totalSupply().call() + reserved_reward = 10 ** 9 + current_supply = total_supply - reserved_reward + + def calculate_reward(locked, total_locked, locked_periods): + return (total_supply - current_supply) * locked * (locked_periods + locked_periods_coefficient) // \ + (total_locked * mining_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 +52,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, reserved_reward).transact({'from': creator}) testerchain.wait_for_receipt(tx) tx = escrow.functions.initialize().transact({'from': creator}) testerchain.wait_for_receipt(tx) @@ -49,7 +60,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 +78,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 +142,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 +157,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 +173,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 +206,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 +225,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 +255,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 +268,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 +277,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 +303,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 +317,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()) 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..ad3fc212f 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 @@ -248,7 +248,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 +398,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] From 3ccc18082d215b49b43df99c1a1f82fcee1932bb Mon Sep 17 00:00:00 2001 From: szotov Date: Tue, 11 Jun 2019 18:34:44 +0300 Subject: [PATCH 04/14] Apply suggestions from code review --- tests/blockchain/eth/entities/deployers/test_economics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/blockchain/eth/entities/deployers/test_economics.py b/tests/blockchain/eth/entities/deployers/test_economics.py index 08c85f95a..5388c00f2 100644 --- a/tests/blockchain/eth/entities/deployers/test_economics.py +++ b/tests/blockchain/eth/entities/deployers/test_economics.py @@ -169,8 +169,8 @@ def test_exact_economics(): assert expected_total_supply == e.token_supply_function(at_period=67000) # After 1 year: - assert 1845111188584347879497984668 == e.token_supply_function(at_period=365) - assert 845111188584347879497984668 == e.cumulative_rewards_at_period(365) + assert 1_845_111_188_584347879497984668 == e.token_supply_function(at_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_function(at_period=365) # Checking that the supply function is monotonic From 44717dcca648d02c10adb10e4947fd33a8de5b72 Mon Sep 17 00:00:00 2001 From: szotov Date: Sat, 15 Jun 2019 19:51:06 +0300 Subject: [PATCH 05/14] Split TokenEconomics into two classes --- nucypher/blockchain/economics.py | 181 +++++++++++------- nucypher/blockchain/eth/actors.py | 6 +- nucypher/blockchain/eth/deployers.py | 4 +- nucypher/blockchain/eth/token.py | 6 +- nucypher/blockchain/eth/utils.py | 19 +- nucypher/characters/chaotic.py | 4 +- nucypher/cli/types.py | 6 +- nucypher/utilities/sandbox/blockchain.py | 10 +- .../eth/contracts/base/test_issuer.py | 85 +++++--- .../test_intercontract_integration.py | 51 +++-- .../contracts/main/staking_escrow/conftest.py | 36 ++-- .../main/staking_escrow/test_staking.py | 19 +- .../test_staking_escrow_additional.py | 17 +- .../eth/entities/actors/test_deployer.py | 2 +- .../deployers/test_deploy_preallocations.py | 2 +- .../eth/entities/deployers/test_economics.py | 34 ++-- .../eth/interfaces/test_token_and_stake.py | 4 +- tests/fixtures.py | 4 +- tests/metrics/estimate_gas.py | 6 +- 19 files changed, 288 insertions(+), 208 deletions(-) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index b050d7307..233c2f8a7 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -28,6 +28,107 @@ 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_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() + + 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 + ): + """ + :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 + """ + + 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.seconds_per_period = hours_per_period * 60 * 60 # Seconds in single period + + @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 + ) + return tuple(map(int, deploy_parameters)) + + +class StandardTokenEconomics(TokenEconomics): """ Calculate parameters to use in token and escrow blockchain deployments from high-level human-understandable parameters. @@ -40,7 +141,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 +161,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 @@ -110,9 +195,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,64 +202,32 @@ 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 ) - return tuple(map(int, deploy_parameters)) - def token_supply_function(self, at_period: int) -> int: - if at_period < 0: + 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 - t = Decimal(at_period) + # 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 @@ -187,14 +237,13 @@ class TokenEconomics: 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, at_period: int) -> int: - return self.token_supply_function(at_period) - self.erc20_initial_supply + 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: HASH_ALGORITHM_KECCAK256 = 0 diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index b0329e2a8..4fe77118d 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 from nucypher.blockchain.eth.agents import ( NucypherTokenAgent, StakingEscrowAgent, @@ -409,7 +409,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 = economics or StandardTokenEconomics() self.stakes = StakeList(registry=self.registry, checksum_address=self.checksum_address) def to_dict(self) -> dict: @@ -668,7 +668,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 = economics or StandardTokenEconomics() self.rate = rate self.duration_periods = duration_periods self.first_period_reward = first_period_reward diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index 23d2c4464..9a351b684 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, SlashingEconomics, StandardTokenEconomics from nucypher.blockchain.eth.agents import ( EthereumContractAgent, StakingEscrowAgent, @@ -275,7 +275,7 @@ class StakingEscrowDeployer(ContractDeployer): def __init__(self, economics: TokenEconomics = None, *args, **kwargs): super().__init__(*args, **kwargs) if not economics: - economics = TokenEconomics() + economics = StandardTokenEconomics() self.__economics = economics self.__dispatcher_contract = None diff --git a/nucypher/blockchain/eth/token.py b/nucypher/blockchain/eth/token.py index 41f84b657..2a8483026 100644 --- a/nucypher/blockchain/eth/token.py +++ b/nucypher/blockchain/eth/token.py @@ -175,8 +175,10 @@ class 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) + self.start_datetime = datetime_at_period(period=first_locked_period, + seconds_per_period=miner.economics.seconds_per_period) + self.unlock_datetime = datetime_at_period(period=final_locked_period + 1, + seconds_per_period=miner.economics.seconds_per_period) # Blockchain self.staking_agent = staking_agent 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..b4e0b2a45 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 TokenEconomics, StandardTokenEconomics from nucypher.blockchain.eth.actors import NucypherTokenActor from nucypher.blockchain.eth.agents import NucypherTokenAgent, ContractAgency from nucypher.blockchain.eth.interfaces import BlockchainInterface @@ -206,7 +206,7 @@ class Felix(Character, NucypherTokenActor): self.start_time = NOT_RUNNING if not economics: - economics = TokenEconomics() + economics = StandardTokenEconomics() self.economics = economics self.MAXIMUM_DISBURSEMENT = economics.maximum_allowed_locked diff --git a/nucypher/cli/types.py b/nucypher/cli/types.py index e95901c30..24bf5bc97 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,7 +43,7 @@ class IPv4Address(click.ParamType): return value -token_economics = TokenEconomics() +token_economics = StandardTokenEconomics() WEI = click.IntRange(min=1, clamp=False) # TODO: Better validation for ether and wei values? # Staking diff --git a/nucypher/utilities/sandbox/blockchain.py b/nucypher/utilities/sandbox/blockchain.py index 1a19d5503..4bc42c741 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,7 +202,7 @@ 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 diff --git a/tests/blockchain/eth/contracts/base/test_issuer.py b/tests/blockchain/eth/contracts/base/test_issuer.py index c82e53041..bf433861b 100644 --- a/tests/blockchain/eth/contracts/base/test_issuer.py +++ b/tests/blockchain/eth/contracts/base/test_issuer.py @@ -20,29 +20,33 @@ 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', _totalSupply=int(NU(2 * 10 ** 40, 'NuNit'))) + token, _ = deploy_contract('NuCypherToken', _totalSupply=TOTAL_SUPPLY) return token @pytest.mark.slow def test_issuer(testerchain, token, deploy_contract): - mining_coefficient = 10 ** 43 - locked_periods_coefficient = 10 ** 4 - total_supply = token.functions.totalSupply().call() - current_supply = 10 ** 30 - reserved_reward = total_supply - current_supply + 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 reserved_reward * locked * (locked_periods + locked_periods_coefficient) // \ - (total_locked * mining_coefficient) + 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] @@ -52,25 +56,25 @@ def test_issuer(testerchain, token, deploy_contract): deploy_contract( contract_name='IssuerMock', _token=ursula, - _hoursPerPeriod=1, - _miningCoefficient=mining_coefficient, - _lockedPeriodsCoefficient=locked_periods_coefficient, - _rewardedPeriods=10 ** 4 + _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=1, - _miningCoefficient=mining_coefficient, - _lockedPeriodsCoefficient=locked_periods_coefficient, - _rewardedPeriods=10 ** 4 + _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, 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 @@ -82,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 @@ -126,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) @@ -195,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, @@ -237,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_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index 0c0240952..7d8a24bfa 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -24,6 +24,7 @@ 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 @@ -44,26 +45,31 @@ 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=2, + minimum_allowed_locked=100, + maximum_allowed_locked=2000) + 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) @@ -243,6 +249,7 @@ def execute_multisig_transaction(testerchain, multisig, accounts, tx): @pytest.mark.slow def test_all(testerchain, + token_economics, token, escrow, policy_manager, @@ -304,8 +311,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) @@ -474,7 +480,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 +500,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 +686,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 diff --git a/tests/blockchain/eth/contracts/main/staking_escrow/conftest.py b/tests/blockchain/eth/contracts/main/staking_escrow/conftest.py index b2348c430..fd056a03d 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,32 @@ 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) + 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[-1] = 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 947ffcf87..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,7 @@ 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') @@ -34,15 +34,12 @@ def test_mining(testerchain, token, escrow_contract): ursula1 = testerchain.client.accounts[1] ursula2 = testerchain.client.accounts[2] - mining_coefficient = escrow.functions.miningCoefficient().call() - locked_periods_coefficient = escrow.functions.lockedPeriodsCoefficient().call() - total_supply = token.functions.totalSupply().call() - reserved_reward = 10 ** 9 - current_supply = total_supply - reserved_reward + current_supply = token_economics.erc20_initial_supply def calculate_reward(locked, total_locked, locked_periods): - return (total_supply - current_supply) * locked * (locked_periods + locked_periods_coefficient) // \ - (total_locked * mining_coefficient) + 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') @@ -52,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, reserved_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().transact({'from': creator}) testerchain.wait_for_receipt(tx) @@ -337,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 @@ -351,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 ad3fc212f..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 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/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 5388c00f2..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 @@ -160,34 +160,34 @@ def test_exact_economics(): assert e.erc20_reward_supply == expected_reward_supply # Additional checks on supply - assert e.token_supply_function(at_period=0) == expected_initial_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_function(at_period=67000) + assert expected_total_supply == e.token_supply_at_period(period=67000) # After 1 year: - assert 1_845_111_188_584347879497984668 == e.token_supply_function(at_period=365) + 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_function(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_function(at_period=0) + todays_supply = e.token_supply_at_period(period=0) for t in range(67000): - tomorrows_supply = e.token_supply_function(at_period=t+1) + 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..c9f2c94fe 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 @@ -110,7 +110,7 @@ def test_stake(testerchain, agency): staking_agent = staking_agent token_agent = token_agent blockchain = testerchain - economics = TokenEconomics() + economics = StandardTokenEconomics() ursula = FakeUrsula() stake = Stake(checksum_address=ursula.checksum_address, diff --git a/tests/fixtures.py b/tests/fixtures.py index fa41e7b1c..05bf34f41 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 SlashingEconomics, StandardTokenEconomics from nucypher.blockchain.eth.actors import Staker from nucypher.blockchain.eth.agents import NucypherTokenAgent from nucypher.blockchain.eth.clients import NuCypherGethDevProcess @@ -343,7 +343,7 @@ def federated_ursulas(ursula_federated_test_config): @pytest.fixture(scope='session') def token_economics(): - economics = TokenEconomics() + economics = StandardTokenEconomics() return economics diff --git a/tests/metrics/estimate_gas.py b/tests/metrics/estimate_gas.py index 9a03769c4..d6adac256 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: From f05454b8554d132c14ddc0bac60e74ccf191ca57 Mon Sep 17 00:00:00 2001 From: szotov Date: Sun, 16 Jun 2019 12:05:19 +0300 Subject: [PATCH 06/14] Fix tests --- .../eth/contracts/integration/test_contract_economics.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/blockchain/eth/contracts/integration/test_contract_economics.py b/tests/blockchain/eth/contracts/integration/test_contract_economics.py index 8fb708c26..8e8f45fe0 100644 --- a/tests/blockchain/eth/contracts/integration/test_contract_economics.py +++ b/tests/blockchain/eth/contracts/integration/test_contract_economics.py @@ -38,7 +38,7 @@ def test_reward(testerchain, three_agents, token_economics): target_address=miner_agent.contract_address, sender_address=ursula) _txhash = miner_agent.deposit_tokens(amount=token_economics.minimum_allowed_locked, - lock_periods=100 * token_economics.maximum_locked_periods, + lock_periods=100 * token_economics.maximum_rewarded_periods, sender_address=ursula) # Get a reward for one period @@ -52,14 +52,16 @@ def test_reward(testerchain, three_agents, token_economics): contract_reward = miner_agent.calculate_staking_reward(checksum_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 ten periods + # Get a reward for other periods for i in range(1, MAX_PERIODS): testerchain.time_travel(periods=1) _txhash = miner_agent.confirm_activity(node_address=ursula) contract_reward = miner_agent.calculate_staking_reward(checksum_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 From a36bf80dc82ed2e71910153fa4e719320fda2978 Mon Sep 17 00:00:00 2001 From: szotov Date: Fri, 21 Jun 2019 16:49:06 +0300 Subject: [PATCH 07/14] Fix after rebasing --- nucypher/blockchain/economics.py | 9 ++++-- nucypher/blockchain/eth/token.py | 10 ++----- .../integration/test_contract_economics.py | 30 +++++++++++-------- .../test_intercontract_integration.py | 3 +- .../contracts/main/staking_escrow/conftest.py | 5 ++-- tests/fixtures.py | 4 +-- 6 files changed, 33 insertions(+), 28 deletions(-) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index 233c2f8a7..fab978deb 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -53,6 +53,7 @@ class TokenEconomics: __default_hours_per_period = 24 # Time Constraints + __default_minimum_worker_periods = 2 __default_minimum_locked_periods = 30 # 720 Hours minimum # Value Constraints @@ -68,7 +69,8 @@ class TokenEconomics: 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 + maximum_allowed_locked: int = __default_maximum_allowed_locked, + minimum_worker_periods: int = __default_minimum_worker_periods ): """ :param initial_supply: Tokens at t=0 @@ -80,6 +82,7 @@ class TokenEconomics: :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 """ self.initial_supply = initial_supply @@ -93,6 +96,7 @@ class TokenEconomics: 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 @property @@ -123,7 +127,8 @@ class TokenEconomics: # 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.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)) diff --git a/nucypher/blockchain/eth/token.py b/nucypher/blockchain/eth/token.py index 2a8483026..976247402 100644 --- a/nucypher/blockchain/eth/token.py +++ b/nucypher/blockchain/eth/token.py @@ -176,19 +176,13 @@ class Stake: # Time self.start_datetime = datetime_at_period(period=first_locked_period, - seconds_per_period=miner.economics.seconds_per_period) + seconds_per_period=self.economics.seconds_per_period) self.unlock_datetime = datetime_at_period(period=final_locked_period + 1, - seconds_per_period=miner.economics.seconds_per_period) + seconds_per_period=self.economics.seconds_per_period) # Blockchain self.staking_agent = staking_agent - # 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() diff --git a/tests/blockchain/eth/contracts/integration/test_contract_economics.py b/tests/blockchain/eth/contracts/integration/test_contract_economics.py index 8e8f45fe0..e20a6c790 100644 --- a/tests/blockchain/eth/contracts/integration/test_contract_economics.py +++ b/tests/blockchain/eth/contracts/integration/test_contract_economics.py @@ -19,14 +19,16 @@ import pytest # Experimental max error +from nucypher.crypto.powers import BlockchainPower + MAX_ERROR = 0.0004751 MAX_PERIODS = 100 @pytest.mark.slow -def test_reward(testerchain, three_agents, token_economics): +def test_reward(testerchain, agency, token_economics): testerchain.time_travel(hours=1) - token_agent, miner_agent, _policy_agent = three_agents + token_agent, staking_agent, _policy_agent = agency origin = testerchain.etherbase_account ursula = testerchain.ursula_account(0) @@ -34,22 +36,24 @@ def test_reward(testerchain, three_agents, token_economics): _txhash = token_agent.transfer(amount=token_economics.minimum_allowed_locked, target_address=ursula, sender_address=origin) + testerchain.transacting_power = BlockchainPower(blockchain=testerchain, account=ursula) _txhash = token_agent.approve_transfer(amount=token_economics.minimum_allowed_locked, - target_address=miner_agent.contract_address, + target_address=staking_agent.contract_address, sender_address=ursula) - _txhash = miner_agent.deposit_tokens(amount=token_economics.minimum_allowed_locked, - lock_periods=100 * token_economics.maximum_rewarded_periods, - 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 = miner_agent.confirm_activity(node_address=ursula) + _txhash = staking_agent.confirm_activity(worker_address=ursula) testerchain.time_travel(periods=1) - _txhash = miner_agent.confirm_activity(node_address=ursula) - assert miner_agent.calculate_staking_reward(checksum_address=ursula) == 0 + _txhash = staking_agent.confirm_activity(worker_address=ursula) + assert staking_agent.calculate_staking_reward(staker_address=ursula) == 0 testerchain.time_travel(periods=1) - _txhash = miner_agent.confirm_activity(node_address=ursula) + _txhash = staking_agent.confirm_activity(worker_address=ursula) - contract_reward = miner_agent.calculate_staking_reward(checksum_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 @@ -58,8 +62,8 @@ def test_reward(testerchain, three_agents, token_economics): # Get a reward for other periods for i in range(1, MAX_PERIODS): testerchain.time_travel(periods=1) - _txhash = miner_agent.confirm_activity(node_address=ursula) - contract_reward = miner_agent.calculate_staking_reward(checksum_address=ursula) + _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 diff --git a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index 7d8a24bfa..ec7749e72 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -54,7 +54,8 @@ def token_economics(): hours_per_period=1, minimum_locked_periods=2, minimum_allowed_locked=100, - maximum_allowed_locked=2000) + maximum_allowed_locked=2000, + minimum_worker_periods=2) return economics diff --git a/tests/blockchain/eth/contracts/main/staking_escrow/conftest.py b/tests/blockchain/eth/contracts/main/staking_escrow/conftest.py index fd056a03d..27c646489 100644 --- a/tests/blockchain/eth/contracts/main/staking_escrow/conftest.py +++ b/tests/blockchain/eth/contracts/main/staking_escrow/conftest.py @@ -37,7 +37,8 @@ def token_economics(): maximum_rewarded_periods=4, hours_per_period=1, minimum_locked_periods=2, - minimum_allowed_locked=100) + minimum_allowed_locked=100, + minimum_worker_periods=1) return economics @@ -53,7 +54,7 @@ def escrow_contract(testerchain, token, token_economics, request, deploy_contrac def make_escrow(max_allowed_locked_tokens): # Creator deploys the escrow deploy_parameters = list(token_economics.staking_deployment_parameters) - deploy_parameters[-1] = max_allowed_locked_tokens + deploy_parameters[-2] = max_allowed_locked_tokens contract, _ = deploy_contract('StakingEscrow', token.address, *deploy_parameters) if request.param: diff --git a/tests/fixtures.py b/tests/fixtures.py index 05bf34f41..af054f457 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -432,7 +432,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 +493,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) From e9d2b1f3c7307d72f9d711ee56968dec09ad730c Mon Sep 17 00:00:00 2001 From: szotov Date: Sat, 29 Jun 2019 14:54:18 +0300 Subject: [PATCH 08/14] Combine TokenEconomics and SlashingEconomics --- nucypher/blockchain/economics.py | 70 +++++++++++-------- nucypher/blockchain/eth/actors.py | 16 +++-- nucypher/blockchain/eth/deployers.py | 8 +-- nucypher/utilities/sandbox/blockchain.py | 6 +- .../test_intercontract_integration.py | 24 ++----- .../contracts/main/adjudicator/conftest.py | 4 +- .../main/adjudicator/test_adjudicator.py | 14 ++-- .../entities/agents/test_adjudicator_agent.py | 3 +- .../deployers/test_adjudicator_deployer.py | 12 ++-- tests/fixtures.py | 8 +-- 10 files changed, 85 insertions(+), 80 deletions(-) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index fab978deb..cc55b1481 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -60,6 +60,17 @@ class TokenEconomics: __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, @@ -70,7 +81,13 @@ class TokenEconomics: 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 + 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 @@ -83,6 +100,12 @@ class TokenEconomics: :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 @@ -99,6 +122,12 @@ class TokenEconomics: 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) @@ -132,6 +161,18 @@ class TokenEconomics: ) 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): """ @@ -247,30 +288,3 @@ class StandardTokenEconomics(TokenEconomics): def rewards_during_period(self, period: int) -> int: return self.cumulative_rewards_at_period(period) - self.cumulative_rewards_at_period(period-1) - - -class SlashingEconomics: - - HASH_ALGORITHM_KECCAK256 = 0 - HASH_ALGORITHM_SHA256 = 1 - HASH_ALGORITHM_RIPEMD160 = 2 - - hash_algorithm = HASH_ALGORITHM_SHA256 - base_penalty = 100 - penalty_history_coefficient = 10 - percentage_penalty_coefficient = 8 - reward_coefficient = 2 - - @property - def 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)) diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index 4fe77118d..cd9c16285 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -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,13 @@ 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, *args, **kwargs) if Deployer._upgradeable: if not plaintext_secret: raise ValueError("Upgrade plaintext_secret must be passed to deploy an upgradeable contract.") @@ -283,12 +287,14 @@ class ContractAdministrator(NucypherTokenActor): if deployer_class in self.standard_deployer_classes: receipts, deployer = self.deploy_contract(contract_name=deployer_class.contract_name, gas_limit=gas_limit, - progress=bar) + progress=bar, + economics=self.economics) else: receipts, deployer = self.deploy_contract(contract_name=deployer_class.contract_name, plaintext_secret=secrets[deployer_class.contract_name], gas_limit=gas_limit, - progress=bar) + progress=bar, + economics=self.economics) if emitter: blockchain = BlockchainInterfaceFactory.get_interface() diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index 9a351b684..9f5c03883 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, StandardTokenEconomics +from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics from nucypher.blockchain.eth.agents import ( EthereumContractAgent, StakingEscrowAgent, @@ -812,9 +812,9 @@ class AdjudicatorDeployer(ContractDeployer): _upgradeable = True _proxy_deployer = DispatcherDeployer - def __init__(self, economics: SlashingEconomics = None, *args, **kwargs): + def __init__(self, economics: TokenEconomics = None, *args, **kwargs): if not economics: - economics = SlashingEconomics() + economics = StandardTokenEconomics() super().__init__(*args, economics=economics, **kwargs) staking_contract_name = StakingEscrowDeployer.contract_name proxy_name = StakingEscrowDeployer._proxy_deployer.contract_name @@ -824,7 +824,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, diff --git a/nucypher/utilities/sandbox/blockchain.py b/nucypher/utilities/sandbox/blockchain.py index 4bc42c741..725412c25 100644 --- a/nucypher/utilities/sandbox/blockchain.py +++ b/nucypher/utilities/sandbox/blockchain.py @@ -206,7 +206,7 @@ class TesterBlockchain(BlockchainDeployerInterface): f"| epoch {end_timestamp}") @classmethod - def bootstrap_network(cls) -> 'TesterBlockchain': + def bootstrap_network(cls, economics: TokenEconomics = None) -> 'TesterBlockchain': """For use with metric testing scripts""" registry = InMemoryContractRegistry() @@ -218,7 +218,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/integration/test_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index ec7749e72..001e4d514 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -28,7 +28,6 @@ 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 @@ -55,7 +54,9 @@ def token_economics(): minimum_locked_periods=2, minimum_allowed_locked=100, maximum_allowed_locked=2000, - minimum_worker_periods=2) + minimum_worker_periods=2, + base_penalty=300, + percentage_penalty_coefficient=2) return economics @@ -108,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) @@ -258,7 +254,6 @@ def test_all(testerchain, worklock, user_escrow_proxy, multisig, - slashing_economics, mock_ursula_reencrypts, deploy_contract): @@ -837,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) @@ -910,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/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/fixtures.py b/tests/fixtures.py index af054f457..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 SlashingEconomics, StandardTokenEconomics +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 @@ -347,12 +347,6 @@ def token_economics(): return economics -@pytest.fixture(scope='session') -def slashing_economics(): - economics = SlashingEconomics() - return economics - - @pytest.fixture(scope='session') def solidity_compiler(): """Doing this more than once per session will result in slower test run times.""" From 7be142a8d8ec9ce62374dd7c22b1ac3fb60aa8dc Mon Sep 17 00:00:00 2001 From: szotov Date: Sun, 30 Jun 2019 13:05:32 +0300 Subject: [PATCH 09/14] estimate_gas script uses custom parameters --- tests/metrics/estimate_gas.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/metrics/estimate_gas.py b/tests/metrics/estimate_gas.py index d6adac256..d06e52fa0 100755 --- a/tests/metrics/estimate_gas.py +++ b/tests/metrics/estimate_gas.py @@ -155,7 +155,12 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None: log = Logger(AnalyzeGas.LOG_NAME) # Blockchain - testerchain, registry = TesterBlockchain.bootstrap_network() + economics = StandardTokenEconomics() + economics.base_penalty = MIN_ALLOWED_LOCKED - 1 + economics.penalty_history_coefficient = 0 + economics.percentage_penalty_coefficient = 2 + economics.reward_coefficient = 2 + testerchain, registry = TesterBlockchain.bootstrap_network(economics=economics) web3 = testerchain.w3 # Accounts From 86465416195ed494fe5c60666ec18e53ea45f613 Mon Sep 17 00:00:00 2001 From: szotov Date: Fri, 12 Jul 2019 13:15:28 +0300 Subject: [PATCH 10/14] Extended `init` in `StandardEconomics` --- nucypher/blockchain/economics.py | 6 ++++-- tests/metrics/estimate_gas.py | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index cc55b1481..9e40b4e63 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -219,7 +219,8 @@ class StandardTokenEconomics(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 @@ -261,7 +262,8 @@ class StandardTokenEconomics(TokenEconomics): total_supply, staking_coefficient, locked_periods_coefficient, - maximum_rewarded_periods + maximum_rewarded_periods, + **kwargs ) def token_supply_at_period(self, period: int) -> int: diff --git a/tests/metrics/estimate_gas.py b/tests/metrics/estimate_gas.py index d06e52fa0..913b857b5 100755 --- a/tests/metrics/estimate_gas.py +++ b/tests/metrics/estimate_gas.py @@ -155,11 +155,12 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None: log = Logger(AnalyzeGas.LOG_NAME) # Blockchain - economics = StandardTokenEconomics() - economics.base_penalty = MIN_ALLOWED_LOCKED - 1 - economics.penalty_history_coefficient = 0 - economics.percentage_penalty_coefficient = 2 - economics.reward_coefficient = 2 + 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 From 14086d538a47b487725f79f7e799bc26583fce45 Mon Sep 17 00:00:00 2001 From: szotov Date: Wed, 17 Jul 2019 13:15:53 +0300 Subject: [PATCH 11/14] Fix after rebasing --- nucypher/blockchain/eth/deployers.py | 26 +++++++------------ .../integration/test_contract_economics.py | 9 ++++--- .../eth/entities/actors/test_investigator.py | 5 ++-- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index 9f5c03883..2caff96cd 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -80,6 +80,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: @@ -101,6 +102,10 @@ class ContractDeployer: def contract(self): return self._contract + @property + def economics(self): + return self.__economics + @property def dispatcher(self): return self.__proxy_contract @@ -272,11 +277,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 = StandardTokenEconomics() - self.__economics = economics self.__dispatcher_contract = None token_contract_name = NucypherTokenDeployer.contract_name @@ -289,7 +291,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 +354,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, @@ -812,19 +814,11 @@ class AdjudicatorDeployer(ContractDeployer): _upgradeable = True _proxy_deployer = DispatcherDeployer - def __init__(self, economics: TokenEconomics = None, *args, **kwargs): - if not economics: - economics = StandardTokenEconomics() - super().__init__(*args, economics=economics, **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, - name=staking_contract_name, - proxy_name=proxy_name) + def __init__(self, *args, **kwargs): def _deploy_essential(self, gas_limit: int = None): constructor_args = (self.staking_contract.address, - *self.__economics.slashing_deployment_parameters) + *self.economics.slashing_deployment_parameters) adjudicator_contract, deploy_receipt = self.blockchain.deploy_contract(self.deployer_address, self.registry, self.contract_name, diff --git a/tests/blockchain/eth/contracts/integration/test_contract_economics.py b/tests/blockchain/eth/contracts/integration/test_contract_economics.py index e20a6c790..6a7dc3b5d 100644 --- a/tests/blockchain/eth/contracts/integration/test_contract_economics.py +++ b/tests/blockchain/eth/contracts/integration/test_contract_economics.py @@ -16,11 +16,11 @@ 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 -from nucypher.crypto.powers import BlockchainPower - MAX_ERROR = 0.0004751 MAX_PERIODS = 100 @@ -36,7 +36,10 @@ def test_reward(testerchain, agency, token_economics): _txhash = token_agent.transfer(amount=token_economics.minimum_allowed_locked, target_address=ursula, sender_address=origin) - testerchain.transacting_power = BlockchainPower(blockchain=testerchain, account=ursula) + testerchain.transacting_power = TransactingPower(blockchain=testerchain, + 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) 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 From e020cc678cb5708fa9cff6ec5fa024889c9aa3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 3 Sep 2019 17:03:11 +0200 Subject: [PATCH 12/14] Fix AdjudicatorDeployer after rebase --- nucypher/blockchain/eth/deployers.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index 2caff96cd..7f79730d2 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -432,7 +432,6 @@ class StakingEscrowDeployer(ContractDeployer): return rollback_receipt - class PolicyManagerDeployer(ContractDeployer): """ Depends on StakingEscrow and NucypherTokenAgent @@ -814,7 +813,13 @@ class AdjudicatorDeployer(ContractDeployer): _upgradeable = True _proxy_deployer = DispatcherDeployer - def __init__(self, *args, **kwargs): + def __init__(self, economics: TokenEconomics = None, *args, **kwargs): + super().__init__(*args, economics=economics, **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, + name=staking_contract_name, + proxy_name=proxy_name) def _deploy_essential(self, gas_limit: int = None): constructor_args = (self.staking_contract.address, @@ -834,8 +839,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]] @@ -882,9 +887,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) From 5588600ada9b022cf76f265f7938697a7cb5e37f Mon Sep 17 00:00:00 2001 From: szotov Date: Wed, 4 Sep 2019 18:03:04 +0300 Subject: [PATCH 13/14] Factory for economics, fixed tests --- nucypher/blockchain/economics.py | 46 +++++++++++++++++++ nucypher/blockchain/eth/actors.py | 27 ++++++----- nucypher/blockchain/eth/deployers.py | 17 +------ nucypher/blockchain/eth/token.py | 46 +++++++++++++++---- nucypher/characters/chaotic.py | 12 ++--- nucypher/characters/lawful.py | 1 - nucypher/cli/characters/stake.py | 28 +++++------ nucypher/cli/types.py | 6 --- nucypher/utilities/sandbox/blockchain.py | 4 +- .../integration/test_contract_economics.py | 3 +- .../test_intercontract_integration.py | 4 +- .../eth/entities/actors/test_staker.py | 3 +- .../eth/interfaces/test_token_and_stake.py | 6 +-- .../cli/ursula/test_stakeholder_and_ursula.py | 3 +- 14 files changed, 131 insertions(+), 75 deletions(-) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index 9e40b4e63..3a8b61f7d 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 @@ -290,3 +292,47 @@ class StandardTokenEconomics(TokenEconomics): def rewards_during_period(self, period: int) -> int: return self.cumulative_rewards_at_period(period) - self.cumulative_rewards_at_period(period-1) + + +class TokenEconomicsFactory: + # TODO: Enforce singleton + + __economics = dict() + + @classmethod + def get_economics(cls, registry: BaseContractRegistry) -> TokenEconomics: + registry_id = registry.id + try: + return cls.__economics[registry_id] + except KeyError: + economics = TokenEconomicsFactory.make_economics(registry=registry) + cls.__economics[registry_id] = economics + return economics + + @staticmethod + def make_economics(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) + total_supply = token_agent.contract.functions.totalSupply().call() + reward_supply = staking_agent.contract.functions.getReservedReward().call() + economics = TokenEconomics( + # it's not real initial_supply value because used current reward instead of initial + initial_supply=total_supply - reward_supply, + total_supply=total_supply, + staking_coefficient=staking_agent.contract.functions.miningCoefficient().call(), + locked_periods_coefficient=staking_agent.contract.functions.lockedPeriodsCoefficient().call(), + maximum_rewarded_periods=staking_agent.contract.functions.rewardedPeriods().call(), + hours_per_period=staking_agent.contract.functions.secondsPerPeriod().call() // 60 // 60, + minimum_locked_periods=staking_agent.contract.functions.minLockedPeriods().call(), + minimum_allowed_locked=staking_agent.contract.functions.minAllowableLockedTokens().call(), + maximum_allowed_locked=staking_agent.contract.functions.maxAllowableLockedTokens().call(), + minimum_worker_periods=staking_agent.contract.functions.minWorkerPeriods().call(), + + hash_algorithm=adjudicator_agent.contract.functions.hashAlgorithm().call(), + base_penalty=adjudicator_agent.contract.functions.basePenalty().call(), + penalty_history_coefficient=adjudicator_agent.contract.functions.penaltyHistoryCoefficient().call(), + percentage_penalty_coefficient=adjudicator_agent.contract.functions.percentagePenaltyCoefficient().call(), + reward_coefficient=adjudicator_agent.contract.functions.rewardCoefficient().call() + ) + return economics diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index cd9c16285..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, StandardTokenEconomics +from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics, TokenEconomicsFactory from nucypher.blockchain.eth.agents import ( NucypherTokenAgent, StakingEscrowAgent, @@ -210,7 +210,11 @@ class ContractAdministrator(NucypherTokenActor): ) -> Tuple[dict, ContractDeployer]: Deployer = self.__get_deployer(contract_name=contract_name) - deployer = Deployer(registry=self.registry, deployer_address=self.deployer_address, *args, **kwargs) + 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.") @@ -287,14 +291,12 @@ class ContractAdministrator(NucypherTokenActor): if deployer_class in self.standard_deployer_classes: receipts, deployer = self.deploy_contract(contract_name=deployer_class.contract_name, gas_limit=gas_limit, - progress=bar, - economics=self.economics) + progress=bar) else: receipts, deployer = self.deploy_contract(contract_name=deployer_class.contract_name, plaintext_secret=secrets[deployer_class.contract_name], gas_limit=gas_limit, - progress=bar, - economics=self.economics) + progress=bar) if emitter: blockchain = BlockchainInterfaceFactory.get_interface() @@ -401,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") @@ -415,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 StandardTokenEconomics() + self.economics = TokenEconomicsFactory.get_economics(registry=self.registry) self.stakes = StakeList(registry=self.registry, checksum_address=self.checksum_address) def to_dict(self) -> dict: @@ -657,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, @@ -674,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 StandardTokenEconomics() + self.economics = TokenEconomicsFactory.get_economics(registry=self.registry) self.rate = rate self.duration_periods = duration_periods self.first_period_reward = first_period_reward @@ -706,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/deployers.py b/nucypher/blockchain/eth/deployers.py index 7f79730d2..4a602053a 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -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 # @@ -102,10 +98,6 @@ class ContractDeployer: def contract(self): return self._contract - @property - def economics(self): - return self.__economics - @property def dispatcher(self): return self.__proxy_contract @@ -447,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, @@ -813,8 +800,8 @@ class AdjudicatorDeployer(ContractDeployer): _upgradeable = True _proxy_deployer = DispatcherDeployer - def __init__(self, economics: TokenEconomics = None, *args, **kwargs): - 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, diff --git a/nucypher/blockchain/eth/token.py b/nucypher/blockchain/eth/token.py index 976247402..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,15 +191,20 @@ class Stake: # and no confirmation can be performed in this period for this stake. self.final_locked_period = final_locked_period + # Blockchain + self.staking_agent = staking_agent + + # Economics + self.economics = economics + self.minimum_nu = NU(int(self.economics.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) - # Blockchain - self.staking_agent = staking_agent - if validate_now: self.validate_duration() @@ -215,6 +237,7 @@ class Stake: checksum_address: str, index: int, stake_info: Tuple[int, int, int], + economics, *args, **kwargs ) -> 'Stake': @@ -226,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) @@ -374,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 @@ -383,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 @@ -421,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() @@ -546,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 @@ -586,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/characters/chaotic.py b/nucypher/characters/chaotic.py index b4e0b2a45..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, StandardTokenEconomics +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 = StandardTokenEconomics() - 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 24bf5bc97..bf86e96fe 100644 --- a/nucypher/cli/types.py +++ b/nucypher/cli/types.py @@ -43,14 +43,8 @@ class IPv4Address(click.ParamType): return value -token_economics = StandardTokenEconomics() 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 725412c25..1a980f0a6 100644 --- a/nucypher/utilities/sandbox/blockchain.py +++ b/nucypher/utilities/sandbox/blockchain.py @@ -206,7 +206,9 @@ class TesterBlockchain(BlockchainDeployerInterface): f"| epoch {end_timestamp}") @classmethod - def bootstrap_network(cls, economics: TokenEconomics = None) -> 'TesterBlockchain': + def bootstrap_network(cls, + economics: TokenEconomics = None + ) -> Tuple['TesterBlockchain', 'InMemoryContractRegistry']: """For use with metric testing scripts""" registry = InMemoryContractRegistry() diff --git a/tests/blockchain/eth/contracts/integration/test_contract_economics.py b/tests/blockchain/eth/contracts/integration/test_contract_economics.py index 6a7dc3b5d..adcceae84 100644 --- a/tests/blockchain/eth/contracts/integration/test_contract_economics.py +++ b/tests/blockchain/eth/contracts/integration/test_contract_economics.py @@ -36,8 +36,7 @@ def test_reward(testerchain, agency, token_economics): _txhash = token_agent.transfer(amount=token_economics.minimum_allowed_locked, target_address=ursula, sender_address=origin) - testerchain.transacting_power = TransactingPower(blockchain=testerchain, - password=INSECURE_DEVELOPMENT_PASSWORD, + 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, diff --git a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index 001e4d514..b1d9aff83 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -51,7 +51,7 @@ def token_economics(): locked_periods_coefficient=4, maximum_rewarded_periods=4, hours_per_period=1, - minimum_locked_periods=2, + minimum_locked_periods=6, minimum_allowed_locked=100, maximum_allowed_locked=2000, minimum_worker_periods=2, @@ -363,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() 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/interfaces/test_token_and_stake.py b/tests/blockchain/eth/interfaces/test_token_and_stake.py index c9f2c94fe..5d01bdc90 100644 --- a/tests/blockchain/eth/interfaces/test_token_and_stake.py +++ b/tests/blockchain/eth/interfaces/test_token_and_stake.py @@ -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 = StandardTokenEconomics() 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 From 6b04252ea9d9270b19b97d726b4e796787cd75ca Mon Sep 17 00:00:00 2001 From: szotov Date: Thu, 5 Sep 2019 11:44:17 +0300 Subject: [PATCH 14/14] Respond to RFCs in PR #1055 --- nucypher/blockchain/economics.py | 34 +++++++++++++------------------ nucypher/blockchain/eth/agents.py | 14 +++++++++++++ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index 3a8b61f7d..65dc04589 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -305,34 +305,28 @@ class TokenEconomicsFactory: try: return cls.__economics[registry_id] except KeyError: - economics = TokenEconomicsFactory.make_economics(registry=registry) + economics = TokenEconomicsFactory.retrieve_from_blockchain(registry=registry) cls.__economics[registry_id] = economics return economics @staticmethod - def make_economics(registry: BaseContractRegistry) -> TokenEconomics: + 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) + total_supply = token_agent.contract.functions.totalSupply().call() reward_supply = staking_agent.contract.functions.getReservedReward().call() - economics = TokenEconomics( - # it's not real initial_supply value because used current reward instead of initial - initial_supply=total_supply - reward_supply, - total_supply=total_supply, - staking_coefficient=staking_agent.contract.functions.miningCoefficient().call(), - locked_periods_coefficient=staking_agent.contract.functions.lockedPeriodsCoefficient().call(), - maximum_rewarded_periods=staking_agent.contract.functions.rewardedPeriods().call(), - hours_per_period=staking_agent.contract.functions.secondsPerPeriod().call() // 60 // 60, - minimum_locked_periods=staking_agent.contract.functions.minLockedPeriods().call(), - minimum_allowed_locked=staking_agent.contract.functions.minAllowableLockedTokens().call(), - maximum_allowed_locked=staking_agent.contract.functions.maxAllowableLockedTokens().call(), - minimum_worker_periods=staking_agent.contract.functions.minWorkerPeriods().call(), + # it's not real initial_supply value because used current reward instead of initial + initial_supply = total_supply - reward_supply - hash_algorithm=adjudicator_agent.contract.functions.hashAlgorithm().call(), - base_penalty=adjudicator_agent.contract.functions.basePenalty().call(), - penalty_history_coefficient=adjudicator_agent.contract.functions.penaltyHistoryCoefficient().call(), - percentage_penalty_coefficient=adjudicator_agent.contract.functions.percentagePenaltyCoefficient().call(), - reward_coefficient=adjudicator_agent.contract.functions.rewardCoefficient().call() - ) + 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/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