logoAcademy

Generating the Precompile

Learn how to generate your precompile.

In the last section, we created the ABI for our precompile contract. Now, we'll use the precompile generation script provided by the precompile-evm template to generate a boilerplate code in go for the precompile implementation that will be wrapped in the solidity interface we created in the previous step.

Running the Generation Script

To start, go to the root directory of your precompile-evm project:

cd $GOPATH/src/github.com/ava-labs/precompile-evm

Now generate the files necessary for the precompile.

./scripts/generate_precompile.sh --abi ./contracts/abis/IMD5.abi --type Md5 --pkg md5 --out ./md5

Now you should have a new directory in your root directory called md5:

LICENSE
README.md
go.mod
go.sum
README.md
config.go
config_test.go
contract.abi
contract.go
contract_test.go
module.go

contract.go

For the rest of this chapter, we'll work with the md5/contract.go file. If you generated the Go files related to your precompile, contract.go should look like the code below.

Do not be intimidated if much of this code does not make sense to you. We'll cover the different parts and add some code to implement the logic of our MD5 precompile.

// Code generated
// This file is a generated precompile contract config with stubbed abstract functions.
// The file is generated by a template. Please inspect every code and comment in this file before use.
 
package md5
 
import (
	"errors"
	"fmt"
	"math/big"
 
	"github.com/ava-labs/subnet-evm/accounts/abi"
	"github.com/ava-labs/subnet-evm/precompile/contract"
	"github.com/ava-labs/subnet-evm/vmerrs"
 
	_ "embed"
 
	"github.com/ethereum/go-ethereum/common"
)
 
const (
	// Gas costs for each function. These are set to 1 by default.
	// You should set a gas cost for each function in your contract.
	// Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks.
	// There are some predefined gas costs in contract/utils.go that you can use.
	HashWithMD5GasCost uint64 = 1 /* SET A GAS COST HERE */
)
 
// CUSTOM CODE STARTS HERE
// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed.
var (
	_ = abi.JSON
	_ = errors.New
	_ = big.NewInt
	_ = vmerrs.ErrOutOfGas
	_ = common.Big0
)
 
// Singleton StatefulPrecompiledContract and signatures.
var (
 
	// Md5RawABI contains the raw ABI of Md5 contract.
	//go:embed contract.abi
	Md5RawABI string
 
	Md5ABI = contract.ParseABI(Md5RawABI)
 
	Md5Precompile = createMd5Precompile()
)
 
// 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
}
 
// PackHashWithMD5 packs [value] of type string into the appropriate arguments for hashWithMD5.
// the packed bytes include selector (first 4 func signature bytes).
// This function is mostly used for tests.
func PackHashWithMD5(value string) ([]byte, error) {
	return Md5ABI.Pack("hashWithMD5", value)
}
 
// 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("hashWithMD5", hash)
}
 
// UnpackHashWithMD5Output attempts to unpack given [output] into the [16]byte type output
// assumes that [output] does not include selector (omits first 4 func signature bytes)
func UnpackHashWithMD5Output(output []byte) ([16]byte, error) {
	res, err := Md5ABI.Unpack("hashWithMD5", output)
	if err != nil {
		return [16]byte{}, err
	}
	unpacked := *abi.ConvertType(res[0], new([16]byte)).(*[16]byte)
	return unpacked, nil
}
 
func hashWithMD5(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
	if remainingGas, err = contract.DeductGas(suppliedGas, HashWithMD5GasCost); err != nil {
		return nil, 0, err
	}
	// attempts to unpack [input] into the arguments to the HashWithMD5Input.
	// Assumes that [input] does not include selector
	// You can use unpacked [inputStruct] variable in your code
	inputStruct, err := UnpackHashWithMD5Input(input)
	if err != nil {
		return nil, remainingGas, err
	}
 
	// CUSTOM CODE STARTS HERE
	_ = inputStruct // CUSTOM CODE OPERATES ON INPUT
 
	var output [16]byte // CUSTOM CODE FOR AN OUTPUT
	packedOutput, err := PackHashWithMD5Output(output)
	if err != nil {
		return nil, remainingGas, err
	}
 
	// Return the packed output and the remaining gas
	return packedOutput, remainingGas, nil
}
 
// createMd5Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
 
func createMd5Precompile() contract.StatefulPrecompiledContract {
	var functions []*contract.StatefulPrecompileFunction
 
	abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{
		"hashWithMD5": hashWithMD5,
	}
 
	for name, function := range abiFunctionMap {
		method, ok := Md5ABI.Methods[name]
		if !ok {
			panic(fmt.Errorf("given method (%s) does not exist in the ABI", name))
		}
		functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function))
	}
	// Construct the contract with no fallback function.
	statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions)
	if err != nil {
		panic(err)
	}
	return statefulContract
}

On this page