Files
evm/accounts/abi/bind/precompilebind/precompile_contract_template.go
2024-01-03 09:44:09 +01:00

366 lines
15 KiB
Go

// (c) 2021-2024, Lux Partners Limited. All rights reserved.
// See the file LICENSE for licensing terms.
package precompilebind
import "github.com/luxdefi/evm/accounts/abi/bind"
// tmplPrecompileData is the data structure required to fill the binding template.
type tmplPrecompileData struct {
Package string
Contract *tmplPrecompileContract // The contract to generate into this file
Structs map[string]*bind.TmplStruct // Contract struct type definitions
}
// tmplPrecompileContract contains the data needed to generate an individual contract binding.
type tmplPrecompileContract struct {
*bind.TmplContract
AllowList bool // Indicator whether the contract uses AllowList precompile
Funcs map[string]*bind.TmplMethod // Contract functions that include both Calls + Transacts in tmplContract
ABIFilename string // Path to the ABI file
}
// tmplSourcePrecompileContractGo is the Go precompiled contract source template.
const tmplSourcePrecompileContractGo = `
// 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 {{.Package}}
import (
"errors"
"fmt"
"math/big"
"github.com/luxdefi/evm/accounts/abi"
{{- if .Contract.AllowList}}
"github.com/luxdefi/evm/precompile/allowlist"
{{- end}}
"github.com/luxdefi/evm/precompile/contract"
"github.com/luxdefi/evm/vmerrs"
_ "embed"
"github.com/ethereum/go-ethereum/common"
)
{{$contract := .Contract}}
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.
{{- if .Contract.AllowList}}
// This contract also uses AllowList precompile.
// You should also increase gas costs of functions that read from AllowList storage.
{{- end}}
{{- range .Contract.Funcs}}
{{.Normalized.Name}}GasCost uint64 = 1 /* SET A GAS COST HERE */ {{if not .Original.IsConstant | and $contract.AllowList}} + allowlist.ReadAllowListGasCost {{end}}
{{- end}}
{{- if .Contract.Fallback}}
{{.Contract.Type}}FallbackGasCost uint64 = 1 // SET A GAS COST LESS THAN 2300 HERE
{{- end}}
)
// 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 (
{{- range .Contract.Funcs}}
{{- if not .Original.IsConstant | and $contract.AllowList}}
ErrCannot{{.Normalized.Name}} = errors.New("non-enabled cannot call {{.Original.Name}}")
{{- end}}
{{- end}}
{{- if .Contract.Fallback | and $contract.AllowList}}
Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot call fallback function")
{{- end}}
// {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract.
{{- if .Contract.ABIFilename | eq ""}}
{{.Contract.Type}}RawABI = "{{.Contract.InputABI}}"
{{- else}}
//go:embed {{.Contract.ABIFilename}}
{{.Contract.Type}}RawABI string
{{- end}}
{{.Contract.Type}}ABI = contract.ParseABI({{.Contract.Type}}RawABI)
{{.Contract.Type}}Precompile = create{{.Contract.Type}}Precompile()
)
{{$structs := .Structs}}
{{range $structs}}
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
type {{.Name}} struct {
{{range $field := .Fields}}
{{$field.Name}} {{$field.Type}}{{end}}
}
{{- end}}
{{- range .Contract.Funcs}}
{{ if len .Normalized.Inputs | lt 1}}
type {{capitalise .Normalized.Name}}Input struct{
{{range .Normalized.Inputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}}
}
{{- end}}
{{ if len .Normalized.Outputs | lt 1}}
type {{capitalise .Normalized.Name}}Output struct{
{{range .Normalized.Outputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}}
}
{{- end}}
{{- end}}
{{if .Contract.AllowList}}
// Get{{.Contract.Type}}AllowListStatus returns the role of [address] for the {{.Contract.Type}} list.
func Get{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role {
return allowlist.GetAllowListStatus(stateDB, ContractAddress, address)
}
// Set{{.Contract.Type}}AllowListStatus sets the permissions of [address] to [role] for the
// {{.Contract.Type}} list. Assumes [role] has already been verified as valid.
// This stores the [role] in the contract storage with address [ContractAddress]
// and [address] hash. It means that any reusage of the [address] key for different value
// conflicts with the same slot [role] is stored.
// Precompile implementations must use a different key than [address] for their storage.
func Set{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) {
allowlist.SetAllowListRole(stateDB, ContractAddress, address, role)
}
{{end}}
{{range .Contract.Funcs}}
{{if len .Normalized.Inputs | lt 1}}
// Unpack{{capitalise .Normalized.Name}}Input attempts to unpack [input] as {{capitalise .Normalized.Name}}Input
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func Unpack{{capitalise .Normalized.Name}}Input(input []byte) ({{capitalise .Normalized.Name}}Input, error) {
inputStruct := {{capitalise .Normalized.Name}}Input{}
// The strict mode in decoding is disabled after DUpgrade. You can re-enable by changing the last argument to true.
err := {{$contract.Type}}ABI.UnpackInputIntoInterface(&inputStruct, "{{.Original.Name}}", input, false)
return inputStruct, err
}
// Pack{{.Normalized.Name}} packs [inputStruct] of type {{capitalise .Normalized.Name}}Input into the appropriate arguments for {{.Original.Name}}.
func Pack{{.Normalized.Name}}(inputStruct {{capitalise .Normalized.Name}}Input) ([]byte, error) {
return {{$contract.Type}}ABI.Pack("{{.Original.Name}}", {{range .Normalized.Inputs}} inputStruct.{{capitalise .Name}}, {{end}})
}
{{else if len .Normalized.Inputs | eq 1 }}
{{$method := .}}
{{$input := index $method.Normalized.Inputs 0}}
{{$bindedType := bindtype $input.Type $structs}}
// Unpack{{capitalise .Normalized.Name}}Input attempts to unpack [input] into the {{$bindedType}} type argument
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func Unpack{{capitalise .Normalized.Name}}Input(input []byte)({{$bindedType}}, error) {
// The strict mode in decoding is disabled after DUpgrade. You can re-enable by changing the last argument to true.
res, err := {{$contract.Type}}ABI.UnpackInput("{{$method.Original.Name}}", input, false)
if err != nil {
return {{bindtypenew $input.Type $structs}}, err
}
unpacked := *abi.ConvertType(res[0], new({{$bindedType}})).(*{{$bindedType}})
return unpacked, nil
}
// Pack{{.Normalized.Name}} packs [{{decapitalise $input.Name}}] of type {{$bindedType}} into the appropriate arguments for {{$method.Original.Name}}.
// the packed bytes include selector (first 4 func signature bytes).
// This function is mostly used for tests.
func Pack{{$method.Normalized.Name}}( {{decapitalise $input.Name}} {{$bindedType}},) ([]byte, error) {
return {{$contract.Type}}ABI.Pack("{{$method.Original.Name}}", {{decapitalise $input.Name}},)
}
{{else}}
// Pack{{.Normalized.Name}} packs the include selector (first 4 func signature bytes).
// This function is mostly used for tests.
func Pack{{.Normalized.Name}}() ([]byte, error) {
return {{$contract.Type}}ABI.Pack("{{.Original.Name}}")
}
{{end}}
{{if len .Normalized.Outputs | lt 1}}
// Pack{{capitalise .Normalized.Name}}Output attempts to pack given [outputStruct] of type {{capitalise .Normalized.Name}}Output
// to conform the ABI outputs.
func Pack{{capitalise .Normalized.Name}}Output (outputStruct {{capitalise .Normalized.Name}}Output) ([]byte, error) {
return {{$contract.Type}}ABI.PackOutput("{{.Original.Name}}",
{{- range .Normalized.Outputs}}
outputStruct.{{capitalise .Name}},
{{- end}}
)
}
// Unpack{{capitalise .Normalized.Name}}Output attempts to unpack [output] as {{capitalise .Normalized.Name}}Output
// assumes that [output] does not include selector (omits first 4 func signature bytes)
func Unpack{{capitalise .Normalized.Name}}Output(output []byte) ({{capitalise .Normalized.Name}}Output, error) {
outputStruct := {{capitalise .Normalized.Name}}Output{}
err := {{$contract.Type}}ABI.UnpackIntoInterface(&outputStruct, "{{.Original.Name}}", output)
return outputStruct, err
}
{{else if len .Normalized.Outputs | eq 1 }}
{{$method := .}}
{{$output := index $method.Normalized.Outputs 0}}
{{$bindedType := bindtype $output.Type $structs}}
// Pack{{capitalise .Normalized.Name}}Output attempts to pack given {{decapitalise $output.Name}} of type {{$bindedType}}
// to conform the ABI outputs.
func Pack{{$method.Normalized.Name}}Output ({{decapitalise $output.Name}} {{$bindedType}}) ([]byte, error) {
return {{$contract.Type}}ABI.PackOutput("{{$method.Original.Name}}", {{decapitalise $output.Name}})
}
// Unpack{{capitalise .Normalized.Name}}Output attempts to unpack given [output] into the {{$bindedType}} type output
// assumes that [output] does not include selector (omits first 4 func signature bytes)
func Unpack{{capitalise .Normalized.Name}}Output(output []byte)({{$bindedType}}, error) {
res, err := {{$contract.Type}}ABI.Unpack("{{$method.Original.Name}}", output)
if err != nil {
return {{bindtypenew $output.Type $structs}}, err
}
unpacked := *abi.ConvertType(res[0], new({{$bindedType}})).(*{{$bindedType}})
return unpacked, nil
}
{{end}}
func {{decapitalise .Normalized.Name}}(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, {{.Normalized.Name}}GasCost); err != nil {
return nil, 0, err
}
{{- if not .Original.IsConstant}}
if readOnly {
return nil, remainingGas, vmerrs.ErrWriteProtection
}
{{- end}}
{{- if len .Normalized.Inputs | eq 0}}
// no input provided for this function
{{else}}
// attempts to unpack [input] into the arguments to the {{.Normalized.Name}}Input.
// Assumes that [input] does not include selector
// You can use unpacked [inputStruct] variable in your code
inputStruct, err := Unpack{{capitalise .Normalized.Name}}Input(input)
if err != nil{
return nil, remainingGas, err
}
{{- end}}
{{if not .Original.IsConstant | and $contract.AllowList}}
// Allow list is enabled and {{.Normalized.Name}} is a state-changer function.
// This part of the code restricts the function to be called only by enabled/admin addresses in the allow list.
// You can modify/delete this code if you don't want this function to be restricted by the allow list.
stateDB := accessibleState.GetStateDB()
// Verify that the caller is in the allow list and therefore has the right to call this function.
callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller)
if !callerStatus.IsEnabled() {
return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannot{{.Normalized.Name}}, caller)
}
// allow list code ends here.
{{end}}
// CUSTOM CODE STARTS HERE
{{- if len .Normalized.Inputs | ne 0}}
_ = inputStruct // CUSTOM CODE OPERATES ON INPUT
{{- end}}
{{- if len .Normalized.Outputs | eq 0}}
// this function does not return an output, leave this one as is
packedOutput := []byte{}
{{- else}}
{{- if len .Normalized.Outputs | lt 1}}
var output {{capitalise .Normalized.Name}}Output // CUSTOM CODE FOR AN OUTPUT
{{- else }}
{{$output := index .Normalized.Outputs 0}}
var output {{bindtype $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT
{{- end}}
packedOutput, err := Pack{{.Normalized.Name}}Output(output)
if err != nil {
return nil, remainingGas, err
}
{{- end}}
// Return the packed output and the remaining gas
return packedOutput, remainingGas, nil
}
{{end}}
{{- if .Contract.Fallback}}
{{- with .Contract.Fallback}}
// {{decapitalise $contract.Type}}Fallback executed if a function identifier does not match any of the available functions in a smart contract.
// This function cannot take an input or return an output.
func {{decapitalise $contract.Type}}Fallback (accessibleState contract.AccessibleState, caller common.Address, addr common.Address, _ []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
if remainingGas, err = contract.DeductGas(suppliedGas, {{$contract.Type}}FallbackGasCost); err != nil {
return nil, 0, err
}
if readOnly {
return nil, remainingGas, vmerrs.ErrWriteProtection
}
{{- if $contract.AllowList}}
// Allow list is enabled and {{.Normalized.Name}} is a state-changer function.
// This part of the code restricts the function to be called only by enabled/admin addresses in the allow list.
// You can modify/delete this code if you don't want this function to be restricted by the allow list.
stateDB := accessibleState.GetStateDB()
// Verify that the caller is in the allow list and therefore has the right to call this function.
callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller)
if !callerStatus.IsEnabled() {
return nil, remainingGas, fmt.Errorf("%w: %s", Err{{$contract.Type}}CannotFallback, caller)
}
// allow list code ends here.
{{- end}}
// CUSTOM CODE STARTS HERE
// Fallback can return data in output.
// The returned data will not be ABI-encoded.
// Instead it will be returned without modifications (not even padding).
output := []byte{}
// return raw output
return output, remainingGas, nil
}
{{- end}}
{{- end}}
// create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for ContractAddress.{{end}}
func create{{.Contract.Type}}Precompile() contract.StatefulPrecompiledContract {
var functions []*contract.StatefulPrecompileFunction
{{- if .Contract.AllowList}}
functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...)
{{- end}}
abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{
{{- range .Contract.Funcs}}
"{{.Original.Name}}": {{decapitalise .Normalized.Name}},
{{- end}}
}
for name, function := range abiFunctionMap {
method, ok := {{$contract.Type}}ABI.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))
}
{{- if .Contract.Fallback}}
// Construct the contract with the fallback function.
statefulContract, err := contract.NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions)
if err != nil {
panic(err)
}
return statefulContract
{{- else}}
// Construct the contract with no fallback function.
statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions)
if err != nil {
panic(err)
}
return statefulContract
{{- end}}
}
`