A low-level assembly language for the Ethereum Virtual Machine built in blazing-fast pure rust.

Overview

huff-rs • ci License Crates.io Discord

huff-rs is a Huff compiler built in rust.

What is a Huff?

Huff is a low-level programming language designed for developing highly optimized smart contracts that run on the Ethereum Virtual Machine (EVM). Huff does not hide the inner workings of the EVM. Instead, Huff exposes its programming stack to the developer for manual manipulation.

Rather than having functions, Huff has macros - individual blocks of bytecode that can be rigorously tested and evaluated using the Huff runtime testing suite.

Huff was originally developed by the Aztec Protocol team to write Weierstrudel. Weierstrudel is an on-chain elliptical curve arithmetic library that requires incredibly optimized code that neither Solidity nor Yul could provide.

While EVM experts can use Huff to write highly-efficient smart contracts for use in production, it can also serve as a way for beginners to learn more about the EVM.

To dive deeper into Huff, visit the Official Huff Docs(also available on github).

Installation

Something not working? Send a message in discord.

First run the command below to get huffup, the Huff installer:

curl -L get.huff.sh | bash

To avoid redirecting the script directly into bash, download and run the huffup installation script.

To install the Huff compiler, simply run huffup.

If you have the old huffc (TypeScript version) npm package installed globally, you can remove it with:

sudo yarn global remove huffc

To make sure you are running the rust version, you can run huffc --version and it should respond with huff_cli <version>. If it responds with 2.0.0 that means you are running the Typescript version.

$ huffc --version
huff_cli 0.1.0

Alternatively

Install from source by running:

git clone https://raw.githubusercontent.com/huff-language/huff-rs
cd huff-rs
cargo install --path ./huff_cli --bins --locked --force

OR

cargo install --git https://raw.githubusercontent.com/huff-language/huff-rs --locked huff_cli

How Fast?

Compilation Benchmarks

Compiler Cold (No Cache) Light Cache Deep Cache Full Cache
[huff-language/huff-rs][huff-rs] XXXms XXXms XXXms XXXms
[huff-language/huffc][huffc] XXXms XXXms XXXms XXXms

Note: Compilation benchmarks were performed on huff-examples erc20.

Architecture

Huff Compiler Architecture

Modules

  • huff_core: The core module to huff-rs. Resolves source file paths, executes compilation, and exports artifacts.
  • huff_cli: The command line interface for the Huff compiler.
  • huff_lexer: Takes in the source of a .huff file and generates a vector of Tokens.
  • huff_parser: Crafts a Contract AST from the the vector of Tokens generated by huff_lexer.
  • huff_codegen: EVM Bytecode generation module that accepts an AST generated by huff_parser.
  • huff_utils: Various utilities and types used by all modules.
  • huffup: Update or revert to a specific huff-rs branch with ease. (Forked from foundry)

Contributing

All contributions are welcome! We want to make contributing to this project as easy and transparent as possible, whether it's:

  • Reporting a bug
  • Discussing the current state of the code
  • Submitting a fix
  • Proposing new features
  • Becoming a maintainer

We use GitHub issues to track public bugs. Report a bug by opening a new issue; it's that easy!

To run examples, make sure you update git submodules to pull down the huff-examples submodule by running git submodule update.

The branching convention used by huff-rs is a stage branch that is meant to be merged off of and is periodically merged into main. So, when creating a feature, branch off of the stage branch and create a pr from your branch into the stage branch!

i.e: Branching Conventions

To pass github actions, please run:

cargo check --all
cargo test --all --all-features
cargo +nightly fmt -- --check
cargo +nightly clippy --all --all-features -- -D warnings

In order to fix any formatting issues, run:

cargo +nightly fmt --all

Recommended PR Template

Here is an example PR template - not strictly required, but will greatly improve the speed at which your PR is reviewed & merged!

## Overview

<Provide a general overview of what your pr accomplishes, why, and how (including links)>

## Checklist

