def test_individual_allocation_registry(get_random_checksum_address, test_registry, tempfile_path): empty_allocation_escrow_deployer = PreallocationEscrowDeployer(registry=test_registry) allocation_contract_abi = empty_allocation_escrow_deployer.get_contract_abi() beneficiary = get_random_checksum_address() contract_address = get_random_checksum_address() allocation_registry = IndividualAllocationRegistry(beneficiary_address=beneficiary, contract_address=contract_address) registry_data = allocation_registry.read() assert len(registry_data) == 1 assert allocation_registry.search(beneficiary_address=beneficiary) == [contract_address, allocation_contract_abi] assert allocation_registry.search(contract_address=contract_address) == [beneficiary, allocation_contract_abi] # Check that searching for an unknown beneficiary or unknown contract raises with pytest.raises(IndividualAllocationRegistry.UnknownBeneficiary): allocation_registry.search(beneficiary_address=get_random_checksum_address()) with pytest.raises(IndividualAllocationRegistry.UnknownContract): allocation_registry.search(contract_address=get_random_checksum_address()) # Check that it gets the same data if using a file to create the allocation registry individual_allocation_file_data = { 'beneficiary_address': beneficiary, 'contract_address': contract_address } with open(tempfile_path, 'w') as outfile: json.dump(individual_allocation_file_data, outfile) allocation_registry = IndividualAllocationRegistry.from_allocation_file(filepath=tempfile_path) assert registry_data == allocation_registry.read()
def _patch_individual_allocation_fetch_latest_publication( agency, test_registry): empty_allocation_escrow_deployer = PreallocationEscrowDeployer( registry=test_registry) allocation_contract_abi = empty_allocation_escrow_deployer.get_contract_abi( ) allocation_template = { "BENEFICIARY_ADDRESS": ["ALLOCATION_CONTRACT_ADDRESS", allocation_contract_abi] } def new_fetch(*args, **kwargs): return json.dumps(allocation_template).encode() original_fetch = GithubRegistrySource.fetch_latest_publication GithubRegistrySource.fetch_latest_publication = new_fetch yield GithubRegistrySource.fetch_latest_publication = original_fetch
def patch_fetch_latest_publication(test_registry): empty_allocation_escrow_deployer = PreallocationEscrowDeployer( registry=test_registry) allocation_contract_abi = empty_allocation_escrow_deployer.get_contract_abi( ) allocation_template = { "BENEFICIARY_ADDRESS": ["ALLOCATION_CONTRACT_ADDRESS", allocation_contract_abi] } new_fetch_result = json.dumps(allocation_template).encode() original_fetch = IndividualAllocationRegistry.fetch_latest_publication def new_fetch(*args, **kwargs): return new_fetch_result IndividualAllocationRegistry.fetch_latest_publication = new_fetch yield IndividualAllocationRegistry.fetch_latest_publication = original_fetch
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