Merge pull request #2319 from KPrasch/upgrader

Bug fixes for upgrade CLI
pull/2339/head
K Prasch 2020-10-06 08:30:43 -07:00 committed by GitHub
commit 4e2391b14b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 142 additions and 46 deletions

View File

@ -224,9 +224,8 @@ class EthereumClient:
@property
def chain_name(self) -> str:
if not self.is_local:
return PUBLIC_CHAINS[int(self.chain_id)]
name = LOCAL_CHAINS.get(self.chain_id, UNKNOWN_DEVELOPMENT_CHAIN_ID)
chain_inventory = LOCAL_CHAINS if self.is_local else PUBLIC_CHAINS
name = chain_inventory.get(self.chain_id, UNKNOWN_DEVELOPMENT_CHAIN_ID)
return name
@property

View File

@ -60,6 +60,7 @@ from nucypher.blockchain.eth.sol.compile import SolidityCompiler
from nucypher.blockchain.eth.utils import get_transaction_name, prettify_eth_amount
from nucypher.characters.control.emitters import JSONRPCStdoutEmitter, StdoutEmitter
from nucypher.utilities.datafeeds import datafeed_fallback_gas_price_strategy
from nucypher.utilities.ethereum import encode_constructor_arguments
from nucypher.utilities.logging import GlobalLoggerSettings, Logger
@ -825,13 +826,19 @@ class BlockchainDeployerInterface(BlockchainInterface):
f"deployer address {deployer_address} "
f"and parameters {pprint_args}")
transaction_function = contract_factory.constructor(*constructor_args, **constructor_kwargs)
constructor_function = contract_factory.constructor(*constructor_args, **constructor_kwargs)
constructor_calldata = encode_constructor_arguments(self.client.w3,
constructor_function,
*constructor_args,
**constructor_kwargs)
if not constructor_calldata:
self.log.info(f"Constructor calldata: {constructor_calldata}")
#
# Transmit the deployment tx #
#
receipt = self.send_transaction(contract_function=transaction_function,
receipt = self.send_transaction(contract_function=constructor_function,
sender_address=deployer_address,
payload=deploy_transaction,
confirmations=confirmations)

View File

@ -25,6 +25,7 @@ from eth_utils import is_address, is_hex, to_checksum_address
from web3 import Web3
from web3.contract import ContractConstructor, ContractFunction
from nucypher.blockchain.eth.clients import PUBLIC_CHAINS
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS
@ -85,18 +86,22 @@ def estimate_block_number_for_period(period: int, seconds_per_period: int, late
def etherscan_url(item, network: str, is_token=False) -> str:
if network is None or network is UNKNOWN_DEVELOPMENT_CHAIN_ID:
raise ValueError("A network must be provided")
elif network == 'mainnet':
if network == PUBLIC_CHAINS[1]: # Mainnet chain ID is 1
domain = "https://etherscan.io"
else:
network = network.lower()
testnets_supported_by_etherscan = ('ropsten', 'goerli', 'rinkeby', 'kovan')
testnets_supported_by_etherscan = (PUBLIC_CHAINS[3], # Ropsten
PUBLIC_CHAINS[4], # Rinkeby
PUBLIC_CHAINS[5], # Goerli
PUBLIC_CHAINS[42], # Kovan
)
if network in testnets_supported_by_etherscan:
domain = f"https://{network}.etherscan.io"
domain = f"https://{network.lower()}.etherscan.io"
else:
raise ValueError(f"'{network}' network not supported by Etherscan")
if is_address(item):
item_type = 'address' if not is_token else 'token'
item_type = 'token' if is_token else 'address'
item = to_checksum_address(item)
elif is_hex(item) and len(item) == 2 + 32*2: # If it's a hash...
item_type = 'tx'

View File

@ -14,11 +14,15 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import Type, Union
import click
from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
from nucypher.blockchain.eth.deployers import BaseContractDeployer
from nucypher.blockchain.eth.registry import LocalContractRegistry, InMemoryContractRegistry
from nucypher.cli.literature import CONFIRM_VERSIONED_UPGRADE
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, VersionedContract, BlockchainInterface
from nucypher.blockchain.eth.token import NU
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.cli.literature import (
@ -32,7 +36,9 @@ from nucypher.cli.literature import (
CONFIRM_STAGED_STAKE,
RESTAKING_AGREEMENT,
RESTAKING_LOCK_AGREEMENT,
WINDING_DOWN_AGREEMENT, SNAPSHOTS_DISABLING_AGREEMENT, CONFIRM_DISABLE_SNAPSHOTS
WINDING_DOWN_AGREEMENT,
SNAPSHOTS_DISABLING_AGREEMENT,
CONFIRM_DISABLE_SNAPSHOTS
)
from nucypher.config.node import CharacterConfiguration
@ -115,3 +121,26 @@ def confirm_destroy_configuration(config: CharacterConfiguration) -> bool:
database=database)
click.confirm(confirmation, abort=True)
return True
def verify_upgrade_details(blockchain: Union[BlockchainDeployerInterface, BlockchainInterface],
registry: LocalContractRegistry,
deployer: Type[BaseContractDeployer],
) -> None:
"""
Compares the versions of two 'implementation' contracts using a local and source registry.
"""
old_contract: VersionedContract = blockchain.get_contract_by_name(
registry=registry,
contract_name=deployer.contract_name,
proxy_name=deployer.agency._proxy_name,
use_proxy_address=False
)
new_contract = blockchain.find_raw_contract_data(contract_name=deployer.contract_name)
new_version = new_contract[0] # Handle index error?
click.confirm(CONFIRM_VERSIONED_UPGRADE.format(contract_name=deployer.contract_name,
old_version=old_contract.version,
new_version=new_version), abort=True)

View File

@ -16,12 +16,12 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import json
from typing import Tuple
import click
import os
from constant_sorrow import constants
from constant_sorrow.constants import FULL
from typing import Tuple
from nucypher.blockchain.eth.actors import ContractAdministrator, Trustee
from nucypher.blockchain.eth.agents import ContractAgency, MultiSigAgent
@ -36,9 +36,10 @@ 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.sol.__conf__ import SOLIDITY_COMPILER_VERSION
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.cli.actions.auth import get_client_password
from nucypher.cli.actions.confirm import confirm_deployment
from nucypher.cli.actions.confirm import confirm_deployment, verify_upgrade_details
from nucypher.cli.actions.select import select_client_account
from nucypher.cli.config import group_general_config
from nucypher.cli.literature import (
@ -69,9 +70,9 @@ from nucypher.cli.literature import (
SUCCESSFUL_SAVE_MULTISIG_TX_PROPOSAL,
SUCCESSFUL_UPGRADE,
UNKNOWN_CONTRACT_NAME,
IDENTICAL_REGISTRY_WARNING,
DEPLOYER_IS_NOT_OWNER,
CONFIRM_VERSIONED_UPGRADE
REGISTRY_PUBLICATION_HINT,
ETHERSCAN_VERIFY_HINT
)
from nucypher.cli.options import (
group_options,
@ -239,7 +240,7 @@ group_actor_options = group_options(
config_root=option_config_root,
etherscan=option_etherscan,
ignore_solidity_check=option_ignore_solidity_version,
network=option_network(required=True, default=NetworksInventory.DEFAULT)
network=option_network(required=True)
)
@ -293,8 +294,10 @@ def download_registry(general_config, config_root, registry_outfile, network, fo
@option_registry_infile
@option_deployer_address
@option_poa
@option_network(required=False, default=NetworksInventory.DEFAULT)
@option_ignore_solidity_version
def inspect(general_config, provider_uri, config_root, registry_infile, deployer_address, poa, ignore_solidity_check):
def inspect(general_config, provider_uri, config_root, registry_infile, deployer_address,
poa, ignore_solidity_check, network):
"""Echo owner information and bare contract metadata."""
emitter = general_config.emitter
ensure_config_root(config_root)
@ -302,9 +305,11 @@ def inspect(general_config, provider_uri, config_root, registry_infile, deployer
provider_uri=provider_uri,
emitter=emitter,
ignore_solidity_check=ignore_solidity_check)
download_required = not bool(registry_infile)
registry = establish_deployer_registry(emitter=emitter,
registry_infile=registry_infile,
download_registry=not bool(registry_infile))
download_registry=download_required,
network=network if download_required else None)
paint_deployer_contract_inspection(emitter=emitter,
registry=registry,
deployer_address=deployer_address)
@ -328,24 +333,23 @@ def upgrade(general_config, actor_options, retarget, target_address, ignore_depl
emitter = general_config.emitter
ADMINISTRATOR, deployer_address, blockchain, local_registry = actor_options.create_actor(emitter, is_multisig=bool(multisig)) # FIXME: Workaround for building MultiSig TXs | NRN
#
# Pre-flight
#
contract_name = actor_options.contract_name
if not contract_name:
raise click.BadArgumentUsage(message="--contract-name is required when using --upgrade")
github_registry = establish_deployer_registry(emitter=emitter, download_registry=True, network=actor_options.network)
try:
# Check contract name exists
Deployer = ADMINISTRATOR.deployers[contract_name]
except KeyError:
message = UNKNOWN_CONTRACT_NAME.format(contract_name=contract_name,
constants=ADMINISTRATOR.deployers.keys())
message = UNKNOWN_CONTRACT_NAME.format(contract_name=contract_name, constants=ADMINISTRATOR.deployers.keys())
emitter.echo(message, color='red', bold=True)
raise click.Abort()
deployer = Deployer(registry=local_registry)
#
# Pre-flight
#
# Check deployer address is owner
if Deployer._ownable and deployer_address != deployer.owner: # blockchain read
emitter.echo(DEPLOYER_IS_NOT_OWNER.format(deployer_address=deployer_address,
@ -355,13 +359,6 @@ def upgrade(general_config, actor_options, retarget, target_address, ignore_depl
else:
emitter.echo('✓ Verified deployer address as contract owner', color='green')
# Check registry ID has changed locally compared to remote source
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')
#
# Business
#
@ -397,6 +394,7 @@ def upgrade(general_config, actor_options, retarget, target_address, ignore_depl
filepath = f'proposal-{trustee.multisig_agent.contract_address[:8]}-TX-{transaction_proposal.nonce}.json'
transaction_proposal.write(filepath=filepath)
emitter.echo(SUCCESSFUL_SAVE_MULTISIG_TX_PROPOSAL.format(filepath=filepath), color='blue', bold=True)
return # Exit
elif retarget:
if not target_address:
@ -407,24 +405,34 @@ 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)
return # Exit
else:
github_registry = establish_deployer_registry(emitter=emitter,
download_registry=True,
network=actor_options.network)
if not actor_options.force:
# Check for human verification of versioned upgrade details
click.confirm(CONFIRM_BEGIN_UPGRADE.format(contract_name=contract_name), abort=True)
if deployer._ownable: # Only ownable + upgradeable contracts apply
old_contract = github_registry.search(contract_name=contract_name)[-1] # latest GH version
new_contract = local_registry.search(contract_name=contract_name)[-1] # latest local version
click.confirm(CONFIRM_VERSIONED_UPGRADE.format(contract_name=contract_name,
old_contract=old_contract,
new_contract=new_contract), abort=True)
verify_upgrade_details(blockchain=blockchain,
registry=github_registry,
deployer=deployer)
# Success
receipts = ADMINISTRATOR.upgrade_contract(contract_name=contract_name,
ignore_deployed=ignore_deployed,
confirmations=confirmations)
emitter.message(SUCCESSFUL_UPGRADE.format(contract_name=contract_name), color='green')
for name, receipt in receipts.items():
paint_receipt_summary(emitter=emitter, receipt=receipt)
emitter.echo(REGISTRY_PUBLICATION_HINT.format(contract_name=contract_name,
local_registry=local_registry,
network=actor_options.network), color='blue')
emitter.echo(ETHERSCAN_VERIFY_HINT.format(solc_version=SOLIDITY_COMPILER_VERSION), color='blue')
return # Exit
@deploy.command()

View File

@ -477,7 +477,22 @@ IDENTICAL_REGISTRY_WARNING = "Local registry ({local_registry.id}) is identical
DEPLOYER_IS_NOT_OWNER = "Address {deployer_address} is not the owner of {contract_name}'s Dispatcher ({agent.contract_address}). Aborting."
CONFIRM_VERSIONED_UPGRADE = "Confirm upgrade {contract_name} from version {old_contract.version} to version {new_contract.version}?"
CONFIRM_VERSIONED_UPGRADE = "Confirm upgrade {contract_name} from version {old_version} to version {new_version}?"
REGISTRY_PUBLICATION_HINT = '''
Remember to commit and/or publish the new registry!
* cp {local_registry.filepath} nucypher/blockchain/eth/contract_registry/{network}/contract_registry.json
* git add nucypher/blockchain/eth/contract_registry/{network}/contract_registry.json
* git commit -m "Update contract registry for {contract_name}"
* Push to the appropriate branch and open a pull request!
'''
ETHERSCAN_VERIFY_HINT = '''
Remember to record deployment parameters for etherscan verification
Compiled with solc version {solc_version}
'''
#
# Multisig

View File

@ -189,6 +189,7 @@ Registry ................ {registry.filepath}
preallocation_escrow_payload = f"""
{staking_interface_agent.contract_name} ......... {bare_contract.address}
~ Version .............. {bare_contract.version}
~ Ethers ............... {Web3.fromWei(blockchain.client.get_balance(bare_contract.address), 'ether')} ETH
~ Tokens ............... {NU.from_nunits(token_agent.get_balance(bare_contract.address))}
~ StakingInterfaceRouter {router_deployer.contract.address}

View File

@ -14,8 +14,11 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from eth_typing import HexStr
from web3 import Web3
from web3._utils.abi import get_constructor_abi, merge_args_and_kwargs
from web3._utils.contracts import encode_abi
from web3.contract import ContractConstructor
def to_bytes32(value=None, hexstr=None) -> bytes:
@ -38,3 +41,20 @@ def get_array_data_location(array_location: int) -> int:
# See https://solidity.readthedocs.io/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays
data_location = Web3.toInt(Web3.keccak(to_bytes32(array_location)))
return data_location
def encode_constructor_arguments(web3: Web3,
constructor_function: ContractConstructor,
*constructor_args, **constructor_kwargs) -> HexStr:
"""
Takes a web3 constructor function and the arguments passed to it, and produces an encoding hex string
of the constructor arguments, following the standard ABI encoding conventions.
If there's no constructor, it returns None.
"""
constructor_abi = get_constructor_abi(constructor_function.abi)
if constructor_abi:
arguments = merge_args_and_kwargs(constructor_abi, constructor_args, constructor_kwargs)
data = encode_abi(web3, constructor_abi, arguments)
else:
data = None
return data

View File

@ -53,6 +53,7 @@ def test_deploy_single_contract(click_runner, tempfile_path):
'--contract-name', NucypherTokenAgent.contract_name,
'--registry-infile', tempfile_path,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--debug']
user_input = '0\n' + YES_ENTER
@ -211,11 +212,12 @@ def test_rollback(click_runner, testerchain, registry_filepath, agency):
command = ('rollback',
'--contract-name', contract_name,
'--registry-infile', registry_filepath,
'--network', TEMPORARY_DOMAIN,
'--provider', TEST_PROVIDER_URI)
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
# TODO unify this, trust more to registry_filepath, reduce calls
registry = LocalContractRegistry(filepath=registry_filepath)

View File

@ -65,6 +65,7 @@ def test_set_range(click_runner, testerchain, agency_local_registry):
'--registry-infile', agency_local_registry.filepath,
'--minimum', minimum,
'--default', default,
'--network', TEMPORARY_DOMAIN,
'--maximum', maximum)
account_index = '0\n'
@ -87,6 +88,7 @@ def test_nucypher_deploy_inspect_fully_deployed(click_runner, agency_local_regis
status_command = ('inspect',
'--registry-infile', agency_local_registry.filepath,
'--network', TEMPORARY_DOMAIN,
'--provider', TEST_PROVIDER_URI)
result = click_runner.invoke(deploy,
@ -120,6 +122,7 @@ def test_transfer_ownership(click_runner, testerchain, agency_local_registry):
'--registry-infile', agency_local_registry.filepath,
'--contract-name', STAKING_ESCROW_CONTRACT_NAME,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--target-address', maclane)
account_index = '0\n'
@ -143,6 +146,7 @@ def test_transfer_ownership(click_runner, testerchain, agency_local_registry):
'--contract-name', STAKING_ESCROW_CONTRACT_NAME,
'--registry-infile', agency_local_registry.filepath,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--target-address', michwill)
user_input = yes
@ -169,6 +173,7 @@ def test_bare_contract_deployment_to_alternate_registry(click_runner, agency_loc
'--provider', TEST_PROVIDER_URI,
'--registry-infile', agency_local_registry.filepath,
'--registry-outfile', ALTERNATE_REGISTRY_FILEPATH,
'--network', TEMPORARY_DOMAIN,
'--ignore-deployed')
user_input = '0\n' + 'Y\n' + 'DEPLOY'
@ -238,6 +243,7 @@ def test_batch_deposits(click_runner,
deploy_command = ('allocations',
'--registry-infile', agency_local_registry.filepath,
'--allocation-infile', mock_allocation_infile,
'--network', TEMPORARY_DOMAIN,
'--provider', TEST_PROVIDER_URI)
account_index = '0\n'
@ -264,6 +270,7 @@ def test_manual_deployment_of_idle_network(click_runner):
command = ('contracts',
'--contract-name', NUCYPHER_TOKEN_CONTRACT_NAME,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--registry-infile', ALTERNATE_REGISTRY_FILEPATH_2)
user_input = '0\n' + YES_ENTER + INSECURE_DEVELOPMENT_PASSWORD
@ -281,6 +288,7 @@ def test_manual_deployment_of_idle_network(click_runner):
'--contract-name', STAKING_ESCROW_CONTRACT_NAME,
'--mode', 'idle',
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--registry-infile', ALTERNATE_REGISTRY_FILEPATH_2)
user_input = '0\n' + YES_ENTER + INSECURE_DEVELOPMENT_PASSWORD
@ -294,6 +302,7 @@ def test_manual_deployment_of_idle_network(click_runner):
command = ('contracts',
'--contract-name', POLICY_MANAGER_CONTRACT_NAME,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--registry-infile', ALTERNATE_REGISTRY_FILEPATH_2)
user_input = '0\n' + YES_ENTER + INSECURE_DEVELOPMENT_PASSWORD
@ -307,6 +316,7 @@ def test_manual_deployment_of_idle_network(click_runner):
command = ('contracts',
'--contract-name', ADJUDICATOR_CONTRACT_NAME,
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--registry-infile', ALTERNATE_REGISTRY_FILEPATH_2)
user_input = '0\n' + YES_ENTER + INSECURE_DEVELOPMENT_PASSWORD
@ -321,6 +331,7 @@ def test_manual_deployment_of_idle_network(click_runner):
'--contract-name', STAKING_ESCROW_CONTRACT_NAME,
'--activate',
'--provider', TEST_PROVIDER_URI,
'--network', TEMPORARY_DOMAIN,
'--registry-infile', ALTERNATE_REGISTRY_FILEPATH_2)
user_input = '0\n' + YES_ENTER + YES_ENTER + INSECURE_DEVELOPMENT_PASSWORD

View File

@ -15,13 +15,12 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_canonical_address
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from tests.utils.solidity import to_32byte_hex
from nucypher.utilities.ethereum import to_32byte_hex
def sign_hash(testerchain, account: str, data_hash: bytes) -> dict:

View File

@ -15,7 +15,6 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from eth_tester.exceptions import TransactionFailed
@ -28,7 +27,8 @@ from nucypher.blockchain.economics import BaseEconomics
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.crypto.api import sha256_digest
from nucypher.crypto.signing import SignatureStamp
from tests.utils.solidity import to_32byte_hex
from nucypher.utilities.ethereum import to_32byte_hex
DISABLED_FIELD = 0

View File

@ -24,7 +24,7 @@ from web3.contract import Contract
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.token import NU
from tests.utils.solidity import get_array_data_location, get_mapping_entry_location, to_bytes32
from nucypher.utilities.ethereum import get_array_data_location, get_mapping_entry_location, to_bytes32
LOCK_RE_STAKE_UNTIL_PERIOD_FIELD = 4