Categorygithub.com/solidifylabs/specops
modulepackage
1.0.0-alpha
Repository: https://github.com/solidifylabs/specops.git
Documentation: pkg.go.dev

# README

SpecOps Go Go Reference

specops is a low-level, domain-specific language and compiler for crafting Ethereum VM bytecode. The project also includes a CLI with code execution and terminal-based debugger.

special opcodes

Writing bytecode is hard. Tracking stack items is difficult enough, made worse by refactoring that renders every DUP and SWAP off-by-X. Reverse Polish Notation may be suited to stack-based programming, but it's unintuitive when context-switching from Solidity.

There's always a temptation to give up and use a higher-level language with all of its conveniences, but that defeats the point. What if we could maintain full control of the opcode placement, but with syntactic sugar to help the medicine go down?

Special opcodes provide just that. Some of them are interpreted by the compiler, converting them into regular equivalents, while others are simply compiler hints that leave the resulting bytecode unchanged.

Getting started

See the getting-started/ directory for creating your first SpecOps code. Also check out the examples and the documentation.

Do I have to learn Go?

No.

There's more about this in the getting-started/ README, including the rationale for a Go-based DSL.

Features

New features will be prioritised based on demand. If there's something you'd like included, please file an Issue.

  • JUMPDEST labels (absolute)
  • JUMPDEST labels (relative to PC)
  • PUSH(JUMPDEST) by label with minimal bytes (1 or 2)
  • Label tags; like JUMPDEST but don't add to code
  • Push multiple, concatenated JUMPDEST / Label tags as one word
  • PUSHSize(T,T) pushes Label and/or JUMPDEST distance
  • Function-like syntax (i.e. Reverse Polish Notation is optional)
  • Inverted DUP/SWAP special opcodes from "bottom" of stack (a.k.a. pseudo-variables)
  • PUSH<T> for native Go types
  • PUSH(v) length detection
  • Macros
  • Compiler-state assertions (e.g. expected stack depth)
  • Automated optimal (least-gas) stack transformations
    • Permutations (SWAP-only transforms)
    • General-purpose (combined DUP + SWAP + POP)
    • Caching of search for optimal route
  • Standalone compiler
  • In-process EVM execution (geth)
    • Full control of configuration (e.g. params.ChainConfig and vm.Config)
    • State preloading (e.g. other contracts to call) and inspection (e.g. SSTORE testing)
    • Message overrides (caller and value)
  • Debugger
    • Stepping
    • Breakpoints
    • Programmatic inspection (e.g. native Go tests at opcode resolution)
      • Memory
      • Stack
    • User interface
  • Source mapping
  • Coverage analysis
  • Fork testing with RPC URL

Documentation

The specops Go documentation covers all functionality.

Examples

Hello world

To run this example Code block with the SpecOps CLI, see the getting-started/ directory.

import . github.com/solidifylabs/specops

…

hello := []byte("Hello world")
code := Code{
    // The compiler determines the shortest-possible PUSH<n> opcode.
    // Fn() simply reverses its arguments (a surprisingly powerful construct)!
    Fn(MSTORE, PUSH0, PUSH(hello)),
    Fn(RETURN, PUSH(32-len(hello)), PUSH(len(hello))),
}

// ----- COMPILE -----
bytecode, err := code.Compile()
// ...

// ----- EXECUTE -----

result, err := code.Run(nil /*callData*/ /*, [runopts.Options]...*/)
// ...

// ----- DEBUG (Programmatic) -----
//
// ***** See below for the debugger's terminal UI *****
//

dbg, results := code.StartDebugging(nil /*callData*/ /*, Options...*/)
defer dbg.FastForward() // best practice to avoid resource leaks

state := dbg.State() // is updated on calls to Step() / FastForward()

for !dbg.Done() {
  dbg.Step()
  fmt.Println("Peek-a-boo", state.ScopeContext.Stack().Back(0))
}

result, err := results()
//...

Other examples

Debugger

Key bindings are described in the getting-started/ README.

image

Acknowledgements

Some of SpecOps was, of course, inspired by Huff. I hope to provide something different, of value, and to inspire them too.

# Packages

Package evmdebug provides debugging mechanisms for EVM contracts, intercepting opcode-level execution and allowing for inspection of data such as the VM's stack and memory.
No description provided by the author
Package revert provides errors and error handling for EVM smart contracts that revert.
Package runopts provides configuration options for specops.Code.Run().
Package specopscli provides a CLI for developing specops.Code.
Package stack provides stack-related "special opcodes" for use in specops code.
Package types defines types used by the specops package, which is intended to be dot-imported so requires a minimal footprint of exported symbols.

# Functions

Fn returns a Bytecoder that returns the concatenation of the *reverse* of bcs.
PUSH returns a PUSH<n> Bytecoder appropriate for the type.
PUSHBytes accepts [1,32] bytes, returning a PUSH<x> Bytecoder where x is the smallest number of bytes (possibly zero) that can represent the concatenated values; i.e.
PUSHSelector returns a PUSH4 Bytecoder that pushes the selector of the signature, i.e.
PUSHSize pushes abs(loc(a),loc(b)), i.e.

# Constants

Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.
Aliases of all regular vm.OpCode constants that don't have "special" replacements.

# Type aliases

Code is a slice of Bytecoders; it is itself a Bytecoder, allowing for nesting.
Inverted applies DUP<X> and SWAP<X> opcodes relative to the bottom-most value on the stack unless there are more than 16 values, in which case they are applied relative to the 16th.
A JUMPDEST is a Bytecoder that is converted into a vm.JUMPDEST while also storing its location in the bytecode for use via PUSH[string|JUMPDEST](<lbl>).
A Label marks a specific point in the code without adding any bytes when compiled.
Raw is a Bytecoder that bypasses all compiler checks and simply appends its contents to bytecode.