logoAcademy

Generating the Precompile

Learn how to generating the precompile

In this step, we will again utilize the precompile generation script to generate all the Go files based on the ABI for your calculator.

Run Generation Script

Change to the root directory of your precompile-evm project and run the command to generate the go files:

# Change to root
cd ..
 
# Generate go files
./scripts/generate_precompile.sh --abi ./contracts/abis/ICalculatorPlus.abi --type Calculatorplus --pkg calculatorplus --out ./calculatorplus

Now you should have a new directory called calculatorplus in the root directory of your project.

If you check our the generated contract.go file you will see right away that it is much longer than in our hash function precompile from earlier. This is due to the fact that our calculator precompile has more functions and parameters. Browse through the code and see if you can spot the new elements:

contract.go
// 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 calculatorplus
 
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.
    ModuloPlusGasCost uint64 = 1 /* SET A GAS COST HERE */
    PowOfThreeGasCost uint64 = 1 /* SET A GAS COST HERE */
    SimplFracGasCost  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 (
    // CalculatorplusRawABI contains the raw ABI of Calculatorplus contract.
    //go:embed contract.abi
    CalculatorplusRawABI string
 
    CalculatorplusABI = contract.ParseABI(CalculatorplusRawABI)
 
    CalculatorplusPrecompile = createCalculatorplusPrecompile()
)
 
type ModuloPlusInput struct {
    Dividend *big.Int
    Divisor  *big.Int
}
 
type ModuloPlusOutput struct {
    Multiple  *big.Int
    Remainder *big.Int
}
 
type PowOfThreeOutput struct {
    SecondPow *big.Int
    ThirdPow  *big.Int
    FourthPow *big.Int
}
 
type SimplFracInput struct {
    Numerator   *big.Int
    Denominator *big.Int
}
 
type SimplFracOutput struct {
    SimplNum   *big.Int
    SimplDenom *big.Int
}
 
// UnpackModuloPlusInput attempts to unpack [input] as ModuloPlusInput
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func UnpackModuloPlusInput(input []byte) (ModuloPlusInput, error) {
    inputStruct := ModuloPlusInput{}
    err := CalculatorplusABI.UnpackInputIntoInterface(&inputStruct, "moduloPlus", input)
 
    return inputStruct, err
}
 
// PackModuloPlus packs [inputStruct] of type ModuloPlusInput into the appropriate arguments for moduloPlus.
func PackModuloPlus(inputStruct ModuloPlusInput) ([]byte, error) {
    return CalculatorplusABI.Pack("moduloPlus", inputStruct.Dividend, inputStruct.Divisor)
}
 
// PackModuloPlusOutput attempts to pack given [outputStruct] of type ModuloPlusOutput
// to conform the ABI outputs.
func PackModuloPlusOutput(outputStruct ModuloPlusOutput) ([]byte, error) {
    return CalculatorplusABI.PackOutput("moduloPlus",
        outputStruct.Multiple,
        outputStruct.Remainder,
    )
}
 
// UnpackModuloPlusOutput attempts to unpack [output] as ModuloPlusOutput
// assumes that [output] does not include selector (omits first 4 func signature bytes)
func UnpackModuloPlusOutput(output []byte) (ModuloPlusOutput, error) {
    outputStruct := ModuloPlusOutput{}
    err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "moduloPlus", output)
 
    return outputStruct, err
}
 
func moduloPlus(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, ModuloPlusGasCost); err != nil {
        return nil, 0, err
    }
    // attempts to unpack [input] into the arguments to the ModuloPlusInput.
    // Assumes that [input] does not include selector
    // You can use unpacked [inputStruct] variable in your code
    inputStruct, err := UnpackModuloPlusInput(input)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // CUSTOM CODE STARTS HERE
    _ = inputStruct             // CUSTOM CODE OPERATES ON INPUT
    var output ModuloPlusOutput // CUSTOM CODE FOR AN OUTPUT
    packedOutput, err := PackModuloPlusOutput(output)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // Return the packed output and the remaining gas
    return packedOutput, remainingGas, nil
}
 
// UnpackPowOfThreeInput attempts to unpack [input] into the *big.Int type argument
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func UnpackPowOfThreeInput(input []byte) (*big.Int, error) {
    res, err := CalculatorplusABI.UnpackInput("powOfThree", input)
    if err != nil {
        return new(big.Int), err
    }
    unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int)
    return unpacked, nil
}
 
