Your resource for web content, online publishing
and the distribution of digital products.
S M T W T F S
 
 
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
 
11
 
12
 
13
 
14
 
15
 
16
 
17
 
18
 
19
 
20
 
21
 
22
 
23
 
24
 
25
 
26
 
27
 
28
 
29
 
30
 
31
 
 

Writing Smart Contract using Huff

DATE POSTED:December 2, 2024

Let’s write our first smart contract using Huff.

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

The contract is a simple storage contract that has a variable of type uint256. There is a function to update the value and one to read it.

Solidity Code:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

contract SimpleStorage {
uint256 public value;

function setValue(uint256 newValue) external {
value = newValue;
}

function getValue() external view returns (uint256) {
return value;
}
}Writing in Huff

In a transaction, we basically have a calldata that is being sent to the blockchain.
Ex:

0x552410770000000000000000000000000000000000000000000000000000000000000001

This consists of:

Now, we need to get the function_selector (starting 4 bytes) from above calldata.

But how??

RIGHT SHIFTING THE CALLDATA. (>>>>)
SHR opcode: shift given bits to the right.
But how many bits to shift?? total size-32 bytes, function_selector — 4 bytes
So, if we shift it right by 32–4 = 28 bytes (or 28 * 8 = 224 bits), we get the function selector.

0x55241077

The Huff code till here looks like:

#define macro MAIN() = takes(0) returns (0) {
0x00 // Stack: [0]
calldataload // [calldata]
0xe0 // [calldata(32 bytes), 0xe0]
shr // [function_selector]
}

Now we got the function selector.
We’ll try to match the function_selector we got with different function selectors we have here (setValue() and getValue()).

Function_selectors:

cast sig "setValue(uint256)"
> 0x55241077
cast sig "getValue()"
> 0x20965255

setValue(uint256) -> 0x55241077
getValue() -> 0x20965255

So, we’ll match the function selector obtained from calldata with the above two and call the matched function.

We can define interfaces in Huff in the following way:

/*Huff interfaces*/
#define function setValue(uint256) nonpayable returns()
#define function getValue() view returns(uint256)

Let’s use the above defined function selectors to try match them with the one obtained from the calldata.

/* Huff interfaces */
#define macro MAIN() = takes(0) returns(0) {
...
// Stack [function_selector]
__FUNC_SIG(setValue) // [function_selector, 0x55241077]
// 0x55241077 -> function selector for setValue()
eq // [true/false (if top two function selectors match)]

}

So, if the function selector from calldata matches with that of “setValue(uint256)”, the program counter should jump to setValue() function and it should be executed. Else it will check for “getValue()” and read the value. If no option matches, it should revert.

#define macro MAIN() = takes(0) returns(0) {
...
// If true (function selectors equal) -> jump to setValue
setJump // program counter(PC) for setValue() function
jumpi // jump to "setJump" PC

...

setJump:
SET_VALUE()
}

#define macro SET_VALUE() = takes(0) returns(0) {}

Also, if any of the function selector matches, we need to skip the order of instructions and jump to that function. Let’s write the case to check for “readValue” function.

#define macro MAIN() = takes(0) returns(0) {
...
// If false (function selectors not equal) -> check if it matches with getValue
__FUNC_SIG(getValue)
eq // Stack: [true if function selectors matches]
getJump // program counter(PC) for getValue() function
// Stack: [true if function selectors matches, getJump(PC)]
jumpi // jump to "getJump" PC, Stack: []

...

getJump:
GET_VALUE()
}

#define macro GET_VALUE() = takes(0) returns(0) {}

If no function selector matches with the one from the calldata, it should revert. We can use revert opcode to halt execution and reverting any state changes made.

The code till now looks like:

/*Huff interfaces*/
#define function setValue(uint256) nonpayable returns()
#define function getValue() view returns(uint256)

#define macro MAIN() = takes(0) returns(0) {
0x00 // Stack: [0]
calldataload // [calldata]
0xe0 // [calldata(32 bytes), 0xe0]
shr // [function_selector]
dup1 // [function_selector, function_selector]

// Stack [function_selector]
__FUNC_SIG(setValue) // [function_selector, 0x55241077]
// 0x55241077 -> function selector for setValue()
eq // [true/false (if top two function selectors match)]

// If true (function selectors equal) -> jump to setValue
setJump // program counter(PC) for setValue() function
jumpi // jump to "setJump" PC

// If false (function selectors not equal) -> check if it matches with getValue
__FUNC_SIG(getValue)
eq // Stack: [true if function selectors matches]
getJump // program counter(PC) for getValue() function
// Stack: [true if function selectors matches, getJump(PC)]
jumpi // jump to "getJump" PC, Stack: []

revert

setJump:
SET_VALUE()

getJump:
GET_VALUE()
}

#define macro SET_VALUE() = takes(0) returns(0) {}
#define macro GET_VALUE() = takes(0) returns(0) {}

Now let’s try to understand what would happen if the function selector matches with that of setValue().

When SET_VALUE macro is called, it should take the input value and update the variable. But how does it update the storage?

  • Firstly, get a storage slot
  • Read the new value from the calldata
  • Use sstore opcode to update the storage

We have something called as FREE_STORAGE_POINTER() that points to currently available storage slot.

#define constant VALUE_STORAGE_SLOT = FREE_STORAGE_POINTER();

#define macro SET_VALUE() = takes(0) returns(0) {
0x04 // [4]-bytes offset(need to remove fn_selector to get input)
calldataload // [calldata - func_selector(or value)]
[VALUE_STORAGE_SLOT] // [value, 0 (or ptr)]
sstore
stop
}

Now let’s see how getValue() works. To read a value:

  • Get the storage slot
  • Load the value to memory
  • Return value
#define macro GET_VALUE() = takes(0) returns(0) {
[VALUE_STORAGE_SLOT] // [KEY]
sload // [VALUE]
0x00 // [VALUE, 0]
mstore // Stack: [], Memory: [VALUE]
return // Stack: [], Memory: []
}

Final Code:

#define function setValue(uint256) nonpayable returns()
#define function getValue() view returns (uint256)

#define constant VALUE_STORAGE_SLOT = FREE_STORAGE_POINTER()

#define macro MAIN = takes(0) returns(0) {
0x00 calldataload 0xe0 shr

dup1 __FUNC_SIG(setValue) eq setValueJump jumpi

__FUNC_SIG(getValue) eq getValueJump jumpi

revert

setValueJump:
SET_VALUE()

getValueJump():
GET_VALUE()


}

#define macro SET_VALUE = takes(0) returns(0) {
0x04
calldataload
[VALUE_STORAGE_SLOT]
sstore
stop

}

# define macro GET_VALUE = takes(0) returns (0) {
[VALUE_STORAGE_SLOT]
sload
0x00
mstore
return
}

Github: https://github.com/dheerajkumardk/horseStore

Writing Smart Contract using Huff was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.