mirror of https://github.com/nucypher/nucypher.git
Formalize upgrade, rollback, and deploy as deploy CLI actions, dehydrate tests and check every upgrade / rollback opportunity.
parent
7d64d7c15c
commit
28e5c78e49
|
@ -34,7 +34,6 @@ from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
|||
@click.argument('action')
|
||||
@click.option('--force', is_flag=True)
|
||||
@click.option('--poa', help="Inject POA middleware", is_flag=True)
|
||||
@click.option('--upgrade', help="Upgrade an already deployed contract", is_flag=True)
|
||||
@click.option('--no-compile', help="Disables solidity contract compilation", is_flag=True)
|
||||
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
|
||||
@click.option('--config-root', help="Custom configuration directory", type=click.Path())
|
||||
|
@ -50,7 +49,6 @@ from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
|||
def deploy(click_config,
|
||||
action,
|
||||
poa,
|
||||
upgrade,
|
||||
provider_uri,
|
||||
deployer_address,
|
||||
contract_name,
|
||||
|
@ -95,7 +93,7 @@ def deploy(click_config,
|
|||
# Upgrade
|
||||
#
|
||||
|
||||
if upgrade:
|
||||
if action == 'upgrade':
|
||||
if not contract_name:
|
||||
raise click.BadArgumentUsage(message="--contract-name is required when using --upgrade")
|
||||
existing_secret = click.prompt('Enter existing contract upgrade secret', hide_input=True, confirmation_prompt=True)
|
||||
|
@ -103,26 +101,31 @@ def deploy(click_config,
|
|||
deployer.upgrade_contract(contract_name=contract_name, existing_secret=existing_secret, new_plaintext_secret=new_secret)
|
||||
return
|
||||
|
||||
|
||||
#
|
||||
# Deploy Single Contract
|
||||
#
|
||||
|
||||
if contract_name:
|
||||
|
||||
try:
|
||||
deployer_func = deployer.deployers[contract_name]
|
||||
except KeyError:
|
||||
message = "No such contract {}. Available contracts are {}".format(contract_name, deployer.deployers.keys())
|
||||
click.secho(message, fg='red', bold=True)
|
||||
raise click.Abort()
|
||||
else:
|
||||
_txs, _agent = deployer_func()
|
||||
|
||||
elif action == 'rollback':
|
||||
existing_secret = click.prompt('Enter existing contract upgrade secret', hide_input=True, confirmation_prompt=True)
|
||||
new_secret = click.prompt('Enter new contract upgrade secret', hide_input=True, confirmation_prompt=True)
|
||||
deployer.rollback_contract(contract_name=contract_name, existing_plaintext_secret=existing_secret, new_plaintext_secret=new_secret)
|
||||
return
|
||||
|
||||
# The Big Three
|
||||
if action == "contracts":
|
||||
elif action == "deploy":
|
||||
|
||||
#
|
||||
# Deploy Single Contract
|
||||
#
|
||||
|
||||
if contract_name:
|
||||
|
||||
try:
|
||||
deployer_func = deployer.deployers[contract_name]
|
||||
except KeyError:
|
||||
message = "No such contract {}. Available contracts are {}".format(contract_name,
|
||||
deployer.deployers.keys())
|
||||
click.secho(message, fg='red', bold=True)
|
||||
raise click.Abort()
|
||||
else:
|
||||
_txs, _agent = deployer_func()
|
||||
|
||||
return
|
||||
|
||||
secrets = click_config.collect_deployment_secrets()
|
||||
|
||||
|
@ -169,6 +172,7 @@ def deploy(click_config,
|
|||
click.secho("Block #{} | {}\n".format(receipt['blockNumber'], receipt['blockHash'].hex()))
|
||||
|
||||
click.secho("Cumulative Gas Consumption: {} gas\n".format(total_gas_used), bold=True, fg='blue')
|
||||
return
|
||||
|
||||
elif action == "allocations":
|
||||
if not allocation_infile:
|
||||
|
@ -176,6 +180,7 @@ def deploy(click_config,
|
|||
click.confirm("Continue deploying and allocating?", abort=True)
|
||||
deployer.deploy_beneficiaries_from_file(allocation_data_filepath=allocation_infile,
|
||||
allocation_outfile=allocation_outfile)
|
||||
return
|
||||
|
||||
elif action == "transfer":
|
||||
token_agent = NucypherTokenAgent(blockchain=blockchain)
|
||||
|
@ -189,6 +194,7 @@ def deploy(click_config,
|
|||
click.confirm(f"Are you absolutely sure you want to destroy the contract registry at {registry_filepath}?", abort=True)
|
||||
os.remove(registry_filepath)
|
||||
click.secho(f"Successfully destroyed {registry_filepath}", fg='red')
|
||||
return
|
||||
|
||||
else:
|
||||
raise click.BadArgumentUsage(message=f"Unknown action '{action}'")
|
||||
|
|
|
@ -25,7 +25,7 @@ def test_nucypher_deploy_contracts(testerchain, click_runner, mock_primary_regis
|
|||
# We start with a blockchain node, and nothing else...
|
||||
assert not os.path.isfile(mock_primary_registry_filepath)
|
||||
|
||||
command = ('contracts',
|
||||
command = ('deploy',
|
||||
'--registry-outfile', mock_primary_registry_filepath,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
|
@ -74,86 +74,122 @@ def test_nucypher_deploy_contracts(testerchain, click_runner, mock_primary_regis
|
|||
|
||||
|
||||
def test_upgrade_contracts(click_runner):
|
||||
contracts_to_upgrade = ('MinersEscrow', # Initial upgrades (version 2)
|
||||
'PolicyManager',
|
||||
'MiningAdjudicator',
|
||||
'UserEscrowProxy',
|
||||
|
||||
miner_agent = MinerAgent()
|
||||
contract_name = miner_agent.contract_name
|
||||
bound_miner_escrow_address = miner_agent.contract_address
|
||||
# Additional Upgrades
|
||||
'MinersEscrow', # v3
|
||||
'MinersEscrow', # v4
|
||||
'MiningAdjudicator', # v3
|
||||
'PolicyAgent,', # v3
|
||||
'UserEscrowProxy' # v3
|
||||
)
|
||||
|
||||
command = ('contracts',
|
||||
'--upgrade',
|
||||
'--contract-name', contract_name,
|
||||
'--registry-infile', MOCK_REGISTRY_FILEPATH,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
|
||||
user_input = 'Y\n' \
|
||||
+ f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2 \
|
||||
+ f'{INSECURE_DEVELOPMENT_PASSWORD[::-1]}\n' * 2
|
||||
|
||||
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
||||
# original bound address is the same
|
||||
assert bound_miner_escrow_address == miner_agent.contract_address
|
||||
|
||||
with open(MOCK_REGISTRY_FILEPATH, 'r') as file:
|
||||
|
||||
# Ensure every contract's name was written to the file, somehow
|
||||
raw_registry_data = file.read()
|
||||
for registry_name in Deployer.contract_names:
|
||||
assert registry_name in raw_registry_data
|
||||
assert raw_registry_data.count(contract_name) == 2
|
||||
|
||||
registry_data = json.loads(raw_registry_data)
|
||||
assert len(registry_data) == 10
|
||||
executed_upgrades = {name: 0 for name in set(contracts_to_upgrade)}
|
||||
|
||||
blockchain = Blockchain.connect(registry=EthereumContractRegistry(registry_filepath=MOCK_REGISTRY_FILEPATH))
|
||||
records = blockchain.interface.registry.search(contract_name=contract_name)
|
||||
assert len(records) == 2
|
||||
old, new = records
|
||||
assert old[1] != new[1] # deployments are different addresses
|
||||
|
||||
# Ensure the dispatcher targets the new deployment
|
||||
dispatcher = blockchain.interface.get_proxy(target_address=new[1], proxy_name='Dispatcher')
|
||||
targeted_address = dispatcher.functions.target().call()
|
||||
assert targeted_address != old[1]
|
||||
assert targeted_address == new[1]
|
||||
yes = 'Y\n'
|
||||
version_1_secret = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
|
||||
version_2_secret = f'{INSECURE_DEVELOPMENT_PASSWORD[::-1]}\n' * 2
|
||||
version_3_secret = f'{INSECURE_DEVELOPMENT_PASSWORD[2:-2:-1]}\n' * 2
|
||||
version_4_secret = f'{INSECURE_DEVELOPMENT_PASSWORD[1:-3:-1]}\n' * 2
|
||||
|
||||
command = ('contracts',
|
||||
'--upgrade',
|
||||
'--contract-name', 'PolicyManager',
|
||||
'--registry-infile', MOCK_REGISTRY_FILEPATH,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
user_input_1_to_2 = yes + version_1_secret + version_2_secret
|
||||
user_input_2_to_3 = yes + version_2_secret + version_3_secret
|
||||
user_input_3_to_4 = yes + version_3_secret + version_4_secret
|
||||
|
||||
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
records = blockchain.interface.registry.search(contract_name='PolicyManager')
|
||||
assert len(records) == 2
|
||||
user_inputs = {0: user_input_1_to_2,
|
||||
1: user_input_2_to_3,
|
||||
2: user_input_3_to_4}
|
||||
|
||||
command = ('contracts',
|
||||
'--upgrade',
|
||||
'--contract-name', 'MiningAdjudicator',
|
||||
'--registry-infile', MOCK_REGISTRY_FILEPATH,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
expected_registrations = 9
|
||||
with open(MOCK_REGISTRY_FILEPATH, 'r') as file:
|
||||
raw_registry_data = file.read()
|
||||
registry_data = json.loads(raw_registry_data)
|
||||
assert len(registry_data) == expected_registrations
|
||||
|
||||
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
records = blockchain.interface.registry.search(contract_name='MiningAdjudicator')
|
||||
assert len(records) == 2
|
||||
for contract_name in contracts_to_upgrade:
|
||||
|
||||
command = ('contracts',
|
||||
'--upgrade',
|
||||
'--contract-name', 'UserEscrowProxy',
|
||||
'--registry-infile', MOCK_REGISTRY_FILEPATH,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
command = ('upgrade',
|
||||
'--contract-name', contract_name,
|
||||
'--registry-infile', MOCK_REGISTRY_FILEPATH,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
|
||||
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
records = blockchain.interface.registry.search(contract_name='UserEscrowProxy')
|
||||
assert len(records) == 2
|
||||
user_input = user_inputs[executed_upgrades[contract_name]]
|
||||
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
||||
executed_upgrades[contract_name] += 1
|
||||
expected_registrations += 1
|
||||
|
||||
with open(MOCK_REGISTRY_FILEPATH, 'r') as file:
|
||||
raw_registry_data = file.read()
|
||||
registry_data = json.loads(raw_registry_data)
|
||||
assert len(registry_data) == expected_registrations
|
||||
|
||||
# Check that there is more than one entry, since we've deployed a "version 2"
|
||||
registered_names = [r[0] for r in registry_data]
|
||||
assert registered_names.count(contract_name) == 2
|
||||
|
||||
# Ensure deployments are different addresses
|
||||
records = blockchain.interface.registry.search(contract_name=contract_name)
|
||||
assert len(records) == executed_upgrades[contract_name] + 1
|
||||
|
||||
# Get the last two entries
|
||||
old, new = records[-2:]
|
||||
old_name, old_address, *abi = old
|
||||
new_name, new_address, *abi = new
|
||||
assert old_name == new_name
|
||||
assert old_address != new_address
|
||||
|
||||
# Ensure the proxy targets the new deployment
|
||||
proxy_name = 'Dispatcher' if contract_name != "UserEscrowProxy" else "UserEscrowLibraryLinker"
|
||||
proxy = blockchain.interface.get_proxy(target_address=new_address, proxy_name=proxy_name)
|
||||
targeted_address = proxy.functions.target().call()
|
||||
assert targeted_address != old_address
|
||||
assert targeted_address == new_address
|
||||
|
||||
|
||||
def test_rollback(click_runner):
|
||||
"""Roll 'em all back!"""
|
||||
|
||||
contracts_to_rollback = ('MinersEscrow', 'PolicyManager', 'MiningAdjudicator')
|
||||
|
||||
user_input = 'Y\n' \
|
||||
+ f'{INSECURE_DEVELOPMENT_PASSWORD[::-1]}\n' * 2 \
|
||||
+ f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
|
||||
|
||||
blockchain = Blockchain.connect(registry=EthereumContractRegistry(registry_filepath=MOCK_REGISTRY_FILEPATH))
|
||||
|
||||
for contract_name in contracts_to_rollback:
|
||||
|
||||
command = ('rollback',
|
||||
'--contract-name', contract_name,
|
||||
'--registry-infile', MOCK_REGISTRY_FILEPATH,
|
||||
'--provider-uri', TEST_PROVIDER_URI,
|
||||
'--poa')
|
||||
|
||||
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
||||
records = blockchain.interface.registry.search(contract_name='UserEscrowProxy')
|
||||
assert len(records) == 2
|
||||
|
||||
old, new = records
|
||||
_name, old_address, *abi = old
|
||||
_name, new_address, *abi = new
|
||||
assert old_address != new_address
|
||||
|
||||
# Ensure the proxy targets the old deployment
|
||||
proxy_name = 'Dispatcher'
|
||||
proxy = blockchain.interface.get_proxy(target_address=new_address, proxy_name=proxy_name)
|
||||
targeted_address = proxy.functions.target().call()
|
||||
assert targeted_address != new_address
|
||||
assert targeted_address == old_address
|
||||
|
||||
|
||||
def test_nucypher_deploy_allocations(testerchain, click_runner, mock_allocation_infile, token_economics):
|
||||
|
|
Loading…
Reference in New Issue