
In this article, we are going to explore the solution to the Huff Challenge 4.
You can find walkthroughs for other challenges below:
Also if you are new to huff and wanna learn how to test and deploy Huff contracts, check out this article.
Alright let’s dive in.
In this challenge, we're tasked with writing a Huff smart contract that reverses the calldata that it receives. Essentially, if you send data to this contract, it should be able to return the same data, but in reverse order.
For those who don’t know, calldata is a type of input data that is sent along with a transaction. It's stored outside of the EVM's storage and memory, making it cheaper to use.
There can be multiple valid solutions to this challenge. I’m going to use one of the solutions posted by @philogy for this walkthrough.
#define constant NEG1 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
#define macro GET_CALLDATA_BYTE() = takes(1) returns(1) {
calldataload 0xf8 shr
}
#define macro MAIN() = takes(0) returns(0) {
calldatasize not_empty jumpi
returndatasize returndatasize return
not_empty:
calldatasize
returndatasize
copy_bytes_iter: // [i, j + 1]
swap1 // [j + 1, i]
[NEG1] add // [j, i]
dup2 dup2 // [j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[i], j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[j], cd[i], j, i, j, i]
swap2 // [j, cd[i], cd[j], i, j, i]
mstore8 // [cd[j], i, j, i]
swap1 mstore8 // [j, i]
swap1 0x1 add // [i', j' + 1]
dup2 dup2 // [i', j' + 1, i', j' + 1]
lt
copy_bytes_iter jumpi
calldatasize returndatasize return
}
Let me break it down for you.
Firstly, the constant NEG1, a 256-bit number representing -1 in two's complement form. This constant will be useful for offsetting indexes.
#define constant NEG1 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Next, a macro called GET_CALLDATA_BYTE() is defined. This macro fetches one byte of calldata at a specified index. calldataload is an EVM opcode that loads 32 bytes of calldata from a specific index. However, we're only interested in a single byte, so we shift right (shr) by 248 bits (0xf8) to isolate the byte we need.
#define macro GET_CALLDATA_BYTE() = takes(1) returns(1) {
calldataload 0xf8 shr
}
Next comes the MAIN() macro. The first part of MAIN() is a short check for whether any calldata is present:
calldatasize not_empty jumpi
returndatasize returndatasize return
Here, calldatasize gets the size of the calldata. If the size is non-zero (meaning calldata is present), the control jumps to the not_empty label. If the size is zero (no calldata), then it immediately returns.
After confirming that calldata is present, the size of the calldata is fetched and pushed to the stack.
not_empty:
calldatasize
returndatasize
Next comes the spiciest part. The logic to reverse the calldata, one byte at a time.
Let's divide the copy_bytes_iter block into smaller chunks and discuss each one:
Block 1: Index preparation and byte retrieval
copy_bytes_iter: // [i, j + 1]
swap1 // [j + 1, i]
[NEG1] add // [j, i]
dup2 dup2 // [j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[i], j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[j], cd[i], j, i, j, i]
In this first block, we prepare the indices and retrieve the bytes to be swapped. We first swap i and j + 1 then subtract 1 from j + 1 to get j. After duplicating j and i for later use, the GET_CALLDATA_BYTE() macro is invoked twice to get the ith and jth bytes (cd[i] and cd[j]) from the calldata.
Block 2: Byte swapping
swap2 // [j, cd[i], cd[j], i, j, i]
mstore8 // [cd[j], i, j, i]
swap1 mstore8 // [j, i]
In the second block, the swapping of the bytes takes place. The contract swaps cd[i] and j, then uses mstore8 to store cd[j] at the ith position and cd[i] at the jth position. At the end of this block, j and i are left on the stack.
Block 3: Loop iteration and continuation
swap1 0x1 add // [i', j' + 1]
dup2 dup2 // [i', j' + 1, i', j' + 1]
lt
copy_bytes_iter jumpi
In the third block, i is incremented to move on to the next byte from the start. The indices i' and j' + 1 are then duplicated for comparison. If i' is less than j' + 1, the loop continues and jumps back to copy_bytes_iter. Otherwise, the loop terminates, and the contract proceeds to the next stage.
By repeating these steps, the copy_bytes_iter block swaps all pairs of bytes in the calldata until all bytes are reversed.
GitHub link to the PoC:
https://github.com/minaminao/ctf-blockchain/tree/main/src/HuffChallenge/challenge4

