コード例 #1
0
    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 _find_contract(tx["to"]) is not None:
            contract = _find_contract(tx["to"])
            self.contract_name = contract._name
            self.fn_name = contract.get_method(tx["input"])
コード例 #2
0
def test_ethaddress_typeerror():
    e = EthAddress("0x0063046686E46Dc6F15918b61AE2B121458534a5")
    with pytest.raises(TypeError):
        e == "potato"
    with pytest.raises(TypeError):
        e == "0x00"
    assert str(e) != "potato"
コード例 #3
0
def _get_last_map(address: EthAddress, sig: str) -> Dict:
    contract = state._find_contract(address)
    last_map = {
        "address": EthAddress(address),
        "jumpDepth": 0,
        "name": None,
        "coverage": False
    }

    if contract:
        if contract.get_method(sig):
            full_fn_name = f"{contract._name}.{contract.get_method(sig)}"
        else:
            full_fn_name = contract._name
        last_map.update(
            contract=contract,
            function=contract.get_method_object(sig),
            name=contract._name,
            internal_calls=[full_fn_name],
            path_map=contract._build.get("allSourcePaths"),
            pc_map=contract._build.get("pcMap"),
        )
        if isinstance(contract._project, project_main.Project):
            # only evaluate coverage for contracts that are part of a `Project`
            last_map["coverage"] = True
            if contract._build["language"] == "Solidity":
                last_map["active_branches"] = set()
    else:
        last_map.update(contract=None,
                        internal_calls=[f"<UnknownContract>.{sig}"],
                        pc_map=None)

    return last_map
コード例 #4
0
ファイル: transaction.py プロジェクト: tomcbean/brownie
def _get_last_map(address: EthAddress, sig: str) -> Dict:
    contract = _find_contract(address)
    last_map = {
        "address": EthAddress(address),
        "jumpDepth": 0,
        "name": None,
        "coverage": False
    }

    if contract:
        if contract.get_method(sig):
            full_fn_name = f"{contract._name}.{contract.get_method(sig)}"
        else:
            full_fn_name = contract._name
        last_map.update(
            contract=contract,
            name=contract._name,
            fn=[full_fn_name],
            path_map=contract._build.get("allSourcePaths"),
            pc_map=contract._build.get("pcMap"),
        )
        if contract._project:
            last_map["coverage"] = True
            if contract._build["language"] == "Solidity":
                last_map["active_branches"] = set()
    else:
        last_map.update(contract=None,
                        fn=[f"<UnknownContract>.{sig}"],
                        pc_map=None)

    return last_map
コード例 #5
0
def _get_last_map(address, sig: str) -> Dict:
    contract = addr_to_contract[address]
    if contract == None:
        # TODO: load abi from explorer
        return

    last_map = {"address": EthAddress(address), "jumpDepth": 0, "coverage": False}

    if contract:
        if contract.get_method(sig):
            full_fn_name = f"{contract.address}.{contract.get_method(sig)}"
        else:
            full_fn_name = contract.address
        last_map.update(
            contract=contract,
            function=contract.get_method_object(sig),
            internal_calls=[full_fn_name],
            # pc_map=contract._build.get("pcMap"),
        )
        # only evaluate coverage for contracts that are part of a `Project`
        last_map["coverage"] = True
        # if contract._build["language"] == "Solidity":
        #     last_map["active_branches"] = set()
        last_map["active_branches"] = set()
    else:
        last_map.update(contract=None, internal_calls=[f"<UnknownContract>.{sig}"], pc_map=None)

    return last_map
コード例 #6
0
ファイル: transaction.py プロジェクト: c0ffeen0w/brownie
def _get_last_map(address: EthAddress, sig: str) -> Dict:
    contract = _find_contract(address)
    last_map = {"address": EthAddress(address), "jumpDepth": 0, "name": None}
    if contract:
        last_map.update({
            "contract": contract,
            "name": contract._name,
            "fn": [f"{contract._name}.{contract.get_method(sig)}"],
        })
        if contract._build:
            last_map["pc_map"] = contract._build["pcMap"]
    else:
        last_map.update({"contract": None, "fn": [f"<UnknownContract>.{sig}"]})
    return last_map
コード例 #7
0
    def get_deployment_address(self, nonce: Optional[int] = None) -> EthAddress:
        """
        Return the address of a contract deployed from this account at the given nonce.

        Arguments
        ---------
        nonce : int, optional
            The nonce of the deployment transaction. If not given, the nonce of the next
            transaction is used.
        """
        if nonce is None:
            nonce = self.nonce

        address = HexBytes(self.address)
        raw = rlp.encode([address, nonce])
        deployment_address = keccak(raw)[12:]

        return EthAddress(deployment_address)
