mirror of https://github.com/nucypher/nucypher.git
Merge pull request #1446 from cygnusv/csv
Touchups to the CLI allocation process: support for CSV, bugfixes, etc.pull/1458/head
commit
6ea77a393b
|
@ -16,6 +16,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
@ -31,7 +32,7 @@ from constant_sorrow.constants import (
|
|||
NO_WORKER_ASSIGNED,
|
||||
)
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import keccak, is_checksum_address
|
||||
from eth_utils import keccak, is_checksum_address, to_checksum_address
|
||||
from twisted.logger import Logger
|
||||
|
||||
from nucypher.blockchain.economics import TokenEconomics, StandardTokenEconomics, TokenEconomicsFactory
|
||||
|
@ -383,13 +384,20 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
emitter: StdoutEmitter = None,
|
||||
) -> Dict[str, dict]:
|
||||
"""
|
||||
The allocation file is a JSON file containing a list of allocations. Each allocation has a:
|
||||
The allocation file contains a list of allocations, each of them composed of:
|
||||
* '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:
|
||||
It accepts both CSV and JSON formats. Example allocation file in CSV format:
|
||||
|
||||
"beneficiary_address","name","amount","duration_seconds"
|
||||
"0xdeadbeef","H. E. Pennypacker",100,31536000
|
||||
"0xabced120","",133432,31536000
|
||||
"0xf7aefec2","",999,31536000
|
||||
|
||||
Example allocation file in JSON format:
|
||||
|
||||
[ {'beneficiary_address': '0xdeadbeef', 'name': 'H. E. Pennypacker', 'amount': 100, 'duration_seconds': 31536000},
|
||||
{'beneficiary_address': '0xabced120', 'amount': 133432, 'duration_seconds': 31536000},
|
||||
|
@ -434,6 +442,12 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
if emitter:
|
||||
emitter.echo(f"Saved allocation template file to {template_filepath}", color='blue', bold=True)
|
||||
|
||||
already_enrolled = [a['beneficiary_address'] for a in allocations
|
||||
if allocation_registry.is_beneficiary_enrolled(a['beneficiary_address'])]
|
||||
if already_enrolled:
|
||||
raise ValueError(f"The following beneficiaries are already enrolled in allocation registry "
|
||||
f"({allocation_registry.filepath}): {already_enrolled}")
|
||||
|
||||
# Deploy each allocation contract
|
||||
with click.progressbar(length=total_deployment_transactions,
|
||||
label="Allocation progress",
|
||||
|
@ -455,25 +469,29 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
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)
|
||||
deployer = self.deploy_preallocation_escrow(allocation_registry=allocation_registry,
|
||||
progress=bar)
|
||||
|
||||
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}")
|
||||
message = f"Failed allocation transaction for {NU.from_nunits(amount)} to {beneficiary}: {e}"
|
||||
self.log.debug(message)
|
||||
if emitter:
|
||||
emitter.echo(message=message, color='red', bold=True)
|
||||
failed.append(allocation)
|
||||
continue
|
||||
|
||||
else:
|
||||
allocation_receipts[beneficiary] = receipts
|
||||
allocation_receipts[beneficiary] = deployer.deployment_receipts
|
||||
allocation_contract_address = deployer.contract_address
|
||||
self.log.info(f"Created {deployer.contract_name} contract at {allocation_contract_address} "
|
||||
f"for beneficiary {beneficiary}.")
|
||||
|
@ -492,7 +510,7 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
if emitter:
|
||||
blockchain = BlockchainInterfaceFactory.get_interface()
|
||||
paint_contract_deployment(contract_name=deployer.contract_name,
|
||||
receipts=receipts,
|
||||
receipts=deployer.deployment_receipts,
|
||||
contract_address=deployer.contract_address,
|
||||
emitter=emitter,
|
||||
chain_name=blockchain.client.chain_name,
|
||||
|
@ -503,14 +521,13 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
if emitter:
|
||||
paint_deployed_allocations(emitter, allocated, failed)
|
||||
|
||||
csv_filename = f'allocations-{self.deployer_address[:6]}-{maya.now().epoch}.csv'
|
||||
csv_filename = f'allocation-summary-{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
|
||||
|
@ -518,11 +535,18 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
@staticmethod
|
||||
def __read_allocation_data(filepath: str) -> list:
|
||||
with open(filepath, 'r') as allocation_file:
|
||||
data = allocation_file.read()
|
||||
try:
|
||||
allocation_data = json.loads(data)
|
||||
except JSONDecodeError:
|
||||
raise
|
||||
if filepath.endswith(".csv"):
|
||||
reader = csv.DictReader(allocation_file)
|
||||
allocation_data = list(reader)
|
||||
else: # Assume it's JSON by default
|
||||
allocation_data = json.load(allocation_file)
|
||||
|
||||
# Pre-process allocation data
|
||||
for entry in allocation_data:
|
||||
entry['beneficiary_address'] = to_checksum_address(entry['beneficiary_address'])
|
||||
entry['amount'] = int(entry['amount'])
|
||||
entry['duration_seconds'] = int(entry['duration_seconds'])
|
||||
|
||||
return allocation_data
|
||||
|
||||
def deploy_beneficiaries_from_file(self,
|
||||
|
@ -535,7 +559,8 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
receipts = self.deploy_beneficiary_contracts(allocations=allocations,
|
||||
allocation_outfile=allocation_outfile,
|
||||
emitter=emitter,
|
||||
interactive=interactive)
|
||||
interactive=interactive,
|
||||
crash_on_failure=False)
|
||||
# Save transaction metadata
|
||||
receipts_filepath = self.save_deployment_receipts(receipts=receipts, filename_prefix='allocation')
|
||||
if emitter:
|
||||
|
@ -549,12 +574,12 @@ class ContractAdministrator(NucypherTokenActor):
|
|||
os.makedirs(DEFAULT_CONFIG_ROOT, exist_ok=True)
|
||||
with open(filepath, 'w') as file:
|
||||
data = dict()
|
||||
for contract_name, receipts in receipts.items():
|
||||
for contract_name, contract_receipts in receipts.items():
|
||||
contract_records = dict()
|
||||
for tx_name, receipt in receipts.items():
|
||||
for tx_name, receipt in contract_receipts.items():
|
||||
# Formatting
|
||||
receipt = {item: str(result) for item, result in receipt.items()}
|
||||
contract_records.update({tx_name: receipt for tx_name in receipts})
|
||||
pretty_receipt = {item: str(result) for item, result in receipt.items()}
|
||||
contract_records[tx_name] = pretty_receipt
|
||||
data[contract_name] = contract_records
|
||||
data = json.dumps(data, indent=4)
|
||||
file.write(data)
|
||||
|
|
|
@ -16,6 +16,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Tuple, Dict
|
||||
|
||||
from constant_sorrow.constants import CONTRACT_NOT_DEPLOYED, NO_DEPLOYER_CONFIGURED, NO_BENEFICIARY
|
||||
|
@ -73,7 +74,7 @@ class BaseContractDeployer:
|
|||
# Defaults
|
||||
#
|
||||
self.registry = registry
|
||||
self.deployment_receipts = dict()
|
||||
self.deployment_receipts = OrderedDict()
|
||||
self._contract = CONTRACT_NOT_DEPLOYED
|
||||
self.__proxy_contract = NotImplemented
|
||||
self.__deployer_address = deployer_address
|
||||
|
@ -794,7 +795,7 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
|
||||
agency = PreallocationEscrowAgent
|
||||
contract_name = agency.registry_contract_name
|
||||
deployment_steps = ('contract_deployment', )
|
||||
deployment_steps = ('contract_deployment', 'approve_transfer', 'initial_deposit', 'transfer_ownership')
|
||||
_router_deployer = StakingInterfaceRouterDeployer
|
||||
__allocation_registry = AllocationRegistry
|
||||
|
||||
|
@ -828,16 +829,18 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
sender_address=self.deployer_address,
|
||||
payload=payload)
|
||||
self.__beneficiary_address = checksum_address
|
||||
self.deployment_receipts.update({self.deployment_steps[3]: transfer_owner_receipt})
|
||||
return transfer_owner_receipt
|
||||
|
||||
def initial_deposit(self, value: int, duration_seconds: int, progress=None) -> dict:
|
||||
def initial_deposit(self, value: int, duration_seconds: int, progress=None):
|
||||
"""Allocate an amount of tokens with lock time in seconds, and transfer ownership to the beneficiary"""
|
||||
# Approve
|
||||
allocation_receipts = dict()
|
||||
approve_function = self.token_contract.functions.approve(self.contract.address, value)
|
||||
approve_function = self.token_contract.functions.approve(self.contract.address, value)
|
||||
approve_receipt = self.blockchain.send_transaction(contract_function=approve_function,
|
||||
sender_address=self.deployer_address) # TODO: Gas
|
||||
allocation_receipts['approve'] = approve_receipt
|
||||
|
||||
self.deployment_receipts.update({self.deployment_steps[1]: approve_receipt})
|
||||
|
||||
if progress:
|
||||
progress.update(1)
|
||||
# Deposit
|
||||
|
@ -848,13 +851,10 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
sender_address=self.deployer_address,
|
||||
payload=args)
|
||||
|
||||
# TODO: Do something with allocation_receipts. Perhaps it should be returned instead of only the last receipt.
|
||||
allocation_receipts['initial_deposit'] = deposit_receipt
|
||||
self.deployment_receipts.update({self.deployment_steps[2]: deposit_receipt})
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
return deposit_receipt
|
||||
|
||||
def enroll_principal_contract(self):
|
||||
if self.__beneficiary_address is NO_BENEFICIARY:
|
||||
raise self.ContractDeploymentError("No beneficiary assigned to {}".format(self.contract.address))
|
||||
|
@ -863,7 +863,7 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
contract_abi=self.contract.abi)
|
||||
|
||||
@validate_checksum_address
|
||||
def deliver(self, value: int, duration: int, beneficiary_address: str, progress=None) -> dict:
|
||||
def deliver(self, value: int, duration: int, beneficiary_address: str, progress=None):
|
||||
"""
|
||||
Transfer allocated tokens and hand-off the contract to the beneficiary.
|
||||
|
||||
|
@ -874,12 +874,11 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
|
||||
"""
|
||||
|
||||
deposit_receipt = self.initial_deposit(value=value, duration_seconds=duration, progress=progress)
|
||||
assign_receipt = self.assign_beneficiary(checksum_address=beneficiary_address)
|
||||
self.initial_deposit(value=value, duration_seconds=duration, progress=progress)
|
||||
self.assign_beneficiary(checksum_address=beneficiary_address)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
self.enroll_principal_contract()
|
||||
return dict(deposit_receipt=deposit_receipt, assign_receipt=assign_receipt)
|
||||
|
||||
def deploy(self, initial_deployment: bool = True, gas_limit: int = None, progress=None) -> dict:
|
||||
"""Deploy a new instance of PreallocationEscrow to the blockchain."""
|
||||
|
@ -892,12 +891,14 @@ class PreallocationEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin
|
|||
router_contract.address,
|
||||
self.token_contract.address)
|
||||
|
||||
preallocation_escrow_contract, deploy_receipt = self.blockchain.deploy_contract(*args, gas_limit=gas_limit, enroll=False)
|
||||
preallocation_escrow_contract, deploy_receipt = self.blockchain.deploy_contract(*args,
|
||||
gas_limit=gas_limit,
|
||||
enroll=False)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
self._contract = preallocation_escrow_contract
|
||||
self.deployment_receipts.update({'deployment': deploy_receipt})
|
||||
self.deployment_receipts.update({self.deployment_steps[0]: deploy_receipt})
|
||||
return deploy_receipt
|
||||
|
||||
def get_contract_abi(self):
|
||||
|
|
|
@ -404,7 +404,7 @@ class AllocationRegistry(LocalContractRegistry):
|
|||
try:
|
||||
_ = self.search(beneficiary_address=beneficiary_address)
|
||||
return True
|
||||
except self.UnknownBeneficiary:
|
||||
except (self.UnknownBeneficiary, self.NoAllocationRegistry):
|
||||
return False
|
||||
# TODO: Tests!
|
||||
|
||||
|
|
|
@ -55,14 +55,12 @@ def test_deploy_and_allocate(agency, token_economics, test_registry):
|
|||
assert token_agent.get_balance(address=origin) > 1
|
||||
|
||||
# Start allocating tokens
|
||||
deposit_receipts, approve_hashes = list(), dict()
|
||||
for address, deployer in deployments.items():
|
||||
assert deployer.deployer_address == origin
|
||||
|
||||
deposit_receipt = deployer.initial_deposit(value=allocation, duration_seconds=token_economics.maximum_rewarded_periods)
|
||||
deposit_receipts.append(deposit_receipt)
|
||||
deployer.initial_deposit(value=allocation, duration_seconds=token_economics.maximum_rewarded_periods)
|
||||
|
||||
beneficiary = random.choice(testerchain.unassigned_accounts)
|
||||
_assign_receipt = deployer.assign_beneficiary(beneficiary)
|
||||
|
||||
assert len(deposit_receipts) == number_of_deployments == len(deployments)
|
||||
assert number_of_deployments == len(deployments)
|
||||
|
|
Loading…
Reference in New Issue