- [x] <Ex: Added a `new` method to the Huff Lexer [here](./huff_lexer/src/lib.rs#50)>
- [x] <Ex: Fully tested the `new` method [here](./huff_lexer/tests/new.rs)>
- [ ] <Ex: Wrote documentation for the `new` method [here](./huff_lexer/README.md#20)>

When the PR checklist isn't complete, it is highly recommended to make it a draft PR. NOTE: if your PR is not complete, it will likely be changed to a draft by one of the repository admins.

For breaking changes: make sure to edit the excalidraw asset and export the file to ./assets/huffc.excalidraw along with an image to ./assets/huffc.png.

Safety

Warning

This is experimental software and is provided on an "as is" and "as available" basis. Expect rapid iteration and use at your own risk.

This code is not designed for safety.

  • There are untested invariants in the code that may break.
  • You can easily shoot yourself in the foot if you're not careful.
  • You should thoroughly read the documentation and examples.

We do not give any warranties and will not be liable for any loss incurred through any use of this codebase.

Acknowledgements

The original Huff Language compiler: huffc.

An exemplary, minimal rust compiler: ripc.

Foundry, for the many scripts, documentation, devops, and code on which huff-rs is based on.

All huff-rs contributors, users, advocates, and enthusiasts!

Comments
  • feat: constructor bootstrap code overrides

    feat: constructor bootstrap code overrides

    I think it could be useful if we allowed the user to override the normal bootstrap code with something custom. This could be used in multiple niche cases, such as metamorphic contracts. Additionally, since Huff is often used for education purposes, some may want to write the bootstrap code themselves to better understand the contract deployment process.

    I see two main ways of accomplishing this:

    1. If a constructor contains the RETURN opcode in it, automatically omit the bootstrap code.
    2. Add a command line flag to omit the bootstrap code.

    I'd be happy to implement either of these myself, but want to make sure that the change would be well received first.

    opened by ncitron 3
  • feat: Interface Output Location Override

    feat: Interface Output Location Override

    Overview

    Currently the huff_cli -g flag tells the compiler to generate a solidity interface file at the same location as the source with an I prefix.

    We should allow the -g flag to accept an optional string location of where to generate the output interfaces (with it by default being in the same location as each source file).

    cli med-priority 
    opened by refcell 3
  • "BASEFEE" opcode

    Hi !

    The basefee opcode (0x48) is not suppoted by Huff.

    I've never used, nor heard about anyone who use it, but it is needed for HyVM to be really exhaustive :) (it is currently implemented as a revert)

    Thanks !

    opened by oguimbal 2
  • add checks for evm types as arg names

    add checks for evm types as arg names

    Overview

    Attempts a fix for #196

    Checklist

    • [x] 1.1 Naming parameters as native solidity datatypes should lead to an error #define function balanceOf(address uint256) view returns (uint256 balance) => now errors with: carbon(2)

    • [x] 1.2 A lack of a comma in a function definition should not lead to following datatypes affecting the function signature

      • This will not compile now as of 1.1
    • [x] 2. You should be allowed to give a parameter a name that starts with "int" / "uint" / "bytes"

      • This was due to a bug in the PrimitiveEVMType that optimistically unwrapped on parsing the remaining bit of a string as a usize.
    • [x] 3. You should be able to name dynamic parameters (string, bytes, <type>[], etc.)

    parser med-priority bug 
    opened by refcell 2
  • feat(huff_tests): Cheatcodes

    feat(huff_tests): Cheatcodes

    Overview

    Adds a new inspector to the test runner to detect calls to a cheatcode address. Cheatcodes can be invoked by sending a staticcall to 0x00000000000000000000000000000000bEefbabe with the following calldata layout:

    ╔═════════╦═══════════════╗
    ║ Offset  ║     Value     ║
    ╠═════════╬═══════════════╣
    ║ 0x00    ║ cheatcode_key ║
    ║ 0x20    ║ current_pc    ║
    ║ 0x40... ║ args          ║
    ╚═════════╩═══════════════╝
    

    The first cheatcode is LOG, which allows logging stack items within Huff tests. Screen Shot 2022-08-16 at 2 23 09 AM

    Will need to add a Huff tests cheatcode contract to Huffmate tomorrow:

    #define macro LOG() = takes (1) {
        // Input stack:   [log_item]
        pc             // [pc, log_item]
        0x01           // [log_cheatcode, pc, log_item]
        0x00 mstore    // [pc, log_item]
        0x20 mstore    // [log_item]
        0x40 mstore    // []
        0x00 dup1      // [0x00, 0x00]
        0x60 dup2      // [0x00, 0x60, 0x00, 0x00]
        0x00000000000000000000000000000000bEefbabe
        gas            // [gas, beef_babe, 0x00, 0x60, 0x00, 0x00]
        staticcall pop // []
    }
    
    huff tests 
    opened by clabby 2
  • feat: Huff tests

    feat: Huff tests

    Overview

    Adds a stripped down test suite to huff-rs, mainly for gas profiling and creating assertions. Peek 2022-08-07 00-56

    Usage

    CLI: huffc <contract_path> -t [-f <list|table|json>] [-m <TEST_NAME>]

    Test syntax:

    #[calldata("0x0000000000000000000000000000000000000000000000000000000000000020"), value(0x01)]
    #define test MY_TEST() = takes (0) returns (0) {
        // test code
    }
    
    #define test MY_TEST_2() = takes (0) returns (0) {
        // test code
    }
    
    #define macro MY_MACRO() = takes (0) returns (0) {
        MY_TEST() // Tests cannot be invoked- this will throw an error.
    }
    

    Checklist

    • [x] Create a test runner that utilizes revm
      • [x] Tests for the runner
      • [x] Configurable test environment (gas limit, chain id, hardfork, call value, etc.)
      • [ ] Simple input fuzzer (? - not necessarily needed, might be nice for fuzzed gas profiling.)
    • [x] Implement test macros into the lexer / parser
      • [x] Restrict regular macros / functions from invoking test macros
    • [x] Add a subcommand / flag to the CLI to run the test suite
    • [x] Clean up
      • [x] Proper errors
      • [x] README
    cli 
    opened by clabby 2
  • docs: add steps to remove old version of huffc

    docs: add steps to remove old version of huffc

    Overview

    Please provide a short description here and review the requirements below.

    I was having probs removing the old huffc npm package and didnt even realize it at first because it seemed to take when i ran yarn global remove huffc and also i didnt know what version we were on or that the --help files look different now. Finally after some headbanging I realized i was still running the old version and went back and tried removing it again but with sudo this time, and that worked.

    Thought I'd add something in the docs here to help others in case they have a similar experience.

    opened by devtooligan 2
  • feat(huff_parser): expand macros

    feat(huff_parser): expand macros

    Overview

    When parsing, we need to expand macros to make code generation as easy as possible.

    Macro expansion refers to the process of taking an invocation, and expanding to its relevant opcodes, with the arguments substituted. This is a crucial part of eventually compiling Huff code, whether we include it in parsing or not.

    An example of macro expansion:

    #define macro EXPAND_ME() = takes(0) returns(0) {
        0x02 0x01 add
    }
    
    #define macro MAIN() = takes(0) returns(0) {
       0x01 0x02 sub
       EXPAND_ME()
    }
    

    Once the compiler has turned the code into tokens, the macro expansion and parsing will begin. The parser will begin parsing the macros, and notice there is an invocation to the EXPAND_ME macro. It will 'expand,' or substitute, the necessary opcodes into the main macro. If we represent the newly parsed code in Huff, it would look like below.

    #define macro MAIN() = takes(0) returns(0) {
       0x01 0x02 sub
       0x02 0x01 add
    }
    

    The alternative to doing this is in code generation. Instead of parsing the expansions, we can look for invocations in code generation.

    Expanding the macros at the parsing stage is somewhat better than doing it add the code generation stage as it means we do not have to track the invocations in vectors, and have ambiguity regarding ordering.

    parser med-priority 
    opened by sudovirtual 2
  • feat(huff_utils): Add `codemap-diagnostic` for fancy errors

    feat(huff_utils): Add `codemap-diagnostic` for fancy errors

    Overview

    Currently, we're returning errors that don't give the developer enough information. #33 added slightly more specific error messages for development purposes, but codemap-diagnostic (suggested by @sudovirtual) provides much more verbose feedback.

    We should create an error handling function or struct in huff_utils/error.rs to make it as easy as possible to create diagnostic messages and emit them throughout the codebase.

    med-priority 
    opened by clabby 2
  • fix(parser): Function definition and test

    fix(parser): Function definition and test

    Overview

    Previously, the function definition in the AST allowed a function to have multiple types.

    It was changed to only allow one single type, as multiple don't make sense.

    A function definition not having a return type should be an invalid syntax.

    parser 
    opened by exp-table 2
  • fix(huff_lexer): Bytes are being incorrectly lexed

    fix(huff_lexer): Bytes are being incorrectly lexed

    Overview

    Right now (master @ 5fd1e325625612a931ef10e428bf07197399b840), the huff_lexer incorrectly parses bytes / hex number tokens.

    Failing Test

    #[test]
    fn parses_bytes() {
        let source = "0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF";
        let mut lexer = Lexer::new(source);
        assert_eq!(lexer.source, source);
    
        // Try to parse the bytes token (assuming that it is a single token)
        let tok = lexer.next();
        let unwrapped = tok.unwrap().unwrap();
        let bytes_span = Span::new(0..source.len());
        assert_eq!(unwrapped, Token::new(TokenKind::Ident(source), bytes_span));
        assert_eq!(lexer.span, bytes_span);
    
        // We covered the whole source
        assert_eq!(lexer.span.end, source.len());
        assert!(lexer.eof);
        assert!(lexer.next().is_none());
    }
    

    Expected Result

    tok = Token { kind: Ident("0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF"), span: Span { start: 0, end: 66 } }

    Or a dedicated TokenKind variant for bytes / hex tokens.

    Actual Result

    tok = Token { kind: Num(0), span: Span { start: 0, end: 1 } }

    Suspected Reason for Issue

    The lexer dyn_consumes all valid ascii digits when it comes across what it thinks is a TokenKind::Num. When dyn_consume hits the second character of a bytes / hex number token, x, it completes, splitting the bytes / hex number token into two parts: The TokenKind::Num(0) and the TokenKind::Ident("xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF"). Some extra logic needs to be added to this match arm in order to correctly lex these tokens.

    lexer 
    opened by clabby 2
  • Using derived storage pointer instead of constant when passing as argument to macro breaks

    Using derived storage pointer instead of constant when passing as argument to macro breaks

    The following snippets are identical in theory:

    #define constant SWAG = 0x00
    
    #define macro M(slot) = {
    	<slot> sload 0x00 mstore 0x14 0x00 return
    }
    
    #define macro MAIN() = {
    	M(SWAG)
    }
    
    #define constant SWAG = FREE_STORAGE_POINTER()
    
    #define macro M(slot) = {
    	<slot> sload 0x00 mstore 0x14 0x00 return
    }
    
    #define macro MAIN() = {
    	M(SWAG)
    }
    

    But the latter errors without context with Error: Storage Pointers Not Derived.

    Seems to be a bug?

    opened by af-afk 1
  • feat(global) make huff_core wasm compatible and add a JS binding

    feat(global) make huff_core wasm compatible and add a JS binding

    Overview

    With some minor tweaks and shims, huff_core can be compiled to a wasm target and then be used with JavaScript which even works in the browser.

    1. Remove unused dependency prop-test which is not wasm compatible
    2. Shim for into_par_iter() for creating parallel iterators. Not supported on wasm but can just fallback to a regular iterator.
    3. SystemTime::now panics on wasm as it's not implemented so use a u64 unix timestamp on wasm target instead.

    The biggest change required was to refactor out the file system stuff and create an alternative "in memory" version which loads files from a HashMap instead of from disk.

    With that all working, I put together a new module huff_js which includes JavaScript bindings so you can interface with the compiler via a JSON interface similar to standard json in solc.

    This would make Huff more accessible to integrate with web-based tools like https://www.evm.codes/ which is why I started looking at this in the first place. It would be awesome to have Huff available on there.

    Example JS usage:

    const { compile } = require('./pkg/huffc.js')
    
    let files = {
        "add.huff": "#define function add(uint256,uint256) nonpayable returns (uint256)\n" +
            "\n" +
            "#define macro MAIN() = {\n" +
            "   // Load our numbers from calldata and add them together.\n" +
            "   0x04 calldataload // [number1]\n" +
            "   0x24 calldataload // [number2]\n" +
            "   add               // [number1+number2]\n" +
            "   // Return our new number.\n" +
            "   0x00 mstore // Store our number in memory.\n" +
            "   0x20 0x00 return // Return it.\n" +
            "}\n"
    }
    
    const result = compile({
        files,
        sources: ['add.huff'],
        // constructor_args: "..."
    });
    
    console.log(result)
    

    Outputs:

    {
      errors: undefined,
      contracts: Map(1) {
        'add.huff' => {
          bytecode: '600f8060093d393df36004356024350160005260206000f3',
          runtime: '6004356024350160005260206000f3',
          abi: [Object]
        }
      }
    }
    
    opened by kanewallmann 1
  • fix(huff_utils) incorrect path localization

    fix(huff_utils) incorrect path localization

    Overview

    When the parent path doesn't start with a ./ or ../ and the child starts with a ../ the calculated path is incorrect.

    e.g.

    files::FileSource::localize_file("examples/ERC20.huff", "../random_dir/Address.huff")
    
    // returns    "../examples/random_dir/Address.huff"
    // expected   "./random_dir/Address.huff"
    

    Fixed by checking if parent starts with a . and if not prefixing it with one.

    Added a regression test.

    opened by kanewallmann 0
  • `huffup` broken symbolic link

    `huffup` broken symbolic link

    huffup, when used with --path to install a local repo does not work for me. The error is:

    huffup: command failed: ln -s PWD/target/release/huffc HUFF_BIN_DIR/huffc
    

    because HUFF_BIN_DIR/huffc already exists.

    I think that we want to link PWD/target/release/huffc with HUFF_BIN_DIR/huffc and not HUFF_BIN_DIR/huff.

    https://github.com/huff-language/huff-rs/blob/e0f640f12373e0a98edfc1e33eb4cc40c6bec129/huffup/huffup#L78

    opened by MathisGD 0