In this article, we are going to explore the solution to the Huff Challenge 4.
You can find walkthroughs for other challenges below:
Also if you are new to huff and wanna learn how to test and deploy Huff contracts, check out this article.
Alright let’s dive in.
In this challenge, we're tasked with writing a Huff smart contract that reverses the calldata that it receives. Essentially, if you send data to this contract, it should be able to return the same data, but in reverse order.
For those who don’t know, calldata is a type of input data that is sent along with a transaction. It's stored outside of the EVM's storage and memory, making it cheaper to use.
There can be multiple valid solutions to this challenge. I’m going to use one of the solutions posted by @philogy for this walkthrough.
#define constant NEG1 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
#define macro GET_CALLDATA_BYTE() = takes(1) returns(1) {
calldataload 0xf8 shr
}
#define macro MAIN() = takes(0) returns(0) {
calldatasize not_empty jumpi
returndatasize returndatasize return
not_empty:
calldatasize
returndatasize
copy_bytes_iter: // [i, j + 1]
swap1 // [j + 1, i]
[NEG1] add // [j, i]
dup2 dup2 // [j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[i], j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[j], cd[i], j, i, j, i]
swap2 // [j, cd[i], cd[j], i, j, i]
mstore8 // [cd[j], i, j, i]
swap1 mstore8 // [j, i]
swap1 0x1 add // [i', j' + 1]
dup2 dup2 // [i', j' + 1, i', j' + 1]
lt
copy_bytes_iter jumpi
calldatasize returndatasize return
}
Let me break it down for you.
Firstly, the constant NEG1, a 256-bit number representing -1 in two's complement form. This constant will be useful for offsetting indexes.
#define constant NEG1 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Next, a macro called GET_CALLDATA_BYTE() is defined. This macro fetches one byte of calldata at a specified index. calldataload is an EVM opcode that loads 32 bytes of calldata from a specific index. However, we're only interested in a single byte, so we shift right (shr) by 248 bits (0xf8) to isolate the byte we need.
#define macro GET_CALLDATA_BYTE() = takes(1) returns(1) {
calldataload 0xf8 shr
}
Next comes the MAIN() macro. The first part of MAIN() is a short check for whether any calldata is present:
calldatasize not_empty jumpi
returndatasize returndatasize return
Here, calldatasize gets the size of the calldata. If the size is non-zero (meaning calldata is present), the control jumps to the not_empty label. If the size is zero (no calldata), then it immediately returns.
After confirming that calldata is present, the size of the calldata is fetched and pushed to the stack.
not_empty:
calldatasize
returndatasize
Next comes the spiciest part. The logic to reverse the calldata, one byte at a time.
Let's divide the copy_bytes_iter block into smaller chunks and discuss each one:
Block 1: Index preparation and byte retrieval
copy_bytes_iter: // [i, j + 1]
swap1 // [j + 1, i]
[NEG1] add // [j, i]
dup2 dup2 // [j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[i], j, i, j, i]
dup2 GET_CALLDATA_BYTE() // [cd[j], cd[i], j, i, j, i]
In this first block, we prepare the indices and retrieve the bytes to be swapped. We first swap i and j + 1 then subtract 1 from j + 1 to get j. After duplicating j and i for later use, the GET_CALLDATA_BYTE() macro is invoked twice to get the ith and jth bytes (cd[i] and cd[j]) from the calldata.
Block 2: Byte swapping
swap2 // [j, cd[i], cd[j], i, j, i]
mstore8 // [cd[j], i, j, i]
swap1 mstore8 // [j, i]
In the second block, the swapping of the bytes takes place. The contract swaps cd[i] and j, then uses mstore8 to store cd[j] at the ith position and cd[i] at the jth position. At the end of this block, j and i are left on the stack.
Block 3: Loop iteration and continuation
swap1 0x1 add // [i', j' + 1]
dup2 dup2 // [i', j' + 1, i', j' + 1]
lt
copy_bytes_iter jumpi
In the third block, i is incremented to move on to the next byte from the start. The indices i' and j' + 1 are then duplicated for comparison. If i' is less than j' + 1, the loop continues and jumps back to copy_bytes_iter. Otherwise, the loop terminates, and the contract proceeds to the next stage.
By repeating these steps, the copy_bytes_iter block swaps all pairs of bytes in the calldata until all bytes are reversed.
GitHub link to the PoC:
https://github.com/minaminao/ctf-blockchain/tree/main/src/HuffChallenge/challenge4

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 writ...

Huff By Example
In this rapidly evolving landscape of blockchain technology, efficiency isn't just an advantage—it's a necessity. More the adoption is, the more congested the networks become (for now this is the case), hence the race for gas optimization intensifies, every opcode, every byte, and every operation counts. Enter Huff, a language so close to the metal of the Ethereum Virtual Machine (EVM) which has the potential to take on-chain possibilities to one step further. In this post, we’ll di...

Walkthrough: Huff Challenge #3
In this article, we are going to explore the solution to the Huff Challenge 3. You can find walkthroughs for Challenge #1 here and Challenge #2 here. If you wanna learn how to test and deploy Huff contracts, check out this article.Overview:Here’s the target contract for the challenge:#define constant OWNER_SLOT = 0x00 #define constant WITHDRAWER_SLOT = 0x01 #define constant LAST_DEPOSITER_SLOT = 0x02 // Deposit into the contract. #define macro DEPOSIT() = takes (0) returns (0) { callvalue isz...

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 writ...

Huff By Example
In this rapidly evolving landscape of blockchain technology, efficiency isn't just an advantage—it's a necessity. More the adoption is, the more congested the networks become (for now this is the case), hence the race for gas optimization intensifies, every opcode, every byte, and every operation counts. Enter Huff, a language so close to the metal of the Ethereum Virtual Machine (EVM) which has the potential to take on-chain possibilities to one step further. In this post, we’ll di...

Walkthrough: Huff Challenge #3
In this article, we are going to explore the solution to the Huff Challenge 3. You can find walkthroughs for Challenge #1 here and Challenge #2 here. If you wanna learn how to test and deploy Huff contracts, check out this article.Overview:Here’s the target contract for the challenge:#define constant OWNER_SLOT = 0x00 #define constant WITHDRAWER_SLOT = 0x01 #define constant LAST_DEPOSITER_SLOT = 0x02 // Deposit into the contract. #define macro DEPOSIT() = takes (0) returns (0) { callvalue isz...
EVM | DeFi | SmartContracts 🚀
EVM | DeFi | SmartContracts 🚀

Subscribe to PraneshASP ⚡

Subscribe to PraneshASP ⚡
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
No activity yet