def test_nucypher_deploy_allocations(testerchain, click_runner, mock_allocation_infile, token_economics): deploy_command = ( 'allocations', '--registry-infile', MOCK_REGISTRY_FILEPATH, '--allocation-infile', MOCK_ALLOCATION_INFILE, '--allocation-outfile', MOCK_ALLOCATION_REGISTRY_FILEPATH, '--provider-uri', TEST_PROVIDER_URI, '--poa', ) user_input = 'Y\n' * 2 result = click_runner.invoke(deploy, deploy_command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # ensure that a pre-allocation recipient has the allocated token quantity. beneficiary = testerchain.interface.w3.eth.accounts[-1] allocation_registry = AllocationRegistry( registry_filepath=MOCK_ALLOCATION_REGISTRY_FILEPATH) user_escrow_agent = UserEscrowAgent( beneficiary=beneficiary, allocation_registry=allocation_registry) assert user_escrow_agent.unvested_tokens == token_economics.maximum_allowed_locked
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.filepath, '--allocation-outfile', MOCK_ALLOCATION_REGISTRY_FILEPATH, '--provider', TEST_PROVIDER_URI, '--poa') account_index = '0\n' yes = 'Y\n' user_input = account_index + yes + yes result = click_runner.invoke(deploy, deploy_command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # 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) user_escrow_agent = UserEscrowAgent( registry=registry, beneficiary=beneficiary, allocation_registry=allocation_registry) assert user_escrow_agent.unvested_tokens == token_economics.minimum_allowed_locked
def mock_allocation_infile(testerchain, token_economics): accounts = testerchain.interface.w3.eth.accounts[5::] allocation_data = [{'address': addr, 'amount': token_economics.maximum_allowed_locked, 'duration': ONE_YEAR_IN_SECONDS} for addr in accounts] with open(MOCK_ALLOCATION_INFILE, 'w') as file: file.write(json.dumps(allocation_data)) registry = AllocationRegistry(registry_filepath=MOCK_ALLOCATION_INFILE) yield registry os.remove(MOCK_ALLOCATION_INFILE)
def test_nucypher_deploy_allocation_contracts(click_runner, testerchain, deploy_user_input, mock_primary_registry_filepath, mock_allocation_infile, token_economics): # Simulate "Reconnection" real_attach_provider = BlockchainDeployerInterface._attach_provider cached_blockchain = BlockchainDeployerInterface.reconnect() registry = cached_blockchain.registry assert registry.filepath == mock_primary_registry_filepath def attach_cached_provider(interface, *args, **kwargs): cached_provider = cached_blockchain.provider real_attach_provider(interface, provider=cached_provider) BlockchainDeployerInterface._attach_provider = attach_cached_provider # # Main # deploy_command = ('allocations', '--registry-infile', MOCK_REGISTRY_FILEPATH, '--allocation-infile', mock_allocation_infile.filepath, '--allocation-outfile', MOCK_ALLOCATION_REGISTRY_FILEPATH, '--provider-uri', TEST_PROVIDER_URI, '--poa') account_index = '0\n' yes = 'Y\n' node_password = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' user_input = account_index + yes + node_password + yes result = click_runner.invoke(deploy, deploy_command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # ensure that a pre-allocation recipient has the allocated token quantity. beneficiary = testerchain.client.accounts[-1] allocation_registry = AllocationRegistry( registry_filepath=MOCK_ALLOCATION_REGISTRY_FILEPATH) user_escrow_agent = UserEscrowAgent( blockchain=cached_blockchain, beneficiary=beneficiary, allocation_registry=allocation_registry) assert user_escrow_agent.unvested_tokens == token_economics.minimum_allowed_locked # # Tear Down # # Destroy existing blockchain testerchain.disconnect()
def deploy_beneficiary_contracts( self, allocations: List[Dict[str, Union[str, int]]], allocation_outfile: str = None, allocation_registry: AllocationRegistry = None, crash_on_failure: bool = True, ) -> Dict[str, dict]: """ Example allocation dataset (one year is 31536000 seconds): data = [{'beneficiary_address': '0xdeadbeef', 'amount': 100, 'duration_seconds': 31536000}, {'beneficiary_address': '0xabced120', 'amount': 133432, 'duration_seconds': 31536000*2}, {'beneficiary_address': '0xf7aefec2', 'amount': 999, 'duration_seconds': 31536000*3}] """ if allocation_registry and allocation_outfile: raise self.ActorError( "Pass either allocation registry or allocation_outfile, not both." ) if allocation_registry is None: allocation_registry = AllocationRegistry( filepath=allocation_outfile) allocation_txhashes, failed = dict(), list() for allocation in allocations: deployer = self.deploy_user_escrow( allocation_registry=allocation_registry) try: txhashes = deployer.deliver( value=allocation['amount'], duration=allocation['duration_seconds'], beneficiary_address=allocation['beneficiary_address']) except TransactionFailed: if crash_on_failure: raise self.log.debug( f"Failed allocation transaction for {allocation['amount']} to {allocation['beneficiary_address']}" ) failed.append(allocation) continue else: allocation_txhashes[ allocation['beneficiary_address']] = txhashes if failed: # TODO: More with these failures: send to isolated logfile, and reattempt self.log.critical( f"FAILED TOKEN ALLOCATION - {len(failed)} Allocations failed.") return allocation_txhashes
def mock_allocation_infile(testerchain, token_economics): accounts = testerchain.unassigned_accounts allocation_data = [{ 'beneficiary_address': addr, 'amount': token_economics.minimum_allowed_locked, 'duration_seconds': ONE_YEAR_IN_SECONDS } for addr in accounts] with open(MOCK_ALLOCATION_INFILE, 'w') as file: file.write(json.dumps(allocation_data)) registry = AllocationRegistry(filepath=MOCK_ALLOCATION_INFILE) yield registry os.remove(MOCK_ALLOCATION_INFILE)
def mock_allocation_registry(testerchain, test_registry, mock_allocation_infile): admin = ContractAdministrator( registry=test_registry, client_password=INSECURE_DEVELOPMENT_PASSWORD, deployer_address=testerchain.etherbase_account) admin.deploy_beneficiaries_from_file( allocation_data_filepath=mock_allocation_infile, allocation_outfile=MOCK_ALLOCATION_REGISTRY_FILEPATH) allocation_registry = AllocationRegistry( filepath=MOCK_ALLOCATION_REGISTRY_FILEPATH) yield allocation_registry if os.path.isfile(MOCK_ALLOCATION_REGISTRY_FILEPATH): os.remove(MOCK_ALLOCATION_REGISTRY_FILEPATH)
def deploy_beneficiary_contracts(self, allocations: List[Dict[str, Union[str, int]]], allocation_outfile: str = None, allocation_registry: AllocationRegistry = None, ) -> None: """ Example allocation dataset (one year is 31540000 seconds): data = [{'address': '0xdeadbeef', 'amount': 100, 'duration': 31540000}, {'address': '0xabced120', 'amount': 133432, 'duration': 31540000*2}, {'address': '0xf7aefec2', 'amount': 999, 'duration': 31540000*3}] """ if allocation_registry and allocation_outfile: raise self.ActorError("Pass either allocation registry or allocation_outfile, not both.") if allocation_registry is None: allocation_registry = AllocationRegistry(registry_filepath=allocation_outfile) for allocation in allocations: deployer = self.deploy_user_escrow(allocation_registry=allocation_registry) deployer.deliver(value=allocation['amount'], duration=allocation['duration'], beneficiary_address=allocation['address'])
def deploy_beneficiary_contracts( self, allocations: List[Dict[str, Union[str, int]]], allocation_outfile: str = None, allocation_registry: AllocationRegistry = None, crash_on_failure: bool = True, interactive: bool = True, emitter: StdoutEmitter = None, ) -> Dict[str, dict]: """ The allocation file is a JSON file containing a list of allocations. Each allocation has a: * 'beneficiary_address': Checksum address of the beneficiary * 'name': User-friendly name of the beneficiary (Optional) * 'amount': Amount of tokens locked, in NuNits * 'duration_seconds': Lock duration expressed in seconds Example allocation file: [ {'beneficiary_address': '0xdeadbeef', 'name': 'H. E. Pennypacker', 'amount': 100, 'duration_seconds': 31536000}, {'beneficiary_address': '0xabced120', 'amount': 133432, 'duration_seconds': 31536000}, {'beneficiary_address': '0xf7aefec2', 'amount': 999, 'duration_seconds': 31536000}] """ if interactive and not emitter: raise ValueError( "'emitter' is a required keyword argument when interactive is True." ) if allocation_registry and allocation_outfile: raise self.ActorError( "Pass either allocation registry or allocation_outfile, not both." ) if allocation_registry is None: allocation_registry = AllocationRegistry( filepath=allocation_outfile) if emitter: paint_input_allocation_file(emitter, allocations) if interactive: click.confirm("Continue with the allocation process?", abort=True) total_to_allocate = NU.from_nunits( sum(allocation['amount'] for allocation in allocations)) balance = ContractAgency.get_agent(NucypherTokenAgent, self.registry).get_balance( self.deployer_address) if balance < total_to_allocate: raise ValueError( f"Not enough tokens to allocate. We need at least {total_to_allocate}." ) allocation_receipts, failed, allocated = dict(), list(), list() total_deployment_transactions = len(allocations) * 4 # Create an allocation template file, containing the allocation contract ABI and placeholder values # for the beneficiary and contract addresses. This file will be shared with all allocation users. empty_allocation_escrow_deployer = PreallocationEscrowDeployer( registry=self.registry) allocation_contract_abi = empty_allocation_escrow_deployer.get_contract_abi( ) allocation_template = { "BENEFICIARY_ADDRESS": ["ALLOCATION_CONTRACT_ADDRESS", allocation_contract_abi] } parent_path = Path(allocation_registry.filepath ).parent # Use same folder as allocation registry template_filename = IndividualAllocationRegistry.REGISTRY_NAME template_filepath = os.path.join(parent_path, template_filename) AllocationRegistry(filepath=template_filepath).write( registry_data=allocation_template) if emitter: emitter.echo( f"Saved allocation template file to {template_filepath}", color='blue', bold=True) # Deploy each allocation contract with click.progressbar(length=total_deployment_transactions, label="Allocation progress", show_eta=False) as bar: bar.short_limit = 0 for allocation in allocations: # TODO: Check if allocation already exists in allocation registry beneficiary = allocation['beneficiary_address'] name = allocation.get('name', 'No name provided') if interactive: click.pause( info=f"\nPress any key to continue with allocation for " f"beneficiary {beneficiary} ({name})") if emitter: emitter.echo( f"\nDeploying PreallocationEscrow contract for beneficiary {beneficiary} ({name})..." ) bar._last_line = None bar.render_progress() deployer = self.deploy_preallocation_escrow( allocation_registry=allocation_registry, progress=bar) amount = allocation['amount'] duration = allocation['duration_seconds'] try: receipts = deployer.deliver( value=amount, duration=duration, beneficiary_address=beneficiary, progress=bar) except TransactionFailed as e: if crash_on_failure: raise self.log.debug( f"Failed allocation transaction for {NU.from_nunits(amount)} to {beneficiary}: {e}" ) failed.append(allocation) continue else: allocation_receipts[beneficiary] = receipts allocation_contract_address = deployer.contract_address self.log.info( f"Created {deployer.contract_name} contract at {allocation_contract_address} " f"for beneficiary {beneficiary}.") allocated.append((allocation, allocation_contract_address)) # Create individual allocation file individual_allocation_filename = f'allocation-{beneficiary}.json' individual_allocation_filepath = os.path.join( parent_path, individual_allocation_filename) individual_allocation_file_data = { 'beneficiary_address': beneficiary, 'contract_address': allocation_contract_address } with open(individual_allocation_filepath, 'w') as outfile: json.dump(individual_allocation_file_data, outfile) if emitter: blockchain = BlockchainInterfaceFactory.get_interface() paint_contract_deployment( contract_name=deployer.contract_name, receipts=receipts, contract_address=deployer.contract_address, emitter=emitter, chain_name=blockchain.client.chain_name, open_in_browser=False) emitter.echo( f"Saved individual allocation file to {individual_allocation_filepath}", color='blue', bold=True) if emitter: paint_deployed_allocations(emitter, allocated, failed) csv_filename = f'allocations-{self.deployer_address[:6]}-{maya.now().epoch}.csv' csv_filepath = os.path.join(parent_path, csv_filename) write_deployed_allocations_to_csv(csv_filepath, allocated, failed) if emitter: emitter.echo(f"Saved allocation summary CSV to {csv_filepath}", color='blue', bold=True) if failed: # TODO: More with these failures: send to isolated logfile, and reattempt self.log.critical( f"FAILED TOKEN ALLOCATION - {len(failed)} allocations failed." ) return allocation_receipts
def test_nucypher_deploy_allocation_contracts(click_runner, testerchain, deploy_user_input, mock_primary_registry_filepath, mock_allocation_infile, token_economics): TesterBlockchain.sever_connection() Agency.clear() if os.path.isfile(MOCK_ALLOCATION_REGISTRY_FILEPATH): os.remove(MOCK_ALLOCATION_REGISTRY_FILEPATH) assert not os.path.isfile(MOCK_ALLOCATION_REGISTRY_FILEPATH) # We start with a blockchain node, and nothing else... if os.path.isfile(mock_primary_registry_filepath): os.remove(mock_primary_registry_filepath) assert not os.path.isfile(mock_primary_registry_filepath) command = ['contracts', '--registry-outfile', mock_primary_registry_filepath, '--provider-uri', TEST_PROVIDER_URI, '--poa', '--no-sync'] user_input = deploy_user_input result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # # Main # deploy_command = ('allocations', '--registry-infile', MOCK_REGISTRY_FILEPATH, '--allocation-infile', mock_allocation_infile.filepath, '--allocation-outfile', MOCK_ALLOCATION_REGISTRY_FILEPATH, '--provider-uri', 'tester://pyevm', '--poa') account_index = '0\n' yes = 'Y\n' node_password = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' user_input = account_index + yes + node_password + yes result = click_runner.invoke(deploy, deploy_command, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # ensure that a pre-allocation recipient has the allocated token quantity. beneficiary = testerchain.interface.w3.eth.accounts[-1] allocation_registry = AllocationRegistry(registry_filepath=MOCK_ALLOCATION_REGISTRY_FILEPATH) user_escrow_agent = UserEscrowAgent(beneficiary=beneficiary, allocation_registry=allocation_registry) assert user_escrow_agent.unvested_tokens == token_economics.minimum_allowed_locked # # Tear Down # # Destroy existing blockchain BlockchainInterface.disconnect()