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.
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!
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)
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: