Deployer tooling for accessing untarget, but enrolled contracts by version index.

pull/1344/head
Kieran Prasch 2019-09-19 15:05:18 -07:00
parent 859e572587
commit 2567bdd3ec
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
4 changed files with 74 additions and 34 deletions

View File

@ -165,6 +165,13 @@ class BaseContractDeployer:
agent = self.agency(registry=self.registry, contract=self._contract)
return agent
def get_latest_enrollment(self, registry: BaseContractRegistry) -> Contract:
"""Get the latest enrolled version of the contract from the registry."""
contract = self.blockchain.get_contract_by_name(name=self.contract_name,
registry=registry,
use_proxy_address=False,
version='latest')
return contract
class OwnableContractMixin:
@ -221,15 +228,17 @@ class UpgradeableContractMixin:
raise self.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.")
raise NotImplementedError
@classmethod
def get_latest_version(cls, registry: BaseContractRegistry, provider_uri: str = None) -> Contract:
"""Get the latest version of the contract without assembling it with it's proxy."""
if not cls._upgradeable:
raise cls.ContractNotUpgradeable(f"{cls.contract_name} is not upgradeable.")
def get_principal_contract(self, registry: BaseContractRegistry, provider_uri: str = None) -> Contract:
"""
Get the on-chain targeted version of the principal contract directly
without assembling it with it's proxy.
"""
if not self._upgradeable:
raise cls.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.")
blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri)
contract = blockchain.get_contract_by_name(name=cls.contract_name,
contract = blockchain.get_contract_by_name(name=self.contract_name,
registry=registry,
proxy_name=cls._proxy_deployer.contract_name,
proxy_name=self._proxy_deployer.contract_name,
use_proxy_address=False)
return contract
@ -247,8 +256,8 @@ class UpgradeableContractMixin:
raise self.ContractNotUpgradeable(f"{self.contract_name} is not upgradeable.")
# 1 - Get Bare Contracts
existing_bare_contract = self.get_latest_version(registry=self.registry,
provider_uri=self.blockchain.provider_uri)
existing_bare_contract = self.get_principal_contract(registry=self.registry,
provider_uri=self.blockchain.provider_uri)
proxy_deployer = self._proxy_deployer(registry=self.registry,
target_contract=existing_bare_contract,
@ -275,8 +284,8 @@ class UpgradeableContractMixin:
self.check_deployment_readiness()
# 2 - Get Bare Contracts
existing_bare_contract = self.get_latest_version(registry=self.registry,
provider_uri=self.blockchain.provider_uri)
existing_bare_contract = self.get_principal_contract(registry=self.registry,
provider_uri=self.blockchain.provider_uri)
proxy_deployer = self._proxy_deployer(registry=self.registry,
target_contract=existing_bare_contract,

View File

@ -425,6 +425,7 @@ class BlockchainInterface:
def get_contract_by_name(self,
registry: BaseContractRegistry,
name: str,
version: int = None,
proxy_name: str = None,
use_proxy_address: bool = True
) -> Union[Contract, List[tuple]]:
@ -438,9 +439,9 @@ class BlockchainInterface:
if not target_contract_records:
raise self.UnknownContract(f"No such contract records with name {name}.")
if proxy_name: # It's upgradeable
# Lookup proxies; Search for a published proxy that targets this contract record
if proxy_name:
# Lookup proxies; Search for a published proxy that targets this contract record
proxy_records = registry.search(contract_name=proxy_name)
results = list()
@ -474,11 +475,21 @@ class BlockchainInterface:
except IndexError:
raise self.UnknownContract(f"There are no Dispatcher records targeting '{name}'")
else: # It's not upgradeable
else:
# NOTE: 0 must be allowed as a valid version number
if len(target_contract_records) != 1:
m = "Multiple records registered for non-upgradeable contract {}"
raise self.InterfaceError(m.format(name))
_target_contract_name, selected_address, selected_abi = target_contract_records[0]
if version is None:
m = f"{len(target_contract_records)} records enrolled for contract {name} " \
f"and no version index was supplied."
raise self.InterfaceError(m.format(name))
version = self.__get_version_index(name=name,
version_index=version,
enrollments=len(target_contract_records))
else:
version = -1 # default
_target_contract_name, selected_address, selected_abi = target_contract_records[version]
# Create the contract from selected sources
unified_contract = self.client.w3.eth.contract(abi=selected_abi,
@ -487,6 +498,23 @@ class BlockchainInterface:
return unified_contract
@staticmethod
def __get_version_index(version_index: Union[int, str], enrollments: int, name: str):
version_names = {'latest': -1, 'earliest': 0}
try:
version = version_names[version_index]
except KeyError:
try:
version = int(version_index)
except ValueError:
what_is_this = version_index
raise ValueError(f"'{what_is_this}' is a valid version number")
else:
if version > enrollments - 1:
message = f"Version index '{version}' is larger than the number of enrollments for {name}."
raise ValueError(message)
return version
class BlockchainDeployerInterface(BlockchainInterface):

View File

@ -22,11 +22,12 @@ import tempfile
from abc import ABC, abstractmethod
from json import JSONDecodeError
from os.path import dirname, abspath
from typing import Union
from typing import Union, Iterator
import requests
from constant_sorrow.constants import REGISTRY_COMMITTED
from twisted.logger import Logger
from web3.contract import Contract
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
@ -129,16 +130,16 @@ class BaseContractRegistry(ABC):
return instance
@property
def enrolled_names(self):
def enrolled_names(self) -> Iterator:
entries = iter(record[0] for record in self.read())
return entries
@property
def enrolled_addresses(self):
def enrolled_addresses(self) -> Iterator:
entries = iter(record[1] for record in self.read())
return entries
def enroll(self, contract_name, contract_address, contract_abi):
def enroll(self, contract_name, contract_address, contract_abi) -> None:
"""
Enrolls a contract to the chain registry by writing the name, address,
and abi information to the filesystem as JSON.
@ -157,7 +158,7 @@ class BaseContractRegistry(ABC):
self.write(registry_data)
self.log.info("Enrolled {}:{} into registry.".format(contract_name, contract_address))
def search(self, contract_name: str = None, contract_address: str = None):
def search(self, contract_name: str = None, contract_address: str = None) -> tuple:
"""
Searches the registry for a contract with the provided name or address
and returns the contracts component data.
@ -185,7 +186,8 @@ class BaseContractRegistry(ABC):
self.log.critical(m)
raise self.IllegalRegistry(m.format(contract_address))
return contracts if contract_name else contracts[0]
result = tuple(contracts) if contract_name else contracts[0]
return result
class LocalContractRegistry(BaseContractRegistry):

View File

@ -155,7 +155,7 @@ def test_deploy_bare_upgradeable_contract_deployment(testerchain, test_registry,
new_number_of_enrollments = enrolled_names.count(StakingEscrowDeployer.contract_name)
new_number_of_proxy_enrollments = enrolled_names.count(StakingEscrowDeployer._proxy_deployer.contract_name)
# The prinicipal contract was deployed.
# The principal contract was deployed.
assert new_number_of_enrollments == (old_number_of_enrollments + 1)
# The Dispatcher was not deployed.
@ -169,30 +169,31 @@ def test_manual_proxy_retargeting(testerchain, test_registry, token_economics):
economics=token_economics)
# Get Proxy-Direct
existing_bare_contract = deployer.get_latest_version(registry=test_registry, provider_uri=TEST_PROVIDER_URI)
existing_bare_contract = deployer.get_principal_contract(registry=test_registry, provider_uri=TEST_PROVIDER_URI)
proxy_deployer = StakingEscrowDeployer._proxy_deployer(registry=test_registry,
target_contract=existing_bare_contract,
deployer_address=testerchain.etherbase_account,
bare=True) # acquire agency for the proxy itself.
# Re-Deploy Staking Escrow
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
old_target = proxy_deployer.contract.functions.target().call()
old_secret = bytes("...maybe not.", encoding='utf-8')
new_secret = keccak_digest(bytes('thistimeforsure', encoding='utf-8'))
receipt = deployer.retarget(target_address=staking_agent.contract_address,
# Get the latest un-targeted contract from the registry
latest_deployment = deployer.get_latest_enrollment(registry=test_registry)
receipt = deployer.retarget(target_address=latest_deployment.address,
existing_secret_plaintext=old_secret,
new_secret_hash=new_secret)
assert receipt['status'] == 1
#
# Post-Retargeting
#
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
# Check proxy targets
new_target = proxy_deployer.contract.functions.target().call()
assert old_target != new_target
assert new_target == staking_agent.contract_address
assert new_target == latest_deployment.address
# Check address consistency
new_bare_contract = deployer.get_principal_contract(registry=test_registry, provider_uri=TEST_PROVIDER_URI)
assert new_bare_contract.address == latest_deployment.address == new_target