Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate precompile tests #565

Merged
merged 23 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1d0c877
add verify allow list tests
ceyonur Mar 2, 2023
bea4567
use new verify allow list suite in precompiles
ceyonur Mar 2, 2023
2a03da6
add equal test suite
ceyonur Mar 2, 2023
742d8fe
Merge branch 'master' into config-test-suites
ceyonur Mar 2, 2023
587e07a
Merge branch 'master' into config-test-suites
ceyonur Mar 2, 2023
921fd57
nits
ceyonur Mar 2, 2023
71cf7ae
Merge branch 'config-test-suites' of github.com:ava-labs/subnet-evm i…
ceyonur Mar 2, 2023
e3b1e32
add cnfig test template
ceyonur Mar 6, 2023
5f1067a
add contract unit test generation
ceyonur Mar 6, 2023
a029ff8
Remove abis
ceyonur Mar 6, 2023
468fbda
Merge branch 'master' into config-test-suites
ceyonur Mar 7, 2023
f93be0e
nits
ceyonur Mar 7, 2023
b463f44
Merge branch 'config-test-suites' into generate-precompile-tests
ceyonur Mar 7, 2023
1ad8e4e
Merge branch 'master' into generate-precompile-tests
ceyonur Apr 28, 2023
21ab80e
fix testing input in template
ceyonur Apr 28, 2023
983f5ed
Merge branch 'master' into generate-precompile-tests
ceyonur Apr 28, 2023
1506ebb
Update accounts/abi/bind/precompilebind/precompile_contract_test_temp…
ceyonur Apr 28, 2023
b81fdfb
Update accounts/abi/bind/precompilebind/precompile_contract_test_temp…
ceyonur Apr 28, 2023
1c498d0
Update accounts/abi/bind/precompilebind/precompile_contract_test_temp…
ceyonur Apr 28, 2023
d8f22bc
Merge branch 'master' into generate-precompile-tests
ceyonur May 1, 2023
c9de2c4
Merge branch 'master' into generate-precompile-tests
aaronbuchwald May 1, 2023
ac3fedb
fix non-allowlist tests
ceyonur May 1, 2023
4c069cb
Merge branch 'generate-precompile-tests' of github.com:ava-labs/subne…
ceyonur May 1, 2023
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
5 changes: 5 additions & 0 deletions accounts/abi/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s
"capitalise": capitalise,
"decapitalise": decapitalise,
"convertToNil": convertToNil,
"mkList": mkList,
}

// render the template
Expand Down Expand Up @@ -700,3 +701,7 @@ func hasStruct(t abi.Type) bool {
return false
}
}

func mkList(args ...interface{}) []interface{} {
return args
}
42 changes: 37 additions & 5 deletions accounts/abi/bind/precompilebind/precompile_bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,58 @@ const (
readAllowListFuncKey = "readAllowList"
)

// BindedFiles contains the generated binding file contents.
// This is used to return the contents in a expandable way.
type BindedFiles struct {
darioush marked this conversation as resolved.
Show resolved Hide resolved
Contract string
Config string
Module string
ConfigTest string
ContractTest string
}

// PrecompileBind generates a Go binding for a precompiled contract. It returns config binding and contract binding.
func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang bind.Lang, libs map[string]string, aliases map[string]string, abifilename string) (string, string, string, error) {
func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang bind.Lang, libs map[string]string, aliases map[string]string, abifilename string, generateTests bool) (BindedFiles, error) {
// create hooks
configHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigGo)
contractHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractGo)
moduleHook := createPrecompileHook(abifilename, tmplSourcePrecompileModuleGo)
configTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigTestGo)
contractTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractTestGo)

configBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook)
if err != nil {
return "", "", "", fmt.Errorf("failed to generate config binding: %w", err)
return BindedFiles{}, fmt.Errorf("failed to generate config binding: %w", err)
}
contractBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook)
if err != nil {
return "", "", "", fmt.Errorf("failed to generate contract binding: %w", err)
return BindedFiles{}, fmt.Errorf("failed to generate contract binding: %w", err)
}
moduleBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, moduleHook)
if err != nil {
return "", "", "", fmt.Errorf("failed to generate module binding: %w", err)
return BindedFiles{}, fmt.Errorf("failed to generate module binding: %w", err)
}
bindedFiles := BindedFiles{
Contract: contractBind,
Config: configBind,
Module: moduleBind,
}
return configBind, contractBind, moduleBind, nil

if generateTests {
configTestBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configTestHook)
if err != nil {
return BindedFiles{}, fmt.Errorf("failed to generate config test binding: %w", err)
}
bindedFiles.ConfigTest = configTestBind

contractTestBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractTestHook)
if err != nil {
return BindedFiles{}, fmt.Errorf("failed to generate contract test binding: %w", err)
}
bindedFiles.ContractTest = contractTestBind
}

return bindedFiles, nil
}