コード例 #8
0
ファイル: transaction.py プロジェクト: trnhgquan/brownie
def _get_last_map(address: EthAddress, sig: str) -> Dict:
    contract = _find_contract(address)
    last_map = {
        "address": EthAddress(address),
        "jumpDepth": 0,
        "name": None,
        "coverage": False
    }
    if contract:
        last_map.update(
            contract=contract,
            name=contract._name,
            fn=[f"{contract._name}.{contract.get_method(sig)}"],
        )
        if contract._build:
            last_map.update(pc_map=contract._build["pcMap"], coverage=True)
            if contract._build["language"] == "Solidity":
                last_map["active_branches"] = set()
    else:
        last_map.update(contract=None, fn=[f"<UnknownContract>.{sig}"])
    return last_map
コード例 #9
0
 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}")}
     )
コード例 #10
0
    def _expand_trace(self) -> None:
        """Adds the following attributes to each step of the stack trace:

        address: The address executing this contract.
        contractName: The name of the contract.
        fn: The name of the function.
        jumpDepth: Number of jumps made since entering this contract. The
                   initial value is 0.
        source: {
            filename: path to the source file for this step
            offset: Start and end offset associated source code
        }
        """
        if self._trace is not None:
            return
        if self._raw_trace is None:
            self._get_trace()
        self._trace = trace = self._raw_trace
        self._new_contracts = []
        self._internal_transfers = []
        if self.contract_address or not trace:
            coverage._add_transaction(self.coverage_hash, {})
            return
        if "fn" in trace[0]:
            return

        if trace[0]["depth"] == 1:
            self._trace_origin = "geth"
            self._call_cost = self.gas_used - trace[0]["gas"] + trace[-1]["gas"]
            for t in trace:
                t["depth"] = t["depth"] - 1
        else:
            self._trace_origin = "ganache"
            self._call_cost = trace[0]["gasCost"]
            for i in range(len(trace) - 1):
                trace[i]["gasCost"] = trace[i + 1]["gasCost"]
            trace[-1]["gasCost"] = 0

        # last_map gives a quick reference of previous values at each depth
        last_map = {0: _get_last_map(self.receiver, self.input[:10])}  # type: ignore
        coverage_eval: Dict = {last_map[0]["name"]: {}}

        for i in range(len(trace)):
            # if depth has increased, tx has called into a different contract
            if trace[i]["depth"] > trace[i - 1]["depth"]:
                step = trace[i - 1]
                if step["op"] in ("CREATE", "CREATE2"):
                    # creating a new contract
                    out = next(x for x in trace[i:] if x["depth"] == step["depth"])
                    address = out["stack"][-1][-40:]
                    sig = f"<{step['op']}>"
                    self._new_contracts.append(EthAddress(address))
                    if int(step["stack"][-1], 16):
                        self._add_internal_xfer(step["address"], address, step["stack"][-1])
                else:
                    # calling an existing contract
                    stack_idx = -4 if step["op"] in ("CALL", "CALLCODE") else -3
                    offset = int(step["stack"][stack_idx], 16) * 2
                    sig = HexBytes("".join(step["memory"])[offset : offset + 8]).hex()
                    address = step["stack"][-2][-40:]

                last_map[trace[i]["depth"]] = _get_last_map(address, sig)
                coverage_eval.setdefault(last_map[trace[i]["depth"]]["name"], {})

            # update trace from last_map
            last = last_map[trace[i]["depth"]]
            trace[i].update(
                address=last["address"],
                contractName=last["name"],
                fn=last["fn"][-1],
                jumpDepth=last["jumpDepth"],
                source=False,
            )

            if trace[i]["op"] == "CALL" and int(trace[i]["stack"][-3], 16):
                self._add_internal_xfer(
                    last["address"], trace[i]["stack"][-2][-40:], trace[i]["stack"][-3]
                )

            if not last["pc_map"]:
                continue
            pc = last["pc_map"][trace[i]["pc"]]

            if "path" not in pc:
                continue
            trace[i]["source"] = {"filename": last["path_map"][pc["path"]], "offset": pc["offset"]}

            if "fn" not in pc:
                continue

            # calculate coverage
            if last["coverage"]:
                if pc["path"] not in coverage_eval[last["name"]]:
                    coverage_eval[last["name"]][pc["path"]] = [set(), set(), set()]
                if "statement" in pc:
                    coverage_eval[last["name"]][pc["path"]][0].add(pc["statement"])
                if "branch" in pc:
                    if pc["op"] != "JUMPI":
                        last["active_branches"].add(pc["branch"])
                    elif "active_branches" not in last or pc["branch"] in last["active_branches"]:
                        # false, true
                        key = 1 if trace[i + 1]["pc"] == trace[i]["pc"] + 1 else 2
                        coverage_eval[last["name"]][pc["path"]][key].add(pc["branch"])
                        if "active_branches" in last:
                            last["active_branches"].remove(pc["branch"])

            # ignore jumps with no function - they are compiler optimizations
            if "jump" in pc:
                # jump 'i' is calling into an internal function
                if pc["jump"] == "i":
                    try:
                        fn = last["pc_map"][trace[i + 1]["pc"]]["fn"]
                    except (KeyError, IndexError):
                        continue
                    if fn != last["fn"][-1]:
                        last["fn"].append(fn)
                        last["jumpDepth"] += 1
                # jump 'o' is returning from an internal function
                elif last["jumpDepth"] > 0:
                    del last["fn"][-1]
                    last["jumpDepth"] -= 1
        coverage._add_transaction(
            self.coverage_hash, dict((k, v) for k, v in coverage_eval.items() if v)
        )
