def call(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> Any: """ Call the contract method without broadcasting a transaction. Arguments --------- *args Contract method inputs. You can optionally provide a dictionary of transaction properties as the last arg. block_identifier : int | str | bytes, optional A block number or hash that the call is executed at. If not given, the latest block used. Raises `ValueError` if this value is too far in the past and you are not using an archival node. Returns ------- Contract method return value(s). """ args, tx = _get_tx(self._owner, args) if tx["from"]: tx["from"] = str(tx["from"]) del tx["required_confs"] tx.update({"to": self._address, "data": self.encode_input(*args)}) try: data = web3.eth.call({k: v for k, v in tx.items() if v}, block_identifier) except ValueError as e: raise VirtualMachineError(e) from None return self.decode_output(data)
def _check_for_revert(self, tx): if ('broadcast_reverting_tx' not in CONFIG['active_network'] or CONFIG['active_network']['broadcast_reverting_tx']): return try: web3.eth.call(dict((k, v) for k, v in tx.items() if v)) except ValueError as e: raise VirtualMachineError(e) from None
def _raise_or_return_tx(exc: ValueError) -> Any: try: data = eval(str(exc))["data"] txid = next(i for i in data.keys() if i[:2] == "0x") reason = data[txid]["reason"] if "reason" in data[txid] else None pc = data[txid]["program_counter"] - 1 error = data[txid]["error"] return txid, [reason, pc, error] except SyntaxError: raise exc except Exception: raise VirtualMachineError(exc) from None
def _raise_or_return_tx(exc): try: data = eval(str(exc))['data'] txid = next(i for i in data.keys() if i[:2] == "0x") reason = data[txid]['reason'] if 'reason' in data[txid] else None pc = data[txid]['program_counter'] - 1 error = data[txid]['error'] return txid, [reason, pc, error] except SyntaxError: raise exc except Exception: raise VirtualMachineError(exc) from None
def call(self, *args: Tuple) -> Any: """Calls the contract method without broadcasting a transaction. Args: *args: Contract method inputs. You can optionally provide a dictionary of transaction properties as the last arg. Returns: Contract method return value(s).""" args, tx = _get_tx(self._owner, args) if tx["from"]: tx["from"] = str(tx["from"]) tx.update({"to": self._address, "data": self.encode_input(*args)}) try: data = web3.eth.call(dict((k, v) for k, v in tx.items() if v)) except ValueError as e: raise VirtualMachineError(e) from None return self.decode_output(data)
def call(self, *args): '''Calls the contract method without broadcasting a transaction. Args: *args: Contract method inputs. You can optionally provide a dictionary of transaction properties as the last arg. Returns: Contract method return value(s).''' args, tx = _get_tx(self._owner, args) if tx['from']: tx['from'] = str(tx['from']) tx.update({'to': self._address, 'data': self.encode_abi(*args)}) try: data = web3.eth.call(dict((k, v) for k, v in tx.items() if v)) except ValueError as e: raise VirtualMachineError(e) from None return self.decode_abi(data)
def __init__(self, txid, sender=None, silent=False, name='', callback=None): if type(txid) is not str: txid = txid.hex() if CONFIG['logging']['tx'] and not silent: color.print_colors("\nTransaction sent: "+txid) history._add_tx(self) self.__dict__.update({ '_trace': None, 'fn_name': name, 'txid': txid, 'sender': sender, 'receiver': None, 'value': None, 'gas_price': None, 'gas_limit': None, 'gas_used': None, 'input': None, 'nonce': None, 'block_number': None, 'txindex': None, 'contract_address': None, 'logs': [], 'status': -1, }) t = threading.Thread( target=self._await_confirm, args=[silent, callback], daemon=True ) t.start() try: t.join() if config.ARGV['mode'] == "script" and not self.status: raise VirtualMachineError({ "message": "revert "+(self.revert_msg or ""), "source": self.error(1) }) except KeyboardInterrupt: if config.ARGV['mode'] == "script": raise
def deploy_raw(self, contract_name: str, hex_str: str): with self._lock: try: gas_price = 0 gas_limit = Wei(12000000) txid = self._transact( # type: ignore { "from": self.address, "value": Wei(0), "nonce": self._pending_nonce(), "gasPrice": gas_price, "gas": gas_limit, "data": HexBytes(hex_str), }, False, ) 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=False, required_confs=1, is_blocking=False, name=contract_name, revert_data=revert_data, ) receipt = self._await_confirmation(receipt, 1, None, None) return receipt
def _raise(msg: str, source: str) -> None: raise VirtualMachineError({"message": msg, "source": source})
def _check_for_revert(self, tx: Dict) -> None: if not CONFIG.active_network["settings"]["reverting_tx_gas_limit"]: try: web3.eth.call(dict((k, v) for k, v in tx.items() if v)) except ValueError as e: raise VirtualMachineError(e) from None
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_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 __init__( self, txid: Union[str, bytes], sender: Any = None, silent: bool = False, name: str = "", revert_data: Optional[Tuple] = None, ) -> None: """Instantiates a new TransactionReceipt object. Args: txid: hexstring transaction ID sender: sender as a hex string or Account object silent: toggles console verbosity name: contract function being called revert_data: (revert string, program counter, revert type) """ if CONFIG.mode == "test": silent = True if isinstance(txid, bytes): txid = txid.hex() if not silent: print(f"Transaction sent: {color('bright blue')}{txid}{color}") history._add_tx(self) self._raw_trace = None self._trace = None self._events = None self._return_value = None self._revert_msg = None self._modified_state = None self._new_contracts = None self._internal_transfers = None self._confirmed = threading.Event() self.sender = sender self.status = -1 self.txid = txid self.fn_name = name if name and "." in name: self.contract_name, self.fn_name = name.split(".", maxsplit=1) # avoid querying the trace to get the revert string if possible revert_msg, self._revert_pc, revert_type = revert_data or (None, None, None) if revert_msg: # revert message was returned self._revert_msg = revert_msg elif revert_type in ("revert", "invalid opcode"): # check for dev revert string as a comment self._revert_msg = build._get_dev_revert(self._revert_pc) else: self._revert_msg = revert_type # threaded to allow impatient users to ctrl-c to stop waiting in the console confirm_thread = threading.Thread( target=self._await_confirmation, args=(silent,), daemon=True ) confirm_thread.start() try: confirm_thread.join() if CONFIG.mode == "console": return # if coverage evaluation is active, evaluate the trace if ( CONFIG.argv["coverage"] and not coverage._check_cached(self.coverage_hash) and self.trace ): self._expand_trace() if not self.status: if self._revert_msg is None: # no revert message and unable to check dev string - have to get trace self._expand_trace() if self.contract_address: source = "" else: source = ( self._traceback_string() if CONFIG.argv["revert"] else self._error_string(1) ) raise VirtualMachineError({"message": self._revert_msg or "", "source": source}) except KeyboardInterrupt: if CONFIG.mode != "console": raise
def _raise(msg, source): raise VirtualMachineError({"message": msg, "source": source})
def _raise(msg: str, source: Any) -> None: # source: Union[str, 'Accounts'] raise VirtualMachineError({"message": msg, "source": source})
def _make_transaction( self, to: Optional["Account"], amount: int, gas_limit: Optional[int], gas_buffer: Optional[float], gas_price: Optional[int], max_fee: Optional[int], priority_fee: Optional[int], data: str, nonce: Optional[int], fn_name: str, required_confs: int, allow_revert: Optional[bool], silent: Optional[bool], ) -> Tuple[TransactionReceipt, Optional[Exception]]: # shared logic for `transfer` and `deploy` 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"]) if gas_price is None: # if gas price is not explicitly set, load the default max fee and priority fee if max_fee is None: max_fee = CONFIG.active_network["settings"]["max_fee"] or None if priority_fee is None: priority_fee = CONFIG.active_network["settings"][ "priority_fee"] or None if priority_fee == "auto": priority_fee = Chain().priority_fee try: # if max fee and priority fee are not set, use gas price if max_fee is None and priority_fee is None: gas_price, gas_strategy, gas_iter = self._gas_price(gas_price) else: gas_strategy, gas_iter = None, None gas_limit = Wei(gas_limit) or self._gas_limit( to, amount, gas_price or max_fee, gas_buffer, data) except ValueError as e: raise VirtualMachineError(e) from None with self._lock: # we use a lock here to prevent nonce issues when sending many tx's at once tx = { "from": self.address, "value": Wei(amount), "nonce": nonce if nonce is not None else self._pending_nonce(), "gas": web3.toHex(gas_limit), "data": HexBytes(data), } if to: tx["to"] = to_address(str(to)) tx = _apply_fee_to_tx(tx, gas_price, max_fee, priority_fee) txid = None while True: try: response = self._transact(tx, allow_revert) # type: ignore exc, revert_data = None, None if txid is None: txid = HexBytes(response).hex() if not silent: print( f"\rTransaction sent: {color('bright blue')}{txid}{color}" ) except ValueError as e: if txid is None: exc = VirtualMachineError(e) if not hasattr(exc, "txid"): raise exc from None txid = exc.txid print( f"\rTransaction sent: {color('bright blue')}{txid}{color}" ) revert_data = (exc.revert_msg, exc.pc, exc.revert_type) try: receipt = TransactionReceipt( txid, self, silent=silent, required_confs=required_confs, is_blocking=False, name=fn_name, revert_data=revert_data, ) # type: ignore break except (TransactionNotFound, ValueError): if not silent: sys.stdout.write( f" Awaiting transaction in the mempool... {_marker[0]}\r" ) sys.stdout.flush() _marker.rotate(1) time.sleep(1) receipt = self._await_confirmation(receipt, required_confs, gas_strategy, gas_iter) return receipt, exc
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 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 raise_or_return_tx(exc): data = eval(str(exc)) try: return next(i for i in data['data'].keys() if i[:2] == "0x") except Exception: raise VirtualMachineError(exc)