// createPrecompileHook creates a bind hook for precompiled contracts.
Expand Down
2 changes: 1 addition & 1 deletion accounts/abi/bind/precompilebind/precompile_bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func golangBindingsFailure(t *testing.T) {
for i, tt := range bindFailedTests {
t.Run(tt.name, func(t *testing.T) {
// Generate the binding
_, _, _, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", bind.LangGo, tt.libs, tt.aliases, "")
_, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", bind.LangGo, tt.libs, tt.aliases, "", true)
if err == nil {
t.Fatalf("test %d: no error occurred but was expected", i)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// (c) 2019-2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package precompilebind

// tmplSourcePrecompileConfigGo is the Go precompiled config source template.
const tmplSourcePrecompileConfigTestGo = `
// Code generated
// This file is a generated precompile config test with the skeleton of test functions.
// The file is generated by a template. Please inspect every code and comment in this file before use.

package {{.Package}}

import (
"math/big"
"testing"

"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"github.com/ava-labs/subnet-evm/precompile/testutils"
{{- if .Contract.AllowList}}
"github.com/ava-labs/subnet-evm/precompile/allowlist"

"github.com/ethereum/go-ethereum/common"
{{- end}}
)

// TestVerify tests the verification of Config.
func TestVerify(t *testing.T) {
{{- if .Contract.AllowList}}
admins := []common.Address{allowlist.TestAdminAddr}
enableds := []common.Address{allowlist.TestEnabledAddr}
{{- end}}
tests := map[string]testutils.ConfigVerifyTest{
"valid config": {
Config: NewConfig(big.NewInt(3){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
ExpectedError: "",
},
// CUSTOM CODE STARTS HERE
// Add your own Verify tests here, e.g.:
// "your custom test name": {
// Config: NewConfig(big.NewInt(3), {{- if .Contract.AllowList}} admins, enableds{{- end}}),
// ExpectedError: ErrYourCustomError.Error(),
// },
}
{{- if .Contract.AllowList}}
// Verify the precompile with the allowlist.
// This adds allowlist verify tests to your custom tests
// and runs them all together.
// Even if you don't add any custom tests, keep this. This will still
// run the default allowlist verify tests.
allowlist.VerifyPrecompileWithAllowListTests(t, Module, tests)
{{- else}}
// Run verify tests.
testutils.RunVerifyTests(t, tests)
{{- end}}
}

// TestEqual tests the equality of Config with other precompile configs.
func TestEqual(t *testing.T) {
{{- if .Contract.AllowList}}
admins := []common.Address{allowlist.TestAdminAddr}
enableds := []common.Address{allowlist.TestEnabledAddr}
{{- end}}
tests := map[string]testutils.ConfigEqualTest{
"non-nil config and nil other": {
Config: NewConfig(big.NewInt(3){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
Other: nil,
Expected: false,
},
"different type": {
Config: NewConfig(big.NewInt(3){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
Other: precompileconfig.NewNoopStatefulPrecompileConfig(),
Expected: false,
},
"different timestamp": {
Config: NewConfig(big.NewInt(3){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
Other: NewConfig(big.NewInt(4){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
Expected: false,
},
"same config": {
Config: NewConfig(big.NewInt(3){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
Other: NewConfig(big.NewInt(3){{- if .Contract.AllowList}}, admins, enableds{{- end}}),
Expected: true,
},
// CUSTOM CODE STARTS HERE
// Add your own Equal tests here
}
{{- if .Contract.AllowList}}
// Run allow list equal tests.
// This adds allowlist equal tests to your custom tests
// and runs them all together.
// Even if you don't add any custom tests, keep this. This will still
// run the default allowlist equal tests.
allowlist.EqualPrecompileWithAllowListTests(t, Module, tests)
{{- else}}
// Run equal tests.
testutils.RunEqualTests(t, tests)
{{- end}}
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import (
)
{{$contract := .Contract}}
const (
// Gas costs for each function. These are set to 0 by default.
// 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.
Expand All @@ -54,10 +54,10 @@ const (
// You should also increase gas costs of functions that read from AllowList storage.
{{- end}}
{{- range .Contract.Funcs}}
{{.Normalized.Name}}GasCost uint64 = 0 {{if not .Original.IsConstant | and $contract.AllowList}} + allowlist.ReadAllowListGasCost {{end}} // SET A GAS COST HERE
{{.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 = 0 // SET A GAS COST LESS THAN 2300 HERE
{{.Contract.Type}}FallbackGasCost uint64 = 1 // SET A GAS COST LESS THAN 2300 HERE
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
{{- end}}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// (c) 2019-2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package precompilebind

// tmplSourcePrecompileConfigGo is the Go precompiled config source template.
const tmplSourcePrecompileContractTestGo = `
// Code generated
// This file is a generated precompile contract test with the skeleton of test functions.
// The file is generated by a template. Please inspect every code and comment in this file before use.

package {{.Package}}

import (
"testing"

"github.com/ava-labs/subnet-evm/core/state"
{{- if .Contract.AllowList}}
"github.com/ava-labs/subnet-evm/precompile/allowlist"
{{- end}}
"github.com/ava-labs/subnet-evm/precompile/testutils"
"github.com/ava-labs/subnet-evm/vmerrs"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

// TestRun tests the Run function of the precompile contract.
// These tests are run against the precompile contract directly with
// the given input and expected output. They're just a guide to
// help you write your own tests. These test are for general cases like
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
// allowlist, readOnly behaviour and gas cost. You should write your own
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
// tests for specific cases.
func TestRun(t *testing.T) {
tests := map[string]testutils.PrecompileTest{
{{- $contract := .Contract}}
{{- $structs := .Structs}}
{{- range .Contract.Funcs}}
{{- $func := .}}
{{- if $contract.AllowList}}
{{- $roles := mkList "NoRole" "Enabled" "Admin"}}
{{- range $role := $roles}}
{{- $fail := and (not $func.Original.IsConstant) (eq $role "NoRole")}}
"calling {{decapitalise $func.Normalized.Name}} from {{$role}} should {{- if $fail}} fail {{- else}} succeed{{- end}}": {
Caller: allowlist.Test{{$role}}Addr,
BeforeHook: allowlist.SetDefaultRoles(Module.Address),
InputFn: func(t testing.TB) []byte {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
{{- if len $func.Normalized.Inputs | lt 1}}
// CUSTOM CODE STARTS HERE
// populate test input here
testInput := {{capitalise $func.Normalized.Name}}Input{}
input, err := Pack{{$func.Normalized.Name}}(testInput)
{{- else if len $func.Normalized.Inputs | eq 1 }}
{{- $input := index $func.Normalized.Inputs 0}}
// CUSTOM CODE STARTS HERE
// set test input to a value here
var testInput {{bindtype $input.Type $structs}}
input, err := Pack{{$func.Normalized.Name}}(testInput)
{{- else}}
input, err := Pack{{$func.Normalized.Name}}()
{{- end}}
require.NoError(t, err)
return input
},
{{- if not $fail}}
// This test is for a successful call. You can set the expected output here.
// CUSTOM CODE STARTS HERE
ExpectedRes: []byte{},
{{- end}}
SuppliedGas: {{$func.Normalized.Name}}GasCost,
ReadOnly: false,
ExpectedErr: {{if $fail}} ErrCannot{{$func.Normalized.Name}}.Error() {{- else}} "" {{- end}},
},
{{- end}}
{{- end}}
{{- if not $func.Original.IsConstant}}
"readOnly {{decapitalise $func.Normalized.Name}} should fail": {
Caller: common.Address{1},
InputFn: func(t testing.TB) []byte {
{{- if len $func.Normalized.Inputs | lt 1}}
// CUSTOM CODE STARTS HERE
// populate test input here
testInput := {{capitalise $func.Normalized.Name}}Input{}
input, err := Pack{{$func.Normalized.Name}}(testInput)
{{- else if len $func.Normalized.Inputs | eq 1 }}
{{- $input := index $func.Normalized.Inputs 0}}
// CUSTOM CODE STARTS HERE
// set test input to a value here
var testInput {{bindtype $input.Type $structs}}
input, err := Pack{{$func.Normalized.Name}}(testInput)
{{- else}}
input, err := Pack{{$func.Normalized.Name}}()
{{- end}}
require.NoError(t, err)
return input
},
SuppliedGas: {{$func.Normalized.Name}}GasCost,
ReadOnly: true,
ExpectedErr: vmerrs.ErrWriteProtection.Error(),
},
{{- end}}
"insufficient gas for {{decapitalise $func.Normalized.Name}} should fail": {
Caller: common.Address{1},
InputFn: func(t testing.TB) []byte {
{{- if len $func.Normalized.Inputs | lt 1}}
// CUSTOM CODE STARTS HERE
// populate test input here
testInput := {{capitalise $func.Normalized.Name}}Input{}
input, err := Pack{{$func.Normalized.Name}}(testInput)
{{- else if len $func.Normalized.Inputs | eq 1 }}
{{- $input := index $func.Normalized.Inputs 0}}
// CUSTOM CODE STARTS HERE
// set test input to a value here
var testInput {{bindtype $input.Type $structs}}
input, err := Pack{{$func.Normalized.Name}}(testInput)
{{- else}}
input, err := Pack{{$func.Normalized.Name}}()
{{- end}}
require.NoError(t, err)
return input
},
SuppliedGas: {{$func.Normalized.Name}}GasCost - 1,
ReadOnly: false,
ExpectedErr: vmerrs.ErrOutOfGas.Error(),
},
{{- end}}
}
{{- if .Contract.AllowList}}
// Run tests with allow list tests.
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
// This adds allowlist run tests to your custom tests
// and runs them all together.
// Even if you don't add any custom tests, keep this. This will still
// run the default allowlist tests.
allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests)
{{- else}}
// Run tests.
for name, test := range tests {
t.Run(name, func(t *testing.T) {
test.Run(t, module, state.NewTestStateDB(t *testing.T))
})
}
{{- end}}
}
`
Loading