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>