def _set_from_tx(self, tx: Dict) -> None: if not self.sender: self.sender = EthAddress(tx["from"]) self.receiver = EthAddress(tx["to"]) if tx["to"] else None self.value = Wei(tx["value"]) self.gas_price = tx["gasPrice"] self.gas_limit = tx["gas"] self.input = tx["input"] self.nonce = tx["nonce"] # if receiver is a known contract, set function name if not self.fn_name and state._find_contract(tx["to"]) is not None: contract = state._find_contract(tx["to"]) self.contract_name = contract._name self.fn_name = contract.get_method(tx["input"])
def __init__( self, initial_speed: str = "standard", max_speed: str = "rapid", increment: float = 1.125, block_duration: int = 2, max_gas_price: Wei = None, ): super().__init__(block_duration) if initial_speed not in ("rapid", "fast", "standard", "slow"): raise ValueError("`initial_speed` must be one of: rapid, fast, standard, slow") self.initial_speed = initial_speed self.max_speed = max_speed self.increment = increment self.max_gas_price = Wei(max_gas_price) or 2**256 - 1
def _set_from_tx(self, tx): if not self.sender: self.sender = tx['from'] self.receiver = tx['to'] self.value = Wei(tx['value']) self.gas_price = tx['gasPrice'] self.gas_limit = tx['gas'] self.input = tx['input'] self.nonce = tx['nonce'] # if receiver is a known contract, set function name if tx['to'] and _contracts.find(tx['to']) is not None: self.receiver = _contracts.find(tx['to']) if not self.fn_name: self.contract_name = self.receiver._name self.fn_name = self.receiver.get_method(tx['input'])
def estimate_gas(self, to, amount, data=""): '''Estimates the gas cost for a transaction. Raises VirtualMachineError if the transaction would revert. Args: to: Account instance or address string of transaction recipient. amount: Amount of ether to send in wei. data: Transaction data hexstring. Returns: Estimated gas value in wei.''' return web3.eth.estimateGas({ 'from': self.address, 'to': str(to), 'value': Wei(amount), 'data': HexBytes(data) })
def estimate_gas( self, to: "Account" = None, amount: int = 0, gas_price: int = None, data: str = None ) -> int: """ Estimate the gas cost for a transaction. Raises VirtualMachineError if the transaction would revert. Arguments --------- to : Account, optional Account instance or address string of transaction recipient. amount : int, optional Amount of ether to send in wei. gas_price : int, optional Gas price of the transaction. data : str, optional Transaction data hexstring. Returns ------- Estimated gas value in wei. """ tx: Dict = { "from": self.address, "to": to_address(str(to)) if to else None, "value": Wei(amount), "data": HexBytes(data or ""), } if gas_price is not None: tx["gasPrice"] = web3.toHex(gas_price) try: return web3.eth.estimate_gas(tx) except ValueError as exc: revert_gas_limit = CONFIG.active_network["settings"]["reverting_tx_gas_limit"] if revert_gas_limit == "max": revert_gas_limit = web3.eth.get_block("latest")["gasLimit"] CONFIG.active_network["settings"]["reverting_tx_gas_limit"] = revert_gas_limit if revert_gas_limit: return revert_gas_limit msg = exc.args[0]["message"] if isinstance(exc.args[0], dict) else str(exc) raise ValueError( f"Gas estimation failed: '{msg}'. This transaction will likely revert. " "If you wish to broadcast, you must set the gas limit manually." )
def estimate_gas(self, to: "Account" = None, amount: int = 0, gas_price: int = None, data: str = None) -> int: """ Estimate the gas cost for a transaction. Raises VirtualMachineError if the transaction would revert. Arguments --------- to : Account, optional Account instance or address string of transaction recipient. amount : int, optional Amount of ether to send in wei. gas_price : int, optional Gas price of the transaction. data : str, optional Transaction data hexstring. Returns ------- Estimated gas value in wei. """ tx: Dict = { "from": self.address, "to": str(to) if to else None, "value": Wei(amount), "data": HexBytes(data or ""), } if gas_price is not None: tx["gasPrice"] = gas_price try: return web3.eth.estimateGas(tx) except ValueError: revert_gas_limit = CONFIG.active_network["settings"][ "reverting_tx_gas_limit"] if revert_gas_limit == "max": revert_gas_limit = web3.eth.getBlock("latest")["gasLimit"] CONFIG.active_network["settings"][ "reverting_tx_gas_limit"] = revert_gas_limit if revert_gas_limit: return revert_gas_limit raise
def gas_price(*args): '''Gets and optionally sets the default gas price. * If an integer value is given, this will be the default gas price. * If set to None, True or False, the gas price is determined automatically.''' if not is_connected(): raise ConnectionError("Not connected to any network") if args: if args[0] in (None, False, True): CONFIG['active_network']['gas_price'] = False else: try: price = Wei(args[0]) except ValueError: raise TypeError(f"Invalid gas price '{args[0]}'") CONFIG['active_network']['gas_price'] = price return CONFIG['active_network']['gas_price']
def gas_price(*args: Tuple[Union[int, str, bool, None]]) -> Union[int, bool]: """Gets and optionally sets the default gas price. * If an integer value is given, this will be the default gas price. * If set to 'auto', the gas price is determined automatically.""" if not is_connected(): raise ConnectionError("Not connected to any network") if args: if args[0] in (None, False, True, "auto"): CONFIG["active_network"]["gas_price"] = False else: try: price = Wei(args[0]) except ValueError: raise TypeError(f"Invalid gas price '{args[0]}'") CONFIG["active_network"]["gas_price"] = price return CONFIG["active_network"]["gas_price"]
def max_fee(*args: Tuple[Union[int, str, bool, None]]) -> Union[int, bool]: """ Gets and optionally sets the default max fee per gas. * If a Wei value is given, this will be the default max fee. * If set to None or False, transactions will default to using gas price. """ if not is_connected(): raise ConnectionError("Not connected to any network") if args: if args[0] in (None, False): CONFIG.active_network["settings"]["max_fee"] = None else: try: price = Wei(args[0]) except ValueError: raise TypeError(f"Invalid max fee '{args[0]}'") CONFIG.active_network["settings"]["max_fee"] = price return CONFIG.active_network["settings"]["max_fee"]
def estimate_gas( self, to: Union[str, "Accounts"], amount: Optional[int], data: str = "" ) -> int: """Estimates the gas cost for a transaction. Raises VirtualMachineError if the transaction would revert. Args: to: Account instance or address string of transaction recipient. amount: Amount of ether to send in wei. data: Transaction data hexstring. Returns: Estimated gas value in wei.""" try: return web3.eth.estimateGas( {"from": self.address, "to": str(to), "value": Wei(amount), "data": HexBytes(data)} ) except ValueError: if CONFIG["active_network"]["reverting_tx_gas_limit"]: return CONFIG["active_network"]["reverting_tx_gas_limit"] raise
def _add_internal_xfer(self, from_: str, to: str, value: str) -> None: self._internal_transfers.append( # type: ignore {"from": EthAddress(from_), "to": EthAddress(to), "value": Wei(f"0x{value}")} )
def balance(self): '''Returns the current ether balance of the contract, in wei.''' balance = web3.eth.getBalance(self.address) return Wei(balance)
def balance(self): '''Returns the current balance at the address, in wei.''' balance = web3.eth.getBalance(self.address) return Wei(balance)
def test_lt(): assert Wei("1 ether") < "2 ether"
def test_ge(): assert Wei("2 ether") >= "1 ether" assert Wei("2 ether") >= "2 ether"
def deploy( self, contract: Any, *args: Tuple, amount: int = 0, gas_limit: Optional[int] = None, gas_buffer: Optional[float] = None, gas_price: Optional[int] = None, nonce: Optional[int] = None, required_confs: int = 1, allow_revert: bool = None, silent: bool = None, publish_source: bool = False, ) -> Any: """Deploys a contract. Args: contract: ContractContainer instance. *args: Constructor arguments. The last argument may optionally be a dictionary of transaction values. Kwargs: amount: Amount of ether to send with transaction, in wei. gas_limit: Gas limit of the transaction. gas_buffer: Multiplier to apply to gas limit. gas_price: Gas price of the transaction. nonce: Nonce to use for the transaction. Returns: * Contract instance if the transaction confirms and the contract exists * TransactionReceipt if the transaction is pending or reverts """ if gas_limit and gas_buffer: raise ValueError("Cannot set gas_limit and gas_buffer together") data = contract.deploy.encode_input(*args) if silent is None: silent = bool(CONFIG.mode == "test" or CONFIG.argv["silent"]) with self._lock: try: gas_price, gas_strategy, gas_iter = self._gas_price(gas_price) gas_limit = Wei(gas_limit) or self._gas_limit( None, amount, gas_price, gas_buffer, data) txid = self._transact( # type: ignore { "from": self.address, "value": Wei(amount), "nonce": nonce if nonce is not None else self._pending_nonce(), "gasPrice": gas_price, "gas": gas_limit, "data": HexBytes(data), }, allow_revert, ) exc, revert_data = None, None except ValueError as e: exc = VirtualMachineError(e) if not hasattr(exc, "txid"): raise exc from None txid = exc.txid revert_data = (exc.revert_msg, exc.pc, exc.revert_type) receipt = TransactionReceipt( txid, self, silent=silent, required_confs=required_confs, is_blocking=False, name=contract._name + ".constructor", revert_data=revert_data, ) receipt = self._await_confirmation(receipt, required_confs, gas_strategy, gas_iter) add_thread = threading.Thread(target=contract._add_from_tx, args=(receipt, ), daemon=True) add_thread.start() if rpc.is_active(): undo_thread = threading.Thread( target=Chain()._add_to_undo_buffer, args=( receipt, self.deploy, (contract, *args), { "amount": amount, "gas_limit": gas_limit, "gas_buffer": gas_buffer, "gas_price": gas_price, }, ), daemon=True, ) undo_thread.start() if receipt.status != 1: receipt._raise_if_reverted(exc) return receipt add_thread.join() try: deployed_contract = contract.at(receipt.contract_address) if publish_source: contract.publish_source(deployed_contract, silent=silent) return deployed_contract except ContractNotFound: # if the contract self-destructed during deployment return receipt
def _gas_price(self) -> Wei: gas_price = CONFIG.active_network["settings"]["gas_price"] if isinstance(gas_price, bool) or gas_price in (None, "auto"): return web3.eth.gasPrice return Wei(gas_price)
def __init__(self, initial_gas_price: Wei, max_gas_price: Wei, time_duration: int = 30): super().__init__(time_duration) self.initial_gas_price = Wei(initial_gas_price) self.max_gas_price = Wei(max_gas_price)
def test_le(): assert Wei("1 ether") <= "2 ether" assert Wei("1 ether") <= "1 ether"
def transfer( self, to: "Accounts" = None, amount: int = 0, gas_limit: Optional[int] = None, gas_price: Optional[int] = None, data: str = None, nonce: Optional[int] = None, required_confs: int = 1, silent: bool = False, ) -> "TransactionReceipt": """ Broadcast a transaction from this account. Kwargs: to: Account instance or address string to transfer to. amount: Amount of ether to send, in wei. gas_limit: Gas limit of the transaction. gas_price: Gas price of the transaction. nonce: Nonce to use for the transaction. data: Hexstring of data to include in transaction. silent: Toggles console verbosity. Returns: TransactionReceipt object """ with self._lock: tx = { "from": self.address, "value": Wei(amount), "nonce": nonce if nonce is not None else self._pending_nonce(), "gasPrice": Wei(gas_price) if gas_price is not None else self._gas_price(), "gas": Wei(gas_limit) or self._gas_limit(to, amount, data), "data": HexBytes(data or ""), } if to: tx["to"] = to_address(str(to)) try: txid = self._transact(tx) # type: ignore exc, revert_data = None, None except ValueError as e: exc = VirtualMachineError(e) if not hasattr(exc, "txid"): raise exc from None txid = exc.txid revert_data = (exc.revert_msg, exc.pc, exc.revert_type) receipt = TransactionReceipt(txid, self, required_confs=required_confs, silent=silent, revert_data=revert_data) if rpc.is_active(): undo_thread = threading.Thread( target=rpc._add_to_undo_buffer, args=(receipt, self.transfer, (to, amount, gas_limit, gas_price, data, None), {}), daemon=True, ) undo_thread.start() receipt._raise_if_reverted(exc) return receipt
def deploy( self, contract: Any, *args: Tuple, amount: int = 0, gas_limit: Optional[int] = None, gas_price: Optional[int] = None, nonce: Optional[int] = None, required_confs: int = 1, ) -> Any: """Deploys a contract. Args: contract: ContractContainer instance. *args: Constructor arguments. The last argument may optionally be a dictionary of transaction values. Kwargs: amount: Amount of ether to send with transaction, in wei. gas_limit: Gas limit of the transaction. gas_price: Gas price of the transaction. nonce: Nonce to use for the transaction. Returns: * Contract instance if the transaction confirms and the contract exists * TransactionReceipt if the transaction is pending or reverts """ evm = contract._build["compiler"]["evm_version"] if rpc.is_active() and not rpc.evm_compatible(evm): raise IncompatibleEVMVersion( f"Local RPC using '{rpc.evm_version()}' but contract was compiled for '{evm}'" ) data = contract.deploy.encode_input(*args) with self._lock: try: txid = self._transact( # type: ignore { "from": self.address, "value": Wei(amount), "nonce": nonce if nonce is not None else self._pending_nonce(), "gasPrice": Wei(gas_price) or self._gas_price(), "gas": Wei(gas_limit) or self._gas_limit(None, amount, data), "data": HexBytes(data), } ) exc, revert_data = None, None except ValueError as e: exc = VirtualMachineError(e) if not hasattr(exc, "txid"): raise exc from None txid = exc.txid revert_data = (exc.revert_msg, exc.pc, exc.revert_type) receipt = TransactionReceipt( txid, self, required_confs=required_confs, name=contract._name + ".constructor", revert_data=revert_data, ) add_thread = threading.Thread(target=contract._add_from_tx, args=(receipt, ), daemon=True) add_thread.start() if rpc.is_active(): undo_thread = threading.Thread( target=rpc._add_to_undo_buffer, args=( receipt, self.deploy, (contract, *args), { "amount": amount, "gas_limit": gas_limit, "gas_price": gas_price }, ), daemon=True, ) undo_thread.start() if receipt.status != 1: receipt._raise_if_reverted(exc) return receipt add_thread.join() try: return contract.at(receipt.contract_address) except ContractNotFound: # if the contract self-destructed during deployment return receipt
def test_bytes(): assert Wei(b"\xff") == 255
def test_nonetype(): assert Wei(None) == 0
def deploy( self, contract: Any, *args: Tuple, amount: int = 0, gas_limit: Optional[int] = None, gas_price: Optional[int] = None, ) -> Any: """Deploys a contract. Args: contract: ContractContainer instance. *args: Constructor arguments. The last argument may optionally be a dictionary of transaction values. Kwargs: amount: Amount of ether to send with transaction, in wei. gas_limit: Gas limit of the transaction. gas_price: Gas price of the transaction. Returns: * Contract instance if the transaction confirms * TransactionReceipt if the transaction is pending or reverts""" evm = contract._build["compiler"]["evm_version"] if rpc.is_active() and not rpc.evm_compatible(evm): raise IncompatibleEVMVersion( f"Local RPC using '{rpc.evm_version()}' but contract was compiled for '{evm}'" ) data = contract.deploy.encode_input(*args) try: txid = self._transact( # type: ignore { "from": self.address, "value": Wei(amount), "nonce": self.nonce, "gasPrice": Wei(gas_price) or self._gas_price(), "gas": Wei(gas_limit) or self._gas_limit(None, amount, data), "data": HexBytes(data), } ) revert_data = None except ValueError as e: txid, revert_data = _raise_or_return_tx(e) tx = TransactionReceipt(txid, self, name=contract._name + ".constructor", revert_data=revert_data) add_thread = threading.Thread(target=contract._add_from_tx, args=(tx, ), daemon=True) add_thread.start() if rpc.is_active(): rpc._add_to_undo_buffer( self.deploy, (contract, *args), { "amount": amount, "gas_limit": gas_limit, "gas_price": gas_price }, ) if tx.status != 1: return tx add_thread.join() return contract.at(tx.contract_address)
def block_gas_limit(self) -> Wei: if time.time() > self._block_gas_time + 3600: block = web3.eth.getBlock("latest") self._block_gas_limit = block["gasLimit"] self._block_gas_time = block["timestamp"] return Wei(self._block_gas_limit)
def _gas_limit(self, to, amount, data=""): if CONFIG['active_network']['gas_limit'] not in (True, False, None): return Wei(CONFIG['active_network']['gas_limit']) return self.estimate_gas(to, amount, data)
def balance(self) -> Wei: """Returns the current balance at the address, in wei.""" balance = web3.eth.get_balance(self.address) return Wei(balance)
def _gas_price(self): return Wei(CONFIG['active_network']['gas_price'] or web3.eth.gasPrice)
def transfer( self, to: "Account" = None, amount: int = 0, gas_limit: Optional[int] = None, gas_buffer: Optional[float] = None, gas_price: Optional[int] = None, data: str = None, nonce: Optional[int] = None, required_confs: int = 1, allow_revert: bool = None, silent: bool = None, ) -> TransactionReceipt: """ Broadcast a transaction from this account. Kwargs: to: Account instance or address string to transfer to. amount: Amount of ether to send, in wei. gas_limit: Gas limit of the transaction. gas_buffer: Multiplier to apply to gas limit. gas_price: Gas price of the transaction. nonce: Nonce to use for the transaction. data: Hexstring of data to include in transaction. silent: Toggles console verbosity. Returns: TransactionReceipt object """ if gas_limit and gas_buffer: raise ValueError("Cannot set gas_limit and gas_buffer together") if silent is None: silent = bool(CONFIG.mode == "test" or CONFIG.argv["silent"]) with self._lock: gas_price, gas_strategy, gas_iter = self._gas_price(gas_price) gas_limit = Wei(gas_limit) or self._gas_limit( to, amount, gas_price, gas_buffer, data) tx = { "from": self.address, "value": Wei(amount), "nonce": nonce if nonce is not None else self._pending_nonce(), "gasPrice": gas_price, "gas": gas_limit, "data": HexBytes(data or ""), } if to: tx["to"] = to_address(str(to)) try: txid = self._transact(tx, allow_revert) # type: ignore exc, revert_data = None, None except ValueError as e: exc = VirtualMachineError(e) if not hasattr(exc, "txid"): raise exc from None txid = exc.txid revert_data = (exc.revert_msg, exc.pc, exc.revert_type) receipt = TransactionReceipt( txid, self, required_confs=required_confs, is_blocking=False, silent=silent, revert_data=revert_data, ) receipt = self._await_confirmation(receipt, required_confs, gas_strategy, gas_iter) if rpc.is_active(): undo_thread = threading.Thread( target=Chain()._add_to_undo_buffer, args=( receipt, self.transfer, (to, amount, gas_limit, gas_buffer, gas_price, data, None), {}, ), daemon=True, ) undo_thread.start() receipt._raise_if_reverted(exc) return receipt
def test_gt(): assert Wei("2 ether") > "1 ether"