Owner
Huff
Huff is a low level assembly language for the Ethereum Virtual Machine
Huff
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.

foundry Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. Foundry consists of: Forge: Ethe

Georgios Konstantopoulos 5.1k Jan 9, 2023
An EVM low-level language that gives full control over the control flow of the smart contract.

Meplang - An EVM low-level language Meplang is a low-level programming language that produces EVM bytecode. It is designed for developers who need ful

MEP 19 Jan 31, 2023
Substreams development kit for Ethereum chains, contains Firehose Block model and helpers as well as utilities for Ethereum ABI encoding/decoding.

Substreams Ethereum Substreams development kit for Ethereum chains, contains Rust Firehose Block model and helpers as well as utilities for Ethereum A

StreamingFast 15 Oct 25, 2022
Ethereum (and Ethereum like) indexer using P2P message to fetch blocks and transactions

Ethereum P2P indexer This project is an indexer for Ethereum and Ethereum forks. It takes advantage of the ETH (Ethereum Wire Protocol) to fetch block

null 5 Nov 10, 2023
Zero-Knowledge Assembly language and compiler

zkAsm A Zero-Knowledge circuit assembly language, designed to represent Zero-Knowledge circuits in a compressed format, to be stored on blockchains. I

null 1 Dec 30, 2021
Rust based Virtual Machine on Avalanche that implements Bulletproof ZK Proofs.

