diff --git a/scripts/gen-std-opcodes.py b/scripts/gen-std-opcodes.py new file mode 100755 index 000000000..478ecf10b --- /dev/null +++ b/scripts/gen-std-opcodes.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Generate std/opcodes.solc from a declarative list of EVM opcodes.""" + +import os +from string import ascii_lowercase + +opcodes = [ + # 0x00-0x0b: stop & arithmetic + {"name": "stop", "inputs": 0, "output": 0}, + {"name": "add", "inputs": 2, "output": 1}, + {"name": "mul", "inputs": 2, "output": 1}, + {"name": "sub", "inputs": 2, "output": 1}, + {"name": "div", "inputs": 2, "output": 1}, + {"name": "sdiv", "inputs": 2, "output": 1}, + {"name": "mod", "inputs": 2, "output": 1}, + {"name": "smod", "inputs": 2, "output": 1}, + {"name": "addmod", "inputs": 3, "output": 1}, + {"name": "mulmod", "inputs": 3, "output": 1}, + {"name": "exp", "inputs": 2, "output": 1}, + {"name": "signextend", "inputs": 2, "output": 1}, + # 0x10-0x1d: comparison & bitwise + {"name": "lt", "inputs": 2, "output": 1}, + {"name": "gt", "inputs": 2, "output": 1}, + {"name": "slt", "inputs": 2, "output": 1}, + {"name": "sgt", "inputs": 2, "output": 1}, + {"name": "eq", "inputs": 2, "output": 1}, + {"name": "iszero", "inputs": 1, "output": 1}, + {"name": "and", "inputs": 2, "output": 1}, + {"name": "or", "inputs": 2, "output": 1}, + {"name": "xor", "inputs": 2, "output": 1}, + {"name": "not", "inputs": 1, "output": 1}, + {"name": "byte", "inputs": 2, "output": 1}, + {"name": "shl", "inputs": 2, "output": 1}, + {"name": "shr", "inputs": 2, "output": 1}, + {"name": "sar", "inputs": 2, "output": 1}, + # 0x20: keccak + {"name": "keccak256", "inputs": 2, "output": 1}, + # 0x30-0x3f: environment + {"name": "address", "inputs": 0, "output": 1}, + {"name": "balance", "inputs": 1, "output": 1}, + {"name": "origin", "inputs": 0, "output": 1}, + {"name": "caller", "inputs": 0, "output": 1}, + {"name": "callvalue", "inputs": 0, "output": 1}, + {"name": "calldataload", "inputs": 1, "output": 1}, + {"name": "calldatasize", "inputs": 0, "output": 1}, + {"name": "calldatacopy", "inputs": 3, "output": 0}, + {"name": "codesize", "inputs": 0, "output": 1}, + {"name": "codecopy", "inputs": 3, "output": 0}, + {"name": "gasprice", "inputs": 0, "output": 1}, + {"name": "extcodesize", "inputs": 1, "output": 1}, + {"name": "extcodecopy", "inputs": 4, "output": 0}, + {"name": "returndatasize", "inputs": 0, "output": 1}, + {"name": "returndatacopy", "inputs": 3, "output": 0}, + {"name": "extcodehash", "inputs": 1, "output": 1}, + # 0x40-0x4a: block information + {"name": "blockhash", "inputs": 1, "output": 1}, + {"name": "coinbase", "inputs": 0, "output": 1}, + {"name": "timestamp", "inputs": 0, "output": 1}, + {"name": "number", "inputs": 0, "output": 1}, + {"name": "prevrandao", "inputs": 0, "output": 1}, + {"name": "gaslimit", "inputs": 0, "output": 1}, + {"name": "chainid", "inputs": 0, "output": 1}, + {"name": "selfbalance", "inputs": 0, "output": 1}, + {"name": "basefee", "inputs": 0, "output": 1}, + {"name": "blobhash", "inputs": 1, "output": 1}, + {"name": "blobbasefee", "inputs": 0, "output": 1}, + # 0x50-0x5e: stack, memory, storage (jump/jumpi/pc/jumpdest are not Yul builtins) + {"name": "pop", "inputs": 1, "output": 0}, + {"name": "mload", "inputs": 1, "output": 1}, + {"name": "mstore", "inputs": 2, "output": 0}, + {"name": "mstore8", "inputs": 2, "output": 0}, + {"name": "sload", "inputs": 1, "output": 1}, + {"name": "sstore", "inputs": 2, "output": 0}, + {"name": "msize", "inputs": 0, "output": 1}, + {"name": "gas", "inputs": 0, "output": 1}, + {"name": "tload", "inputs": 1, "output": 1}, + {"name": "tstore", "inputs": 2, "output": 0}, + {"name": "mcopy", "inputs": 3, "output": 0}, + # 0xa0-0xa4: logging + {"name": "log0", "inputs": 2, "output": 0}, + {"name": "log1", "inputs": 3, "output": 0}, + {"name": "log2", "inputs": 4, "output": 0}, + {"name": "log3", "inputs": 5, "output": 0}, + {"name": "log4", "inputs": 6, "output": 0}, + # 0xf0-0xff: system + {"name": "create", "inputs": 3, "output": 1}, + {"name": "call", "inputs": 7, "output": 1}, + {"name": "callcode", "inputs": 7, "output": 1}, + {"name": "return", "inputs": 2, "output": 0}, + {"name": "delegatecall", "inputs": 6, "output": 1}, + {"name": "create2", "inputs": 4, "output": 1}, + {"name": "staticcall", "inputs": 6, "output": 1}, + {"name": "revert", "inputs": 2, "output": 0}, + {"name": "invalid", "inputs": 0, "output": 0}, + {"name": "selfdestruct", "inputs": 1, "output": 0}, +] + + +# Opcodes whose names clash with solcore keywords get a trailing underscore +# in the wrapper function name, while the inner assembly call still uses the +# real EVM mnemonic. +RESERVED_NAMES = {"return": "return_"} + + +def wrapper_name(op): + return RESERVED_NAMES.get(op["name"], op["name"]) + + +def arg_names(n): + return [ascii_lowercase[i] for i in range(n)] + + +def gen_export(ops): + lines = ["export {"] + for i, op in enumerate(ops): + sep = "," if i < len(ops) - 1 else "" + lines.append(f" {wrapper_name(op)}{sep}") + lines.append("};") + return "\n".join(lines) + + +def gen_function(op): + name = op["name"] + fname = wrapper_name(op) + args = arg_names(op["inputs"]) + params = ", ".join(f"{a}: word" for a in args) + call_args = ", ".join(args) + ret_type = "word" if op["output"] == 1 else "()" + + lines = [f"function {fname}({params}) -> {ret_type} {{"] + if op["output"] == 1: + lines.append(" let res;") + lines.append(" assembly {") + lines.append(f" res := {name}({call_args})") + lines.append(" }") + lines.append(" return res;") + else: + lines.append(" assembly {") + lines.append(f" {name}({call_args})") + lines.append(" }") + lines.append("}") + return "\n".join(lines) + + +def render(ops): + parts = [gen_export(ops)] + for op in ops: + parts.append("") + parts.append(gen_function(op)) + return "\n".join(parts) + "\n" + + +def main(): + script_dir = os.path.dirname(os.path.abspath(__file__)) + out_path = os.path.join(script_dir, os.pardir, "std", "opcodes.solc") + with open(out_path, "w") as f: + f.write(render(opcodes)) + + +if __name__ == "__main__": + main() diff --git a/std/opcodes.solc b/std/opcodes.solc new file mode 100644 index 000000000..57d1990b4 --- /dev/null +++ b/std/opcodes.solc @@ -0,0 +1,684 @@ +export { + stop, + add, + mul, + sub, + div, + sdiv, + mod, + smod, + addmod, + mulmod, + exp, + signextend, + lt, + gt, + slt, + sgt, + eq, + iszero, + and, + or, + xor, + not, + byte, + shl, + shr, + sar, + keccak256, + address, + balance, + origin, + caller, + callvalue, + calldataload, + calldatasize, + calldatacopy, + codesize, + codecopy, + gasprice, + extcodesize, + extcodecopy, + returndatasize, + returndatacopy, + extcodehash, + blockhash, + coinbase, + timestamp, + number, + prevrandao, + gaslimit, + chainid, + selfbalance, + basefee, + blobhash, + blobbasefee, + pop, + mload, + mstore, + mstore8, + sload, + sstore, + msize, + gas, + tload, + tstore, + mcopy, + log0, + log1, + log2, + log3, + log4, + create, + call, + callcode, + return_, + delegatecall, + create2, + staticcall, + revert, + invalid, + selfdestruct +}; + +function stop() -> () { + assembly { + stop() + } +} + +function add(a: word, b: word) -> word { + let res; + assembly { + res := add(a, b) + } + return res; +} + +function mul(a: word, b: word) -> word { + let res; + assembly { + res := mul(a, b) + } + return res; +} + +function sub(a: word, b: word) -> word { + let res; + assembly { + res := sub(a, b) + } + return res; +} + +function div(a: word, b: word) -> word { + let res; + assembly { + res := div(a, b) + } + return res; +} + +function sdiv(a: word, b: word) -> word { + let res; + assembly { + res := sdiv(a, b) + } + return res; +} + +function mod(a: word, b: word) -> word { + let res; + assembly { + res := mod(a, b) + } + return res; +} + +function smod(a: word, b: word) -> word { + let res; + assembly { + res := smod(a, b) + } + return res; +} + +function addmod(a: word, b: word, c: word) -> word { + let res; + assembly { + res := addmod(a, b, c) + } + return res; +} + +function mulmod(a: word, b: word, c: word) -> word { + let res; + assembly { + res := mulmod(a, b, c) + } + return res; +} + +function exp(a: word, b: word) -> word { + let res; + assembly { + res := exp(a, b) + } + return res; +} + +function signextend(a: word, b: word) -> word { + let res; + assembly { + res := signextend(a, b) + } + return res; +} + +function lt(a: word, b: word) -> word { + let res; + assembly { + res := lt(a, b) + } + return res; +} + +function gt(a: word, b: word) -> word { + let res; + assembly { + res := gt(a, b) + } + return res; +} + +function slt(a: word, b: word) -> word { + let res; + assembly { + res := slt(a, b) + } + return res; +} + +function sgt(a: word, b: word) -> word { + let res; + assembly { + res := sgt(a, b) + } + return res; +} + +function eq(a: word, b: word) -> word { + let res; + assembly { + res := eq(a, b) + } + return res; +} + +function iszero(a: word) -> word { + let res; + assembly { + res := iszero(a) + } + return res; +} + +function and(a: word, b: word) -> word { + let res; + assembly { + res := and(a, b) + } + return res; +} + +function or(a: word, b: word) -> word { + let res; + assembly { + res := or(a, b) + } + return res; +} + +function xor(a: word, b: word) -> word { + let res; + assembly { + res := xor(a, b) + } + return res; +} + +function not(a: word) -> word { + let res; + assembly { + res := not(a) + } + return res; +} + +function byte(a: word, b: word) -> word { + let res; + assembly { + res := byte(a, b) + } + return res; +} + +function shl(a: word, b: word) -> word { + let res; + assembly { + res := shl(a, b) + } + return res; +} + +function shr(a: word, b: word) -> word { + let res; + assembly { + res := shr(a, b) + } + return res; +} + +function sar(a: word, b: word) -> word { + let res; + assembly { + res := sar(a, b) + } + return res; +} + +function keccak256(a: word, b: word) -> word { + let res; + assembly { + res := keccak256(a, b) + } + return res; +} + +function address() -> word { + let res; + assembly { + res := address() + } + return res; +} + +function balance(a: word) -> word { + let res; + assembly { + res := balance(a) + } + return res; +} + +function origin() -> word { + let res; + assembly { + res := origin() + } + return res; +} + +function caller() -> word { + let res; + assembly { + res := caller() + } + return res; +} + +function callvalue() -> word { + let res; + assembly { + res := callvalue() + } + return res; +} + +function calldataload(a: word) -> word { + let res; + assembly { + res := calldataload(a) + } + return res; +} + +function calldatasize() -> word { + let res; + assembly { + res := calldatasize() + } + return res; +} + +function calldatacopy(a: word, b: word, c: word) -> () { + assembly { + calldatacopy(a, b, c) + } +} + +function codesize() -> word { + let res; + assembly { + res := codesize() + } + return res; +} + +function codecopy(a: word, b: word, c: word) -> () { + assembly { + codecopy(a, b, c) + } +} + +function gasprice() -> word { + let res; + assembly { + res := gasprice() + } + return res; +} + +function extcodesize(a: word) -> word { + let res; + assembly { + res := extcodesize(a) + } + return res; +} + + +function extcodecopy(a: word, b: word, c: word, d: word) -> () { + assembly { + extcodecopy(a, b, c, d) + } +} + + +function returndatasize() -> word { + let res; + assembly { + res := returndatasize() + } + return res; +} + +function returndatacopy(a: word, b: word, c: word) -> () { + assembly { + returndatacopy(a, b, c) + } +} + +function extcodehash(a: word) -> word { + let res; + assembly { + res := extcodehash(a) + } + return res; +} + +function blockhash(a: word) -> word { + let res; + assembly { + res := blockhash(a) + } + return res; +} + +function coinbase() -> word { + let res; + assembly { + res := coinbase() + } + return res; +} + +function timestamp() -> word { + let res; + assembly { + res := timestamp() + } + return res; +} + +function number() -> word { + let res; + assembly { + res := number() + } + return res; +} + +function prevrandao() -> word { + let res; + assembly { + res := prevrandao() + } + return res; +} + +function gaslimit() -> word { + let res; + assembly { + res := gaslimit() + } + return res; +} + +function chainid() -> word { + let res; + assembly { + res := chainid() + } + return res; +} + +function selfbalance() -> word { + let res; + assembly { + res := selfbalance() + } + return res; +} + +function basefee() -> word { + let res; + assembly { + res := basefee() + } + return res; +} + +function blobhash(a: word) -> word { + let res; + assembly { + res := blobhash(a) + } + return res; +} + +function blobbasefee() -> word { + let res; + assembly { + res := blobbasefee() + } + return res; +} + +function pop(a: word) -> () { + assembly { + pop(a) + } +} + +function mload(a: word) -> word { + let res; + assembly { + res := mload(a) + } + return res; +} + +function mstore(a: word, b: word) -> () { + assembly { + mstore(a, b) + } +} + +function mstore8(a: word, b: word) -> () { + assembly { + mstore8(a, b) + } +} + +function sload(a: word) -> word { + let res; + assembly { + res := sload(a) + } + return res; +} + +function sstore(a: word, b: word) -> () { + assembly { + sstore(a, b) + } +} + +function msize() -> word { + let res; + assembly { + res := msize() + } + return res; +} + +function gas() -> word { + let res; + assembly { + res := gas() + } + return res; +} + +function tload(a: word) -> word { + let res; + assembly { + res := tload(a) + } + return res; +} + +function tstore(a: word, b: word) -> () { + assembly { + tstore(a, b) + } +} + +function mcopy(a: word, b: word, c: word) -> () { + assembly { + mcopy(a, b, c) + } +} + +function log0(a: word, b: word) -> () { + assembly { + log0(a, b) + } +} + +function log1(a: word, b: word, c: word) -> () { + assembly { + log1(a, b, c) + } +} + +function log2(a: word, b: word, c: word, d: word) -> () { + assembly { + log2(a, b, c, d) + } +} + +function log3(a: word, b: word, c: word, d: word, e: word) -> () { + assembly { + log3(a, b, c, d, e) + } +} + +function log4(a: word, b: word, c: word, d: word, e: word, f: word) -> () { + assembly { + log4(a, b, c, d, e, f) + } +} + +function create(a: word, b: word, c: word) -> word { + let res; + assembly { + res := create(a, b, c) + } + return res; +} + +function call(a: word, b: word, c: word, d: word, e: word, f: word, g: word) -> word { + let res; + assembly { + res := call(a, b, c, d, e, f, g) + } + return res; +} + +function callcode(a: word, b: word, c: word, d: word, e: word, f: word, g: word) -> word { + let res; + assembly { + res := callcode(a, b, c, d, e, f, g) + } + return res; +} + +function return_(a: word, b: word) -> () { + assembly { + return(a, b) + } +} + +function delegatecall(a: word, b: word, c: word, d: word, e: word, f: word) -> word { + let res; + assembly { + res := delegatecall(a, b, c, d, e, f) + } + return res; +} + +function create2(a: word, b: word, c: word, d: word) -> word { + let res; + assembly { + res := create2(a, b, c, d) + } + return res; +} + +function staticcall(a: word, b: word, c: word, d: word, e: word, f: word) -> word { + let res; + assembly { + res := staticcall(a, b, c, d, e, f) + } + return res; +} + +function revert(a: word, b: word) -> () { + assembly { + revert(a, b) + } +} + +function invalid() -> () { + assembly { + invalid() + } +} + +function selfdestruct(a: word) -> () { + assembly { + selfdestruct(a) + } +} diff --git a/test/Cases.hs b/test/Cases.hs index dc5f0472b..b928cbfb8 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -209,6 +209,15 @@ pragmas = where pragmaFolder = "./test/examples/pragmas" +opcodes :: TestTree +opcodes = + testGroup + "Files for opcodes wrappers" + [ runTestForFile "all-shapes.solc" opcodesFolder + ] + where + opcodesFolder = "./test/examples/opcodes" + cases :: TestTree cases = testGroup diff --git a/test/Main.hs b/test/Main.hs index 1d1a1cbdf..eff92945d 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -17,6 +17,7 @@ tests = [ parserTests, cases, comptime, + opcodes, pragmas, spec, std, diff --git a/test/examples/opcodes/all-shapes.solc b/test/examples/opcodes/all-shapes.solc new file mode 100644 index 000000000..c09bb4691 --- /dev/null +++ b/test/examples/opcodes/all-shapes.solc @@ -0,0 +1,31 @@ +import std.opcodes.{*}; + +// Compilation test for the std/opcodes wrappers. +// Picks two opcodes from each of the four shape categories so the +// pipeline exercises every wrapper signature. + +// no inputs, no return +function shape_void_void() -> () { + stop(); + invalid(); +} + +// no inputs, returns a word +function shape_void_word() -> word { + let a = address(); + let t = timestamp(); + return a; +} + +// inputs, no return +function shape_word_void(x: word) -> () { + pop(x); + mstore(0, x); +} + +// inputs, returns a word +function shape_word_word(a: word, b: word) -> word { + let s = add(a, b); + let m = mload(0); + return s; +}