mirror of https://github.com/nucypher/nucypher.git
Deprecates TestEconomics; Adds worklock supply and funding management - Extracts statistical methods to worklock agent.
parent
b2bf0f487d
commit
355d7ff887
|
@ -207,12 +207,28 @@ class BaseEconomics:
|
|||
|
||||
@property
|
||||
def worklock_deployment_parameters(self):
|
||||
"""
|
||||
0 token - Token contract
|
||||
1 escrow - Escrow contract
|
||||
2 router - Router contract
|
||||
...
|
||||
3 startBidDate - Timestamp when bidding starts
|
||||
4 endBidDate - Timestamp when bidding will end
|
||||
5 boostingRefund - Coefficient to boost refund ETH
|
||||
6 lockingDuration - Duration of tokens locking
|
||||
"""
|
||||
|
||||
deployment_parameters = [self.bidding_start_date,
|
||||
self.bidding_end_date,
|
||||
self.worklock_boosting_refund_rate,
|
||||
self.worklock_commitment_duration]
|
||||
return tuple(map(int, deployment_parameters))
|
||||
|
||||
@property
|
||||
def bidding_duration(self) -> int:
|
||||
"""Returns the total bidding window duration in seconds."""
|
||||
return self.bidding_end_date - self.bidding_start_date
|
||||
|
||||
|
||||
class StandardTokenEconomics(BaseEconomics):
|
||||
"""
|
||||
|
@ -335,68 +351,6 @@ class StandardTokenEconomics(BaseEconomics):
|
|||
return self.cumulative_rewards_at_period(period) - self.cumulative_rewards_at_period(period-1)
|
||||
|
||||
|
||||
class TestEconomics(StandardTokenEconomics):
|
||||
|
||||
# TODO: Move to fixture as instance of base economics
|
||||
|
||||
nickname = 'test-economics'
|
||||
description = f'Identical to {StandardTokenEconomics.nickname} with Instant-start one-hour worklock.'
|
||||
|
||||
__default_worklock_boosting_refund_rate = 200
|
||||
__default_worklock_supply = NotImplemented
|
||||
|
||||
def __init__(self,
|
||||
worklock_boosting_refund_rate: int = __default_worklock_boosting_refund_rate,
|
||||
worklock_supply: int = __default_worklock_supply,
|
||||
*args, **kwargs):
|
||||
|
||||
#
|
||||
# Injected
|
||||
#
|
||||
|
||||
super().__init__(worklock_boosting_refund_rate=worklock_boosting_refund_rate,
|
||||
worklock_supply=worklock_supply,
|
||||
*args, **kwargs)
|
||||
|
||||
#
|
||||
# Calculated
|
||||
#
|
||||
|
||||
self.worklock_supply = self.maximum_allowed_locked
|
||||
|
||||
@property
|
||||
def worklock_deployment_parameters(self):
|
||||
"""
|
||||
0 token - Token contract
|
||||
1 escrow - Escrow contract
|
||||
2 router - Router contract
|
||||
...
|
||||
3 startBidDate - Timestamp when bidding starts
|
||||
4 endBidDate - Timestamp when bidding will end
|
||||
5 boostingRefund - Coefficient to boost refund ETH
|
||||
6 lockingDuration - Duration of tokens locking
|
||||
"""
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
# from nucypher.utilities.sandbox.constants import TEST_PROVIDER_URI
|
||||
|
||||
# Get current blocktime
|
||||
blockchain = BlockchainInterfaceFactory.get_interface()
|
||||
now = blockchain.w3.eth.getBlock(block_identifier='latest').timestamp
|
||||
|
||||
# Calculate instant start time
|
||||
one_hour_in_seconds = (60 * 60)
|
||||
start_date = now
|
||||
self.bidding_start_date = start_date
|
||||
self.bidding_end_date = start_date + one_hour_in_seconds
|
||||
self.worklock_commitment_duration = 2 * self.minimum_locked_periods
|
||||
|
||||
deployment_parameters = [self.bidding_start_date,
|
||||
self.bidding_end_date,
|
||||
self.worklock_boosting_refund_rate,
|
||||
self.worklock_commitment_duration]
|
||||
return tuple(map(int, deployment_parameters))
|
||||
|
||||
|
||||
class EconomicsFactory:
|
||||
# TODO: Enforce singleton
|
||||
|
||||
|
@ -424,7 +378,7 @@ class EconomicsFactory:
|
|||
# Token
|
||||
total_supply = token_agent.contract.functions.totalSupply().call()
|
||||
reward_supply = staking_agent.contract.functions.getReservedReward().call()
|
||||
# it's not real initial_supply value because used current reward instead of initial
|
||||
# Not the "real" initial_supply value because used current reward instead of initial reward
|
||||
initial_supply = total_supply - reward_supply
|
||||
|
||||
# Staking Escrow
|
||||
|
@ -437,14 +391,12 @@ class EconomicsFactory:
|
|||
|
||||
# Worklock
|
||||
worklock_parameters = worklock_agent.worklock_parameters()
|
||||
worklock_supply = worklock_agent.total_supply()
|
||||
|
||||
# Aggregate (order-sensitive)
|
||||
economics_parameters = (initial_supply,
|
||||
total_supply,
|
||||
*staking_parameters,
|
||||
*slashing_parameters,
|
||||
worklock_supply,
|
||||
*worklock_parameters)
|
||||
|
||||
economics = BaseEconomics(*economics_parameters)
|
||||
|
|
|
@ -16,10 +16,10 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
|
||||
import math
|
||||
import random
|
||||
from typing import Generator, List, Tuple, Union
|
||||
|
||||
import math
|
||||
from constant_sorrow.constants import NO_CONTRACT_AVAILABLE
|
||||
from eth_utils.address import to_checksum_address
|
||||
from twisted.logger import Logger
|
||||
|
@ -39,7 +39,6 @@ from nucypher.blockchain.eth.constants import (
|
|||
from nucypher.blockchain.eth.decorators import validate_checksum_address
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import AllocationRegistry, BaseContractRegistry, IndividualAllocationRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.blockchain.eth.utils import epoch_to_period
|
||||
from nucypher.crypto.api import sha256_digest
|
||||
|
||||
|
@ -614,6 +613,10 @@ class StakingEscrowAgent(EthereumContractAgent):
|
|||
result = missing_confirmations
|
||||
return result
|
||||
|
||||
def get_completed_work(self, allocation_address: str):
|
||||
total_completed_work = self.contract.functions.getCompletedWork(allocation_address).call()
|
||||
return total_completed_work
|
||||
|
||||
|
||||
class PolicyManagerAgent(EthereumContractAgent):
|
||||
|
||||
|
@ -955,20 +958,6 @@ class WorkLockAgent(EthereumContractAgent):
|
|||
|
||||
registry_contract_name = "WorkLock"
|
||||
|
||||
def fund(self, sender_address: str, supply) -> dict:
|
||||
"""Convenience method for funding the contract."""
|
||||
supply = supply.to_nunits()
|
||||
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=self.registry)
|
||||
approve_function = token_agent.contract.functions.approve(self.contract_address, supply)
|
||||
approve_receipt = self.blockchain.send_transaction(contract_function=approve_function,
|
||||
sender_address=sender_address)
|
||||
|
||||
funding_function = self.contract.functions.tokenDeposit(supply)
|
||||
funding_receipt = self.blockchain.send_transaction(contract_function=funding_function,
|
||||
sender_address=sender_address)
|
||||
return funding_receipt
|
||||
|
||||
def bid(self, value: int, sender_address: str) -> dict:
|
||||
"""
|
||||
Bid for NU tokens with ETH.
|
||||
|
@ -984,48 +973,40 @@ class WorkLockAgent(EthereumContractAgent):
|
|||
return current_bid
|
||||
|
||||
def get_allocation_from_bidder(self, bidder_address: str) -> str:
|
||||
preallocation_address = self.contract.functions.workInfo(bidder_address).call()[2]
|
||||
return preallocation_address
|
||||
|
||||
def get_token_supply(self) -> int: # TODO: Needs better name
|
||||
supply = self.contract.functions.tokenSupply().call()
|
||||
return supply
|
||||
|
||||
def cancel_bid(self, sender_address: str) -> dict:
|
||||
"""
|
||||
Cancel bid and refund deposited ETH.
|
||||
"""
|
||||
contract_function = self.contract.functions.cancelBid()
|
||||
receipt = self.blockchain.send_transaction(contract_function=contract_function,
|
||||
sender_address=sender_address)
|
||||
return receipt
|
||||
|
||||
def _make_preallocation_registry(self, bidder_address: str) -> IndividualAllocationRegistry:
|
||||
preallocation_address = self.get_allocation_from_bidder(bidder_address=bidder_address)
|
||||
|
||||
compiler = SolidityCompiler()
|
||||
# compiler.install_compiler()
|
||||
|
||||
compiled_contracts = compiler.compile()
|
||||
all_contracts = compiled_contracts[PreallocationEscrowAgent.registry_contract_name]
|
||||
contract = all_contracts[0] # There is only one version
|
||||
|
||||
allocation_registry = IndividualAllocationRegistry(beneficiary_address=bidder_address,
|
||||
contract_address=preallocation_address,
|
||||
contract_abi=contract.abi)
|
||||
|
||||
return allocation_registry
|
||||
allocation_address = self.contract.functions.workInfo(bidder_address).call()[2]
|
||||
return allocation_address
|
||||
|
||||
def get_bidder_from_allocation(self, allocation_address: str) -> str:
|
||||
bidder = self.contract.functions.depositors(allocation_address).call()
|
||||
return bidder
|
||||
|
||||
@property
|
||||
def lot_value(self) -> int:
|
||||
"""
|
||||
Total number of tokens than can be bid for and awarded in allocation contracts;
|
||||
or the number of NU tokens deposited before the bidding windows begins via tokenDeposit().
|
||||
"""
|
||||
supply = self.contract.functions.tokenSupply().call()
|
||||
return supply
|
||||
|
||||
def cancel_bid(self, bidder_address: str) -> dict:
|
||||
"""
|
||||
Cancel bid and refund deposited ETH.
|
||||
"""
|
||||
contract_function = self.contract.functions.cancelBid()
|
||||
receipt = self.blockchain.send_transaction(contract_function=contract_function,
|
||||
sender_address=bidder_address)
|
||||
return receipt
|
||||
|
||||
def _make_allocation_registry(self, bidder_address: str) -> IndividualAllocationRegistry:
|
||||
preallocation_address = self.get_allocation_from_bidder(bidder_address=bidder_address)
|
||||
allocation_registry = IndividualAllocationRegistry(beneficiary_address=bidder_address,
|
||||
contract_address=preallocation_address)
|
||||
return allocation_registry
|
||||
|
||||
def available_refund(self, bidder_address: str = None, allocation_address: str = None) -> int:
|
||||
# TODO: move up one layer
|
||||
# TODO: make decorator
|
||||
if bidder_address and allocation_address:
|
||||
raise ValueError("Pass bidder address or allocation address, got both.")
|
||||
|
||||
if bidder_address:
|
||||
allocation_address = self.get_allocation_from_bidder(bidder_address=bidder_address)
|
||||
else:
|
||||
|
@ -1033,22 +1014,22 @@ class WorkLockAgent(EthereumContractAgent):
|
|||
|
||||
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
|
||||
|
||||
# TODO: Move to agent method
|
||||
total_completed_work = staking_agent.contracts.functions.getCompletedWork(allocation_address)
|
||||
# Calculate
|
||||
total_completed_work = staking_agent.get_completed_work(allocation_address=allocation_address)
|
||||
refunded_work = self.contract.functions.workInfo(bidder_address).call()[1]
|
||||
completed_work = total_completed_work - refunded_work
|
||||
refund_eth = self.contract.functions.workToETH(completed_work).call()
|
||||
|
||||
return refund_eth
|
||||
|
||||
def claim(self, sender_address: str) -> dict:
|
||||
def claim(self, bidder_address: str) -> dict:
|
||||
"""
|
||||
Claim tokens - will be deposited and locked as stake in the StakingEscrow contract.
|
||||
This function produces a new deployment or PreAllocationEscrow for the claimee.
|
||||
"""
|
||||
contract_function = self.contract.functions.claim()
|
||||
receipt = self.blockchain.send_transaction(contract_function=contract_function,
|
||||
sender_address=sender_address)
|
||||
sender_address=bidder_address)
|
||||
|
||||
return receipt
|
||||
|
||||
|
@ -1062,27 +1043,54 @@ class WorkLockAgent(EthereumContractAgent):
|
|||
sender_address=sender_address)
|
||||
return receipt
|
||||
|
||||
def refund(self, sender_address: str, allocation_address: str) -> dict:
|
||||
def refund(self, beneficiary_address: str, allocation_address: str = None) -> dict:
|
||||
"""
|
||||
Refund ETH for completed work.
|
||||
Refund ETH for completed work to the beneficiary address (must be the owner of the allocation).
|
||||
"""
|
||||
if not allocation_address:
|
||||
allocation_address = self.get_allocation_from_bidder(bidder_address=beneficiary_address)
|
||||
if allocation_address == BlockchainInterface.NULL_ADDRESS:
|
||||
raise ValueError(f"Unable to locate allocation for {beneficiary_address}")
|
||||
contract_function = self.contract.functions.refund(allocation_address)
|
||||
receipt = self.blockchain.send_transaction(contract_function=contract_function, sender_address=sender_address)
|
||||
receipt = self.blockchain.send_transaction(contract_function=contract_function,
|
||||
sender_address=beneficiary_address)
|
||||
return receipt
|
||||
|
||||
def get_remaining_work(self, allocation_address: str) -> int:
|
||||
def get_remaining_work(self, bidder_address: str = None, allocation_address: str = None) -> int:
|
||||
"""
|
||||
Get remaining work periods until full refund for the target address.
|
||||
"""
|
||||
if bidder_address and allocation_address:
|
||||
raise ValueError("Pass bidder address or allocation address, got both.")
|
||||
if bidder_address:
|
||||
allocation_address = self.get_allocation_from_bidder(bidder_address=bidder_address)
|
||||
result = self.contract.functions.getRemainingWork(allocation_address).call()
|
||||
return result
|
||||
|
||||
def get_eth_supply(self):
|
||||
supply = self.blockchain.w3.client.get_balance(self.contract_address)
|
||||
return supply
|
||||
|
||||
def get_refund_rate(self):
|
||||
f = self.contract.functions
|
||||
refund_rate = self.get_deposit_rate() * f.SLOWING_REFUND().call() / f.boostingRefund().call()
|
||||
return refund_rate
|
||||
|
||||
def get_deposit_rate(self):
|
||||
deposit_rate = self.lot_value / self.get_eth_supply()
|
||||
return deposit_rate
|
||||
|
||||
def get_unclaimed_tokens(self):
|
||||
tokens = self.contract.functions.unclaimedTokens().call()
|
||||
return tokens
|
||||
|
||||
def worklock_parameters(self) -> Tuple:
|
||||
parameter_signatures = (
|
||||
'tokenSupply',
|
||||
'startBidDate',
|
||||
'endBidDate',
|
||||
'boostingRefund',
|
||||
'lockingDuration'
|
||||
'lockingDuration',
|
||||
)
|
||||
|
||||
def _call_function_by_name(name: str):
|
||||
|
|
|
@ -94,7 +94,7 @@ class BaseContractDeployer:
|
|||
@property
|
||||
def contract_address(self) -> str:
|
||||
if self._contract is CONTRACT_NOT_DEPLOYED:
|
||||
raise self.ContractNotDeployed
|
||||
raise self.ContractNotDeployed(self.contract_name)
|
||||
address = self._contract.address # type: str
|
||||
return address
|
||||
|
||||
|
@ -173,7 +173,7 @@ class BaseContractDeployer:
|
|||
raise self.ContractDeploymentError(message)
|
||||
return True
|
||||
|
||||
def deploy(self, gas_limit: int, progress, **overrides) -> dict:
|
||||
def deploy(self, gas_limit: int = None, progress: int = None, **overrides) -> dict:
|
||||
"""
|
||||
Provides for the setup, deployment, and initialization of ethereum smart contracts.
|
||||
Emits the configured blockchain network transactions for single contract instance publication.
|
||||
|
@ -933,7 +933,6 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
contract_abi=self.contract.abi)
|
||||
|
||||
def deploy(self,
|
||||
initial_deployment: bool = True,
|
||||
gas_limit: int = None,
|
||||
use_sidekick: bool = False,
|
||||
progress=None) -> dict:
|
||||
|
@ -1069,7 +1068,7 @@ class WorklockDeployer(BaseContractDeployer):
|
|||
|
||||
agency = WorkLockAgent
|
||||
contract_name = agency.registry_contract_name
|
||||
deployment_steps = ('contract_deployment', 'bond_escrow')
|
||||
deployment_steps = ('contract_deployment', 'bond_escrow', 'fund_worklock')
|
||||
|
||||
_upgradeable = False
|
||||
__proxy_deployer = NotImplemented
|
||||
|
@ -1082,7 +1081,10 @@ class WorklockDeployer(BaseContractDeployer):
|
|||
self.interface_agent = ContractAgency.get_agent(PreallocationEscrowAgent.StakingInterfaceAgent,
|
||||
registry=self.registry)
|
||||
|
||||
def deploy(self, initial_deployment: bool = True, gas_limit: int = None, progress=None) -> Dict[str, dict]:
|
||||
def deploy(self,
|
||||
gas_limit: int = None,
|
||||
progress: int = None,
|
||||
fund_now: bool = True) -> Dict[str, dict]:
|
||||
self.check_deployment_readiness()
|
||||
|
||||
interface_router = self.blockchain.get_proxy_contract(registry=self.registry,
|
||||
|
@ -1099,17 +1101,41 @@ class WorklockDeployer(BaseContractDeployer):
|
|||
self.contract_name,
|
||||
*constructor_args,
|
||||
gas_limit=gas_limit)
|
||||
|
||||
self._contract = worklock_contract
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
bonding_function = self.staking_agent.contract.functions.setWorkLock(worklock_contract.address)
|
||||
bonding_receipt = self.blockchain.send_transaction(sender_address=self.deployer_address,
|
||||
contract_function=bonding_function)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
funding_receipt = self.fund(sender_address=self.deployer_address)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
# Gather the transaction hashes
|
||||
self.deployment_transactions = {'contract_deployment': deploy_txhash, 'bond_escrow': bonding_receipt}
|
||||
self._contract = worklock_contract
|
||||
self.deployment_transactions = {'contract_deployment': deploy_txhash,
|
||||
'bond_escrow': bonding_receipt,
|
||||
'fund_worklock': funding_receipt}
|
||||
return self.deployment_transactions
|
||||
|
||||
def fund(self, sender_address: str) -> dict:
|
||||
"""
|
||||
Convenience method for funding the contract and establishing the
|
||||
total worklock lot value to be auctioned.
|
||||
"""
|
||||
|
||||
supply = self.economics.worklock_supply.to_nunits()
|
||||
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=self.registry)
|
||||
approve_function = token_agent.contract.functions.approve(self.contract_address, supply)
|
||||
approve_receipt = self.blockchain.send_transaction(contract_function=approve_function,
|
||||
sender_address=sender_address)
|
||||
|
||||
funding_function = self.contract.functions.tokenDeposit(supply)
|
||||
funding_receipt = self.blockchain.send_transaction(contract_function=funding_function,
|
||||
sender_address=sender_address)
|
||||
return funding_receipt
|
||||
|
|
|
@ -729,13 +729,6 @@ def echo_solidity_version(ctx, param, value):
|
|||
|
||||
|
||||
def paint_worklock_status(emitter, registry: BaseContractRegistry):
|
||||
"""
|
||||
# TODO: move some of these to agent?
|
||||
* depositRate = tokenSupply / ethSupply
|
||||
* claimedTokens = depositedETH * depositRate
|
||||
* refundRate = depositRate * SLOWING_REFUND / boostingRefund
|
||||
* refundETH = completedWork / refundRate
|
||||
"""
|
||||
from maya import MayaDT
|
||||
WORKLOCK_AGENT = ContractAgency.get_agent(WorkLockAgent, registry=registry)
|
||||
blockchain = WORKLOCK_AGENT.blockchain
|
||||
|
@ -763,16 +756,28 @@ Time Remaining .... {remaining}
|
|||
Economics
|
||||
======================================================
|
||||
Boosting Refund .... {WORKLOCK_AGENT.contract.functions.boostingRefund().call()}
|
||||
Boosting Refund .... {WORKLOCK_AGENT.contract.functions.boostingRefund().call()}
|
||||
|
||||
Slowing Refund .... {WORKLOCK_AGENT.contract.functions.slowingRefund().call()}
|
||||
Refund Rate ....... {WORKLOCK_AGENT.get_refund_rate()}
|
||||
Deposit Rate ...... {WORKLOCK_AGENT.get_deposit_rate()}
|
||||
|
||||
Total Bids ......... {blockchain.client.get_balance(WORKLOCK_AGENT.contract_address)}
|
||||
Unclaimed Tokens ... {WORKLOCK_AGENT.contract.functions.unclaimedTokens().call()}
|
||||
Claimed Tokens ..... {WORKLOCK_AGENT.get_claimed_tokens()}
|
||||
Unclaimed Tokens ... {WORKLOCK_AGENT.get_unclaimed_tokens()}
|
||||
"""
|
||||
emitter.message(payload)
|
||||
return
|
||||
|
||||
|
||||
def paint_worklock_participant_status(emitter):
|
||||
message = f"""
|
||||
Allocations
|
||||
=====================================================
|
||||
|
||||
"""
|
||||
emitter.message(message)
|
||||
return
|
||||
|
||||
|
||||
def paint_worklock_participant_notice(emitter, bidder_address, registry):
|
||||
worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=registry)
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
|||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, LocalContractRegistry
|
||||
from nucypher.characters.banners import WORKLOCK_BANNER
|
||||
from nucypher.cli.actions import select_client_account
|
||||
from nucypher.cli.config import nucypher_click_config
|
||||
from nucypher.cli.painting import paint_receipt_summary, paint_worklock_status, paint_worklock_participant_notice
|
||||
from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS, WEI, EXISTING_READABLE_FILE
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU Affero General Public License for more details.
|
|||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import requests
|
||||
import socket
|
||||
|
||||
|
|
|
@ -3,12 +3,7 @@ import rlp
|
|||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import to_canonical_address, keccak, to_checksum_address
|
||||
|
||||
from nucypher.blockchain.eth.agents import WorkLockAgent, ContractAgency, NucypherTokenAgent
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
||||
DEPOSIT_RATE = 100
|
||||
from nucypher.blockchain.eth.agents import WorkLockAgent, ContractAgency
|
||||
|
||||
|
||||
def next_address(testerchain, worklock):
|
||||
|
@ -25,101 +20,78 @@ def test_create_worklock_agent(testerchain, test_registry, agency, token_economi
|
|||
assert agent == same_agent
|
||||
|
||||
|
||||
def test_funding_worklock_contract(testerchain, agency, test_registry, token_economics):
|
||||
transacting_power = TransactingPower(account=testerchain.etherbase_account, password=INSECURE_DEVELOPMENT_PASSWORD)
|
||||
transacting_power.activate()
|
||||
|
||||
worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
|
||||
# WorkLock contract is unfunded.
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
|
||||
assert token_agent.get_balance(worklock_agent.contract_address) == 0
|
||||
|
||||
# Funding account has enough tokens to fund the contract.
|
||||
worklock_supply = NU.from_nunits(2 * token_economics.maximum_allowed_locked - 1)
|
||||
assert token_agent.get_balance(testerchain.etherbase_account) > token_economics.maximum_allowed_locked
|
||||
|
||||
# Fund.
|
||||
receipt = worklock_agent.fund(sender_address=testerchain.etherbase_account,
|
||||
supply=worklock_supply)
|
||||
assert receipt['status'] == 1
|
||||
|
||||
|
||||
def test_bidding(testerchain, agency, token_economics, test_registry):
|
||||
maximum_deposit_eth = token_economics.maximum_allowed_locked // DEPOSIT_RATE
|
||||
minimum_deposit_eth = token_economics.minimum_allowed_locked // DEPOSIT_RATE
|
||||
big_bid = token_economics.maximum_allowed_locked // 100
|
||||
small_bid = token_economics.minimum_allowed_locked // 100
|
||||
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
|
||||
# Round 1
|
||||
for multiplier, bidder in enumerate(testerchain.unassigned_accounts[:3], start=1):
|
||||
bid = minimum_deposit_eth * multiplier
|
||||
bid = big_bid * multiplier
|
||||
receipt = agent.bid(sender_address=bidder, value=bid)
|
||||
assert receipt['status'] == 1
|
||||
|
||||
# Round 2
|
||||
for multiplier, bidder in enumerate(testerchain.unassigned_accounts[:3], start=1):
|
||||
bid = (minimum_deposit_eth * 2) * multiplier
|
||||
bid = (small_bid * 2) * multiplier
|
||||
receipt = agent.bid(sender_address=bidder, value=bid)
|
||||
assert receipt['status'] == 1
|
||||
|
||||
big_bidder = testerchain.unassigned_accounts[-1]
|
||||
bid_wei = maximum_deposit_eth - 1
|
||||
receipt = agent.bid(sender_address=big_bidder, value=bid_wei)
|
||||
assert receipt['status'] == 1
|
||||
|
||||
|
||||
def test_get_bid(testerchain, agency, token_economics, test_registry):
|
||||
bidder = testerchain.unassigned_accounts[-1]
|
||||
big_bid = token_economics.maximum_allowed_locked // 10
|
||||
big_bidder = testerchain.unassigned_accounts[-1]
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
bid = agent.get_bid(bidder)
|
||||
assert bid == 39999999999999999999999
|
||||
receipt = agent.bid(sender_address=big_bidder, value=big_bid)
|
||||
assert receipt['status'] == 1
|
||||
bid = agent.get_bid(big_bidder)
|
||||
assert bid == big_bid
|
||||
|
||||
|
||||
def test_cancel_bid(testerchain, agency, token_economics, test_registry):
|
||||
bidder = testerchain.unassigned_accounts[0]
|
||||
bidder = testerchain.unassigned_accounts[1]
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
receipt = agent.cancel_bid(bidder)
|
||||
assert receipt['status'] == 1
|
||||
|
||||
# Can't cancel twice in a row
|
||||
# Can't cancel a bid twice in a row
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
_receipt = agent.cancel_bid(bidder)
|
||||
|
||||
|
||||
def test_get_remaining_work_before_bidding_ends(testerchain, agency, token_economics, test_registry):
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
preallocation_address = next_address(testerchain, agent.contract)
|
||||
remaining = agent.get_remaining_work(allocation_address=preallocation_address)
|
||||
bidder = testerchain.unassigned_accounts[0]
|
||||
remaining = agent.get_remaining_work(bidder_address=bidder)
|
||||
assert remaining == 0
|
||||
|
||||
|
||||
def test_early_claim(testerchain, agency, token_economics, test_registry):
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
bidder = testerchain.unassigned_accounts[-1]
|
||||
bidder = testerchain.unassigned_accounts[0]
|
||||
with pytest.raises(TransactionFailed):
|
||||
receipt = agent.claim(sender_address=bidder)
|
||||
receipt = agent.claim(bidder_address=bidder)
|
||||
assert receipt
|
||||
|
||||
|
||||
def test_refund_before_bidding_ends(testerchain, agency, token_economics, test_registry):
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
bidder = testerchain.unassigned_accounts[-1]
|
||||
allocation_address = next_address(testerchain, agent.contract)
|
||||
with pytest.raises(TransactionFailed):
|
||||
_receipt = agent.refund(sender_address=bidder, allocation_address=allocation_address)
|
||||
|
||||
|
||||
def test_successful_claim(testerchain, agency, token_economics, test_registry):
|
||||
# Wait exactly 1 hour + 1 second
|
||||
testerchain.time_travel(seconds=(60*60)+1)
|
||||
|
||||
# Wait until the bidding window closes...
|
||||
testerchain.time_travel(seconds=token_economics.bidding_duration+1)
|
||||
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
bidder = testerchain.unassigned_accounts[-1]
|
||||
receipt = agent.claim(sender_address=bidder)
|
||||
bidder = testerchain.unassigned_accounts[0]
|
||||
receipt = agent.claim(bidder_address=bidder)
|
||||
assert receipt
|
||||
|
||||
# Cant claim more than once
|
||||
with pytest.raises(TransactionFailed):
|
||||
_receipt = agent.claim(sender_address=bidder)
|
||||
_receipt = agent.claim(bidder_address=bidder)
|
||||
|
||||
|
||||
def test_lookup_bidders_and_allocations(testerchain, agency, token_economics, test_registry):
|
||||
bidder = testerchain.unassigned_accounts[0]
|
||||
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
|
||||
allocation_address = agent.get_allocation_from_bidder(bidder)
|
||||
recovered_bidder = agent.get_bidder_from_allocation(allocation_address)
|
||||
assert recovered_bidder == bidder
|
||||
|
|
|
@ -19,11 +19,12 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
import pytest
|
||||
from eth_utils import keccak
|
||||
|
||||
from nucypher.blockchain.eth.agents import WorkLockAgent, ContractAgency
|
||||
from nucypher.blockchain.eth.agents import WorkLockAgent, ContractAgency, NucypherTokenAgent
|
||||
from nucypher.blockchain.eth.constants import WORKLOCK_CONTRACT_NAME
|
||||
from nucypher.blockchain.eth.deployers import WorklockDeployer, StakingInterfaceDeployer, AdjudicatorDeployer
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.utilities.sandbox.constants import STAKING_ESCROW_DEPLOYMENT_SECRET, INSECURE_DEPLOYMENT_SECRET_HASH, \
|
||||
POLICY_MANAGER_DEPLOYMENT_SECRET
|
||||
POLICY_MANAGER_DEPLOYMENT_SECRET, INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
@ -54,7 +55,7 @@ def test_worklock_deployment(worklock_deployer, staking_escrow_deployer, deploym
|
|||
|
||||
# deployment steps must match expected number of steps
|
||||
steps = worklock_deployer.deployment_steps
|
||||
assert deployment_progress.num_steps == len(steps) == len(deployment_receipts) == 2
|
||||
assert deployment_progress.num_steps == len(steps) == len(deployment_receipts) == 3
|
||||
|
||||
# Ensure every step is successful
|
||||
for step_title in steps:
|
||||
|
|
|
@ -33,7 +33,7 @@ from umbral.keys import UmbralPrivateKey
|
|||
from umbral.signing import Signer
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.economics import TestEconomics
|
||||
from nucypher.blockchain.economics import StandardTokenEconomics
|
||||
from nucypher.blockchain.eth.actors import Staker, StakeHolder
|
||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent
|
||||
from nucypher.blockchain.eth.clients import NuCypherGethDevProcess
|
||||
|
@ -44,7 +44,7 @@ from nucypher.blockchain.eth.deployers import (NucypherTokenDeployer,
|
|||
AdjudicatorDeployer,
|
||||
StakingInterfaceDeployer,
|
||||
WorklockDeployer
|
||||
)
|
||||
)
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.networks import NetworksInventory
|
||||
from nucypher.blockchain.eth.registry import (
|
||||
|
@ -385,9 +385,28 @@ def federated_ursulas(ursula_federated_test_config):
|
|||
# Blockchain
|
||||
#
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def token_economics():
|
||||
economics = TestEconomics()
|
||||
@pytest.fixture(scope='module')
|
||||
def token_economics(testerchain):
|
||||
|
||||
# Get current blocktime
|
||||
blockchain = BlockchainInterfaceFactory.get_interface()
|
||||
now = blockchain.w3.eth.getBlock(block_identifier='latest').timestamp
|
||||
|
||||
# Calculate instant start time
|
||||
one_hour_in_seconds = (60 * 60)
|
||||
start_date = now
|
||||
bidding_start_date = start_date
|
||||
|
||||
# Ends in one hour
|
||||
bidding_end_date = start_date + one_hour_in_seconds
|
||||
|
||||
economics = StandardTokenEconomics(
|
||||
worklock_boosting_refund_rate=200,
|
||||
worklock_commitment_duration=60, # Periods
|
||||
worklock_supply=NU.from_nunits(1_000_000),
|
||||
bidding_start_date=bidding_start_date,
|
||||
bidding_end_date=bidding_end_date
|
||||
)
|
||||
return economics
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue