Updates worklock API to work-based unlock without allocation deployments.

pull/1550/head
Kieran Prasch 2020-01-14 12:18:21 -08:00 committed by Kieran R. Prasch
parent bb55ac05d7
commit bbcb9e649c
7 changed files with 68 additions and 127 deletions

View File

@ -38,7 +38,7 @@ 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.registry import AllocationRegistry, BaseContractRegistry
from nucypher.blockchain.eth.utils import epoch_to_period
from nucypher.crypto.api import sha256_digest
@ -613,8 +613,8 @@ 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()
def get_completed_work(self, bidder_address: str):
total_completed_work = self.contract.functions.getCompletedWork(bidder_address).call()
return total_completed_work
@ -958,28 +958,56 @@ class WorkLockAgent(EthereumContractAgent):
registry_contract_name = "WorkLock"
#
# Transactions
#
def bid(self, value: int, bidder_address: str) -> dict:
"""
Bid for NU tokens with ETH.
"""
"""Bid for NU tokens with ETH."""
contract_function = self.contract.functions.bid()
receipt = self.blockchain.send_transaction(contract_function=contract_function,
sender_address=bidder_address,
receipt = self.blockchain.send_transaction(contract_function=contract_function, sender_address=bidder_address,
payload={'value': value})
return receipt
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 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=bidder_address)
return receipt
def burn_unclaimed(self, sender_address: str) -> dict:
"""
Burn unclaimed tokens - Out of the goodness of your heart...
of course the caller must pay for the transaction gas.
"""
contract_function = self.contract.functions.burnUnclaimed()
receipt = self.blockchain.send_transaction(contract_function=contract_function, sender_address=sender_address)
return receipt
def refund(self, bidder_address: str) -> dict:
"""Refund ETH for completed work."""
contract_function = self.contract.functions.refund()
receipt = self.blockchain.send_transaction(contract_function=contract_function, sender_address=bidder_address)
return receipt
#
# Calls
#
def get_bid(self, checksum_address: str) -> int:
current_bid = self.contract.functions.workInfo(checksum_address).call()[0]
return current_bid
def get_allocation_from_bidder(self, bidder_address: str) -> str:
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:
"""
@ -989,82 +1017,17 @@ class WorkLockAgent(EthereumContractAgent):
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:
if not (bool(bidder_address) ^ bool(allocation_address)):
raise ValueError(f"Pass bidder address or allocation address, got '{bidder_address}' and '{allocation_address}'.")
if bidder_address:
allocation_address = self.get_allocation_from_bidder(bidder_address=bidder_address)
else:
bidder_address = self.get_bidder_from_allocation(allocation_address=allocation_address)
def available_refund(self, bidder_address: str) -> int:
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
# Calculate
total_completed_work = staking_agent.get_completed_work(allocation_address=allocation_address)
total_completed_work = staking_agent.get_completed_work(bidder_address=bidder_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, 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=bidder_address)
return receipt
def burn_unclaimed(self, sender_address: str) -> dict:
"""
Burn unclaimed tokens -
Out of the goodness of your heart - of course the caller must pay for the gas...
"""
contract_function = self.contract.functions.burnUnclaimed()
receipt = self.blockchain.send_transaction(contract_function=contract_function,
sender_address=sender_address)
return receipt
def refund(self, beneficiary_address: str, allocation_address: str = None) -> dict:
"""
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=beneficiary_address)
return receipt
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 not (bool(bidder_address) ^ bool(allocation_address)):
raise ValueError(f"Pass bidder address or allocation address, got '{bidder_address}' and '{allocation_address}'.")
if bidder_address:
allocation_address = self.get_allocation_from_bidder(bidder_address=bidder_address)
result = self.contract.functions.getRemainingWork(allocation_address).call()
def get_remaining_work(self, bidder_address: str) -> int:
"""Get remaining work periods until full refund for the target address."""
result = self.contract.functions.getRemainingWork(bidder_address).call()
return result
def get_eth_supply(self) -> int:
@ -1073,7 +1036,9 @@ class WorkLockAgent(EthereumContractAgent):
def get_refund_rate(self) -> int:
f = self.contract.functions
refund_rate = self.get_deposit_rate() * f.SLOWING_REFUND().call() / f.boostingRefund().call()
slowing_refund = f.SLOWING_REFUND().call()
boosting_refund = f.boostingRefund().call()
refund_rate = self.get_deposit_rate() * slowing_refund / boosting_refund
return refund_rate
def get_deposit_rate(self) -> int:
@ -1093,7 +1058,7 @@ class WorkLockAgent(EthereumContractAgent):
'startBidDate',
'endBidDate',
'boostingRefund',
'lockingDuration',
'stakingPeriods',
)
def _call_function_by_name(name: str):

