mirror of https://github.com/nucypher/nucypher.git
commit
3f94b93df3
|
@ -20,12 +20,12 @@ to deploy a single-host network with the geth CLI.
|
||||||
(nucypher)$ nucypher-deploy contracts --provider-uri ipc:///tmp/geth.ipc --poa
|
(nucypher)$ nucypher-deploy contracts --provider-uri ipc:///tmp/geth.ipc --poa
|
||||||
...
|
...
|
||||||
|
|
||||||
This will deploy the main NuCypher contracts: NuCypherToken, MinerEscrow, PolicyManager,
|
This will deploy the main NuCypher contracts, namely ``NuCypherToken``, ``MinerEscrow``, and ``PolicyManager``,
|
||||||
along with their proxies, as well as executing initialization transactions. You will need to enter
|
along with their proxies, as well as executing initialization transactions. You will need to enter
|
||||||
the contract's upgrade secrets, which can be any alphanumeric string.
|
the contract's upgrade secrets, which can be any alphanumeric string.
|
||||||
|
|
||||||
A summary of deployed contract addresses, transactions, and gas usage will be displayed on success, and a
|
A summary of deployed contract addresses, transactions, and gas usage will be displayed on success, and a
|
||||||
`contract_registry.json` file will be generated in your nucypher application directory.
|
``contract_registry.json`` file will be generated in your nucypher application directory.
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
|
|
|
@ -4,30 +4,30 @@ NuCypher Staking Guide
|
||||||
|
|
||||||
**Ursula Staking Actions**
|
**Ursula Staking Actions**
|
||||||
|
|
||||||
+-------------------+-------------------------------------------------------------------------+
|
+-------------------+------------------------------------------------------------------------------+
|
||||||
| Action | Description |
|
| Action | Description |
|
||||||
+===================+=========================================================================+
|
+===================+==============================================================================+
|
||||||
| `stake` | Initialize or view NuCpher stakes (used with `--value` and `--duration`)|
|
| ``stake`` | Initialize or view NuCypher stakes (used with ``--value`` and ``--duration``)|
|
||||||
+-------------------+-------------------------------------------------------------------------+
|
+-------------------+------------------------------------------------------------------------------+
|
||||||
| `divide-stake` | Divide stake, used with `--value` and `--duration` |
|
| ``collect-reward``| Collect staking reward (Policy reward collection in future PR) |
|
||||||
+-------------------+-------------------------------------------------------------------------+
|
+-------------------+------------------------------------------------------------------------------+
|
||||||
| `collect-reward` | Collect staking reward (Policy reward collection in future PR) |
|
|
||||||
+-------------------+-------------------------------------------------------------------------+
|
|
||||||
|
|
||||||
|
|
||||||
**Ursula Staking Options**
|
**Ursula Staking Options**
|
||||||
|
|
||||||
+----------------+----------------------------------------+
|
+-----------------+--------------------------------------------+
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
+================+========================================+
|
+=================+============================================+
|
||||||
| `--value` | Stake value |
|
| ``--value`` | Stake value |
|
||||||
+----------------+----------------------------------------+
|
+-----------------+--------------------------------------------+
|
||||||
| `--duration` | Stake duration of extension |
|
| ``--duration`` | Stake duration of extension |
|
||||||
+----------------+----------------------------------------+
|
+-----------------+--------------------------------------------+
|
||||||
| `--index` | Stake index |
|
| ``--index`` | Stake index |
|
||||||
+----------------+----------------------------------------+
|
+-----------------+--------------------------------------------+
|
||||||
| `--list` | List stakes (used with `stake` action) |
|
| ``--list`` | List stakes (used with ``stake`` action) |
|
||||||
+----------------+----------------------------------------+
|
+-----------------+--------------------------------------------+
|
||||||
|
| ``--divide`` | Divide stakes (used with ``stake`` action) |
|
||||||
|
+-----------------+--------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ Interactive Method
|
||||||
(nucypher)$ nucypher ursula stake
|
(nucypher)$ nucypher ursula stake
|
||||||
|
|
||||||
|
|
||||||
*Initial a new stake*
|
*Initialize a new stake*
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ Interactive Method
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
(nucypher)$ nucypher ursula divide-stake
|
(nucypher)$ nucypher ursula stake --divide
|
||||||
|
|
||||||
|
|
||||||
| # | Duration | Enact | Expiration | Value
|
| # | Duration | Enact | Expiration | Value
|
||||||
|
@ -100,7 +100,7 @@ Interactive Method
|
||||||
| 0 | 32 periods . | yesterday | 14 Apr ... | 30000 NU
|
| 0 | 32 periods . | yesterday | 14 Apr ... | 30000 NU
|
||||||
|
|
||||||
Select a stake to divide: 0
|
Select a stake to divide: 0
|
||||||
Enter target value (must be less than 3000 NU): 1500
|
Enter target value (must be less than 30000 NU): 15000
|
||||||
Enter number of periods to extend: 30
|
Enter number of periods to extend: 30
|
||||||
|
|
||||||
============================== ORIGINAL STAKE ============================
|
============================== ORIGINAL STAKE ============================
|
||||||
|
@ -133,17 +133,17 @@ Inline Method
|
||||||
+----------------+------------+--------------+
|
+----------------+------------+--------------+
|
||||||
|
|
||||||
|
|
||||||
*Stake 3000 NU for 90 Periods*
|
*Stake 30000 NU for 90 Periods*
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
(nucypher)$ nucypher ursula stake --value 3000 --duration 90
|
(nucypher)$ nucypher ursula stake --value 30000 --duration 90
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
*Divide stake at index 0, at 1500 NU for 30 additional Periods*
|
*Divide stake at index 0, at 15000 NU for 30 additional Periods*
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
(nucypher)$ nucypher ursula divide-stake --index 0 --value 1500 --duration 30
|
(nucypher)$ nucypher ursula stake --divide --index 0 --value 15000 --duration 30
|
||||||
...
|
...
|
|
@ -24,19 +24,21 @@ If your installation in non-functional, be sure you have the latest version inst
|
||||||
2(a). Configure a new Ursula node
|
2(a). Configure a new Ursula node
|
||||||
|
|
||||||
*Decentralized Ursula Configuration*
|
*Decentralized Ursula Configuration*
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
(nucypher)$ nucypher ursula init --provider-uri <YOUR PROVIDER URI> --network <NETWORK NAME>
|
(nucypher)$ nucypher ursula init --provider-uri <YOUR PROVIDER URI> --network <NETWORK NAME>
|
||||||
|
|
||||||
Replace ``<YOUR PROVIDER URI>`` with a valid node web3 node provider string, for example:
|
Replace ``<YOUR PROVIDER URI>`` with a valid node web3 node provider string, for example:
|
||||||
|
|
||||||
- *ipc:///tmp/geth.ipc* - Geth Development Node (IPC; Also use --poa flag)
|
- ``ipc:///tmp/geth.ipc`` - Geth Development Node (IPC; **Note**: Also use ``--poa`` flag)
|
||||||
- *http://localhost:7545* - Ganache TestRPC (HTTP-JSON-RPC)
|
- ``http://localhost:7545`` - Ganache TestRPC (HTTP-JSON-RPC)
|
||||||
- *ws://0.0.0.0:8080* - Websocket Provider
|
- ``ws://0.0.0.0:8080`` - Websocket Provider
|
||||||
|
|
||||||
2(b). Configure a new Ursula node
|
2(b). Configure a new Ursula node
|
||||||
|
|
||||||
*Federated Ursula Initialization*
|
*Federated Ursula Initialization*
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
(nucypher)$ nucypher ursula init --federated-only --network <NETWORK NAME>
|
(nucypher)$ nucypher ursula init --federated-only --network <NETWORK NAME>
|
||||||
|
|
|
@ -18,26 +18,26 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from twisted.logger import Logger
|
|
||||||
|
|
||||||
import maya
|
import maya
|
||||||
from constant_sorrow import constants
|
from constant_sorrow import constants
|
||||||
from constant_sorrow.constants import CONTRACT_NOT_DEPLOYED, NO_CONTRACT_AVAILABLE, NO_DEPLOYER_ADDRESS
|
from constant_sorrow.constants import CONTRACT_NOT_DEPLOYED, NO_DEPLOYER_ADDRESS
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from twisted.internet import task, reactor
|
from twisted.internet import task, reactor
|
||||||
|
from twisted.logger import Logger
|
||||||
from typing import Tuple, List, Dict, Union
|
from typing import Tuple, List, Dict, Union
|
||||||
|
|
||||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent
|
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent
|
||||||
from nucypher.blockchain.eth.chains import Blockchain
|
from nucypher.blockchain.eth.chains import Blockchain
|
||||||
from nucypher.blockchain.eth.deployers import NucypherTokenDeployer, MinerEscrowDeployer, PolicyManagerDeployer, \
|
from nucypher.blockchain.eth.deployers import NucypherTokenDeployer, MinerEscrowDeployer, PolicyManagerDeployer, \
|
||||||
UserEscrowProxyDeployer, UserEscrowDeployer, DispatcherDeployer
|
UserEscrowProxyDeployer, UserEscrowDeployer
|
||||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
|
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
|
||||||
from nucypher.blockchain.eth.registry import EthereumContractRegistry, AllocationRegistry
|
from nucypher.blockchain.eth.registry import AllocationRegistry
|
||||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
|
||||||
from nucypher.blockchain.eth.utils import (datetime_to_period,
|
from nucypher.blockchain.eth.utils import (datetime_to_period,
|
||||||
validate_stake_amount,
|
validate_stake_amount,
|
||||||
validate_locktime,
|
validate_locktime,
|
||||||
calculate_period_duration)
|
calculate_period_duration)
|
||||||
|
from nucypher.blockchain.eth.token import NU, Stake
|
||||||
|
|
||||||
|
|
||||||
def only_me(func):
|
def only_me(func):
|
||||||
|
@ -95,10 +95,11 @@ class NucypherTokenActor:
|
||||||
return balance
|
return balance
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def token_balance(self):
|
def token_balance(self) -> NU:
|
||||||
"""Return this actors's current token balance"""
|
"""Return this actors's current token balance"""
|
||||||
balance = self.token_agent.get_balance(address=self.checksum_public_address)
|
balance = int(self.token_agent.get_balance(address=self.checksum_public_address))
|
||||||
return balance
|
nu_balance = NU(balance, 'NuNit')
|
||||||
|
return nu_balance
|
||||||
|
|
||||||
|
|
||||||
class Deployer(NucypherTokenActor):
|
class Deployer(NucypherTokenActor):
|
||||||
|
@ -292,23 +293,25 @@ class Miner(NucypherTokenActor):
|
||||||
|
|
||||||
self.miner_agent = MinerAgent(blockchain=self.blockchain)
|
self.miner_agent = MinerAgent(blockchain=self.blockchain)
|
||||||
|
|
||||||
|
self.__stakes = constants.NO_STAKES
|
||||||
|
self.__start_time = constants.NO_STAKES
|
||||||
|
self.__uptime_period = constants.NO_STAKES
|
||||||
|
self.__terminal_period = constants.NO_STAKES
|
||||||
|
|
||||||
|
self.__read_stakes()
|
||||||
if self.stakes and start_staking_loop:
|
if self.stakes and start_staking_loop:
|
||||||
self.stake()
|
self.stake()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Staking
|
# Staking
|
||||||
#
|
#
|
||||||
@only_me
|
@only_me
|
||||||
def stake(self, confirm_now=False) -> None:
|
def stake(self, confirm_now: bool = True) -> None:
|
||||||
|
"""High-level staking looping call initialization"""
|
||||||
|
# TODO #841: Check if there is an active stake in the current period: Resume staking daemon
|
||||||
|
|
||||||
"""High-level staking daemon loop"""
|
# Get the last stake end period of all stakes
|
||||||
|
terminal_period = max(stake.end_period for stake in self.stakes.values())
|
||||||
# TODO: Check if this period has already been confirmed
|
|
||||||
# TODO: Check if there is an active stake in the current period: Resume staking daemon
|
|
||||||
# TODO: Validation and Sanity checks
|
|
||||||
|
|
||||||
terminal_period = max(stake[1] for stake in self.stakes)
|
|
||||||
|
|
||||||
if confirm_now:
|
if confirm_now:
|
||||||
self.confirm_activity()
|
self.confirm_activity()
|
||||||
|
@ -320,10 +323,6 @@ class Miner(NucypherTokenActor):
|
||||||
self.__current_period = self.__uptime_period
|
self.__current_period = self.__uptime_period
|
||||||
self.start_staking_loop()
|
self.start_staking_loop()
|
||||||
|
|
||||||
#
|
|
||||||
# Daemon
|
|
||||||
#
|
|
||||||
|
|
||||||
@only_me
|
@only_me
|
||||||
def _confirm_period(self):
|
def _confirm_period(self):
|
||||||
|
|
||||||
|
@ -381,16 +380,26 @@ class Miner(NucypherTokenActor):
|
||||||
return self.miner_agent.get_locked_tokens(miner_address=self.checksum_public_address)
|
return self.miner_agent.get_locked_tokens(miner_address=self.checksum_public_address)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_staked(self) -> int:
|
def total_staked(self) -> NU:
|
||||||
if self.stakes:
|
if self.stakes:
|
||||||
return sum(stake[-1] for stake in self.stakes)
|
return NU(sum(int(stake.value) for stake in self.stakes.values()), 'NuNit')
|
||||||
return 0
|
else:
|
||||||
|
return NU(0, 'NuNit')
|
||||||
|
|
||||||
|
def __read_stakes(self) -> None:
|
||||||
|
stakes_reader = self.miner_agent.get_all_stakes(miner_address=self.checksum_public_address)
|
||||||
|
stakes = dict()
|
||||||
|
for index, stake_info in enumerate(stakes_reader):
|
||||||
|
stake = Stake.from_stake_info(owner_address=self.checksum_public_address,
|
||||||
|
stake_info=stake_info,
|
||||||
|
index=index)
|
||||||
|
stakes[index] = stake
|
||||||
|
self.__stakes = stakes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stakes(self) -> Tuple[list]:
|
def stakes(self) -> Dict[str, Stake]:
|
||||||
"""Read all live stake data from the blockchain and return it as a tuple"""
|
"""Return all cached stakes from the blockchain."""
|
||||||
stakes_reader = self.miner_agent.get_all_stakes(miner_address=self.checksum_public_address)
|
return self.__stakes
|
||||||
return tuple(stakes_reader)
|
|
||||||
|
|
||||||
@only_me
|
@only_me
|
||||||
def deposit(self, amount: int, lock_periods: int) -> Tuple[str, str]:
|
def deposit(self, amount: int, lock_periods: int) -> Tuple[str, str]:
|
||||||
|
@ -409,7 +418,7 @@ class Miner(NucypherTokenActor):
|
||||||
@only_me
|
@only_me
|
||||||
def divide_stake(self,
|
def divide_stake(self,
|
||||||
stake_index: int,
|
stake_index: int,
|
||||||
target_value: int,
|
target_value: NU,
|
||||||
additional_periods: int = None,
|
additional_periods: int = None,
|
||||||
expiration: maya.MayaDT = None) -> dict:
|
expiration: maya.MayaDT = None) -> dict:
|
||||||
"""
|
"""
|
||||||
|
@ -418,8 +427,10 @@ class Miner(NucypherTokenActor):
|
||||||
This actor requires that is_me is True, and that the expiration datetime is after the existing
|
This actor requires that is_me is True, and that the expiration datetime is after the existing
|
||||||
locking schedule of this miner, or an exception will be raised.
|
locking schedule of this miner, or an exception will be raised.
|
||||||
|
|
||||||
:param target_value: The quantity of tokens in the smallest denomination.
|
:param stake_index: The miner's stake index of the stake to divide
|
||||||
:param expiration: The new expiration date to set.
|
:param additional_periods: The number of periods to extend the stake by
|
||||||
|
:param target_value: The quantity of tokens in the smallest denomination to divide.
|
||||||
|
:param expiration: The new expiration date to set as an end period for stake division.
|
||||||
:return: Returns the blockchain transaction hash
|
:return: Returns the blockchain transaction hash
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -427,34 +438,35 @@ class Miner(NucypherTokenActor):
|
||||||
if additional_periods and expiration:
|
if additional_periods and expiration:
|
||||||
raise ValueError("Pass the number of lock periods or an expiration MayaDT; not both.")
|
raise ValueError("Pass the number of lock periods or an expiration MayaDT; not both.")
|
||||||
|
|
||||||
_first_period, last_period, locked_value = self.miner_agent.get_stake_info(miner_address=self.checksum_public_address, stake_index=stake_index)
|
stake = self.__stakes[stake_index]
|
||||||
|
|
||||||
if expiration:
|
if expiration:
|
||||||
additional_periods = datetime_to_period(datetime=expiration) - last_period
|
additional_periods = datetime_to_period(datetime=expiration) - stake.end_period
|
||||||
|
if additional_periods <= 0:
|
||||||
|
raise self.MinerError("Expiration {} must be at least 1 period from now.".format(expiration))
|
||||||
|
|
||||||
if additional_periods <= 0:
|
if target_value >= stake.value:
|
||||||
raise self.MinerError("Expiration {} must be at least 1 period from now.".format(expiration))
|
raise self.MinerError(f"Cannot divide stake; Value ({target_value}) must be less "
|
||||||
|
f"than the existing stake value {stake.value}.")
|
||||||
if target_value >= locked_value:
|
|
||||||
raise self.MinerError("Cannot divide stake; Value must be less than the specified stake value.")
|
|
||||||
|
|
||||||
# Ensure both halves are for valid amounts
|
# Ensure both halves are for valid amounts
|
||||||
validate_stake_amount(amount=target_value)
|
validate_stake_amount(amount=target_value)
|
||||||
validate_stake_amount(amount=locked_value - target_value)
|
validate_stake_amount(amount=stake.value - target_value)
|
||||||
|
|
||||||
tx = self.miner_agent.divide_stake(miner_address=self.checksum_public_address,
|
tx = self.miner_agent.divide_stake(miner_address=self.checksum_public_address,
|
||||||
stake_index=stake_index,
|
stake_index=stake_index,
|
||||||
target_value=target_value,
|
target_value=int(target_value),
|
||||||
periods=additional_periods)
|
periods=additional_periods)
|
||||||
|
|
||||||
self.blockchain.wait_for_receipt(tx)
|
self.blockchain.wait_for_receipt(tx)
|
||||||
|
self.__read_stakes() # update local on-chain stake cache
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
@only_me
|
@only_me
|
||||||
def __validate_stake(self, amount: int, lock_periods: int) -> bool:
|
def __validate_stake(self, amount: NU, lock_periods: int) -> bool:
|
||||||
|
|
||||||
assert validate_stake_amount(amount=amount) # TODO: remove assertions..?
|
validate_stake_amount(amount=amount)
|
||||||
assert validate_locktime(lock_periods=lock_periods)
|
validate_locktime(lock_periods=lock_periods)
|
||||||
|
|
||||||
if not self.token_balance >= amount:
|
if not self.token_balance >= amount:
|
||||||
raise self.MinerError("Insufficient miner token balance ({balance})".format(balance=self.token_balance))
|
raise self.MinerError("Insufficient miner token balance ({balance})".format(balance=self.token_balance))
|
||||||
|
@ -463,7 +475,7 @@ class Miner(NucypherTokenActor):
|
||||||
|
|
||||||
@only_me
|
@only_me
|
||||||
def initialize_stake(self,
|
def initialize_stake(self,
|
||||||
amount: int,
|
amount: NU,
|
||||||
lock_periods: int = None,
|
lock_periods: int = None,
|
||||||
expiration: maya.MayaDT = None,
|
expiration: maya.MayaDT = None,
|
||||||
entire_balance: bool = False) -> dict:
|
entire_balance: bool = False) -> dict:
|
||||||
|
@ -484,20 +496,24 @@ class Miner(NucypherTokenActor):
|
||||||
|
|
||||||
if expiration:
|
if expiration:
|
||||||
lock_periods = calculate_period_duration(future_time=expiration)
|
lock_periods = calculate_period_duration(future_time=expiration)
|
||||||
|
|
||||||
if entire_balance is True:
|
if entire_balance is True:
|
||||||
amount = self.token_balance
|
amount = self.token_balance
|
||||||
|
|
||||||
|
amount = NU(int(amount), 'NuNit')
|
||||||
|
|
||||||
staking_transactions = OrderedDict() # type: OrderedDict # Time series of txhases
|
staking_transactions = OrderedDict() # type: OrderedDict # Time series of txhases
|
||||||
|
|
||||||
# Validate
|
# Validate
|
||||||
assert self.__validate_stake(amount=amount, lock_periods=lock_periods)
|
assert self.__validate_stake(amount=amount, lock_periods=lock_periods)
|
||||||
|
|
||||||
# Transact
|
# Transact
|
||||||
approve_txhash, initial_deposit_txhash = self.deposit(amount=amount, lock_periods=lock_periods)
|
approve_txhash, initial_deposit_txhash = self.deposit(amount=int(amount), lock_periods=lock_periods)
|
||||||
self._transaction_cache.append((datetime.utcnow(), initial_deposit_txhash))
|
self._transaction_cache.append((datetime.utcnow(), initial_deposit_txhash))
|
||||||
|
|
||||||
staking_transactions['approve'] = approve_txhash
|
staking_transactions['approve'] = approve_txhash
|
||||||
staking_transactions['deposit'] = initial_deposit_txhash
|
staking_transactions['deposit'] = initial_deposit_txhash
|
||||||
|
self.__read_stakes() # update local on-chain stake cache
|
||||||
|
|
||||||
self.log.info("{} Initialized new stake: {} tokens for {} periods".format(self.checksum_public_address, amount, lock_periods))
|
self.log.info("{} Initialized new stake: {} tokens for {} periods".format(self.checksum_public_address, amount, lock_periods))
|
||||||
return staking_transactions
|
return staking_transactions
|
||||||
|
|
|
@ -14,10 +14,8 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
"""Nucypher Token and Miner constants."""
|
|
||||||
|
|
||||||
ONE_YEAR_IN_SECONDS = 31540000
|
ONE_YEAR_IN_SECONDS = 31540000
|
||||||
NUCYPHER_GAS_LIMIT = 6500000 # TODO: move elsewhere?
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Dispatcher
|
# Dispatcher
|
||||||
|
@ -38,28 +36,19 @@ POLICY_ID_LENGTH = 16
|
||||||
|
|
||||||
NULL_ADDRESS = '0x' + '0' * 40
|
NULL_ADDRESS = '0x' + '0' * 40
|
||||||
|
|
||||||
__subdigits = 18
|
# Denomination
|
||||||
M = 10 ** __subdigits # Unit designation
|
TOKEN_DECIMALS = 18
|
||||||
|
NUNITS_PER_TOKEN = 10 ** TOKEN_DECIMALS # Smallest unit designation
|
||||||
|
|
||||||
__initial_supply = int(1e9) * M # Initial token supply
|
# Supply
|
||||||
__saturation = int(3.89e9) * M # Token supply cap
|
__initial_supply = int(1e9) * NUNITS_PER_TOKEN # Initial token supply
|
||||||
TOKEN_SUPPLY = __saturation - __initial_supply # Remaining supply
|
__saturation = int(3.89e9) * NUNITS_PER_TOKEN # Token supply cap
|
||||||
|
TOKEN_SUPPLY = __saturation - __initial_supply # Remaining supply
|
||||||
TOKEN_SATURATION = __saturation
|
TOKEN_SATURATION = __saturation
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Miner
|
|
||||||
#
|
|
||||||
|
|
||||||
HOURS_PER_PERIOD = 24 # Hours in single period
|
|
||||||
SECONDS_PER_PERIOD = HOURS_PER_PERIOD * 60 * 60 # Seconds in single period
|
|
||||||
MIN_LOCKED_PERIODS = 30 # 720 Hours minimum
|
|
||||||
MAX_MINTING_PERIODS = 365 # Maximum number of periods
|
|
||||||
|
|
||||||
MIN_ALLOWED_LOCKED = 15000 * M
|
|
||||||
MAX_ALLOWED_LOCKED = int(4e6) * M
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
~ Staking ~
|
||||||
|
|
||||||
Mining formula for one stake in one period:
|
Mining formula for one stake in one period:
|
||||||
(totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2
|
(totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2
|
||||||
|
@ -73,14 +62,28 @@ Mining formula for one stake in one period:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__mining_coeff = (
|
|
||||||
|
# Periods
|
||||||
|
HOURS_PER_PERIOD = 24 # Hours in single period
|
||||||
|
SECONDS_PER_PERIOD = HOURS_PER_PERIOD * 60 * 60 # Seconds in single period
|
||||||
|
|
||||||
|
# Lock Time
|
||||||
|
MIN_LOCKED_PERIODS = 30 # 720 Hours minimum
|
||||||
|
MAX_MINTING_PERIODS = 365 # Maximum number of periods
|
||||||
|
|
||||||
|
# Lock Value
|
||||||
|
MIN_ALLOWED_LOCKED = 15000 * NUNITS_PER_TOKEN
|
||||||
|
MAX_ALLOWED_LOCKED = int(4e6) * NUNITS_PER_TOKEN
|
||||||
|
|
||||||
|
# Rewards
|
||||||
|
K2 = 2 * 10 ** 7 # Mining Coefficient
|
||||||
|
|
||||||
|
MINING_COEFFICIENT = (
|
||||||
HOURS_PER_PERIOD, # Hours in single period
|
HOURS_PER_PERIOD, # Hours in single period
|
||||||
2 * 10 ** 7, # Mining coefficient (k2)
|
K2, # Mining coefficient (k2)
|
||||||
MAX_MINTING_PERIODS, # Locked periods coefficient (k1)
|
MAX_MINTING_PERIODS, # Locked periods coefficient (k1)
|
||||||
MAX_MINTING_PERIODS, # Max periods that will be additionally rewarded (rewardedPeriods)
|
MAX_MINTING_PERIODS, # Max periods that will be additionally rewarded (rewardedPeriods)
|
||||||
MIN_LOCKED_PERIODS, # Min amount of periods during which tokens can be locked
|
MIN_LOCKED_PERIODS, # Min amount of periods during which tokens can be locked
|
||||||
MIN_ALLOWED_LOCKED, # Min amount of tokens that can be locked
|
MIN_ALLOWED_LOCKED, # Min amount of tokens that can be locked
|
||||||
MAX_ALLOWED_LOCKED # Max amount of tokens that can be locked
|
MAX_ALLOWED_LOCKED # Max amount of tokens that can be locked
|
||||||
)
|
)
|
||||||
|
|
||||||
MINING_COEFFICIENT = __mining_coeff
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ from constant_sorrow.constants import (
|
||||||
)
|
)
|
||||||
from eth_tester import EthereumTester
|
from eth_tester import EthereumTester
|
||||||
from eth_tester import PyEVMBackend
|
from eth_tester import PyEVMBackend
|
||||||
from nucypher.blockchain.eth.constants import NUCYPHER_GAS_LIMIT
|
|
||||||
from nucypher.blockchain.eth.registry import EthereumContractRegistry
|
from nucypher.blockchain.eth.registry import EthereumContractRegistry
|
||||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||||
|
|
||||||
|
@ -232,7 +231,8 @@ class BlockchainInterface:
|
||||||
if uri_breakdown.scheme == 'tester':
|
if uri_breakdown.scheme == 'tester':
|
||||||
|
|
||||||
if uri_breakdown.netloc == 'pyevm':
|
if uri_breakdown.netloc == 'pyevm':
|
||||||
genesis_params = PyEVMBackend._generate_genesis_params(overrides={'gas_limit': NUCYPHER_GAS_LIMIT})
|
from nucypher.utilities.sandbox.constants import PYEVM_GAS_LIMIT
|
||||||
|
genesis_params = PyEVMBackend._generate_genesis_params(overrides={'gas_limit': PYEVM_GAS_LIMIT})
|
||||||
pyevm_backend = PyEVMBackend(genesis_parameters=genesis_params)
|
pyevm_backend = PyEVMBackend(genesis_parameters=genesis_params)
|
||||||
eth_tester = EthereumTester(backend=pyevm_backend, auto_mine_transactions=True)
|
eth_tester = EthereumTester(backend=pyevm_backend, auto_mine_transactions=True)
|
||||||
provider = EthereumTesterProvider(ethereum_tester=eth_tester)
|
provider = EthereumTesterProvider(ethereum_tester=eth_tester)
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
from _pydecimal import Decimal
|
||||||
|
|
||||||
|
import eth_utils
|
||||||
|
import maya
|
||||||
|
from eth_utils import currency
|
||||||
|
from nacl.hash import sha256
|
||||||
|
from typing import Union, Tuple
|
||||||
|
|
||||||
|
from nucypher.blockchain.eth.agents import NucypherTokenAgent
|
||||||
|
from nucypher.blockchain.eth.constants import TOKEN_DECIMALS
|
||||||
|
from nucypher.blockchain.eth.utils import datetime_at_period, datetime_to_period
|
||||||
|
|
||||||
|
|
||||||
|
class NU:
|
||||||
|
"""
|
||||||
|
An amount of NuCypher tokens that doesn't hurt your eyes.
|
||||||
|
Wraps the eth_utils currency conversion methods.
|
||||||
|
|
||||||
|
The easiest way to use NU, is to pass an int, float, or str, and denomination string:
|
||||||
|
|
||||||
|
Int: nu = NU(100, 'NU')
|
||||||
|
Int: nu_wei = NU(15000000000000000000000, 'NuNit')
|
||||||
|
|
||||||
|
Float: nu = NU(15042.445, 'NU')
|
||||||
|
String: nu = NU('10002.302', 'NU')
|
||||||
|
|
||||||
|
...or alternately...
|
||||||
|
|
||||||
|
Float: nu = NU.from_tokens(100.50)
|
||||||
|
Int: nu_wei = NU.from_nu_wei(15000000000000000000000)
|
||||||
|
|
||||||
|
Token quantity is stored internally as an int in the smallest denomination,
|
||||||
|
and all arithmetic operations use this value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__symbol = 'NU'
|
||||||
|
__decimals = TOKEN_DECIMALS
|
||||||
|
__agent_class = NucypherTokenAgent
|
||||||
|
|
||||||
|
# conversions
|
||||||
|
__denominations = {'NuNit': 'wei',
|
||||||
|
'NU': 'ether'}
|
||||||
|
|
||||||
|
class InvalidAmount(ValueError):
|
||||||
|
"""Raised when an invalid input amount is provided"""
|
||||||
|
|
||||||
|
def __init__(self, value: Union[int, float, str], denomination: str):
|
||||||
|
|
||||||
|
# Calculate smallest denomination and store it
|
||||||
|
wrapped_denom = self.__denominations[denomination]
|
||||||
|
|
||||||
|
# Convert or Raise
|
||||||
|
try:
|
||||||
|
self.__value = currency.to_wei(number=value, unit=wrapped_denom)
|
||||||
|
except ValueError as e:
|
||||||
|
raise NU.InvalidAmount(f"{value} is an invalid amount of tokens: {str(e)}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_nunits(cls, value: int):
|
||||||
|
return cls(value, denomination='NuNit')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_tokens(cls, value: Union[int, float, str]):
|
||||||
|
return cls(value, denomination='NU')
|
||||||
|
|
||||||
|
def to_tokens(self) -> Decimal:
|
||||||
|
"""Returns an decimal value of NU"""
|
||||||
|
return currency.from_wei(self.__value, unit='ether')
|
||||||
|
|
||||||
|
def to_nunits(self) -> int:
|
||||||
|
"""Returns an int value in NuNit"""
|
||||||
|
return int(self.__value)
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
return int(self) == int(other)
|
||||||
|
|
||||||
|
def __radd__(self, other) -> 'NU':
|
||||||
|
return NU(int(self) + int(other), 'NuNit')
|
||||||
|
|
||||||
|
def __add__(self, other) -> 'NU':
|
||||||
|
return NU(int(self) + int(other), 'NuNit')
|
||||||
|
|
||||||
|
def __sub__(self, other) -> 'NU':
|
||||||
|
return NU(int(self) - int(other), 'NuNit')
|
||||||
|
|
||||||
|
def __rmul__(self, other) -> 'NU':
|
||||||
|
return NU(int(self) * int(other), 'NuNit')
|
||||||
|
|
||||||
|
def __mul__(self, other) -> 'NU':
|
||||||
|
return NU(int(self) * int(other), 'NuNit')
|
||||||
|
|
||||||
|
def __floordiv__(self, other) -> 'NU':
|
||||||
|
return NU(int(self) // int(other), 'NuNit')
|
||||||
|
|
||||||
|
def __gt__(self, other) -> bool:
|
||||||
|
return int(self) > int(other)
|
||||||
|
|
||||||
|
def __ge__(self, other) -> bool:
|
||||||
|
return int(self) >= int(other)
|
||||||
|
|
||||||
|
def __lt__(self, other) -> bool:
|
||||||
|
return int(self) < int(other)
|
||||||
|
|
||||||
|
def __le__(self, other) -> bool:
|
||||||
|
return int(self) <= int(other)
|
||||||
|
|
||||||
|
def __int__(self) -> int:
|
||||||
|
"""Cast to smallest denomination"""
|
||||||
|
return int(self.to_nunits())
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
r = f'{self.__symbol}(value={str(self.__value)})'
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{str(self.to_tokens())} {self.__symbol}'
|
||||||
|
|
||||||
|
|
||||||
|
class Stake:
|
||||||
|
"""
|
||||||
|
A quantity of tokens, and staking time-frame for one stake for one miner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__ID_LENGTH = 16
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
owner_address: str,
|
||||||
|
index: int,
|
||||||
|
value: NU,
|
||||||
|
start_period: int,
|
||||||
|
end_period: int):
|
||||||
|
|
||||||
|
# Stake Info
|
||||||
|
self.owner_address = owner_address
|
||||||
|
self.index = index
|
||||||
|
self.value = value
|
||||||
|
self.start_period = start_period
|
||||||
|
self.end_period = end_period
|
||||||
|
self.duration = (self.end_period-self.start_period) + 1
|
||||||
|
|
||||||
|
# Internals
|
||||||
|
self.start_datetime = datetime_at_period(period=start_period)
|
||||||
|
self.end_datetime = datetime_at_period(period=end_period)
|
||||||
|
self.duration_delta = self.end_datetime - self.start_datetime
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
r = f'Stake(index={self.index}, value={self.value}, end_period={self.end_period})'
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return bool(self.value == other.value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_stake_info(cls, owner_address, index: int, stake_info: Tuple[int, int, int]):
|
||||||
|
"""Reads staking values as they exist on the blockchain"""
|
||||||
|
start_period, end_period, value = stake_info
|
||||||
|
instance = cls(owner_address=owner_address,
|
||||||
|
index=index,
|
||||||
|
start_period=start_period,
|
||||||
|
end_period=end_period,
|
||||||
|
value=NU(value, 'NuNit'))
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def to_stake_info(self) -> Tuple[int, int, int]:
|
||||||
|
"""Returns a tuple representing the blockchain record of a stake"""
|
||||||
|
return self.start_period, self.end_period, int(self.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> str:
|
||||||
|
"""TODO: Unique staking ID, currently unused"""
|
||||||
|
digest_elements = list()
|
||||||
|
digest_elements.append(eth_utils.to_canonical_address(address=self.owner_address))
|
||||||
|
digest_elements.append(str(self.index).encode())
|
||||||
|
digest_elements.append(str(self.start_period).encode())
|
||||||
|
digest_elements.append(str(self.end_period).encode())
|
||||||
|
digest_elements.append(str(self.value).encode())
|
||||||
|
digest = b'|'.join(digest_elements)
|
||||||
|
stake_id = sha256(digest).hex()[:16]
|
||||||
|
return stake_id[:self.__ID_LENGTH]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def periods_remaining(self) -> int:
|
||||||
|
"""Returns the number of periods remaining in the stake from now."""
|
||||||
|
current_period = datetime_to_period(datetime=maya.now())
|
||||||
|
return self.end_period - current_period
|
||||||
|
|
||||||
|
def time_remaining(self, slang: bool = False) -> Union[int, str]:
|
||||||
|
"""Returns the time delta remaining in the stake from now."""
|
||||||
|
now = maya.now()
|
||||||
|
delta = self.end_datetime - now
|
||||||
|
|
||||||
|
if slang:
|
||||||
|
result = self.end_datetime.slang_date()
|
||||||
|
else:
|
||||||
|
result = delta.seconds
|
||||||
|
return result
|
|
@ -14,6 +14,7 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import maya
|
import maya
|
||||||
|
|
||||||
from nucypher.blockchain.eth.constants import (MIN_ALLOWED_LOCKED,
|
from nucypher.blockchain.eth.constants import (MIN_ALLOWED_LOCKED,
|
||||||
|
@ -23,59 +24,17 @@ from nucypher.blockchain.eth.constants import (MIN_ALLOWED_LOCKED,
|
||||||
SECONDS_PER_PERIOD)
|
SECONDS_PER_PERIOD)
|
||||||
|
|
||||||
|
|
||||||
def __validate(rulebook) -> bool:
|
|
||||||
for rule, failure_message in rulebook:
|
|
||||||
if not rule:
|
|
||||||
raise ValueError(failure_message)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_stake_amount(amount: int, raise_on_fail=True) -> bool:
|
|
||||||
|
|
||||||
rulebook = (
|
|
||||||
|
|
||||||
(MIN_ALLOWED_LOCKED <= amount,
|
|
||||||
'Stake amount too low; ({amount}) must be at least {minimum}'
|
|
||||||
.format(minimum=MIN_ALLOWED_LOCKED, amount=amount)),
|
|
||||||
|
|
||||||
(MAX_ALLOWED_LOCKED >= amount,
|
|
||||||
'Stake amount too high; ({amount}) must be no more than {maximum}.'
|
|
||||||
.format(maximum=MAX_ALLOWED_LOCKED, amount=amount)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if raise_on_fail is True:
|
|
||||||
__validate(rulebook=rulebook)
|
|
||||||
return all(rulebook)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_locktime(lock_periods: int, raise_on_fail=True) -> bool:
|
|
||||||
|
|
||||||
rulebook = (
|
|
||||||
|
|
||||||
(MIN_LOCKED_PERIODS <= lock_periods,
|
|
||||||
'Locktime ({locktime}) too short; must be at least {minimum}'
|
|
||||||
.format(minimum=MIN_LOCKED_PERIODS, locktime=lock_periods)),
|
|
||||||
|
|
||||||
(MAX_MINTING_PERIODS >= lock_periods,
|
|
||||||
'Locktime ({locktime}) too long; must be no more than {maximum}'
|
|
||||||
.format(maximum=MAX_MINTING_PERIODS, locktime=lock_periods)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if raise_on_fail is True:
|
|
||||||
__validate(rulebook=rulebook)
|
|
||||||
return all(rulebook)
|
|
||||||
|
|
||||||
|
|
||||||
def datetime_to_period(datetime: maya.MayaDT) -> int:
|
def datetime_to_period(datetime: maya.MayaDT) -> int:
|
||||||
"""Converts a MayaDT instance to a period number."""
|
"""Converts a MayaDT instance to a period number."""
|
||||||
future_period = datetime._epoch // int(SECONDS_PER_PERIOD)
|
future_period = datetime.epoch // int(SECONDS_PER_PERIOD)
|
||||||
return int(future_period)
|
return int(future_period)
|
||||||
|
|
||||||
|
|
||||||
def datetime_at_period(period: int) -> maya.MayaDT:
|
def datetime_at_period(period: int) -> maya.MayaDT:
|
||||||
|
"""Returns the datetime object at a given period, future, or past."""
|
||||||
|
|
||||||
now = maya.now()
|
now = maya.now()
|
||||||
current_period = datetime_to_period(datetime=now)
|
current_period = datetime_to_period(datetime=now)
|
||||||
|
|
||||||
delta_periods = period - current_period
|
delta_periods = period - current_period
|
||||||
|
|
||||||
# +
|
# +
|
||||||
|
@ -95,3 +54,53 @@ def calculate_period_duration(future_time: maya.MayaDT) -> int:
|
||||||
current_period = datetime_to_period(datetime=maya.now())
|
current_period = datetime_to_period(datetime=maya.now())
|
||||||
periods = future_period - current_period
|
periods = future_period - current_period
|
||||||
return periods
|
return periods
|
||||||
|
|
||||||
|
|
||||||
|
def __validate(rulebook) -> bool:
|
||||||
|
"""Validate a rulebook"""
|
||||||
|
for rule, failure_message in rulebook:
|
||||||
|
if not rule:
|
||||||
|
raise ValueError(failure_message)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_stake_amount(amount, raise_on_fail=True) -> bool:
|
||||||
|
"""Validate a single staking value against pre-defined requirements"""
|
||||||
|
from nucypher.blockchain.eth.token import NU
|
||||||
|
|
||||||
|
min_locked = NU(MIN_ALLOWED_LOCKED, 'NuNit')
|
||||||
|
max_locked = NU(MAX_ALLOWED_LOCKED, 'NuNit')
|
||||||
|
|
||||||
|
rulebook = (
|
||||||
|
|
||||||
|
(min_locked <= amount,
|
||||||
|
'Stake amount too low; ({amount}) must be at least {minimum}'
|
||||||
|
.format(minimum=min_locked, amount=amount)),
|
||||||
|
|
||||||
|
(max_locked >= amount,
|
||||||
|
'Stake amount too high; ({amount}) must be no more than {maximum}.'
|
||||||
|
.format(maximum=max_locked, amount=amount)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if raise_on_fail is True:
|
||||||
|
__validate(rulebook=rulebook)
|
||||||
|
return all(rulebook)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_locktime(lock_periods: int, raise_on_fail=True) -> bool:
|
||||||
|
"""Validate a single staking lock-time against pre-defined requirements"""
|
||||||
|
|
||||||
|
rulebook = (
|
||||||
|
|
||||||
|
(MIN_LOCKED_PERIODS <= lock_periods,
|
||||||
|
'Locktime ({locktime}) too short; must be at least {minimum}'
|
||||||
|
.format(minimum=MIN_LOCKED_PERIODS, locktime=lock_periods)),
|
||||||
|
|
||||||
|
(MAX_MINTING_PERIODS >= lock_periods,
|
||||||
|
'Locktime ({locktime}) too long; must be no more than {maximum}'
|
||||||
|
.format(maximum=MAX_MINTING_PERIODS, locktime=lock_periods)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if raise_on_fail is True:
|
||||||
|
__validate(rulebook=rulebook)
|
||||||
|
return all(rulebook)
|
||||||
|
|
|
@ -98,7 +98,7 @@ def confirm_staged_stake(ursula, value, duration):
|
||||||
* Ursula Node Operator Notice *
|
* Ursula Node Operator Notice *
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
By agreeing to stake {value} NU:
|
By agreeing to stake {str(value)}:
|
||||||
|
|
||||||
- Staked tokens will be locked, and unavailable for transactions for the stake duration.
|
- Staked tokens will be locked, and unavailable for transactions for the stake duration.
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ from nucypher.config.constants import GLOBAL_DOMAIN
|
||||||
@click.option('--bob-encrypting-key', help="Bob's encrypting key as a hexideicmal string", type=click.STRING)
|
@click.option('--bob-encrypting-key', help="Bob's encrypting key as a hexideicmal string", type=click.STRING)
|
||||||
@click.option('--bob-verifying-key', help="Bob's verifying key as a hexideicmal string", type=click.STRING)
|
@click.option('--bob-verifying-key', help="Bob's verifying key as a hexideicmal string", type=click.STRING)
|
||||||
@click.option('--label', help="The label for a policy", type=click.STRING)
|
@click.option('--label', help="The label for a policy", type=click.STRING)
|
||||||
@click.option('--m', help="M", type=click.INT)
|
@click.option('--m', help="M-Threshold KFrags", type=click.INT)
|
||||||
@click.option('--n', help="N", type=click.INT)
|
@click.option('--n', help="N-Total KFrags", type=click.INT)
|
||||||
@click.option('--dev', '-d', help="Enable development mode", is_flag=True)
|
@click.option('--dev', '-d', help="Enable development mode", is_flag=True)
|
||||||
@click.option('--force', help="Don't ask for confirmation", is_flag=True)
|
@click.option('--force', help="Don't ask for confirmation", is_flag=True)
|
||||||
@click.option('--dry-run', '-x', help="Execute normally without actually starting the node", is_flag=True)
|
@click.option('--dry-run', '-x', help="Execute normally without actually starting the node", is_flag=True)
|
||||||
|
|
|
@ -17,14 +17,12 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import maya
|
|
||||||
from constant_sorrow.constants import TEMPORARY_DOMAIN
|
from constant_sorrow.constants import TEMPORARY_DOMAIN
|
||||||
from twisted.internet import stdio
|
from twisted.internet import stdio
|
||||||
from twisted.logger import Logger
|
from twisted.logger import Logger
|
||||||
from web3 import Web3
|
|
||||||
|
|
||||||
from nucypher.blockchain.eth.constants import MIN_LOCKED_PERIODS, MAX_MINTING_PERIODS, MIN_ALLOWED_LOCKED
|
from nucypher.blockchain.eth.constants import MIN_LOCKED_PERIODS, MAX_MINTING_PERIODS, MIN_ALLOWED_LOCKED
|
||||||
from nucypher.blockchain.eth.utils import datetime_at_period, calculate_period_duration
|
from nucypher.blockchain.eth.token import NU
|
||||||
from nucypher.characters.banners import URSULA_BANNER
|
from nucypher.characters.banners import URSULA_BANNER
|
||||||
from nucypher.cli import actions, painting
|
from nucypher.cli import actions, painting
|
||||||
from nucypher.cli.actions import destroy_system_configuration
|
from nucypher.cli.actions import destroy_system_configuration
|
||||||
|
@ -34,7 +32,10 @@ from nucypher.cli.types import (
|
||||||
EIP55_CHECKSUM_ADDRESS,
|
EIP55_CHECKSUM_ADDRESS,
|
||||||
NETWORK_PORT,
|
NETWORK_PORT,
|
||||||
EXISTING_READABLE_FILE,
|
EXISTING_READABLE_FILE,
|
||||||
STAKE_DURATION, STAKE_EXTENSION)
|
STAKE_DURATION,
|
||||||
|
STAKE_EXTENSION,
|
||||||
|
STAKE_VALUE
|
||||||
|
)
|
||||||
from nucypher.config.characters import UrsulaConfiguration
|
from nucypher.config.characters import UrsulaConfiguration
|
||||||
from nucypher.utilities.sandbox.constants import (
|
from nucypher.utilities.sandbox.constants import (
|
||||||
TEMPORARY_DOMAIN,
|
TEMPORARY_DOMAIN,
|
||||||
|
@ -68,6 +69,7 @@ from nucypher.utilities.sandbox.constants import (
|
||||||
@click.option('--duration', help="Period duration of stake", type=click.INT)
|
@click.option('--duration', help="Period duration of stake", type=click.INT)
|
||||||
@click.option('--index', help="A specific stake index to resume", type=click.INT)
|
@click.option('--index', help="A specific stake index to resume", type=click.INT)
|
||||||
@click.option('--list', '-l', 'list_', help="List all blockchain stakes", is_flag=True)
|
@click.option('--list', '-l', 'list_', help="List all blockchain stakes", is_flag=True)
|
||||||
|
@click.option('--divide', '-d', help="Divide an existing stake into sub-stakes.", is_flag=True)
|
||||||
@nucypher_click_config
|
@nucypher_click_config
|
||||||
def ursula(click_config,
|
def ursula(click_config,
|
||||||
action,
|
action,
|
||||||
|
@ -95,7 +97,8 @@ def ursula(click_config,
|
||||||
value,
|
value,
|
||||||
duration,
|
duration,
|
||||||
index,
|
index,
|
||||||
list_
|
list_,
|
||||||
|
divide
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Manage and run an "Ursula" PRE node.
|
Manage and run an "Ursula" PRE node.
|
||||||
|
@ -112,7 +115,6 @@ def ursula(click_config,
|
||||||
destroy Delete Ursula node configuration.
|
destroy Delete Ursula node configuration.
|
||||||
stake Manage stakes for this node.
|
stake Manage stakes for this node.
|
||||||
confirm-activity Manually confirm-activity for the current period.
|
confirm-activity Manually confirm-activity for the current period.
|
||||||
divide-stake Divide an existing stake.
|
|
||||||
collect-reward Withdraw staking reward.
|
collect-reward Withdraw staking reward.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -139,7 +141,7 @@ def ursula(click_config,
|
||||||
click.secho("WARNING: Force is enabled", fg='yellow')
|
click.secho("WARNING: Force is enabled", fg='yellow')
|
||||||
|
|
||||||
#
|
#
|
||||||
# Unauthenticated Configurations & Unconfigured Ursula Control
|
# Unauthenticated Configurations & Un-configured Ursula Control
|
||||||
#
|
#
|
||||||
if action == "init":
|
if action == "init":
|
||||||
"""Create a brand-new persistent Ursula"""
|
"""Create a brand-new persistent Ursula"""
|
||||||
|
@ -169,7 +171,10 @@ def ursula(click_config,
|
||||||
provider_uri=provider_uri,
|
provider_uri=provider_uri,
|
||||||
poa=poa)
|
poa=poa)
|
||||||
|
|
||||||
painting.paint_new_installation_help(new_configuration=ursula_config, config_root=config_root, config_file=config_file)
|
painting.paint_new_installation_help(new_configuration=ursula_config,
|
||||||
|
config_root=config_root,
|
||||||
|
config_file=config_file,
|
||||||
|
federated_only=federated_only)
|
||||||
return
|
return
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -255,9 +260,8 @@ def ursula(click_config,
|
||||||
bold=True)
|
bold=True)
|
||||||
|
|
||||||
if not URSULA.federated_only and URSULA.stakes:
|
if not URSULA.federated_only and URSULA.stakes:
|
||||||
total = URSULA.blockchain.interface.w3.fromWei(URSULA.total_staked, 'ether')
|
|
||||||
click_config.emitter(
|
click_config.emitter(
|
||||||
message=f"Staking {total} NU ~ Keep Ursula Online!",
|
message=f"Staking {str(URSULA.total_staked)} ~ Keep Ursula Online!",
|
||||||
color='blue',
|
color='blue',
|
||||||
bold=True)
|
bold=True)
|
||||||
|
|
||||||
|
@ -320,12 +324,61 @@ def ursula(click_config,
|
||||||
|
|
||||||
elif action == 'stake':
|
elif action == 'stake':
|
||||||
|
|
||||||
# List only
|
# List Only
|
||||||
if list_:
|
if list_:
|
||||||
live_stakes = list(URSULA.miner_agent.get_all_stakes(miner_address=URSULA.checksum_public_address))
|
if not URSULA.stakes:
|
||||||
if not live_stakes:
|
|
||||||
click.echo(f"There are no existing stakes for {URSULA.checksum_public_address}")
|
click.echo(f"There are no existing stakes for {URSULA.checksum_public_address}")
|
||||||
painting.paint_stakes(stakes=live_stakes)
|
painting.paint_stakes(stakes=URSULA.stakes)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Divide Only
|
||||||
|
if divide:
|
||||||
|
"""Divide an existing stake by specifying the new target value and end period"""
|
||||||
|
|
||||||
|
# Validate
|
||||||
|
if len(URSULA.stakes) == 0:
|
||||||
|
click.secho("There are no active stakes for {}".format(URSULA.checksum_public_address))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Selection
|
||||||
|
if index is None:
|
||||||
|
painting.paint_stakes(stakes=URSULA.stakes)
|
||||||
|
index = click.prompt("Select a stake to divide", type=click.IntRange(min=0, max=len(URSULA.stakes)-1))
|
||||||
|
|
||||||
|
# Lookup the stake
|
||||||
|
current_stake = URSULA.stakes[index]
|
||||||
|
|
||||||
|
# Value
|
||||||
|
if not value:
|
||||||
|
value = click.prompt(f"Enter target value (must be less than {str(current_stake.value)})", type=STAKE_VALUE)
|
||||||
|
value = NU(value, 'NU')
|
||||||
|
|
||||||
|
# Duration
|
||||||
|
if not duration:
|
||||||
|
extension = click.prompt("Enter number of periods to extend", type=STAKE_EXTENSION)
|
||||||
|
else:
|
||||||
|
extension = duration
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
painting.paint_staged_stake_division(ursula=URSULA,
|
||||||
|
original_index=index,
|
||||||
|
original_stake=current_stake,
|
||||||
|
target_value=value,
|
||||||
|
extension=extension)
|
||||||
|
|
||||||
|
click.confirm("Is this correct?", abort=True)
|
||||||
|
|
||||||
|
txhash_bytes = URSULA.divide_stake(stake_index=index,
|
||||||
|
target_value=value,
|
||||||
|
additional_periods=extension)
|
||||||
|
|
||||||
|
if not quiet:
|
||||||
|
click.secho('Successfully divided stake', fg='green')
|
||||||
|
click.secho(f'Transaction Hash ........... {txhash_bytes.hex()}')
|
||||||
|
|
||||||
|
# Show the resulting stake list
|
||||||
|
painting.paint_stakes(stakes=URSULA.stakes)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Confirm new stake init
|
# Confirm new stake init
|
||||||
|
@ -333,7 +386,7 @@ def ursula(click_config,
|
||||||
click.confirm("Stage a new stake?", abort=True)
|
click.confirm("Stage a new stake?", abort=True)
|
||||||
|
|
||||||
# Validate balance
|
# Validate balance
|
||||||
balance = URSULA.token_agent.get_balance(address=URSULA.checksum_public_address)
|
balance = URSULA.token_balance
|
||||||
if balance == 0:
|
if balance == 0:
|
||||||
click.secho(f"{ursula.checksum_public_address} has 0 NU.")
|
click.secho(f"{ursula.checksum_public_address} has 0 NU.")
|
||||||
raise click.Abort
|
raise click.Abort
|
||||||
|
@ -342,10 +395,9 @@ def ursula(click_config,
|
||||||
|
|
||||||
# Gather stake value
|
# Gather stake value
|
||||||
if not value:
|
if not value:
|
||||||
min_stake_nu = int(URSULA.blockchain.interface.w3.fromWei(MIN_ALLOWED_LOCKED, 'ether'))
|
value = click.prompt(f"Enter stake value", type=STAKE_VALUE, default=NU(MIN_ALLOWED_LOCKED, 'NuNit'))
|
||||||
value = click.prompt(f"Enter stake value in NU", type=click.INT, default=min_stake_nu)
|
else:
|
||||||
stake_wei = URSULA.blockchain.interface.w3.toWei(value, 'ether')
|
value = NU(int(value), 'NU')
|
||||||
stake_nu = value
|
|
||||||
|
|
||||||
# Duration
|
# Duration
|
||||||
if not quiet:
|
if not quiet:
|
||||||
|
@ -359,8 +411,7 @@ def ursula(click_config,
|
||||||
# Review
|
# Review
|
||||||
if not force:
|
if not force:
|
||||||
painting.paint_staged_stake(ursula=URSULA,
|
painting.paint_staged_stake(ursula=URSULA,
|
||||||
stake_nu=stake_nu,
|
stake_value=value,
|
||||||
stake_wei=stake_wei,
|
|
||||||
duration=duration,
|
duration=duration,
|
||||||
start_period=start_period,
|
start_period=start_period,
|
||||||
end_period=end_period)
|
end_period=end_period)
|
||||||
|
@ -372,80 +423,17 @@ def ursula(click_config,
|
||||||
if not force:
|
if not force:
|
||||||
click.confirm("Publish staged stake to the blockchain?", abort=True)
|
click.confirm("Publish staged stake to the blockchain?", abort=True)
|
||||||
|
|
||||||
staking_transactions = URSULA.initialize_stake(amount=stake_wei, lock_periods=duration)
|
staking_transactions = URSULA.initialize_stake(amount=int(value), lock_periods=duration)
|
||||||
painting.paint_staking_confirmation(ursula=URSULA, transactions=staking_transactions)
|
painting.paint_staking_confirmation(ursula=URSULA, transactions=staking_transactions)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif action == 'confirm-activity':
|
elif action == 'confirm-activity':
|
||||||
stakes = URSULA.miner_agent.get_all_stakes(miner_address=URSULA.checksum_public_address)
|
if not URSULA.stakes:
|
||||||
if len(stakes) == 0:
|
|
||||||
click.secho("There are no active stakes for {}".format(URSULA.checksum_public_address))
|
click.secho("There are no active stakes for {}".format(URSULA.checksum_public_address))
|
||||||
return
|
return
|
||||||
URSULA.miner_agent.confirm_activity(node_address=URSULA.checksum_public_address)
|
URSULA.miner_agent.confirm_activity(node_address=URSULA.checksum_public_address)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif action == 'divide-stake':
|
|
||||||
"""Divide an existing stake by specifying the new target value and end period"""
|
|
||||||
|
|
||||||
stakes = list(URSULA.stakes)
|
|
||||||
if len(stakes) == 0:
|
|
||||||
click.secho("There are no active stakes for {}".format(URSULA.checksum_public_address))
|
|
||||||
return
|
|
||||||
|
|
||||||
if index is None:
|
|
||||||
painting.paint_stakes(stakes=stakes)
|
|
||||||
index = click.prompt("Select a stake to divide", type=click.IntRange(min=0, max=len(stakes)-1, clamp=False))
|
|
||||||
stake_info = stakes[index]
|
|
||||||
start_period, end_period, current_stake_wei = stake_info
|
|
||||||
current_stake_nu = int(Web3.fromWei(current_stake_wei, 'ether'))
|
|
||||||
|
|
||||||
# Value
|
|
||||||
if not value:
|
|
||||||
min_value = URSULA.blockchain.interface.w3.fromWei(MIN_ALLOWED_LOCKED, 'ether')
|
|
||||||
target_value = click.prompt(f"Enter target value (must be less than {current_stake_nu} NU)",
|
|
||||||
type=click.IntRange(min=min_value, max=current_stake_nu, clamp=False))
|
|
||||||
target_value = Web3.toWei(target_value, 'ether')
|
|
||||||
else:
|
|
||||||
target_value = value
|
|
||||||
|
|
||||||
# Duration
|
|
||||||
if not duration:
|
|
||||||
extension = click.prompt("Enter number of periods to extend", type=STAKE_EXTENSION)
|
|
||||||
else:
|
|
||||||
extension = duration
|
|
||||||
|
|
||||||
if not force:
|
|
||||||
new_end_period = end_period + extension
|
|
||||||
duration = new_end_period - start_period
|
|
||||||
|
|
||||||
division_message = f"""
|
|
||||||
{URSULA}
|
|
||||||
~ Original Stake: {painting.prettify_stake(stake_index=index, stake_info=stake_info)}
|
|
||||||
"""
|
|
||||||
|
|
||||||
painting.paint_staged_stake(ursula=URSULA,
|
|
||||||
stake_nu=int(Web3.fromWei(target_value, 'ether')),
|
|
||||||
stake_wei=target_value,
|
|
||||||
duration=duration,
|
|
||||||
start_period=stakes[index][0],
|
|
||||||
end_period=new_end_period,
|
|
||||||
division_message=division_message)
|
|
||||||
|
|
||||||
click.confirm("Is this correct?", abort=True)
|
|
||||||
|
|
||||||
txhash_bytes = URSULA.divide_stake(stake_index=index,
|
|
||||||
target_value=target_value,
|
|
||||||
additional_periods=extension)
|
|
||||||
|
|
||||||
if not quiet:
|
|
||||||
click.secho('\nSuccessfully divided stake', fg='green')
|
|
||||||
click.secho(f'Transaction Hash ........... {txhash_bytes.hex()}\n', fg='green')
|
|
||||||
|
|
||||||
# Show the resulting stake list
|
|
||||||
painting.paint_stakes(stakes=URSULA.stakes)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
elif action == 'collect-reward':
|
elif action == 'collect-reward':
|
||||||
"""Withdraw staking reward to the specified wallet address"""
|
"""Withdraw staking reward to the specified wallet address"""
|
||||||
if not force:
|
if not force:
|
||||||
|
|
|
@ -15,12 +15,11 @@ You should have received a copy of the GNU Affero General Public License
|
||||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from typing import Tuple
|
from decimal import Decimal
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import maya
|
import maya
|
||||||
from constant_sorrow.constants import NO_KNOWN_NODES
|
from constant_sorrow.constants import NO_KNOWN_NODES
|
||||||
from web3 import Web3
|
|
||||||
|
|
||||||
from nucypher.blockchain.eth.utils import datetime_at_period
|
from nucypher.blockchain.eth.utils import datetime_at_period
|
||||||
from nucypher.characters.banners import NUCYPHER_BANNER
|
from nucypher.characters.banners import NUCYPHER_BANNER
|
||||||
|
@ -37,14 +36,18 @@ def echo_version(ctx, param, value):
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
|
|
||||||
def paint_new_installation_help(new_configuration, config_root=None, config_file=None):
|
def paint_new_installation_help(new_configuration, federated_only: bool = False, config_root=None, config_file=None):
|
||||||
character_config_class = new_configuration.__class__
|
character_config_class = new_configuration.__class__
|
||||||
character_name = character_config_class._NAME.lower()
|
character_name = character_config_class._NAME.lower()
|
||||||
|
|
||||||
emitter(message="Generated keyring {}".format(new_configuration.keyring_dir), color='green')
|
emitter(message="Generated keyring {}".format(new_configuration.keyring_dir), color='green')
|
||||||
|
|
||||||
emitter(message="Saved configuration file {}".format(new_configuration.config_file_location), color='green')
|
emitter(message="Saved configuration file {}".format(new_configuration.config_file_location), color='green')
|
||||||
|
|
||||||
|
if character_name == 'ursula' and not federated_only:
|
||||||
|
suggested_staking_command = f'nucypher ursula stake'
|
||||||
|
how_to_stake_message = f"\nTo initialize a NU stake, run '{suggested_staking_command}' or"
|
||||||
|
emitter(message=how_to_stake_message, color='green')
|
||||||
|
|
||||||
# Give the use a suggestion as to what to do next...
|
# Give the use a suggestion as to what to do next...
|
||||||
suggested_command = f'nucypher {character_name} run'
|
suggested_command = f'nucypher {character_name} run'
|
||||||
how_to_run_message = f"\nTo run an {character_name.capitalize()} node from the default configuration filepath run: \n\n'{suggested_command}'\n"
|
how_to_run_message = f"\nTo run an {character_name.capitalize()} node from the default configuration filepath run: \n\n'{suggested_command}'\n"
|
||||||
|
@ -194,8 +197,7 @@ def paint_contract_status(ursula_config, click_config):
|
||||||
|
|
||||||
|
|
||||||
def paint_staged_stake(ursula,
|
def paint_staged_stake(ursula,
|
||||||
stake_nu,
|
stake_value,
|
||||||
stake_wei,
|
|
||||||
duration,
|
duration,
|
||||||
start_period,
|
start_period,
|
||||||
end_period,
|
end_period,
|
||||||
|
@ -209,7 +211,7 @@ def paint_staged_stake(ursula,
|
||||||
|
|
||||||
click.echo(f"""
|
click.echo(f"""
|
||||||
{ursula}
|
{ursula}
|
||||||
~ Value -> {stake_nu} NU ({stake_wei} NU-wei)
|
~ Value -> {stake_value} ({Decimal(int(stake_value)):.2E} NuNit)
|
||||||
~ Duration -> {duration} Days ({duration} Periods)
|
~ Duration -> {duration} Days ({duration} Periods)
|
||||||
~ Enactment -> {datetime_at_period(period=start_period)} (period #{start_period})
|
~ Enactment -> {datetime_at_period(period=start_period)} (period #{start_period})
|
||||||
~ Expiration -> {datetime_at_period(period=end_period)} (period #{end_period})
|
~ Expiration -> {datetime_at_period(period=end_period)} (period #{end_period})
|
||||||
|
@ -231,26 +233,45 @@ or start your Ursula node by running 'nucypher ursula run'.
|
||||||
''', fg='green')
|
''', fg='green')
|
||||||
|
|
||||||
|
|
||||||
def prettify_stake(stake_index: int, stake_info: Tuple[int, int, str]) -> str:
|
def prettify_stake(stake_index: int, stake) -> str:
|
||||||
start, expiration, stake_wei = stake_info
|
|
||||||
|
|
||||||
stake_nu = int(Web3.fromWei(stake_wei, 'ether'))
|
start_datetime = str(stake.start_datetime.slang_date())
|
||||||
|
expiration_datetime = str(stake.end_datetime.slang_date())
|
||||||
start_datetime = str(datetime_at_period(period=start).slang_date())
|
duration = stake.duration
|
||||||
expiration_datetime = str(datetime_at_period(period=expiration).slang_date())
|
|
||||||
duration = expiration - start
|
|
||||||
|
|
||||||
pretty_periods = f'{duration} periods {"." if len(str(duration)) == 2 else ""}'
|
pretty_periods = f'{duration} periods {"." if len(str(duration)) == 2 else ""}'
|
||||||
pretty = f'| {stake_index} | {pretty_periods} | {start_datetime} .. | {expiration_datetime} ... | {stake_nu} NU '
|
pretty = f'| {stake_index} | {pretty_periods} | {start_datetime} .. | {expiration_datetime} ... | {str(stake.value)}'
|
||||||
return pretty
|
return pretty
|
||||||
|
|
||||||
|
|
||||||
def paint_stakes(stakes):
|
def paint_stakes(stakes):
|
||||||
header = f'| # | Duration | Enact | Expiration | Value '
|
header = f'| # | Duration | Enact | Expiration | Value '
|
||||||
breaky = f'| - | ------------ | --------- | -----------| ----- '
|
breaky = f'| - | ------------ | ----------- | -----------| ----- '
|
||||||
click.secho(header, bold=True)
|
click.secho(header, bold=True)
|
||||||
click.secho(breaky, bold=True)
|
click.secho(breaky, bold=True)
|
||||||
for index, stake_info in enumerate(stakes):
|
for index, stake in stakes.items():
|
||||||
row = prettify_stake(stake_index=index, stake_info=stake_info)
|
row = prettify_stake(stake_index=index, stake=stake)
|
||||||
click.echo(row)
|
click.echo(row)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def paint_staged_stake_division(ursula,
|
||||||
|
original_index,
|
||||||
|
original_stake,
|
||||||
|
target_value,
|
||||||
|
extension):
|
||||||
|
|
||||||
|
new_end_period = original_stake.end_period + extension
|
||||||
|
new_duration = new_end_period - original_stake.start_period
|
||||||
|
|
||||||
|
division_message = f"""
|
||||||
|
{ursula}
|
||||||
|
~ Original Stake: {prettify_stake(stake_index=original_index, stake=original_stake)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
paint_staged_stake(ursula=ursula,
|
||||||
|
stake_value=target_value,
|
||||||
|
duration=new_duration,
|
||||||
|
start_period=original_stake.start_period,
|
||||||
|
end_period=new_end_period,
|
||||||
|
division_message=division_message)
|
||||||
|
|
|
@ -20,8 +20,13 @@ from ipaddress import ip_address
|
||||||
import click
|
import click
|
||||||
from eth_utils import is_checksum_address
|
from eth_utils import is_checksum_address
|
||||||
|
|
||||||
from nucypher.blockchain.eth.constants import MIN_ALLOWED_LOCKED, MAX_MINTING_PERIODS, MIN_LOCKED_PERIODS, \
|
from nucypher.blockchain.eth.constants import (
|
||||||
|
MIN_ALLOWED_LOCKED,
|
||||||
|
MAX_MINTING_PERIODS,
|
||||||
|
MIN_LOCKED_PERIODS,
|
||||||
MAX_ALLOWED_LOCKED
|
MAX_ALLOWED_LOCKED
|
||||||
|
)
|
||||||
|
from nucypher.blockchain.eth.token import NU
|
||||||
|
|
||||||
|
|
||||||
class ChecksumAddress(click.ParamType):
|
class ChecksumAddress(click.ParamType):
|
||||||
|
@ -45,11 +50,17 @@ class IPv4Address(click.ParamType):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# Staking
|
||||||
STAKE_DURATION = click.IntRange(min=MIN_LOCKED_PERIODS, max=MAX_MINTING_PERIODS, clamp=False)
|
STAKE_DURATION = click.IntRange(min=MIN_LOCKED_PERIODS, max=MAX_MINTING_PERIODS, clamp=False)
|
||||||
STAKE_EXTENSION = click.IntRange(min=1, max=MAX_MINTING_PERIODS, clamp=False)
|
STAKE_EXTENSION = click.IntRange(min=1, max=MAX_MINTING_PERIODS, clamp=False)
|
||||||
STAKE_VALUE = click.IntRange(min=MIN_ALLOWED_LOCKED, max=MAX_ALLOWED_LOCKED, clamp=False)
|
STAKE_VALUE = click.IntRange(min=NU(MIN_ALLOWED_LOCKED, 'NuNit').to_tokens(),
|
||||||
|
max=NU(MAX_ALLOWED_LOCKED, 'NuNit').to_tokens(), clamp=False)
|
||||||
|
|
||||||
|
# Filesystem
|
||||||
EXISTING_WRITABLE_DIRECTORY = click.Path(exists=True, dir_okay=True, file_okay=False, writable=True)
|
EXISTING_WRITABLE_DIRECTORY = click.Path(exists=True, dir_okay=True, file_okay=False, writable=True)
|
||||||
EXISTING_READABLE_FILE = click.Path(exists=True, dir_okay=False, file_okay=True, readable=True)
|
EXISTING_READABLE_FILE = click.Path(exists=True, dir_okay=False, file_okay=True, readable=True)
|
||||||
|
|
||||||
|
# Network
|
||||||
NETWORK_PORT = click.IntRange(min=0, max=65535, clamp=False)
|
NETWORK_PORT = click.IntRange(min=0, max=65535, clamp=False)
|
||||||
IPV4_ADDRESS = IPv4Address()
|
IPV4_ADDRESS = IPv4Address()
|
||||||
EIP55_CHECKSUM_ADDRESS = ChecksumAddress()
|
EIP55_CHECKSUM_ADDRESS = ChecksumAddress()
|
||||||
|
|
|
@ -382,7 +382,6 @@ class NodeConfiguration(ABC):
|
||||||
storage_type = storage_payload[NodeStorage._TYPE_LABEL]
|
storage_type = storage_payload[NodeStorage._TYPE_LABEL]
|
||||||
storage_class = node_storage_subclasses[storage_type]
|
storage_class = node_storage_subclasses[storage_type]
|
||||||
node_storage = storage_class.from_payload(payload=storage_payload,
|
node_storage = storage_class.from_payload(payload=storage_payload,
|
||||||
# character_class=cls._CHARACTER_CLASS, # TODO: Do not pass this here - Always Use Ursula
|
|
||||||
federated_only=payload['federated_only'],
|
federated_only=payload['federated_only'],
|
||||||
serializer=cls.NODE_SERIALIZER,
|
serializer=cls.NODE_SERIALIZER,
|
||||||
deserializer=cls.NODE_DESERIALIZER)
|
deserializer=cls.NODE_DESERIALIZER)
|
||||||
|
@ -552,7 +551,7 @@ class NodeConfiguration(ABC):
|
||||||
|
|
||||||
# Keyring
|
# Keyring
|
||||||
if not self.dev_mode:
|
if not self.dev_mode:
|
||||||
os.mkdir(self.keyring_dir, mode=0o700) # keyring TODO: Keyring backend entry point
|
os.mkdir(self.keyring_dir, mode=0o700) # keyring TODO: Keyring backend entry point: COS
|
||||||
self.write_keyring(password=password)
|
self.write_keyring(password=password)
|
||||||
|
|
||||||
# Registry
|
# Registry
|
||||||
|
|
|
@ -24,7 +24,9 @@ from datetime import datetime
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from string import digits, ascii_uppercase
|
from string import digits, ascii_uppercase
|
||||||
|
|
||||||
from nucypher.blockchain.eth.constants import DISPATCHER_SECRET_LENGTH, M
|
from web3 import Web3
|
||||||
|
|
||||||
|
from nucypher.blockchain.eth.constants import DISPATCHER_SECRET_LENGTH, NUNITS_PER_TOKEN
|
||||||
from nucypher.config.characters import UrsulaConfiguration
|
from nucypher.config.characters import UrsulaConfiguration
|
||||||
from nucypher.config.constants import BASE_DIR
|
from nucypher.config.constants import BASE_DIR
|
||||||
|
|
||||||
|
@ -64,7 +66,7 @@ NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK = 10
|
||||||
|
|
||||||
TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts')
|
TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts')
|
||||||
|
|
||||||
DEVELOPMENT_TOKEN_AIRDROP_AMOUNT = 1000000 * int(M)
|
DEVELOPMENT_TOKEN_AIRDROP_AMOUNT = 1000000 * int(NUNITS_PER_TOKEN)
|
||||||
|
|
||||||
DEVELOPMENT_ETH_AIRDROP_AMOUNT = 10 ** 6 * 10 ** 18 # wei -> ether
|
DEVELOPMENT_ETH_AIRDROP_AMOUNT = 10 ** 6 * 10 ** 18 # wei -> ether
|
||||||
|
|
||||||
|
@ -76,6 +78,8 @@ INSECURE_DEVELOPMENT_PASSWORD = ''.join(SystemRandom().choice(ascii_uppercase +
|
||||||
|
|
||||||
MAX_TEST_SEEDER_ENTRIES = 20
|
MAX_TEST_SEEDER_ENTRIES = 20
|
||||||
|
|
||||||
|
TESTING_ETH_AIRDROP_AMOUNT = int(Web3().fromWei(100, 'ether'))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Temporary Directories and Files
|
# Temporary Directories and Files
|
||||||
|
@ -93,8 +97,6 @@ MOCK_ALLOCATION_REGISTRY_FILEPATH = os.path.join(BASE_TEMP_DIR, f'{BASE_TEMP_PRE
|
||||||
|
|
||||||
MOCK_CUSTOM_INSTALLATION_PATH_2 = '/tmp/nucypher-tmp-test-custom-2-{}'.format(time.time())
|
MOCK_CUSTOM_INSTALLATION_PATH_2 = '/tmp/nucypher-tmp-test-custom-2-{}'.format(time.time())
|
||||||
|
|
||||||
TEMPORARY_DOMAIN = 'TEMPORARY_DOMAIN'
|
|
||||||
|
|
||||||
MOCK_REGISTRY_FILEPATH = os.path.join(BASE_TEMP_DIR, f'{BASE_TEMP_PREFIX}mock-registry-{str(datetime.now())}.json')
|
MOCK_REGISTRY_FILEPATH = os.path.join(BASE_TEMP_DIR, f'{BASE_TEMP_PREFIX}mock-registry-{str(datetime.now())}.json')
|
||||||
|
|
||||||
TEMPORARY_DOMAIN = b':TEMPORARY_DOMAIN:' # for use with `--dev` node runtimes
|
TEMPORARY_DOMAIN = b':TEMPORARY_DOMAIN:' # for use with `--dev` node runtimes
|
||||||
|
@ -116,3 +118,4 @@ MOCK_IP_ADDRESS_2 = '10.10.10.10'
|
||||||
|
|
||||||
MOCK_URSULA_DB_FILEPATH = ':memory:'
|
MOCK_URSULA_DB_FILEPATH = ':memory:'
|
||||||
|
|
||||||
|
PYEVM_GAS_LIMIT = 6500000 # TODO: move elsewhere (used to set pyevm gas limit in tests)?
|
|
@ -28,6 +28,7 @@ from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
|
||||||
from nucypher.blockchain.eth.registry import InMemoryEthereumContractRegistry, InMemoryAllocationRegistry
|
from nucypher.blockchain.eth.registry import InMemoryEthereumContractRegistry, InMemoryAllocationRegistry
|
||||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||||
from nucypher.utilities.sandbox.blockchain import TesterBlockchain
|
from nucypher.utilities.sandbox.blockchain import TesterBlockchain
|
||||||
|
from nucypher.utilities.sandbox.constants import TESTING_ETH_AIRDROP_AMOUNT
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow()
|
@pytest.mark.slow()
|
||||||
|
@ -39,7 +40,7 @@ def test_rapid_deployment():
|
||||||
registry=registry,
|
registry=registry,
|
||||||
provider_uri='tester://pyevm')
|
provider_uri='tester://pyevm')
|
||||||
blockchain = TesterBlockchain(interface=interface, airdrop=False, test_accounts=4)
|
blockchain = TesterBlockchain(interface=interface, airdrop=False, test_accounts=4)
|
||||||
blockchain.ether_airdrop(amount=1000000000)
|
blockchain.ether_airdrop(amount=TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
origin, *everyone = blockchain.interface.w3.eth.accounts
|
origin, *everyone = blockchain.interface.w3.eth.accounts
|
||||||
|
|
||||||
deployer = Deployer(blockchain=blockchain,
|
deployer = Deployer(blockchain=blockchain,
|
||||||
|
|
|
@ -19,8 +19,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import maya
|
import maya
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from nucypher.blockchain.eth import constants
|
|
||||||
from nucypher.blockchain.eth.actors import Miner
|
from nucypher.blockchain.eth.actors import Miner
|
||||||
|
from nucypher.blockchain.eth.constants import MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS
|
||||||
|
from nucypher.blockchain.eth.token import NU, Stake
|
||||||
from nucypher.utilities.sandbox.blockchain import token_airdrop
|
from nucypher.utilities.sandbox.blockchain import token_airdrop
|
||||||
from nucypher.utilities.sandbox.constants import DEVELOPMENT_TOKEN_AIRDROP_AMOUNT
|
from nucypher.utilities.sandbox.constants import DEVELOPMENT_TOKEN_AIRDROP_AMOUNT
|
||||||
|
|
||||||
|
@ -37,12 +38,11 @@ def miner(testerchain, three_agents):
|
||||||
@pytest.mark.slow()
|
@pytest.mark.slow()
|
||||||
def test_miner_locking_tokens(testerchain, three_agents, miner):
|
def test_miner_locking_tokens(testerchain, three_agents, miner):
|
||||||
token_agent, miner_agent, policy_agent = three_agents
|
token_agent, miner_agent, policy_agent = three_agents
|
||||||
# testerchain.ether_airdrop(amount=10000)
|
|
||||||
|
|
||||||
assert constants.MIN_ALLOWED_LOCKED < miner.token_balance, "Insufficient miner balance"
|
assert NU(MIN_ALLOWED_LOCKED, 'NuNit') < miner.token_balance, "Insufficient miner balance"
|
||||||
|
|
||||||
expiration = maya.now().add(days=constants.MIN_LOCKED_PERIODS)
|
expiration = maya.now().add(days=MIN_LOCKED_PERIODS)
|
||||||
miner.initialize_stake(amount=int(constants.MIN_ALLOWED_LOCKED), # Lock the minimum amount of tokens
|
miner.initialize_stake(amount=NU(MIN_ALLOWED_LOCKED, 'NuNit'), # Lock the minimum amount of tokens
|
||||||
expiration=expiration)
|
expiration=expiration)
|
||||||
|
|
||||||
# Verify that the escrow is "approved" to receive tokens
|
# Verify that the escrow is "approved" to receive tokens
|
||||||
|
@ -55,42 +55,42 @@ def test_miner_locking_tokens(testerchain, three_agents, miner):
|
||||||
locked_tokens = miner_agent.contract.functions.getLockedTokens(miner.checksum_public_address).call()
|
locked_tokens = miner_agent.contract.functions.getLockedTokens(miner.checksum_public_address).call()
|
||||||
assert 0 == locked_tokens
|
assert 0 == locked_tokens
|
||||||
|
|
||||||
# testerchain.time_travel(periods=1)
|
|
||||||
|
|
||||||
locked_tokens = miner_agent.contract.functions.getLockedTokens(miner.checksum_public_address, 1).call()
|
locked_tokens = miner_agent.contract.functions.getLockedTokens(miner.checksum_public_address, 1).call()
|
||||||
assert constants.MIN_ALLOWED_LOCKED == locked_tokens
|
assert MIN_ALLOWED_LOCKED == locked_tokens
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow()
|
@pytest.mark.slow()
|
||||||
@pytest.mark.usefixtures("three_agents")
|
@pytest.mark.usefixtures("three_agents")
|
||||||
def test_miner_divides_stake(miner):
|
def test_miner_divides_stake(miner):
|
||||||
|
stake_value = NU(MIN_ALLOWED_LOCKED*5, 'NuNit')
|
||||||
|
new_stake_value = NU(MIN_ALLOWED_LOCKED*2, 'NuNit')
|
||||||
|
|
||||||
|
stake_index = 0
|
||||||
|
miner.initialize_stake(amount=stake_value, lock_periods=int(MIN_LOCKED_PERIODS))
|
||||||
|
miner.divide_stake(target_value=new_stake_value, stake_index=stake_index+1, additional_periods=2)
|
||||||
|
|
||||||
current_period = miner.miner_agent.get_current_period()
|
current_period = miner.miner_agent.get_current_period()
|
||||||
stake_value = int(constants.MIN_ALLOWED_LOCKED) * 5
|
|
||||||
new_stake_value = int(constants.MIN_ALLOWED_LOCKED) * 2
|
|
||||||
|
|
||||||
stake_index = len(list(miner.stakes))
|
|
||||||
miner.initialize_stake(amount=stake_value, lock_periods=int(constants.MIN_LOCKED_PERIODS))
|
|
||||||
miner.divide_stake(target_value=new_stake_value, stake_index=stake_index, additional_periods=2)
|
|
||||||
|
|
||||||
stakes = list(miner.stakes)
|
|
||||||
expected_old_stake = (current_period + 1, current_period + 30, stake_value - new_stake_value)
|
expected_old_stake = (current_period + 1, current_period + 30, stake_value - new_stake_value)
|
||||||
expected_new_stake = (current_period + 1, current_period + 32, new_stake_value)
|
expected_new_stake = (current_period + 1, current_period + 32, new_stake_value)
|
||||||
|
|
||||||
assert stake_index + 2 == len(stakes), 'A new stake was not added to this miners stakes'
|
assert 3 == len(miner.stakes), 'A new stake was not added to this miners stakes'
|
||||||
assert expected_old_stake == stakes[stake_index], 'Old stake values are invalid'
|
assert expected_old_stake == miner.stakes[stake_index+1].to_stake_info(), 'Old stake values are invalid'
|
||||||
assert expected_new_stake == stakes[stake_index + 1], 'New stake values are invalid'
|
assert expected_new_stake == miner.stakes[stake_index + 2].to_stake_info(), 'New stake values are invalid'
|
||||||
|
|
||||||
yet_another_stake_value = int(constants.MIN_ALLOWED_LOCKED)
|
yet_another_stake_value = NU(MIN_ALLOWED_LOCKED, 'NuNit')
|
||||||
miner.divide_stake(target_value=yet_another_stake_value, stake_index=stake_index + 1, additional_periods=2)
|
miner.divide_stake(target_value=yet_another_stake_value, stake_index=stake_index + 2, additional_periods=2)
|
||||||
|
|
||||||
stakes = list(miner.stakes)
|
|
||||||
expected_new_stake = (current_period + 1, current_period + 32, new_stake_value - yet_another_stake_value)
|
expected_new_stake = (current_period + 1, current_period + 32, new_stake_value - yet_another_stake_value)
|
||||||
expected_yet_another_stake = (current_period + 1, current_period + 34, yet_another_stake_value)
|
expected_yet_another_stake = Stake(start_period=current_period + 1,
|
||||||
|
end_period=current_period + 34,
|
||||||
|
value=yet_another_stake_value,
|
||||||
|
owner_address=miner.checksum_public_address,
|
||||||
|
index=3)
|
||||||
|
|
||||||
assert stake_index + 3 == len(stakes), 'A new stake was not added after two stake divisions'
|
assert 4 == len(miner.stakes), 'A new stake was not added after two stake divisions'
|
||||||
assert expected_old_stake == stakes[stake_index], 'Old stake values are invalid after two stake divisions'
|
assert expected_old_stake == miner.stakes[stake_index + 1].to_stake_info(), 'Old stake values are invalid after two stake divisions'
|
||||||
assert expected_new_stake == stakes[stake_index + 1], 'New stake values are invalid after two stake divisions'
|
assert expected_new_stake == miner.stakes[stake_index + 2].to_stake_info(), 'New stake values are invalid after two stake divisions'
|
||||||
assert expected_yet_another_stake == stakes[stake_index + 2], 'Third stake values are invalid'
|
assert expected_yet_another_stake == miner.stakes[stake_index + 3], 'Third stake values are invalid'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow()
|
@pytest.mark.slow()
|
||||||
|
@ -102,17 +102,16 @@ def test_miner_collects_staking_reward(testerchain, miner, three_agents):
|
||||||
initial_balance = miner.token_balance
|
initial_balance = miner.token_balance
|
||||||
assert token_agent.get_balance(miner.checksum_public_address) == initial_balance
|
assert token_agent.get_balance(miner.checksum_public_address) == initial_balance
|
||||||
|
|
||||||
miner.initialize_stake(amount=int(constants.MIN_ALLOWED_LOCKED), # Lock the minimum amount of tokens
|
miner.initialize_stake(amount=NU(MIN_ALLOWED_LOCKED, 'NuNit'), # Lock the minimum amount of tokens
|
||||||
lock_periods=int(constants.MIN_LOCKED_PERIODS)) # ... for the fewest number of periods
|
lock_periods=int(MIN_LOCKED_PERIODS)) # ... for the fewest number of periods
|
||||||
|
|
||||||
# ...wait out the lock period...
|
# ...wait out the lock period...
|
||||||
for _ in range(28):
|
for _ in range(MIN_LOCKED_PERIODS):
|
||||||
testerchain.time_travel(periods=1)
|
testerchain.time_travel(periods=1)
|
||||||
miner.confirm_activity()
|
miner.confirm_activity()
|
||||||
|
|
||||||
# ...wait more...
|
# ...wait more...
|
||||||
testerchain.time_travel(periods=2)
|
testerchain.time_travel(periods=2)
|
||||||
miner.mint()
|
|
||||||
miner.collect_staking_reward()
|
miner.collect_staking_reward()
|
||||||
|
|
||||||
final_balance = token_agent.get_balance(miner.checksum_public_address)
|
final_balance = token_agent.get_balance(miner.checksum_public_address)
|
||||||
|
|
|
@ -18,13 +18,14 @@ import pytest
|
||||||
|
|
||||||
from nucypher.blockchain.eth import constants
|
from nucypher.blockchain.eth import constants
|
||||||
from nucypher.blockchain.eth.actors import PolicyAuthor
|
from nucypher.blockchain.eth.actors import PolicyAuthor
|
||||||
|
from nucypher.utilities.sandbox.constants import TESTING_ETH_AIRDROP_AMOUNT
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow()
|
@pytest.mark.slow()
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope='module')
|
||||||
def author(testerchain, three_agents):
|
def author(testerchain, three_agents):
|
||||||
token_agent, miner_agent, policy_agent = three_agents
|
token_agent, miner_agent, policy_agent = three_agents
|
||||||
token_agent.ether_airdrop(amount=100000 * constants.M)
|
token_agent.ether_airdrop(amount=TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
_origin, ursula, alice, *everybody_else = testerchain.interface.w3.eth.accounts
|
_origin, ursula, alice, *everybody_else = testerchain.interface.w3.eth.accounts
|
||||||
author = PolicyAuthor(checksum_address=alice)
|
author = PolicyAuthor(checksum_address=alice)
|
||||||
return author
|
return author
|
||||||
|
@ -32,7 +33,6 @@ def author(testerchain, three_agents):
|
||||||
|
|
||||||
@pytest.mark.slow()
|
@pytest.mark.slow()
|
||||||
def test_create_policy_author(testerchain, three_agents):
|
def test_create_policy_author(testerchain, three_agents):
|
||||||
token_agent, miner_agent, policy_agent = three_agents
|
|
||||||
_origin, ursula, alice, *everybody_else = testerchain.interface.w3.eth.accounts
|
_origin, ursula, alice, *everybody_else = testerchain.interface.w3.eth.accounts
|
||||||
policy_author = PolicyAuthor(checksum_address=alice)
|
policy_author = PolicyAuthor(checksum_address=alice)
|
||||||
assert policy_author.checksum_public_address == alice
|
assert policy_author.checksum_public_address == alice
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
from decimal import InvalidOperation, Decimal
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from web3 import Web3
|
||||||
|
|
||||||
|
from nucypher.blockchain.eth.constants import MIN_ALLOWED_LOCKED
|
||||||
|
from nucypher.blockchain.eth.token import NU, Stake
|
||||||
|
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
|
def test_NU():
|
||||||
|
|
||||||
|
# Starting Small
|
||||||
|
min_allowed_locked = NU(MIN_ALLOWED_LOCKED, 'NuNit')
|
||||||
|
assert MIN_ALLOWED_LOCKED == int(min_allowed_locked.to_nunits())
|
||||||
|
|
||||||
|
min_NU_locked = int(str(MIN_ALLOWED_LOCKED)[0:-18])
|
||||||
|
expected = NU(min_NU_locked, 'NU')
|
||||||
|
assert min_allowed_locked == expected
|
||||||
|
|
||||||
|
# Starting Big
|
||||||
|
min_allowed_locked = NU(min_NU_locked, 'NU')
|
||||||
|
assert MIN_ALLOWED_LOCKED == int(min_allowed_locked)
|
||||||
|
assert MIN_ALLOWED_LOCKED == int(min_allowed_locked.to_nunits())
|
||||||
|
assert str(min_allowed_locked) == '15000 NU'
|
||||||
|
|
||||||
|
# Alternate construction
|
||||||
|
assert NU(1, 'NU') == NU('1.0', 'NU') == NU(1.0, 'NU')
|
||||||
|
|
||||||
|
# Arithmetic
|
||||||
|
|
||||||
|
# NUs
|
||||||
|
one_nu = NU(1, 'NU')
|
||||||
|
zero_nu = NU(0, 'NU')
|
||||||
|
one_hundred_nu = NU(100, 'NU')
|
||||||
|
two_hundred_nu = NU(200, 'NU')
|
||||||
|
three_hundred_nu = NU(300, 'NU')
|
||||||
|
|
||||||
|
# Nits
|
||||||
|
one_nu_wei = NU(1, 'NuNit')
|
||||||
|
three_nu_wei = NU(3, 'NuNit')
|
||||||
|
assert three_nu_wei.to_tokens() == Decimal('3E-18')
|
||||||
|
assert one_nu_wei.to_tokens() == Decimal('1E-18')
|
||||||
|
|
||||||
|
# Base Operations
|
||||||
|
assert one_hundred_nu < two_hundred_nu < three_hundred_nu
|
||||||
|
assert one_hundred_nu <= two_hundred_nu <= three_hundred_nu
|
||||||
|
|
||||||
|
assert three_hundred_nu > two_hundred_nu > one_hundred_nu
|
||||||
|
assert three_hundred_nu >= two_hundred_nu >= one_hundred_nu
|
||||||
|
|
||||||
|
assert (one_hundred_nu + two_hundred_nu) == three_hundred_nu
|
||||||
|
assert (three_hundred_nu - two_hundred_nu) == one_hundred_nu
|
||||||
|
|
||||||
|
difference = one_nu - one_nu_wei
|
||||||
|
assert not difference == zero_nu
|
||||||
|
|
||||||
|
actual = float(difference.to_tokens())
|
||||||
|
expected = 0.999999999999999999
|
||||||
|
assert actual == expected
|
||||||
|
|
||||||
|
# 3.14 NU is 3_140_000_000_000_000_000 NuNit
|
||||||
|
pi_nuweis = NU(3.14, 'NU')
|
||||||
|
assert NU('3.14', 'NU') == pi_nuweis.to_nunits() == NU(3_140_000_000_000_000_000, 'NuNit')
|
||||||
|
|
||||||
|
# Mixed type operations
|
||||||
|
difference = NU('3.14159265', 'NU') - NU(1.1, 'NU')
|
||||||
|
assert difference == NU('2.04159265', 'NU')
|
||||||
|
|
||||||
|
result = difference + one_nu_wei
|
||||||
|
assert result == NU(2041592650000000001, 'NuNit')
|
||||||
|
|
||||||
|
# Similar to stake read + metadata operations in Miner
|
||||||
|
collection = [one_hundred_nu, two_hundred_nu, three_hundred_nu]
|
||||||
|
assert sum(collection) == NU('600', 'NU') == NU(600, 'NU') == NU(600.0, 'NU') == NU(600e+18, 'NuNit')
|
||||||
|
|
||||||
|
#
|
||||||
|
# Fractional Inputs
|
||||||
|
#
|
||||||
|
|
||||||
|
# A decimal amount of NuNit (i.e., a fraction of a NuNit)
|
||||||
|
pi_nuweis = NU('3.14', 'NuNit')
|
||||||
|
assert pi_nuweis == three_nu_wei # Floor
|
||||||
|
|
||||||
|
# A decimal amount of NU, which amounts to NuNit with decimals
|
||||||
|
pi_nus = NU('3.14159265358979323846', 'NU')
|
||||||
|
assert pi_nus == NU(3141592653589793238, 'NuNit') # Floor
|
||||||
|
|
||||||
|
# Positive Infinity
|
||||||
|
with pytest.raises(NU.InvalidAmount):
|
||||||
|
_inf = NU(float('infinity'), 'NU')
|
||||||
|
|
||||||
|
# Negative Infinity
|
||||||
|
with pytest.raises(NU.InvalidAmount):
|
||||||
|
_neg_inf = NU(float('-infinity'), 'NU')
|
||||||
|
|
||||||
|
# Not a Number
|
||||||
|
with pytest.raises(InvalidOperation):
|
||||||
|
_nan = NU(float('NaN'), 'NU')
|
||||||
|
|
||||||
|
|
||||||
|
def test_stake():
|
||||||
|
|
||||||
|
class FakeUrsula:
|
||||||
|
burner_wallet = Web3().eth.account.create(INSECURE_DEVELOPMENT_PASSWORD)
|
||||||
|
checksum_public_address = burner_wallet.address
|
||||||
|
miner_agent = None
|
||||||
|
|
||||||
|
ursula = FakeUrsula()
|
||||||
|
stake = Stake(owner_address=ursula.checksum_public_address,
|
||||||
|
start_period=1,
|
||||||
|
end_period=100,
|
||||||
|
value=NU(100, 'NU'),
|
||||||
|
index=0)
|
||||||
|
|
||||||
|
assert len(stake.id) == 16
|
||||||
|
assert stake.value, 'NU' == NU(100, 'NU')
|
||||||
|
|
||||||
|
assert isinstance(stake.time_remaining(), int) # seconds
|
||||||
|
slang_remaining = stake.time_remaining(slang=True) # words
|
||||||
|
assert isinstance(slang_remaining, str)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stake_integration(blockchain_ursulas):
|
||||||
|
staking_ursula = list(blockchain_ursulas)[1]
|
||||||
|
stakes = staking_ursula.stakes
|
||||||
|
assert stakes
|
||||||
|
|
||||||
|
stake = stakes[0]
|
||||||
|
blockchain_stakes = staking_ursula.miner_agent.get_all_stakes(miner_address=staking_ursula.checksum_public_address)
|
||||||
|
|
||||||
|
stake_info = (stake.start_period, stake.end_period, int(stake.value))
|
||||||
|
published_stake_info = list(blockchain_stakes)[0]
|
||||||
|
assert stake_info == published_stake_info == stake.to_stake_info()
|
|
@ -33,7 +33,7 @@ from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||||
from nucypher.config.characters import UrsulaConfiguration
|
from nucypher.config.characters import UrsulaConfiguration
|
||||||
from nucypher.utilities.sandbox.blockchain import TesterBlockchain
|
from nucypher.utilities.sandbox.blockchain import TesterBlockchain
|
||||||
from nucypher.utilities.sandbox.constants import MOCK_CUSTOM_INSTALLATION_PATH, TEST_PROVIDER_URI, \
|
from nucypher.utilities.sandbox.constants import MOCK_CUSTOM_INSTALLATION_PATH, TEST_PROVIDER_URI, \
|
||||||
MOCK_ALLOCATION_INFILE, MOCK_REGISTRY_FILEPATH
|
MOCK_ALLOCATION_INFILE, MOCK_REGISTRY_FILEPATH, TESTING_ETH_AIRDROP_AMOUNT
|
||||||
from nucypher.utilities.sandbox.constants import MOCK_CUSTOM_INSTALLATION_PATH_2
|
from nucypher.utilities.sandbox.constants import MOCK_CUSTOM_INSTALLATION_PATH_2
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ def deployed_blockchain():
|
||||||
# Blockchain
|
# Blockchain
|
||||||
#
|
#
|
||||||
blockchain = TesterBlockchain(interface=interface, airdrop=False, test_accounts=5, poa=True)
|
blockchain = TesterBlockchain(interface=interface, airdrop=False, test_accounts=5, poa=True)
|
||||||
blockchain.ether_airdrop(amount=1000000000)
|
blockchain.ether_airdrop(amount=TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
origin, *everyone = blockchain.interface.w3.eth.accounts
|
origin, *everyone = blockchain.interface.w3.eth.accounts
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -12,6 +12,7 @@ from umbral.keys import UmbralPublicKey
|
||||||
from nucypher.blockchain.eth.actors import Miner
|
from nucypher.blockchain.eth.actors import Miner
|
||||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent
|
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent
|
||||||
from nucypher.blockchain.eth.constants import MIN_LOCKED_PERIODS, MIN_ALLOWED_LOCKED
|
from nucypher.blockchain.eth.constants import MIN_LOCKED_PERIODS, MIN_ALLOWED_LOCKED
|
||||||
|
from nucypher.blockchain.eth.token import NU
|
||||||
from nucypher.characters.lawful import Enrico
|
from nucypher.characters.lawful import Enrico
|
||||||
from nucypher.cli.main import nucypher_cli
|
from nucypher.cli.main import nucypher_cli
|
||||||
from nucypher.config.characters import UrsulaConfiguration, BobConfiguration
|
from nucypher.config.characters import UrsulaConfiguration, BobConfiguration
|
||||||
|
@ -22,15 +23,14 @@ from nucypher.utilities.sandbox.constants import (
|
||||||
TEST_PROVIDER_URI,
|
TEST_PROVIDER_URI,
|
||||||
MOCK_URSULA_STARTING_PORT,
|
MOCK_URSULA_STARTING_PORT,
|
||||||
INSECURE_DEVELOPMENT_PASSWORD,
|
INSECURE_DEVELOPMENT_PASSWORD,
|
||||||
MOCK_REGISTRY_FILEPATH, TEMPORARY_DOMAIN)
|
MOCK_REGISTRY_FILEPATH, TEMPORARY_DOMAIN, TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
|
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
|
||||||
from nucypher.utilities.sandbox.ursula import start_pytest_ursula_services
|
from nucypher.utilities.sandbox.ursula import start_pytest_ursula_services
|
||||||
from web3 import Web3
|
|
||||||
|
|
||||||
from web3 import Web3
|
from web3 import Web3
|
||||||
|
|
||||||
|
|
||||||
STAKE_VALUE = Web3.fromWei(MIN_ALLOWED_LOCKED * 2, 'ether')
|
STAKE_VALUE = NU(MIN_ALLOWED_LOCKED * 2, 'NuNit')
|
||||||
POLICY_RATE = Web3.toWei(21, 'gwei')
|
POLICY_RATE = Web3.toWei(21, 'gwei')
|
||||||
POLICY_VALUE = POLICY_RATE * MIN_LOCKED_PERIODS # * len(ursula)
|
POLICY_VALUE = POLICY_RATE * MIN_LOCKED_PERIODS # * len(ursula)
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ def funded_blockchain(deployed_blockchain):
|
||||||
deployer_address, *everyone_else, staking_participant = blockchain.interface.w3.eth.accounts
|
deployer_address, *everyone_else, staking_participant = blockchain.interface.w3.eth.accounts
|
||||||
|
|
||||||
# Free ETH!!!
|
# Free ETH!!!
|
||||||
blockchain.ether_airdrop(amount=1000000)
|
blockchain.ether_airdrop(amount=TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
|
|
||||||
# Free Tokens!!!
|
# Free Tokens!!!
|
||||||
token_airdrop(token_agent=NucypherTokenAgent(blockchain=blockchain),
|
token_airdrop(token_agent=NucypherTokenAgent(blockchain=blockchain),
|
||||||
|
@ -148,7 +148,7 @@ def test_initialize_system_blockchain_configuration(click_runner,
|
||||||
def test_init_ursula_stake(click_runner, configuration_file_location, funded_blockchain):
|
def test_init_ursula_stake(click_runner, configuration_file_location, funded_blockchain):
|
||||||
stake_args = ('ursula', 'stake',
|
stake_args = ('ursula', 'stake',
|
||||||
'--config-file', configuration_file_location,
|
'--config-file', configuration_file_location,
|
||||||
'--value', STAKE_VALUE,
|
'--value', STAKE_VALUE.to_tokens(),
|
||||||
'--duration', MIN_LOCKED_PERIODS,
|
'--duration', MIN_LOCKED_PERIODS,
|
||||||
'--poa',
|
'--poa',
|
||||||
'--force')
|
'--force')
|
||||||
|
@ -164,7 +164,7 @@ def test_init_ursula_stake(click_runner, configuration_file_location, funded_blo
|
||||||
stakes = list(miner_agent.get_all_stakes(miner_address=config_data['checksum_public_address']))
|
stakes = list(miner_agent.get_all_stakes(miner_address=config_data['checksum_public_address']))
|
||||||
assert len(stakes) == 1
|
assert len(stakes) == 1
|
||||||
start_period, end_period, value = stakes[0]
|
start_period, end_period, value = stakes[0]
|
||||||
assert Web3.fromWei(value, 'ether') == int(STAKE_VALUE)
|
assert NU(int(value), 'NuNit') == STAKE_VALUE
|
||||||
|
|
||||||
|
|
||||||
def test_list_ursula_stakes(click_runner, funded_blockchain, configuration_file_location):
|
def test_list_ursula_stakes(click_runner, funded_blockchain, configuration_file_location):
|
||||||
|
@ -183,12 +183,13 @@ def test_list_ursula_stakes(click_runner, funded_blockchain, configuration_file_
|
||||||
|
|
||||||
def test_ursula_divide_stakes(click_runner, configuration_file_location):
|
def test_ursula_divide_stakes(click_runner, configuration_file_location):
|
||||||
|
|
||||||
divide_args = ('ursula', 'divide-stake',
|
divide_args = ('ursula', 'stake',
|
||||||
|
'--divide',
|
||||||
'--config-file', configuration_file_location,
|
'--config-file', configuration_file_location,
|
||||||
'--poa',
|
'--poa',
|
||||||
'--force',
|
'--force',
|
||||||
'--index', 0,
|
'--index', 0,
|
||||||
'--value', MIN_ALLOWED_LOCKED,
|
'--value', NU(MIN_ALLOWED_LOCKED, 'NuNit').to_tokens(),
|
||||||
'--duration', 10)
|
'--duration', 10)
|
||||||
|
|
||||||
result = click_runner.invoke(nucypher_cli,
|
result = click_runner.invoke(nucypher_cli,
|
||||||
|
@ -205,7 +206,7 @@ def test_ursula_divide_stakes(click_runner, configuration_file_location):
|
||||||
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
|
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
|
||||||
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
|
result = click_runner.invoke(nucypher_cli, stake_args, input=user_input, catch_exceptions=False)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert str(Web3.fromWei(MIN_ALLOWED_LOCKED, 'ether')) in result.output
|
assert str(NU(MIN_ALLOWED_LOCKED, 'NuNit').to_tokens()) in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_run_blockchain_ursula(click_runner,
|
def test_run_blockchain_ursula(click_runner,
|
||||||
|
|
|
@ -37,7 +37,7 @@ from nucypher.keystore import keystore
|
||||||
from nucypher.keystore.db import Base
|
from nucypher.keystore.db import Base
|
||||||
from nucypher.utilities.sandbox.blockchain import token_airdrop, TesterBlockchain
|
from nucypher.utilities.sandbox.blockchain import token_airdrop, TesterBlockchain
|
||||||
from nucypher.utilities.sandbox.constants import (MOCK_URSULA_STARTING_PORT,
|
from nucypher.utilities.sandbox.constants import (MOCK_URSULA_STARTING_PORT,
|
||||||
MOCK_POLICY_DEFAULT_M)
|
MOCK_POLICY_DEFAULT_M, TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
from nucypher.utilities.sandbox.constants import (NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK,
|
from nucypher.utilities.sandbox.constants import (NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK,
|
||||||
DEVELOPMENT_TOKEN_AIRDROP_AMOUNT)
|
DEVELOPMENT_TOKEN_AIRDROP_AMOUNT)
|
||||||
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
|
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
|
||||||
|
@ -347,7 +347,7 @@ def testerchain(solidity_compiler):
|
||||||
|
|
||||||
origin, *everyone = testerchain.interface.w3.eth.accounts
|
origin, *everyone = testerchain.interface.w3.eth.accounts
|
||||||
deployer_interface.deployer_address = origin # Set the deployer address from a freshly created test account
|
deployer_interface.deployer_address = origin # Set the deployer address from a freshly created test account
|
||||||
testerchain.ether_airdrop(amount=1000000000) # TODO: Use test constant
|
testerchain.ether_airdrop(amount=TESTING_ETH_AIRDROP_AMOUNT)
|
||||||
|
|
||||||
yield testerchain
|
yield testerchain
|
||||||
testerchain.sever_connection()
|
testerchain.sever_connection()
|
||||||
|
|
Loading…
Reference in New Issue