3

How do I call another contract with huff?

And then bonus points... Return the data from the call.

Let's say I have a solidity-style contract with a function as such:

NumberGetter.sol

uint256 myNumber = 777;

function getNumber() public view returns (uint256) {
        return myNumber;
    }

How would I call this function from a huff contract?

We'd need to use call or staticcall (let's use staticcall for this example).

I think it would look something like this:

#define function getNumberFromOtherContract() view returns (uint256)
#define constant NUMBER_GETTER_ADDRESS = 0x0000000000000000000000000000000000000000 // Swap out for NumberGetter.sol contract address


#define macro GET_NUMBER_FROM_OTHER_CONTRACT() = takes (0) returns (0) {
    // store the function to call in memory
    __FUNC_SIG("getNumber()") 0x00 mstore 

    // return size = 0x20
    // return offset = 0x00
    // arguments size = 0x04 (function selector size)
    // arguments offset = 0x00
    // address = NUMBER_GETTER_ADDRESS
    // gas = whatever gas is left = gas
    // staticcall calls our NUMBER_GETTER_ADDRESS contract 
    0x20 0x00 0x04 0x00 [NUMBER_GETTER_ADDRESS] gas staticcall 

    // size = 0x20
    // return data offset = 0x00
    // destination in memory offset = 0x00
    0x20 0x00 0x00 returndatacopy

    0x20 0x00 return
}

1 Answer 1

6

This is close!

When you're storing the four byte function selector, you're actually storing 32 bytes where the selector is to the right.

0xaabbccdd   // [selector]
0x00         // [offset, selector]
mstore       // []

// MEMORY SLOT 0 AFTER MSTORE:
// 0x00000000000000000000000000000000000000000000000000000000aabbccdd

So in your example, you'll want your "arguments offset" to be at the 28th byte in memory (32 byte word - 4 bytes).

#define constant GETTER_ADDR = 0x0000000000000000000000000000000000000000 // Swap out for NumberGetter.sol contract address


#define macro GET_NUMBER_FROM_OTHER_CONTRACT() = takes (0) returns (0) {
  // store the function to call in memory

  __FUNC_SIG("getNumber()") 0x00 mstore 

  // make call. return size is zero because we can fetch it after the call

  0x00           // [ret_size]
  0x00           // [ret_offset, ret_size]
  0x04           // [args_size, ret_offset, ret_size]
  0x1c           // [args_offset, args_size, ret_offset, ret_size]
  [GETTER_ADDR]  // [getter_addr, args_offset, args_size, ret_offset, ret_size]
  gas            // [gas, getter_addr, args_offset, args_size, ret_offset, ret_size]
  staticcall     // [successs]

  // store returndata in memory
  returndatasize // [returndatasize, success]
  0x00           // [mem_offset, returndatasize, success]
  0x00           // [destOffset, mem_offset, returndatasize, success]
  returndatacopy // [success] // this stores the return data in memory

  // return the returndata from memory to the msg.sender

  returndatasize // [returndatasize, success]
  0x00           // [mem_offset, returndatasize, success]
  return
}

Notice, staticcall pushes a "success" value to the stack, which is 0x01 if the call was successful and 0x00 if the call to the getter address reverted. I left it on the stack for this example since it's beyond the scope of this question, but it would be a good idea to create some logic to handle a revert! See Huffmate Errors for more on handling call reverts.

2
  • Hmm... This looks like it almost worked for me. I'm getting some odd jank. Do we need to clean the data at all that we returned? Or should it be good to go. How does mstore know to grab from return data? It seems we should need returndata copy no? Aug 3, 2022 at 23:15
  • Edit worked like a charm, thanks! Aug 3, 2022 at 23:53

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.