BulletproofVM Rust based Virtual Machine on Avalanche that implements Bulletproof ZK Proofs. Zero-Knowledge Virtual Machine This is a virtual machine

null 14 Jan 4, 2023
Low-level Bitcoin P2P Network Client

Peerlink What is Peerlink? Peerlink is a low-level network client for the Bitcoin P2P network written in Rust. It uses a nonblocking reactor to accept

Alfred Hodler 6 Dec 23, 2022
Simple, reliable, open-source contract verification built for an L2 centric Ethereum ecosystem

Cove This repo contains the backend verification1 code for Cove, a simple, reliable, open-source contract verification built for an L2 centric Ethereu

ScopeLift 12 Apr 1, 2023
A blazing fast, type-safe template engine for Rust.

markup.rs A blazing fast, type-safe template engine for Rust. markup.rs is a template engine for Rust powered by procedural macros which parses the te

Utkarsh Kukreti 209 Dec 24, 2022
🥷🩸 Madara is a ⚡ blazing fast ⚡ Starknet sequencer, based on substrate, powered by Rust 🦀

Report a Bug - Request a Feature - Ask a Question ⚡ Madara: Starknet Sequencer on Substrate ?? Welcome to Madara, a blazing fast ⚡ Starknet sequencer

Keep StarkNet Strange 138 Apr 22, 2023
Open sourcing a profitable MEV Arbitrage Bot written in blazing fast Rust.