コード例 #11
0
    def _expand_trace(self) -> None:
        """Adds the following attributes to each step of the stack trace:

        address: The address executing this contract.
        contractName: The name of the contract.
        fn: The name of the function.
        jumpDepth: Number of jumps made since entering this contract. The
                   initial value is 0.
        source: {
            filename: path to the source file for this step
            offset: Start and end offset associated source code
        }
        """
        if self._raw_trace is None:
            self._get_trace()
        if self._trace is not None:
            # in case `_get_trace` also expanded the trace, do not repeat
            return

        self._trace = trace = self._raw_trace
        self._new_contracts = []
        self._internal_transfers = []
        self._subcalls = []
        if self.contract_address or not trace:
            coverage._add_transaction(self.coverage_hash, {})
            return

        if trace[0]["depth"] == 1:
            self._trace_origin = "geth"
            self._call_cost = self.gas_used - trace[0]["gas"] + trace[-1]["gas"]
            for t in trace:
                t["depth"] = t["depth"] - 1
        else:
            self._trace_origin = "ganache"
            if trace[0]["gasCost"] >= 21000:
                # in ganache <6.10.0, gas costs are shifted by one step - we can
                # identify this when the first step has a gas cost >= 21000
                self._call_cost = trace[0]["gasCost"]
                for i in range(len(trace) - 1):
                    trace[i]["gasCost"] = trace[i + 1]["gasCost"]
                trace[-1]["gasCost"] = 0
            else:
                self._call_cost = self.gas_used - trace[0]["gas"] + trace[-1][
                    "gas"]

        # last_map gives a quick reference of previous values at each depth
        last_map = {
            0: _get_last_map(self.receiver, self.input[:10])
        }  # type: ignore
        coverage_eval: Dict = {last_map[0]["name"]: {}}

        for i in range(len(trace)):
            # if depth has increased, tx has called into a different contract
            if trace[i]["depth"] > trace[i - 1]["depth"]:
                step = trace[i - 1]
                if step["op"] in ("CREATE", "CREATE2"):
                    # creating a new contract
                    out = next(x for x in trace[i:]
                               if x["depth"] == step["depth"])
                    address = out["stack"][-1][-40:]
                    sig = f"<{step['op']}>"
                    calldata = None
                    self._new_contracts.append(EthAddress(address))
                    if int(step["stack"][-1], 16):
                        self._add_internal_xfer(step["address"], address,
                                                step["stack"][-1])
                else:
                    # calling an existing contract
                    stack_idx = -4 if step["op"] in ("CALL",
                                                     "CALLCODE") else -3
                    offset = int(step["stack"][stack_idx], 16)
                    length = int(step["stack"][stack_idx - 1], 16)
                    calldata = HexBytes("".join(
                        step["memory"]))[offset:offset + length]
                    sig = calldata[:4].hex()
                    address = step["stack"][-2][-40:]

                last_map[trace[i]["depth"]] = _get_last_map(address, sig)
                coverage_eval.setdefault(last_map[trace[i]["depth"]]["name"],
                                         {})

                self._subcalls.append({
                    "from": step["address"],
                    "to": EthAddress(address),
                    "op": step["op"]
                })
                if step["op"] in ("CALL", "CALLCODE"):
                    self._subcalls[-1]["value"] = int(step["stack"][-3], 16)
                if calldata and last_map[trace[i]["depth"]].get("function"):
                    fn = last_map[trace[i]["depth"]]["function"]
                    zip_ = zip(fn.abi["inputs"], fn.decode_input(calldata))
                    self._subcalls[-1].update(
                        inputs={i[0]["name"]: i[1]
                                for i in zip_},  # type:ignore
                        function=fn._input_sig,
                    )
                elif calldata:
                    self._subcalls[-1]["calldata"] = calldata.hex()

            # update trace from last_map
            last = last_map[trace[i]["depth"]]
            trace[i].update(
                address=last["address"],
                contractName=last["name"],
                fn=last["internal_calls"][-1],
                jumpDepth=last["jumpDepth"],
                source=False,
            )

            opcode = trace[i]["op"]
            if opcode == "CALL" and int(trace[i]["stack"][-3], 16):
                self._add_internal_xfer(last["address"],
                                        trace[i]["stack"][-2][-40:],
                                        trace[i]["stack"][-3])

            try:
                pc = last["pc_map"][trace[i]["pc"]]
            except (KeyError, TypeError):
                # we don't have enough information about this contract
                continue

            if trace[i]["depth"] and opcode in ("RETURN", "REVERT", "INVALID",
                                                "SELFDESTRUCT"):
                subcall: dict = next(
                    i for i in self._subcalls[::-1]
                    if i["to"] == last["address"]  # type: ignore
                )

                if opcode == "RETURN":
                    returndata = _get_memory(trace[i], -1)
                    if returndata:
                        fn = last["function"]
                        try:
                            return_values = fn.decode_output(returndata)
                            if len(fn.abi["outputs"]) == 1:
                                return_values = (return_values, )
                            subcall["return_value"] = return_values
                        except Exception:
                            subcall["returndata"] = returndata.hex()
                    else:
                        subcall["return_value"] = None
                elif opcode == "SELFDESTRUCT":
                    subcall["selfdestruct"] = True
                else:
                    if opcode == "REVERT":
                        data = _get_memory(trace[i], -1)[4:]
                        if data:
                            subcall["revert_msg"] = decode_abi(["string"],
                                                               data)[0]
                    if "revert_msg" not in subcall and "dev" in pc:
                        subcall["revert_msg"] = pc["dev"]

            if "path" not in pc:
                continue
            trace[i]["source"] = {
                "filename": last["path_map"][pc["path"]],
                "offset": pc["offset"]
            }

            if "fn" not in pc:
                continue

            # calculate coverage
            if last["coverage"]:
                if pc["path"] not in coverage_eval[last["name"]]:
                    coverage_eval[last["name"]][pc["path"]] = [
                        set(), set(), set()
                    ]
                if "statement" in pc:
                    coverage_eval[last["name"]][pc["path"]][0].add(
                        pc["statement"])
                if "branch" in pc:
                    if pc["op"] != "JUMPI":
                        last["active_branches"].add(pc["branch"])
                    elif "active_branches" not in last or pc["branch"] in last[
                            "active_branches"]:
                        # false, true
                        key = 1 if trace[i +
                                         1]["pc"] == trace[i]["pc"] + 1 else 2
                        coverage_eval[last["name"]][pc["path"]][key].add(
                            pc["branch"])
                        if "active_branches" in last:
                            last["active_branches"].remove(pc["branch"])

            # ignore jumps with no function - they are compiler optimizations
            if "jump" in pc:
                # jump 'i' is calling into an internal function
                if pc["jump"] == "i":
                    try:
                        fn = last["pc_map"][trace[i + 1]["pc"]]["fn"]
                    except (KeyError, IndexError):
                        continue
                    if fn != last["internal_calls"][-1]:
                        last["internal_calls"].append(fn)
                        last["jumpDepth"] += 1
                # jump 'o' is returning from an internal function
                elif last["jumpDepth"] > 0:
                    del last["internal_calls"][-1]
                    last["jumpDepth"] -= 1
        coverage._add_transaction(
            self.coverage_hash,
            dict((k, v) for k, v in coverage_eval.items() if v))
