def link_bytecode(cls, attr_dict: Dict[str, str]) -> Type["LinkableContract"]: """ Return a cloned contract factory with the deployment / runtime bytecode linked. :attr_dict: Dict[`ContractType`: `Address`] for all deployment and runtime link references. """ if not cls.unlinked_references and not cls.linked_references: raise BytecodeLinkingError( "Contract factory has no linkable bytecode.") if not cls.needs_bytecode_linking: raise BytecodeLinkingError( "Bytecode for this contract factory does not require bytecode linking." ) cls.validate_attr_dict(attr_dict) bytecode = apply_all_link_refs(cls.bytecode, cls.unlinked_references, attr_dict) runtime = apply_all_link_refs(cls.bytecode_runtime, cls.linked_references, attr_dict) linked_class = cls.factory(cls.web3, bytecode_runtime=runtime, bytecode=bytecode) if linked_class.needs_bytecode_linking: raise BytecodeLinkingError( "Expected class to be fully linked, but class still needs bytecode linking." ) return linked_class
def validate_attr_dict(self, attr_dict: Dict[str, str]) -> None: """ Validates that ContractType keys in attr_dict reference existing manifest ContractTypes. """ attr_dict_names = list(attr_dict.keys()) if not self.unlinked_references and not self.linked_references: raise BytecodeLinkingError( "Unable to validate attr dict, this contract has no linked/unlinked references." ) unlinked_refs = self.unlinked_references or ({}, ) linked_refs = self.linked_references or ({}, ) all_link_refs = unlinked_refs + linked_refs all_link_names = [ref["name"] for ref in all_link_refs if ref] if set(attr_dict_names) != set(all_link_names): raise BytecodeLinkingError( "All link references must be defined when calling " "`link_bytecode` on a contract factory.") for address in attr_dict.values(): if not is_canonical_address(address): raise BytecodeLinkingError( f"Address: {address} as specified in the attr_dict is not " "a valid canoncial address.")
def _resolve_linked_references( self, link_ref: Tuple[int, str, str], deployments: Dict[str, Any]) -> Generator[Tuple[int, bytes], None, None]: # No nested deployment: i.e. 'Owned' offset, link_type, value = link_ref if link_type == "literal": yield offset, to_canonical_address(value) elif value in deployments: yield offset, to_canonical_address(deployments[value]["address"]) # No nested deployment, but invalid ref elif ":" not in value: raise BytecodeLinkingError( f"Contract instance reference: {value} not found in package's deployment data." ) # Expects child pkg in build_dependencies elif value.split(":")[0] not in self.build_dependencies: raise BytecodeLinkingError( f"Expected build dependency: {value.split(':')[0]} not found " "in package's build dependencies.") # Find and return resolved, nested ref else: unresolved_linked_ref = value.split(":", 1)[-1] build_dependency = self.build_dependencies[value.split(":")[0]] yield build_dependency._resolve_link_dependencies( unresolved_linked_ref)
def __init__(self, address: bytes, **kwargs: Any) -> None: if self.needs_bytecode_linking: raise BytecodeLinkingError( "Contract cannot be instantiated until its bytecode is linked." ) validate_address(address) super(LinkableContract, self).__init__(address=address, **kwargs)
def __init__(self, address: bytes, **kwargs: Any) -> None: if self.needs_bytecode_linking: raise BytecodeLinkingError( "Contract cannot be instantiated until its bytecode is linked." ) validate_address(address) # type ignored to allow for undefined **kwargs on `Contract` base class __init__ super().__init__(address=address, **kwargs) # type: ignore
def __init__(self, address: bytes, **kwargs: Any) -> None: if self.needs_bytecode_linking: raise BytecodeLinkingError( "Contract cannot be instantiated until its bytecode is linked." ) validate_address(address) # todo: remove automatic checksumming of address once web3 dep is updated in pytest-ethereum super(LinkableContract, self).__init__(address=to_checksum_address(address), **kwargs)
def validate_attr_dict(self, attr_dict: Dict[str, str]) -> None: """ Validates that ContractType keys in attr_dict reference existing manifest ContractTypes. """ attr_dict_names = attr_dict.keys() if not self.unlinked_references and not self.linked_references: raise BytecodeLinkingError( "Unable to validate attr dict, this contract has no linked/unlinked references." ) unlinked_refs = self.unlinked_references or ({}, ) linked_refs = self.linked_references or ({}, ) all_link_refs = unlinked_refs + linked_refs all_link_names = {ref["name"] for ref in all_link_refs if ref} if attr_dict_names != all_link_names: raise BytecodeLinkingError( "All link references must be defined when calling " "`link_bytecode` on a contract factory.") for address in attr_dict.values(): validate_address(address)
def apply_link_ref(offset: int, length: int, value: bytes, bytecode: bytes) -> bytes: """ Returns the new bytecode with `value` put into the location indicated by `offset` and `length`. """ try: validate_empty_bytes(offset, length, bytecode) except EthPMValidationError: raise BytecodeLinkingError("Link references cannot be applied to bytecode") address = value if is_canonical_address(value) else to_canonical_address(value) new_bytes = ( # Ignore linting error b/c conflict b/w black & flake8 bytecode[:offset] + address + bytecode[offset + length:] # noqa: E201, E203 ) return new_bytes
def get_linked_deployments(deployments: Dict[str, Any]) -> Dict[str, Any]: """ Returns all deployments found in a chain URI's deployment data that contain link dependencies. """ linked_deployments = { dep: data for dep, data in deployments.items() if get_in(("runtimeBytecode", "linkDependencies"), data) } for deployment, data in linked_deployments.items(): if any(link_dep["value"] == deployment for link_dep in data["runtimeBytecode"]["linkDependencies"]): raise BytecodeLinkingError( f"Link dependency found in {deployment} deployment that references its " "own contract instance, which is disallowed") return linked_deployments
def constructor(cls, *args: Any, **kwargs: Any) -> bool: if cls.needs_bytecode_linking: raise BytecodeLinkingError( "Contract cannot be deployed until its bytecode is linked.") return super(LinkableContract, cls).constructor(*args, **kwargs)