View File

@ -1094,7 +1094,6 @@ class WorklockDeployer(BaseContractDeployer):
# Deploy
constructor_args = (self.token_agent.contract_address,
self.staking_agent.contract_address,
interface_router.address,
*self.economics.worklock_deployment_parameters)
worklock_contract, receipt = self.blockchain.deploy_contract(self.deployer_address,

View File

@ -19,7 +19,6 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import collections
import os
import pprint
import time
from typing import List
from typing import Tuple
from typing import Union
@ -27,6 +26,7 @@ from urllib.parse import urlparse
import click
import requests
import time
from constant_sorrow.constants import (
NO_BLOCKCHAIN_CONNECTION,
NO_COMPILATION_PERFORMED,
@ -35,7 +35,7 @@ from constant_sorrow.constants import (
READ_ONLY_INTERFACE
)
from eth_tester import EthereumTester
from eth_utils import to_checksum_address, is_checksum_address
from eth_utils import to_checksum_address
from twisted.logger import Logger
from web3 import Web3, WebsocketProvider, HTTPProvider, IPCProvider
from web3.contract import ContractConstructor, Contract
@ -58,7 +58,6 @@ from nucypher.blockchain.eth.providers import (
)
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
from nucypher.characters.control.emitters import StdoutEmitter
Web3Providers = Union[IPCProvider, WebsocketProvider, HTTPProvider, EthereumTester]
@ -588,7 +587,7 @@ class BlockchainDeployerInterface(BlockchainInterface):
deploy_transaction.update({'gas': gas_limit})
pprint_args = ', '.join(list(map(str, constructor_args)) + list(f"{k}={v}" for k, v in constructor_kwargs.items()))
pprint_args = pprint_args.replace("{", "{{").replace("}", "}}") # See #724
pprint_args = pprint_args.replace("{", "{{").replace("}", "}}") # TODO: See #724
contract_factory = self.get_contract_factory(contract_name=contract_name, version=contract_version)
self.log.info(f"Deploying contract {contract_name}:{contract_factory.version} with "

View File

@ -775,7 +775,6 @@ def paint_worklock_participant_status(emitter, registry, bidder_address):
Allocations
=====================================================
Bidder ............... {bidder_address}
Allocation Contract... {WORKLOCK_AGENT.get_allocation_from_bidder(bidder_address)}
Available Refund ..... {WORKLOCK_AGENT.available_refund(bidder_address=bidder_address)}
Remaining Work ....... {WORKLOCK_AGENT.get_remaining_work(bidder_address=bidder_address)}
"""
@ -816,11 +815,10 @@ Accept worklock terms and node operator obligation?"""
return
def paint_worklock_claim(emitter, bidder_address: str, allocation_address: str):
def paint_worklock_claim(emitter, bidder_address: str):
message = f"""
Successfully claimed WorkLock tokens for {bidder_address}.
***New Allocation Contract Deployed at {allocation_address}***
Next Steps for Worklock Winners
===============================
@ -828,7 +826,7 @@ Next Steps for Worklock Winners
See the nucypher official documentation for a comprehensive guide!
Create a stake with your allocation contract:
'nucypher stake create --provider <URI> --staking-address {allocation_address} --beneficiary-address {bidder_address}'
'nucypher stake create --provider <URI> --staking-address {bidder_address}'
Bond a worker to your stake: 'nucypher stake set-worker --worker-address <WORKER ADDRESS>'

View File

@ -38,10 +38,6 @@ option_bidder_address = click.option('--bidder-address',
help="Bidder's checksum address.",
type=EIP55_CHECKSUM_ADDRESS)
option_allocation_address = click.option('--allocation-address',
help="Worklock allocation contract address",
type=EIP55_CHECKSUM_ADDRESS)
def _setup_emitter(general_config):
emitter = general_config.emitter
@ -54,9 +50,8 @@ class WorkLockOptions:
__option_name__ = 'worklock_options'
def __init__(self, bidder_address, allocation_address):
def __init__(self, bidder_address: str):
self.bidder_address = bidder_address
self.allocation_address = allocation_address
def create_agent(self, registry):
agent = ContractAgency.get_agent(WorkLockAgent, registry=registry)
@ -65,8 +60,7 @@ class WorkLockOptions:
group_worklock_options = group_options(
WorkLockOptions,
bidder_address=option_bidder_address,
allocation_address=option_allocation_address)
bidder_address=option_bidder_address)
@click.group()
@ -154,8 +148,7 @@ def claim(general_config, worklock_options, registry_options, force):
worklock_agent = worklock_options.create_agent(registry=registry)
receipt = worklock_agent.claim(bidder_address=worklock_options.bidder_address)
paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=worklock_agent.blockchain.client.chain_name)
allocation_address = worklock_agent.get_allocation_from_bidder(bidder_address=worklock_options.bidder_address)
paint_worklock_claim(emitter, bidder_address=worklock_options.bidder_address, allocation_address=allocation_address)
paint_worklock_claim(emitter, bidder_address=worklock_options.bidder_address)
return # Exit
@ -190,7 +183,7 @@ def refund(general_config, worklock_options, registry_options, force):
emitter.message("Submitting WorkLock refund request...")
registry = registry_options.get_registry(emitter, general_config.debug)
worklock_agent = worklock_options.create_agent(registry=registry)
receipt = worklock_agent.refund(beneficiary_address=worklock_options.bidder_address)
receipt = worklock_agent.refund(bidder_address=worklock_options.bidder_address)
paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=worklock_agent.blockchain.client.chain_name)
return # Exit

View File

@ -59,11 +59,11 @@ def test_cancel_bid(testerchain, agency, token_economics, test_registry):
_receipt = agent.cancel_bid(bidder)
def test_get_remaining_work_before_bidding_ends(testerchain, agency, token_economics, test_registry):
def test_get_remaining_work(testerchain, agency, token_economics, test_registry):
agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
bidder = testerchain.unassigned_accounts[0]
remaining = agent.get_remaining_work(bidder_address=bidder)
assert remaining == 0
assert remaining == 35905203136136849607983
def test_early_claim(testerchain, agency, token_economics, test_registry):
@ -87,11 +87,3 @@ def test_successful_claim(testerchain, agency, token_economics, test_registry):
# Cant claim more than once
with pytest.raises(TransactionFailed):
_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

View File

@ -148,17 +148,12 @@ def test_refund(click_runner, testerchain, agency, test_registry, token_economic
#
worklock_agent = ContractAgency.get_agent(WorkLockAgent, registry=test_registry)
allocation_address = worklock_agent.get_allocation_from_bidder(bidder_address=bidder)
individual_allocation = IndividualAllocationRegistry(beneficiary_address=bidder,
contract_address=allocation_address)
staker = Staker(is_me=True,
registry=test_registry,
individual_allocation=individual_allocation)
staker = Staker(is_me=True, checksum_address=bidder, registry=test_registry)
# Create a new stake with the new allocation
new_stake = staker.initialize_stake(entire_balance=True, lock_periods=30)
assert new_stake
# new_stake = staker.initialize_stake(entire_balance=True, lock_periods=30)
# assert new_stake
# Bond the worker
receipt = staker.set_worker(worker_address=worker_address)
@ -166,7 +161,7 @@ def test_refund(click_runner, testerchain, agency, test_registry, token_economic
worker = Ursula(is_me=True,
registry=test_registry,
checksum_address=allocation_address,
checksum_address=bidder,
worker_address=worker_address,
rest_host=MOCK_IP_ADDRESS,
rest_port=select_test_port())