logoAcademy

Implementing Precompile

Learn how to implement the precompile in `contract.go`

In this section, we will go define the logic for our CalculatorPlus precompile; in particular, we want to add the logic for the following three functions: powOfThree, moduloPlus, and simplFrac.

For those worried about this section - don't be! Our solution only added 12 lines of code to contract.go.

Looking at Calculator

Before we define the logic of CalculatorPlus, we first will examine the implementation of the Calculator precompile:

func add(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, AddGasCost); err != nil {
        return nil, 0, err
    }
    // attempts to unpack [input] into the arguments to the AddInput.
    // Assumes that [input] does not include selector
    // You can use unpacked [inputStruct] variable in your code
    inputStruct, err := UnpackAddInput(input)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // CUSTOM CODE STARTS HERE
  
    _ = inputStruct          // CUSTOM CODE OPERATES ON INPUT
  
    var output *big.Int // CUSTOM CODE FOR AN OUTPUT
 
    output = big.NewInt(0).Add(inputStruct.Value1, inputStruct.Value2)
 
    packedOutput, err := PackAddOutput(output)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // Return the packed output and the remaining gas
    return packedOutput, remainingGas, nil
}
// ...
 
func repeat(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, RepeatGasCost); err != nil {
        return nil, 0, err
    }
    // attempts to unpack [input] into the arguments to the RepeatInput.
    // Assumes that [input] does not include selector
    // You can use unpacked [inputStruct] variable in your code
    inputStruct, err := UnpackRepeatInput(input)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // CUSTOM CODE STARTS HERE
    _ = inputStruct          // CUSTOM CODE OPERATES ON INPUT
  
    var output string // CUSTOM CODE FOR AN OUTPUT
 
    output = strings.Repeat(inputStruct.Text, int(inputStruct.Times.Int64()))
 
    packedOutput, err := PackRepeatOutput(output)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // Return the packed output and the remaining gas
    return packedOutput, remainingGas, nil
}
 
// ...
 
func nextTwo(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, NextTwoGasCost); err != nil {
        return nil, 0, err
    }
    // attempts to unpack [input] into the arguments to the NextTwoInput.
    // Assumes that [input] does not include selector
    // You can use unpacked [inputStruct] variable in your code
    inputStruct, err := UnpackNextTwoInput(input)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // CUSTOM CODE STARTS HERE
  
    _ = inputStruct          // CUSTOM CODE OPERATES ON INPUT
  
    var output NextTwoOutput // CUSTOM CODE FOR AN OUTPUT
 
    output.Result1 = big.NewInt(0).Add(inputStruct, big.NewInt(1))
    output.Result2 = big.NewInt(0).Add(inputStruct, big.NewInt(2))
 
    packedOutput, err := PackNextTwoOutput(output)
    if err != nil {
        return nil, remainingGas, err
    }
 
    // Return the packed output and the remaining gas
    return packedOutput, remainingGas, nil
}

Although the code snippet above may be long, you might notice that we added only four lines of code to the autogenerated code provided to us by Precompile-EVM! In particular, we only added lines 19, 48, 79, and 80. In general, note the following:

  • Structs vs Singular Values: make sure to keep track which inputs/outputs are structs and which one are values like big.Int. As an example, in nextTwo, we are dealing with a big.Int type input. However, in repeat, we are passed in a input of type struct RepeatInput.
  • Documentation: for both Calculator and CalculatorPlus, the big package documentation is of great reference: https://pkg.go.dev/math/big

Now that we have looked at the implementation for the Calculator precompile, its time you define the CalculatorPlus precompile!

Implementing moduloPlus

We start by looking at the starter code for moduloPlus:

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
}

We want to note the following:

  • inputStruct is the input that we want to work with (i.e. inputStruct contains the two numbers that we want to use for the modulo calculation)
  • All of our code will go after line 15
  • We want the struct output to contain the result of our modulo operation (the struct will contain the multiple and remainder)

With this in mind, try to implement moduloPlus.

Implementing powOfThree

Likewise, for powOfThree, we want to define the logic of the function in the custom code section. However, note that while we are working with an output struct, our input is a singular value. With this in mind, take a crack at implementing powOfThree:

Implementing simplFrac

For implementing simplFrac, note the following:

  • The documentation for the big package will be of use here
  • Remember to take care of the case when the denominator is 0

Final Solution

Below is the final solution for the CalculatorPlus precompile:

On this page