diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index 8a886462a..27609cfbc 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -178,19 +178,19 @@ class Deployer(NucypherTokenActor): self.deploy_miner_contract(secret=miner_secret) self.deploy_policy_contract(secret=policy_secret) - def deploy_beneficiary_contracts(self, allocations: List[Dict[str, Union[str, int]]]): + def deploy_beneficiary_contracts(self, allocations: List[Dict[str, Union[str, int]]]) -> None: """ - Example allocation dataset: + Example allocation dataset (one year is 31540000 seconds): - data = [{'address': '0xdeadbeef', 'amount': 100, 'periods': 100}, - {'address': '0xabced120', 'amount': 133432, 'periods': 1}, - {'address': '0xf7aefec2', 'amount': 999, 'periods': 30}] + data = [{'address': '0xdeadbeef', 'amount': 100, 'duration': 31540000}, + {'address': '0xabced120', 'amount': 133432, 'duration': 31540000*2}, + {'address': '0xf7aefec2', 'amount': 999, 'duration': 31540000*3}] """ for allocation in allocations: deployer = self.deploy_user_escrow() deployer.deliver(value=allocation['amount'], - duration=allocation['periods'], + duration=allocation['duration'], beneficiary_address=allocation['address']) @staticmethod @@ -330,9 +330,9 @@ class Miner(NucypherTokenActor): return bool(self.locked_tokens > 0) @property - def locked_tokens(self, ): + def locked_tokens(self): """Returns the amount of tokens this miner has locked.""" - return self.miner_agent.get_locked_tokens(node_address=self.checksum_public_address) + return self.miner_agent.get_locked_tokens(miner_address=self.checksum_public_address) @property def stakes(self) -> Tuple[list]: diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 1eca7d908..de251d7bc 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -115,9 +115,16 @@ class MinerAgent(EthereumContractAgent): # MinersEscrow Contract API # - def get_locked_tokens(self, node_address): - """Returns the amount of tokens this miner has locked.""" - return self.contract.functions.getLockedTokens(node_address).call() + def get_locked_tokens(self, miner_address: str, periods: int = 0) -> int: + """ + Returns the amount of tokens this miner has locked. + + TODO: Validate input (periods not less then 0) + """ + return self.contract.functions.getLockedTokens(miner_address, periods).call() + + def owned_tokens(self, address: str) -> int: + return self.contract.functions.minerInfo(address).call()[0] def get_stake_info(self, miner_address: str, stake_index: int): first_period, *others, locked_value = self.contract.functions.getStakeInfo(miner_address, stake_index).call() @@ -152,7 +159,11 @@ class MinerAgent(EthereumContractAgent): return txhash def mint(self, node_address) -> Tuple[str, str]: - """Computes reward tokens for the miner's account""" + """ + Computes reward tokens for the miner's account; + This is only used to calculate the reward for the final period of a stake, + when you intend to withdraw 100% of tokens. + """ mint_txhash = self.contract.functions.mint().transact({'from': node_address}) self.blockchain.wait_for_receipt(mint_txhash) @@ -231,12 +242,12 @@ class PolicyAgent(EthereumContractAgent): author_address: str, value: int, periods: int, - reward: int, + initial_reward: int, node_addresses: List[str]) -> str: txhash = self.contract.functions.createPolicy(policy_id, periods, - reward, + initial_reward, node_addresses).transact({'from': author_address, 'value': value}) self.blockchain.wait_for_receipt(txhash) @@ -315,8 +326,6 @@ class UserEscrowAgent(EthereumContractAgent): self.__read_proxy() super().__init__(blockchain=self.blockchain, contract=self.principal_contract, *args, **kwargs) - self.token_agent = NucypherTokenAgent(blockchain=self.blockchain) - def __read_proxy(self): self.__proxy_agent = self.UserEscrowProxyAgent(blockchain=self.blockchain) contract = self.__proxy_agent._generate_beneficiary_agency(principal_address=self.principal_contract.address) @@ -353,7 +362,6 @@ class UserEscrowAgent(EthereumContractAgent): @property def proxy_contract(self): - """Directly reference the beneficiary's deployed contract instead of the proxy contracts's interface""" if self.__proxy_contract is NO_CONTRACT_AVAILABLE: raise RuntimeError("{} not available".format(self.registry_contract_name)) return self.__proxy_contract @@ -366,31 +374,18 @@ class UserEscrowAgent(EthereumContractAgent): return self.__principal_contract @property - def allocation(self): - return self.principal_contract.functions.lockedValue().call() - - @property - def end_timestamp(self): - return self.principal_contract.functions.endLockTimestamp().call() - - @property - def locked_tokens(self) -> int: - """Returns the amount of tokens this miner has locked for the beneficiary.""" + def allocation(self) -> int: return self.principal_contract.functions.getLockedTokens().call() + @property + def end_timestamp(self) -> int: + return self.principal_contract.functions.endLockTimestamp().call() + def lock(self, amount: int, periods: int) -> str: txhash = self.__proxy_contract.functions.lock(amount, periods).transact({'from': self.__beneficiary}) self.blockchain.wait_for_receipt(txhash) return txhash - def deposit_tokens(self, amount: int, sender_address: str): - """Deposit without locking""" - txhash = self.token_agent.transfer(amount=amount, - sender_address=sender_address, - target_address=self.principal_contract.address) - self.blockchain.wait_for_receipt(txhash) - return txhash - def withdraw_tokens(self, value: int) -> str: txhash = self.principal_contract.functions.withdrawTokens(value).transact({'from': self.__beneficiary}) self.blockchain.wait_for_receipt(txhash) @@ -410,3 +405,23 @@ class UserEscrowAgent(EthereumContractAgent): txhash = self.__proxy_contract.functions.withdrawAsMiner(value).transact({'from': self.__beneficiary}) self.blockchain.wait_for_receipt(txhash) return txhash + + def confirm_activity(self) -> str: + txhash = self.__proxy_contract.functions.confirmActivity().transact({'from': self.__beneficiary}) + self.blockchain.wait_for_receipt(txhash) + return txhash + + def mint(self) -> str: + txhash = self.__proxy_contract.functions.mint().transact({'from': self.__beneficiary}) + self.blockchain.wait_for_receipt(txhash) + return txhash + + def collect_policy_reward(self) -> str: + txhash = self.__proxy_contract.functions.withdrawPolicyReward().transact({'from': self.__beneficiary}) + self.blockchain.wait_for_receipt(txhash) + return txhash + + def set_min_reward_rate(self, rate: int) -> str: + txhash = self.__proxy_contract.functions.setMinRewardRate(rate).transact({'from': self.__beneficiary}) + self.blockchain.wait_for_receipt(txhash) + return txhash diff --git a/nucypher/blockchain/eth/constants.py b/nucypher/blockchain/eth/constants.py index 9a427dd98..1e4cae12d 100644 --- a/nucypher/blockchain/eth/constants.py +++ b/nucypher/blockchain/eth/constants.py @@ -1,5 +1,6 @@ """Nucypher Token and Miner constants.""" +ONE_YEAR_IN_SECONDS = 31540000 NUCYPHER_GAS_LIMIT = 5000000 # TODO: move elsewhere? # diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index 7c259d09b..4cbad9f75 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -21,7 +21,6 @@ class ContractDeployer: agency = NotImplemented _contract_name = NotImplemented _interface_class = BlockchainDeployerInterface - __is_proxy = False __upgradeable = NotImplemented __proxy_deployer = NotImplemented @@ -40,6 +39,7 @@ class ContractDeployer: self.__armed = False self.__proxy_contract = NotImplemented self.__deployer_address = deployer_address + self.__ready_to_deploy = False @property def contract_address(self) -> str: @@ -68,7 +68,8 @@ class ContractDeployer: def is_armed(self) -> bool: return bool(self.__armed is True) - def check_ready_to_deploy(self, fail=False, check_arming=False) -> Tuple[bool, list]: + @property + def ready_to_deploy(self, fail=False, check_arming=False) -> Tuple[bool, list]: """ Iterates through a set of rules required for an ethereum contract deployer to be eligible for deployment returning a @@ -80,6 +81,9 @@ class ContractDeployer: If fail is set to True, raise a configuration error, instead of returning. """ + if self.__ready_to_deploy is True: + return True, list() + rules = [ (self.is_deployed is not True, 'Contract already deployed'), (self.deployer_address is not None, 'No deployer address set.'), @@ -111,7 +115,7 @@ class ContractDeployer: return True - def arm(self, abort=True) -> tuple: + def arm(self, abort=True) -> bool: """ Safety mechanism for ethereum contract deployment @@ -124,8 +128,8 @@ class ContractDeployer: """ if self.__armed is True and abort is True: raise self.ContractDeploymentError('{} deployer is already armed.'.format(self._contract_name)) - self.__armed, disqualifications = self.check_ready_to_deploy(fail=abort, check_arming=False) - return self.__armed, disqualifications + self.__armed, disqualifications = self.ready_to_deploy + return self.__armed def deploy(self) -> dict: """ @@ -158,7 +162,7 @@ class NucypherTokenDeployer(ContractDeployer): The contract must be armed before it can be deployed. Deployment can only ever be executed exactly once! """ - self.check_ready_to_deploy(fail=True, check_arming=True) + self.ready_to_deploy _contract, deployment_txhash = self.blockchain.interface.deploy_contract( self._contract_name, @@ -175,7 +179,6 @@ class DispatcherDeployer(ContractDeployer): """ _contract_name = 'Dispatcher' - __is_proxy = True __upgradeable = False def __init__(self, target_contract: Contract, secret_hash: bytes, *args, **kwargs): @@ -228,7 +231,7 @@ class MinerEscrowDeployer(ContractDeployer): """ # Raise if not all-systems-go - self.check_ready_to_deploy(fail=True, check_arming=True) + self.ready_to_deploy # Build deployment arguments origin_args = {'from': self.deployer_address} @@ -307,7 +310,7 @@ class PolicyManagerDeployer(ContractDeployer): self.secret_hash = secret_hash def deploy(self) -> Dict[str, str]: - self.check_ready_to_deploy(fail=True, check_arming=True) + self.ready_to_deploy # Creator deploys the policy manager policy_manager_contract, deploy_txhash = self.blockchain.interface.deploy_contract( @@ -352,8 +355,6 @@ class PolicyManagerDeployer(ContractDeployer): class LibraryLinkerDeployer(ContractDeployer): _contract_name = 'UserEscrowLibraryLinker' - __is_proxy = True - __upgradeable = False def __init__(self, target_contract: Contract, secret_hash: bytes, *args, **kwargs): self.target_contract = target_contract @@ -370,8 +371,6 @@ class LibraryLinkerDeployer(ContractDeployer): class UserEscrowProxyDeployer(ContractDeployer): _contract_name = 'UserEscrowProxy' - __is_proxy = True - __upgradeable = True __proxy_deployer = LibraryLinkerDeployer def __init__(self, secret_hash: bytes, *args, **kwargs): @@ -418,7 +417,7 @@ class UserEscrowDeployer(ContractDeployer): agency = UserEscrowAgent _contract_name = agency.registry_contract_name - __proxy_deployer = UserEscrowProxyDeployer + __linker_deployer = LibraryLinkerDeployer __allocation_registry = AllocationRegistry def __init__(self, allocation_registry: AllocationRegistry = None, *args, **kwargs) -> None: @@ -500,12 +499,12 @@ class UserEscrowDeployer(ContractDeployer): def deploy(self) -> dict: """Deploy a new instance of UserEscrow to the blockchain.""" - self.check_ready_to_deploy(fail=True, check_arming=True) + self.ready_to_deploy deployment_transactions = dict() - proxy_contract = self.__proxy_deployer.get_latest_version(blockchain=self.blockchain) - args = (self._contract_name, proxy_contract.address, self.token_agent.contract_address) + linker_contract = self.blockchain.interface.get_contract_by_name(name=self.__linker_deployer._contract_name) + args = (self._contract_name, linker_contract.address, self.token_agent.contract_address) user_escrow_contract, deploy_txhash = self.blockchain.interface.deploy_contract(*args, enroll=False) self.__principal_contract = user_escrow_contract deployment_transactions['deploy_user_escrow'] = deploy_txhash diff --git a/nucypher/blockchain/eth/interfaces.py b/nucypher/blockchain/eth/interfaces.py index f34b4ace7..095807db5 100644 --- a/nucypher/blockchain/eth/interfaces.py +++ b/nucypher/blockchain/eth/interfaces.py @@ -206,7 +206,7 @@ class BlockchainInterface: if uri_breakdown.scheme == 'tester': if uri_breakdown.netloc == 'pyevm': - genesis_params = PyEVMBackend._generate_genesis_params(overrides={'gas_limit': NUCYPHER_GAS_LIMIT}) + genesis_params = PyEVMBackend.generate_genesis_params(overrides={'gas_limit': NUCYPHER_GAS_LIMIT}) pyevm_backend = PyEVMBackend(genesis_parameters=genesis_params) eth_tester = EthereumTester(backend=pyevm_backend, auto_mine_transactions=True) provider = EthereumTesterProvider(ethereum_tester=eth_tester)