Deployment CLI plumbing adjustments: allow for domain-specefic pre-flight checks on upgrade.

pull/2315/head
Kieran Prasch 2020-09-29 20:22:10 -07:00
parent 997a481738
commit e36d4da5d4
6 changed files with 52 additions and 146 deletions

View File

@ -152,7 +152,7 @@ class RegistrySourceManager:
def get_primary_sources(cls):
return [source for source in cls._FALLBACK_CHAIN if source.is_primary]
def fetch_latest_publication(self, registry_class, network: str = NetworksInventory.DEFAULT): # TODO: see #1496
def fetch_latest_publication(self, registry_class, network: str): # TODO: see #1496
"""
Get the latest contract registry data available from a registry source chain.
"""

View File

@ -21,11 +21,10 @@ import click
import os
from constant_sorrow import constants
from constant_sorrow.constants import FULL
from eth_utils.address import to_checksum_address
from typing import Tuple
from nucypher.blockchain.eth.actors import ContractAdministrator, Trustee
from nucypher.blockchain.eth.agents import ContractAgency, MultiSigAgent, NucypherTokenAgent
from nucypher.blockchain.eth.agents import ContractAgency, MultiSigAgent
from nucypher.blockchain.eth.constants import STAKING_ESCROW_CONTRACT_NAME
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.blockchain.eth.networks import NetworksInventory
@ -37,7 +36,6 @@ from nucypher.blockchain.eth.registry import (
)
from nucypher.blockchain.eth.signers.base import Signer
from nucypher.blockchain.eth.signers.software import ClefSigner
from nucypher.blockchain.eth.token import NU
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.cli.actions.auth import get_client_password
from nucypher.cli.actions.confirm import confirm_deployment
@ -52,19 +50,14 @@ from nucypher.cli.literature import (
CONFIRM_NETWORK_ACTIVATION,
CONFIRM_RETARGET,
CONFIRM_SELECTED_ACCOUNT,
CONFIRM_TOKEN_ALLOWANCE,
CONFIRM_TOKEN_TRANSFER,
CONTRACT_DEPLOYMENT_SERIES_BEGIN_ADVISORY,
CONTRACT_IS_NOT_OWNABLE,
DEPLOYER_ADDRESS_ZERO_ETH,
DEPLOYER_BALANCE,
DISPLAY_SENDER_TOKEN_BALANCE_BEFORE_TRANSFER,
EXISTING_REGISTRY_FOR_DOMAIN,
MINIMUM_POLICY_RATE_EXCEEDED_WARNING,
PROMPT_FOR_ALLOCATION_DATA_FILEPATH,
PROMPT_NEW_OWNER_ADDRESS,
PROMPT_RECIPIENT_CHECKSUM_ADDRESS,
PROMPT_TOKEN_VALUE,
REGISTRY_NOT_AVAILABLE,
SELECT_DEPLOYER_ACCOUNT,
SUCCESSFUL_REGISTRY_CREATION,
@ -76,7 +69,7 @@ from nucypher.cli.literature import (
SUCCESSFUL_SAVE_MULTISIG_TX_PROPOSAL,
SUCCESSFUL_UPGRADE,
UNKNOWN_CONTRACT_NAME,
IDENTICAL_REGISTRY_WARNING
IDENTICAL_REGISTRY_WARNING, DEPLOYER_IS_NOT_OWNER
)
from nucypher.cli.options import (
group_options,
@ -106,7 +99,6 @@ from nucypher.cli.utils import (
establish_deployer_registry,
initialize_deployer_interface
)
from nucypher.types import NuNits
option_deployer_address = click.option('--deployer-address', help="Deployer's checksum address", type=EIP55_CHECKSUM_ADDRESS)
option_registry_infile = click.option('--registry-infile', help="Input path for contract registry file", type=EXISTING_READABLE_FILE)
@ -138,7 +130,8 @@ class ActorOptions:
se_test_mode,
ignore_solidity_check,
gas_strategy: str,
signer_uri: str
signer_uri: str,
network: str
):
self.provider_uri = provider_uri
@ -156,6 +149,7 @@ class ActorOptions:
self.poa = poa
self.se_test_mode = se_test_mode
self.ignore_solidity_check = ignore_solidity_check
self.network = network
def create_actor(self,
emitter: StdoutEmitter,
@ -175,11 +169,13 @@ class ActorOptions:
#
# Establish Registry
#
local_registry = establish_deployer_registry(emitter=emitter,
use_existing_registry=bool(self.contract_name), # TODO: Eh?
use_existing_registry=bool(self.contract_name), # TODO: Issue #2314
registry_infile=self.registry_infile,
registry_outfile=self.registry_outfile,
dev=self.dev)
dev=self.dev,
network=self.network)
#
# Make Authenticated Deployment Actor
#
@ -240,7 +236,8 @@ group_actor_options = group_options(
se_test_mode=click.option('--se-test-mode', help="Enable test mode for StakingEscrow in deployment.", is_flag=True),
config_root=option_config_root,
etherscan=option_etherscan,
ignore_solidity_check=option_ignore_solidity_version
ignore_solidity_check=option_ignore_solidity_version,
network=option_network(required=True, default=NetworksInventory.DEFAULT)
)
@ -333,24 +330,25 @@ def upgrade(general_config, actor_options, retarget, target_address, ignore_depl
contract_name = actor_options.contract_name
# Check deployer address is owner
agent = ContractAgency.get_agent_by_contract_name(contract_name=contract_name, registry=local_registry)
dispatcher = agent.get_proxy_contract() # Implicit verification of updatability
dispatcher_owner = dispatcher.owner # contract read
if deployer_address != dispatcher_owner:
DEPLOYER_IS_NOT_OWNER = f"Address {deployer_address} is not the owner of {contract_name}'s" \
f" Dispatcher ({dispatcher.address}). Aborting." # TODO: Move to literature.py
emitter.echo(DEPLOYER_IS_NOT_OWNER)
Deployer = ADMINISTRATOR.deployers[contract_name]
deployer = Deployer(registry=local_registry)
if Deployer._ownable and deployer_address != deployer.owner: # blockchain read
emitter.echo(DEPLOYER_IS_NOT_OWNER.format(deployer_address=deployer_address,
contract_name=contract_name,
agent=deployer.make_agent()))
raise click.Abort()
else:
emitter.echo('✓ Verified deployer address as contract owner', color='green')
# Check registry ID has changed locally compared to remote source
github_registry = establish_deployer_registry(emitter=emitter, download_registry=True)
if github_registry.id == local_registry.id:
github_registry = establish_deployer_registry(emitter=emitter, download_registry=True, network=actor_options.network)
if (github_registry.id == local_registry.id) and not actor_options.force:
emitter.echo(IDENTICAL_REGISTRY_WARNING.format(github_registry=github_registry,
local_registry=local_registry), color='red')
raise click.Abort()
else:
emitter.echo('✓ Verified local registry contains updates', color='green')
if not contract_name:
raise click.BadArgumentUsage(message="--contract-name is required when using --upgrade")
#
# Business
@ -397,6 +395,7 @@ def upgrade(general_config, actor_options, retarget, target_address, ignore_depl
message = SUCCESSFUL_RETARGET.format(contract_name=contract_name, target_address=target_address)
emitter.message(message, color='green')
paint_receipt_summary(emitter=emitter, receipt=receipt)
else:
if not actor_options.force:
click.confirm(CONFIRM_BEGIN_UPGRADE.format(contract_name=contract_name), abort=True)

View File

@ -475,6 +475,7 @@ WARNING: --etherscan is disabled. If you want to see deployed contracts and TXs
IDENTICAL_REGISTRY_WARNING = "Local registry ({local_registry.id}) is identical to the one on GitHub ({github_registry.id})."
DEPLOYER_IS_NOT_OWNER = "Address {deployer_address} is not the owner of {contract_name}'s Dispatcher ({agent.contract_address}). Aborting."
#
# Multisig

View File

@ -105,6 +105,7 @@ def make_cli_character(character_config,
def establish_deployer_registry(emitter,
network: str = None,
registry_infile: str = None,
registry_outfile: str = None,
use_existing_registry: bool = False,
@ -113,7 +114,7 @@ def establish_deployer_registry(emitter,
) -> BaseContractRegistry:
if download_registry:
registry = InMemoryContractRegistry.from_latest_publication()
registry = InMemoryContractRegistry.from_latest_publication(network=network)
emitter.message(PRODUCTION_REGISTRY_ADVISORY.format(source=registry.source))
return registry

View File

@ -20,9 +20,10 @@ import json
import os
import pytest
from nucypher.blockchain.eth.actors import ContractAdministrator
from nucypher.blockchain.eth.agents import (AdjudicatorAgent, ContractAgency, NucypherTokenAgent, PolicyManagerAgent,
StakingEscrowAgent)
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.agents import NucypherTokenAgent
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.blockchain.eth.registry import LocalContractRegistry
from nucypher.blockchain.eth.sol.compile import SOLIDITY_COMPILER_VERSION
@ -33,6 +34,14 @@ PLANNED_UPGRADES = 4
CONTRACTS_TO_UPGRADE = ('StakingEscrow', 'PolicyManager', 'Adjudicator', 'StakingInterface')
@pytest.fixture(autouse=True)
def monkeypatch_confirmations(testerchain, monkeypatch):
def monkey_block_until_enough_confirmations(client, transaction_hash, timeout, confirmations):
receipt = testerchain.wait_for_receipt(txhash=transaction_hash)
return receipt
monkeypatch.setattr(EthereumClient, 'block_until_enough_confirmations', monkey_block_until_enough_confirmations)
@pytest.fixture(scope="module")
def registry_filepath(temp_dir_path):
return os.path.join(temp_dir_path, 'nucypher-test-autodeploy.json')
@ -45,75 +54,6 @@ def test_echo_solidity_version(click_runner):
assert str(SOLIDITY_COMPILER_VERSION) in result.output, 'Solidity version text was not produced.'
@pytest.mark.skip("Retired test")
def test_nucypher_deploy_contracts(click_runner,
token_economics,
registry_filepath,
testerchain):
#
# Main
#
assert not os.path.exists(registry_filepath), f"Registry File '{registry_filepath}' Exists."
assert not os.path.lexists(registry_filepath), f"Registry File '{registry_filepath}' Exists."
command = ['contracts',
'--registry-outfile', registry_filepath,
'--provider', TEST_PROVIDER_URI,
'--se-test-mode']
user_input = '0\n' + YES_ENTER + 'DEPLOY'
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Ensure there is a report on each primary contract
contract_names = tuple(a.contract_name for a in ContractAdministrator.primary_deployer_classes)
for registry_name in contract_names:
assert registry_name in result.output
# Check that the primary contract registry was written
# and peek at some of the registered entries
assert os.path.isfile(registry_filepath)
with open(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 contract_names:
assert registry_name in raw_registry_data
# Ensure the Registry is JSON deserializable
registry_data = json.loads(raw_registry_data)
# and that is has the correct number of entries
assert len(registry_data) == 9
# Read several records
token_record, escrow_record, dispatcher_record, *other_records = registry_data
registered_name, registered_version, registered_address, registered_abi = token_record
#
# Agency
#
registry = LocalContractRegistry(filepath=registry_filepath)
token_agent = NucypherTokenAgent(registry=registry)
assert token_agent.contract_name == registered_name
assert token_agent.contract_name == registered_name
assert token_agent.contract_address == registered_address
assert token_agent.contract.version == registered_version
# Now show that we can use contract Agency and read from the blockchain
assert token_agent.get_balance() == 0
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
assert staking_agent.get_current_period()
assert staking_agent.contract.functions.isTestContract().call()
# and at least the others can be instantiated
assert PolicyManagerAgent(registry=registry)
assert AdjudicatorAgent(registry=registry)
def test_deploy_single_contract(click_runner, tempfile_path):
# Perform the Test
@ -125,59 +65,21 @@ def test_deploy_single_contract(click_runner, tempfile_path):
user_input = '0\n' + YES_ENTER
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
assert result.exit_code == 0, result.output
def test_transfer_tokens(click_runner, registry_filepath, get_random_checksum_address, testerchain):
def test_upgrade_contracts(click_runner, test_registry_source_manager, test_registry,
testerchain, registry_filepath, agency):
# Let's transfer some NU to a random stranger
recipient_address = get_random_checksum_address()
registry = LocalContractRegistry(filepath=registry_filepath)
token_agent = NucypherTokenAgent(registry=registry)
assert token_agent.get_balance(address=recipient_address) == 0
command = ['transfer-tokens',
'--target-address', recipient_address,
'--value', 42,
'--registry-infile', registry_filepath,
'--provider', TEST_PROVIDER_URI]
user_input = '0\n' + YES_ENTER + YES_ENTER
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Check that the NU has arrived to the recipient
assert token_agent.get_balance(address=recipient_address) == 42
# Let's approve an allowance to a random spender
spender_address = get_random_checksum_address()
owner_address = testerchain.client.accounts[0]
assert token_agent.get_allowance(spender=spender_address, owner=owner_address) == 0
command = ['transfer-tokens',
'--target-address', spender_address,
'--value', 42,
'--allowance',
'--registry-infile', registry_filepath,
'--provider', TEST_PROVIDER_URI]
user_input = '0\n' + YES_ENTER + YES_ENTER
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# Check that the NU was approved for the spender
assert token_agent.get_allowance(spender=spender_address, owner=owner_address) == 42
def test_upgrade_contracts(click_runner, registry_filepath, testerchain):
NetworksInventory.DEFAULT = TEMPORARY_DOMAIN
registry_filepath = test_registry.commit(filepath=registry_filepath)
#
# Setup
#
# Check the existing state of the registry before the meat and potatoes
expected_enrollments = 9
expected_enrollments = 10
with open(registry_filepath, 'r') as file:
raw_registry_data = file.read()
registry_data = json.loads(raw_registry_data)
@ -190,7 +92,9 @@ def test_upgrade_contracts(click_runner, registry_filepath, testerchain):
cli_action = 'upgrade'
base_command = ('--registry-infile', registry_filepath,
'--provider', TEST_PROVIDER_URI,
'--confirmations', 1)
'--confirmations', 30,
'--network', TEMPORARY_DOMAIN,
'--force') # skip some preflights
#
# Stage Upgrades
@ -251,7 +155,7 @@ def test_upgrade_contracts(click_runner, registry_filepath, testerchain):
# Execute upgrade (Meat)
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert "Successfully deployed" in result.output
# Mutate the version tracking
@ -299,7 +203,7 @@ def test_upgrade_contracts(click_runner, registry_filepath, testerchain):
assert targeted_address == new_address
def test_rollback(click_runner, testerchain, registry_filepath):
def test_rollback(click_runner, testerchain, registry_filepath, agency):
"""Roll 'em back!"""
# Stage Rollbacks

View File

@ -15,6 +15,7 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest