def pprint_logic(exp, indent=2): if type(exp) == list: for idx, line in enumerate(exp): pprint_logic(line, indent) elif opcode(exp) == 'or' and len(exp)>1: print(' '*indent + 'if') pprint_logic(exp[1], indent+2) for line in exp[2:]: print(' '*indent + 'or') pprint_logic(line, indent+2) else: print(' '*indent + prettify(exp, add_color=True))
def _print(self): set_func(self.hash) set_func_params_if_none(self.params) if self.const is not None: val = self.const if opcode(val) == "return": val = val[1] return [ COLOR_HEADER + "const " + ENDC + str(self.color_name.split("()")[0]) + " = " + COLOR_BOLD + prettify(val) + ENDC ] else: comment = "" if not self.payable: comment = "# not payable" if self.name == "_fallback()": if self.payable: comment = "# default function" else: comment = "# not payable, default function" # qweqw header = [ color("def ", C.header) + self.color_name + (color(" payable", C.header) if self.payable else "") + ": " + color(comment, C.gray) ] if self.ast is not None: res = list(pprint_logic(self.ast)) else: res = list(pprint_logic(self.trace)) if len(res) == 0: res = [" stop"] return header + res
def store_to_set(line): if line ~ ('store', :size, :off, :idx, :val): return ('set', ('stor', size, off, idx), val) else: return line def loc_to_name(exp): if exp ~ ('loc', int:num): if num < 1000: return ('name', 'stor' + str(num), num) else: return ('name', 'stor' + hex(num)[2:6].upper(), num) if exp ~ ('loc', :num): return ('name', 'stor' + prettify(num, add_color=False, parentheses=True), num) else: return exp def arr_rem_mul(exp): if exp ~ ('array', ('mask_shl', :size, :off, int:shl, :idx), :loc): r = 2 ** shl e_loc = get_loc(loc) for s in self.stor_defs: assert s ~ ('def', _, :d_loc, :d_def) if s ~ ('def', _, e_loc, ('array', ('struct', r))): return ('array', ('mask_shl', size, off, 0, idx), loc) # exit()
def pprint(exp): for e in exp: print(' ', prettify(e, add_color=True))
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 ret ~ ('bool', ('storage', _, _, :loc)):
# 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) return self
def __str__(self): return "[" + (", ".join([prettify(el, parentheses=False) for el in self.stack])) + "]"
else: return line def loc_to_name(exp): if m := match(exp, ("loc", ":int:num")): num = m.num if num < 1000: return ("name", "stor" + str(num), num) else: return ("name", "stor" + hex(num)[2:6].upper(), num) if m := match(exp, ("loc", ":num")): return ( "name", "stor" + prettify(m.num, add_color=False, parentheses=True), m.num, ) return exp def arr_rem_mul(exp): if m := match( exp, ("array", ("mask_shl", ":size", ":off", ":int:shl", ":idx"), ":loc"), ): size, off, shl, idx, loc = m.size, m.off, m.shl, m.idx, m.loc r = 2**shl e_loc = get_loc(loc)
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))
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)) else: pass if self.getter: exp_text.append((f"getter for", prettify(self.getter))) explain_text("function traits", exp_text) return self