if (m := match( exp, ("if", ":cond", ":if_true", ":if_false"))) and str( ("cd", 0)) in str(m.cond): if find_f_list(m.if_false, func_calls) == []: fi = m.if_false[0] if m2 := match(fi, ("jd", ":jd")): return int(m2.jd) if find_f_list(m.if_true, func_calls) == []: fi = m.if_true[0] if m2 := match(fi, ("jd", ":jd")): return int(m2.jd) default = find_f(trace, find_default) if func_list else None self.add_func(default or 0, name="_fallback()") except Exception: logger.exception("Loader issue.") self.add_func(0, name="_fallback()") abi = make_abi(self.hash_targets) for hash, (target, stack) in self.hash_targets.items(): fname = get_func_name(hash) self.func_list.append((hash, fname, target, stack)) def next_line(self, i): i += 1 while i not in self.lines and self.last_line > i: i += 1
def find_default(exp): if exp ~ ('if', :cond, :if_true, :if_false) and str(('cd', 0)) in str(cond): if find_f_list(if_false, func_calls) == []: fi = if_false[0] if fi ~ ('jd', :jd): return int(jd) if find_f_list(if_true, func_calls) == []: fi = if_true[0] if fi ~ ('jd', :jd): return int(jd) else: return None default = find_f(trace, find_default) self.add_func(default or 0, name='_fallback()') except: logger.error(f"Loader issues%s\n%s", ENDC, traceback.format_exc()) self.add_func(0, name='_fallback()') abi = make_abi(self.hash_targets) for hash, target in self.hash_targets.items(): fname = get_func_name(hash) self.func_list.append((hash, fname ,target, )) def next_line(self, i): i += 1 while i not in self.lines and self.last_line > i: i += 1
else: self.getter = t0 # kitties getKitten - with more cases this and the above could be uniformed if self.getter is None: prev_loc = -1 for e in terms: def l2(x): if x ~ ('sha3', ('data', _, int:l)): if l < 1000: return l if x ~ ('sha3', int:l) and l < 1000: return l return None loc = find_f(e, l2) if not loc or (prev_loc != -1 and prev_loc != loc): break prev_loc = loc else: self.getter = ('struct', ('loc', loc)) else: pass if self.getter: exp_text.append((f'getter for', prettify(self.getter))) explain_text('function traits', exp_text)
def analyse(self): assert len(self.trace) > 0 def find_returns(exp): if opcode(exp) == "return": return [exp] else: return [] exp_text = [] self.returns = find_f_list(self.trace, find_returns) exp_text.append(("possible return values", prettify(self.returns))) first = self.trace[0] if (opcode(first) == "if" and simplify_bool(first[1]) == "callvalue" and (first[2][0] == ("revert", 0) or opcode(first[2][0]) == "invalid")): self.trace = self.trace[0][3] self.payable = False elif (opcode(first) == "if" and simplify_bool(first[1]) == ("iszero", "callvalue") and (first[3][0] == ("revert", 0) or opcode(first[3][0]) == "invalid")): self.trace = self.trace[0][2] self.payable = False else: self.payable = True exp_text.append(("payable", self.payable)) self.read_only = True for op in [ "store", "selfdestruct", "call", "delegatecall", "codecall", "create", ]: if f"'{op}'" in str(self.trace): self.read_only = False exp_text.append(("read_only", self.read_only)) """ const func detection """ self.const = self.read_only for exp in ["storage", "calldata", "calldataload", "store", "cd"]: if exp in str(self.trace) or len(self.returns) != 1: self.const = False if self.const: self.const = self.returns[0] if len(self.const) == 3 and opcode(self.const[2]) == "data": self.const = self.const[2] if len(self.const) == 3 and opcode(self.const[2]) == "mask_shl": self.const = self.const[2] if len(self.const) == 3 and type(self.const[2]) == int: self.const = self.const[2] else: self.const = None if self.const: exp_text.append(("const", self.const)) """ getter detection """ self.getter = None self.simplify_string_getter_from_storage() if self.const is None and self.read_only and len(self.returns) == 1: ret = self.returns[0][1] if match(ret, ("bool", ("storage", Any, Any, ":loc"))): self.getter = ( ret # we have to be careful when using this for naming purposes, ) # because sometimes the storage can refer to array length elif opcode(ret) == "mask_shl" and opcode(ret[4]) == "storage": self.getter = ret[4] elif opcode(ret) == "storage": self.getter = ret elif opcode(ret) == "data": terms = ret[1:] # for structs, we check if all the parts of the struct are storage from the same # location. if so, we return the location number t0 = terms[ 0] # 0xFAFfea71A6da719D6CAfCF7F52eA04Eb643F6De2 - documents if m := match(t0, ("storage", 256, 0, ":loc")): loc = m.loc for e in terms[1:]: if not match(e, ("storage", 256, 0, ("add", Any, loc))): break else: self.getter = t0 # kitties getKitten - with more cases this and the above could be uniformed if self.getter is None: prev_loc = -1 for e in terms: def l2(x): if m := match(x, ("sha3", ("data", Any, ":l"))): if type(m.l) == int and m.l < 1000: return m.l if (opcode(x) == "sha3" and type(x[1]) == int and x[1] < 1000): return x[1] return None loc = find_f(e, l2) if not loc or (prev_loc != -1 and prev_loc != loc): break prev_loc = loc else: self.getter = ("struct", ("loc", loc))