Testing and deploying Huff contracts

In this post, we will dive into the steps involved in testing and deploying a smart-contract written using the Huff programming language.

For those who don’t know:

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 and instead exposes its programming stack to the developer for manual manipulation.

The Aztec Protocol team originally created Huff to write Weierstrudel, an on-chain elliptical curve arithmetic library that requires incredibly optimized code that neither Solidity nor Yul could provide.

Note that this post is neither an introduction to Huff nor a step-by-step tutorial.This post aims to help the beginners to quickly get started with testing and deployment of their Huff contracts. If you are new to Huff, this video by devtooligan is a great resource. Also feel free to explore the official documentation of Huff at your own pace.

The Math Contract:

We are going to use a simple huff contract that performs basic math operations like add, subtract, multiply, etc.,

Note that the code below is not a complete Huff contract. It just contains the logic. The function dispatcher and other parts are contained in the wrapper. You can view the wrapper here.

#define macro ADD_NUMBERS() = takes (2) returns (1) {
// Input stack:      // [num2, num1]
}

#define macro SUB_NUMBERS() = takes (2) returns (1) {
// Input stack:      // [num2, num1]
swap1                // [num1, num2]
sub                  // [num1 - num2]
}

#define macro MULTIPLY_NUMBERS() = takes (2) returns (1) {
// Input stack:      // [num2, num1]
mul                  // [num2 * num1]
}

#define macro DIVIDE_NUMBERS() = takes (2) returns (1) {
// Input stack:      // [num2, num1]
swap1                // [num1, num2]
div                  // [num1 / num2]
}

#define macro ABS() = takes (2) returns (1) {
// Input stack:     // [num2, num1]
dup1
dup3
lt
iszero swapAndSubtract jumpi
sub
complete jump

swapAndSubtract:
swap1
sub

complete:
}


The above implementation is very basic and as always there is a plenty of room for improvements. But that’s a topic for another day.

Testing the contract:

We can test the above contract in 2 ways.

Using Foundry:

To test the above logic using foundry, we need to deploy the wrapper contract since it is the one that exposes the functions. Also we need to use HuffDeployer helper contract from the foundry-huff library.

Here’s how the tests should be setup:

• Define the interface of the contract (IMath).

• Deploy the wrapper contract (in the setUp()method) using the HuffDeployer.

• Cast the returned address to the IMath interface.

• Write tests as usual.

/// Import HuffDeployer
import {HuffDeployer} from "foundry-huff/HuffDeployer.sol";

contract MathTest is Test {
IMath public math;

function setUp() public {
"../test/foundry/wrappers/MathWrapper"
);
}

assertEq(result, 489);
}

function testAddNumbers_fuzz(uint256 a, uint256 b) public {
unchecked {
uint256 c = a + b;

if (c > MAX) {
vm.expectRevert();
return;
}

assertEq(result, a + b);
}
}

function testSubNumbers() public {
uint256 result = math.subNumbers(420, 69);
assertEq(result, 351);
}

...
...

}


You can view all the foundry tests here in the repo.

Huff Tests:

Testing Huff contracts using Foundry is the most commonly used method. But we can also write simpler (and faster) unit tests using Huff itself. The huff compiler (huffc) has a test command, which takes in the filename as an argument which contains the tests. We can use the TestHelpers util created by Maddiaa for basic operations like ASSERT , etc.,

A sample Huff test contract will look like:

// ./test/huff/Math.t.huff

/* Imports */
#include "./helpers/TestHelpers.huff"
#include "../../src/Math.huff"

/* Tests */
0x01                // [1]
0x02                // [2,1]
0x03                // [3,sum]
ASSERT_EQ()         // [3 == sum]

0x4563918244f40000  // [5e18]
0x4563918244f40000  // [5e18, 5e18]
0x8ac7230489e80000  // [10e18, SUM]
ASSERT_EQ()         // [10e18==SUM]
}
...
...


Complete test file here.

We can run the tests using the command:

$huffc ./test/Math.t.huff test The output will be something similar to the below image: Deploying Huff contracts: We just saw how to test Huff contracts using Foundry and Huff. Now we can move on to the next step which is deploying the Huff contract to a EVM-based blockchain (Goerli , in this case). Here’s the foundry script to deploy the MathWrapper contract. // scripts/DeployMath.s.sol // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.15; import "foundry-huff/HuffDeployer.sol"; import "forge-std/Script.sol"; import {IMath} from "../src/interfaces/IMath.sol"; contract Deploy is Script { function run() public returns (IMath math) { math = IMath(HuffDeployer.broadcast("wrappers/MathWrapper")); console2.log("MathWrapper contract deployed to: ", address(math)); } }  The above script can be executed by running the following command: $ source .env && forge script scripts/DeployMath.s.sol:DeployMath --fork-url $RPC_URL --private-key$PRIVATE_KEY --broadcast


You need to configure the RPC_URL of the network and PRIVATE_KEY of the deployer in the .env file.

If everything works as intended, you’ll be seeing an output something like this:

To validate the deployment you can either add assertions in the script’s run() method or you can also implement fork tests with the deployed contract address similar to this one. If you want to deploy contracts with arguments, you can have a look into my Huffbound deployment script.

Sweet. We have successfully tested and deployed our simple Math.huff` contract. You can follow the same process to test and deploy more complex Huff contracts as well.

Here’s the link to GitHub repo:

Until next time 👋👋.

Subscribe to PraneshASP ⚡