mirror of https://github.com/nucypher/nucypher.git
Deployment CLI plumbing adjustments: allow for domain-specefic pre-flight checks on upgrade.
parent
997a481738
commit
e36d4da5d4
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -15,6 +15,7 @@
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
|
Loading…
Reference in New Issue