예제 #1
0
    def __init__(self,
                 is_me: bool,
                 individual_allocation: IndividualAllocationRegistry = None,
                 *args, **kwargs) -> None:

        super().__init__(*args, **kwargs)
        self.log = Logger("staker")

        self.is_me = is_me
        self.__worker_address = None

        # Blockchain
        self.policy_agent = ContractAgency.get_agent(PolicyManagerAgent, registry=self.registry)  # type: PolicyManagerAgent
        self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)  # type: StakingEscrowAgent
        self.economics = TokenEconomicsFactory.get_economics(registry=self.registry)

        # Staking via contract
        self.individual_allocation = individual_allocation
        if self.individual_allocation:
            self.beneficiary_address = individual_allocation.beneficiary_address
            self.checksum_address = individual_allocation.contract_address
            self.preallocation_escrow_agent = PreallocationEscrowAgent(registry=self.registry,
                                                                       allocation_registry=self.individual_allocation,
                                                                       beneficiary=self.beneficiary_address)
        else:
            self.beneficiary_address = None
            self.preallocation_escrow_agent = None

        # Check stakes
        self.stakes = StakeList(registry=self.registry, checksum_address=self.checksum_address)
def agent(testerchain, test_registry, allocation_value, agency,
          mock_transacting_power_activation) -> PreallocationEscrowAgent:
    deployer_address, beneficiary_address, *everybody_else = testerchain.client.accounts

    escrow_deployer = PreallocationEscrowDeployer(deployer_address=deployer_address,
                                                  registry=test_registry,
                                                  allocation_registry=TEST_ALLOCATION_REGISTRY)

    mock_transacting_power_activation(account=deployer_address, password=INSECURE_DEVELOPMENT_PASSWORD)
    _receipt = escrow_deployer.deploy()

    escrow_deployer.initial_deposit(value=allocation_value, duration_seconds=TEST_LOCK_DURATION_IN_SECONDS)
    assert escrow_deployer.contract.functions.getLockedTokens().call() == allocation_value
    escrow_deployer.assign_beneficiary(checksum_address=beneficiary_address)
    escrow_deployer.enroll_principal_contract()
    assert escrow_deployer.contract.functions.getLockedTokens().call() == allocation_value
    agent = escrow_deployer.make_agent()

    direct_agent = PreallocationEscrowAgent(registry=test_registry,
                                            allocation_registry=TEST_ALLOCATION_REGISTRY,
                                            beneficiary=beneficiary_address)

    assert direct_agent == agent
    assert direct_agent.contract.abi == agent.contract.abi
    assert direct_agent.contract.address == agent.contract.address
    assert agent.principal_contract.address == escrow_deployer.contract.address
    assert agent.principal_contract.abi == escrow_deployer.contract.abi
    assert direct_agent.contract.abi == escrow_deployer.contract.abi
    assert direct_agent.contract.address == escrow_deployer.contract.address

    yield agent
    TEST_ALLOCATION_REGISTRY.clear()
예제 #3
0
def preallocation_escrow_agent(beneficiary,
                               agency_local_registry,
                               mock_allocation_registry,
                               test_registry_source_manager,
                               individual_allocation):
    preallocation_escrow_agent = PreallocationEscrowAgent(beneficiary=beneficiary,
                                                          registry=agency_local_registry,
                                                          allocation_registry=individual_allocation)
    return preallocation_escrow_agent
def preallocation_escrow_agent(beneficiary, test_registry,
                               mock_allocation_registry):
    individual_allocation = IndividualAllocationRegistry.from_allocation_file(
        MOCK_INDIVIDUAL_ALLOCATION_FILEPATH)
    preallocation_escrow_agent = PreallocationEscrowAgent(
        beneficiary=beneficiary,
        registry=test_registry,
        allocation_registry=individual_allocation)
    return preallocation_escrow_agent
예제 #5
0
def test_nucypher_deploy_allocation_contracts(click_runner,
                                              testerchain,
                                              registry_filepath,
                                              mock_allocation_infile,
                                              token_economics):

    #
    # Main
    #

    deploy_command = ('allocations',
                      '--registry-infile', registry_filepath,
                      '--allocation-infile', mock_allocation_infile,
                      '--allocation-outfile', MOCK_ALLOCATION_REGISTRY_FILEPATH,
                      '--provider', TEST_PROVIDER_URI,
                      '--poa')

    account_index = '0\n'
    yes = 'Y\n'
    no = 'N\n'
    user_input = account_index + yes + no + yes

    result = click_runner.invoke(deploy,
                                 deploy_command,
                                 input=user_input,
                                 catch_exceptions=False)
    assert result.exit_code == 0
    for allocation_address in testerchain.unassigned_accounts:
        assert allocation_address in result.output

    # ensure that a pre-allocation recipient has the allocated token quantity.
    beneficiary = testerchain.client.accounts[-1]
    allocation_registry = AllocationRegistry(filepath=MOCK_ALLOCATION_REGISTRY_FILEPATH)
    registry = LocalContractRegistry(filepath=registry_filepath)
    preallocation_escrow_agent = PreallocationEscrowAgent(registry=registry,
                                                          beneficiary=beneficiary,
                                                          allocation_registry=allocation_registry)
    assert preallocation_escrow_agent.unvested_tokens == 2 * token_economics.minimum_allowed_locked
예제 #6
0
def paint_deployer_contract_inspection(emitter, registry,
                                       deployer_address) -> None:

    blockchain = BlockchainInterfaceFactory.get_interface()

    sep = '-' * 45
    emitter.echo(sep)

    provider_info = f"""

* Web3 Provider
====================================================================

Provider URI ............. {blockchain.provider_uri}
Registry  ................ {registry.filepath}

* Standard Deployments
=====================================================================
"""
    emitter.echo(provider_info)

    try:
        token_agent = ContractAgency.get_agent(NucypherTokenAgent,
                                               registry=registry)
        token_contract_info = f"""

{token_agent.contract_name} ........... {token_agent.contract_address}
    ~ Ethers ............ {Web3.fromWei(blockchain.client.get_balance(token_agent.contract_address), 'ether')} ETH
    ~ Tokens ............ {NU.from_nunits(token_agent.get_balance(token_agent.contract_address))}"""
    except BaseContractRegistry.UnknownContract:
        message = f"\n{NucypherTokenAgent.contract_name} is not enrolled in {registry.filepath}"
        emitter.echo(message, color='yellow')
        emitter.echo(sep, nl=False)
    else:
        emitter.echo(token_contract_info)

    banner = """
* Proxy-Contract Deployments
====================================================================="""
    emitter.echo(banner)

    from nucypher.blockchain.eth.actors import ContractAdministrator
    for contract_deployer_class in ContractAdministrator.dispatched_upgradeable_deployer_classes:
        try:
            bare_contract = blockchain.get_contract_by_name(
                contract_name=contract_deployer_class.contract_name,
                proxy_name=DispatcherDeployer.contract_name,
                registry=registry,
                use_proxy_address=False)

            dispatcher_deployer = DispatcherDeployer(
                registry=registry,
                target_contract=bare_contract,
                deployer_address=deployer_address,
                bare=True)  # acquire agency for the dispatcher itself.

            agent = contract_deployer_class.agency(registry=registry,
                                                   contract=bare_contract)

            proxy_payload = f"""
{agent.contract_name} .... {bare_contract.address}
    ~ Owner .............. {bare_contract.functions.owner().call()}
    ~ Ethers ............. {Web3.fromWei(blockchain.client.get_balance(bare_contract.address), 'ether')} ETH
    ~ Tokens ............. {NU.from_nunits(token_agent.get_balance(bare_contract.address))}
    ~ Dispatcher ......... {dispatcher_deployer.contract_address}
        ~ Owner .......... {dispatcher_deployer.contract.functions.owner().call()}
        ~ Target ......... {dispatcher_deployer.contract.functions.target().call()}
        ~ Ethers ......... {Web3.fromWei(blockchain.client.get_balance(dispatcher_deployer.contract_address), 'ether')} ETH
        ~ Tokens ......... {NU.from_nunits(token_agent.get_balance(dispatcher_deployer.contract_address))}"""
            emitter.echo(proxy_payload)
            emitter.echo(sep, nl=False)

        except BaseContractRegistry.UnknownContract:
            message = f"\n{contract_deployer_class.contract_name} is not enrolled in {registry.filepath}"
            emitter.echo(message, color='yellow')
            emitter.echo(sep, nl=False)

    try:

        #
        # StakingInterface
        #

        staking_interface_agent = PreallocationEscrowAgent.StakingInterfaceAgent(
            registry=registry)
        bare_contract = blockchain.get_contract_by_name(
            contract_name=staking_interface_agent.contract_name,
            proxy_name=StakingInterfaceRouterDeployer.contract_name,
            use_proxy_address=False,
            registry=registry)

        router_deployer = StakingInterfaceRouterDeployer(
            registry=registry,
            target_contract=bare_contract,
            deployer_address=deployer_address,
            bare=True)  # acquire agency for the dispatcher itself.

        preallocation_escrow_payload = f"""
{staking_interface_agent.contract_name} ......... {bare_contract.address}
  ~ Ethers ............... {Web3.fromWei(blockchain.client.get_balance(bare_contract.address), 'ether')} ETH
  ~ Tokens ............... {NU.from_nunits(token_agent.get_balance(bare_contract.address))}
  ~ StakingInterfaceRouter {router_deployer.contract.address}
        ~ Owner .......... {router_deployer.contract.functions.owner().call()}
        ~ Target ......... {router_deployer.contract.functions.target().call()}
        ~ Ethers ......... {Web3.fromWei(blockchain.client.get_balance(router_deployer.contract_address), 'ether')} ETH
        ~ Tokens ......... {NU.from_nunits(token_agent.get_balance(router_deployer.contract_address))}"""
        emitter.echo(preallocation_escrow_payload)
        emitter.echo(sep)

    except BaseContractRegistry.UnknownContract:
        message = f"\nStakingInterface is not enrolled in {registry.filepath}"
        emitter.echo(message, color='yellow')

    try:

        policy_agent = ContractAgency.get_agent(PolicyManagerAgent,
                                                registry=registry)
        paint_fee_rate_range(emitter, policy_agent)
        emitter.echo(sep, nl=False)

    except BaseContractRegistry.UnknownContract:
        message = f"\n{PolicyManagerDeployer.contract_name} is not enrolled in {registry.filepath}"
        emitter.echo(message, color='yellow')
        emitter.echo(sep, nl=False)