Dex Arbitrage - MEV Bot Open sourcing a profitable MEV Arbitrage Bot written in blazing fast Rust. Before Starting I am a self-taught programmer, a co

null 4 Sep 18, 2023
Blazing fast Pedersen hash implementation for Node.JS

pedersen-fast Blazing fast Pedersen hash implementation for Node.JS Exposes starknet-crypto's implementation written in Rust as WASM package. Usage np

L2BEAT 7 Mar 10, 2023
Blazing fast toolkit for developing Starknet contracts.

Starknet Foundry Blazingly fast toolkit for developing Starknet contracts designed & developed by ex Protostar team from Software Mansion based on nat

Foundry 149 Aug 1, 2023
The fast, light, and robust client for the Ethereum mainnet.

OpenEthereum Fast and feature-rich multi-network Ethereum client. » Download the latest release « Table of Contents Description Technical Overview Bui

OpenEthereum 1.6k Dec 28, 2022
The fast, light, and robust client for Ethereum-like networks.

The Fastest and most Advanced Ethereum Client. » Download the latest release « Table of Contents Description Technical Overview Building 3.1 Building

OpenEthereum 6.7k Dec 24, 2022
Emerging smart contract language for the Ethereum blockchain.

Emerging smart contract language for the Ethereum blockchain.

null 1.4k Jan 9, 2023
Built for Perpetual Protocol v2 Curie on Optimism chain. This CLI tool was built with Rust.

Perpetual Protocol CLI for Perp v2 Curie This tool is to provide a simple, fast and efficient way to interact Perpetual Protocol contracts from your t

Brendan Wenzel 4 Jan 11, 2023
Rust project for working with ETH - Ethereum transactions with Rust on Ganache and also deploy smart contracts :)

Just a test project to work with Ethereum but using Rust. I'm using plain Rust here, not Foundry. In future we will use Foundry. Hope you're already f

Akhil Sharma 2 Dec 20, 2022
Custom Ethereum vanity address generator made in Rust

ethaddrgen Custom Ethereum address generator Get a shiny ethereum address and stand out from the crowd! Disclaimer: Do not use the private key shown i

Jakub Hlusička 153 Dec 27, 2022