示例#1
0
    def __init__(self,
                 value: Union[str, int],
                 args: List['LLLnode'] = None,
                 typ: 'BaseType' = None,
                 location: str = None,
                 pos: Optional[Tuple[int, int]] = None,
                 annotation: Optional[str] = None,
                 mutable: bool = True,
                 add_gas_estimate: int = 0,
                 valency: Optional[int] = None):
        if args is None:
            args = []

        self.value = value
        self.args = args
        self.typ = typ
        assert isinstance(self.typ, NodeType) or self.typ is None, repr(
            self.typ)
        self.location = location
        self.pos = pos
        self.annotation = annotation
        self.mutable = mutable
        self.add_gas_estimate = add_gas_estimate
        self.as_hex = AS_HEX_DEFAULT

        # Optional annotation properties for gas estimation
        self.total_gas = None
        self.func_name = None

        # Determine this node's valency (1 if it pushes a value on the stack,
        # 0 otherwise) and checks to make sure the number and valencies of
        # children are correct. Also, find an upper bound on gas consumption
        # Numbers
        if isinstance(self.value, int):
            self.valency = 1
            self.gas = 5
        elif isinstance(self.value, str):
            # Opcodes and pseudo-opcodes (e.g. clamp)
            if self.value.upper() in get_comb_opcodes():
                _, ins, outs, gas = get_comb_opcodes()[self.value.upper()]
                self.valency = outs
                if len(self.args) != ins:
                    raise CompilerPanic(
                        f"Number of arguments mismatched: {self.value} {self.args}"
                    )
                # We add 2 per stack height at push time and take it back
                # at pop time; this makes `break` easier to handle
                self.gas = gas + 2 * (outs - ins)
                for arg in self.args:
                    # pop and pass are used to push/pop values on the stack to be
                    # consumed for private functions, therefore we whitelist this as a zero valency
                    # allowed argument.
                    zero_valency_whitelist = {'pass', 'pop'}
                    if arg.valency == 0 and arg.value not in zero_valency_whitelist:
                        raise CompilerPanic(
                            "Can't have a zerovalent argument to an opcode or a pseudo-opcode! "
                            f"{arg.value}: {arg}. Please file a bug report.")
                    self.gas += arg.gas
                # Dynamic gas cost: 8 gas for each byte of logging data
                if self.value.upper()[0:3] == 'LOG' and isinstance(
                        self.args[1].value, int):
                    self.gas += self.args[1].value * 8
                # Dynamic gas cost: non-zero-valued call
                if self.value.upper() == 'CALL' and self.args[2].value != 0:
                    self.gas += 34000
                # Dynamic gas cost: filling sstore (ie. not clearing)
                elif self.value.upper(
                ) == 'SSTORE' and self.args[1].value != 0:
                    self.gas += 15000
                # Dynamic gas cost: calldatacopy
                elif self.value.upper() in ('CALLDATACOPY', 'CODECOPY'):
                    size = 34000
                    if isinstance(self.args[2].value, int):
                        size = self.args[2].value
                    self.gas += ceil32(size) // 32 * 3
                # Gas limits in call
                if self.value.upper() == 'CALL' and isinstance(
                        self.args[0].value, int):
                    self.gas += self.args[0].value
            # If statements
            elif self.value == 'if':
                if len(self.args) == 3:
                    self.gas = self.args[0].gas + max(self.args[1].gas,
                                                      self.args[2].gas) + 3
                if len(self.args) == 2:
                    self.gas = self.args[0].gas + self.args[1].gas + 17
                if not self.args[0].valency:
                    raise CompilerPanic(
                        "Can't have a zerovalent argument as a test to an if "
                        f"statement! {self.args[0]}")
                if len(self.args) not in (2, 3):
                    raise CompilerPanic("If can only have 2 or 3 arguments")
                self.valency = self.args[1].valency
            # With statements: with <var> <initial> <statement>
            elif self.value == 'with':
                if len(self.args) != 3:
                    raise CompilerPanic("With statement must have 3 arguments")
                if len(self.args[0].args) or not isinstance(
                        self.args[0].value, str):
                    raise CompilerPanic(
                        "First argument to with statement must be a variable")
                if not self.args[1].valency:
                    raise CompilerPanic(
                        ("Second argument to with statement (initial value) "
                         f"cannot be zerovalent: {self.args[1]}"))
                self.valency = self.args[2].valency
                self.gas = sum([arg.gas for arg in self.args]) + 5
            # Repeat statements: repeat <index_memloc> <startval> <rounds> <body>
            elif self.value == 'repeat':
                is_invalid_repeat_count = any((
                    len(self.args[2].args),
                    not isinstance(self.args[2].value, int),
                    isinstance(self.args[2].value, int)
                    and self.args[2].value <= 0,
                ))

                if is_invalid_repeat_count:
                    raise CompilerPanic(
                        ("Number of times repeated must be a constant nonzero "
                         f"positive integer: {self.args[2]}"))
                if not self.args[0].valency:
                    raise CompilerPanic((
                        "First argument to repeat (memory location) cannot be "
                        f"zerovalent: {self.args[0]}"))
                if not self.args[1].valency:
                    raise CompilerPanic(
                        ("Second argument to repeat (start value) cannot be "
                         f"zerovalent: {self.args[1]}"))
                if self.args[3].valency:
                    raise CompilerPanic((
                        "Third argument to repeat (clause to be repeated) must "
                        f"be zerovalent: {self.args[3]}"))
                self.valency = 0
                rounds: int
                if self.args[1].value in ('calldataload', 'mload'
                                          ) or self.args[1].value == 'sload':
                    if isinstance(self.args[2].value, int):
                        rounds = self.args[2].value
                    else:
                        raise CompilerPanic(
                            f'Unsupported rounds argument type. {self.args[2]}'
                        )
                else:
                    if isinstance(self.args[2].value, int) and isinstance(
                            self.args[1].value, int):
                        rounds = abs(self.args[2].value - self.args[1].value)
                    else:
                        raise CompilerPanic(
                            f'Unsupported second argument types. {self.args}')
                self.gas = rounds * (self.args[3].gas + 50) + 30
            # Seq statements: seq <statement> <statement> ...
            elif self.value == 'seq':
                self.valency = self.args[-1].valency if self.args else 0
                self.gas = sum([arg.gas for arg in self.args]) + 30
            # Multi statements: multi <expr> <expr> ...
            elif self.value == 'multi':
                for arg in self.args:
                    if not arg.valency:
                        raise CompilerPanic(
                            f"Multi expects all children to not be zerovalent: {arg}"
                        )
                self.valency = sum([arg.valency for arg in self.args])
                self.gas = sum([arg.gas for arg in self.args])
            # LLL brackets (don't bother gas counting)
            elif self.value == 'lll':
                self.valency = 1
                self.gas = NullAttractor()
            # Stack variables
            else:
                self.valency = 1
                self.gas = 5
                if self.value == 'seq_unchecked':
                    self.gas = sum([arg.gas for arg in self.args]) + 30
                if self.value == 'if_unchecked':
                    self.gas = self.args[0].gas + self.args[1].gas + 17
        elif self.value is None:
            self.valency = 1
            # None LLLnodes always get compiled into something else, e.g.
            # mzero or PUSH1 0, and the gas will get re-estimated then.
            self.gas = 3
        else:
            raise CompilerPanic(
                f"Invalid value for LLL AST node: {self.value}")
        assert isinstance(self.args, list)

        if valency is not None:
            self.valency = valency

        self.gas += self.add_gas_estimate
示例#2
0
 def _colorise_keywords(val):
     if val.lower() in VALID_LLL_MACROS:  # highlight macro
         return OKLIGHTMAGENTA + val + ENDC
     elif val.upper() in get_comb_opcodes().keys():
         return OKMAGENTA + val + ENDC
     return val