mirror of https://github.com/nucypher/nucypher.git
Remove code/tests that still reference the legacy notion of periods.
parent
e1e84c80ba
commit
889cc58a88
|
@ -1,20 +1,18 @@
|
|||
|
||||
|
||||
import random
|
||||
from _pydecimal import Decimal
|
||||
from typing import Callable, Dict, Union
|
||||
|
||||
import maya
|
||||
from constant_sorrow.constants import (
|
||||
NOT_STAKING,
|
||||
UNTRACKED_PENDING_TRANSACTION
|
||||
)
|
||||
from _pydecimal import Decimal
|
||||
from constant_sorrow.constants import NOT_STAKING, UNTRACKED_PENDING_TRANSACTION
|
||||
from eth_utils import currency
|
||||
from hexbytes.main import HexBytes
|
||||
from twisted.internet import reactor, task
|
||||
from web3.exceptions import TransactionNotFound
|
||||
|
||||
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS, NULL_ADDRESS
|
||||
from nucypher.blockchain.eth.constants import (
|
||||
AVERAGE_BLOCK_TIME_IN_SECONDS,
|
||||
NULL_ADDRESS,
|
||||
)
|
||||
from nucypher.types import ERC20UNits, NuNits, TuNits
|
||||
from nucypher.utilities.gas_strategies import EXPECTED_CONFIRMATION_TIME_IN_SECONDS
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
@ -178,7 +176,6 @@ class WorkTrackerBase:
|
|||
self.__pending = dict() # TODO: Prime with pending worker transactions
|
||||
self.__requirement = None
|
||||
self.__start_time = NOT_STAKING
|
||||
self.__uptime_period = NOT_STAKING
|
||||
self._abort_on_error = False
|
||||
|
||||
self._consecutive_fails = 0
|
||||
|
|
|
@ -1,77 +1,12 @@
|
|||
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Union
|
||||
|
||||
import maya
|
||||
from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
|
||||
from eth_typing import BlockNumber
|
||||
from eth_utils import is_address, is_hex, to_checksum_address
|
||||
from web3 import Web3
|
||||
from web3.contract import ContractConstructor, ContractFunction
|
||||
|
||||
from nucypher.blockchain.eth.clients import PUBLIC_CHAINS
|
||||
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS
|
||||
|
||||
|
||||
def epoch_to_period(epoch: int, seconds_per_period: int) -> int:
|
||||
period = epoch // seconds_per_period
|
||||
return period
|
||||
|
||||
|
||||
def datetime_to_period(datetime: maya.MayaDT, seconds_per_period: int) -> int:
|
||||
"""Converts a MayaDT instance to a period number."""
|
||||
future_period = epoch_to_period(epoch=datetime.epoch, seconds_per_period=seconds_per_period)
|
||||
return int(future_period)
|
||||
|
||||
|
||||
def period_to_epoch(period: int, seconds_per_period: int) -> int:
|
||||
epoch = period * seconds_per_period
|
||||
return epoch
|
||||
|
||||
|
||||
def get_current_period(seconds_per_period: int) -> int:
|
||||
now = maya.now().epoch
|
||||
period = epoch_to_period(epoch=now, seconds_per_period=seconds_per_period)
|
||||
return period
|
||||
|
||||
|
||||
def datetime_at_period(period: int, seconds_per_period: int, start_of_period: bool = False) -> maya.MayaDT:
|
||||
"""
|
||||
Returns the datetime object at a given period, future, or past.
|
||||
If start_of_period, the datetime object represents the first second of said period.
|
||||
"""
|
||||
if start_of_period:
|
||||
datetime_at_start_of_period = maya.MayaDT(epoch=period_to_epoch(period, seconds_per_period))
|
||||
return datetime_at_start_of_period
|
||||
else:
|
||||
now = maya.now()
|
||||
current_period = datetime_to_period(datetime=now, seconds_per_period=seconds_per_period)
|
||||
delta_periods = period - current_period
|
||||
target_datetime = now + maya.timedelta(seconds=seconds_per_period) * delta_periods
|
||||
return target_datetime
|
||||
|
||||
|
||||
def calculate_period_duration(future_time: maya.MayaDT, seconds_per_period: int, now: maya.MayaDT = None) -> int:
|
||||
"""Takes a future MayaDT instance and calculates the duration from now, returning in periods"""
|
||||
if now is None:
|
||||
now = maya.now()
|
||||
future_period = datetime_to_period(datetime=future_time, seconds_per_period=seconds_per_period)
|
||||
current_period = datetime_to_period(datetime=now, seconds_per_period=seconds_per_period)
|
||||
periods = future_period - current_period
|
||||
return periods
|
||||
|
||||
|
||||
def estimate_block_number_for_period(period: int, seconds_per_period: int, latest_block: BlockNumber) -> BlockNumber:
|
||||
"""Logic for getting the approximate block height of the start of the specified period."""
|
||||
period_start = datetime_at_period(period=period,
|
||||
seconds_per_period=seconds_per_period,
|
||||
start_of_period=True)
|
||||
seconds_from_midnight = int((maya.now() - period_start).total_seconds())
|
||||
blocks_from_midnight = seconds_from_midnight // AVERAGE_BLOCK_TIME_IN_SECONDS
|
||||
|
||||
block_number_for_period = latest_block - blocks_from_midnight
|
||||
return block_number_for_period
|
||||
|
||||
|
||||
def etherscan_url(item, network: str, is_token=False) -> str:
|
||||
|
|
|
@ -24,8 +24,6 @@ PRODUCTION_REGISTRY_ADVISORY = "Using latest published registry from {source}"
|
|||
|
||||
LOCAL_REGISTRY_ADVISORY = "Configured to registry filepath {registry_filepath}"
|
||||
|
||||
PERIOD_ADVANCED_WARNING = "Current period advanced before the action could be completed. Please try again."
|
||||
|
||||
#
|
||||
# Events
|
||||
#
|
||||
|
@ -33,49 +31,6 @@ PERIOD_ADVANCED_WARNING = "Current period advanced before the action could be co
|
|||
CONFIRM_OVERWRITE_EVENTS_CSV_FILE = "Overwrite existing CSV events file - {csv_file}?"
|
||||
|
||||
|
||||
#
|
||||
# Bonding
|
||||
#
|
||||
|
||||
PROMPT_OPERATOR_ADDRESS = "Enter operator address"
|
||||
|
||||
CONFIRM_PROVIDER_AND_OPERATOR_ADDRESSES_ARE_EQUAL = """
|
||||
|
||||
{address}
|
||||
The operator address provided is the same as the staking provider.
|
||||
Continue using the same account for operator and staking provider?"""
|
||||
|
||||
SUCCESSFUL_OPERATOR_BONDING = "\nOperator {operator_address} successfully bonded to staking provider {staking_provider_address}"
|
||||
|
||||
BONDING_DETAILS = "Bonded at {bonded_date}"
|
||||
|
||||
BONDING_RELEASE_INFO = "This operator can be replaced or detached after {release_date}"
|
||||
|
||||
SUCCESSFUL_UNBOND_OPERATOR = "Successfully unbonded operator {operator_address} from staking provider {staking_provider_address}"
|
||||
|
||||
DETACH_DETAILS = "Unbonded at {bonded_date}"
|
||||
|
||||
|
||||
#
|
||||
# Rewards
|
||||
#
|
||||
|
||||
COLLECTING_TOKEN_REWARD = 'Collecting {reward_amount} from staking rewards...'
|
||||
|
||||
COLLECTING_ETH_FEE = 'Collecting {fee_amount} ETH from policy fees...'
|
||||
|
||||
NO_TOKENS_TO_WITHDRAW = "No tokens can be withdrawn."
|
||||
|
||||
NO_FEE_TO_WITHDRAW = "No policy fee can be withdrawn."
|
||||
|
||||
TOKEN_REWARD_CURRENT = 'Available staking rewards: {reward_amount}.'
|
||||
|
||||
TOKEN_REWARD_PAST_HEADER = 'Staking rewards in the last {periods} periods ({days} days):'
|
||||
|
||||
TOKEN_REWARD_PAST = 'Total staking rewards: {reward_amount}.'
|
||||
|
||||
TOKEN_REWARD_NOT_FOUND = "No staking rewards found."
|
||||
|
||||
#
|
||||
# Configuration
|
||||
#
|
||||
|
|
|
@ -51,10 +51,6 @@ option_staking_provider = click.option('--staking-provider', help="Staking provi
|
|||
option_teacher_uri = click.option('--teacher', 'teacher_uri', help="An Ursula URI to start learning from (seednode)", type=click.STRING)
|
||||
_option_middleware = click.option('-Z', '--mock-networking', help="Use in-memory transport instead of networking", count=True)
|
||||
|
||||
# Avoid circular input
|
||||
option_rate = click.option('--rate', help="Policy rate per period (in wei)", type=WEI) # TODO: Is wei a sane unit here? Perhaps gwei?
|
||||
|
||||
|
||||
#
|
||||
# Alphabetical
|
||||
#
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
import time
|
||||
from collections import defaultdict, deque
|
||||
from contextlib import suppress
|
||||
|
@ -877,9 +875,9 @@ class Learner:
|
|||
f'{sprout} {NOT_SIGNED}.')
|
||||
|
||||
except sprout.NotStaking:
|
||||
self.log.warn(f'Verification Failed - '
|
||||
f'{sprout} has no active stakes in the current period '
|
||||
f'({self.staking_agent.get_current_period()}')
|
||||
self.log.warn(
|
||||
f"Verification Failed - " f"{sprout} has no active stakes "
|
||||
)
|
||||
|
||||
except sprout.InvalidOperatorSignature:
|
||||
self.log.warn(f'Verification Failed - '
|
||||
|
|
|
@ -84,8 +84,10 @@ def _resolve_any_context_variables(
|
|||
|
||||
def _validate_chain(chain: int) -> None:
|
||||
if not isinstance(chain, int):
|
||||
raise ValueError(f'"The chain" field of c a condition must be the '
|
||||
f'integer of a chain ID (got "{chain}").')
|
||||
raise ValueError(
|
||||
f'The "chain" field of a condition must be the '
|
||||
f'integer chain ID (got "{chain}").'
|
||||
)
|
||||
if chain not in _CONDITION_CHAINS:
|
||||
raise InvalidCondition(
|
||||
f"chain ID {chain} is not a permitted "
|
||||
|
|
|
@ -20,8 +20,8 @@ class PaymentMethod(ABC):
|
|||
rate: int
|
||||
value: int
|
||||
commencement: int # epoch
|
||||
expiration: int # epoch
|
||||
duration: int # seconds or periods
|
||||
expiration: int # epoch
|
||||
duration: int # seconds
|
||||
shares: int
|
||||
|
||||
@abstractmethod
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
|
||||
|
||||
|
||||
from typing import TypeVar, NewType, NamedTuple, Union
|
||||
from typing import NamedTuple, NewType, TypeVar, Union
|
||||
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
from web3.types import Wei, TxReceipt
|
||||
from web3.types import TxReceipt, Wei
|
||||
|
||||
ERC20UNits = NewType("ERC20UNits", int)
|
||||
NuNits = NewType("NuNits", ERC20UNits)
|
||||
TuNits = NewType("TuNits", ERC20UNits)
|
||||
|
||||
Work = NewType("Work", int)
|
||||
Agent = TypeVar('Agent', bound='EthereumContractAgent')
|
||||
Period = NewType('Period', int)
|
||||
PeriodDelta = NewType('PeriodDelta', int)
|
||||
ContractReturnValue = TypeVar('ContractReturnValue', bound=Union[TxReceipt, Wei, int, str, bool])
|
||||
|
||||
|
||||
|
@ -37,9 +31,3 @@ class PolicyInfo(NamedTuple):
|
|||
# reserved_slot_3
|
||||
# reserved_slot_4
|
||||
# reserved_slot_5
|
||||
|
||||
|
||||
class ArrangementInfo(NamedTuple):
|
||||
node: ChecksumAddress
|
||||
downtime_index: int
|
||||
last_refunded_period: int
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.blockchain.eth.actors import ContractAdministrator
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from tests.constants import NUMBER_OF_ALLOCATIONS_IN_TESTS
|
||||
|
||||
# Prevents TesterBlockchain to be picked up by py.test as a test class
|
||||
from tests.utils.blockchain import TesterBlockchain as _TesterBlockchain
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
@pytest.mark.usefixtures('testerchain')
|
||||
def test_rapid_deployment(application_economics, test_registry, temp_dir_path, get_random_checksum_address):
|
||||
|
||||
blockchain = _TesterBlockchain(eth_airdrop=False, test_accounts=4)
|
||||
|
||||
deployer_address = blockchain.etherbase_account
|
||||
deployer_power = TransactingPower(signer=Web3Signer(blockchain.client), account=deployer_address)
|
||||
|
||||
administrator = ContractAdministrator(transacting_power=deployer_power,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
registry=test_registry)
|
||||
blockchain.bootstrap_network(registry=test_registry)
|
||||
|
||||
all_yall = blockchain.unassigned_accounts
|
||||
|
||||
# Start with some hard-coded cases...
|
||||
allocation_data = [{'checksum_address': all_yall[1],
|
||||
'amount': application_economics.maximum_allowed_locked,
|
||||
'lock_periods': application_economics.min_operator_seconds},
|
||||
|
||||
{'checksum_address': all_yall[2],
|
||||
'amount': application_economics.min_authorization,
|
||||
'lock_periods': application_economics.min_operator_seconds},
|
||||
|
||||
{'checksum_address': all_yall[3],
|
||||
'amount': application_economics.min_authorization * 100,
|
||||
'lock_periods': application_economics.min_operator_seconds},
|
||||
]
|
||||
|
||||
# Pile on the rest
|
||||
for _ in range(NUMBER_OF_ALLOCATIONS_IN_TESTS - len(allocation_data)):
|
||||
checksum_address = get_random_checksum_address()
|
||||
amount = random.randint(application_economics.min_authorization, application_economics.maximum_allowed_locked)
|
||||
duration = random.randint(application_economics.min_operator_seconds, application_economics.maximum_rewarded_periods)
|
||||
random_allocation = {'checksum_address': checksum_address, 'amount': amount, 'lock_periods': duration}
|
||||
allocation_data.append(random_allocation)
|
||||
|
||||
filepath = temp_dir_path / "allocations.json"
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(allocation_data, f)
|
||||
|
||||
minimum, default, maximum = 10, 20, 30
|
||||
administrator.set_fee_rate_range(minimum, default, maximum)
|
|
@ -1,94 +0,0 @@
|
|||
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher_core.umbral import SecretKeyFactory, Signer
|
||||
|
||||
from nucypher.blockchain.eth.actors import Investigator
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
|
||||
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
|
||||
|
||||
def mock_ursula(testerchain, account, mocker):
|
||||
ursula_privkey = SecretKeyFactory.random()
|
||||
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.public_key(),
|
||||
signer=Signer(ursula_privkey))
|
||||
|
||||
signed_stamp = testerchain.client.sign_message(account=account,
|
||||
message=bytes(ursula_stamp))
|
||||
|
||||
ursula = mocker.Mock(stamp=ursula_stamp, operator_signature=signed_stamp)
|
||||
return ursula
|
||||
|
||||
|
||||
@pytest.mark.skip("David, send help!")
|
||||
def test_investigator_requests_slashing(testerchain,
|
||||
test_registry,
|
||||
agency,
|
||||
#mock_ursula_reencrypts,
|
||||
application_economics,
|
||||
mocker):
|
||||
|
||||
staker_account = testerchain.stake_provider_account(0)
|
||||
worker_account = testerchain.ursula_account(0)
|
||||
|
||||
##### STAKING ESCROW STUFF #####
|
||||
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
|
||||
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
|
||||
|
||||
locked_tokens = application_economics.min_authorization * 5
|
||||
|
||||
# The staker receives an initial amount of tokens
|
||||
tpower = TransactingPower(account=testerchain.etherbase_account, signer=Web3Signer(testerchain.client))
|
||||
_txhash = token_agent.transfer(amount=locked_tokens,
|
||||
target_address=staker_account,
|
||||
transacting_power=tpower)
|
||||
|
||||
# Deposit: The staker deposits tokens in the StakingEscrow contract.
|
||||
staker_tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
|
||||
staker = Staker(transacting_power=staker_tpower,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
registry=test_registry)
|
||||
|
||||
staker.initialize_stake(amount=NU(locked_tokens, 'NuNit'),
|
||||
lock_periods=application_economics.min_operator_seconds)
|
||||
assert staker.locked_tokens(periods=1) == locked_tokens
|
||||
|
||||
# The staker hasn't bond a worker yet
|
||||
assert NULL_ADDRESS == staking_agent.get_worker_from_staker(staker_address=staker_account)
|
||||
|
||||
_txhash = staking_agent.bond_worker(transacting_power=staker_tpower, worker_address=worker_account)
|
||||
|
||||
assert worker_account == staking_agent.get_worker_from_staker(staker_address=staker_account)
|
||||
assert staker_account == staking_agent.get_staker_from_worker(worker_address=worker_account)
|
||||
|
||||
###### END OF STAKING ESCROW STUFF ####
|
||||
|
||||
bob_account = testerchain.bob_account
|
||||
bob_tpower = TransactingPower(account=bob_account, signer=Web3Signer(testerchain.client))
|
||||
investigator = Investigator(registry=test_registry,
|
||||
transacting_power=bob_tpower,
|
||||
domain=TEMPORARY_DOMAIN)
|
||||
ursula = mock_ursula(testerchain, worker_account, mocker=mocker)
|
||||
|
||||
# Let's create a bad cfrag
|
||||
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
|
||||
|
||||
assert not investigator.was_this_evidence_evaluated(evidence)
|
||||
bobby_old_balance = investigator.token_balance
|
||||
|
||||
investigator.request_evaluation(evidence=evidence)
|
||||
|
||||
assert investigator.was_this_evidence_evaluated(evidence)
|
||||
investigator_reward = investigator.token_balance - bobby_old_balance
|
||||
|
||||
assert investigator_reward > 0
|
||||
assert investigator_reward == application_economics.base_penalty / application_economics.reward_coefficient
|
||||
assert staker.locked_tokens(periods=1) < locked_tokens
|
|
@ -1,19 +1,10 @@
|
|||
|
||||
|
||||
|
||||
import pytest
|
||||
import pytest_twisted
|
||||
from twisted.internet import threads
|
||||
from twisted.internet.task import Clock
|
||||
from web3.middleware.simulate_unmined_transaction import (
|
||||
unmined_receipt_simulator_middleware,
|
||||
)
|
||||
|
||||
from nucypher.blockchain.eth.actors import Operator
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
|
||||
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.utilities.logging import Logger
|
||||
from tests.utils.ursula import make_ursulas, start_pytest_ursula_services
|
||||
|
@ -26,169 +17,6 @@ def log(message):
|
|||
print(message)
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
@pytest_twisted.inlineCallbacks
|
||||
def test_work_tracker(
|
||||
mocker,
|
||||
testerchain,
|
||||
test_registry,
|
||||
staker,
|
||||
agency,
|
||||
application_economics,
|
||||
ursula_test_config,
|
||||
):
|
||||
|
||||
staker.initialize_stake(
|
||||
amount=NU(application_economics.min_authorization, "NuNit"),
|
||||
lock_periods=int(application_economics.min_operator_seconds),
|
||||
)
|
||||
|
||||
# Get an unused address and create a new worker
|
||||
worker_address = testerchain.unassigned_accounts[-1]
|
||||
|
||||
# Control time
|
||||
clock = Clock()
|
||||
ClassicPREWorkTracker.CLOCK = clock
|
||||
|
||||
# Bond the Worker and Staker
|
||||
staker.bond_worker(worker_address=worker_address)
|
||||
|
||||
commit_spy = mocker.spy(Worker, "commit_to_next_period")
|
||||
replacement_spy = mocker.spy(
|
||||
ClassicPREWorkTracker, "_ClassicPREWorkTracker__fire_replacement_commitment"
|
||||
)
|
||||
|
||||
# Make the Worker
|
||||
ursula = make_ursulas(
|
||||
ursula_config=ursula_test_config,
|
||||
staking_provider_addresses=[staker.checksum_address],
|
||||
operator_addresses=[worker_address],
|
||||
registry=test_registry,
|
||||
).pop()
|
||||
|
||||
ursula.run(
|
||||
preflight=False,
|
||||
discovery=False,
|
||||
start_reactor=False,
|
||||
worker=True,
|
||||
eager=True,
|
||||
block_until_ready=False,
|
||||
) # "start" services
|
||||
|
||||
initial_period = staker.staking_agent.get_current_period()
|
||||
|
||||
def start():
|
||||
log("Starting Worker for auto-commitment simulation")
|
||||
start_pytest_ursula_services(ursula=ursula)
|
||||
|
||||
def advance_one_period(_):
|
||||
log("Advancing one period")
|
||||
testerchain.time_travel(periods=1)
|
||||
clock.advance(ClassicPREWorkTracker.INTERVAL_CEIL + 1)
|
||||
|
||||
def check_pending_commitments(number_of_commitments):
|
||||
def _check_pending_commitments(_):
|
||||
log(f"Checking we have {number_of_commitments} pending commitments")
|
||||
assert number_of_commitments == len(ursula.work_tracker.pending)
|
||||
|
||||
return _check_pending_commitments
|
||||
|
||||
def pending_commitments(_):
|
||||
log("Starting unmined transaction simulation")
|
||||
testerchain.client.add_middleware(unmined_receipt_simulator_middleware)
|
||||
|
||||
def advance_one_cycle(_):
|
||||
log("Advancing one tracking iteration")
|
||||
clock.advance(ursula.work_tracker._tracking_task.interval + 1)
|
||||
|
||||
def advance_until_replacement_indicated(_):
|
||||
last_committed_period = staker.staking_agent.get_last_committed_period(
|
||||
staker_address=staker.checksum_address
|
||||
)
|
||||
log("Advancing until replacement is indicated")
|
||||
testerchain.time_travel(periods=1)
|
||||
clock.advance(ClassicPREWorkTracker.INTERVAL_CEIL + 1)
|
||||
mocker.patch.object(
|
||||
ClassicPREWorkTracker, "max_confirmation_time", return_value=1.0
|
||||
)
|
||||
mock_last_committed_period = mocker.PropertyMock(
|
||||
return_value=last_committed_period
|
||||
)
|
||||
mocker.patch.object(
|
||||
Worker, "last_committed_period", new_callable=mock_last_committed_period
|
||||
)
|
||||
clock.advance(ursula.work_tracker.max_confirmation_time() + 1)
|
||||
|
||||
def verify_unmined_commitment(_):
|
||||
log("Verifying worker has unmined commitment transaction")
|
||||
|
||||
# FIXME: The test doesn't model accurately an unmined TX, but an unconfirmed receipt,
|
||||
# so the tracker does not have pending TXs. If we want to model pending TXs we need to actually
|
||||
# prevent them from being mined.
|
||||
#
|
||||
# assert len(ursula.work_tracker.pending) == 1
|
||||
current_period = staker.staking_agent.get_current_period()
|
||||
assert commit_spy.call_count == current_period - initial_period + 1
|
||||
|
||||
def verify_replacement_commitment(_):
|
||||
log("Verifying worker has replaced commitment transaction")
|
||||
assert replacement_spy.call_count > 0
|
||||
|
||||
def verify_confirmed(_):
|
||||
# Verify that periods were committed on-chain automatically
|
||||
last_committed_period = staker.staking_agent.get_last_committed_period(
|
||||
staker_address=staker.checksum_address
|
||||
)
|
||||
current_period = staker.staking_agent.get_current_period()
|
||||
|
||||
expected_commitments = current_period - initial_period + 1
|
||||
log(f"Verifying worker made {expected_commitments} commitments so far")
|
||||
assert (last_committed_period - current_period) == 1
|
||||
assert commit_spy.call_count == expected_commitments
|
||||
assert replacement_spy.call_count == 0
|
||||
|
||||
# Behavioural Test, like a screenplay made of legos
|
||||
|
||||
# Ursula commits on startup
|
||||
d = threads.deferToThread(start)
|
||||
d.addCallback(verify_confirmed)
|
||||
d.addCallback(advance_one_period)
|
||||
|
||||
d.addCallback(check_pending_commitments(1))
|
||||
d.addCallback(advance_one_cycle)
|
||||
d.addCallback(check_pending_commitments(0))
|
||||
|
||||
# Ursula commits for 3 periods with no problem
|
||||
for i in range(3):
|
||||
d.addCallback(advance_one_period)
|
||||
d.addCallback(verify_confirmed)
|
||||
d.addCallback(check_pending_commitments(1))
|
||||
|
||||
# Introduce unmined transactions
|
||||
d.addCallback(advance_one_period)
|
||||
d.addCallback(pending_commitments)
|
||||
|
||||
# Ursula's commitment transaction gets stuck
|
||||
for i in range(4):
|
||||
d.addCallback(advance_one_cycle)
|
||||
d.addCallback(verify_unmined_commitment)
|
||||
|
||||
# Ursula recovers from this situation
|
||||
d.addCallback(advance_one_cycle)
|
||||
d.addCallback(verify_confirmed)
|
||||
d.addCallback(advance_one_cycle)
|
||||
d.addCallback(check_pending_commitments(0))
|
||||
|
||||
# but it happens again, resulting in a replacement transaction
|
||||
d.addCallback(advance_until_replacement_indicated)
|
||||
d.addCallback(advance_one_cycle)
|
||||
d.addCallback(check_pending_commitments(1))
|
||||
d.addCallback(advance_one_cycle)
|
||||
d.addCallback(verify_replacement_commitment)
|
||||
|
||||
yield d
|
||||
|
||||
|
||||
def test_ursula_operator_confirmation(
|
||||
ursula_test_config,
|
||||
testerchain,
|
||||
|
@ -308,7 +136,7 @@ def test_ursula_operator_confirmation_autopilot(
|
|||
start_pytest_ursula_services(ursula=ursula)
|
||||
|
||||
def verify_confirmed(_):
|
||||
# Verify that periods were committed on-chain automatically
|
||||
# Verify that commitment made on-chain automatically
|
||||
expected_commitments = 1
|
||||
log(f"Verifying worker made {expected_commitments} commitments so far")
|
||||
assert commit_spy.call_count == expected_commitments
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher_core.umbral import SecretKeyFactory, Signer
|
||||
|
||||
from nucypher.blockchain.eth.actors import NucypherTokenActor
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
AdjudicatorAgent,
|
||||
ContractAgency,
|
||||
NucypherTokenAgent
|
||||
)
|
||||
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
|
||||
|
||||
def mock_ursula(testerchain, account, mocker):
|
||||
ursula_privkey = SecretKeyFactory.random()
|
||||
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.public_key(),
|
||||
signer=Signer(ursula_privkey))
|
||||
|
||||
signed_stamp = testerchain.client.sign_message(account=account,
|
||||
message=bytes(ursula_stamp))
|
||||
|
||||
ursula = mocker.Mock(stamp=ursula_stamp, operator_signature=signed_stamp)
|
||||
return ursula
|
||||
|
||||
|
||||
@pytest.mark.skip("David, send help!")
|
||||
def test_adjudicator_slashes(agency,
|
||||
testerchain,
|
||||
#mock_ursula_reencrypts,
|
||||
application_economics,
|
||||
test_registry,
|
||||
mocker):
|
||||
|
||||
staker_account = testerchain.stake_provider_account(0)
|
||||
worker_account = testerchain.ursula_account(0)
|
||||
|
||||
##### STAKING ESCROW STUFF #####
|
||||
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
|
||||
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
|
||||
|
||||
locked_tokens = application_economics.min_authorization * 5
|
||||
|
||||
# The staker receives an initial amount of tokens
|
||||
tpower = TransactingPower(account=testerchain.etherbase_account, signer=Web3Signer(testerchain.client))
|
||||
_txhash = token_agent.transfer(amount=locked_tokens,
|
||||
target_address=staker_account,
|
||||
transacting_power=tpower)
|
||||
|
||||
# Deposit: The staker deposits tokens in the StakingEscrow contract.
|
||||
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
|
||||
staker = Staker(domain=TEMPORARY_DOMAIN,
|
||||
registry=test_registry,
|
||||
transacting_power=tpower)
|
||||
|
||||
staker.initialize_stake(amount=NU(locked_tokens, 'NuNit'),
|
||||
lock_periods=application_economics.min_operator_seconds)
|
||||
assert staker.locked_tokens(periods=1) == locked_tokens
|
||||
|
||||
# The staker hasn't bond a worker yet
|
||||
assert NULL_ADDRESS == staking_agent.get_worker_from_staker(staker_address=staker_account)
|
||||
|
||||
_txhash = staking_agent.bond_worker(transacting_power=tpower, operator_address=worker_account)
|
||||
|
||||
assert worker_account == staking_agent.get_worker_from_staker(staker_address=staker_account)
|
||||
assert staker_account == staking_agent.get_staker_from_worker(operator_address=worker_account)
|
||||
|
||||
###### END OF STAKING ESCROW STUFF ####
|
||||
|
||||
adjudicator_agent = AdjudicatorAgent(registry=test_registry)
|
||||
bob_account = testerchain.bob_account
|
||||
bobby = NucypherTokenActor(checksum_address=bob_account,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
registry=test_registry)
|
||||
ursula = mock_ursula(testerchain, worker_account, mocker=mocker)
|
||||
|
||||
# Let's create a bad cfrag
|
||||
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
|
||||
|
||||
assert not adjudicator_agent.was_this_evidence_evaluated(evidence)
|
||||
bobby_old_balance = bobby.token_balance
|
||||
bob_tpower = TransactingPower(account=bob_account, signer=Web3Signer(testerchain.client))
|
||||
|
||||
adjudicator_agent.evaluate_cfrag(evidence=evidence, transacting_power=bob_tpower)
|
||||
|
||||
assert adjudicator_agent.was_this_evidence_evaluated(evidence)
|
||||
investigator_reward = bobby.token_balance - bobby_old_balance
|
||||
|
||||
assert investigator_reward > 0
|
||||
assert investigator_reward == application_economics.base_penalty / application_economics.reward_coefficient
|
||||
assert staker.locked_tokens(periods=1) < locked_tokens
|
|
@ -1,13 +1,8 @@
|
|||
|
||||
|
||||
import pytest
|
||||
from nucypher_core import MetadataResponse, MetadataResponsePayload
|
||||
from twisted.logger import LogLevel, globalLogPublisher
|
||||
|
||||
from nucypher.acumen.perception import FleetSensor
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from tests.utils.middleware import MockRestMiddleware
|
||||
from tests.utils.ursula import make_ursula_for_staking_provider
|
||||
|
||||
|
||||
def test_ursula_stamp_verification_tolerance(ursulas, mocker):
|
||||
|
@ -74,100 +69,3 @@ def test_ursula_stamp_verification_tolerance(ursulas, mocker):
|
|||
warning = warnings[1]['log_format']
|
||||
assert str(teacher) in warning
|
||||
assert "Failed to verify MetadataResponse from Teacher" in warning # TODO: Cleanup logging templates
|
||||
|
||||
|
||||
@pytest.mark.skip("See Issue #1075") # TODO: Issue #1075
|
||||
def test_invalid_operators_tolerance(
|
||||
testerchain,
|
||||
test_registry,
|
||||
ursulas,
|
||||
agency,
|
||||
idle_staker,
|
||||
application_economics,
|
||||
ursula_test_config,
|
||||
):
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others = list(ursulas)
|
||||
_, staking_agent, _ = agency
|
||||
|
||||
warnings = []
|
||||
|
||||
def warning_trapper(event):
|
||||
if event['log_level'] == LogLevel.warn:
|
||||
warnings.append(event)
|
||||
|
||||
# We start with an "idle_staker" (i.e., no tokens in StakingEscrow)
|
||||
assert 0 == staking_agent.owned_tokens(idle_staker.checksum_address)
|
||||
|
||||
# Now let's create an active worker for this staker.
|
||||
# First, stake something (e.g. the bare minimum)
|
||||
amount = application_economics.min_authorization
|
||||
periods = application_economics.min_operator_seconds
|
||||
|
||||
idle_staker.initialize_stake(amount=amount, lock_periods=periods)
|
||||
|
||||
# Stake starts next period (or else signature validation will fail)
|
||||
testerchain.time_travel(periods=1)
|
||||
idle_staker.stake_tracker.refresh()
|
||||
|
||||
# We create an active worker node for this staker
|
||||
worker = make_ursula_for_staking_provider(
|
||||
staking_provider=idle_staker,
|
||||
operator_address=testerchain.unassigned_accounts[-1],
|
||||
ursula_config=ursula_test_config,
|
||||
blockchain=testerchain,
|
||||
ursulas_to_learn_about=None,
|
||||
)
|
||||
|
||||
# Since we made a commitment, we need to advance one period
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
# The worker is valid and can be verified (even with the force option)
|
||||
worker.verify_node(force=True, network_middleware=MockRestMiddleware())
|
||||
# In particular, we know that it's bonded to a staker who is really staking.
|
||||
assert worker._operator_is_bonded(registry=test_registry)
|
||||
assert worker._staking_provider_is_really_staking(registry=test_registry)
|
||||
|
||||
# OK. Now we learn about this worker.
|
||||
lonely_blockchain_learner.remember_node(worker)
|
||||
|
||||
# The worker already committed one period before. Let's commit to the remaining 29.
|
||||
for i in range(29):
|
||||
worker.commit_to_next_period()
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
# The stake period has ended, and the staker wants her tokens back ("when lambo?").
|
||||
# She withdraws up to the last penny (well, last nunit, actually).
|
||||
|
||||
idle_staker.mint()
|
||||
testerchain.time_travel(periods=1)
|
||||
i_want_it_all = staking_agent.owned_tokens(idle_staker.checksum_address)
|
||||
idle_staker.withdraw(i_want_it_all)
|
||||
|
||||
# OK...so...the staker is not staking anymore ...
|
||||
assert 0 == staking_agent.owned_tokens(idle_staker.checksum_address)
|
||||
|
||||
# ... but the worker node still is "verified" (since we're not forcing on-chain verification)
|
||||
worker.verify_node(network_middleware=MockRestMiddleware())
|
||||
|
||||
# If we force, on-chain verification, the worker is of course not verified
|
||||
with pytest.raises(worker.NotStaking):
|
||||
worker.verify_node(force=True, network_middleware=MockRestMiddleware())
|
||||
|
||||
# Let's learn from this invalid node
|
||||
lonely_blockchain_learner._current_teacher_node = worker
|
||||
globalLogPublisher.addObserver(warning_trapper)
|
||||
lonely_blockchain_learner.learn_from_teacher_node()
|
||||
# lonely_blockchain_learner.remember_node(worker) # The same problem occurs if we directly try to remember this node
|
||||
globalLogPublisher.removeObserver(warning_trapper)
|
||||
|
||||
# TODO: What should we really check here? (#1075)
|
||||
assert len(warnings) == 1
|
||||
warning = warnings[-1]['log_format']
|
||||
assert str(worker) in warning
|
||||
assert "no active stakes" in warning # TODO: Cleanup logging templates
|
||||
assert worker not in lonely_blockchain_learner.known_nodes
|
||||
|
||||
# TODO: Write a similar test but for detached worker (#1075)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import to_checksum_address
|
||||
|
||||
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
||||
from eth_utils import to_checksum_address
|
||||
|
||||
CONFIRMATION_SLOT = 1
|
||||
|
||||
|
@ -157,7 +155,7 @@ def test_bond_operator(testerchain, threshold_staking, pre_application, applicat
|
|||
assert event_args['stakingProvider'] == staking_provider_3
|
||||
# Now the operator has been unbonded ...
|
||||
assert event_args['operator'] == NULL_ADDRESS
|
||||
# ... with a new starting period.
|
||||
# ... with a new start time.
|
||||
assert event_args['startTimestamp'] == timestamp
|
||||
|
||||
# The staking provider can bond now a new operator, without waiting additional time.
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
|
||||
|
||||
import contextlib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from constant_sorrow import constants
|
||||
from web3.exceptions import ValidationError
|
||||
|
||||
from nucypher.blockchain.eth.deployers import (
|
||||
BaseContractDeployer,
|
||||
NucypherTokenDeployer,
|
||||
StakingEscrowDeployer
|
||||
)
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.sol.compile.constants import SOLIDITY_SOURCE_ROOT, TEST_SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
from tests.utils.blockchain import free_gas_price_strategy, TesterBlockchain
|
||||
|
||||
USER = "nucypher"
|
||||
REPO = "nucypher"
|
||||
BRANCH = "main"
|
||||
GITHUB_SOURCE_LINK = f"https://api.github.com/repos/{USER}/{REPO}/contents/nucypher/blockchain/eth/sol/source?ref={BRANCH}"
|
||||
|
||||
|
||||
BlockchainDeployerInterface.GAS_STRATEGIES = {**BlockchainDeployerInterface.GAS_STRATEGIES,
|
||||
'free': free_gas_price_strategy}
|
||||
|
||||
|
||||
def download_github_dir(source_link: str, target_folder: Path):
|
||||
response = requests.get(source_link)
|
||||
if response.status_code != 200:
|
||||
error = f"Failed to call api {source_link} with status code {response.status_code}"
|
||||
raise RuntimeError(error)
|
||||
|
||||
for content in response.json():
|
||||
path = target_folder / content["name"]
|
||||
if content["type"] == "dir":
|
||||
path.mkdir()
|
||||
download_github_dir(content["url"], path)
|
||||
else:
|
||||
download_github_file(content["download_url"], path)
|
||||
|
||||
|
||||
def download_github_file(source_link: str, target_folder: Path):
|
||||
response = requests.get(source_link)
|
||||
if response.status_code != 200:
|
||||
error = f"Failed to call api {source_link} with status code {response.status_code}"
|
||||
raise RuntimeError(error)
|
||||
|
||||
raw_data = response.content
|
||||
with open(target_folder, 'wb') as registry_file:
|
||||
registry_file.seek(0)
|
||||
registry_file.write(raw_data)
|
||||
registry_file.truncate()
|
||||
|
||||
|
||||
# def parameters_v611(blockchain_interface: BlockchainDeployerInterface,
|
||||
# transacting_power: TransactingPower,
|
||||
# deployer: BaseContractDeployer):
|
||||
# policy_manager_mock, _ = blockchain_interface.deploy_contract(
|
||||
# transacting_power,
|
||||
# deployer.registry,
|
||||
# "OldPolicyManagerMock"
|
||||
# )
|
||||
# adjudicator_mock, _ = blockchain_interface.deploy_contract(
|
||||
# transacting_power,
|
||||
# deployer.registry,
|
||||
# "OldAdjudicatorMock"
|
||||
# )
|
||||
# parameters = {
|
||||
# "_genesisHoursPerPeriod": 1,
|
||||
# "_hoursPerPeriod": 1,
|
||||
# "_issuanceDecayCoefficient": 1,
|
||||
# "_lockDurationCoefficient1": 1,
|
||||
# "_lockDurationCoefficient2": 2,
|
||||
# "_maximumRewardedPeriods": 1,
|
||||
# "_firstPhaseTotalSupply": 1,
|
||||
# "_firstPhaseMaxIssuance": 1,
|
||||
# "_minLockedPeriods": 2,
|
||||
# "_minAllowableLockedTokens": 0,
|
||||
# "_maxAllowableLockedTokens": deployer.economics.maximum_allowed_locked,
|
||||
# "_minWorkerPeriods": 1,
|
||||
# "_policyManager": policy_manager_mock.address,
|
||||
# "_adjudicator": adjudicator_mock.address
|
||||
# }
|
||||
# return parameters
|
||||
|
||||
|
||||
# Constructor parameters overrides for previous versions if needed
|
||||
# All versions below the specified version must use these overrides
|
||||
# 'None' value removes arg from list of constructor parameters
|
||||
CONSTRUCTOR_OVERRIDES = {
|
||||
StakingEscrowDeployer.contract_name: {
|
||||
"v5.7.1": lambda *args: {"_genesisHoursPerPeriod": None},
|
||||
# "v6.1.1": parameters_v611
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_SKIP = {
|
||||
StakingEscrowDeployer.contract_name: ["v5.6.1", "v6.2.1"],
|
||||
}
|
||||
|
||||
|
||||
def deploy_base_contract(blockchain_interface: BlockchainDeployerInterface,
|
||||
deployer: BaseContractDeployer,
|
||||
transacting_power: TransactingPower,
|
||||
skipt_test: bool):
|
||||
contract_name = deployer.contract_name
|
||||
latest_version, _data = blockchain_interface.find_raw_contract_data(contract_name, "latest")
|
||||
raw_contracts = blockchain_interface._raw_contract_cache
|
||||
overrides = dict()
|
||||
if len(raw_contracts[contract_name]) != 1:
|
||||
try:
|
||||
overrides_func = CONSTRUCTOR_OVERRIDES[contract_name][latest_version]
|
||||
overrides = overrides_func(blockchain_interface,
|
||||
transacting_power,
|
||||
deployer)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
version = "latest" if skipt_test else "earliest"
|
||||
try:
|
||||
deployer.deploy(transacting_power=transacting_power,
|
||||
contract_version=version,
|
||||
deployment_mode=constants.FULL, **overrides)
|
||||
except ValidationError:
|
||||
pass # Skip errors related to initialization
|
||||
|
||||
|
||||
def skip_test(blockchain_interface: BlockchainDeployerInterface, contract_name: str):
|
||||
latest_version, _data = blockchain_interface.find_raw_contract_data(contract_name, "latest")
|
||||
raw_contracts = blockchain_interface._raw_contract_cache
|
||||
try:
|
||||
force_skip = latest_version in FORCE_SKIP[contract_name]
|
||||
except KeyError:
|
||||
force_skip = False
|
||||
|
||||
return force_skip or len(raw_contracts[contract_name]) == 1
|
||||
|
||||
|
||||
def prepare_staker(blockchain_interface: TesterBlockchain,
|
||||
deployer: StakingEscrowDeployer,
|
||||
transacting_power: TransactingPower):
|
||||
worklock_agent = WorkLockAgent(registry=deployer.registry)
|
||||
value = worklock_agent.minimum_allowed_bid
|
||||
worklock_agent.bid(value=value, transacting_power=transacting_power)
|
||||
blockchain_interface.time_travel(hours=100)
|
||||
worklock_agent.verify_bidding_correctness(transacting_power=transacting_power, gas_limit=1000000)
|
||||
worklock_agent.claim(transacting_power=transacting_power)
|
||||
|
||||
|
||||
@pytest.mark.skip("fix after deployers and merging threshold-network")
|
||||
def test_upgradeability(temp_dir_path):
|
||||
# Prepare remote source for compilation
|
||||
download_github_dir(GITHUB_SOURCE_LINK, temp_dir_path)
|
||||
|
||||
# Prepare the blockchain
|
||||
TesterBlockchain.SOURCES = [
|
||||
SourceBundle(base_path=SOLIDITY_SOURCE_ROOT,
|
||||
other_paths=(TEST_SOLIDITY_SOURCE_ROOT,)),
|
||||
SourceBundle(base_path=Path(temp_dir_path))
|
||||
]
|
||||
|
||||
eth_provider_uri = 'tester://pyevm/2' # TODO: Testerchain caching Issues
|
||||
try:
|
||||
blockchain_interface = TesterBlockchain(gas_strategy='free')
|
||||
blockchain_interface.eth_provider_uri = eth_provider_uri
|
||||
blockchain_interface.connect()
|
||||
origin = blockchain_interface.client.accounts[0]
|
||||
BlockchainInterfaceFactory.register_interface(interface=blockchain_interface)
|
||||
transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
signer=Web3Signer(blockchain_interface.client),
|
||||
account=origin)
|
||||
|
||||
economics = make_token_economics(blockchain_interface)
|
||||
|
||||
# Check contracts with multiple versions
|
||||
contract_name = StakingEscrowDeployer.contract_name
|
||||
skip_staking_escrow_test = skip_test(blockchain_interface, contract_name)
|
||||
|
||||
if skip_staking_escrow_test:
|
||||
return
|
||||
|
||||
# Prepare master version of contracts and upgrade to the latest
|
||||
registry = InMemoryContractRegistry()
|
||||
|
||||
token_deployer = NucypherTokenDeployer(registry=registry, economics=economics)
|
||||
token_deployer.deploy(transacting_power=transacting_power)
|
||||
|
||||
staking_escrow_deployer = StakingEscrowDeployer(registry=registry, economics=economics)
|
||||
staking_escrow_deployer.deploy(deployment_mode=constants.INIT, transacting_power=transacting_power)
|
||||
|
||||
if not skip_staking_escrow_test:
|
||||
economics.worklock_supply = economics.maximum_allowed_locked
|
||||
worklock_deployer = WorklockDeployer(registry=registry, economics=economics)
|
||||
worklock_deployer.deploy(transacting_power=transacting_power)
|
||||
|
||||
staking_escrow_deployer = StakingEscrowDeployer(registry=registry, economics=economics)
|
||||
deploy_base_contract(blockchain_interface, staking_escrow_deployer,
|
||||
transacting_power=transacting_power,
|
||||
skipt_test=skip_staking_escrow_test)
|
||||
|
||||
if not skip_staking_escrow_test:
|
||||
prepare_staker(blockchain_interface=blockchain_interface,
|
||||
deployer=staking_escrow_deployer,
|
||||
transacting_power=transacting_power)
|
||||
staking_escrow_deployer.upgrade(transacting_power=transacting_power,
|
||||
contract_version="latest",
|
||||
confirmations=0)
|
||||
|
||||
finally:
|
||||
# Unregister interface # TODO: Move to method?
|
||||
with contextlib.suppress(KeyError):
|
||||
del BlockchainInterfaceFactory._interfaces[eth_provider_uri]
|
|
@ -1,161 +0,0 @@
|
|||
|
||||
|
||||
|
||||
from decimal import Decimal, localcontext
|
||||
from math import log
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher.blockchain.economics import Economics
|
||||
|
||||
|
||||
@pytest.mark.skip("remove me")
|
||||
def test_exact_economics():
|
||||
"""
|
||||
Formula for staking in one period:
|
||||
(totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / d / k2
|
||||
|
||||
d - Coefficient which modifies the rate at which the maximum issuance decays
|
||||
k1 - Numerator of the locking duration coefficient
|
||||
k2 - Denominator of the locking duration coefficient
|
||||
|
||||
if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods
|
||||
kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / d / k2
|
||||
|
||||
kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1
|
||||
where allLockedPeriods == min(T, T1)
|
||||
"""
|
||||
|
||||
#
|
||||
# Expected Output
|
||||
#
|
||||
one_year_in_periods = 365 / 7
|
||||
|
||||
# Supply
|
||||
expected_total_supply = 3885390081748248632541961138
|
||||
expected_supply_ratio = Decimal('3.885390081748248632541961138')
|
||||
expected_initial_supply = 1000000000000000000000000000
|
||||
expected_phase1_supply = 1829579800000000000000000000
|
||||
|
||||
# Reward
|
||||
expected_reward_supply = 2885390081748248632541961138
|
||||
reward_saturation = 1
|
||||
|
||||
# Staking 2 phase
|
||||
decay_half_life = 2
|
||||
multiplier = 0.5
|
||||
expected_lock_duration_coefficient_1 = one_year_in_periods
|
||||
expected_lock_duration_coefficient_2 = 2 * expected_lock_duration_coefficient_1
|
||||
expected_phase2_coefficient = 150
|
||||
expected_minting_coefficient = expected_phase2_coefficient * expected_lock_duration_coefficient_2
|
||||
|
||||
assert int(expected_lock_duration_coefficient_1 * decay_half_life) == \
|
||||
round(expected_minting_coefficient * log(2) * multiplier / one_year_in_periods)
|
||||
|
||||
#
|
||||
# Sanity
|
||||
#
|
||||
|
||||
# Sanity check ratio accuracy
|
||||
expected_scaled_ratio = str(expected_supply_ratio).replace('.', '')
|
||||
assert str(expected_total_supply) == expected_scaled_ratio
|
||||
|
||||
# Sanity check denomination size
|
||||
expected_scale = 28
|
||||
assert len(str(expected_total_supply)) == expected_scale
|
||||
assert len(str(expected_initial_supply)) == expected_scale
|
||||
assert len(str(expected_reward_supply)) == expected_scale
|
||||
|
||||
# Use same precision as economics class
|
||||
with localcontext() as ctx:
|
||||
ctx.prec = Economics._precision
|
||||
|
||||
# Sanity check expected testing outputs
|
||||
assert Decimal(expected_total_supply) / expected_initial_supply == expected_supply_ratio
|
||||
assert expected_reward_supply == expected_total_supply - expected_initial_supply
|
||||
assert reward_saturation * one_year_in_periods * multiplier == expected_lock_duration_coefficient_1 * (1 - multiplier)
|
||||
assert int(one_year_in_periods ** 2 * reward_saturation * decay_half_life / log(2) / (1-multiplier) / expected_lock_duration_coefficient_2) == \
|
||||
expected_phase2_coefficient
|
||||
|
||||
|
||||
|
||||
# After sanity checking, assemble expected test deployment parameters
|
||||
expected_deployment_parameters = (24, # Hours in single period at genesis
|
||||
24 * 7, # Hours in single period
|
||||
150, # Coefficient which modifies the rate at which the maximum issuance decays (d)
|
||||
52, # Numerator of the locking duration coefficient (k1)
|
||||
52 * 2, # Denominator of the locking duration coefficient (k2)
|
||||
52, # Max periods that will be additionally rewarded (awarded_periods)
|
||||
2829579800000000000000000000, # Total supply for the first phase
|
||||
7017566356164383151812537, # Max possible reward for one period for all stakers in the first phase
|
||||
4, # Min amount of periods during which tokens can be locked
|
||||
15000000000000000000000, # min locked NuNits
|
||||
30000000000000000000000000, # max locked NuNits
|
||||
2) # Min worker periods
|
||||
#
|
||||
# Token Economics
|
||||
#
|
||||
|
||||
# Check creation
|
||||
e = Economics()
|
||||
|
||||
with localcontext() as ctx:
|
||||
ctx.prec = Economics._precision
|
||||
one_year_in_periods = Decimal(one_year_in_periods)
|
||||
|
||||
# Check that total_supply calculated correctly
|
||||
assert Decimal(e.erc20_total_supply) / e.initial_supply == expected_supply_ratio
|
||||
assert e.erc20_total_supply == expected_total_supply
|
||||
|
||||
# Check reward rates for the second phase
|
||||
initial_rate = (e.erc20_total_supply - int(e.first_phase_total_supply)) * (e.lock_duration_coefficient_1 + one_year_in_periods) / \
|
||||
(e.issuance_decay_coefficient * e.lock_duration_coefficient_2)
|
||||
assert int(initial_rate) == int(e.first_phase_max_issuance)
|
||||
assert int(LOG2 / (e.token_halving * one_year_in_periods) * (e.erc20_total_supply - int(e.first_phase_total_supply))) == \
|
||||
int(initial_rate)
|
||||
|
||||
initial_rate_small = (e.erc20_total_supply - int(e.first_phase_total_supply)) * e.lock_duration_coefficient_1 / \
|
||||
(e.issuance_decay_coefficient * e.lock_duration_coefficient_2)
|
||||
assert int(initial_rate_small) == int(initial_rate / 2)
|
||||
|
||||
# Check reward supply
|
||||
assert e.reward_supply == expected_total_supply - expected_initial_supply
|
||||
|
||||
# Check deployment parameters
|
||||
assert e.pre_application_deployment_parameters == expected_deployment_parameters
|
||||
assert e.erc20_initial_supply == expected_initial_supply
|
||||
assert e.erc20_reward_supply == expected_reward_supply
|
||||
|
||||
# Additional checks on supply
|
||||
assert e.token_supply_at_period(period=0) == expected_initial_supply
|
||||
assert e.cumulative_rewards_at_period(0) == 0
|
||||
|
||||
# Check phase 1 doesn't overshoot
|
||||
switch_period = 5 * 52
|
||||
assert e.first_phase_final_period() == switch_period
|
||||
assert e.token_supply_at_period(period=switch_period) <= expected_phase1_supply + expected_initial_supply
|
||||
assert e.token_supply_at_period(period=switch_period + 1) > expected_phase1_supply + expected_initial_supply
|
||||
assert e.token_supply_at_period(period=switch_period) < e.token_supply_at_period(period=switch_period + 1)
|
||||
|
||||
assert e.rewards_during_period(period=1) == round(e.first_phase_max_issuance)
|
||||
assert e.rewards_during_period(period=switch_period) == round(e.first_phase_max_issuance)
|
||||
assert e.rewards_during_period(period=switch_period + 1) < int(e.first_phase_max_issuance)
|
||||
|
||||
# Last NuNit is minted after 188 years (or 9800 periods).
|
||||
# That's the year 2208, if token is launched in 2020.
|
||||
# 23rd century schizoid man!
|
||||
assert abs(expected_total_supply - e.token_supply_at_period(period=9800)) < e.first_phase_max_issuance
|
||||
assert e.erc20_total_supply == expected_total_supply
|
||||
|
||||
# After 1 year:
|
||||
expected_reward_one_year = 52 * 7017566356164383151812537
|
||||
assert abs((expected_initial_supply + expected_reward_one_year) - e.token_supply_at_period(period=52)) <= 100
|
||||
assert abs(expected_reward_one_year - e.cumulative_rewards_at_period(period=52)) <= 100
|
||||
assert e.erc20_initial_supply + e.cumulative_rewards_at_period(52) == e.token_supply_at_period(period=52)
|
||||
|
||||
# Checking that the supply function is monotonic in phase 1
|
||||
todays_supply = e.token_supply_at_period(period=0)
|
||||
for t in range(9800):
|
||||
tomorrows_supply = e.token_supply_at_period(period=t + 1)
|
||||
assert tomorrows_supply >= todays_supply
|
||||
todays_supply = tomorrows_supply
|
|
@ -1,686 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import tabulate
|
||||
from nucypher_core.umbral import SecretKey, Signer
|
||||
from twisted.logger import ILogObserver, globalLogPublisher, jsonFileLogObserver
|
||||
from web3.contract import Contract
|
||||
from zope.interface import provider
|
||||
|
||||
from nucypher.blockchain.economics import Economics
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
AdjudicatorAgent,
|
||||
NucypherTokenAgent,
|
||||
PolicyManagerAgent,
|
||||
)
|
||||
from nucypher.blockchain.eth.constants import NUCYPHER_CONTRACT_NAMES, NULL_ADDRESS, POLICY_ID_LENGTH
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
from nucypher.utilities.logging import Logger
|
||||
from tests.utils.blockchain import TesterBlockchain
|
||||
|
||||
ALGORITHM_SHA256 = 1
|
||||
TOKEN_ECONOMICS = Economics()
|
||||
MIN_ALLOWED_LOCKED = TOKEN_ECONOMICS.min_authorization
|
||||
LOCKED_PERIODS = 30
|
||||
MAX_ALLOWED_LOCKED = TOKEN_ECONOMICS.maximum_allowed_locked
|
||||
MAX_MINTING_PERIODS = TOKEN_ECONOMICS.maximum_rewarded_periods
|
||||
|
||||
|
||||
class AnalyzeGas:
|
||||
"""
|
||||
Callable twisted log observer with built-in record-keeping for gas estimation runs.
|
||||
"""
|
||||
|
||||
# Logging
|
||||
LOG_NAME = 'estimate-gas'
|
||||
LOG_FILENAME = '{}.log.json'.format(LOG_NAME)
|
||||
OUTPUT_DIR = Path(__file__).parent / 'results'
|
||||
JSON_OUTPUT_FILENAME = '{}.json'.format(LOG_NAME)
|
||||
|
||||
_PATTERN = re.compile(r'''
|
||||
^ # Anchor at the start of a string
|
||||
(.+) # Any character sequence longer than 1; Captured
|
||||
\s=\s # Space-Equal-Space
|
||||
(\d+) # A sequence of digits; Captured
|
||||
\s\|\s # Space-Slash-Space
|
||||
(\d+) # A sequence of digits; Captured
|
||||
$ # Anchor at the end of the string
|
||||
''', re.VERBOSE)
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self.log = Logger(self.__class__.__name__)
|
||||
self.gas_estimations = dict()
|
||||
|
||||
if not self.OUTPUT_DIR.is_dir():
|
||||
self.OUTPUT_DIR.mkdir()
|
||||
|
||||
@provider(ILogObserver)
|
||||
def __call__(self, event, *args, **kwargs) -> None:
|
||||
|
||||
if event.get('log_namespace') == self.LOG_NAME:
|
||||
message = event.get("log_format")
|
||||
|
||||
matches = self._PATTERN.match(message)
|
||||
if not matches:
|
||||
self.log.debug("No match for {} with pattern {}".format(message, self._PATTERN))
|
||||
return
|
||||
|
||||
label, estimates, gas_used = matches.groups()
|
||||
self.paint_line(label, estimates, gas_used)
|
||||
self.gas_estimations[label] = int(gas_used)
|
||||
|
||||
@staticmethod
|
||||
def paint_line(label: str, estimates: str, gas_used: str) -> None:
|
||||
print('{label} {estimates:7,} | {gas:7,}'.format(
|
||||
label=label.ljust(72, '.'), estimates=int(estimates), gas=int(gas_used)))
|
||||
|
||||
def to_json_file(self) -> None:
|
||||
print('Saving JSON Output...')
|
||||
|
||||
epoch_time = str(int(time.time()))
|
||||
timestamped_filename = '{}-{}'.format(epoch_time, self.JSON_OUTPUT_FILENAME)
|
||||
filepath = self.OUTPUT_DIR / timestamped_filename
|
||||
with open(filepath, 'w') as file:
|
||||
file.write(json.dumps(self.gas_estimations, indent=4))
|
||||
|
||||
def start_collection(self) -> None:
|
||||
print("Starting Data Collection...")
|
||||
|
||||
json_filepath = self.OUTPUT_DIR / AnalyzeGas.LOG_FILENAME
|
||||
json_io = io.open(json_filepath, "w")
|
||||
json_observer = jsonFileLogObserver(json_io)
|
||||
globalLogPublisher.addObserver(json_observer)
|
||||
globalLogPublisher.addObserver(self)
|
||||
|
||||
|
||||
def mock_ursula(testerchain, account):
|
||||
ursula_privkey = SecretKey.random()
|
||||
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.public_key(),
|
||||
signer=Signer(ursula_privkey))
|
||||
|
||||
signed_stamp = testerchain.client.sign_message(account=account,
|
||||
message=bytes(ursula_stamp))
|
||||
|
||||
ursula = Mock(stamp=ursula_stamp, operator_signature=signed_stamp)
|
||||
return ursula
|
||||
|
||||
|
||||
def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
||||
"""
|
||||
Execute a linear sequence of NyCypher transactions mimicking
|
||||
post-deployment usage on a local PyEVM blockchain;
|
||||
Record the resulting estimated transaction gas expenditure.
|
||||
|
||||
Note: The function calls below are *order dependant*
|
||||
"""
|
||||
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
|
||||
if analyzer is None:
|
||||
analyzer = AnalyzeGas()
|
||||
|
||||
log = Logger(AnalyzeGas.LOG_NAME)
|
||||
os.environ['GAS_ESTIMATOR_BACKEND_FUNC'] = 'eth.estimators.gas.binary_gas_search_exact'
|
||||
|
||||
# Blockchain
|
||||
economics = Economics(
|
||||
base_penalty=MIN_ALLOWED_LOCKED - 1,
|
||||
penalty_history_coefficient=0,
|
||||
percentage_penalty_coefficient=2,
|
||||
reward_coefficient=2
|
||||
)
|
||||
testerchain, registry = TesterBlockchain.bootstrap_network(economics=economics)
|
||||
web3 = testerchain.w3
|
||||
|
||||
print("\n********* SIZE OF MAIN CONTRACTS *********")
|
||||
MAX_SIZE = 24576
|
||||
rows = list()
|
||||
for contract_name in NUCYPHER_CONTRACT_NAMES:
|
||||
compiled_contract = testerchain._raw_contract_cache[contract_name]
|
||||
|
||||
version = list(compiled_contract).pop()
|
||||
# FIXME this value includes constructor code size but should not
|
||||
bin_runtime = compiled_contract[version]['evm']['bytecode']['object']
|
||||
bin_length_in_bytes = len(bin_runtime) // 2
|
||||
percentage = int(100 * bin_length_in_bytes / MAX_SIZE)
|
||||
bar = ('*'*(percentage//2)).ljust(50)
|
||||
rows.append((contract_name, bin_length_in_bytes, f'{bar} {percentage}%'))
|
||||
|
||||
headers = ('Contract', 'Size (B)', f'% of max allowed contract size ({MAX_SIZE} B)')
|
||||
print(tabulate.tabulate(rows, headers=headers, tablefmt="simple"), end="\n\n")
|
||||
|
||||
# Accounts
|
||||
origin, staker1, staker2, staker3, staker4, alice1, alice2, *everyone_else = testerchain.client.accounts
|
||||
|
||||
ursula_with_stamp = mock_ursula(testerchain, staker1)
|
||||
|
||||
# Contracts
|
||||
token_agent = NucypherTokenAgent(registry=registry)
|
||||
staking_agent = StakingEscrowAgent(registry=registry)
|
||||
policy_agent = PolicyManagerAgent(registry=registry)
|
||||
adjudicator_agent = AdjudicatorAgent(registry=registry)
|
||||
|
||||
# Contract Callers
|
||||
token_functions = token_agent.contract.functions
|
||||
staker_functions = staking_agent.contract.functions
|
||||
policy_functions = policy_agent.contract.functions
|
||||
adjudicator_functions = adjudicator_agent.contract.functions
|
||||
|
||||
analyzer.start_collection()
|
||||
print("********* Estimating Gas *********")
|
||||
|
||||
def transact_and_log(label, function, transaction):
|
||||
estimates = function.estimate_gas(transaction)
|
||||
transaction.update(gas=estimates)
|
||||
tx = function.transact(transaction)
|
||||
receipt = testerchain.wait_for_receipt(tx)
|
||||
log.info(f"{label} = {estimates} | {receipt['gasUsed']}")
|
||||
|
||||
def transact(function, transaction):
|
||||
transaction.update(gas=1000000)
|
||||
tx = function.transact(transaction)
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# First deposit ever is the most expensive, make it to remove unusual gas spending
|
||||
transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 10), {'from': origin})
|
||||
transact(staker_functions.deposit(everyone_else[0], MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': origin})
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
#
|
||||
# Give Ursula and Alice some coins
|
||||
#
|
||||
transact_and_log("Transfer tokens", token_functions.transfer(staker1, MIN_ALLOWED_LOCKED * 10), {'from': origin})
|
||||
transact(token_functions.transfer(staker2, MIN_ALLOWED_LOCKED * 10), {'from': origin})
|
||||
transact(token_functions.transfer(staker3, MIN_ALLOWED_LOCKED * 10), {'from': origin})
|
||||
|
||||
#
|
||||
# Ursula and Alice give Escrow rights to transfer
|
||||
#
|
||||
transact_and_log("Approving transfer",
|
||||
token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 7),
|
||||
{'from': staker1})
|
||||
transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': staker2})
|
||||
transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': staker3})
|
||||
|
||||
#
|
||||
# Ursula and Alice transfer some tokens to the escrow and lock them
|
||||
#
|
||||
transact_and_log("Initial deposit tokens, first",
|
||||
staker_functions.deposit(staker1, MIN_ALLOWED_LOCKED * 3, LOCKED_PERIODS),
|
||||
{'from': staker1})
|
||||
transact_and_log("Initial deposit tokens, other",
|
||||
staker_functions.deposit(staker2, MIN_ALLOWED_LOCKED * 3, LOCKED_PERIODS),
|
||||
{'from': staker2})
|
||||
transact(staker_functions.deposit(staker3, MIN_ALLOWED_LOCKED * 3, LOCKED_PERIODS), {'from': staker3})
|
||||
|
||||
transact(staker_functions.bondOperator(staker1), {'from': staker1})
|
||||
transact(staker_functions.bondOperator(staker2), {'from': staker2})
|
||||
transact(staker_functions.bondOperator(staker3), {'from': staker3})
|
||||
transact(staker_functions.setReStake(False), {'from': staker1})
|
||||
transact(staker_functions.setReStake(False), {'from': staker2})
|
||||
transact(staker_functions.setWindDown(True), {'from': staker1})
|
||||
transact(staker_functions.setWindDown(True), {'from': staker2})
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker2})
|
||||
|
||||
#
|
||||
# Wait 1 period and make a commitment
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact_and_log("Make a commitment, first", staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact_and_log("Make a commitment, other", staker_functions.commitToNextPeriod(), {'from': staker2})
|
||||
|
||||
#
|
||||
# Wait 1 period and mint tokens
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact_and_log("Minting (1 stake), first", staker_functions.mint(), {'from': staker1})
|
||||
transact_and_log("Minting (1 stake), other", staker_functions.mint(), {'from': staker2})
|
||||
transact_and_log("Make a commitment again, first", staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact_and_log("Make a commitment again, other", staker_functions.commitToNextPeriod(), {'from': staker2})
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker3})
|
||||
|
||||
#
|
||||
# Commit again
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact_and_log("Make a commitment + mint, first", staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact_and_log("Make a commitment + mint, other", staker_functions.commitToNextPeriod(), {'from': staker2})
|
||||
|
||||
#
|
||||
# Create policy
|
||||
#
|
||||
policy_id_1 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
policy_id_2 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
number_of_periods = 10
|
||||
rate = 100
|
||||
one_period = economics.hours_per_period * 60 * 60
|
||||
value = number_of_periods * rate
|
||||
current_timestamp = testerchain.w3.eth.get_block('latest').timestamp
|
||||
end_timestamp = current_timestamp + (number_of_periods - 1) * one_period
|
||||
transact_and_log("Creating policy (1 node, 10 periods, pre-committed), first",
|
||||
policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [staker1]),
|
||||
{'from': alice1, 'value': value})
|
||||
transact_and_log("Creating policy (1 node, 10 periods, pre-committed), other",
|
||||
policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [staker1]),
|
||||
{'from': alice1, 'value': value})
|
||||
|
||||
#
|
||||
# Get locked tokens
|
||||
#
|
||||
transact_and_log("Getting locked tokens", staker_functions.getLockedTokens(staker1, 0), {})
|
||||
|
||||
#
|
||||
# Wait 1 period and withdraw tokens
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact_and_log("Withdraw", staker_functions.withdraw(1), {'from': staker1})
|
||||
|
||||
#
|
||||
# Make a commitment with re-stake
|
||||
#
|
||||
transact(staker_functions.setReStake(True), {'from': staker1})
|
||||
transact(staker_functions.setReStake(True), {'from': staker2})
|
||||
|
||||
# Used to remove spending for first call in a period for mint and commitToNextPeriod
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker3})
|
||||
|
||||
transact_and_log("Make a commitment + mint + re-stake",
|
||||
staker_functions.commitToNextPeriod(),
|
||||
{'from': staker2})
|
||||
transact_and_log("Make a commitment + mint + re-stake + first fee + first fee rate",
|
||||
staker_functions.commitToNextPeriod(),
|
||||
{'from': staker1})
|
||||
|
||||
transact(staker_functions.setReStake(False), {'from': staker1})
|
||||
transact(staker_functions.setReStake(False), {'from': staker2})
|
||||
|
||||
#
|
||||
# Wait 2 periods and make a commitment after downtime
|
||||
#
|
||||
testerchain.time_travel(periods=2)
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker3})
|
||||
transact_and_log("Make a commitment after downtime", staker_functions.commitToNextPeriod(), {'from': staker2})
|
||||
transact_and_log("Make a commitment after downtime + updating fee",
|
||||
staker_functions.commitToNextPeriod(),
|
||||
{'from': staker1})
|
||||
|
||||
#
|
||||
# Ursula and Alice deposit some tokens to the escrow again
|
||||
#
|
||||
transact_and_log("Deposit tokens after making a commitment",
|
||||
staker_functions.deposit(staker1, MIN_ALLOWED_LOCKED * 2, LOCKED_PERIODS),
|
||||
{'from': staker1})
|
||||
transact(staker_functions.deposit(staker2, MIN_ALLOWED_LOCKED * 2, LOCKED_PERIODS), {'from': staker2})
|
||||
|
||||
#
|
||||
# Revoke policy
|
||||
#
|
||||
transact_and_log("Revoking policy", policy_functions.revokePolicy(policy_id_1), {'from': alice1})
|
||||
|
||||
#
|
||||
# Wait 1 period
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
#
|
||||
# Create policy with multiple pre-committed nodes
|
||||
#
|
||||
policy_id_1 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
policy_id_2 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
policy_id_3 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
number_of_periods = 100
|
||||
value = 3 * number_of_periods * rate
|
||||
current_timestamp = testerchain.w3.eth.get_block('latest').timestamp
|
||||
end_timestamp = current_timestamp + (number_of_periods - 1) * one_period
|
||||
transact_and_log("Creating policy (3 nodes, 100 periods, pre-committed), first",
|
||||
policy_functions.createPolicy(policy_id_1, alice1, end_timestamp, [staker1, staker2, staker3]),
|
||||
{'from': alice1, 'value': value})
|
||||
transact_and_log("Creating policy (3 nodes, 100 periods, pre-committed), other",
|
||||
policy_functions.createPolicy(policy_id_2, alice1, end_timestamp, [staker1, staker2, staker3]),
|
||||
{'from': alice1, 'value': value})
|
||||
value = 2 * number_of_periods * rate
|
||||
transact_and_log("Creating policy (2 nodes, 100 periods, pre-committed), other",
|
||||
policy_functions.createPolicy(policy_id_3, alice1, end_timestamp, [staker1, staker2]),
|
||||
{'from': alice1, 'value': value})
|
||||
|
||||
#
|
||||
# Wait 1 period and mint tokens
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.mint(), {'from': staker3})
|
||||
transact_and_log("Last minting + updating fee + updating fee rate", staker_functions.mint(), {'from': staker1})
|
||||
transact_and_log("Last minting + first fee + first fee rate", staker_functions.mint(), {'from': staker2})
|
||||
|
||||
#
|
||||
# Create policy again without pre-committed nodes
|
||||
#
|
||||
policy_id_1 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
policy_id_2 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
policy_id_3 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
number_of_periods = 100
|
||||
value = number_of_periods * rate
|
||||
current_timestamp = testerchain.w3.eth.get_block('latest').timestamp
|
||||
end_timestamp = current_timestamp + (number_of_periods - 1) * one_period
|
||||
transact_and_log("Creating policy (1 node, 100 periods)",
|
||||
policy_functions.createPolicy(policy_id_1, alice2, end_timestamp, [staker2]),
|
||||
{'from': alice1, 'value': value})
|
||||
testerchain.time_travel(periods=1)
|
||||
current_timestamp = testerchain.w3.eth.get_block('latest').timestamp
|
||||
end_timestamp = current_timestamp + (number_of_periods - 1) * one_period
|
||||
transact_and_log("Creating policy (1 node, 100 periods), next period",
|
||||
policy_functions.createPolicy(policy_id_2, alice2, end_timestamp, [staker2]),
|
||||
{'from': alice1, 'value': value})
|
||||
transact_and_log("Creating policy (1 node, 100 periods), another node",
|
||||
policy_functions.createPolicy(policy_id_3, alice2, end_timestamp, [staker1]),
|
||||
{'from': alice1, 'value': value})
|
||||
|
||||
#
|
||||
# Mint and revoke policy
|
||||
#
|
||||
testerchain.time_travel(periods=10)
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker3})
|
||||
|
||||
testerchain.time_travel(periods=2)
|
||||
transact(staker_functions.mint(), {'from': staker3})
|
||||
transact_and_log("Last minting after downtime + updating fee",
|
||||
staker_functions.mint(),
|
||||
{'from': staker1})
|
||||
|
||||
testerchain.time_travel(periods=10)
|
||||
transact_and_log("Revoking policy after downtime, 1st policy",
|
||||
policy_functions.revokePolicy(policy_id_1),
|
||||
{'from': alice2})
|
||||
transact_and_log("Revoking policy after downtime, 2nd policy",
|
||||
policy_functions.revokePolicy(policy_id_2),
|
||||
{'from': alice2})
|
||||
transact_and_log("Revoking policy after downtime, 3rd policy",
|
||||
policy_functions.revokePolicy(policy_id_3),
|
||||
{'from': alice2})
|
||||
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker2})
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker3})
|
||||
testerchain.time_travel(periods=1)
|
||||
#
|
||||
# Batch granting
|
||||
#
|
||||
policy_id_1 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
policy_id_2 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
current_timestamp = testerchain.w3.eth.get_block('latest').timestamp
|
||||
end_timestamp = current_timestamp + (number_of_periods - 1) * one_period
|
||||
value = 3 * number_of_periods * rate
|
||||
transact_and_log("Creating 2 policies (3 nodes, 100 periods, pre-committed)",
|
||||
policy_functions.createPolicies([policy_id_1, policy_id_2],
|
||||
alice1,
|
||||
end_timestamp,
|
||||
[staker1, staker2, staker3]),
|
||||
{'from': alice1, 'value': 2 * value})
|
||||
|
||||
for index in range(4):
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.mint(), {'from': staker1})
|
||||
|
||||
#
|
||||
# Check regular deposit
|
||||
#
|
||||
transact_and_log("Deposit tokens to new sub-stake",
|
||||
staker_functions.deposit(staker1, MIN_ALLOWED_LOCKED, LOCKED_PERIODS),
|
||||
{'from': staker1})
|
||||
transact_and_log("Deposit tokens using existing sub-stake",
|
||||
staker_functions.depositAndIncrease(0, MIN_ALLOWED_LOCKED),
|
||||
{'from': staker1})
|
||||
|
||||
#
|
||||
# ApproveAndCall
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.mint(), {'from': staker1})
|
||||
|
||||
transact_and_log("ApproveAndCall",
|
||||
token_functions.approveAndCall(staking_agent.contract_address,
|
||||
MIN_ALLOWED_LOCKED * 2,
|
||||
web3.toBytes(LOCKED_PERIODS)),
|
||||
{'from': staker1})
|
||||
|
||||
#
|
||||
# Locking tokens
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
transact_and_log("Locking tokens and creating new sub-stake",
|
||||
staker_functions.lockAndCreate(MIN_ALLOWED_LOCKED, LOCKED_PERIODS),
|
||||
{'from': staker1})
|
||||
transact_and_log("Locking tokens using existing sub-stake",
|
||||
staker_functions.lockAndIncrease(0, MIN_ALLOWED_LOCKED),
|
||||
{'from': staker1})
|
||||
|
||||
#
|
||||
# Divide stake
|
||||
#
|
||||
transact_and_log("Divide stake", staker_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2), {'from': staker1})
|
||||
transact(staker_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2), {'from': staker1})
|
||||
|
||||
#
|
||||
# Divide almost finished stake
|
||||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
for index in range(18):
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
transact(staker_functions.lockAndCreate(MIN_ALLOWED_LOCKED, LOCKED_PERIODS), {'from': staker1})
|
||||
deposit = staker_functions.stakerInfo(staker1).call()[0]
|
||||
unlocked = deposit - staker_functions.getLockedTokens(staker1, 1).call()
|
||||
transact(staker_functions.withdraw(unlocked), {'from': staker1})
|
||||
|
||||
transact_and_log("Prolong stake", staker_functions.prolongStake(0, 20), {'from': staker1})
|
||||
transact_and_log("Merge sub-stakes", staker_functions.mergeStake(2, 3), {'from': staker1})
|
||||
|
||||
# Large number of sub-stakes
|
||||
number_of_sub_stakes = 24
|
||||
transact(token_functions.approve(staking_agent.contract_address, 0), {'from': origin})
|
||||
transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * number_of_sub_stakes),
|
||||
{'from': origin})
|
||||
for i in range(number_of_sub_stakes):
|
||||
transact(staker_functions.deposit(staker4, MIN_ALLOWED_LOCKED, LOCKED_PERIODS),
|
||||
{'from': origin})
|
||||
transact(staker_functions.bondOperator(staker4), {'from': staker4})
|
||||
transact(staker_functions.setWindDown(True), {'from': staker4})
|
||||
|
||||
# Used to remove spending for first call in a period for mint and commitToNextPeriod
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
|
||||
transact_and_log(f"Make a commitment ({number_of_sub_stakes} sub-stakes)",
|
||||
staker_functions.commitToNextPeriod(),
|
||||
{'from': staker4})
|
||||
|
||||
testerchain.time_travel(periods=1)
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker4})
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
# Used to remove spending for first call in a period for mint and commitToNextPeriod
|
||||
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
|
||||
|
||||
transact_and_log(f"Make a commitment + mint + re-stake ({number_of_sub_stakes} sub-stakes)",
|
||||
staker_functions.commitToNextPeriod(),
|
||||
{'from': staker4})
|
||||
|
||||
print("********* Estimates of migration *********")
|
||||
|
||||
registry = InMemoryContractRegistry()
|
||||
deployer_power = TransactingPower(signer=Web3Signer(testerchain.client),
|
||||
account=testerchain.etherbase_account)
|
||||
|
||||
def deploy_contract(contract_name, *args, **kwargs):
|
||||
return testerchain.deploy_contract(deployer_power,
|
||||
registry,
|
||||
contract_name,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
token_economics = Economics(genesis_hours_per_period=Economics._default_hours_per_period,
|
||||
hours_per_period=2 * Economics._default_hours_per_period)
|
||||
|
||||
token, _ = deploy_contract('NuCypherToken', _totalSupplyOfTokens=token_economics.erc20_total_supply)
|
||||
# Deploy Adjudicator mock
|
||||
adjudicator, _ = deploy_contract('AdjudicatorForStakingEscrowMock', token_economics.reward_coefficient)
|
||||
|
||||
# Deploy old StakingEscrow contract
|
||||
deploy_args = token_economics.pre_application_deployment_parameters
|
||||
deploy_args = (deploy_args[0], *deploy_args[2:])
|
||||
escrow_old_library, _ = deploy_contract(
|
||||
'StakingEscrowOld',
|
||||
token.address,
|
||||
*deploy_args,
|
||||
False # testContract
|
||||
)
|
||||
escrow_dispatcher, _ = deploy_contract('Dispatcher', escrow_old_library.address)
|
||||
|
||||
escrow = testerchain.client.get_contract(
|
||||
abi=escrow_old_library.abi,
|
||||
address=escrow_dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
|
||||
# Deploy old PolicyManager contract
|
||||
policy_manager_old_library, _ = deploy_contract(contract_name='PolicyManagerOld', _escrow=escrow.address)
|
||||
policy_manager_dispatcher, _ = deploy_contract('Dispatcher', policy_manager_old_library.address)
|
||||
|
||||
policy_manager = testerchain.client.get_contract(
|
||||
abi=policy_manager_old_library.abi,
|
||||
address=policy_manager_dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
|
||||
tx = adjudicator.functions.setStakingEscrow(escrow.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.setPolicyManager(policy_manager.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.setAdjudicator(adjudicator.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Initialize Escrow contract
|
||||
tx = token.functions.approve(escrow.address, token_economics.erc20_reward_supply).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.initialize(token_economics.erc20_reward_supply, testerchain.etherbase_account).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Prepare stakers
|
||||
stakers = (staker1, staker2, staker3, staker4)
|
||||
for staker in stakers:
|
||||
max_stake_size = token_economics.maximum_allowed_locked
|
||||
tx = token.functions.transfer(staker, max_stake_size).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token.functions.approve(escrow.address, max_stake_size).transact({'from': staker})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_1 = 2
|
||||
duration = token_economics.min_operator_seconds
|
||||
stake_size = token_economics.min_authorization
|
||||
for staker in (staker1, staker3):
|
||||
for i in range(1, sub_stakes_1 + 1):
|
||||
tx = escrow.functions.deposit(staker, stake_size, duration * i).transact({'from': staker})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
sub_stakes_2 = 24
|
||||
for staker in (staker2, staker4):
|
||||
for i in range(1, sub_stakes_2 + 1):
|
||||
tx = escrow.functions.deposit(staker, stake_size, duration * i).transact({'from': staker})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
for staker in stakers:
|
||||
tx = escrow.functions.bondOperator(staker).transact({'from': staker})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
for i in range(duration):
|
||||
tx = escrow.functions.commitToNextPeriod().transact({'from': staker1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.commitToNextPeriod().transact({'from': staker3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
if i % 2 == 0:
|
||||
tx = escrow.functions.commitToNextPeriod().transact({'from': staker2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.commitToNextPeriod().transact({'from': staker4})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(periods=1, periods_base=token_economics.genesis_seconds_per_period)
|
||||
|
||||
##########
|
||||
# Deploy new version of contracts
|
||||
##########
|
||||
deploy_args = token_economics.pre_application_deployment_parameters
|
||||
escrow_library, _ = deploy_contract(
|
||||
'StakingEscrow',
|
||||
token.address,
|
||||
policy_manager.address,
|
||||
adjudicator.address,
|
||||
NULL_ADDRESS,
|
||||
*deploy_args)
|
||||
escrow = testerchain.client.get_contract(
|
||||
abi=escrow_library.abi,
|
||||
address=escrow_dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
|
||||
policy_manager_library, _ = deploy_contract(contract_name='PolicyManager',
|
||||
_escrowDispatcher=escrow.address,
|
||||
_escrowImplementation=escrow_library.address)
|
||||
|
||||
tx = escrow_dispatcher.functions.upgrade(escrow_library.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = policy_manager_dispatcher.functions.upgrade(policy_manager_library.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
for staker in (staker1, staker2):
|
||||
downtime_length = escrow.functions.getPastDowntimeLength(staker).call()
|
||||
sub_stakes_length = escrow.functions.getSubStakesLength(staker).call()
|
||||
transact_and_log(f"Migrate with {sub_stakes_length} sub-stakes and {downtime_length} downtimes",
|
||||
escrow.functions.migrate(staker),
|
||||
{'from': staker})
|
||||
downtime_length = escrow.functions.getPastDowntimeLength(staker).call()
|
||||
sub_stakes_length = escrow.functions.getSubStakesLength(staker).call()
|
||||
transact_and_log(f"Commit after migration with {sub_stakes_length} sub-stakes and {downtime_length} downtimes",
|
||||
escrow.functions.commitToNextPeriod(),
|
||||
{'from': staker})
|
||||
|
||||
for staker in (staker3, staker4):
|
||||
downtime_length = escrow.functions.getPastDowntimeLength(staker).call()
|
||||
sub_stakes_length = escrow.functions.getSubStakesLength(staker).call()
|
||||
transact_and_log(
|
||||
f"Commit together with migration with {sub_stakes_length} sub-stakes and {downtime_length} downtimes",
|
||||
escrow.functions.commitToNextPeriod(),
|
||||
{'from': staker})
|
||||
|
||||
transact_and_log(f"Dummy migrate call",
|
||||
escrow.functions.migrate(staker1),
|
||||
{'from': staker1})
|
||||
|
||||
print("********* All Done! *********")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting Up...")
|
||||
analyzer = AnalyzeGas()
|
||||
estimate_gas(analyzer=analyzer)
|
||||
analyzer.to_json_file()
|
|
@ -1,66 +0,0 @@
|
|||
|
||||
|
||||
|
||||
from decimal import Decimal, localcontext
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher.blockchain.economics import Economics
|
||||
|
||||
|
||||
@pytest.mark.skip('remove me')
|
||||
def test_rough_economics():
|
||||
"""
|
||||
Formula for staking in one period:
|
||||
(totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / d / k2
|
||||
|
||||
d - Coefficient which modifies the rate at which the maximum issuance decays
|
||||
k1 - Numerator of the locking duration coefficient
|
||||
k2 - Denominator of the locking duration coefficient
|
||||
|
||||
if allLockedPeriods > awarded_periods then allLockedPeriods = awarded_periods
|
||||
kappa * log(2) / halving_delay === (k1 + allLockedPeriods) / d / k2
|
||||
|
||||
kappa = small_stake_multiplier + (1 - small_stake_multiplier) * min(T, T1) / T1
|
||||
where allLockedPeriods == min(T, T1)
|
||||
"""
|
||||
|
||||
e = Economics(initial_supply=int(1e9),
|
||||
first_phase_supply=1829579800,
|
||||
first_phase_duration=5,
|
||||
decay_half_life=2,
|
||||
reward_saturation=1,
|
||||
small_stake_multiplier=Decimal(0.5))
|
||||
|
||||
assert float(round(e.erc20_total_supply / Decimal(1e9), 2)) == 3.89 # As per economics paper
|
||||
|
||||
# Check that we have correct numbers in day 1 of the second phase
|
||||
one_year_in_periods = Decimal(365 / 7)
|
||||
initial_rate = (e.erc20_total_supply - int(e.first_phase_total_supply)) \
|
||||
* (e.lock_duration_coefficient_1 + one_year_in_periods) \
|
||||
/ (e.issuance_decay_coefficient * e.lock_duration_coefficient_2)
|
||||
assert int(initial_rate) == int(e.first_phase_max_issuance)
|
||||
|
||||
initial_rate_small = (e.erc20_total_supply - int(e.first_phase_total_supply))\
|
||||
* e.lock_duration_coefficient_1 \
|
||||
/ (e.issuance_decay_coefficient * e.lock_duration_coefficient_2)
|
||||
assert int(initial_rate_small) == int(initial_rate / 2)
|
||||
|
||||
# Sanity check that total and reward supply calculated correctly
|
||||
assert int(LOG2 / (e.token_halving * one_year_in_periods) * (e.erc20_total_supply - int(e.first_phase_total_supply))) == int(initial_rate)
|
||||
assert int(e.reward_supply) == int(e.erc20_total_supply - Decimal(int(1e9)))
|
||||
|
||||
with localcontext() as ctx: # TODO: Needs follow up - why the sudden failure (python 3.8.0)?
|
||||
ctx.prec = 18 # Perform a high precision calculation
|
||||
# Sanity check for lock_duration_coefficient_1 (k1), issuance_decay_coefficient (d) and lock_duration_coefficient_2 (k2)
|
||||
expected = e.lock_duration_coefficient_1 * e.token_halving
|
||||
result = e.issuance_decay_coefficient * e.lock_duration_coefficient_2 * LOG2 * e.small_stake_multiplier / one_year_in_periods
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_economic_parameter_aliases():
|
||||
e = Economics()
|
||||
deployment_params = e.pre_application_deployment_parameters
|
||||
assert isinstance(deployment_params, tuple)
|
||||
for parameter in deployment_params:
|
||||
assert isinstance(parameter, int)
|
|
@ -1,51 +0,0 @@
|
|||
|
||||
import maya
|
||||
from eth_typing import BlockNumber
|
||||
from unittest.mock import patch
|
||||
|
||||
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS
|
||||
from nucypher.blockchain.eth.utils import epoch_to_period, estimate_block_number_for_period, period_to_epoch
|
||||
|
||||
SECONDS_PER_PERIOD = 60 * 60 * 24
|
||||
|
||||
|
||||
def test_epoch_to_period():
|
||||
timestamp = maya.now().epoch
|
||||
|
||||
current_period = epoch_to_period(epoch=timestamp, seconds_per_period=SECONDS_PER_PERIOD)
|
||||
assert current_period == (timestamp // SECONDS_PER_PERIOD)
|
||||
|
||||
|
||||
def test_period_to_epoch():
|
||||
current_period = 12345678
|
||||
epoch = period_to_epoch(period=current_period, seconds_per_period=SECONDS_PER_PERIOD)
|
||||
assert epoch == (current_period * SECONDS_PER_PERIOD)
|
||||
|
||||
|
||||
def test_estimate_block_number_for_period():
|
||||
timestamp = maya.now().epoch
|
||||
period = timestamp // SECONDS_PER_PERIOD
|
||||
|
||||
three_periods_back = period - 3
|
||||
ten_periods_back = period - 10
|
||||
latest_block_number = BlockNumber(12345678)
|
||||
|
||||
now = maya.now()
|
||||
now_epoch = now.epoch
|
||||
# ensure the same time is used in method and in test
|
||||
with patch.object(maya, 'now', return_value=maya.MayaDT(epoch=now_epoch)):
|
||||
block_number_for_three_periods_back = estimate_block_number_for_period(period=three_periods_back,
|
||||
seconds_per_period=SECONDS_PER_PERIOD,
|
||||
latest_block=latest_block_number)
|
||||
block_number_for_ten_periods_back = estimate_block_number_for_period(period=ten_periods_back,
|
||||
seconds_per_period=SECONDS_PER_PERIOD,
|
||||
latest_block=latest_block_number)
|
||||
|
||||
for past_period, block_number_for_past_period in ((three_periods_back, block_number_for_three_periods_back),
|
||||
(ten_periods_back, block_number_for_ten_periods_back)):
|
||||
start_of_past_period = maya.MayaDT(epoch=(past_period * SECONDS_PER_PERIOD))
|
||||
diff_in_seconds = int((now - start_of_past_period).total_seconds())
|
||||
diff_in_blocks = diff_in_seconds // AVERAGE_BLOCK_TIME_IN_SECONDS
|
||||
|
||||
assert block_number_for_past_period < latest_block_number
|
||||
assert block_number_for_past_period == (latest_block_number - diff_in_blocks)
|
|
@ -1,18 +1,15 @@
|
|||
|
||||
from unittest.mock import MagicMock, Mock
|
||||
|
||||
import pytest
|
||||
import pytest_twisted
|
||||
from twisted.internet import task
|
||||
from twisted.internet import threads
|
||||
from twisted.internet import task, threads
|
||||
from twisted.internet.task import Clock
|
||||
from twisted.logger import globalLogPublisher, LogLevel
|
||||
from twisted.logger import LogLevel, globalLogPublisher
|
||||
|
||||
from nucypher.blockchain.eth.clients import EthereumClient
|
||||
from nucypher.blockchain.eth.token import WorkTracker, WorkTrackerBase
|
||||
from nucypher.utilities.gas_strategies import GasStrategyError
|
||||
|
||||
from nucypher.blockchain.eth.token import WorkTrackerBase, WorkTracker
|
||||
from nucypher.utilities.logging import Logger, GlobalLoggerSettings
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings, Logger
|
||||
|
||||
logger = Logger("test-logging")
|
||||
|
||||
|
@ -41,14 +38,6 @@ class WorkTrackerArbitraryFailureConditions(WorkTrackerBase):
|
|||
self.workdone += 1
|
||||
self._consecutive_fails = 0
|
||||
|
||||
@property
|
||||
def staking_agent(self):
|
||||
class MockStakingAgent:
|
||||
def get_current_period(self):
|
||||
return 1
|
||||
|
||||
return MockStakingAgent()
|
||||
|
||||
def _crash_gracefully(self, failure=None) -> None:
|
||||
assert 'zomg something went wrong' in failure.getErrorMessage()
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
|
||||
|
||||
|
||||
import os
|
||||
from typing import List, Tuple, Union, Optional
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import maya
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
|
@ -12,10 +9,19 @@ from web3 import Web3
|
|||
|
||||
from nucypher.blockchain.economics import Economics
|
||||
from nucypher.blockchain.eth.actors import ContractAdministrator
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry
|
||||
from nucypher.blockchain.eth.interfaces import (
|
||||
BlockchainDeployerInterface,
|
||||
BlockchainInterfaceFactory,
|
||||
)
|
||||
from nucypher.blockchain.eth.registry import (
|
||||
BaseContractRegistry,
|
||||
InMemoryContractRegistry,
|
||||
)
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.sol.compile.constants import TEST_SOLIDITY_SOURCE_ROOT, SOLIDITY_SOURCE_ROOT
|
||||
from nucypher.blockchain.eth.sol.compile.constants import (
|
||||
SOLIDITY_SOURCE_ROOT,
|
||||
TEST_SOLIDITY_SOURCE_ROOT,
|
||||
)
|
||||
from nucypher.blockchain.eth.sol.compile.types import SourceBundle
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
|
@ -28,7 +34,7 @@ from tests.constants import (
|
|||
NUMBER_OF_ETH_TEST_ACCOUNTS,
|
||||
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS,
|
||||
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS,
|
||||
PYEVM_DEV_URI, TEST_GAS_LIMIT
|
||||
PYEVM_DEV_URI,
|
||||
)
|
||||
|
||||
|
||||
|
@ -177,7 +183,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
|
|||
|
||||
more_than_one_arg = sum(map(bool, (hours, seconds))) > 1
|
||||
if more_than_one_arg:
|
||||
raise ValueError("Specify hours, seconds, or periods, not a combination")
|
||||
raise ValueError("Specify hours or seconds not a combination")
|
||||
|
||||
if hours:
|
||||
duration = hours * (60*60)
|
||||
|
|
Loading…
Reference in New Issue