예제 #7
0
class Staker(NucypherTokenActor):
    """
    Baseclass for staking-related operations on the blockchain.
    """
    class StakerError(NucypherTokenActor.ActorError):
        pass

    class InsufficientTokens(StakerError):
        pass

    def __init__(self,
                 is_me: bool,
                 individual_allocation: IndividualAllocationRegistry = None,
                 *args,
                 **kwargs) -> None:

        super().__init__(*args, **kwargs)
        self.log = Logger("staker")

        self.is_me = is_me
        self.__worker_address = None

        # Blockchain
        self.policy_agent = ContractAgency.get_agent(
            PolicyManagerAgent,
            registry=self.registry)  # type: PolicyManagerAgent
        self.staking_agent = ContractAgency.get_agent(
            StakingEscrowAgent,
            registry=self.registry)  # type: StakingEscrowAgent
        self.economics = TokenEconomicsFactory.get_economics(
            registry=self.registry)

        # Staking via contract
        self.individual_allocation = individual_allocation
        if self.individual_allocation:
            self.beneficiary_address = individual_allocation.beneficiary_address
            self.checksum_address = individual_allocation.contract_address
            self.preallocation_escrow_agent = PreallocationEscrowAgent(
                registry=self.registry,
                allocation_registry=self.individual_allocation,
                beneficiary=self.beneficiary_address)
        else:
            self.beneficiary_address = None
            self.preallocation_escrow_agent = None

        # Check stakes
        self.stakes = StakeList(registry=self.registry,
                                checksum_address=self.checksum_address)

    @property
    def is_contract(self) -> bool:
        return self.preallocation_escrow_agent is not None

    def to_dict(self) -> dict:
        stake_info = [stake.to_stake_info() for stake in self.stakes]
        worker_address = self.worker_address or BlockchainInterface.NULL_ADDRESS
        staker_funds = {
            'ETH': int(self.eth_balance),
            'NU': int(self.token_balance)
        }
        staker_payload = {
            'staker': self.checksum_address,
            'balances': staker_funds,
            'worker': worker_address,
            'stakes': stake_info
        }
        return staker_payload

    # TODO: This is unused. Why? Should we remove it?
    @classmethod
    def from_dict(cls, staker_payload: dict) -> 'Staker':
        staker = Staker(is_me=True,
                        checksum_address=staker_payload['checksum_address'])
        return staker

    @property
    def is_staking(self) -> bool:
        """Checks if this Staker currently has active stakes / locked tokens."""
        self.stakes.refresh()
        return bool(self.stakes)

    def locked_tokens(self, periods: int = 0) -> NU:
        """Returns the amount of tokens this staker has locked for a given duration in periods."""
        self.stakes.refresh()
        raw_value = self.staking_agent.get_locked_tokens(
            staker_address=self.checksum_address, periods=periods)
        value = NU.from_nunits(raw_value)
        return value

    @property
    def current_stake(self) -> NU:
        """
        The total number of staked tokens, i.e., tokens locked in the current period.
        """
        return self.locked_tokens(periods=0)

    @only_me
    def divide_stake(self,
                     stake_index: int,
                     target_value: NU,
                     additional_periods: int = None,
                     expiration: maya.MayaDT = None) -> tuple:

        # Calculate duration in periods
        if additional_periods and expiration:
            raise ValueError(
                "Pass the number of lock periods or an expiration MayaDT; not both."
            )

        # Update staking cache element
        stakes = self.stakes

        # Select stake to divide from local cache
        try:
            current_stake = stakes[stake_index]
        except KeyError:
            if len(stakes):
                message = f"Cannot divide stake - No stake exists with index {stake_index}."
            else:
                message = "Cannot divide stake - There are no active stakes."
            raise Stake.StakingError(message)

        # Calculate stake duration in periods
        if expiration:
            additional_periods = datetime_to_period(
                datetime=expiration,
                seconds_per_period=self.economics.seconds_per_period
            ) - current_stake.final_locked_period
            if additional_periods <= 0:
                raise Stake.StakingError(
                    f"New expiration {expiration} must be at least 1 period from the "
                    f"current stake's end period ({current_stake.final_locked_period})."
                )

        # Do it already!
        modified_stake, new_stake = current_stake.divide(
            target_value=target_value, additional_periods=additional_periods)

        # Update staking cache element
        self.stakes.refresh()

        return modified_stake, new_stake

    @only_me
    def initialize_stake(self,
                         amount: NU,
                         lock_periods: int = None,
                         expiration: maya.MayaDT = None,
                         entire_balance: bool = False) -> Stake:
        """Create a new stake."""

        # Duration
        if lock_periods and expiration:
            raise ValueError(
                "Pass the number of lock periods or an expiration MayaDT; not both."
            )
        if expiration:
            lock_periods = calculate_period_duration(
                future_time=expiration,
                seconds_per_period=self.economics.seconds_per_period)

        # Value
        if entire_balance and amount:
            raise ValueError("Specify an amount or entire balance, not both")
        if entire_balance:
            amount = self.token_balance
        if not self.token_balance >= amount:
            raise self.InsufficientTokens(
                f"Insufficient token balance ({self.token_agent}) "
                f"for new stake initialization of {amount}")

        # Ensure the new stake will not exceed the staking limit
        if (self.current_stake +
                amount) > self.economics.maximum_allowed_locked:
            raise Stake.StakingError(
                f"Cannot initialize stake - "
                f"Maximum stake value exceeded for {self.checksum_address} "
                f"with a target value of {amount}.")

        # Write to blockchain
        new_stake = Stake.initialize_stake(staker=self,
                                           amount=amount,
                                           lock_periods=lock_periods)

        # Update staking cache element
        self.stakes.refresh()

        return new_stake

    def deposit(self, amount: int, lock_periods: int) -> Tuple[str, str]:
        """Public facing method for token locking."""
        if self.is_contract:
            approve_receipt = self.token_agent.approve_transfer(
                amount=amount,
                target_address=self.staking_agent.contract_address,
                sender_address=self.beneficiary_address)
            deposit_receipt = self.preallocation_escrow_agent.deposit_as_staker(
                amount=amount, lock_periods=lock_periods)
        else:
            approve_receipt = self.token_agent.approve_transfer(
                amount=amount,
                target_address=self.staking_agent.contract_address,
                sender_address=self.checksum_address)
            deposit_receipt = self.staking_agent.deposit_tokens(
                amount=amount,
                lock_periods=lock_periods,
                sender_address=self.checksum_address)

        return approve_receipt, deposit_receipt

    @property
    def is_restaking(self) -> bool:
        restaking = self.staking_agent.is_restaking(
            staker_address=self.checksum_address)
        return restaking

    @only_me
    @save_receipt
    def _set_restaking_value(self, value: bool) -> dict:
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.set_restaking(
                value=value)
        else:
            receipt = self.staking_agent.set_restaking(
                staker_address=self.checksum_address, value=value)
        return receipt

    def enable_restaking(self) -> dict:
        receipt = self._set_restaking_value(value=True)
        return receipt

    @only_me
    @save_receipt
    def enable_restaking_lock(self, release_period: int):
        current_period = self.staking_agent.get_current_period()
        if release_period < current_period:
            raise ValueError(
                f"Release period for re-staking lock must be in the future.  "
                f"Current period is {current_period}, got '{release_period}'.")
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.lock_restaking(
                release_period=release_period)
        else:
            receipt = self.staking_agent.lock_restaking(
                staker_address=self.checksum_address,
                release_period=release_period)
        return receipt

    @property
    def restaking_lock_enabled(self) -> bool:
        status = self.staking_agent.is_restaking_locked(
            staker_address=self.checksum_address)
        return status

    def disable_restaking(self) -> dict:
        receipt = self._set_restaking_value(value=False)
        return receipt

    #
    # Bonding with Worker
    #

    @only_me
    @save_receipt
    @validate_checksum_address
    def set_worker(self, worker_address: str) -> str:
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.set_worker(
                worker_address=worker_address)
        else:
            receipt = self.staking_agent.set_worker(
                staker_address=self.checksum_address,
                worker_address=worker_address)
        self.__worker_address = worker_address
        return receipt

    @property
    def worker_address(self) -> str:
        if self.__worker_address:
            # TODO: This is broken for StakeHolder with different stakers - See #1358
            return self.__worker_address
        else:
            worker_address = self.staking_agent.get_worker_from_staker(
                staker_address=self.checksum_address)
            self.__worker_address = worker_address

        if self.__worker_address == BlockchainInterface.NULL_ADDRESS:
            return NO_WORKER_ASSIGNED.bool_value(False)
        return self.__worker_address

    @only_me
    @save_receipt
    def detach_worker(self) -> str:
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.release_worker()
        else:
            receipt = self.staking_agent.release_worker(
                staker_address=self.checksum_address)
        self.__worker_address = BlockchainInterface.NULL_ADDRESS
        return receipt

    #
    # Reward and Collection
    #

    @only_me
    @save_receipt
    def mint(self) -> Tuple[str, str]:
        """Computes and transfers tokens to the staker's account"""
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.mint()
        else:
            receipt = self.staking_agent.mint(
                staker_address=self.checksum_address)
        return receipt

    def calculate_staking_reward(self) -> int:
        staking_reward = self.staking_agent.calculate_staking_reward(
            staker_address=self.checksum_address)
        return staking_reward

    def calculate_policy_reward(self) -> int:
        policy_reward = self.policy_agent.get_reward_amount(
            staker_address=self.checksum_address)
        return policy_reward

    @only_me
    @save_receipt
    @validate_checksum_address
    def collect_policy_reward(self, collector_address=None) -> dict:
        """Collect rewarded ETH."""
        withdraw_address = collector_address or self.checksum_address
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.collect_policy_reward(
                collector_address=withdraw_address)
        else:
            receipt = self.policy_agent.collect_policy_reward(
                collector_address=withdraw_address,
                staker_address=self.checksum_address)
        return receipt

    @only_me
    @save_receipt
    def collect_staking_reward(self) -> str:
        """Withdraw tokens rewarded for staking."""
        if self.is_contract:
            reward_amount = self.calculate_staking_reward()
            self.log.debug(
                f"Withdrawing staking reward ({NU.from_nunits(reward_amount)}) to {self.checksum_address}"
            )
            receipt = self.preallocation_escrow_agent.withdraw_as_staker(
                value=reward_amount)
        else:
            receipt = self.staking_agent.collect_staking_reward(
                staker_address=self.checksum_address)
        return receipt

    @only_me
    @save_receipt
    def withdraw(self, amount: NU) -> str:
        """Withdraw tokens (assuming they're unlocked)"""
        if self.is_contract:
            receipt = self.preallocation_escrow_agent.withdraw_as_staker(
                value=int(amount))
        else:
            receipt = self.staking_agent.withdraw(
                staker_address=self.checksum_address, amount=int(amount))
        return receipt