def test_lll_from_s_expression(get_contract_from_lll): code = """ (seq (return 0 (lll ; just return 32 byte of calldata back (seq (calldatacopy 0 4 32) (return 0 32) stop ) 0))) """ abi = [{ "name": "test", "outputs": [{ "type": "int128", "name": "out" }], "inputs": [{ "type": "int128", "name": "a" }], "constant": False, "payable": False, "type": "function", "gas": 394 }] s_expressions = parse_s_exp(code) lll = LLLnode.from_list(s_expressions[0]) c = get_contract_from_lll(lll, abi=abi) assert c.test(-123456) == -123456
def preapproved_call_to(addr): return LLLnode.from_list([ 'seq', ['return', [0], ['lll', ['seq', ['calldatacopy', 0, 0, 128], ['call', 3000, int(addr, 16), 0, 0, 128, 0, 32], ['return', 0, 32]], [0]]]])
def compile_to_assembly(code, withargs=None, existing_labels=None, break_dest=None, height=0): if withargs is None: withargs = {} assert isinstance(withargs, dict) if existing_labels is None: existing_labels = set() assert isinstance(existing_labels, set) # Opcodes if isinstance(code.value, str) and code.value.upper() in opcodes: o = [] for i, c in enumerate(code.args[::-1]): o.extend(compile_to_assembly(c, withargs, existing_labels, break_dest, height + i)) o.append(code.value.upper()) return o # Numbers elif isinstance(code.value, int): if code.value <= -2**255: raise Exception("Value too low: %d" % code.value) elif code.value >= 2**256: raise Exception("Value too high: %d" % code.value) bytez = num_to_bytearray(code.value % 2**256) or [0] return ['PUSH' + str(len(bytez))] + bytez # Variables connected to with statements elif isinstance(code.value, str) and code.value in withargs: if height - withargs[code.value] > 16: raise Exception("With statement too deep") return ['DUP' + str(height - withargs[code.value])] # Setting variables connected to with statements elif code.value == "set": if len(code.args) != 2 or code.args[0].value not in withargs: raise Exception("Set expects two arguments, the first being a stack variable") if height - withargs[code.args[0].value] > 16: raise Exception("With statement too deep") return compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height) + \ ['SWAP' + str(height - withargs[code.args[0].value]), 'POP'] # Pass statements elif code.value == 'pass': return [] # Code length elif code.value == '~codelen': return ['_sym_codeend'] # Calldataload equivalent for code elif code.value == 'codeload': return compile_to_assembly(LLLnode.from_list( [ 'seq', ['codecopy', MemoryPositions.FREE_VAR_SPACE, code.args[0], 32], ['mload', MemoryPositions.FREE_VAR_SPACE] ] ), withargs, break_dest, height) # If statements (2 arguments, ie. if x: y) elif code.value in ('if', 'if_unchecked') and len(code.args) == 2: o = [] o.extend(compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) end_symbol = mksymbol() o.extend(['ISZERO', end_symbol, 'JUMPI']) o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend([end_symbol, 'JUMPDEST']) return o # If statements (3 arguments, ie. if x: y, else: z) elif code.value == 'if' and len(code.args) == 3: o = [] o.extend(compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) mid_symbol = mksymbol() end_symbol = mksymbol() o.extend(['ISZERO', mid_symbol, 'JUMPI']) o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend([end_symbol, 'JUMP', mid_symbol, 'JUMPDEST']) o.extend(compile_to_assembly(code.args[2], withargs, existing_labels, break_dest, height)) o.extend([end_symbol, 'JUMPDEST']) return o # Repeat statements (compiled from for loops) # Repeat(memloc, start, rounds, body) elif code.value == 'repeat': o = [] loops = num_to_bytearray(code.args[2].value) start, continue_dest, end = mksymbol(), mksymbol(), mksymbol() o.extend(compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) o.extend(compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height + 1, )) o.extend(['PUSH' + str(len(loops))] + loops) # stack: memloc, startvalue, rounds o.extend(['DUP2', 'DUP4', 'MSTORE', 'ADD', start, 'JUMPDEST']) # stack: memloc, exit_index o.extend(compile_to_assembly( code.args[3], withargs, existing_labels, (end, continue_dest, height + 2), height + 2, )) # stack: memloc, exit_index o.extend([ continue_dest, 'JUMPDEST', 'DUP2', 'MLOAD', 'PUSH1', 1, 'ADD', 'DUP1', 'DUP4', 'MSTORE', ]) # stack: len(loops), index memory address, new index o.extend(['DUP2', 'EQ', 'ISZERO', start, 'JUMPI', end, 'JUMPDEST', 'POP', 'POP']) return o # Continue to the next iteration of the for loop elif code.value == 'continue': if not break_dest: raise Exception("Invalid break") dest, continue_dest, break_height = break_dest return [continue_dest, 'JUMP'] # Break from inside a for loop elif code.value == 'break': if not break_dest: raise Exception("Invalid break") dest, continue_dest, break_height = break_dest return ['POP'] * (height - break_height) + [dest, 'JUMP'] # With statements elif code.value == 'with': o = [] o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) old = withargs.get(code.args[0].value, None) withargs[code.args[0].value] = height o.extend(compile_to_assembly( code.args[2], withargs, existing_labels, break_dest, height + 1, )) if code.args[2].valency: o.extend(['SWAP1', 'POP']) else: o.extend(['POP']) if old is not None: withargs[code.args[0].value] = old else: del withargs[code.args[0].value] return o # LLL statement (used to contain code inside code) elif code.value == 'lll': o = [] begincode = mksymbol() endcode = mksymbol() o.extend([endcode, 'JUMP', begincode, 'BLANK']) # The `append(...)` call here is intentional o.append(compile_to_assembly(code.args[0], {}, existing_labels, None, 0)) o.extend([endcode, 'JUMPDEST', begincode, endcode, 'SUB', begincode]) o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend(['CODECOPY', begincode, endcode, 'SUB']) return o # Seq (used to piece together multiple statements) elif code.value == 'seq': o = [] for arg in code.args: o.extend(compile_to_assembly(arg, withargs, existing_labels, break_dest, height)) if arg.valency == 1 and arg != code.args[-1]: o.append('POP') return o # Seq without popping. elif code.value == 'seq_unchecked': o = [] for arg in code.args: o.extend(compile_to_assembly(arg, withargs, existing_labels, break_dest, height)) # if arg.valency == 1 and arg != code.args[-1]: # o.append('POP') return o # Assure (if false, invalid opcode) elif code.value == 'assert_unreachable': o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) end_symbol = mksymbol() o.extend([ end_symbol, 'JUMPI', 'INVALID', end_symbol, 'JUMPDEST' ]) return o # Assert (if false, exit) elif code.value == 'assert': o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(get_revert()) return o elif code.value == 'assert_reason': o = compile_to_assembly(code.args[0], withargs, break_dest, height) mem_start = compile_to_assembly(code.args[1], withargs, break_dest, height) mem_len = compile_to_assembly(code.args[2], withargs, break_dest, height) o.extend(get_revert(mem_start, mem_len)) return o # Unsigned/signed clamp, check less-than elif code.value in CLAMP_OP_NAMES: if isinstance(code.args[0].value, int) and isinstance(code.args[1].value, int): # Checks for clamp errors at compile time as opposed to run time args_0_val = code.args[0].value args_1_val = code.args[1].value is_free_of_clamp_errors = any(( code.value in ('uclamplt', 'clamplt') and 0 <= args_0_val < args_1_val, code.value in ('uclample', 'clample') and 0 <= args_0_val <= args_1_val, code.value in ('uclampgt', 'clampgt') and 0 <= args_0_val > args_1_val, code.value in ('uclampge', 'clampge') and 0 <= args_0_val >= args_1_val, )) if is_free_of_clamp_errors: return compile_to_assembly( code.args[0], withargs, existing_labels, break_dest, height, ) else: raise Exception( "Invalid %r with values %r and %r" % (code.value, code.args[0], code.args[1]) ) o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height + 1, )) o.extend(['DUP2']) # Stack: num num bound if code.value == 'uclamplt': o.extend(['LT']) elif code.value == "clamplt": o.extend(['SLT']) elif code.value == "uclample": o.extend(['GT', 'ISZERO']) elif code.value == "clample": o.extend(['SGT', 'ISZERO']) elif code.value == 'uclampgt': o.extend(['GT']) elif code.value == "clampgt": o.extend(['SGT']) elif code.value == "uclampge": o.extend(['LT', 'ISZERO']) elif code.value == "clampge": o.extend(['SLT', 'ISZERO']) o.extend(get_revert()) return o # Signed clamp, check against upper and lower bounds elif code.value in ('clamp', 'uclamp'): comp1 = 'SGT' if code.value == 'clamp' else 'GT' comp2 = 'SLT' if code.value == 'clamp' else 'LT' o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height + 1, )) o.extend(['DUP1']) o.extend(compile_to_assembly( code.args[2], withargs, existing_labels, break_dest, height + 3, )) o.extend(['SWAP1', comp1, 'ISZERO']) o.extend(get_revert()) o.extend(['DUP1', 'SWAP2', 'SWAP1', comp2, 'ISZERO']) o.extend(get_revert()) return o # Checks that a value is nonzero elif code.value == 'clamp_nonzero': o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(['DUP1']) o.extend(get_revert()) return o # SHA3 a single value elif code.value == 'sha3_32': o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend([ 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'MSTORE', 'PUSH1', 32, 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'SHA3' ]) return o # SHA3 a 64 byte value elif code.value == 'sha3_64': o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend([ 'PUSH1', MemoryPositions.FREE_VAR_SPACE2, 'MSTORE', 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'MSTORE', 'PUSH1', 64, 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'SHA3' ]) return o # <= operator elif code.value == 'le': return compile_to_assembly(LLLnode.from_list( [ 'iszero', ['gt', code.args[0], code.args[1]], ] ), withargs, existing_labels, break_dest, height) # >= operator elif code.value == 'ge': return compile_to_assembly(LLLnode.from_list( [ 'iszero', ['lt', code.args[0], code.args[1]], ] ), withargs, existing_labels, break_dest, height) # <= operator elif code.value == 'sle': return compile_to_assembly(LLLnode.from_list( [ 'iszero', ['sgt', code.args[0], code.args[1]], ] ), withargs, existing_labels, break_dest, height) # >= operator elif code.value == 'sge': return compile_to_assembly(LLLnode.from_list( [ 'iszero', ['slt', code.args[0], code.args[1]], ] ), withargs, existing_labels, break_dest, height) # != operator elif code.value == 'ne': return compile_to_assembly(LLLnode.from_list( [ 'iszero', ['eq', code.args[0], code.args[1]], ] ), withargs, existing_labels, break_dest, height) # e.g. 95 -> 96, 96 -> 96, 97 -> 128 elif code.value == "ceil32": return compile_to_assembly(LLLnode.from_list( [ 'with', '_val', code.args[0], [ 'sub', ['add', '_val', 31], ['mod', ['sub', '_val', 1], 32], ] ] ), withargs, existing_labels, break_dest, height) # # jump to a symbol elif code.value == 'goto': return [ '_sym_' + str(code.args[0]), 'JUMP' ] elif isinstance(code.value, str) and code.value.startswith('_sym_'): return code.value # set a symbol as a location. elif code.value == 'label': label_name = str(code.args[0]) if label_name in existing_labels: raise Exception('Label with name %s already exists!', label_name) else: existing_labels.add(label_name) return [ '_sym_' + label_name, 'JUMPDEST' ] # inject debug opcode. elif code.value == 'debugger': return mkdebug(pc_debugger=False, pos=code.pos) # inject debug opcode. elif code.value == 'pc_debugger': return mkdebug(pc_debugger=True, pos=code.pos) else: raise Exception("Weird code element: " + repr(code))
def test_lll_compile_fail(lll): optimized = optimizer.optimize(LLLnode.from_list(lll[0])) optimized.repr_show_gas = True hand_optimized = LLLnode.from_list(lll[1]) hand_optimized.repr_show_gas = True assert optimized == hand_optimized
def compile_to_assembly(code, withargs=None, break_dest=None, height=0): if withargs is None: withargs = {} # Opcodes if isinstance(code.value, str) and code.value.upper() in opcodes: o = [] for i, c in enumerate(code.args[::-1]): o.extend(compile_to_assembly(c, withargs, break_dest, height + i)) o.append(code.value.upper()) return o # Numbers elif isinstance(code.value, int): if code.value <= -2**255: raise Exception("Value too low: %d" % code.value) elif code.value >= 2**256: raise Exception("Value too high: %d" % code.value) bytez = num_to_bytearray(code.value % 2**256) or [0] return ['PUSH' + str(len(bytez))] + bytez # Variables connected to with statements elif isinstance(code.value, str) and code.value in withargs: if height - withargs[code.value] > 16: raise Exception("With statement too deep") return ['DUP' + str(height - withargs[code.value])] # Setting variables connected to with statements elif code.value == "set": if height - withargs[code.args[0].value] > 16: raise Exception("With statement too deep") if len(code.args) != 2 or code.args[0].value not in withargs: raise Exception("Set expects two arguments, the first being a stack variable") return compile_to_assembly(code.args[1], withargs, break_dest, height) + \ ['SWAP' + str(height - withargs[code.args[0].value]), 'POP'] # Pass statements elif code.value == 'pass': return [] # Code length elif code.value == '~codelen': return ['_sym_codeend'] # Calldataload equivalent for code elif code.value == 'codeload': return compile_to_assembly(LLLnode.from_list(['seq', ['codecopy', MemoryPositions.FREE_VAR_SPACE, code.args[0], 32], ['mload', MemoryPositions.FREE_VAR_SPACE]]), withargs, break_dest, height) # If statements (2 arguments, ie. if x: y) elif code.value == 'if' and len(code.args) == 2: o = [] o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height)) end_symbol = mksymbol() o.extend(['ISZERO', end_symbol, 'JUMPI']) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height)) o.extend([end_symbol, 'JUMPDEST']) return o # If statements (3 arguments, ie. if x: y, else: z) elif code.value == 'if' and len(code.args) == 3: o = [] o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height)) mid_symbol = mksymbol() end_symbol = mksymbol() o.extend(['ISZERO', mid_symbol, 'JUMPI']) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height)) o.extend([end_symbol, 'JUMP', mid_symbol, 'JUMPDEST']) o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height)) o.extend([end_symbol, 'JUMPDEST']) return o # Repeat statements (compiled from for loops) # Repeat(memloc, start, rounds, body) elif code.value == 'repeat': o = [] loops = num_to_bytearray(code.args[2].value) if not loops: raise Exception("Number of times repeated must be a constant nonzero positive integer: %r" % loops) start, continue_dest, end = mksymbol(), mksymbol(), mksymbol() o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height)) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1)) o.extend(['PUSH' + str(len(loops))] + loops) # stack: memloc, startvalue, rounds o.extend(['DUP2', 'DUP4', 'MSTORE', 'ADD', start, 'JUMPDEST']) # stack: memloc, exit_index o.extend(compile_to_assembly(code.args[3], withargs, (end, continue_dest, height + 2), height + 2)) # stack: memloc, exit_index o.extend([continue_dest, 'JUMPDEST', 'DUP2', 'MLOAD', 'PUSH1', 1, 'ADD', 'DUP1', 'DUP4', 'MSTORE']) # stack: len(loops), index memory address, new index o.extend(['DUP2', 'EQ', 'ISZERO', start, 'JUMPI', end, 'JUMPDEST', 'POP', 'POP']) return o # Continue to the next iteration of the for loop elif code.value == 'continue': if not break_dest: raise Exception("Invalid break") dest, continue_dest, break_height = break_dest return [continue_dest, 'JUMP'] # Break from inside a for loop elif code.value == 'break': if not break_dest: raise Exception("Invalid break") dest, continue_dest, break_height = break_dest return ['POP'] * (height - break_height) + [dest, 'JUMP'] # With statements elif code.value == 'with': o = [] o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height)) old = withargs.get(code.args[0].value, None) withargs[code.args[0].value] = height o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height + 1)) if code.args[2].valency: o.extend(['SWAP1', 'POP']) else: o.extend(['POP']) if old is not None: withargs[code.args[0].value] = old else: del withargs[code.args[0].value] return o # LLL statement (used to contain code inside code) elif code.value == 'lll': o = [] begincode = mksymbol() endcode = mksymbol() o.extend([endcode, 'JUMP', begincode, 'BLANK']) o.append(compile_to_assembly(code.args[0], {}, None, 0)) # Append is intentional o.extend([endcode, 'JUMPDEST', begincode, endcode, 'SUB', begincode]) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height)) o.extend(['CODECOPY', begincode, endcode, 'SUB']) return o # Seq (used to piece together multiple statements) elif code.value == 'seq': o = [] for arg in code.args: o.extend(compile_to_assembly(arg, withargs, break_dest, height)) if arg.valency == 1 and arg != code.args[-1]: o.append('POP') return o # Assert (if false, exit) elif code.value == 'assert': o = compile_to_assembly(code.args[0], withargs, break_dest, height) o.extend(get_revert()) return o # Unsigned/signed clamp, check less-than elif code.value in ('uclamplt', 'uclample', 'clamplt', 'clample', 'uclampgt', 'uclampge', 'clampgt', 'clampge'): if isinstance(code.args[0].value, int) and isinstance(code.args[1].value, int): # Checks for clamp errors at compile time as opposed to run time if code.value in ('uclamplt', 'clamplt') and 0 <= code.args[0].value < code.args[1].value or \ code.value in ('uclample', 'clample') and 0 <= code.args[0].value <= code.args[1].value or \ code.value in ('uclampgt', 'clampgt') and 0 <= code.args[0].value > code.args[1].value or \ code.value in ('uclampge', 'clampge') and 0 <= code.args[0].value >= code.args[1].value: return compile_to_assembly(code.args[0], withargs, break_dest, height) else: raise Exception("Invalid %r with values %r and %r" % (code.value, code.args[0], code.args[1])) o = compile_to_assembly(code.args[0], withargs, break_dest, height) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1)) o.extend(['DUP2']) # Stack: num num bound if code.value == 'uclamplt': o.extend(['LT']) elif code.value == "clamplt": o.extend(['SLT']) elif code.value == "uclample": o.extend(['GT', 'ISZERO']) elif code.value == "clample": o.extend(['SGT', 'ISZERO']) elif code.value == 'uclampgt': o.extend(['GT']) elif code.value == "clampgt": o.extend(['SGT']) elif code.value == "uclampge": o.extend(['LT', 'ISZERO']) elif code.value == "clampge": o.extend(['SLT', 'ISZERO']) o.extend(get_revert()) return o # Signed clamp, check against upper and lower bounds elif code.value in ('clamp', 'uclamp'): comp1 = 'SGT' if code.value == 'clamp' else 'GT' comp2 = 'SLT' if code.value == 'clamp' else 'LT' o = compile_to_assembly(code.args[0], withargs, break_dest, height) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1)) o.extend(['DUP1']) o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height + 3)) o.extend(['SWAP1', comp1, 'PC', 'JUMPI']) o.extend(['DUP1', 'SWAP2', 'SWAP1', comp2, 'ISZERO']) o.extend(get_revert()) return o # Checks that a value is nonzero elif code.value == 'clamp_nonzero': o = compile_to_assembly(code.args[0], withargs, break_dest, height) o.extend(['DUP1']) o.extend(get_revert()) return o # SHA3 a single value elif code.value == 'sha3_32': o = compile_to_assembly(code.args[0], withargs, break_dest, height) o.extend(['PUSH1', MemoryPositions.FREE_VAR_SPACE, 'MSTORE', 'PUSH1', 32, 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'SHA3']) return o # <= operator elif code.value == 'le': return compile_to_assembly(LLLnode.from_list(['iszero', ['gt', code.args[0], code.args[1]]]), withargs, break_dest, height) # >= operator elif code.value == 'ge': return compile_to_assembly(LLLnode.from_list(['iszero', ['lt', code.args[0], code.args[1]]]), withargs, break_dest, height) # <= operator elif code.value == 'sle': return compile_to_assembly(LLLnode.from_list(['iszero', ['sgt', code.args[0], code.args[1]]]), withargs, break_dest, height) # >= operator elif code.value == 'sge': return compile_to_assembly(LLLnode.from_list(['iszero', ['slt', code.args[0], code.args[1]]]), withargs, break_dest, height) # != operator elif code.value == 'ne': return compile_to_assembly(LLLnode.from_list(['iszero', ['eq', code.args[0], code.args[1]]]), withargs, break_dest, height) # eg. 95 -> 96, 96 -> 96, 97 -> 128 elif code.value == "ceil32": return compile_to_assembly(LLLnode.from_list(['with', '_val', code.args[0], ['sub', ['add', '_val', 31], ['mod', ['sub', '_val', 1], 32]]]), withargs, break_dest, height) else: raise Exception("Weird code element: " + repr(code))
def foo() -> uint256: return block.number """, """ @public def foo() -> uint256: return msg.gas """] ecrecover_lll_src = LLLnode.from_list([ 'seq', ['return', [0], ['lll', ['seq', ['calldatacopy', 0, 0, 128], ['call', 3000, 1, 0, 0, 128, 0, 32], ['mstore', 0, ['eq', ['mload', 0], 0]], ['return', 0, 32]], [0]]]]) def preapproved_call_to(addr): return LLLnode.from_list([ 'seq', ['return', [0], ['lll', ['seq', ['calldatacopy', 0, 0, 128], ['call', 3000, int(addr, 16), 0, 0, 128, 0, 32],
def test_compile_lll_good(good_lll, get_contract_from_lll): get_contract_from_lll(LLLnode.from_list(good_lll))
def test_lll_compile_fail(bad_lll, get_contract_from_lll, assert_compile_failed): assert_compile_failed( lambda: get_contract_from_lll(LLLnode.from_list(bad_lll)), Exception)
def compile_to_assembly(code, withargs=None, existing_labels=None, break_dest=None, height=0): if withargs is None: withargs = {} if not isinstance(withargs, dict): raise CompilerPanic(f"Incorrect type for withargs: {type(withargs)}") if existing_labels is None: existing_labels = set() if not isinstance(existing_labels, set): raise CompilerPanic( f"Incorrect type for existing_labels: {type(existing_labels)}") # Opcodes if isinstance(code.value, str) and code.value.upper() in get_opcodes(): o = [] for i, c in enumerate(code.args[::-1]): o.extend( compile_to_assembly(c, withargs, existing_labels, break_dest, height + i)) o.append(code.value.upper()) return o # Numbers elif isinstance(code.value, int): if code.value <= -(2**255): raise Exception(f"Value too low: {code.value}") elif code.value >= 2**256: raise Exception(f"Value too high: {code.value}") bytez = num_to_bytearray(code.value % 2**256) or [0] return ["PUSH" + str(len(bytez))] + bytez # Variables connected to with statements elif isinstance(code.value, str) and code.value in withargs: if height - withargs[code.value] > 16: raise Exception("With statement too deep") return ["DUP" + str(height - withargs[code.value])] # Setting variables connected to with statements elif code.value == "set": if len(code.args) != 2 or code.args[0].value not in withargs: raise Exception( "Set expects two arguments, the first being a stack variable") if height - withargs[code.args[0].value] > 16: raise Exception("With statement too deep") return compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height) + [ "SWAP" + str(height - withargs[code.args[0].value]), "POP", ] # Pass statements elif code.value == "pass": return [] # Code length elif code.value == "~codelen": return ["_sym_codeend"] # Calldataload equivalent for code elif code.value == "codeload": return compile_to_assembly( LLLnode.from_list([ "seq", ["codecopy", MemoryPositions.FREE_VAR_SPACE, code.args[0], 32], ["mload", MemoryPositions.FREE_VAR_SPACE], ]), withargs, existing_labels, break_dest, height, ) # If statements (2 arguments, ie. if x: y) elif code.value in ("if", "if_unchecked") and len(code.args) == 2: o = [] o.extend( compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) end_symbol = mksymbol() o.extend(["ISZERO", end_symbol, "JUMPI"]) o.extend( compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend([end_symbol, "JUMPDEST"]) return o # If statements (3 arguments, ie. if x: y, else: z) elif code.value == "if" and len(code.args) == 3: o = [] o.extend( compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) mid_symbol = mksymbol() end_symbol = mksymbol() o.extend(["ISZERO", mid_symbol, "JUMPI"]) o.extend( compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend([end_symbol, "JUMP", mid_symbol, "JUMPDEST"]) o.extend( compile_to_assembly(code.args[2], withargs, existing_labels, break_dest, height)) o.extend([end_symbol, "JUMPDEST"]) return o # Repeat statements (compiled from for loops) # Repeat(memloc, start, rounds, body) elif code.value == "repeat": o = [] loops = num_to_bytearray(code.args[2].value) start, continue_dest, end = mksymbol(), mksymbol(), mksymbol() o.extend( compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) o.extend( compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height + 1, )) o.extend(["PUSH" + str(len(loops))] + loops) # stack: memloc, startvalue, rounds o.extend(["DUP2", "DUP4", "MSTORE", "ADD", start, "JUMPDEST"]) # stack: memloc, exit_index o.extend( compile_to_assembly( code.args[3], withargs, existing_labels, (end, continue_dest, height + 2), height + 2, )) # stack: memloc, exit_index o.extend([ continue_dest, "JUMPDEST", "DUP2", "MLOAD", "PUSH1", 1, "ADD", "DUP1", "DUP4", "MSTORE", ]) # stack: len(loops), index memory address, new index o.extend([ "DUP2", "EQ", "ISZERO", start, "JUMPI", end, "JUMPDEST", "POP", "POP" ]) return o # Continue to the next iteration of the for loop elif code.value == "continue": if not break_dest: raise CompilerPanic("Invalid break") dest, continue_dest, break_height = break_dest return [continue_dest, "JUMP"] # Break from inside a for loop elif code.value == "break": if not break_dest: raise CompilerPanic("Invalid break") dest, continue_dest, break_height = break_dest return ["POP"] * (height - break_height) + [dest, "JUMP"] # Break from inside one or more for loops prior to a return statement inside the loop elif code.value == "exit_repeater": if not break_dest: raise CompilerPanic("Invalid break") _, _, break_height = break_dest return ["POP"] * break_height # With statements elif code.value == "with": o = [] o.extend( compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) old = withargs.get(code.args[0].value, None) withargs[code.args[0].value] = height o.extend( compile_to_assembly( code.args[2], withargs, existing_labels, break_dest, height + 1, )) if code.args[2].valency: o.extend(["SWAP1", "POP"]) else: o.extend(["POP"]) if old is not None: withargs[code.args[0].value] = old else: del withargs[code.args[0].value] return o # LLL statement (used to contain code inside code) elif code.value == "lll": o = [] begincode = mksymbol() endcode = mksymbol() o.extend([endcode, "JUMP", begincode, "BLANK"]) # The `append(...)` call here is intentional o.append( compile_to_assembly(code.args[0], {}, existing_labels, None, 0)) o.extend([endcode, "JUMPDEST", begincode, endcode, "SUB", begincode]) o.extend( compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend(["CODECOPY", begincode, endcode, "SUB"]) return o # Seq (used to piece together multiple statements) elif code.value == "seq": o = [] for arg in code.args: o.extend( compile_to_assembly(arg, withargs, existing_labels, break_dest, height)) if arg.valency == 1 and arg != code.args[-1]: o.append("POP") return o # Seq without popping. elif code.value == "seq_unchecked": o = [] for arg in code.args: o.extend( compile_to_assembly(arg, withargs, existing_labels, break_dest, height)) # if arg.valency == 1 and arg != code.args[-1]: # o.append('POP') return o # Assure (if false, invalid opcode) elif code.value == "assert_unreachable": o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) end_symbol = mksymbol() o.extend([end_symbol, "JUMPI", "INVALID", end_symbol, "JUMPDEST"]) return o # Assert (if false, exit) elif code.value == "assert": o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(get_revert()) return o elif code.value == "assert_reason": o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) mem_start = compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height) mem_len = compile_to_assembly(code.args[2], withargs, existing_labels, break_dest, height) o.extend(get_revert(mem_start, mem_len)) return o # Unsigned/signed clamp, check less-than elif code.value in CLAMP_OP_NAMES: if isinstance(code.args[0].value, int) and isinstance( code.args[1].value, int): # Checks for clamp errors at compile time as opposed to run time args_0_val = code.args[0].value args_1_val = code.args[1].value is_free_of_clamp_errors = any(( code.value in ("uclamplt", "clamplt") and 0 <= args_0_val < args_1_val, code.value in ("uclample", "clample") and 0 <= args_0_val <= args_1_val, code.value in ("uclampgt", "clampgt") and 0 <= args_0_val > args_1_val, code.value in ("uclampge", "clampge") and 0 <= args_0_val >= args_1_val, )) if is_free_of_clamp_errors: return compile_to_assembly( code.args[0], withargs, existing_labels, break_dest, height, ) else: raise Exception( f"Invalid {code.value} with values {code.args[0]} and {code.args[1]}" ) o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend( compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height + 1, )) o.extend(["DUP2"]) # Stack: num num bound if code.value == "uclamplt": o.extend(["LT"]) elif code.value == "clamplt": o.extend(["SLT"]) elif code.value == "uclample": o.extend(["GT", "ISZERO"]) elif code.value == "clample": o.extend(["SGT", "ISZERO"]) elif code.value == "uclampgt": o.extend(["GT"]) elif code.value == "clampgt": o.extend(["SGT"]) elif code.value == "uclampge": o.extend(["LT", "ISZERO"]) elif code.value == "clampge": o.extend(["SLT", "ISZERO"]) o.extend(get_revert()) return o # Signed clamp, check against upper and lower bounds elif code.value in ("clamp", "uclamp"): comp1 = "SGT" if code.value == "clamp" else "GT" comp2 = "SLT" if code.value == "clamp" else "LT" o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend( compile_to_assembly( code.args[1], withargs, existing_labels, break_dest, height + 1, )) o.extend(["DUP1"]) o.extend( compile_to_assembly( code.args[2], withargs, existing_labels, break_dest, height + 3, )) o.extend(["SWAP1", comp1, "ISZERO"]) o.extend(get_revert()) o.extend(["DUP1", "SWAP2", "SWAP1", comp2, "ISZERO"]) o.extend(get_revert()) return o # Checks that a value is nonzero elif code.value == "clamp_nonzero": o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend(["DUP1"]) o.extend(get_revert()) return o # SHA3 a single value elif code.value == "sha3_32": o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend([ "PUSH1", MemoryPositions.FREE_VAR_SPACE, "MSTORE", "PUSH1", 32, "PUSH1", MemoryPositions.FREE_VAR_SPACE, "SHA3", ]) return o # SHA3 a 64 byte value elif code.value == "sha3_64": o = compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height) o.extend( compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) o.extend([ "PUSH1", MemoryPositions.FREE_VAR_SPACE2, "MSTORE", "PUSH1", MemoryPositions.FREE_VAR_SPACE, "MSTORE", "PUSH1", 64, "PUSH1", MemoryPositions.FREE_VAR_SPACE, "SHA3", ]) return o # <= operator elif code.value == "le": return compile_to_assembly( LLLnode.from_list(["iszero", ["gt", code.args[0], code.args[1]]]), withargs, existing_labels, break_dest, height, ) # >= operator elif code.value == "ge": return compile_to_assembly( LLLnode.from_list(["iszero", ["lt", code.args[0], code.args[1]]]), withargs, existing_labels, break_dest, height, ) # <= operator elif code.value == "sle": return compile_to_assembly( LLLnode.from_list(["iszero", ["sgt", code.args[0], code.args[1]]]), withargs, existing_labels, break_dest, height, ) # >= operator elif code.value == "sge": return compile_to_assembly( LLLnode.from_list(["iszero", ["slt", code.args[0], code.args[1]]]), withargs, existing_labels, break_dest, height, ) # != operator elif code.value == "ne": return compile_to_assembly( LLLnode.from_list(["iszero", ["eq", code.args[0], code.args[1]]]), withargs, existing_labels, break_dest, height, ) # e.g. 95 -> 96, 96 -> 96, 97 -> 128 elif code.value == "ceil32": return compile_to_assembly( LLLnode.from_list([ "with", "_val", code.args[0], ["sub", ["add", "_val", 31], ["mod", ["sub", "_val", 1], 32]], ]), withargs, existing_labels, break_dest, height, ) # # jump to a symbol elif code.value == "goto": return ["_sym_" + str(code.args[0]), "JUMP"] elif isinstance(code.value, str) and code.value.startswith("_sym_"): return code.value # set a symbol as a location. elif code.value == "label": label_name = str(code.args[0]) if label_name in existing_labels: raise Exception(f"Label with name {label_name} already exists!") else: existing_labels.add(label_name) return ["_sym_" + label_name, "JUMPDEST"] # inject debug opcode. elif code.value == "debugger": return mkdebug(pc_debugger=False, pos=code.pos) # inject debug opcode. elif code.value == "pc_debugger": return mkdebug(pc_debugger=True, pos=code.pos) else: raise Exception("Weird code element: " + repr(code))
def test_pc_debugger(): debugger_lll = ["seq_unchecked", ["mstore", 0, 32], ["pc_debugger"]] lll_nodes = LLLnode.from_list(debugger_lll) _, line_number_map = compile_lll.assembly_to_evm( compile_lll.compile_to_assembly(lll_nodes)) assert line_number_map["pc_breakpoints"][0] == 5
purity_checker_lll = LLLnode.from_list([ "seq", [ "return", 0, [ "lll", [ "seq", ["mstore", 28, ["calldataload", 0]], [ "mstore", 32, 1461501637330902918203684832716283019655932542976 ], ["mstore", 64, 170141183460469231731687303715884105727], ["mstore", 96, -170141183460469231731687303715884105728], [ "mstore", 128, 1701411834604692317316873037158841057270000000000 ], [ "mstore", 160, -1701411834604692317316873037158841057280000000000 ], [ "if", ["eq", ["mload", 0], 2710585003], # submit [ "seq", ["calldatacopy", 320, 4, 32], ["assert", ["iszero", "callvalue"]], ["uclamplt", ["calldataload", 4], ["mload", 32]], # checking address input # scan bytecode at address input [ "with", "_EXTCODESIZE", ["extcodesize", ["mload", 320]], # addr [ "if", ["eq", "_EXTCODESIZE", 0], "invalid", # ban accounts with no code [ "seq", [ "extcodecopy", ["mload", 320], 352, 0, "_EXTCODESIZE" ], [ "with", "_i", [ "add", 352, ["mul", 65, "_EXTCODESIZE"] ], [ "with", "_op", 0, [ "repeat", "_i", 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935, loop_body ] ] ] ] ] ], # approve the address `addr` ["sstore", ["add", ["sha3_32", 0], ["mload", 320]], 1], ["mstore", 0, 1], ["return", 0, 32], "stop" ] ], [ "if", ["eq", ["mload", 0], 3258357672], # check [ "seq", ["calldatacopy", 320, 4, 32], ["assert", ["iszero", "callvalue"]], ["uclamplt", ["calldataload", 4], ["mload", 32]], # checking address input [ "mstore", 0, ["sload", ["add", ["sha3_32", 0], ["mload", 320]]] ], ["return", 0, 32], "stop" ] ] ], 0 ] ] ])
def test_pc_debugger(): debugger_lll = ['seq_unchecked', ['mstore', 0, 32], ['pc_debugger']] lll_nodes = LLLnode.from_list(debugger_lll) _, line_number_map = compile_lll.assembly_to_evm( compile_lll.compile_to_assembly(lll_nodes)) assert line_number_map['pc_breakpoints'][0] == 5