コード例 #12
0
                sig = f"<{step['op']}>"
                calldata = None
                # self._new_contracts.append(EthAddress(address))
                # if int(step["stack"][-1], 16):
                #     self._add_internal_xfer(step["address"], address, step["stack"][-1])
            else:
                # calling an existing contract
                stack_idx = -4 if step["op"] in ("CALL", "CALLCODE") else -3
                offset = int(step["stack"][stack_idx], 16)
                length = int(step["stack"][stack_idx - 1], 16)
                calldata = HexBytes("".join(step["memory"]))[offset: offset + length]
                sig = calldata[:4].hex()
                address = step["stack"][-2][-40:]

            depth = trace[i]["depth"]
            last_map[depth] = _get_last_map(EthAddress(address), sig)
            coverage_eval.setdefault(last_map[trace[i]["depth"]]["address"], {})

            if receipt._subcalls == None:
                receipt._subcalls = []
            receipt._subcalls.append(
                {"from": step["address"], "to": EthAddress(address), "op": step["op"]}
            )
            if step["op"] in ("CALL", "CALLCODE"):
                receipt._subcalls[-1]["value"] = int(step["stack"][-3], 16)
            if calldata and last_map[trace[i]["depth"]].get("function"):
                fn = last_map[trace[i]["depth"]]["function"]
                receipt._subcalls[-1]["function"] = fn._input_sig
                try:
                    zip_ = zip(fn.abi["inputs"], fn.decode_input(calldata))
                    inputs = {i[0]["name"]: i[1] for i in zip_}  # type: ignore