Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 15 additions & 61 deletions accounts/abi/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const (
readAllowListFuncKey = "readAllowList"
)

type BindHook func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (data interface{}, templateSource string, err error)

// Lang is a target programming language selector to generate bindings for.
type Lang int

Expand Down Expand Up @@ -101,7 +103,11 @@ func isKeyWord(arg string) bool {
// to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention opposed to having to
// manually maintain hard coded strings that break on runtime.
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, isPrecompile bool) (string, error) {
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
return bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, nil)
}

func bindHelper(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, bindHook BindHook) (string, error) {
var (
// contracts is the map of each individual contract requested binding
contracts = make(map[string]*tmplContract)
Expand Down Expand Up @@ -155,7 +161,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))

// Ensure there is no duplicated identifier
var identifiers = callIdentifiers
identifiers := callIdentifiers
if !original.IsConstant() {
identifiers = transactIdentifiers
}
Expand All @@ -178,11 +184,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
copy(normalized.Outputs, original.Outputs)
for j, output := range normalized.Outputs {
if isPrecompile {
if output.Name == "" {
return "", fmt.Errorf("ABI outputs for %s require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", normalized.Name)
}
}
if output.Name != "" {
normalized.Outputs[j].Name = capitalise(output.Name)
}
Expand Down Expand Up @@ -291,19 +292,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
templateSource string
)

// Generate the contract template data according to contract type (precompile/non)
if isPrecompile {
if lang != LangGo {
return "", errors.New("only GoLang binding for precompiled contracts is supported yet")
}

if len(contracts) != 1 {
return "", errors.New("cannot generate more than 1 contract")
// Generate the contract template data according to hook
if bindHook != nil {
var err error
data, templateSource, err = bindHook(lang, types, contracts, structs)
if err != nil {
return "", err
}
precompileType := types[0]
firstContract := contracts[precompileType]
data, templateSource = createPrecompileDataAndTemplate(firstContract, structs)
} else {
} else { // default to generate contract binding
templateSource = tmplSource[lang]
data = &tmplData{
Package: pkg,
Expand Down Expand Up @@ -710,45 +706,3 @@ func hasStruct(t abi.Type) bool {
return false
}
}

func createPrecompileDataAndTemplate(contract *tmplContract, structs map[string]*tmplStruct) (interface{}, string) {
funcs := make(map[string]*tmplMethod)

for k, v := range contract.Transacts {
funcs[k] = v
}

for k, v := range contract.Calls {
funcs[k] = v
}
isAllowList := allowListEnabled(funcs)
if isAllowList {
// remove these functions as we will directly inherit AllowList
delete(funcs, readAllowListFuncKey)
delete(funcs, setAdminFuncKey)
delete(funcs, setEnabledFuncKey)
delete(funcs, setNoneFuncKey)
}

precompileContract := &tmplPrecompileContract{
tmplContract: contract,
AllowList: isAllowList,
Funcs: funcs,
}

data := &tmplPrecompileData{
Contract: precompileContract,
Structs: structs,
}
return data, tmplSourcePrecompileGo
}

func allowListEnabled(funcs map[string]*tmplMethod) bool {
keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey}
for _, key := range keys {
if _, ok := funcs[key]; !ok {
return false
}
}
return true
}
4 changes: 2 additions & 2 deletions accounts/abi/bind/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2099,7 +2099,7 @@ func golangBindings(t *testing.T, overload bool) {
types = []string{tt.name}
}
// Generate the binding and create a Go source file in the workspace
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, false)
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases)
if err != nil {
t.Fatalf("test %d: failed to generate binding: %v", i, err)
}
Expand Down Expand Up @@ -2529,7 +2529,7 @@ public class Test {
},
}
for i, c := range cases {
binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil, false)
binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil)
if err != nil {
t.Fatalf("test %d: failed to generate binding: %v", i, err)
}
Expand Down
109 changes: 109 additions & 0 deletions accounts/abi/bind/precompile_bind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// (c) 2019-2020, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

// Package bind generates Ethereum contract Go bindings.
//
// Detailed usage document and tutorial available on the go-ethereum Wiki page:
// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
package bind

import (
"errors"
"fmt"
)

func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, abifilename string) (string, error) {
// create hook
var createPrecompileFunc BindHook = func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (interface{}, string, error) {
// verify first
if lang != LangGo {
return nil, "", errors.New("only GoLang binding for precompiled contracts is supported yet")
}

if len(types) != 1 {
return nil, "", errors.New("cannot generate more than 1 contract")
}
funcs := make(map[string]*tmplMethod)

contract := contracts[types[0]]

for k, v := range contract.Transacts {
if err := checkOutputName(*v); err != nil {
return nil, "", err
}
funcs[k] = v
}

for k, v := range contract.Calls {
if err := checkOutputName(*v); err != nil {
return nil, "", err
}
funcs[k] = v
}
isAllowList := allowListEnabled(funcs)
if isAllowList {
// remove these functions as we will directly inherit AllowList
delete(funcs, readAllowListFuncKey)
delete(funcs, setAdminFuncKey)
delete(funcs, setEnabledFuncKey)
delete(funcs, setNoneFuncKey)
}

precompileContract := &tmplPrecompileContract{
tmplContract: contract,
AllowList: isAllowList,
Funcs: funcs,
ABIFilename: abifilename,
}

data := &tmplPrecompileData{
Contract: precompileContract,
Structs: structs,
}
return data, tmplSourcePrecompileGo, nil
}

return bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, createPrecompileFunc)
}

