Foundry x Huff
A foundry library for working with huff contracts. Take a look at our project template to see an example project that uses this library.
Installing
First, install the huff compiler by running:
curl -L get.huff.sh | bash
Then, install this library with forge:
forge install huff-language/foundry-huff
Usage
The HuffDeployer is a Solidity library that takes a filename and deploys the corresponding Huff contract, returning the address that the bytecode was deployed to. To use it, simply import it into your file by doing:
import {HuffDeployer} from "foundry-huff/HuffDeployer.sol";
To compile contracts, you can use HuffDeployer.deploy(string fileName)
, which takes in a single string representing the filename's path relative to the src
directory. Note that the file ending, i.e. .huff
, must be omitted.
Here is an example deployment (where the contract is located in src/test/contracts/Number.huff
):
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.7.0 <0.9.0;
import {HuffDeployer} from "foundry-huff/HuffDeployer";
interface Number {
function setNumber(uint256) external;
function getNumber() external returns (uint256);
}
contract HuffDeployerExample {
function deploy() public {
// Deploy a new instance of src/test/contracts/Number.huff
address addr = HuffDeployer.deploy("test/contracts/Number");
// To call a function on the deployed contract, create an interface and wrap the address like so
Number number = Number(addr);
}
}
To deploy a Huff contract with constructor arguments, you can chain commands onto the HuffDeployer.
For example, to deploy the contract src/test/contracts/Constructor.huff
with arguments (uint256(0x420), uint256(0x420))
, you are encouraged to follow the logic defined in the deploy
function of the HuffDeployerArguments
contract below.
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.7.0 <0.9.0;
import {HuffDeployer} from "foundry-huff/HuffDeployer";
interface Constructor {
function getArgOne() external returns (address);
function getArgTwo() external returns (uint256);
}
contract HuffDeployerArguments {
function deploy() public {
// Deploy the contract with arguments
address addr = HuffDeployer
.config()
.with_args(bytes.concat(abi.encode(uint256(0x420)), abi.encode(uint256(0x420))))
.deploy("test/contracts/Constructor");
// To call a function on the deployed contract, create an interface and wrap the address
Constructor construct = Constructor(addr);
// Validate we deployed the Constructor with the correct arguments
assert(construct.getArgOne() == address(0x420));
assert(construct.getArgTwo() == uint256(0x420));
}
function depreciated_deploy() public {
address addr = HuffDeployer.deploy_with_args(
"test/contracts/Constructor",
bytes.concat(abi.encode(uint256(0x420)), abi.encode(uint256(0x420)))
);
// ...
}
}
HuffDeployer also enables you to instantiate contracts, from the test file, even if they have no constructor macro!
This is possible by using Foundry's ffi cheatcode.
NOTE: It is highly recommended that you read the foundry book, or at least familiarize yourself with foundry, before using this library to avoid easily susceptible footguns.
Let's use the huff contract src/test/contracts/NoConstructor.huff
, which has no defined constructor macro. The inline-instantiation defined in the deploy
function of the HuffDeployerCode
contract below is recommended.
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.7.0 <0.9.0;
import {HuffDeployer} from "foundry-huff/HuffDeployer";
interface Constructor {
function getArgOne() external returns (address);
function getArgTwo() external returns (uint256);
}
contract HuffDeployerCode {
function deploy() public {
// Define a new constructor macro as a string
string memory constructor_macro = "#define macro CONSTRUCTOR() = takes(0) returns (0) {"
" // Copy the first argument into memory \n"
" 0x20 // [size] - byte size to copy \n"
" 0x40 codesize sub // [offset, size] - offset in the code to copy from\n "
" 0x00 // [mem, offset, size] - offset in memory to copy to \n"
" codecopy // [] \n"
" // Store the first argument in storage\n"
" 0x00 mload // [arg] \n"
" [CONSTRUCTOR_ARG_ONE] // [CONSTRUCTOR_ARG_ONE, arg] \n"
" sstore // [] \n"
" // Copy the second argument into memory \n"
" 0x20 // [size] - byte size to copy \n"
" 0x20 codesize sub // [offset, size] - offset in the code to copy from \n"
" 0x00 // [mem, offset, size] - offset in memory to copy to \n"
" codecopy // [] \n"
" // Store the second argument in storage \n"
" 0x00 mload // [arg] \n"
" [CONSTRUCTOR_ARG_TWO] // [CONSTRUCTOR_ARG_TWO, arg] \n"
" sstore // [] \n"
"}";
// Deploy the contract with arguments
address addr = HuffDeployer
.config()
.with_args(bytes.concat(abi.encode(uint256(0x420)), abi.encode(uint256(0x420))))
.with_code(constructor_macro)
.deploy("test/contracts/NoConstructor");
// To call a function on the deployed contract, create an interface and wrap the address
Constructor construct = Constructor(addr);
// Validate we deployed the Constructor with the correct arguments
assert(construct.getArgOne() == address(0x420));
assert(construct.getArgTwo() == uint256(0x420));
}
function depreciated_deploy_with_code() public {
address addr = HuffDeployer.deploy_with_code(
"test/contracts/Constructor",
constructor_macro
);
// ...
}
}