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)
Exemple #2
0
 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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
    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)
Exemple #6
0
    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)
Exemple #7
0
 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
Exemple #8
0
    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
Exemple #9
0
def _raise(msg: str, source: str) -> None:
    raise VirtualMachineError({"message": msg, "source": source})
Exemple #10
0
 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
Exemple #11
0
    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
Exemple #12
0
    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
Exemple #14
0
def _raise(msg, source):
    raise VirtualMachineError({"message": msg, "source": source})
Exemple #15
0
def _raise(msg: str, source: Any) -> None:  # source: Union[str, 'Accounts']
    raise VirtualMachineError({"message": msg, "source": source})
Exemple #16
0
    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
Exemple #17
0
    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
Exemple #18
0
    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
Exemple #19
0
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)