func allowListEnabled(funcs map[string]*tmplMethod) bool {
keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey}
for _, key := range keys {
if _, ok := funcs[key]; !ok {
return false
}
}
return true
}

func checkOutputName(method tmplMethod) error {
for _, output := range method.Original.Outputs {
if output.Name == "" {
return fmt.Errorf("ABI outputs for %s require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", method.Original.Name)
}
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var bindFailedTests = []struct {
{"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]}
]
`},
"ABI outputs for AnonOutput require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs",
"ABI outputs for anonOutput require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs",
nil,
nil,
nil,
Expand All @@ -64,7 +64,7 @@ var bindFailedTests = []struct {
{"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]}
]
`},
"ABI outputs for AnonOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs",
"ABI outputs for anonOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs",
nil,
nil,
nil,
Expand All @@ -79,7 +79,7 @@ var bindFailedTests = []struct {
{"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]}
]
`},
"ABI outputs for MixedOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs",
"ABI outputs for mixedOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs",
nil,
nil,
nil,
Expand All @@ -96,7 +96,7 @@ func golangBindingsFailure(t *testing.T) {
for i, tt := range bindFailedTests {
t.Run(tt.name, func(t *testing.T) {
// Generate the binding
_, err := Bind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, true)
_, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, "")
if err == nil {
t.Fatalf("test %d: no error occurred but was expected", i)
}
Expand Down
17 changes: 12 additions & 5 deletions accounts/abi/bind/precompile_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ type tmplPrecompileData struct {
// tmplPrecompileContract contains the data needed to generate an individual contract binding.
type tmplPrecompileContract struct {
*tmplContract
AllowList bool // Indicator whether the contract uses AllowList precompile
Funcs map[string]*tmplMethod // Contract functions that include both Calls + Transacts in tmplContract
AllowList bool // Indicator whether the contract uses AllowList precompile
Funcs map[string]*tmplMethod // Contract functions that include both Calls + Transacts in tmplContract
ABIFilename string // Path to the ABI file
}

// tmplSourcePrecompileGo is the Go precompiled source template.
Expand Down Expand Up @@ -53,6 +54,8 @@ import (
"github.com/ava-labs/subnet-evm/accounts/abi"
"github.com/ava-labs/subnet-evm/vmerrs"

_ "embed"

"github.com/ethereum/go-ethereum/common"
)

Expand All @@ -63,9 +66,6 @@ const (
{{- if .Contract.Fallback}}
{{.Contract.Type}}FallbackGasCost uint64 = 0 // SET A GAS COST LESS THAN 2300 HERE
{{- end}}

// {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract.
{{.Contract.Type}}RawABI = "{{.Contract.InputABI}}"
)

// CUSTOM CODE STARTS HERE
Expand Down Expand Up @@ -94,6 +94,13 @@ var (
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 abi.ABI // will be initialized by init function

{{.Contract.Type}}Precompile StatefulPrecompiledContract // will be initialized by init function
Expand Down
2 changes: 1 addition & 1 deletion cmd/abigen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func abigen(c *cli.Context) error {
}
}
// Generate the contract binding
code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases, false)
code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases)
if err != nil {
utils.Fatalf("Failed to generate ABI binding: %v", err)
}
Expand Down
24 changes: 19 additions & 5 deletions cmd/precompilegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,35 @@ func precompilegen(c *cli.Context) error {
kind = strings.TrimSpace(kind)
}
types = append(types, kind)

outFlagSet := c.IsSet(outFlag.Name)
outFlag := c.String(outFlag.Name)
abifilename := ""
abipath := ""
// we should not generate the abi file if output is set to stdout
if outFlagSet {
// get file name from the output path
pathNoExt := strings.TrimSuffix(outFlag, filepath.Ext(outFlag))
abipath = pathNoExt + ".abi"
abifilename = filepath.Base(abipath)
}
// Generate the contract precompile
code, err := bind.Bind(types, abis, bins, sigs, pkg, lang, libs, aliases, true)
code, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename)
if err != nil {
utils.Fatalf("Failed to generate ABI precompile: %v", err)
}

// Either flush it out to a file or display on the standard output
if !c.IsSet(outFlag.Name) {
if !outFlagSet {
fmt.Printf("%s\n", code)
return nil
}

if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0o600); err != nil {
utils.Fatalf("Failed to write ABI precompile: %v", err)
if err := os.WriteFile(outFlag, []byte(code), 0o600); err != nil {
utils.Fatalf("Failed to write generated precompile: %v", err)
}

if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil {
utils.Fatalf("Failed to write ABI: %v", err)
}

fmt.Println("Precompile Generation was a success!")
Expand Down
1 change: 1 addition & 0 deletions precompile/reward_manager.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Loading