// PackPowOfThree packs [base] of type *big.Int into the appropriate arguments for powOfThree.
// the packed bytes include selector (first 4 func signature bytes).
// This function is mostly used for tests.
func PackPowOfThree(base *big.Int) ([]byte, error) {
    return CalculatorplusABI.Pack("powOfThree", base)
}
 
// PackPowOfThreeOutput attempts to pack given [outputStruct] of type PowOfThreeOutput
// to conform the ABI outputs.
func PackPowOfThreeOutput(outputStruct PowOfThreeOutput) ([]byte, error) {
    return CalculatorplusABI.PackOutput("powOfThree",
        outputStruct.SecondPow,
        outputStruct.ThirdPow,
        outputStruct.FourthPow,
    )
}
 
// UnpackPowOfThreeOutput attempts to unpack [output] as PowOfThreeOutput
// assumes that [output] does not include selector (omits first 4 func signature bytes)
func UnpackPowOfThreeOutput(output []byte) (PowOfThreeOutput, error) {
    outputStruct := PowOfThreeOutput{}
    err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "powOfThree", output)
 
    return outputStruct, err
}
 
func powOfThree(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, PowOfThreeGasCost); err != nil {
        return nil, 0, err
    }
    // attempts to unpack [input] into the arguments to the PowOfThreeInput.
    // Assumes that [input] does not include selector
    // You can use unpacked [inputStruct] variable in your code
    inputStruct, err := UnpackPowOfThreeInput(input)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // CUSTOM CODE STARTS HERE
    _ = inputStruct             // CUSTOM CODE OPERATES ON INPUT
    var output PowOfThreeOutput // CUSTOM CODE FOR AN OUTPUT
    packedOutput, err := PackPowOfThreeOutput(output)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // Return the packed output and the remaining gas
    return packedOutput, remainingGas, nil
}
 
// UnpackSimplFracInput attempts to unpack [input] as SimplFracInput
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func UnpackSimplFracInput(input []byte) (SimplFracInput, error) {
    inputStruct := SimplFracInput{}
    err := CalculatorplusABI.UnpackInputIntoInterface(&inputStruct, "simplFrac", input)
 
    return inputStruct, err
}
 
// PackSimplFrac packs [inputStruct] of type SimplFracInput into the appropriate arguments for simplFrac.
func PackSimplFrac(inputStruct SimplFracInput) ([]byte, error) {
    return CalculatorplusABI.Pack("simplFrac", inputStruct.Numerator, inputStruct.Denominator)
}
 
// PackSimplFracOutput attempts to pack given [outputStruct] of type SimplFracOutput
// to conform the ABI outputs.
func PackSimplFracOutput(outputStruct SimplFracOutput) ([]byte, error) {
    return CalculatorplusABI.PackOutput("simplFrac",
        outputStruct.SimplNum,
        outputStruct.SimplDenom,
    )
}
 
// UnpackSimplFracOutput attempts to unpack [output] as SimplFracOutput
// assumes that [output] does not include selector (omits first 4 func signature bytes)
func UnpackSimplFracOutput(output []byte) (SimplFracOutput, error) {
    outputStruct := SimplFracOutput{}
    err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "simplFrac", output)
 
    return outputStruct, err
}
 
func simplFrac(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, SimplFracGasCost); err != nil {
        return nil, 0, err
    }
    // attempts to unpack [input] into the arguments to the SimplFracInput.
    // Assumes that [input] does not include selector
    // You can use unpacked [inputStruct] variable in your code
    inputStruct, err := UnpackSimplFracInput(input)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // CUSTOM CODE STARTS HERE
    _ = inputStruct            // CUSTOM CODE OPERATES ON INPUT
    var output SimplFracOutput // CUSTOM CODE FOR AN OUTPUT
    packedOutput, err := PackSimplFracOutput(output)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // Return the packed output and the remaining gas
    return packedOutput, remainingGas, nil
}
 
// createCalculatorplusPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
 
func createCalculatorplusPrecompile() contract.StatefulPrecompiledContract {
    var functions []*contract.StatefulPrecompileFunction
 
    abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{
        "moduloPlus": moduloPlus,
        "powOfThree": powOfThree,
        "simplFrac":  simplFrac,
    }
 
    for name, function := range abiFunctionMap {
        method, ok := CalculatorplusABI.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