logoAcademy

Unpacking Inputs and Packing Outputs

Learn how to unpack inputs and pack outputs.

In this first segment of examining the contract.go file generated for us, we will go over the packing and unpacking functions in this file.

The Notion of Packing

Those eager to implement the MD5 algorithm might be wondering why we're discussing packing. However, there is good reason to discuss packing, and it comes down to the specification of the staticcall function in Solidity.

We begin by referring to the example of calling the SHA-256 precompiled contract:

(bool ok, bytes memory out) = address(2).staticcall(abi.encode(numberToHash));

As seen above, the staticcall function accepts input in bytes format, generated by abi.encode, and returns a boolean value indicating success, along with a bytes format output.

Therefore, our precompiled contract should be designed to accept and return data in bytes format, involving the packing and unpacking of values. Since packing is a deterministic process, there's no concern about data corruption during translation. However, some preprocessing or postprocessing is necessary to ensure the contract functions correctly.

Unpacking Inputs

In contract.go, the function UnpackHashWithMd5Input unpacks our data and converts it into a type relevant to us. It takes a byte array as an input and returns a Go string. We will look at more complex precompiles that have multiple functions that may take multiple inputs later.

// UnpackHashWithMD5Input attempts to unpack [input] into the string type argument
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func UnpackHashWithMD5Input(input []byte) (string, error) {
    res, err := Md5ABI.UnpackInput("hashWithMD5", input)
    if err != nil {
        return "", err
    }
    unpacked := *abi.ConvertType(res[0], new(string)).(*string)
    return unpacked, nil
}

Packing Outputs

Ignoring hashWithMD5 for now, note that whatever value hashWithMD5 outputs, we will need to postprocess it (i.e. pack it). PackHashWithMD5Output does just this, taking in an input of type [16]byte and outputting a byte array which can be returned by staticcall.

// PackHashWithMd5Output attempts to pack given hash of type [16]byte
// to conform the ABI outputs.
func PackHashWithMd5Output(hash [16]byte) ([]byte, error) {
    return Md5ABI.PackOutput("hash_with_md5", hash)
}

This may seem trivial, but if our Solidity interface defined our function to return a uint or string, the type of our input to this function would differ accordingly.

On this page