1

I am testing out using Huff language for smart contract development. It is my understanding that the benefit of Huff language is the improved gas efficiency. However, in my small test, the contract written in Huff actually uses slightly more gas compared to the contract written in Solidity. I'm assuming that it is due to poorly written code, but the example is so small that I have trouble identifying where the poor performance is coming from. If anyone is more familiar with the language and could provide some feedback on this contract, it would be much appreciated.

SolidityCounter.sol:

// SPDX-License-Identifier: BSL-1.1
pragma solidity 0.8.19;

contract SolidityCounter {
    modifier onlyOwner {
        require(msg.sender == owner, "Caller is not owner");
        _;
    }

    address public immutable owner;

    uint256 public counter = 0;

    constructor() {
        owner = msg.sender;
    }

    function increase(uint256 amount_) public onlyOwner returns (bool) {
        require(amount_ < 100, "Max increase exceeded");

        counter += amount_;

        return true;
    }
}

HuffCounter.huff:

/// @title HuffCounter
/// @notice SPDX-License-Identifier: MIT

/* Interface */
#define function increaseCount(uint256) nonpayable returns ()
#define function getCount() view returns (uint256)
#define function getOwner() view returns (address)

/* Storage Slots */
#define constant OWNER = 0x0000000000000000000000000000000000000000 // Must substitute owner at compile time
#define constant COUNT_LOCATION = FREE_STORAGE_POINTER()

/* Constructor */
#define macro CONSTRUCTOR() = takes (0) returns (0) {}

/* Methods */
#define macro IS_OWNER() = takes (0) returns (0) {
  caller                      // [msg.sender]
  [OWNER]                     // [owner, msg.sender]
  eq authed jumpi             // [authed]

  // Revert otherwise
  0x00 0x00 revert

  authed:
}

#define macro INCREASE_COUNT() = takes (0) returns (0) {
    IS_OWNER()

    0x04 calldataload   // [amount]
    dup1                // [amount, amount]
    0x64 lt             // [100 > amount, amount]
    err jumpi
    
    [COUNT_LOCATION]    // [ptr, amount]
    sload               // [count, amount]
    add                 // [count + amount]
    [COUNT_LOCATION]    // [ptr, count + amount]
    sstore              // []
    0x00 0x00 return

    err:
        0x00 0x00 revert
}

#define macro GET_COUNT() = takes (0) returns (0) {
    // Load value from storage.
    [COUNT_LOCATION]   // [ptr]
    sload              // [count]

    // Store value in memory.
    0x00 mstore

    // Return value
    0x20 0x00 return
}

#define macro GET_OWNER() = takes (0) returns (0) {
    // Load value from storage.
    [OWNER]   // [owner]

    // Store value in memory.
    0x00 mstore

    // Return value
    0x20 0x00 return
}

#define macro MAIN() = takes (0) returns (0) {
    // Identify which function is being called.
    0x00 calldataload 0xE0 shr
    
    dup1 __FUNC_SIG(increaseCount) eq increaseCount jumpi
    dup1 __FUNC_SIG(getCount) eq getCount jumpi
    dup1 __FUNC_SIG(getOwner) eq getOwner jumpi

    0x00 0x00 revert

    increaseCount:
        INCREASE_COUNT()
    getCount:
        GET_COUNT()
    getOwner:
        GET_OWNER()
}

Tx for Solidity Counter: https://goerli.etherscan.io/tx/0x9f886894965f2083f284d754faa4b2a8f73a318d2e9c7e7d0d82a5b307040092 Tx for Huff Counter: https://goerli.etherscan.io/tx/0x01969fd9cce49ea80c6624da2fa95c6516a85c3c802dce9beb9c7b8342ddda33

EDIT:

By changing the Huff contract to store the owner as a constant address, I was able to reduce the gas used from 45,500 to 43,400. To substitute the desired address at compile time, you must pass the constants like so:

huffc -c <CONSTANT_NAME>=<CONSTANT_VALUE> <PATH>

1 Answer 1

1

The huff implementation stores the owner address in a storage slot which cost more gas to read/write from/to. The solidity implementation however uses an immutable variable which stores it in the runtime code itself and is read only and way cheaper. It basically hardcodes the address wherever it is used in the runtime code, so just a push20 opcode.

You can hardcode the owner address to replicate the solidity 'constant' functionality and just call it in huff with [OWNER_LOCATION].

If it can only be known at deploy time however, you can use the foundry-huff helper .withCode(string) and parse in #define constant OWNER = vm.toString(owner)". This will append this string to your huff code before compiling.

1
  • Thanks so much for the feedback. Will try it out and let you know how it goes! Nov 7, 2023 at 9:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.